什么是服务雪崩?
参考: <<重新定义spring cloud>>
代码:https://gitee.com/08081/hello-springcloud/tree/springcloud-fallback/
在微服务中,我们是服务于服务之间调用,当在微服务突然有大量的请求过来,一个服务瘫痪之后,后面的服务的请求积压,这就造成了服务雪崩!
一个服务瘫痪,另外调用这个服务的服务就会超时,导致整个服务群瘫痪.
造成雪崩的原因可以归结为以下三点:
1. 服务提供者不可用 (硬件故障,程序bug 缓存击穿,用户大量请求)
2. 重试加大流量(用户重试,代码逻辑重试)
3. 服务调用者不可用(同步等待造成的资源耗尽)
最终的结果就是一个服务不可用,导致一系列的服务不可用,这种后果无法预料
如何解决在灾难性雪崩效应?
1.降级: 超时降级资源不足时(线程或信号量)降级,降级后可以配合降级接口返回托底数据,实现一个fallback方法,当请求出现异常之后,调用这个方法
2.隔离(线程池隔离和信号量隔离): 限制调用分布式服务的资源使用,某一个调用的服务出现问题不会影响其他服务调用
3.熔断: 当失败率(如因网络故障/超时造成的失败率高)达到阈值自动触发降级.熔断器触发的快速失败会进行快速恢复
4.缓存:提供请求缓存
5.提供请求合并
解决方案之一服务降级
先说遇到的坑吧
1. 坑1 mapping 报错.feign调用服务端的时候说实例已经实例过了
当时报错的时候错误信息没有记录.具体参考的是这篇文章 感谢作者.https://my.oschina.net/u/2000675/blog/2244769
//@FeignClient(name = "book-service",fallback = BookServiceFallback.class,path = "/")
2.坑2 在写好fallback方法之后,一直调用fallback方法,不调用原来的方法.不知为什么hystrix的默认时间一直1秒钟
尝试配置了很多次.最终结合官网解决:
feign: hystrix: enabled: true #局部配置 这个必须加 client: config: default: connectTimeout: 5000 readTimeout: 5000 loggerLevel: basic hystrix: command: default: execution: isolation: thread: timeoutInMilliseconds: 2000
3.坑3: 使用fallbackFactory报错
要是因为我返回空的list还去添加
代码
如何下载代码运行案例?
1.到码云下下载项目
2.运行ch3-eureka-ribbon 下的eureka-server
3.运行ch4-fegin下的ch4-book-service
4.运行ch6-hystrix下的book-consumer-hystrix
我创建了一个子项目book-consumer-hystrix
pom文件如下:
spring: application: name: book-hystrix eureka: client: service-url: defaultZone: http://127.0.0.1:8888/eureka/ instance: prefer-ip-address: true server: port: 8002 feign: hystrix: enabled: true #局部配置 client: config: default: connectTimeout: 5000 readTimeout: 5000 loggerLevel: basic hystrix: command: default: execution: isolation: thread: timeoutInMilliseconds: 5000
启动类:
/** * Created by xiaodao * date: 2019/7/18 */ @SpringBootApplication @EnableDiscoveryClient @EnableFeignClients @EnableHystrix public class BookConsumerHystrixApplication { public static void main(String[] args) { SpringApplication.run(BookConsumerHystrixApplication.class,args); } }
feignClient调用类:
注意坑2.的解决方案:必须加path
/** * Created by xiaodao * date: 2019/7/17 */ @Component //@FeignClient(name = "book-service",fallback = BookServiceFallback.class,path = "/") @FeignClient(name = "book-service",fallbackFactory = WebFeignFallbackFactory.class,path = "/") public interface BookService extends BookApi { }
controller 调用类:
/** * Created by xiaodao * date: 2019/7/17 */ @RestController @RequestMapping("/book") @AllArgsConstructor public class BookController { private BookService bookService; @GetMapping("/findList") public List<Book> findList(){ return bookService.findList(); } }
fallback的俩个类
一个是正常的fallback
另一个是打印错误日志的fallbackFactory
/** * Created by xiaodao * date: 2019/7/18 */ @Component public class BookServiceFallback implements BookService { public List<Book> findList() { return Collections.emptyList(); } }
/** * Created by xiaodao * date: 2019/7/18 * 这个fallback工厂类为了查看回退的原因的异常信息 */ @Component public class WebFeignFallbackFactory implements FallbackFactory<BookService> { private static final Logger LOGGER = LoggerFactory.getLogger(WebFeignFallbackFactory.class); @Override public BookService create(Throwable throwable) { return new BookService(){ // 日志最好放在各个fallback方法中,而不要直接放在create方法中 @Override public List<Book> findList() { WebFeignFallbackFactory.LOGGER.info("fallback; reason was:", throwable); Book book = Book.builder().id(1000).name("异常回退!").build(); List<Book> list = new ArrayList<>(); list.add(book); return list; }; }; } }
以下4种情况触发fallback调用
1.方法抛出非HystrixBadRequestException异常
2.方法调用超时
3.熔断器开启拦截调用
4.线程池/信号量/队列是否跑满
解决方案之一服务请求缓存
hystrix 有俩个缺点:
1.不支持集群
2.不支持第三方缓存
所以我们做集成第三方缓存,并且可以支持集群.所以我们选用redis, 在书上,和网上的案例很多都是基于hystrixCommand 来实现的,我们来来模仿真实案例来实现.
ch6下创建俩个项目
1.book-consumer-hystrix-cache
2.book-service-cache
启动eurekaserver服务
代码
book-consumer-hystrix-cache服务
controller 添加了俩个方法
@RestController @RequestMapping("/book-consumer") @AllArgsConstructor public class BookController { private BookService bookService; @GetMapping("/findList") public List<Book> findList(){ return bookService.findList(); } @GetMapping("get/{id}") public Book get(@PathVariable(value = "id") Integer id ){ return bookService.get(id); } @GetMapping("del/{id}") public Book del(@PathVariable(value = "id") Integer id ){ return bookService.del(id); } }
service:
@FeignClient(name = "book-service-cache",path = "/") public interface BookService extends BookApi { }
book-service-cache 服务
pom文件 依赖
<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-data-redis</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency>
bootstrap.yml配置了redis的连接池
spring: application: name: book-service-cache #redis的索引默认是0 redis: database: 1 host: localhost port: 6379 password: lettuce: #负数表示没有限制 pool: max-active: 100 #最大空闲连接 max-idle: 10 #连接池最大阻塞等待时间(使用负数表示没有限制) max-wait: -1ms #连接池最小空闲连接 min-idle: 0 eureka: client: service-url: defaultZone: http://127.0.0.1:8888/eureka/ instance: prefer-ip-address: true server: port: 8000
bookServiceImpl
/** * Created by xiaodao * date: 2019/7/17 */ @RestController @RequestMapping @CacheConfig(cacheNames = {"xiaodao.book"}) public class BookServiceImpl implements BookApi { // @GetMapping(value = "list") @Override public List<Book> findList() { try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } Book book =Book.builder().id(1).name("第一本书").build(); System.out.println(book); List<Book> list = new CopyOnWriteArrayList<>(); list.add(book); return list; } @Cacheable(key = "'get-' + #id") @Override public Book get(Integer id) { System.out.println("-----查询------"+id+"-------"); return Book.builder().id(id).name("使用缓存").build(); } @CacheEvict(key = "'get-'+ #id") @Override public Book del(Integer id) { System.out.println("------删除-----"+id+"-------"); return Book.builder().id(id).name("使用缓存").build(); } }
还有book-api也需要改造一下.
就是加了俩个接口.
public interface BookApi { @GetMapping(value = "/book/list") List<Book> findList(); @GetMapping(value = "/book/get/{id}") Book get(@PathVariable(value = "id") Integer id ); @GetMapping(value = "/book/del/{id}") Book del(@PathVariable(value = "id") Integer id ); }
上面基本就是缓存方案的所有代码的所有代码
贴一张注册中心的图:
http://localhost:8002/book-consumer/get/100
{"id":100,"name":"使用缓存"}
在后台看到无论请求多少遍.只有第一遍,会加载后去就会到缓存中查找
------删除-----100------- ------删除-----100------- ------删除-----100------- -----查询------100-------
删除的时候回调用多次,这样我们就实现了请求缓存