什么是服務雪崩?
參考: <<重新定義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-------
刪除的時候回調用多次,這樣我們就實現了請求緩存