本文是 Spring Cloud 专栏的第五篇文章, 了解前四篇文章内容有助于更好的理解本文:
Spring Cloud 第一篇 | Spring Cloud 前言及其常用组件介绍概览
Spring Cloud 第二篇 | 使用并认识 Eureka 注册中心
Spring Cloud 第三篇 | 搭建高可用 Eureka 注册中心
Spring Cloud 第四篇 | 客户端负载均衡 Ribbon
一, 微服务高可用技术
大型复杂的分布式系统中, 高可用相关的技术架构非常重要.
高可用架构非常重要的一个环节, 就是如何将分布式系统中的各个服务打造成高可用的服务, 从而足以应对分布式系统环境中的各种各样的问题, 避免整个分布式系统被某个服务的故障给拖垮.
比如:
服务间的调用超时
服务间的调用失败
要解决这些棘手的分布式系统可用性问题, 就涉及到了高可用分布式系统中的很多重要的技术, 包括:
资源隔离
限流与过载保护
熔断
优雅降级
容错
超时控制
监控运维
二, 服务降级, 熔断, 限流概念
1, 服务雪崩效应
服务雪崩效应产生与服务堆积在同一个线程池中, 因为所有的请求都是同一个线程池进行处理, 这时候如果在高并发情况下, 所有的请求全部访问同一个接口, 这时候可能会导致其他服务没有线程进行接受请求, 这就是服务雪崩效应效应.
2, 服务降级
在高并发情况下, 防止用户一直等待, 使用服务降级方式(直接返回一个友好的提示给客户端, 调用 fallBack 方法)
3, 服务熔断
熔断机制目的为了保护服务, 在高并发的情况下, 如果请求达到一定极限 (可以自己设置阔值) 如果流量超出了设置阈值, 然后直接拒绝访问, 保护当前服务. 使用服务降级方式返回一个友好提示, 服务熔断和服务降级一起使用
4, 服务隔离
因为默认情况下, 只有一个线程池会维护所有的服务接口, 如果大量的请求访问同一个接口, 达到 tomcat 线程池默认极限, 可能会导致其他服务无法访问.
解决服务雪崩效应: 使用服务隔离机制 (线程池方式和信号量), 使用线程池方式实现隔离的原理: 相当于每个接口(服务) 都有自己独立的线程池, 因为每个线程池互不影响, 这样的话就可以解决服务雪崩效应.
线程池隔离:
每个服务接口, 都有自己独立的线程池, 每个线程池互不影响.
信号量隔离:
使用一个原子计数器 (或信号量) 来记录当前有多少个线程在运行, 当请求进来时先判断计数器的数值, 若超过设置的最大线程个数则拒绝该请求, 若不超过则通行, 这时候计数器 + 1, 请求返回成功后计数器 - 1.
5, 服务限流
服务限流就是对接口访问进行限制, 常用服务限流算法令牌桶, 漏桶. 计数器也可以进行粗暴限流实现.
三, Hystrix 简单介绍
Hystrix 是国外知名的视频网站 Netflix 所开源的非常流行的高可用架构框架. Hystrix 能够完美的解决分布式系统架构中打造高可用服务面临的一系列技术难题.
Hystrix "豪猪", 具有自我保护的能力. hystrix 通过如下机制来解决雪崩效应问题.
在微服务架构中, 我们把每个业务都拆成了单个服务模块, 然后当有业务需求时, 服务间可互相调用, 但是, 由于网络原因或者其他一些因素, 有可能出现服务不可用的情况, 当某个服务出现问题时, 其他服务如果继续调用这个服务, 就有可能出现线程阻塞, 但如果同时有大量的请求, 就会造成线程资源被用完, 这样就可能会导致服务瘫痪, 由于服务间会相互调用, 很容易造成蝴蝶效应导致整个系统宕掉. 因此, 就有人提出来断路器来解决这一问题.
资源隔离: 包括线程池隔离和信号量隔离, 限制调用分布式服务的资源使用, 某一个调用的服务出现问题不会影响其他服务调用.
降级机制: 超时降级, 资源不足时 (线程或信号量) 降级, 降级后可以配合降级接口返回托底数据.
融断: 当失败率达到阀值自动触发降级(如因网络故障 / 超时造成的失败率高), 熔断器触发的快速失败会进行快速恢复.
缓存: 提供了请求缓存, 请求合并实现.
四, Hystrix 环境搭建
1, 添加依赖
- <!-- hystrix 断路器 -->
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
- </dependency>
2, 在主类上添加注解 @EnableCircuitBreaker
3, 在调用远程的服务的方法上添加 @HystrixCommand(fallbackMethod="error")
4, 服务消费者 Hystrix 测试
Hystrix 默认超时时间为 1000 毫秒, 如果后端响应超过 1000ms, 就会触发熔断
修改默认超时时间:
@HystrixCommand(fallbackMethod="error",commandProperties={@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3500")})//fallbackMethod 如果发生熔断, 调用 error()方法
5,Hystrix 的服务降级
有了服务的熔断, 随之就会有服务的降级, 所谓服务降级, 就是当某个服务熔断之后, 服务端提供的服务将不再被调用, 此时由客户端自己准备一个本地的 fallback 回调, 返回一个默认值来代表服务端的返回, 这种做法, 虽然不能得到正确的返回结果, 但至少保证了服务的可用, 比直接抛出错误或服务不可用要好很多, 当然这需要根据具体的业务场景来选择
6,Hystrix 的异常处理
我们在调用服务提供者时, 我们自己也有可能会抛异常, 默认情况下方法拋了异常会自动进行服务降级, 交给服务降级中的方法去处理.
当我们自己发生异常后, 只需要在服务降级方法中添加一个 Throwable 类型的参数就能够获取到抛出的异常的类型, 如下
- public String error(Throwable throwable){
- System.out.println("异常信息:"+throwable.getMessage());
- // 访问远程服务失败, 该如何处理, 这些处理逻辑就可以写在该方法中
- return "ERROR";
- }
此时我们可以在控制台看到异常的类型; 如果远程服务有一个异常抛出后我们不希望进入到服务降级方法中去处理, 而是直接将异常抛给用户, 那么我们可以在 @HystrixCommand 注解中忽略异常, 如下:
@HystrixCommand(fallbackMethod="error",ignoreExceptions = Exception.class)
7, 自定义 Hystrix 请求的服务异常熔断处理
我们也可以自定义类继承自 HystriXCommand 来实现自定义的 Hystrix 请求, 在 getFallback 方法中调用 getEXecutionException 方法来获取服务抛出的异常
com.netflix.hystrix.HystrixCommand.Setter.withGroupkey(HystrixCommandGrouke.Factory.asKey("")
8, 请求缓存
请求缓存是在同一请求多次访问中保证只调用一次这个服务提供者的接口, 在这同一次请求第一次的结果会被缓存, 保证同一请求中同样的多次访问返回结果相同.
请求缓存有两种方式实现:
第一种方式是通过自定义类继承 HystriXCommand 覆盖 getCacheKey 方法开启缓存
第二种方式使用注解的形式, 和缓存相关的注解一共有三个, 分别是 @CacheResult,@CacheKey 和 @CacheRemove
具体操作参考: https://segmentfault.com/a/1190000011468804
9, 请求合并
在微服务架构中, 我们将一个项目拆分成很多个独立的模块, 这些独立的模块通过远程调用来互相配合工作, 但是在高并发情况下, 通信次数的增加会导致总的通信时间增加, 同时线程池的资源也是有限的, 高并发环境会导致有大量的线程处于等待状态, 进而导致响应延迟, 为了解决这些问题, 我们需要来了解 Hystrix 的请求合并.
Hystrix 的请求合并就是把重复的请求批量的用一个 HystrixCommand 命令去执行, 以减少通信消耗和线程数的占用. Hystrix 的请求合并用到了 HystrixCollapser 这个抽象类, 它在 HystrixCommand 之前放置一个合并处理器, 将处于一个很短的时间窗 (默认 10ms) 内对同一依赖服务的多个请求进行整合并以批量方式发起请求的功能.
具体操作参考: https://segmentfault.com/a/1190000011468804
详细参考案例源码: https://gitee.com/coding-farmer/spirngcloud-learn
来源: https://www.cnblogs.com/coding-farmer/p/12020693.html