Hystrix里面的一项核心功能,就是资源隔离,就是将多个依赖服务的调用分别隔离到各自的资源池内。避免对某一个依赖服务的调用,因为依赖服务的延迟或失败,导致服务所有的线程资源全部耗费在这个服务的接口调用上。
Hystrix实现资源隔离,主要有两种技术:

  • 线程池
  • 信号量

默认情况使用线程池。

1.信号量机制

信号量的资源隔离只是起到一个开关的作用,比如,服务A的信号量大于3,那么就是说它同时只允许有3个线程来访问服务A,其他的请求都会被拒绝,从而达到资源隔离和限流保护的作用。
基于Hystrix信号量机制实现资源隔离 - 图1

2.线程池与信号量的区别

线程池隔离技术,使用Hystrix自己的线程去执行调用;信号量隔离技术,是直接让tomcat线程去调用依赖服务,信号量有多少,就允许多少个tomcat线程调用它。
适用场景:

  • 线程池隔离:适合大多数场景,比如对依赖服务的网络请求的调用和访问、需要对调用的timeout进行控制。
  • 信号量隔离:适合不是对外部依赖的访问,而是对内部的比较复杂的业务逻辑的访问,不涉及任何网络的请求。

    3.信号量简单Demo

    例子:我们在从商品服务获取到商品数据后,还要去获取商品属于哪个地理位置、省、市、区等。这些数据我们可能只需要在自己本地内存中获取,比如Map中。

    1. public class LocationCache {
    2. private static final Map<Long, String> CITY_MAP = new HashMap<>();
    3. static {
    4. CITY_MAP.put(1L, "北京");
    5. }
    6. /**
    7. * 通过cityId 获取 cityName
    8. *
    9. * @param cityId 城市id
    10. * @return 城市名
    11. */
    12. public static String getCityName(Long cityId) {
    13. return CITY_MAP.get(cityId);
    14. }
    15. }

    写一个CityNameCommand,策略设置为信号量。run()方法中获取本地缓存。目的是对本地缓存进行资源隔离。

    1. public class CityNameCommand extends HystrixCommand<String> {
    2. private final Long cityId;
    3. public CityNameCommand(Long cityId) {
    4. // 设置信号量隔离策略
    5. super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("GetCityNameGroup"))
    6. .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
    7. .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE)));
    8. this.cityId = cityId;
    9. }
    10. @Override
    11. protected String run() {
    12. // 需要进行信号量隔离的代码
    13. return LocationCache.getCityName(cityId);
    14. }
    15. }

    在接口层,通过创建CityNameCommand,传入 cityId,执行 execute() 方法,那么获取本地 cityName 缓存的代码将会进行信号量的资源隔离。

    1. @GetMapping("/v2/product-info")
    2. public String getProductInfoV2(Long productId) {
    3. HystrixCommand<ProductInfo> getProductInfoCommand = new ProductInfoCommand(productId);
    4. // 通过command执行,获取最新商品数据
    5. ProductInfo productInfo = getProductInfoCommand.execute();
    6. Long cityId = productInfo.getCityId();
    7. CityNameCommand cityNameCommand = new CityNameCommand(cityId);
    8. // 获取本地内存(cityName)的代码会被信号量进行资源隔离
    9. String cityName = cityNameCommand.execute();
    10. productInfo.setCityName(cityName);
    11. System.out.println(productInfo);
    12. return "success";
    13. }