10.1 Hystrix是什么
10.2 Hystrix停更进维
学习资料:https://github.com/Netflix/Hystrix/wiki/How-To-Use
首页停更说明:https://github.com/Netflix/Hystrix
10.3 Hystrix的服务降级熔断限流概念初讲
服务降级:对方系统不可用时,给出一个兜底的解决方法(返回友好提示)。
服务熔断:服务器达到最大访问后,直接拒绝访问,然后调用服务降级方法。类似于保险丝。
服务限流:秒杀高并发等场景,流量高于服务器负载,于是限制一定的访问流(比如每秒N个,有序访问)。
10.4 Hystrix支付微服务构建
module:cloud-provider-hystrix-payment8081
pom:
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-actuator
org.atguigu.springcloud
cloud-api-common
${project.version}
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-devtools
runtime
true
yml:
server:
port: 8081
spring:
application:
name: cloud-provider-hystrix-payment
eureka:
client:
register-with-eureka: true
fetchRegistry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka
主启动:
com.atguigu.springcloud.PaymentHystrixMain8001:
@SpringBootApplication
@EnableEurekaClient
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class,args);
}
}
在springcloud下建service,创建类PaymentService:
@Service
public class PaymentService {
public String paymentInfo_OK(Integer id){
return "线程池: "+Thread.currentThread().getName()+" paymentInfo_OK,id: "+id+"\t"+"O(∩_∩)O哈哈~";
}
public String paymentInfo_TimeOut(Integer id){
int timeNumber = 3;
try{
TimeUnit.SECONDS.sleep(timeNumber);
}catch (InterruptedException e){
e.printStackTrace();
}
return "线程池: "+Thread.currentThread().getName()+" paymentInfo_Timeout,id: "+id+"\t"+"O(∩_∩)O哈哈~"+"耗时3秒钟";
}
}
键controller包PaymentController
@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:"+result);
return result;
}
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
String result = paymentService.paymentInfo_TimeOut(id);
log.info("****result:"+result);
return result;
}
}
测试:
localhost:8081/payment/hystrix/ok/31
localhost:8081/payment/hystrix/timeout/31
10.5 JMeter高并发压测后卡顿
下载Jmeter
开启Jmeter,来20000个并发压死8001,20000个请求都访问payment
此时重新访问:localhost:8081/payment/hystrix/ok/31,发现会出现卡顿。
10.6 订单微服务调用支付服务出现卡顿
module:cloud-consumer-feign-hystrix-order81
pom:
org.springframework.cloud
spring-cloud-starter-openfeign
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.atguigu.springcloud
cloud-api-common
${project.version}
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-devtools
runtime
true
YML:
server:
port: 81
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka
主启动:
@SpringBootApplication
@EnableFeignClients
public class OrderHystrixMain81 {
public static void main(String[] args) {
SpringApplication.run(OrderHystrixMain81.class,args);
}
}
service下创建PaymentHystrixService接口:
@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下创建OrderHystrixController类:
@RestController
@Slf4j
public class OrderHystrixController {
@Resource
private PaymentHystrixService paymentHystrixService;
@GetMapping("/consumer/payment/hytrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id){
String result = paymentHystrixService.paymentInfo_OK(id);
return result;
}
@GetMapping("/consumer/payment/hytrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
String result = paymentHystrixService.paymentInfo_TimeOut(id);
return result;
}
}
启动cloud-provider-hystrix-payment8081,cloud-consumer-feign-hystrix-order81,7001
访问localhost:81/consumer/payment/hystrix/ok/31
测试:OrderHystrixMain81、PaymentHystrixMain8081、EurekaMain7001。压测地址:http//localhost:8081/payment/hystrix/timeout/31。
出现效果:转圈圈等待。
10.7 降级容错解决的维度要求
10.7 Hystrix之服务降级支付侧fallback
在cloud-provider-hystrix-payment8081的service中添加如下代码:
@Service
public class PaymentService {
public String paymentInfo_OK(Integer id){
return "线程池: "+Thread.currentThread().getName()+" paymentInfo_OK,id: "+id+"\t"+"O(∩_∩)O哈哈~";
}
@HystrixCommand(fallbackMethod="paymentInfo_TimeOutHandler",commandProperties={
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="3000")
})
public String paymentInfo_TimeOut(Integer id){
int timeNumber = 5;
try{TimeUnit.SECONDS.sleep(timeNumber);}catch (InterruptedException e){e.printStackTrace();}
return "线程池: "+Thread.currentThread().getName()+" paymentInfo_Timeout,id: "+id+"\t"+"O(∩_∩)O哈哈~"+"耗时"+timeNumber+"秒";
}
public String paymentInfo_TimeOutHandler(Integer id){
return "线程池: "+Thread.currentThread().getName()+" 系统繁忙或者运行报错,请稍后再试,id: "+id+"\t"+"o(╥﹏╥)o";
}
}
添加@HystrixCommand注解,paymentInfo_TimeOutHandler是下面兜底的方法,用于输出抚慰的话,@HystrixProperty注解后是时间,设定为3000毫秒,即3秒未收到就判断为不成功发送消息。在paymentInfo_TimeOut里定义的是5秒的延迟,所以一定会超时。
在main中添加如下注解:
@EnableCircuitBreaker
测试:localhost:8081/payment/hystrix/timeout/31
在paymentInfo_Timeout方法里加上int m = 10/0,会出错,照理也会出现安抚语句
10.8 Hystrix之服务降级订单侧fallback
对80降级保护,服务降级一般放在客户端。
对cloud-consumer-feign-hystrix-order81模块进行修改:
YML中加入如下代码:
feign:
hystrix:
enabled: true
启动类OrderHystrixMain中加入@EnableHystrix。
controller包下的OrderHystrixController类:
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="1500")
})
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
String result = paymentHystrixService.paymentInfo_TimeOut(id);
return result;
}
public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id) {
return "我是消费者81,对方支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,/(ㄒoㄒ)/~~";
}
逻辑:服务端超过5秒报错,设置3秒。客户端时1.5秒。此时客户端访问不上,报错。
测试:localhost:81/consumer/payment/hystrix/timeout/31
在81加上错误同样效果:
10.9 Hystrix之全局服务降级DefaultProperties
在81的controller里写:
public String payment_Global_FallbackMethod(){
return "Global异常处理信息,请稍后再试,/(ㄒoㄒ)/~~";
}
在controller包下的OrderHystrixController
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
注释掉上面一长串@HystrixCommand注解重新写一个@HystrixCommand注解
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
/*@HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="1500")
})*/
@HystrixCommand
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
int age = 10/0;
String result = paymentHystrixService.paymentInfo_TimeOut(id);
return result;
}
10.10 Hystrix之通配服务降级FeignFallback
解决全局服务降级的耦合问题
在cloud-consumer-feign-hystrix-order81下的service里创建PaymentFallbackService类,继承实现PaymentHystrixService接口:
@Component
public class PaymentFallbackService implements PaymentHystrixService{
@Override
public String paymentInfo_OK(Integer id) {
return "------PaymentFallbackService fall back-paymentInfo_OK,o(╥﹏╥)o";
}
@Override
public String paymentInfo_TimeOut(Integer id) {
return "------PaymentFallbackService fall back-paymentInfo_TimeOut,o(╥﹏╥)o";
}
}
把fallback = PaymentFallbackService.class加入到@FeignClient里,PaymentFallbackService是实现类的类名:
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentFallbackService.class)
测试:
访问localhost:81/consumer/payment/hystrix/ok/31
故意关闭微服务8001,此时访问不上,服务降级,返回安抚语句:
10.11 Hystrix值服务熔断理论
先是服务降级,然后熔断,最后会慢慢恢复调用链路,这一点很重要。
10.12 Hystrix之服务熔断案例(上)
把下面依赖存入到cloud-api-commons
cn.hutool
hutool-all
5.1.0
糊涂工具包生成流水号。
在cloud-provider-hystrix-payment8081的service包里的PaymentService类里写如下代码:
@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(╥﹏╥)o id:"+id;
}
10.13 Hystrix之服务熔断案例(下)
在cloud-provider-hystrix-payment8081的controller包里的PaymentController类里加入如下代码:
//服务熔断
@GetMapping("/payment/circuit/{id}")
public String paymentCircuitBreaker(@PathVariable("id") Integer id){
String result = paymentService.paymentCircuitBreaker(id);
log.info("****result"+result);
return result;
}
测试:
http://localhost:8081/payment/circuit/31
正数是调用成功:
负数是调用失败:
狂点负数,请求N次,失败后会熔断,此时再输入正数仍旧会错,当错误率下降,等待一会儿会恢复。
10.14 Hystrix之服务熔断总结
下面是对上面程序代码的解读:
熔断的流程:
下面是常用的一些属性:
10.15 Hystrix工作流程最后总结
10.16 Hystrix图形化Dashboard搭建
Module:cloud-consumer-hystrix-dashboard9001
POM:
org.springframework.cloud
spring-cloud-starter-netflix-hystrix-dashboard
org.springframework.boot
spring-boot-starter-actuator
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-devtools
runtime
true
YML:
server:
port: 9001
主启动:
创建HystrixDashboardMain9001,加上新注解@EnableHystrixDashboard
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboardMain9001 {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardMain9001.class,args);
}
}
所有Provider微服务提供类(8001/8002/8003)都需要监控依赖配置,即POM文件里必须要有actuator。
测试:启动9001。输入地址:localhost:9001/hystrix
10.17 Hystrix图形化Dashboard监控实战
注意要加一个@EnableCircuitBreaker注解:
在8081的PaymentHystrixMain8081里添加如下方法代码:
@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;
}
测试:启动EurekaMain7001和HystrixDashboardMain9001
把http://localhost:8081/hystrix.stream的地址填入url栏:
启动PaymentHystrixMain8081
localhost:8081/payment/circuit/31是正确地址
localhost:8081/payment/circuit/-31是错误地址
先狂点下面的地址多次:
然后点击监控:
下面每一种颜色对应一种故障:
下面是出现的次数:
圈的含义:
点的越多圈圈会变大,Circuit是保险丝的开合情况,Closed表示的是允许流量通过:
如果输入
12.1 GateWay是什么
12.2 GateWay非阻塞异步模型
12.3 GateWay工作流程
核心:路由转发+执行过滤链
Route 路由
Predicate 断言:匹配条件
Filter 过滤
12.4 GateWay9527搭建
建POM:cloud-gateway-gateway9527
POM:
org.springframework.cloud
spring-cloud-starter-gateway
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.atguigu.springcloud
cloud-api-common
${project.version}
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-devtools
runtime
true
org.springframework.boot
spring-boot-starter-test
test
YML:
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
routes:
- id: payment_routh
uri: http://localhost:8081 #匹配后提供服务的路由地址
predicates:
- Path=/payment/get/**
- id: payment_routh2
uri: http://localhost:8081
predicates:
- Path=/payment/lb/**
eureka:
instance:
hostname: cloud-gateway-service
client:
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka
启动类:
@SpringBootApplication
@EnableEurekaClient
public class GateWayMain9527 {
public static void main(String[] args) {
SpringApplication.run(GateWayMain9527.class,args);
}
}
测试:启动7001,启动cloud-provider-payment8081,9527网关
先localhost:8081/payment/get/31看看能不能访问上
再输入localhost:9527/payment/get/31通过网关进行访问
localhost:9527/payment/lb
注意要事先把父POM里的dependencies里的代码全部清空,不然会有依赖的传递影响。
12.5 GateWay配置路由的两种方式
业务需求:通过9527网关访问到外网的百度新闻网址。
http://news.baidu.com/guonei
http://news.baidu.com/guoji
在9527的com/atguigu下创建config包,创建GateWayConfig配置类:
@Configuration
@Import({RouteLocatorBuilder.class})
public class GateWayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder){
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
routes.route("path_route_atguigu",
r -> r.path("/guonei")
.uri("http://news.baidu.com/guonei")).build();
return routes.build();
}
@Bean
public RouteLocator customRouteLocator2(RouteLocatorBuilder routeLocatorBuilder){
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
routes.route("path_route_atguigu2",
r -> r.path("/guoji")
.uri("http://news.baidu.com/guoji")).build();
return routes.build();
}
}
测试:localhost:9527/guonei,localhost:9527/guonei,会跳转到相应页面(失败)。
12.6 GateWay配置动态路由
网关侧实现负载和均衡
启动:
一个eureka7001+两个服务提供者8081和8082
POM:
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
YML在上面基础上作如下修改:
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enable: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
测试:http://localhost:9527/payment/lb
实现在8081和8083间切换。
12.7 GateWay常用的Predicate
在test下的java里创建T2类:
public class T2 {
public static void main(String[] args){
ZonedDateTime zbj = ZonedDateTime.now();
System.out.println(zbj);
}
}
先运行测试类,获得当前时间比如:2023-11-03T18:02:51.473+08:00[Asia/Shanghai]。
修改YML:
测试:localhost:9527/payment/lb
改动时间,在当前时间基础上加上1个小时,访问失败:
下面是Between:
测试:
在cmd中用curl发带有cookie的命令可以访问:
下面是正则表达式:
测试:
12.8 GateWay的Filter
在cloud-gateway-gateway9527下的com.atguigu.springcloud创建filter包,在包下创建MyLogGateWayFilter
@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter, Ordered {
@Override
public Mono 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,非法用户,o(╥﹏╥)o");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder(){
return 0;
}
}
测试:
带uname的可以正常访问:
不带uname的不能访问(失败,可访问)