SpringCloud 微服务(中)

Hystrix

Hystrix概述

分布式系统面临的问题:
复杂分布式体系结构中的应用程序 有数10个依赖关系,每个依赖关系在某些时候将不可避免地失败
在这里插入图片描述

在这里插入图片描述

Hystrix是网飞公司出品的框架,虽然已经停止更新,但其设计理念优秀,出道即巅峰,其设计理念,设计思想在其他断路器都有借鉴

在这里插入图片描述
Hystrix主要工作:服务降级、服务熔断、接近实时的监控…

HyStrix重要概念

1、服务降级

2、服务熔断
类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示

就是保险丝: 服务的降级->进而熔断->恢复调用链路

3、服务限流
秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行

hystrix案例(服务降级)

构建一个模块

在这里插入图片描述

JMeter高并发压测后卡顿

开启Jmeter,来20000个并发压死8001,20000个请求都去访问paymentInfo_TimeOut服务
在这里插入图片描述

在这里插入图片描述

演示结果:
两个都在转圈圈
为什么会被卡死,tomcat的默认工作线程数被打满了,没有多余的线程来分解压力和处理

Jmeter压测结论:
上面还只是服务提供者8001自己测试,假如此时外部的消费者80也来访问,那消费者只能干等,最终导致消费端80不满意,服务端8001直接被拖死

其他服务再做远程调用这个需要3秒完成的接口,会出现服务卡顿和读取超时

严重影响影响业务逻辑和体验

故障和导致现象:
8001同一层次的其他接口被困死,因为tomcat线程池里面的工作线程已经被挤占完毕
80此时调用8001,客户端访问响应缓慢,转圈圈

正因为有上述故障或不佳表现 才有我们的降级/容错/限流等技术诞生

  • 如何解决?解决的要求
    超时导致服务器变慢(转圈):超时不再等待
    出错(宕机或程序运行出错):出错要有兜底

  • 对方服务(8001)超时了,调用者(80)不能一直卡死等待,必须有服务降级

  • 对方服务(8001)down机了,调用者(80)不能一直卡死等待,必须有服务降级

  • 对方服务(8001)ok,调用者(80)自己有故障或有自我要求(自己的等待时间小于服务提供者)

Hystrix之服务降级支付侧(服务提供者)fallback

先处理服务提供者(8001),8001先从自身找问题,设置自身调用超时时间的峰值,峰值内可以正常运行, 超过了需要有兜底的方法处理,做服务降级fallback。
业务方法中,@HystrixCommand注解指派兜底方法(在同一个类中)

@HystrixCommand(fallbackMethod = "payment_TimeOutHandler", commandProperties = {
        @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
})
public String getPaymentInfo_Error(Integer id) {
    try {
        TimeUnit.SECONDS.sleep(3);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return "线程池:"+Thread.currentThread().getName()+"paymentInfo_TimeOut,Id"+id+"\t"+"😄"+"耗时3秒";
}

public String payment_TimeOutHandler(Integer id){
    return "线程池:"+Thread.currentThread().getName()+"系统繁忙或运行错误,请稍后再试";
}

8001服务提供者的主启动类添加注解@EnableCircuitBreaker启用服务降级兜底功能。

此时8001本身再调用此方法,超过设定的等待时间,会直接转到payment_TimeOutHandler方法

Hystrix之服务降级订单侧(服务调用者)fallback

同上操作做好调用方的兜底
需在配置开启

feign:
  hystrix:
    enabled: true  #在feign中开启hystrix
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "paymentInfo_ERROR_Handler",commandProperties = {
        @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1500")
})
public String paymentInfo_timeout(@PathVariable("id") Integer id){
    String res = paymentHystrix.paymentInfo_TimeOut(id);
    System.out.println(res);
    return res;
}

public String paymentInfo_ERROR_Handler(@PathVariable("id") Integer id){
    return "我是消费者80____对方系统繁忙,请稍后再试paymentInfo_ERROR_Handler___异常返回";
}
Hystrix之全局服务降级DefaultProperties

目前问题:每个业务方法对应一个兜底的方法,代码膨胀
需要做到统一和自定义的分开

@RestController
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
public class OrderController {


    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
//    @HystrixCommand(fallbackMethod = "paymentInfo_ERROR_Handler",commandProperties = {
//            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1500")
//    })
    @HystrixCommand
    public String paymentInfo_timeout(@PathVariable("id") Integer id){
        String res = paymentHystrix.paymentInfo_TimeOut(id);
        System.out.println(res);
        return res;
    }

    public String payment_Global_FallbackMethod() {
        return "Global 异常处理信息 , 请稍后重试 .o(╥ ﹏ ╥)o";
    }
}

若精确指定了,则使用指定的方法

Hystrix之通配服务降级FeignFallback

和业务逻辑混在一起???混乱,由于Fallback都是写在业务类中,造成了高耦合,类功能混乱。

创建一个类实现远程服务业务接口,接口@FeignClient中指定这个类,就可以实现Fallback的剥离,完成解耦

@Component
public class PaymentHystrixFallback implements PaymentHystrix{
    @Override
    public String paymentInfo_OK(Integer id) {
        return "通配服务降级FeignFallback";
    }

    @Override
    public String paymentInfo_TimeOut(Integer id) {
        return "通配服务降级FeignFallback";
    }
}

在这里插入图片描述

hystrix案例(服务熔断)

断路器:一句话就是家里的保险丝
熔断是什么:马丁福乐提出的概念
在这里插入图片描述

熔断案例

服务熔断 @HystrixCommand注解,配置了如下参数,当一直抛出异常失败率达到后,会跳匝,此时无论是正常请求还是错误请求都会返回fallbackMethod,持续一会儿后会自动恢复。

//---服务的熔断
@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 simpleUUID = IdUtil.simpleUUID();
    return Thread.currentThread().getName()+"\t" + "成功调用,流水号是:" + simpleUUID;
}

public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id) {
    return "id不能为负数,请稍后再试............"+id;
}

熔断状态

  • 熔断打开:请求不再调用当前服务,内部设置一般为MTTR(平均故障处理时间),当打开长达导所设时钟则进入半熔断状态

  • 熔断关闭:熔断关闭后不会对服务进行熔断

  • 熔断半开:部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断

GateWay

概述

gateway是什么:
在这里插入图片描述
概述:
在这里插入图片描述

在这里插入图片描述

微服务架构中网关在哪里

在这里插入图片描述

为什么选择Gateway

1、netflix不太靠谱,zuul2.0一直跳票,迟迟不发布
在这里插入图片描述

2、SpringCloud Gateway具有如下特性
在这里插入图片描述

3、SpringCloud Gateway与Zuul的区别
在这里插入图片描述

Zuul1.x模型:

在这里插入图片描述
在这里插入图片描述

Gateway模型:

在这里插入图片描述

总结选择gateway主要原因,非阻塞异步、亲儿子

三大核心概念

  • Route(路由):
    路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如断言为true则匹配该路由

  • Predicate(断言):
    参考的是Java8的java.util.function.Predicate
    开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由

  • Filter(过滤)
    指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改.

在这里插入图片描述

入门案例配置

1、新建一个网关模块 cloud-gateway-gateway9527

2、导入依赖

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

3、启动类

@SpringBootApplication
@EnableEurekaClient
public class GateWayMain9527 {
    public static void main(String[] args) {
        SpringApplication.run(GateWayMain9527.class,args);
    }
}

4、application.yml中配置网关路由

server:
  port: 9527
spring:
  application:
    name: cloud-gateway-service
  cloud:
    gateway:
      routes:
        - id: payment_routh
          #          uri: http://localhost:8001
          uri: lb://CLOUD-PROVIDER-PAYMENT #动态路由
          predicates:
            - Path=/payment/get/**
        - id: payment_routh2
          #          uri: http://localhost:8001
          uri: lb://CLOUD-PROVIDER-PAYMENT
          predicates:
            - Path=/payment/lb/**
      discovery:
        locator:
          enabled: true  #开启从注册中心动态生成路由的功能,用微服务名进行路由

eureka:
  instance:
    hostname: cloud_gateway_service
  client:
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/
    register-with-eureka: true
    fetch-registry: true

此时就可以通过网关http://localhost:9527/payment/get/2访问到 http://localhost:8001/payment/get/2

GateWay的动态路由uri: lb://CLOUD-PROVIDER-PAYMENT

在这里插入图片描述
就能动态路由到相同服务名的服务

GateWay常用的Predicate 断言

在这里插入图片描述
各个断言的详解可以参考网上各种解答–各种断言

GateWay的Filter

GateWay有自带的31种过滤器,不过都用不到且很麻烦,这里自定义GateWay的全局过滤器,只需实现两个接口

@Component
public class MyGatewayFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("**************come in MylogGateWayGilter:  " + new Date());
        String uname = exchange.getRequest().getQueryParams().getFirst("uname");
        if (uname == null) {
            System.out.println("********用户名为空,非法用户。");
            exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

就可以过滤全局的请求
http://localhost:9527/payment/lb?uname=123123

SpringCloud Stream消息驱动

概念

是什么:
一句话:屏蔽底层消息中间件的差异,降低切换成本,统一消息的编程模型
在这里插入图片描述

设计思想

在这里插入图片描述

Stream如何统一底层差异

在这里插入图片描述

Binder

INPUT适用于消费者
OUTPUT适用于生产者

在这里插入图片描述

在这里插入图片描述
通过定义绑定器Binder作为中间层,实现了应用程序与消息中间件细节之间的隔离。

Stream中的消息通讯方式遵循了发布-订阅模式

  • Topic主题进行广播
    • 在RabbitMQ就是Exchange
    • 在Kafka中就是Topic

Stream编码常用注解简介

在这里插入图片描述

Stream消息驱动之生产者

1、新建模块cloud-stream-rabbitmq-provider8801

2、导入依赖,关键依赖

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

3、配置yml文件

server:
  port: 8801
spring:
  application:
    name: cloud-stream-privider
  cloud:
    stream:
      binders:   #自此处配置要绑定的rabbitmq的服务信息
        defaultRabbit: #表示定义的名称,用于binding整合
          type: rabbit #消息组件类型
          environment:  # 设置rabbitmq的相关的环境配置
            spring:
              rabbitmq:
                host: 115.159.35.229
                port: 5672
                username: guest
                password: guest
      bindings:   #服务的整合处理
        output: #这个名字是一个通道的名称
          destination: studyExchange #表示要使用的exchange名称定义
          content-type: application/json #设置消息类型,本次为json
          binder: defaultRabbit  #设置要绑定的消息服务的具体设置
eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/
  instance:
    lease-expiration-duration-in-seconds: 5 #如果现在超过了5秒的间隔
    lease-renewal-interval-in-seconds: 2 #设置心跳的时间间隔
    instance-id: send-8801.com
    prefer-ip-address: true #访问的路径变为IP地址

4、service中定义消息的推送管道

@EnableBinding(Source.class) //定义消息的推送管道
public class MessageProviderImpl implements IMessageProvider {

    @Autowired
    private MessageChannel output;//消息发送管道

    @Override
    public String send() {
        String serial = UUID.randomUUID().toString();
        output.send(MessageBuilder.withPayload(serial).build());
        System.out.println("****serial"+serial);
        return null;
    }
}
-----------------
@RestController
public class SendMessageController {

    @Autowired
    private IMessageProvider messageProvider;

    @GetMapping("/sendMessage")
    public String sendMessage(){
        return messageProvider.send();
    }
}

5、先启动RabbitMq,启动Eureka,启动Stream服务端,此时根据yml中配置的output会自动创建Exchange
在这里插入图片描述

Stream消息驱动之消费者

同生产者一样的依赖,区别在于配置文件中,bindings为input

server:
  port: 8802
spring:
  application:
    name: cloud-stream-privider
  cloud:
    stream:
      binders:   #自此处配置要绑定的rabbitmq的服务信息
        defaultRabbit: #表示定义的名称,用于binding整合
          type: rabbit #消息组件类型
          environment:  # 设置rabbitmq的相关的环境配置
            spring:
              rabbitmq:
                host: 115.159.35.229
                port: 5672
                username: guest
                password: guest
      bindings:   #服务的整合处理
        input: #这个名字是一个通道的名称
          destination: studyExchange #表示要使用的exchange名称定义
          content-type: application/json #设置消息类型,本次为json
          binder: defaultRabbit  #设置要绑定的消息服务的具体设置

eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/
  instance:
    lease-expiration-duration-in-seconds: 5 #如果现在超过了5秒的间隔
    lease-renewal-interval-in-seconds: 2 #设置心跳的时间间隔
    instance-id: send-8802.com
    prefer-ip-address: true #访问的路径变为IP地址

controller中定义消费者

@Component
@EnableBinding(Sink.class)
public class ReceiveMessageListenerController {

    @Value("${server.port}")
    private String serverPort;

    @StreamListener(Sink.INPUT)
    public void input(Message<String> message){
        System.out.println("我是消费者1号,-----》接受到的消息是:"+message.getPayload()+"\t"+serverPort);
    }

}

此就可以监听到生产者studyExchange交换机中的消息

Stream之消息重复消费与解决办法

再创建一个消费者,配置和8802一模一样,此时这两个消费者就会产生重复消费问题

在这里插入图片描述
故障现象:重复消费
导致原因:默认分组group组流水号是不同的,被认为是不同组,导致重复消费

在这里插入图片描述
解决办法:自定义配置分组分为同一个组
在这里插入图片描述
在这里插入图片描述
在两个服务的yml配置中 指定相同的分组名,就可将其划分至同一分组
在这里插入图片描述

此时就只存在一个分组,不会再出现重复消费

在这里插入图片描述

配置了group属性的才有消息队列持久化

Sleuth

在这里插入图片描述

SpringCloud Sleuth提供了一套完整的服务跟踪的解决方案,在分布式系统中提供追踪解决方案并且兼容支持了zipkin

end

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值