Hystrix 的优化可以从线程
、请求缓存
、线程传递与并发
、命令注解
、Collapser 请求合并
等方面入手优化
Hystrix 线程调整 线程的调整主要依赖于在生产环境中的实际情况与服务器配置进行相对应的调整,由于生产环境不可能完全一致,所以没有一个具体的值。
请求缓存 Hystrix 请求缓存是 Hystrix 在同一个上下文请求中缓存请求结果,与传统缓存有区别。Hystrix 的请求缓存是在同一个请求中进行,在第一次请求调用结束后对结果缓存,然后在接下来同参数的请求会使用第一次的结果。 Hystrix 请求缓存的声明周期为一次请求。传统缓存的声明周期根据时间需要设定,最长可能长达几年。
Hystrix 请求有两种方式:继承 HystrixCommand 类、使用 @HystrixCommand 注解。Hystrix 缓存同时支持这两种方案。
Cache Consumer 源码:https://gitee.com/laiyy0728/spring-cloud/tree/master/spring-cloud-hystrix/spring-cloud-hystrix-cache/spring-cloud-hystrix-cache-impl
pom、yml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <dependencies > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-eureka-client</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-openfeign</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-hystrix</artifactId > </dependency > </dependencies >
1 2 3 4 5 6 7 8 9 10 11 12 13 server: port: 8989 spring: application: name: spring-cloud-hystrix-cache-impl eureka: instance: instance-id: ${spring.application.name}:${server.port} prefer-ip-address: true client: service-url: defaultZone: http://localhost:8761/eureka/
Interceptor 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 public class CacheContextInterceptor implements HandlerInterceptor { private HystrixRequestContext context; @Override public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { context = HystrixRequestContext.initializeContext(); return true ; } @Override public void postHandle (HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion (HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { context.shutdown(); } } @Configuration public class CacheConfiguration { @Bean @ConditionalOnClass (Controller.class) public CacheContextInterceptor cacheContextInterceptor () { return new CacheContextInterceptor(); } @Configuration @ConditionalOnClass (Controller.class) public class WebMvcConfig extends WebMvcConfigurationSupport { private final CacheContextInterceptor interceptor; @Autowired public WebMvcConfig (CacheContextInterceptor interceptor) { this .interceptor = interceptor; } @Override protected void addInterceptors (InterceptorRegistry registry) { registry.addInterceptor(interceptor); } } }
@HystrixCommand 方式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 public interface IHelloService { String hello (int id) ; String getUserToCommandKey (@CacheKey int id) ; String updateUser (@CacheKey int id) ; } @Component public class HelloServiceImpl implements IHelloService { private final RestTemplate restTemplate; @Autowired public HelloServiceImpl (RestTemplate restTemplate) { this .restTemplate = restTemplate; } @Override @CacheResult @HystrixCommand public String hello (int id) { String result = restTemplate.getForObject("http://spring-cloud-hystrix-cache-provider-user/get-user/{1}" , String.class, id); System.out.println("正在进行远程调用:hello " + result); return result; } @Override @CacheResult @HystrixCommand (commandKey = "getUser" ) public String getUserToCommandKey (int id) { String result = restTemplate.getForObject("http://spring-cloud-hystrix-cache-provider-user/get-user/{1}" , String.class, id); System.out.println("正在进行远程调用:getUserToCommandKey " + result); return result; } @Override @CacheRemove (commandKey = "getUser" ) @HystrixCommand public String updateUser (int id) { System.out.println("正在进行远程调用:updateUser " + id); return "update success" ; } }
继承 HystrixCommand 类形式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public class HelloCommand extends HystrixCommand <String > { private RestTemplate restTemplate; private int id; public HelloCommand (RestTemplate restTemplate, int id) { super (HystrixCommandGroupKey.Factory.asKey("springCloudCacheGroup" )); this .id = id; this .restTemplate = restTemplate; } @Override protected String run () throws Exception { String result = restTemplate.getForObject("http://spring-cloud-hystrix-cache-provider-user/get-user/{1}" , String.class, id); System.out.println("正在使用继承 HystrixCommand 方式进行远程调用:" + result); return result; } @Override protected String getFallback () { return "hello command fallback" ; } @Override protected String getCacheKey () { return String.valueOf(id); } public static void cleanCache (int id) { HystrixRequestCache.getInstance( HystrixCommandKey.Factory.asKey("springCloudCacheGroup" ), HystrixConcurrencyStrategyDefault.getInstance()) .clear(String.valueOf(id)); } }
Controller 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 @RestController public class CacheController { private final RestTemplate restTemplate; private final IHelloService helloService; @Autowired public CacheController (RestTemplate restTemplate, IHelloService helloService) { this .restTemplate = restTemplate; this .helloService = helloService; } @GetMapping (value = "/get-user/{id}" ) public String getUser (@PathVariable int id) { helloService.hello(id); helloService.hello(id); helloService.hello(id); helloService.hello(id); return "getUser success!" ; } @GetMapping (value = "/get-user-id-update/{id}" ) public String getUserIdUpdate (@PathVariable int id) { helloService.hello(id); helloService.hello(id); helloService.hello(5 ); helloService.hello(5 ); return "getUserIdUpdate success!" ; } @GetMapping (value = "/get-user-id-by-command/{id}" ) public String getUserIdByCommand (@PathVariable int id) { HelloCommand helloCommand = new HelloCommand(restTemplate, id); helloCommand.execute(); System.out.println("from Cache:" + helloCommand.isResponseFromCache()) ; helloCommand = new HelloCommand(restTemplate, id); helloCommand.execute(); System.out.println("from Cache:" + helloCommand.isResponseFromCache()) ; return "getUserIdByCommand success!" ; } @GetMapping (value = "/get-and-update/{id}" ) public String getAndUpdateUser (@PathVariable int id) { helloService.getUserToCommandKey(id); helloService.getUserToCommandKey(id); helloService.updateUser(id); helloService.getUserToCommandKey(id); helloService.getUserToCommandKey(id); return "getAndUpdateUser success!" ; } }
Cache Service 源码:https://gitee.com/laiyy0728/spring-cloud/tree/master/spring-cloud-hystrix/spring-cloud-hystrix-cache/spring-cloud-hystrix-cache-provider-user
pom、yml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <dependencies > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-eureka-client</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-netflix-hystrix</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-openfeign</artifactId > </dependency > </dependencies >
1 2 3 4 5 6 7 8 9 10 11 12 13 server: port: 9999 spring: application: name: spring-cloud-hystrix-cache-provider-user eureka: instance: prefer-ip-address: true instance-id: ${spring.application.name}:${server.port} client: service-url: defaultZone: http://localhost:8761/eureka/
Controller 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @RestController public class UserController { @GetMapping (value = "/get-user/{id}" ) public User getUser (@PathVariable int id) { switch (id) { case 1 : return new User("zhangsan" , "list" , 22 ); case 2 : return new User("laiyy" , "123456" , 24 ); default : return new User("hahaha" , "error" , 0 ); } } }
验证 验证 @HystrixCommand 注解形式缓存 请求 http://localhost:8989/get-user/2 ,查看控制台输出,发现控制台输出一次:
1 正在进行远程调用:hello {"username":"laiyy","password":"123456","age":24}
在 HelloServiceImpl
中,去掉 hello 方法的 @CacheResult 注解,重新启动后请求,发现控制台输出了 4 次:
1 2 3 4 正在进行远程调用:hello {"username":"laiyy","password":"123456","age":24} 正在进行远程调用:hello {"username":"laiyy","password":"123456","age":24} 正在进行远程调用:hello {"username":"laiyy","password":"123456","age":24} 正在进行远程调用:hello {"username":"laiyy","password":"123456","age":24}
由此验证 @HystrixCommand 注解形式缓存成功
验证 @HystrixCommand 形式中途修改参数 请求 http://localhost:8989/get-user-id-update/2 ,查看控制台,发现控制台输出:
1 2 正在进行远程调用:hello {"username":"laiyy","password":"123456","age":24} 正在进行远程调用:hello {"username":"hahaha","password":"error","age":0}
由此验证在调用 hello 方法时,hello 的参数改变后,会再次进行远程调用
验证清理缓存 请求 http://localhost:8989/get-and-update/2 ,查看控制,发现控制台输出:
1 2 3 正在进行远程调用:getUserToCommandKey {"username":"laiyy","password":"123456","age":24} 正在进行远程调用:updateUser 2 正在进行远程调用:getUserToCommandKey {"username":"laiyy","password":"123456","age":24}
修改 update 方法的 commandKey,重新启动项目,再次请求,发现控制台输出:
1 2 正在进行远程调用:getUserToCommandKey {"username":"laiyy","password":"123456","age":24} 正在进行远程调用:updateUser 2
比较后发现,修改 commandKey 后,没有进行再次调用,证明 update 没有清理掉 getUserToCommandKey 的缓存。 由此验证在调用 getUserToCommandKey 方法时,会根据 commandKey
进行缓存,在调用 updateUser 方法时,会根据 commandKey
进行缓存删除。缓存删除后再次调用,会再次调用远程接口。
继承 HystrixCommand 方式 访问 http://localhost:8989/get-user-id-by-command/2 ,查看控制台:
1 2 3 正在使用继承 HystrixCommand 方式进行远程调用:{"username":"laiyy","password":"123456","age":24} from Cache:false from Cache:true
可以看到,第二次请求中,isResponseFromCache
为 true,证明缓存生效。
由上面几种方式请求可以验证,Husyrix 的缓存可以由 @HystrixCommand 实现,也可以由继承 HystrixCommand 实现。
总结 @CacheResult:使用该注解后,调用结果会被缓存,要和 @HystrixCommand 同时使用,注解参数用 cacheKeyMethod @CacheRemove:清除缓存,需要指定 commandKey,参数为 commandKey、cacheKeyMethod @CacheKey:指定请求参数,默认使用方法的所有参数作为缓存 key,直接属性为 value。 一般在读操作接口上使用 @CacheResult、在写操作接口上使用 @CacheRemove 注意事项: 再一些请求量大或者重复调用接口的情况下,可以利用缓存有效减轻请求压力,但是在使用 Hystrix 缓存时,需要注意:
需要开启 @EnableHystrix 需要初始化 HystrixRequestContext 在指定了 HystrixCommand 的 commandKey 后,在 @CacheRemove 也要指定 commandKey 如果不初始化 HystrixRequestContext,即在 CacheContextInterceptor
中不使用 HystrixRequestContext.initializeContext()
初始化,进行调用时会出现如下错误:
1 2 3 4 java.lang.IllegalStateException: Request caching is not available. Maybe you need to initialize the HystrixRequestContext? at com.netflix.hystrix.HystrixRequestCache.get(HystrixRequestCache.java:104) ~[hystrix-core-1.5.12.jar:1.5.12] at com.netflix.hystrix.AbstractCommand$7.call(AbstractCommand.java:478) ~[hystrix-core-1.5.12.jar:1.5.12] ...
另外,使用 RestTemplate 进行远程调用时,在指定远程服务时,如果出现如下错误,需要在 RestTemplate 上使用 @LoadBalance
1 2 3 4 5 6 7 java.net.UnknownHostException: spring-cloud-hystrix-cache-provider-user at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:184) ~[na:1.8.0_171] at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172) ~[na:1.8.0_171] at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392) ~[na:1.8.0_171] at java.net.Socket.connect(Socket.java:589) ~[na:1.8.0_171] at java.net.Socket.connect(Socket.java:538) ~[na:1.8.0_171] ...