一、问题背景
我们的系统中有一个微服务(以下简称A服务)需要调用多个部署在各省(市)的外部服务(简称B服务,一共有30多个B服务),不同省(市)网络、硬件性能不同,所以省市系统的处理数据能力有很大差异,平均响应时间超过5秒,部分省系统平均响应时间超过10秒,A服务调用所有B服务超时时间设置为30秒。
二、问题现象
恰逢其会,某日某省网络不知什么原因出现问题,A服务调用该省B服务推送数据大量超时,因为,因为超时时间较长,导致A的上游服务请求积压,A服务的连接池被沾满,导致A服务不能继续接收请求,导致业务终端几个小时,也就是一个外部服务的错误导致全国所有省市的用户都不能使用系统服务。
三、解决思路
计划使用Hystrix的线程隔离功能,实现A服务调用各省B服务时的线程隔离,从而因各种原因导致的某省B服务响应能力不佳时,其他省市用户仍能正常使用业务。
使用Hystrix命令来实现线程隔离时,有三个关键的Key:groupKey、commandKey和threadPoolKey,groupKey缺省为@HystrixCommand标注的类名,commandKey缺省是@HystrixCommand标注的方法名,threadPoolKey缺省与groupKey相同,这三个键均可以通过注解来修改。Hystrix根据threadPoolKey进行隔离,不同的threadPoolKey对应不同的线程池。根据系统实际情况,应该把各省(市)B服务的URL作为threadPoolKey。如果采用注解的方式来配置线程隔离,因为threadPool是需要提前配置的,所以就需要用HystrixCommand进行30多处注解,在分发用户的请求时,如果不使用反射,那么就意味着有一个30多个分支的switch-case,实在是丑陋之极,所以决定使用继承HystrixCommand类的方法。b
四、代码片段
class MyCommand extends HystrixCommand<BaseRes> { private String url; //省B服务的地址 private String msg; //发送到省的数据 public MyCommand(String url, String msg){ super(Setter.withGroupKey(HystrixCommandGroupKey.Factory .asKey("Send2ProvinceCommandGroup")) .andCommandKey(HystrixCommandKey.Factory.asKey("Send2ProvinceCommand")) .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey(url)) .andCommandPropertiesDefaults(HystrixCommandProperties.Setter() .withExecutionTimeoutInMilliseconds(10000))); this.url = url; this.msg = msg; } @Override protected BaseRes run(){ RestTemplate restTemplate = new RestTemplate(); return restTemplate.getForObject(url, BaseRes.class); } @Override protected BaseRes getFallback(){ return BaseRes.getInstance(ErrorCodeEnum.FORWARD_PROVINCE_AUTH_FAILURE); } }
五、断路保护与线程隔离关系
一个commandKey唯一标识一个HystrixCommand,一个HystrixCommand关联一个断路保护器。不同的断路保护器对自己监控的HystrixCommand独立进行错误统计。
如果在生成创建HystrixCommand未指定threadPoolKey,则相同groupKey的HystrixCommand共享一个线程池。
可以考虑如下几种创建HystrixCommand的情况:
1)未指定threadPoolKey,未指定commandKey,那么相同groupKey的HystrxiCommand使用同一个断路保护,使用同一个线程池
2)未指定threadPoolKey,指定了commandKey,每个commandKey标识的HystrixCommand拥有自己的断路保护器,那么相同groupKey的HystrixCommand使用相同的线程池
3)threadPoolKey和commandkey均做了指定,那么相同commandKey的HystrixCommadn使用同一个断路保护器,相同的threadPoolKey的HystrixCommand使用相同的线程池