与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/