SpringCloud知识总结(中篇) :Hystrix断路器+Gateway网关+Config分布式配置中心+Bus消息总线+Stream消息驱动+Sleuth分布式请求链路跟踪

叁、中级

十、Hystrix断路器

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:

<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>
    </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.atguigu.springcloud</groupId>
        <artifactId>cloud-api-common</artifactId>
        <version>${project.version}</version>
    </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>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
</dependencies>

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:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<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>
</dependency>
<dependency>
    <groupId>org.atguigu.springcloud</groupId>
    <artifactId>cloud-api-common</artifactId>
    <version>${project.version}</version>
</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>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
</dependency>

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

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.1.0</version>
</dependency>

糊涂工具包生成流水号。

在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:

<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.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>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
</dependencies>

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表示的是允许流量通过:

如果输入

十二、Gateway新一代网关

12.1 GateWay是什么

12.2 GateWay非阻塞异步模型

12.3 GateWay工作流程

核心:路由转发+执行过滤链

Route 路由

Predicate 断言:匹配条件

Filter 过滤

12.4 GateWay9527搭建

建POM:cloud-gateway-gateway9527

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>
    </dependency>
    <dependency>
        <groupId>org.atguigu.springcloud</groupId>
        <artifactId>cloud-api-common</artifactId>
        <version>${project.version}</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

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:

<dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

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<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,非法用户,o(╥﹏╥)o");
            exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }
    @Override
    public int getOrder(){
        return 0;
    }
}

测试:

带uname的可以正常访问:

不带uname的不能访问(失败,可访问)

猜你喜欢

转载自blog.csdn.net/RuanFun/article/details/134179425
今日推荐