|
1 | 1 | ## 基于 Hystrix 信号量机制实现资源隔离 |
2 | | -待补充。 |
| 2 | +Hystrix 里面核心的一项功能,其实就是所谓的**资源隔离**,要解决的最最核心的问题,就是将多个依赖服务的调用分别隔离到各自的资源池内。避免说对某一个依赖服务的调用,因为依赖服务的接口调用的延迟或者失败,导致服务所有的线程资源全部耗费在这个服务的接口调用上。一旦说某个服务的线程资源全部耗尽的话,就可能导致服务崩溃,甚至说这种故障会不断蔓延。 |
| 3 | + |
| 4 | +Hystrix 实现资源隔离,主要有两种技术: |
| 5 | + |
| 6 | +- 线程池 |
| 7 | +- 信号量 |
| 8 | + |
| 9 | +默认情况下,Hystrix 使用线程池模式。 |
| 10 | + |
| 11 | +前面已经说过线程池技术了,这一小节就来说说信号量机制实现资源隔离,以及这两种技术的区别与具体应用场景。 |
| 12 | + |
| 13 | +### 信号量机制 |
| 14 | +信号量的资源隔离只是起到一个开关的作用,比如,服务 A 的信号量大小为 10,那么就是说它同时只允许有 10 个 tomcat 线程来访问服务 A,其它的请求都会被拒绝,从而达到资源隔离和限流保护的作用。 |
| 15 | + |
| 16 | + |
| 17 | + |
| 18 | +### 线程池与信号量区别 |
| 19 | +线程池隔离技术,并不是说去控制类似 tomcat 这种 web 容器的线程。更加严格的意义上来说,Hystrix 的线程池隔离技术,控制的是 tomcat 线程的执行。Hystrix 线程池满后,会确保说,tomcat 的线程不会因为依赖服务的接口调用延迟或故障而被 hang 住,tomcat 其它的线程不会卡死,可以快速返回,然后支撑其它的事情。 |
| 20 | + |
| 21 | +线程池隔离技术,是用 Hystrix 自己的线程去执行调用;而信号量隔离技术,是直接让 tomcat 线程去调用依赖服务。信号量隔离,只是一道关卡,信号量有多少,就允许多少个 tomcat 线程通过它,然后去执行。 |
| 22 | + |
| 23 | + |
| 24 | + |
| 25 | +**适用场景**: |
| 26 | +- **线程池技术**,适合绝大多数场景,比如说我们对依赖服务的网络请求的调用和访问、需要对调用的 timeout 进行控制(捕捉 timeout 超时异常)。 |
| 27 | +- **信号量技术**,适合说你的访问不是对外部依赖的访问,而是对内部的一些比较复杂的业务逻辑的访问,并且系统内部的代码,其实不涉及任何的网络请求,那么只要做信号量的普通限流就可以了,因为不需要去捕获 timeout 类似的问题。 |
| 28 | + |
| 29 | +### 信号量简单 Demo |
| 30 | +业务背景里,比较适合信号量的是什么场景呢? |
| 31 | + |
| 32 | +比如说,我们一般来说,缓存服务,可能会将一些量特别少、访问又特别频繁的数据,放在自己的纯内存中。 |
| 33 | + |
| 34 | +举个栗子。一般我们在获取到商品数据之后,都要去获取商品是属于哪个地理位置、省、市、卖家等,可能在自己的纯内存中,比如就一个 Map 去获取。对于这种直接访问本地内存的逻辑,比较适合用信号量做一下简单的隔离。 |
| 35 | + |
| 36 | +优点在于,不用自己管理线程池啦,不用 care timeout 超时啦,也不需要进行线程的上下文切换啦。信号量做隔离的话,性能相对来说会高一些。 |
| 37 | + |
| 38 | +假如这是本地缓存,我们可以通过 cityId,拿到 cityName。 |
| 39 | +```java |
| 40 | +public class LocationCache { |
| 41 | + private static Map<Long, String> cityMap = new HashMap<>(); |
| 42 | + |
| 43 | + static { |
| 44 | + cityMap.put(1L, "北京"); |
| 45 | + } |
| 46 | + |
| 47 | + /** |
| 48 | + * 通过cityId 获取 cityName |
| 49 | + * |
| 50 | + * @param cityId 城市id |
| 51 | + * @return 城市名 |
| 52 | + */ |
| 53 | + public static String getCityName(Long cityId) { |
| 54 | + return cityMap.get(cityId); |
| 55 | + } |
| 56 | +} |
| 57 | +``` |
| 58 | + |
| 59 | +写一个 GetCityNameCommand,策略设置为**信号量**。run() 方法中获取本地缓存。我们目的就是对获取本地缓存的代码进行资源隔离。 |
| 60 | +```java |
| 61 | +/** |
| 62 | + * @author bingo |
| 63 | + * @since 2018/12/29 |
| 64 | + */ |
| 65 | + |
| 66 | +public class GetCityNameCommand extends HystrixCommand<String> { |
| 67 | + |
| 68 | + private Long cityId; |
| 69 | + |
| 70 | + public GetCityNameCommand(Long cityId) { |
| 71 | + // 设置信号量隔离策略 |
| 72 | + super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("GetCityNameGroup")) |
| 73 | + .andCommandPropertiesDefaults(HystrixCommandProperties.Setter() |
| 74 | + .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE))); |
| 75 | + |
| 76 | + this.cityId = cityId; |
| 77 | + } |
| 78 | + |
| 79 | + @Override |
| 80 | + protected String run() { |
| 81 | + // 需要进行信号量隔离的代码 |
| 82 | + return LocationCache.getCityName(cityId); |
| 83 | + } |
| 84 | +} |
| 85 | +``` |
| 86 | + |
| 87 | +在接口层,通过创建 GetCityNameCommand,传入 cityId,执行 execute() 方法,那么获取本地 cityName 缓存的代码将会进行信号量的资源隔离。 |
| 88 | +```java |
| 89 | +@RequestMapping("/getProductInfo") |
| 90 | +@ResponseBody |
| 91 | +public String getProductInfo(Long productId) { |
| 92 | + HystrixCommand<ProductInfo> getProductInfoCommand = new GetProductInfoCommand(productId); |
| 93 | + |
| 94 | + // 通过command执行,获取最新商品数据 |
| 95 | + ProductInfo productInfo = getProductInfoCommand.execute(); |
| 96 | + |
| 97 | + Long cityId = productInfo.getCityId(); |
| 98 | + |
| 99 | + GetCityNameCommand getCityNameCommand = new GetCityNameCommand(cityId); |
| 100 | + // 获取本地内存(cityName)的代码会被信号量进行资源隔离 |
| 101 | + String cityName = getCityNameCommand.execute(); |
| 102 | + |
| 103 | + productInfo.setCityName(cityName); |
| 104 | + |
| 105 | + System.out.println(productInfo); |
| 106 | + return "success"; |
| 107 | +} |
| 108 | +``` |
0 commit comments