SpringCloud
SpringCloud
文章推荐:Eureka:Spring Cloud服务注册与发现组件(非常详细) (biancheng.net)
概述
Spring Cloud 是一个服务治理平台,是若干个框架的集合,提供了全套的分布式系统解决方案。包含了:服务注册与发现、配置中心、服务网关、智能路由、负载均衡、断路器、监控跟踪、分布式消息队列等等。
Spring Cloud 通过 Spring Boot 风格的封装,屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、容易部署的分布式系统开发工具包。开发者可以快速的启动服务或构建应用、同时能够快速和云平台资源进行对接。微服务是可以独立部署、水平扩展、独立访问(或者有独立的数据库)的服务单元
,Spring Cloud 就是这些微服务的大管家,采用了微服务这种架构之后,项目的数量会非常多,Spring Cloud 做为大管家需要管理好这些微服务,自然需要很多小弟来帮忙。
SpringCloud常用组件表
服务的注册和发现(eureka,nacos,consul)
服务的负载均衡(ribbon,dubbo)
服务的相互调用(openFeign,dubbo)
服务的容错(hystrix,sentinel)
服务网关(gateway,zuul)
服务配置的统一管理(config-server,nacos,apollo)
服务消息总线(bus)
服务安全组件(security,Oauth2.0)
服务监控(admin)(jvm)
链路追综(sleuth+zipkin)
SpringCloud Alibaba与SpringCloud Netflix对照
注册和发现中心
Eureka快速入门
什么是CAP原则?
Eureka和zookeeper的区别
CAP原则是指一个分布式系统中,一致性,可用性,分区容错性
一致性:多个节点的数据保持一致。 (consistent)
可用性:当一个节点发生异常不可用之后,其他的节点任然可以提供服务。 available
分区容错性:由于每个节点存在的机房或者是分区不一样,存在数据传输时间的消耗,所以每个节点上的数据可能会短暂的不一致。 partition
CAP原则指的是,这三个要素最多只能同时存在实现两个,不可能三者兼顾。但是每一个分布式系统中都会存在P原则,所以通常只会出现CP和AP组合。
Zookeeper:CP 注重的数据的一致性,当节点发生异常不可用时,可能会造成几分钟无法访问
Eureka:AP 注重的时可可用,但是可能用户访问的数据存在一定的差异。
Eureka快速入门
搭建一个组测中心
客户端负载均衡
相较于服务端负载均衡,客户端服务在均衡则是一个比较小众的概念。
客户端负载均衡的工作原理如下图。
客户端负载均衡是将负载均衡逻辑以代码的形式
封装到客户端上,即负载均衡器位于客户端。客户端通过服务注册中心(例如 Eureka Server)获取到一份服务端提供的可用服务清单。有了服务清单后,负载均衡器会在客户端发送请求前通过负载均衡算法选择一个服务端实例再进行访问,以达到负载均衡的目的;
客户端负载均衡具有以下特点:
- 负载均衡器位于客户端,不需要单独搭建一个负载均衡服务器。
- 负载均衡是在客户端发送请求前进行的,因此客户端清楚地知道是哪个服务端提供的服务。
- 客户端都维护了一份可用服务清单,而这份清单都是从服务注册中心获取的。
Ribbon入门
<!--Spring Cloud Ribbon 依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
使用Ribbon+RestTemplate
@Bean //将 RestTemplate 注入到容器中
@LoadBalanced //在客户端使用 RestTemplate 请求服务端时,开启负载均衡(Ribbon)
public RestTemplate restTemplate() {
return new RestTemplate();
}
使用LoadBalancerClient
1.注入LoadBalancerClient
@Autowired
private LoadBalancerClient loadBalancerClient;
2.调用loadBalancerClient或者加了 @LoadBalanced
注解的RestTemplate
@GetMapping("/ribbon")
public URI ribbon(){
// 通过地址自动负载均衡
ServiceInstance choose = loadBalancerClient.choose("provider");
return choose.getUri();
}
在consumer发送发送请求给provider时只需要将原来的URL中的IP使用应用名称来代替。
Ribbon为我们所做的事情
- 将我们的请求拦截。获取当中的服务名称。
- 通过服务名称结合Eureka的服务发现,获取到服务列表
- 通过服务列表使用负载均衡算法,获取相应的ip和port
- 重构URL
- 发送Http请求
Ribbon 实现负载均衡
Ribbon 是一个客户端的负载均衡器,它可以与 Eureka 配合使用轻松地实现客户端的负载均衡。Ribbon 会先从 Eureka Server(服务注册中心)去获取服务端列表,然后通过负载均衡策略将请求分摊给多个服务端,从而达到负载均衡的目的。
Spring Cloud Ribbon 提供了一个 IRule 接口,该接口主要用来定义负载均衡策略,它有 7 个默认实现类,每一个实现类都是一种负载均衡策略。
序号 | 实现类 | 负载均衡策略 |
---|---|---|
1 | RoundRobinRule | 按照线性轮询策略,即按照一定的顺序依次选取服务实例 |
2 | RandomRule | 随机选取一个服务实例 |
3 | RetryRule | 按照 RoundRobinRule(轮询)的策略来获取服务,如果获取的服务实例为 null 或已经失效,则在指定的时间之内不断地进行重试(重试时获取服务的策略还是 RoundRobinRule 中定义的策略),如果超过指定时间依然没获取到服务实例则返回 null 。 |
4 | WeightedResponseTimeRule | WeightedResponseTimeRule 是 RoundRobinRule 的一个子类,它对 RoundRobinRule 的功能进行了扩展。 根据平均响应时间,来计算所有服务实例的权重,响应时间越短的服务实例权重越高,被选中的概率越大。刚启动时,如果统计信息不足,则使用线性轮询策略,等信息足够时,再切换到 WeightedResponseTimeRule。 |
5 | BestAvailableRule | 继承自 ClientConfigEnabledRoundRobinRule。先过滤点故障或失效的服务实例,然后再选择并发量最小的服务实例。 |
6 | AvailabilityFilteringRule | 先过滤掉故障或失效的服务实例,然后再选择并发量较小的服务实例。 |
7 | ZoneAvoidanceRule | 默认的负载均衡策略 ,综合判断服务所在区域(zone)的性能和服务(server)的可用性,来选择服务实例。在没有区域的环境下,该策略与轮询(RandomRule)策略类似。 |
探究一些ZoneAvoidanceRule,通过调用父类PredicateBasedRule的Choose函数,服务数量进行一个取模运算。
为了保持线程安全,Ribbon使用CAS,和原子类保证了线程的安全。
private int incrementAndGetModulo(int modulo) {
for (;;) {
int current = nextIndex.get();
int next = (current + 1) % modulo;
if (nextIndex.compareAndSet(current, next) && current < modulo)
return current;
}
}
更改负载均衡算法
指定不同的服务使用不同的负载均衡算法
#访问不同的服务可以使用不同的算法规则
provider: #先写服务提供者的应用名称
rabbion:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule
更改全局的负载均衡算法
@Bean
public IRule myRule(){
return new RandomRule();
}
由于IRule是一个接口,所以我可以通过实现这个接口,自定义自己的负载均衡算法。
OpenFeign:Spring Cloud声明式服务调用组件
OpenFeign是一个Web声明式的Http客户端调用工具,提供接口和注解形式调用
OpenFeign是一个声明式RESTful网络请求客户端。OpenFeign会根据带有注解的函数信息构建出网络请求的模板,在发送网络请求之前,OpenFeign会将函数的参数值设置到这些请求模板中。虽然OpenFeign只能支持基于文本的网络请求,但是它可以极大简化网络请求的实现,方便编程人员快速构建自己的网络请求应用
OpenFeign 常用注解
使用 OpenFegin 进行远程服务调用时,常用注解如下表。
注解 | 说明 |
---|---|
@FeignClient | 该注解用于通知 OpenFeign 组件对 @RequestMapping 注解下的接口进行解析,并通过动态代理的方式产生实现类,实现负载均衡和服务调用。 |
@EnableFeignClients | 该注解用于开启 OpenFeign 功能,当 Spring Cloud 应用启动时,OpenFeign 会扫描标有 @FeignClient 注解的接口,生成代理并注册到 Spring 容器中。 |
@RequestMapping | Spring MVC 注解,在 Spring MVC 中使用该注解映射请求,通过它来指定控制器(Controller)可以处理哪些 URL 请求,相当于 Servlet 中 web.xml 的配置。 |
@GetMapping | Spring MVC 注解,用来映射 GET 请求,它是一个组合注解,相当于 @RequestMapping(method = RequestMethod.GET) 。 |
@PostMapping | Spring MVC 注解,用来映射 POST 请求,它是一个组合注解,相当于 @RequestMapping(method = RequestMethod.POST) 。 |
OpenFeign的入门
<!--添加 OpenFeign 依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
开启OpenFeign服务@EnableFeignClients
简单应用
使用OpenFeign,UserServer远程调用OrderServer中DoOrder接口
OrderServer
@RestController
public class OrderController {
@GetMapping("/DoOrder")
public String DoOrder(){
return "用户下了订单";
}
}
在UserServer中需要定义一个接口
@FeignClient(value = "eureka-client-a") //服务名称
@Component
public interface UserOrderFeign {
//需要远程调用服务方法的方法签名
@GetMapping("/DoOrder")
public String DoOrder();
}
@RestController
public class UserDoOrder {
@Autowired
private UserOrderFeign userOrderFeign;
@GetMapping("/UserDoOrder")
public String UserDoOrder(){
return userOrderFeign.DoOrder();
}
}
注意点:如果在我们请求订单模块时,订单模块需要对数据库进行操作,可能会比较的消耗时间。那么我们的UserOrder会不会出现超时异常,通过实验,结果是UserOrder会抛出一个超时异常。
修改OpenFeign远程调用的超时时间
由于OpenFeign的原理是将Ribbon进行一个封装,所以如果我们希望修改OpenFeign的超时时间的话,其实质是修改Ribbon的配置。默认超时时间默认是1S
ribbon:
ReadTimeout: 3000 #访问超时时间
ConnectTimeout: 3000 #连接超时时间
OpenFeign核心探索
OPenFeign使用一个注解就可以实现远程调用是如何做到
可以猜测这个接口一个会议代理对象,我们知道只有两种代理的方式(JDK动态代理,cglib动态代理)
JDK动态代理为接口创建代理实例
,
CGLIB通过继承方式实现代理
所以OPenFeign一定是采用的是JDK动态代理生成代理对象的
@Test
void contextLoads() {
UserOrderFeign o = (UserOrderFeign)Proxy.newProxyInstance(EurekaClientBApplication.class.getClassLoader(), new Class[]{UserOrderFeign.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/*
* 通过代理获取到方法注解上的API名称
* 通过获取接口上方的FeignClient()中的Value,应用的名称,结合上Eureka的方法发现
* 通过拼接URL,使用Ribbon向URL接口发送请求就可以实现远程调用
* */
//获取API
GetMapping annotation = method.getAnnotation(GetMapping.class);
String[] Api = annotation.value();
String API = Api[0];
//获取应用名称
Class<?> aClass = method.getDeclaringClass();
FeignClient feignClient = aClass.getAnnotation(FeignClient.class);
String appName = feignClient.value();
//拼接URL
String url="http://"+appName+"/"+API;
//使用Ribbon发送请求实现负载均衡
String forObject = restTemplate.getForObject(url, String.class);
return forObject;
}
});
//使用JDK动态代理所以一定会调用invoke方法,类似与AOP机制
String s = o.DoOrder();
System.out.println(s);
}
OpenFeign 日志增强
Logger.Level 的具体级别如下:
- NONE:不记录任何信息。
- BASIC:仅记录请求方法、URL 以及响应状态码和执行时间。
- HEADERS:除了记录 BASIC 级别的信息外,还会记录请求和响应的头信息。
- FULL:记录所有请求与响应的明细,包括头信息、请求体、元数据等等。
/**
* Controls the level of logging.
*/
public enum Level {
/**
* No logging.
*/
NONE,
/**
* Log only the request method and URL and the response status code and execution time.
*/
BASIC,
/**
* Log the basic information along with request and response headers.
*/
HEADERS,
/**
* Log the headers, body, and metadata for both requests and responses.
*/
FULL
}
在配置文件中开启接口的日志级别
logging:
level:
#feign 日志以什么样的级别监控该接口
net.biancheng.c.service.DeptFeignService: debug
@Bean
public Logger.Level level(){
return Logger.Level.FULL;
}
Hystrix:Spring Cloud服务熔断与降级组件
服务雪崩
在一个微服务系统中,我们的一个服务可能是一个链式调用的,A->B->C如果C发生了宕机的话,那么A,B中的线程只会在等待超时之后才会将线程回收,在并发量很大的时候,就会出现服务线程无法及时的回收,导致整个服务器出现崩溃。
Hystrix 实现熔断机制
在 Spring Cloud 中,熔断机制是通过 Hystrix 实现的。Hystrix 会监控微服务间调用的状况,当失败调用到一定比例时(例如 5 秒内失败 20 次),就会启动熔断机制。
Hystrix 实现服务熔断的步骤如下:
- 当服务的调用出错率达到或超过 Hystix 规定的比率(默认为 50%)后,熔断器进入熔断开启状态。
- 熔断器进入熔断开启状态后,Hystrix 会启动一个休眠时间窗,在这个时间窗内,该服务的降级逻辑会临时充当业务主逻辑,而原来的业务主逻辑不可用。
- 当有请求再次调用该服务时,会直接调用降级逻辑快速地返回失败响应,以避免系统雪崩。
- 当休眠时间窗到期后,Hystrix 会进入半熔断转态,允许部分请求对服务原来的主业务逻辑进行调用,并监控其调用成功率。
- 如果调用成功率达到预期,则说明服务已恢复正常,Hystrix 进入熔断关闭状态,服务原来的主业务逻辑恢复;否则 Hystrix 重新进入熔断开启状态,休眠时间窗口重新计时,继续重复第 2 到第 5 步。
第一个微服务架构
所以可以猜测一下网关的功能应该需要有哪些
- 路由转发,所有的请求全部需要在网关这通过相应的方式找到具体的API,转发到具体的服务上。
- 安全方面,不需要直接将微服务里面的服务端口暴露出去。
- 负载均衡。一个服务如果有多台机器运行,那么我们需要负载均衡一下,缓解服务器压力。
作用:就是可以实现用户的验证登陆、解决跨域、日志拦截、权限控制、限流熔断、负载均衡、黑名单和白名单机制等。
Zuul与GateWay有那些区别
Zuul网关属于NetFix公司开源框架,属于第一代微服务网关
GateWay属于SpringCloud自己研发的网关框架,属于第二代微服务网关。相比来说GateWay比Zuul网关的性能要好很多。
Zuul 1.0网关底层基于Servlet实现,阻塞式(BIO)api,不支持长连接
Zuul2.0 NIO
SpringBoot-WebSpringCloudGateWay基于Spring5构建,能够实现响应式非阻塞式(NIO)api,支持长连接,能够更好的支持Spring体系产品,依赖SpringBoot-WebFux
springCloud没有集成和支持Zuul2.0
SpringCloudGateway是基于webFlux框架实现的,而webFlux框架底层则使用了高性能的Reactor模式
通信框架的Netty
网关服务的端口号一般多少:80或者443
Spring Cloud Gateway 核心概念
Spring Cloud GateWay 最主要的功能就是路由转发,而在定义转发规则时主要涉及了以下三个核心概念,如下表。
核心概念 | 描述 |
---|---|
Route(路由) | 网关最基本的模块。它由一个 ID、一个目标 URI、一组断言(Predicate)和一组过滤器(Filter)组成。 |
Predicate(断言) | 路由转发的判断条件,我们可以通过 Predicate 对 HTTP 请求进行匹配,例如请求方式、请求路径、请求头、参数等,如果请求与断言匹配成功,则将请求转发到相应的服务。 |
Filter(过滤器) | 过滤器,我们可以使用它对请求进行拦截和修改,还可以使用它对上文的响应进行再处理。 |
Gateway 的工作流程
Spring Cloud Gateway 工作流程如下图。
Spring Cloud Gateway 工作流程说明如下:
- 客户端将请求发送到 Spring Cloud Gateway 上。
- Spring Cloud Gateway 通过 Gateway Handler Mapping 找到与请求相匹配的路由,将其发送给 Gateway Web Handler。
- Gateway Web Handler 通过指定的过滤器链(Filter Chain),将请求转发到实际的服务节点中,执行业务逻辑返回响应结果。
- 过滤器之间用虚线分开是因为过滤器可能会在转发请求之前(pre)或之后(post)执行业务逻辑。
- 过滤器(Filter)可以在请求被转发到服务端前,对请求进行拦截和修改,例如参数校验、权限校验、流量监控、日志输出以及协议转换等。
- 过滤器可以在响应返回客户端之前,对响应进行拦截和再处理,例如修改响应内容或响应头、日志输出、流量监控等。
- 响应原路返回给客户端。
Nginx和Gateway区别
实现:
package com.zl.config;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import reactor.core.publisher.Mono;
@Configuration
public class RequestLimited {
@Bean
@Primary //作为主选方案
public KeyResolver ipKeyResolver(){
// 通过对IP进行限制
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostString());
}
@Bean
public KeyResolver apiKeyResolver(){
return exchange -> Mono.just(exchange.getRequest().getPath().toString());
}
}
配置针对的API做限流
spring:
application:
name: gateway-server
cloud:
gateway:
enabled: true #只要添加了依赖默认开启
routes:
- id: user-server-route
uri: lb://user-service
predicates:
- Path=/UserDoOrder
filters:
- name: RequestRateLimiter
args:
# 用于限流的键的解析器的 Bean 对象的名字。它使用 SpEL 表达式根据#{@beanName}从 Spring 容器中获取 Bean 对象。
key-resolver: "#{@ipKeyResolver}"
# 令牌桶每秒填充平均速率,即行等价于允许用户每秒处理多少个请求平均数
redis-rate-limiter.replenishRate: 1
# 令牌桶的容量,允许在一秒钟内完成的最大请求数
redis-rate-limiter.burstCapacity: 2
gateweay进行跨域
package com.zl.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.util.pattern.PathPatternParser;
@Configuration
public class CorsConfig {
@Bean
public CorsWebFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedMethod("*");//允许所有请求头
config.addAllowedOrigin("*");//允许所有请求方法,例如get,post等
config.addAllowedHeader("*");//允许所有的请求来源
config.setAllowCredentials(true);//允许携带cookie
UrlBasedCorsConfigurationSource source= new UrlBasedCorsConfigurationSource(new PathPatternParser());
source.registerCorsConfiguration("/**", config);//对所有经过网关的请求都生效
return new CorsWebFilter(source);
}
}