这篇笔记承接之前的服务注册与调用篇 https://blog.csdn.net/qq_28356977/article/details/126454940?spm=1001.2014.3001.5501
1. Ribbon负载均衡服务调用
官网:https://github.com/Netflix/ribbon/wiki/Getting-Started
我们先把环境恢复到之前Eureka的时候,服务器是7001和7002,支付微服务提供者是8001与8002,80是微服务的消费者。
先来简单地回顾一下环境
1.1Ribbon的简介
注:ribbon已经进入维护状态,未来可能会由spring cloud Loadbalancer替代
a)ribbon是什么? Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端(80) 负载均衡的工具。 简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法。
b)能干嘛
一句话:负载均衡+RestTemplate调用
ribbon与nginx的区别: nginx是多个终端请求微服务,ngnix把请求分散到对应的服务器。ribbon是把服务器列表获取,本地随机选择服务器做均衡
1.2ribbon负载均衡的调用和RestTemplate的二次理解
Ribbon其实就是一个软负载均衡的客户端组件, 他可以和其他所需请求的客户端结合使用,和eureka结合只是其中的一个实例。
Ribbon在工作时分成两步 第一步:先选择 EurekaServer ,它优先选择在同一个区域内负载较少的server. 第二步:再根据用户指定的策略,在从server取到的服务注册列表中选择一个地址。 其中Ribbon提供了多种策略:比如轮询、随机和根据响应时间加权。
(1)pom文件
我们之前,使用在RestTemplate配置类上的注解@LoadBalanced时,已经实现了轮询,但是我们并没有引入ribbon相关的依赖,这是为什么呢?原来新版的Eureka已经自带了ribbon。
我们点开依赖,发现Eureka已经自带了
如果不放心,也可以自己引入(但没必要)
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
(2)二说RestTemplate的使用
a)getForObject方法/getForEntity方法 getForObject:返回对象为响应体中数据转化成的对象,基本上可以理解为Json getForEntity:返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头、响应状态码、响应体等
@GetMapping("/consumer/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){
return restTemplate.getForObject(PAYMENT_URL+"/payment/get/"+id,CommonResult.class);
}
@GetMapping("/consumer/payment/getForEntity/{id}")
public CommonResult<Payment> getPaymentById2(@PathVariable("id") Long id){
ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_URL+"/payment/get/"+id,CommonResult.class);
if (entity.getStatusCode().is2xxSuccessful()){
log.info(entity.getStatusCode()+"\t"+"entity中可以获取更详细的信息");
return entity.getBody();
}else {
return new CommonResult<>(444,"操作失败!");
}
}
b)postForObject/postForEntity
前面的get开头的方法,我们的80消费者转发给微服务提供者后,会被微服务提供者的GetMapping标注的方法处理,而用post开头的方法,就会被微服务提供者被PostMapping标注的方法处理
参数一:这个参数是请求的url路径 参数二:请求的body 这个参数需要再controller类用 @RequestBody 注解接收 参数三:接收响应体的类型
1.3Ribbon核心组件IRule(负载规则)
IRule:根据特定算法中从服务列表中选取一个要访问的服务,默认是轮询
a)如何替换负载规则 注意细节,自定义配置类不能放在@ComponentScan(这个在springboot中学过,@SpringBootApplication是一个注解)所扫描的当前包下以及子包下,否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的了。
1)创建一个package
2)上面包下新建MySelfRule规则类
@Configuration
public class MySelfRule {
@Bean
public IRule myRule()
{
return new RandomRule();
}
}
3)主启动类添加@RibbonClient 4)测试 发现已经从轮询变成了随机
1.4负载均衡算法的原理(以轮询为例)
刚刚我们已经学习了如何替换负载规则,那么接下来我们就要学习原理了
a)原理
负载均衡算法:rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标 ,每次服务重启动后rest接口计数从1开始。
List instances = discoveryClient.getInstances(“CLOUD-PAYMENT-SERVICE”);
如: List [0] instances = 127.0.0.1:8002 List [1] instances = 127.0.0.1:8001
8001+ 8002 组合成为集群,它们共计2台机器,集群总数为2, 按照轮询算法原理:
当总请求数为1时: 1 % 2 =1 对应下标位置为1 ,则获得服务地址为127.0.0.1:8001 当总请求数位2时: 2 % 2 =0 对应下标位置为0 ,则获得服务地址为127.0.0.1:8002 当总请求数位3时: 3 % 2 =1 对应下标位置为1 ,则获得服务地址为127.0.0.1:8001 当总请求数位4时: 4 % 2 =0 对应下标位置为0 ,则获得服务地址为127.0.0.1:8002 如此类推…
2.OpenFeign服务接口调用(用在消费端80)
官网:https://cloud.spring.io/spring-cloud-static/Hoxton.SR1/reference/htmlsingle/#spring-cloud-openfeign 源码:https://github.com/spring-cloud/spring-cloud-openfeign
2.1OpenFeign简介
Feign是一个声明式的Web服务客户端,让编写Web服务客户端变得非常容易,只需创建一个接口并在接口上添加注解即可
a)能干嘛? 前面在使用Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模版化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下,我们只需创建一个接口并使用注解的方式来配置它(以前是Dao接口上面标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可)
b)OpenFeign与Feign
2.2OpenFeign的使用步骤
接下来我们要从之前的ribbon+restTemplate转换位openfeign了
1、微服务调用接口+@FeignClient
2、新建模块cloud-consumer-feign-order80
3、改pom
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<exclusions>
<exclusion>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-client</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-core</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jersey.contribs</groupId>
<artifactId>jersey-apache-client4</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.javalearn.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
从依赖中也可以看出来openfeign包含了ribbon 4、写yml
server:
port: 80
eureka:
client:
# 这个客户端我们不注册金Eureka
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
5、主启动,注意加@EnableFeignClients 6、业务类,注意写@FeignClient注解 回顾一下我们微服务提供者的两个方法,这里以getById为例
@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {
@GetMapping("/payment/get/{id}")
public CommonResult getPaymentById(@PathVariable("id") Long id);
}
这里可能有同学会有疑问,为什么service层会有GetMapping注解呢? 我们之前是使用ribbon+testTemplate来实现微服务的调用,我们在service上加的注解,其实就是代替了之前的ribbon+testTemplate,微服务调用的顺序:80的controller->80的service->8001的controller->8001的service
7、controller
@RestController
@Slf4j
public class OrderFeignController {
@Resource
private PaymentFeignService paymentFeignService;
@GetMapping("/consumer/payment/get/{id}")
public CommonResult getPaymentById(@PathVariable("id") Long id){
return paymentFeignService.getPaymentById(id);
}
}
8、测试 启动顺序:7001->7002->8001->8002->80 经过测试,我们发现openfeign自带负载均衡的功能。
2.3OpenFeign的超时控制
a)在8001中故意写暂停程序
@GetMapping("/payment/feign/timeout")
public String paymentFeignTimeout(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return serverPort;
}
b)在80的service中添加超时方法 c)在80的controller中添加一个方法
@GetMapping("/consumer/payment/feign/timeout")
public String paymentFeignTimeout(){
return paymentFeignService.paymentFeignTimeout();
}
d)测试 启动顺序:7001->7002->8001->80
OpenFeign默认等待1秒钟,超过后报错.
为了避免这样的情况,有时候我们需要设置Feign客户端的超时控制,yml文件中开启配置,feign自带ribbon,所以超时也由ribbon控制(新版本已经不是这个了,不过大体思路都是一样的,可以去文档找)
#设置feign客户端超时时间(OpenFeign默认支持ribbon)
ribbon:
#指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
ReadTimeout: 5000
#指的是建立连接后从服务器读取到可用资源所用的时间
ConnectTimeout: 5000
然后重启80,读取成功了
2.4OpenFeign日志打印功能
Feign 提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解 Feign 中 Http 请求的细节。 说白了就是对Feign接口的调用情况进行监控和输出
a)日志级别 b)写一个配置类
@Configuration
public class FeignConfig
{
@Bean
Logger.Level feignLoggerLevel()
{
return Logger.Level.FULL;
}
}
c)在yml中配置
logging:
level:
# feign日志以什么级别监控哪个接口
com.javalearn.springcloud.service.PaymentFeignService: debug
d)测试 随便运行一个请求,发现控制台已经显示了我们要的日志
3.Hystrix断路器
官网:https://github.com/Netflix/Hystrix/wiki/How-To-Use
3.1一些重要的概念
(1)目前分布式所面对的问题-服务雪崩
服务雪崩: 多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”.
(2)Hystrix是什么 Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。 “断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
(3)Hystrix能干嘛?
(4)Hystrix的停更 (5)服务降级(fallback)
(6)服务熔断(break)
(7)服务限流(flowlimit)
3.2Hystrix的案例
首先先搞定服务注册中心,把7001恢复成单机版
3.2.1构建微服务提供者8001
我们先新建一个正常的微服务,后面就以他为根基,从正确->错误->降级熔断->恢复
1、新建module:cloud-provider-hystrix-payment8001
2、改pom 新的内容主要是Hystrix的依赖,其他的和我们之前的很相似
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<exclusions>
<exclusion>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-client</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-core</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jersey.contribs</groupId>
<artifactId>jersey-apache-client4</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.javalearn.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
3、写yml
server:
port: 8001
spring:
application:
name: cloud-provider-hystrix-payment
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
#defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
defaultZone: http://eureka7001.com:7001/eureka #单机版,这里写你的Eureka地址
4、主启动 没什么好说的,和上一篇笔记注意记上这个注解@EnableEurekaClient
5、业务类 service
public interface PaymentService {
public String paymentInfo_OK (Integer id);
public String paymentInfo_Time0ut(Integer id);
}
他的实现类
@Service
public class PaymentServiceImpl implements PaymentService {
@Override
public String paymentInfo_OK(Integer id) {
return "线程池:"+Thread.currentThread().getName()+" paymentInfo_OK:id: "+id+"\t"+"haha~";
}
@Override
public String paymentInfo_Time0ut(Integer id) {
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
return "线程池:"+Thread.currentThread().getName()+"paymentInfo_TimeOut,id: "+id+"\t"+"O(∩_∩)O,耗费3秒";
}
}
6、controller
@RestController
@Slf4j
public class PaymentController {
@Resource
private PaymentService paymentService;
@Value("${server.port}")
private String serverPort;
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id){
String result = paymentService.paymentInfo_OK(id);
log.info("***"+result);
return result;
}
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
String result = paymentService.paymentInfo_Time0ut(id);
log.info("***"+result);
return result;
}
}
7、测试 启动顺序:7001->8001 经过测试,带Hystrix熔断框架的8001中的两个方法运行正常
3.2.2JMeter高并发测试(引出降级容错解决)
我们接下来用JMeter发送两万个请求给TimeOut
测试结果:本来应该没有延迟的ok方法也开始转圈了 原因:tomcat的默认的工作线程数被打满 了,没有多余的线程来分解压力和处理。
上面还是服务提供者8001自己测试,假如此时外部的消费者80也来访问,那消费者只能干等,最终导致消费端80不满意,服务端8001直接被拖死
那么接下来我们再加入80消费者 a)创建module:cloud-consumer-feign-hystrix-order80
b)改pom
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<exclusions>
<exclusion>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-client</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-core</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jersey.contribs</groupId>
<artifactId>jersey-apache-client4</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.javalearn.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
c)写yml
server:
port: 80
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
d)主启动 注意要加上@EnableFeignClients注解
e)业务类 首先是service,具体写法和上面第二章的openfeign的消费者写法一样,我们的这个消费者,要调用我们刚刚写的8001的微服务。
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")
public interface PayMentHystrixService {
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id);
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}
然后是controller
@RestController
@Slf4j
public class PaymentHystrixController {
@Resource
private PayMentHystrixService payMentHystrixService;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id){
String result = payMentHystrixService.paymentInfo_OK(id);
return result;
}
@GetMapping("/comsumer/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
String result = payMentHystrixService.paymentInfo_TimeOut(id);
return result;
}
}
f)测试 我们也是先对8001进行高并发测试,然后再使用80消费者去访问,最后发现80消费者访问本来没有延迟的方法,不是转圈圈就是报超时错误
那么接下来就是我们针对这一高并发测试,所要解决的问题了 1、 超时导致服务器变慢(转圈):超时不再等待 2、出错(宕机或程序运行出错):出错要有兜底 解决: 1、对方服务(8001)超时了,调用者(80)不能一直卡死等待,必须有服务降级 2、对方服务(8001)down机了,调用者(80)不能一直卡死等待,必须有服务降级 3、对方服务(8001)OK,调用者(80)自己出故障或有自我要求(自己的等待时间小于服务提供者),自己处理降级
3.2.3解决高并发问题之服务降级
在这里我们将使用一个新的注解@HystrixCommand去配置
(1)先从8001自身寻找问题
我们的8001中,不是有一个需要等待三秒的方法吗? 设置自身调用超时时间的峰值,峰值内可以正常运行,超过了需要有兜底的方法处理,作服务降级fallback
那么接下来,我们规定三秒以内是正常的业务逻辑,超过三秒,会自动调用@HystrixCommand标注好的fallbackMethod调用类中的指定方法 需要在主启动类上加上@EnableCircuitBreaker注解
经过测试,发现达到了我们预期的效果,只要是服务不可用了,都会去调用我们的兜底方法
(2)在消费者80端中也做修改
接下来我们假设,我们的8001设置的是5秒超时,但是我们的80设置的是2秒超时,注意Hystrix虽然即可以用在消费端,也可以用在客户端,但是一般都是用在消费端的。
首先先修改yml
feign:
hystrix:
enabled: true
在主启动类上加上注释@EnableHystrix 然后修改80的controller,其实方法和之前的8001基本一致
如果说我们想让这个程序正常运行,我们只需要在80中的设置的时间小于8001中的等待时间即可,同时不要忘了在80中设置Hystrix的默认超时时间
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: Hystrix的默认超时时间
(3)全局降级DefaultProperties
我们之前虽然已经完成了服务降级,但是我们业务逻辑的方法和兜底的方法放在一起,代码就会非常冗余,所以接下来我们就需要使用全局的fallback来解决代码膨胀的功能 使用注解:@DefaultProperties(defaultFallback = “”), 通用的和独享的各自分开,避免了代码膨胀,合理减少了代码量
我们以80来演示,在80的controller中加上一个全局兜底的方法
public String payment_Global_FallbackMethod()
{
return "Global异常处理信息,请稍后再试,/(ㄒoㄒ)/~~";
}
然后再类上加上@DefaultProperties(defaultFallback = “payment_Global_FallbackMethod”) 说明: (4)通配服务降级
上面我们解决了代码冗余的问题,现在还有一个问题,就是我们的兜底方法和业务逻辑混在一块导致代码混乱。
回顾一下我们的80,是使用feign接口去调用8001微服务的方法,那么我们可以为Feign客户端定义的接口添加一个服务降级处理的实现类即可实现解耦,下图是我们80 的feign接口 根据cloud-consumer-feign-hystrix-order80已经有的PaymentHystrixService接口,重新新建一个类(PaymentFallbackService)实现该接口,统一为接口里面的方法进行异常处理
@Component
public class PaymentFallbackService implements PayMentHystrixService{
@Override
public String paymentInfo_OK(Integer id) {
return "----payment fall back-method_ok";
}
@Override
public String paymentInfo_TimeOut(Integer id) {
return "----payment fall back-method_timeout";
}
}
注意配置文件中要加上
feign:
hystrix:
enabled: true
最后一步,我们把我们的这个实现类,配置到我们的接口中 经过测试,我们达到了目的
3.2.4解决高并发问题之服务熔断
服务的降级->进而熔断->恢复调用链路 注意降级和熔断的区别:调用失败会触发降级,而降级会调用fallback方法,但无论如何降级的流程一定会调用正常方法再调用fallbcak方法,假如单位时间内调用失败次数过多,也就是降级次数过多,则触发熔断,熔断以后就会跳过正常的方法直接执行fallback方法,所谓熔断后服务不可用就是因为跳过了正常方法直接执行fallback方法 那么接下来就来实操了,同样也是使用@HystrixCommand这个注解
(1)修改8001,在service的实现类中加上
@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled",value = "true"),
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"),
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60"),
})
public String paymentCircuitBreaker(@PathVariable("id") Integer id)
{
if(id < 0)
{
throw new RuntimeException("******id 不能负数");
}
String serialNumber = IdUtil.simpleUUID();
return Thread.currentThread().getName()+"\t"+"调用成功,流水号: " + serialNumber;
}
public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id)
{
return "id 不能负数,请稍后再试,/(ㄒoㄒ)/~~ id: " +id;
}
这里顺带提一句,想要查看@HystrixCommand注解中有多少参数,可以去HystrixCommandProperties这个类或者去官网查看
(2)修改8001的controller
@GetMapping("/payment/circuit/{id}")
public String paymentCircuitBreaker(@PathVariable("id") Integer id)
{
String result = paymentService.paymentCircuitBreaker(id);
log.info("****result: "+result);
return result;
}
(3)测试 启动顺序:7001->8001
还记得我们的方法吗?参数为正数成功,为负数,那么就会触发兜底
当在浏览器中一直进行复参数的浏览,错误率达到60%后,就会触发熔断,那么我们再输入正参数,那么也会调用兜底方法,当达到我们的窗口期时间后,才会慢慢的尝试恢复链路,至此,服务熔断结束
接下来做一个总结:断路器的工作流程 下面是官网给出的Hystrix的运行流程,看着吓人,其实很容易懂
3.3Hystrix图形化Dashboard构建与实战
除了隔离依赖服务的调用以外,Hystrix还提供了准实时的调用监控(Hystrix Dashboard),Hystrix会持续地记录所有通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求多少成功,多少失败等。Netflix通过hystrix-metrics-event-stream项目实现了对以上指标的监控。Spring Cloud也提供了Hystrix Dashboard的整合,对监控内容转化成可视化界面。
(1)新建module:cloud-consumer-hystrix-dashboard9001
(2)改pom
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
(3)写yml
server:
port: 9001
(4)主启动+新注解@EnableHystrixDashboard
(5)所有Provider微服务提供类(8001/8002/8003)都需要监控依赖配置 这个一般我们都是和web的starter一起引入的
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
(6)启动9001后续将监控微服务8001 浏览器输入:http://localhost:9001/hystrix,如果有下图的图标,那么说明你搭建成功了! (7)实战第一步:修改8001 注意:新版本Hystrix需要在主启动类MainAppHystrix8001中指定监控路径,下面的配置,直接粘贴到8001的配置类即可
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
并且注意主启动类要加上注解:@EnableCircuitBreaker (2)实战第二步:启动8001,在9001的web界面中监控8001 填写监控地址:http://localhost:8001/hystrix.stream 我们先发几个正确的请求
再发几个异常的请求,使断路器打开 下面说一下这个图怎么看
4.Gateway新一代网关
官网:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/
4.1核心概念简介
(1)是什么 SpringCloud Gateway 是 Spring Cloud 的一个全新项目,基于 Spring 5.0+Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。
用一句话概括:SpringCloud Gateway 使用的Webflux中的reactor-netty响应式编程组件,底层使用了Netty通讯框架。 (2)微服务架构中网关在哪? (3)为什么使用Gateway 1、Gateway有以下特性 基于Spring Framework 5, Project Reactor 和 Spring Boot 2.0 进行构建; 动态路由:能够匹配任何请求属性; 可以对路由指定 Predicate(断言)和 Filter(过滤器); 集成Hystrix的断路器功能; 集成 Spring Cloud 服务发现功能; 易于编写的 Predicate(断言)和 Filter(过滤器); 请求限流功能; 支持路径重写。
2、neflix不太靠谱,zuul2.0一直跳票,迟迟不发布
3、Spring WebFlux 是 Spring 5.0 引入的新的响应式框架,区别于 Spring MVC,它不需要依赖Servlet API,它是完全异步非阻塞的,并且基于 Reactor 来实现响应式流规范。
(5)三大个核心概念
(6)工作流程
核心逻辑:路由转发+执行过滤器链
客户端向 Spring Cloud Gateway 发出请求。然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。
Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。 过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑。
4.2搭建一个Gateway
(1)新建一个module:cloud-gateway-gateway9527
(2)改pom
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<exclusions>
<exclusion>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-client</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-core</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jersey.contribs</groupId>
<artifactId>jersey-apache-client4</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.javalearn.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
(3)写yml
server:
port: 9527
spring:
application:
name: cloud-gateway
eureka:
instance:
hostname: cloud-gateway-service
client: #服务提供者provider注册进eureka服务列表内
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka
(4)主启动 加上注解:@EnableEurekaClient使其成为一个客户端
(5)映射到微服务提供者8001 8001中有两个方法,就以这两个方法为例 在9527的yml中配置,这段配置在spring:下
cloud:
gateway:
#负数,代表多个路由
routes:
- id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址(真实路径)
predicates:
- Path=/payment/get/** # 断言,路径相匹配的进行路由(加在真实路径后的)
- id: payment_routh2 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/payment/lb/** # 断言,路径相匹配的进行路由
(6)测试 启动9527,7001,8001 注意,9527是网关,不需要web,不然会导致jar包冲突 启动后,我们发现我们的网关和微服务都已经注册进了Eureka 我们之前访问8001get方法,都是用http://localhost:8001/payment/get/31这个网址 现在用http://localhost:9527/payment/get/31这个网址也可以访问了,这就是网关的初步体验了,我们淡化了我们的真实地址
匹配的规则:
4.3第二种配置路由的方式
第一种配置方式,就是在yml中配置,可以参考上文的方式,接下来是第二种代码中注入RouteLocator的Bean
接下来,我们就自己写一个,需求:通过网关9527访问到外网的百度新闻网址
@Configuration
public class GatewayConfig {
@Bean
public RouteLocator customerRouteLocator(RouteLocatorBuilder routeLocatorBuilder){
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
routes.route("path_route_javalearn",r->r.path("/guonei").uri("http://news.baidu.com/guonei")).build();
return routes.build();
}
}
启动9527,浏览器中输入http://localhost:9527/guonei
4.4配置动态路由
默认情况下Gateway会根据注册中心注册的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能
(1)启动7001,两个服务提供者8001与8002
(2)修改9527的yml,实现动态路由
(3)测试 启动9527
浏览器输入:http://localhost:9527/payment/lb 经过测试,动态路由成功,且实现了负载均衡,即8001和8002轮询应答请求。
4.5Gateway的常用Predicate
我们每次启动9527的时候,都能在控制台看到一些下图的内容。 a)Route Predicate Factories这个是什么东东? Spring Cloud Gateway包括许多内置的Route Predicate工厂。所有这些Predicate都与HTTP请求的不同属性匹配。多个Route Predicate工厂可以进行组合
Spring Cloud Gateway 创建 Route 对象时, 使用 RoutePredicateFactory 创建 Predicate 对象,Predicate 对象可以赋值给 Route。 Spring Cloud Gateway 包含许多内置的Route Predicate Factories。
所有这些谓词都匹配HTTP请求的不同属性。多种谓词工厂可以组合,并通过逻辑and。 在官网文档中也可以查到 b)常用的常用的Route Predicate 1、After Route Predicate
要获取这个时间也很简单,只需要运行下面的代码
public static void main(String[] args)
{
ZonedDateTime zbj = ZonedDateTime.now();
System.out.println(zbj);
}
同样的,before,between也是一样的用法,这里只以after为例
2、Cookie Route Predicate Cookie Route Predicate需要两个参数,一个是 Cookie name ,一个是正则表达式。 路由规则会通过获取对应的 Cookie name 值和正则表达式去匹配,如果匹配上就会执行路由,如果没有匹配上则不执行
3、其他 用法都大同小异
4.6Gateway的Filter
Spring Cloud Gateway 内置了多种路由过滤器,他们都由GatewayFilter的工厂类来产生
a)概念 过滤器生命周期有两种:在业务逻辑之前和在业务逻辑之后 类型也分为两种:单一过滤器(GatewayFilter)和全局过滤器(GlobalFilter) 官网位置:共有31个,就不一一解释了 一个小例子: b)自定义一个过滤器
要实现两个接口:GlobalFilter,Ordered
@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("***************come in MyLogGateWayFilter:"+new Date());
String uname = exchange.getRequest().getQueryParams().getFirst("uname");
if (uname==null){
log.info("用户名为null,非法用户");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
测试后,我们的请求只要不带有uname这个参数,就报406
|