與Eureka和Ribbon同樣,Hystrix也是Netfix開源的一個框架,中文名:容錯保護系統。SpringCloudHystrix實現了斷路器、線程隔離等一系列服務保護功能。在微服務架構中,每一個單元都在不一樣的進程中運行,進程間經過遠程調用的方式相互依賴,這樣就可能由於網絡的緣由出現調用故障和延遲,若是調用請求不斷增長,將會致使自身服務的癱瘓。爲了解決這些問題,產生了斷路器等一系列服務保護機制。斷路器詳細介紹:斷路器<!--more-->html
直接使用上一篇:SpringCloud學習之Ribbon,在article-service中添加。
pom文件java
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix</artifactId> </dependency>
在主類上添加@EnableCircuitBreaker
或@EnableHystrix
註解開啓Hystrix的使用。git
@SpringBootApplication @EnableEurekaClient @EnableCircuitBreaker public class ArticleApplication { @Bean @LoadBalanced public RestTemplate restTemplate(){ return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(ArticleApplication.class, args); } }
這裏也可使用@SpringCloudApplication
註解,該註解已經包含了咱們添加的三個註解,因此能夠看出SpringCloud的標準應用應該包服務發現和斷路器
而後在ArticleController添加方法,並添加@HystrixCommand
定義服務降級,這裏的fallbackMethod
服務調用失敗後調用的方法。spring
/** * 使用Hystrix斷路器 * @param id * @return */ @HystrixCommand(fallbackMethod = "fallback") @GetMapping("/hystrix/{id}") public String findUserHystrix(@PathVariable("id") Long id){ return restTemplate.getForObject("http://USER-SERVICE/user/{1}",User.class,id).toString(); } private String fallback(Long id){ return "Error:"+id; }
重啓服務,若是沒有出現故障,這裏是能夠正常訪問並返回正確的數據。下面將服務接口sleep來模擬網絡延遲:數據庫
@RestController public class UserController { @Autowired private UserRepository userRepository; @GetMapping("/user/{id}") public User findById(@PathVariable("id") Long id) throws InterruptedException { Thread.sleep(5000); return userRepository.findOne(id); } }
訪問:http://localhost:30000/hystrix/3,這裏會調用回調函數返回數據。緩存
經過上面的使用,發現一個問題:使用這種方法配置服務降級的方式,回調函數的入參和返回值必須與接口函數的一直,否則會拋出異常。網絡
上面使用註解方式配置很是簡單。在Hystrix中咱們也能夠經過繼承HystrixCommand
來實現自定義的HystrixCommand
,並且還支持同步請求和異步請求兩種方式。架構
建立UserCommand並繼承HystrixCommand,實現run方法:併發
public class UserCommand extends HystrixCommand<User> { private final Logger logger = LoggerFactory.getLogger(UserCommand.class); private RestTemplate restTemplate; private Long id; public UserCommand(Setter setter,RestTemplate restTemplate,Long id){ super(setter); this.restTemplate = restTemplate; this.id = id; } @Override protected User run() throws Exception { logger.info(">>>>>>>>>>>>>自定義HystrixCommand請求>>>>>>>>>>>>>>>>>>>>>>>>>>"); return restTemplate.getForObject("http://USER-SERVICE/user/{1}",User.class,id); } }
而後添加一個接口app
@GetMapping("/command/{id}") public User findUserCommand(@PathVariable("id") Long id) throws ExecutionException, InterruptedException { com.netflix.hystrix.HystrixCommand.Setter setter = com.netflix.hystrix.HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("")); UserCommand userCommand = new UserCommand(setter,restTemplate,id); //同步調用 // User user = userCommand.execute(); //異步請求 Future<User> queue = userCommand.queue(); User user = queue.get(); return user; }
Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey(""))
是設置自定義命令的參數。先調用withGroupKye
來設置分組,而後經過asKey來設置命令名;由於在Setter的定義中,只有withGroupKye靜態函數能夠建立Setter實例,因此GroupKey是Setter必需的參數。深刻介紹能夠查看源碼或者看DD大佬的《SpringCloud微服務實戰》。查看@HystrixCommand
註解源碼,能夠看到這裏也有groupKey、commandKey等參數,這也就是說使用@HystrixCommand註解時是能夠配置命令名稱、命令分組和線程池劃分等參數的。
上面自定義命令中能夠實現異步,一樣也能夠直接使用註解來實現異步請求;
HystrixCommandAspect
的Bean@Bean public HystrixCommandAspect hystrixCommandAspect(){ return new HystrixCommandAspect(); }
@HystrixCommand @GetMapping("/async/{id}") public Future<User> findUserAsync(@PathVariable("id") Long id){ return new AsyncResult<User>() { @Override public User invoke() { return restTemplate.getForObject("http://USER-SERVICE/user/{1}",User.class,id); } }; }
查看@HystrixCommand
註解源碼能夠發現裏面有個ignoreExceptions
參數。該參數是定義忽略指定的異常功能。以下代碼,當方法拋出NullPointerException
時會將異常拋出,而不觸發降級服務。
@HystrixCommand(fallbackMethod = "fallback",ignoreExceptions = {NullPointerException.class}) @GetMapping("/hystrix/{id}") public User findUserHystrix(@PathVariable("id") Long id){ return restTemplate.getForObject("http://USER-SERVICE/user/{1}",User.class,id); }
HystrixCommand
類中重寫getFallback()
方法,這裏在run方法中添加弄出一個異常@Override protected User getFallback() { Throwable e = getExecutionException(); logger.info(">>>>>>>>>>>>>>>>>>>>>{}<<<<<<<<<<<<<<<<",e.getMessage()); return new User(-1L,"",-1); } @Override protected User run() throws Exception { logger.info(">>>>>>>>>>>>>自定義HystrixCommand請求>>>>>>>>>>>>>>>>>>>>>>>>>>"); int i = 1/0; return restTemplate.getForObject("http://USER-SERVICE/user/{1}",User.class,id); }
@HystrixCommand(fallbackMethod = "fallback") @GetMapping("/hystrix/{id}") public User findUserHystrix(@PathVariable("id") Long id){ int i = 1/0; return restTemplate.getForObject("http://USER-SERVICE/user/{1}",User.class,id); } private User fallback(Long id,Throwable throwable){ LoggerFactory.getLogger(ArticleController.class).info("========{}=============",throwable.getMessage()); return new User(); }
在高併發的場景下,Hystrix中提供了請求緩存的功能,能夠方便的開啓和使用請求緩存來優化系統,達到減輕高併發時的請求線程消耗、下降請求相應時間。
在繼承了HystrixCommand
類中重寫getCacheKey()
方法
@Override protected String getCacheKey() { return String.valueOf(id); } public UserCommand(RestTemplate restTemplate,Long id){ super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("userGroup"))); this.restTemplate = restTemplate; this.id = id; }
經過getCacheKey()方法返回請求的Key值,Hystrix會根據getCacheKey返回的值來區分是不是重複請求,若是cacheKey相同,那麼該依賴服務只會在第一個請求達到時被真實的調用,另外一個請求則是直接從請求緩存中返回結果。
修改後的接口類,該方法第一句爲初始化HystrixRequestContext,若是不初始化該對象會報錯。這裏是在測試環境,若是在真正項目中該初始化不該該在指定方法中。
@GetMapping("/command/{id}") public User findUserCommand(@PathVariable("id") Long id) throws ExecutionException, InterruptedException { HystrixRequestContext.initializeContext(); UserCommand u1 = new UserCommand(restTemplate,id); UserCommand u2 = new UserCommand(restTemplate,id); UserCommand u3 = new UserCommand(restTemplate,id); UserCommand u4 = new UserCommand(restTemplate,id); User user1 = u1.execute(); System.out.println("第一次請求"+user1); User user2 = u2.execute(); System.out.println("第二次請求"+user2); User user3 = u3.execute(); System.out.println("第三次請求"+user3); User user4 = u4.execute(); System.out.println("第四次請求"+user4); return user1; }
在SpringCloudHystrix中與緩存有關的三個註解:
設置請求緩存,修改ArticleService方法,
@Service public class ArticleService { @Autowired private RestTemplate restTemplate; @HystrixCommand @CacheResult public User getUserById(Long id){ return restTemplate.getForObject("http://USER-SERVICE/user/{1}",User.class,id); } }
添加接口
@GetMapping("/cache/{id}") public User findUserCache(@PathVariable("id") Long id){ HystrixRequestContext.initializeContext(); User user1 = articleService.getUserById(id); System.out.println("第一次請求"+user1); User user2 = articleService.getUserById(id); System.out.println("第二次請求"+user2); User user3 = articleService.getUserById(id); System.out.println("第三次請求"+user3); User user4 =articleService.getUserById(id); System.out.println("第四次請求"+user4); return articleService.getUserById(id); }
定義緩存的Key
@HystrixCommand @CacheResult public User getUserById(@CacheKey("id") Long id){ return restTemplate.getForObject("http://USER-SERVICE/user/{1}",User.class,id); }
@HystrixCommand @CacheResult(cacheKeyMethod = "getCacheKey") public User getUserById(Long id){ return restTemplate.getForObject("http://USER-SERVICE/user/{1}",User.class,id); } private Long getCacheKey(Long id){ return id; }
上面說經過繼承和註解方式均可以將請求保存到緩存,可是當咱們更新了數據庫的數據,緩存的數據已是過時數據,這時候再次請求,數據已經失效。因此咱們須要更新緩存。在Hystrix中繼承和註解均可以實現清除緩存。
1. 使用繼承方式:前面介紹使用繼承是繼承HystrixCommand,而後再run方法中觸發請求操做,因此這裏建立兩個類進程HystrixCommand,一個實現查詢,一個實現更新。
public class GetUserCommand extends HystrixCommand<User> { private static final Logger logger = LoggerFactory.getLogger(GetUserCommand.class); private static final HystrixCommandKey GETTER_KEY = HystrixCommandKey.Factory.asKey("CommandKey"); private RestTemplate restTemplate; private Long id; public GetUserCommand(RestTemplate restTemplate, Long id) { super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("userGroup"))); this.restTemplate = restTemplate; this.id = id; } @Override protected User run() throws Exception { logger.info(">>>>>>>>>>>>>查詢操做>>>>>>>>>>>>>>>>>>>>>>>>>>"); return restTemplate.getForObject("http://USER-SERVICE/user/{1}", User.class, id); } @Override protected String getCacheKey() { //根據id保存緩存 return String.valueOf(id); } /** * 根據id清理緩存 * @param id */ public static void flushCache(Long id){ logger.info(" >>>>>>>>>>>>>GETTER_KEY:{}>>>>>>>>>>>>>>>>",GETTER_KEY); HystrixRequestCache.getInstance(GETTER_KEY, HystrixConcurrencyStrategyDefault.getInstance()).clear(String.valueOf(id)); } }
public class PostUserCommand extends HystrixCommand<User> { private final Logger logger = LoggerFactory.getLogger(UserCommand.class); private RestTemplate restTemplate; private User user; public PostUserCommand(RestTemplate restTemplate,User user){ super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("userGroup"))); this.restTemplate = restTemplate; this.user = user; } @Override protected User run() throws Exception { logger.info(">>>>>>>>>>>>>更新操做>>>>>>>>>>>>>>>>>>>>>>>>>>"); User user1 = restTemplate.postForEntity("http://USER-SERVICE/u/update", user, User.class).getBody(); //刷新緩存,清理失效的緩存 GetUserCommand.flushCache(user1.getId()); return user1; } }
添加接口:
@GetMapping("/getcommand/{id}") public User testGetCommand(@PathVariable("id") Long id){ GetUserCommand u1 = new GetUserCommand(restTemplate,id); GetUserCommand u2 = new GetUserCommand(restTemplate,id); GetUserCommand u3 = new GetUserCommand(restTemplate,id); GetUserCommand u4 = new GetUserCommand(restTemplate,id); User user1 = u1.execute(); System.out.println("第一次請求"+user1); User user2 = u2.execute(); System.out.println("第二次請求"+user2); User user3 = u3.execute(); System.out.println("第三次請求"+user3); User user4 = u4.execute(); System.out.println("第四次請求"+user4); return user1; } @PostMapping("/postcommand") public User testPostCommand(User user){ HystrixRequestContext.initializeContext(); PostUserCommand u1 = new PostUserCommand(restTemplate,user); User execute = u1.execute(); return execute; }
在上面GetUserCommand方法中添加flushCache的靜態方法,該方法經過HystrixRequestCache.getInstance(GETTER_KEY, HystrixConcurrencyStrategyDefault.getInstance());
方法從默認的Hystrix併發策略中根據GETTER_KEY
獲取到該命令的請求緩存對象HystrixRequestCache,而後再調用clear方法清理key爲id的緩存。
2. 使用註解方式:上面提到了@CacheRemove
註解是使緩存失效
@CacheRemove(commandKey = "getUserById") public User update(@CacheKey("id")User user){ return restTemplate.postForEntity("http://USER-SERVICE/u/update", user, User.class).getBody(); }
@CacheRemove
的commandKey屬性是必須指定的,它用來指明須要使用請求緩存的請求命令,只有經過該屬性的配置,Hystrix才能找到正確的請求命令緩存位置。
使用請求緩存的時候須要注意的是,必須先使用 HystrixRequestContext.initializeContext();
,該方法的調用能夠放到攔截器中執行,這裏由於是測試,因此直接在接口中調用。
做爲SpringCloud學習筆記,有不少地方很差。望指出!!!
源碼地址:https://gitee.com/wqh3520/spring-cloud-1-9/tree/master/