SpringCloud-微服务入门之Ribbon-Hystrix(2)

x33g5p2x  于2022-05-16 转载在 Spring  
字(7.0k)|赞(0)|评价(0)|浏览(523)

确保你已经学完了SpringCloud-微服务入门之Ribbon(1)

项目结构图

什么是Ribbon

Spring Cloud Ribbon 是一个客户端的负载均衡器,它提供对大量的HTTP和TCP客户端的访问控制。

客户端负载均衡即是当浏览器向后台发出请求的时候,客户端会向 Eureka Server 读取注册到服务器的可用服务信息列表,然后根据设定的负载均衡策略(没有设置即用默认的),抉择出向哪台服务器发送请求。

Ribbon在工作时分成两步:

第一步先选择EurekaServer ,它优先选择在同一个区域内负载较少的server。
第二步再根据用户指定的策略,在从server取到的服务注册列表中选择一个地址。
其中Ribbon提供了多种策略:比如轮询、随机和根据响应时间加权。

我简单比喻一下 : 就比如userService 这个服务 如果1000个人同事访问这一个服务的话 是不是很慢 但是在一般情况下 往往会开启很多个 userService 的集群。此时获取的服务列表中就会有多个,到底该访问哪一个呢? ,一般这种情况下就需要编写负载均衡算法,在多个实例列表中进行选择 ,而Ribbon默认就是轮询算法

轮询算法 :
就是依据多个服务器的端口号 从小到大 或者从大到小 依次执行 等所有这个服务器都执行一遍然后在从头开始,比如我集群了10个userService 服务器 端口号 为: 9080~9090 现在有100个人访问,那么 第一个人访问 第一个userService :9080 第二个人访问userService :9081等等以此类推一直到userService :9090 然后在从头开始这就是轮询算法

随机算法
就是写一个随机数抽到哪一个服务就是哪一个,我们可以通过配置进行修改

user-service:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

使用Ribbon

因为Eureka中已经集成了Ribbon,所以我们无需引入新的依赖 但是我们需要手动开启。

在之前的项目中我们已经创建了一个producer生产者那么我们在创建2个相同的消费者
producer: 9010 ,producer: 9011 ,producer: 9012

然后都启动

然后就是修改调用方consumer(消费者)的启动类里的 RestTemplate 这个方法上添加负载均衡注解@LoadBalanced

然后在consumer服务Controller层中我们把请求方式改一下无须在获取ip 和端口号了而是 通过producer 生产者的服务器的名称来获取

@RestController
@RequestMapping("/user")
public class ConsumerUserController {
    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/getUserAll")
    @ResponseBody
    public ResponseEntity<List<UserEneity>> getUserAll(){
        String url = "http://producer/user/all" ;
        List<UserEneity> list = restTemplate.getForObject(url,List.class);
        return ResponseEntity.ok(list);
    }
}

然后重启消费者服务,之后访问http://localhost:9001/user/getUserAll

如果想测试到底负载均衡了没,你可以在所有的生产者的Controller中加一句打印System.out.println("我是服务:producer"); 然后重启所有的生产者,之后在通过消费者进行疯狂的请求测试就能看出来了

Ribbon重试

如果我们将其中n台生产者关闭了,然后立即去访问,那么会出现什么情况呢?

我们将producer:9010 , producer:9012 这个2生产者服务关闭了,现在还有1台生产者存活中

然后赶紧疯狂的使用消费者去请求生产者,就会出现下面报错,如果你没出来,就反复按照上流程进行


这是什么问题呢? 我明明还有一个还有1台生产者存活中打开着呢?
Eureka的服务治理强调了CAP原则中的AP,即可用性和可靠性。它与Zookeeper这一类强调CP(一致性,可靠性)的服务治理框架最大的区别在于:Eureka为了实现更高的服务可用性,牺牲了一定的一致性,极端情况下它宁愿接收故障实例也不愿丢掉健康实例,正如我们上面所说的自我保护机制。所以此时如果我们调用了这些不正常的服务,调用就会失败,从而导致一系列的其它服务都不能正常工作!这显然不是我们愿意看到的。

解决办法:使用Ribbon重试
原理就是遇到事故服务,自动切换到下一个服务,直到遇到正常的服务为止,或者超过了切换次数

application-common.yml中添加如下配置:

# 设置服务名称  会在Eureka中显示 而且其他方也要用的
spring:
  cloud:
    loadbalancer:
      retry:
        enabled: true # 开启Spring Cloud的重试功能

# 服务异常重试配置
ribbon:
  ConnectTimeout: 1000 # Ribbon的连接超时时间
  ReadTimeout: 5000 # Ribbon的数据读取超时时间
  OkToRetryOnAllOperations: true  #默认只对GET请求重试, 当设置为true时, 对POST等所有类型请求都重试
  MaxAutoRetriesNextServer: 1 # 最大重试次数
  MaxAutoRetries: 2 # 切换实例的重试次数
  ServerListRefreshInterval: 2000  #Ribbon更新服务注册列表的频率

然后我们在需要重试的consumer服务 添加如下依赖就能开启重试:

<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
</dependency>

熔断

通过配置了重试配置难道就没问题了吗?

计算重试的次数:

MaxAutoRetries+MaxAutoRetriesNextServer+(MaxAutoRetries *MaxAutoRetriesNextServer)

1+2+(1*2)= 5(次) 即重试4次 则一共产生5次调用 。 最坏的结果

计算用户需要等待多少时间:
重试次数*ReadTimeout
5*5000= 25000(毫秒) =25(秒) 最坏的结果

通过以上的计算结果我们发现用户最大需要等待25秒才能返回结果,这是肯定不允许发送的事情,非常影响用户的体验的解决办法添加熔断器

什么是熔断器

那么Hystrix的作用是什么呢?具体要保护什么呢?

Hystrix是Netflflix开源的一个延迟和容错库,用于隔离访问远程服务、第三方库,防止出现级联失败

在大型的分布式系统中,通常需要调用或操作远程的服务或者资源,这些远程的服务或者资源由于调用者不可以控的原因比如网络连接缓慢,资源被占用或者暂时不可用等原因,导致对这些远程资源的调用失败。这些错误通常在稍后的一段时间内可以恢复正常。

比如:
用户的一次请求 然后SpringCloud 向多个不同功能的服务器发送请求然后将请求的结果经过处理后返回给请求的用户,如果一次业务请求,需要调用A、P、H、I四个服务,这四个服务又可能调用其它服务。如果此时,某个服务出现异常:

例如: 服务器I发生异常,那么用户请求会阻塞一段时间 不断的进行重试连接 持续一段时间大概也就2~5秒 ,则tomcat的这个线程不会释放 ,在这个段时间内 越来越多的用户请求到来 , 则越来越多的线程会阻塞:

服务器支持的线程和并发数有限,请求一直阻塞,会导致服务器资源耗尽,从而导致当前服务的所有其它功能都不可用,形成雪崩效应。

这就好比,一个汽车生产线,生产不同的汽车,需要使用不同的零件,如果某个零件因为种种原因无法使用,那么就会造成整台车无法装配,陷入等待零件的状态,直到零件到位,才能继续组装。 此时如果有很多个车型都需要这个零件,那么整个工厂都将陷入等待的状态,导致所有生产都陷入瘫痪。一个零件的波及范围不断扩大。

Hystrix解决雪崩问题的手段主要是服务降级
线程隔离

Hystrix为每个依赖服务调用分配一个小的线程池,如果线程池已满调用将被立即拒绝,默认不采用排队
失败判定时间

弊端就是,如果人多的时候,小的线程池就会爆满其他用户就访问不进入 ,可以设置maxQueueSize 和 queueSizeRejectionThreshold 来开启线程队列和队列数量

如果线程池已满,或者请求超时,则会进行降级处理,什么是服务降级?

用户的请求故障时,不会被阻塞,更不会无休止的等待或者看到系统崩溃,而是返回一个执行结果(例如: 返回友好的提示信息,然后发送应急邮件通知管理员进行维护…)。
服务降级虽然会导致请求失败,但是不会导致阻塞,而且对当前服务的其他应用没有影响, 比如我当前A服务有3个接口,如果其中一个接口出问题了,那么其他2个接口还可以正常使用

配置熔断

因为Hystrix的默认超时时长为1秒有点太快了 只要网速稍微有点卡请求就超过1秒 导致请求失败,我们可以通过配置修改这个值;

hystrix访问超时计算ReadTimeout+(MaxAutoRetries * ReadTimeout) Ribbon的超时时间 < Hystrix熔断的超时。

按照我们自己的配置计算如下: 5000+(2*5000)*5000=20000(毫秒)

线程池大小 = 服务响应时长(s) * 每秒请求量 + 冗余缓冲值

基本上服务响应大部分都是秒级的,请求量可以预估一下,冗余缓冲值就是留点余地

1*30+20=40 (基本上足够了日常所需)

application-common.yml中添加如下配置:

hystrix:
  command:
    default:
      coreSize: 20    # 并发执行的核心线程数,默认10
      maximumSize: 20 #并发执行的最大线程数
      maxQueueSize: 50 #并发执行的最大线程数
      queueSizeRejectionThreshold: 50 #  队列截断阈值  ,超过可就不允许了 ,和maxQueueSize一致
      keepAliveTimeMinutes: 3   #分钟, 线程空闲存活时间
      circuitBreaker:
        errorThresholdPercentage: 50 # 触发熔断错误比例阈值,默认值50%
        sleepWindowInMilliseconds: 5000 # 短路多久以后开始尝试是否恢复,默认5s
        requestVolumeThreshold: 10 # 熔断触发最小请求次数,默认值是20
      execution:
        isolation:
          semaphore:
            maxConcurrentRequests: 20 # 允许的最大并发请求数  默认10  一般和coreSize设置一样   如果达到最大并发数时,后续请求会被拒绝 直接熔断
          thread:
            timeoutInMilliseconds: 20000   # 访问超时时间

在consumer服务中添加如下依赖配置:

<!--     微服务重试  -->
        <dependency>
            <groupId>org.springframework.retry</groupId>
            <artifactId>spring-retry</artifactId>
        </dependency>
        <!--     微服务熔断  -->
        <dependency>
            <groupId>com.netflix.hystrix</groupId>
            <artifactId>hystrix-javanica</artifactId>
        </dependency>

然后在consumer服务的启动类上添加@EnableCircuitBreaker注解

配置降级

降级规则: 方法的返回类型和原来的方法一致,否则将会报错hystrix Fallback type literal pos: unknown

方法降级

修改consumer服务的Controller代码

@RestController
@RequestMapping("/user")
public class ConsumerUserController {
    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/getUserAll")
    @ResponseBody
    @HystrixCommand(fallbackMethod = "getUserAllFallback")
    public ResponseEntity<List<UserEneity>> getUserAll(){
        String url = "http://producer/user/all" ;
        List<UserEneity> list = restTemplate.getForObject(url,List.class);
        return ResponseEntity.ok(list);
    }

    //访问失败后执行的方法
    public ResponseEntity<List<UserEneity>> getUserAllFallback()  {
        //发送邮件.....给管理员
        //进行报警
        return ResponseEntity.ok(new ArrayList<UserEneity>());
    }
}

然后从新启动consumer服务服务

如何测试呢?

  1. 修改降级时间为1000毫秒
  2. 我们关闭生产者2个服务producer
  3. 然后使用消费者去访问,直接触发降级

统一降级设置

如果每一个接口我们都设置一个降级那么、会导致代码膨胀,我们可以针对一个Controller去设置默认的降级规则,然后在需要降级的方法上添加@HystrixCommand就行了,前提条件是降级的方法和降级规则方法返回类型必须保持一致

@RestController
@RequestMapping("/user")
@DefaultProperties(defaultFallback = "consumerUserControllerFallback")
public class ConsumerUserController {
    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/getUserAll")
    @ResponseBody
    @HystrixCommand   //不要忘记添加
    public ResponseEntity getUserAll(){
        String url = "http://producer/user/all" ;
        List<UserEneity> list = restTemplate.getForObject(url,List.class);
        return ResponseEntity.ok(list);
    }
    @GetMapping("/getUser")
    @ResponseBody
    @HystrixCommand   
    public ResponseEntity getUser(){
        String url = "http://producer/user/all" ;
        List<UserEneity> list = restTemplate.getForObject(url,List.class);
        return ResponseEntity.ok(list.get(0));
    }
    //访问失败后执行的方法
    public ResponseEntity consumerUserControllerFallback()  {
        //发送邮件.....给管理员
        //进行报警
        return ResponseEntity.ok(new ArrayList<UserEneity>());
    }
}

小提示

可以看到,我们类上的注解越来越多,在微服务中,经常会引入上面的三个注解,于是Spring就提供了一个组合注解:@SpringCloudApplication 这个注解里面包含了上面三个注解

@SpringCloudApplication
public class ConsumerApplication {
//............
}

点赞 -收藏-关注-便于以后复习和收到最新内容有其他问题在评论区讨论-或者私信我-收到会在第一时间回复如有侵权,请私信联系我感谢,配合,希望我的努力对你有帮助^_^

相关文章