|
| 1 | +## 基于本地缓存的 fallback 降级机制 |
| 2 | +Hystrix 出现以下四种情况,都会去调用 fallback 降级机制: |
| 3 | + |
| 4 | +- 断路器处于打开的状态。 |
| 5 | +- 资源池已满(线程池+队列 / 信号量)。 |
| 6 | +- Hystrix 调用各种接口,或者访问外部依赖,比如 MySQL、Redis、Zookeeper、Kafka 等等,出现了任何异常的情况。 |
| 7 | +- 访问外部依赖的时候,访问时间过长,报了 TimeoutException 异常。 |
| 8 | + |
| 9 | +### 两种最经典的降级机制 |
| 10 | + |
| 11 | +- 纯内存数据<br> |
| 12 | +在降级逻辑中,你可以在内存中维护一个 ehcache,作为一个纯内存的基于 LRU 自动清理的缓存,让数据放在缓存内。如果说外部依赖有异常,fallback 这里直接尝试从 ehcache 中获取数据。 |
| 13 | + |
| 14 | +- 默认值<br> |
| 15 | +fallback 降级逻辑中,也可以直接返回一个默认值。 |
| 16 | + |
| 17 | +在 `HystrixCommand`,降级逻辑的书写,是通过实现 getFallback() 接口;而在 `HystrixObservableCommand` 中,则是实现 resumeWithFallback() 方法。 |
| 18 | + |
| 19 | + |
| 20 | +现在,我们用一个简单的栗子,来演示 fallback 降级是怎么做的。 |
| 21 | + |
| 22 | +比如,有这么个**场景**。我们现在有个包含 brandId 的商品数据,假设正常的逻辑是这样:拿到一个商品数据,根据 brandId 去调用品牌服务的接口,获取品牌的最新名称 brandName。 |
| 23 | + |
| 24 | +假如说,品牌服务接口挂掉了,那么我们可以尝试从本地内存中,获取一份稍过期的数据,先凑合着用。 |
| 25 | + |
| 26 | +### 本地缓存获取数据 |
| 27 | +本地获取品牌名称的代码大致如下。 |
| 28 | + |
| 29 | +```java |
| 30 | +/** |
| 31 | + * 品牌名称本地缓存 |
| 32 | + * |
| 33 | + */ |
| 34 | + |
| 35 | +public class BrandCache { |
| 36 | + |
| 37 | + private static Map<Long, String> brandMap = new HashMap<>(); |
| 38 | + |
| 39 | + static { |
| 40 | + brandMap.put(1L, "Nike"); |
| 41 | + } |
| 42 | + |
| 43 | + /** |
| 44 | + * brandId 获取 brandName |
| 45 | + * @param brandId 品牌id |
| 46 | + * @return 品牌名 |
| 47 | + */ |
| 48 | + public static String getBrandName(Long brandId) { |
| 49 | + return brandMap.get(brandId); |
| 50 | + } |
| 51 | +``` |
| 52 | + |
| 53 | +### 实现 GetBrandNameCommand |
| 54 | +在 GetBrandNameCommand 中,run() 方法的正常逻辑是去调用品牌服务的接口获取到品牌名称,如果调用失败,报错了,那么就会去调用 fallback 降级机制。 |
| 55 | + |
| 56 | +这里,我们直接**模拟接口调用报错**,给它抛出个异常。 |
| 57 | + |
| 58 | +而在 getFallback() 方法中,就是我们的**降级逻辑**,我们直接从本地的缓存中,**获取到品牌名称**的数据。 |
| 59 | + |
| 60 | +```java |
| 61 | +/** |
| 62 | + * 获取品牌名称的command |
| 63 | + * |
| 64 | + */ |
| 65 | + |
| 66 | +public class GetBrandNameCommand extends HystrixCommand<String> { |
| 67 | + |
| 68 | + private Long brandId; |
| 69 | + |
| 70 | + public GetBrandNameCommand(Long brandId) { |
| 71 | + super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("BrandService")) |
| 72 | + .andCommandKey(HystrixCommandKey.Factory.asKey("GetBrandNameCommand")) |
| 73 | + .andCommandPropertiesDefaults(HystrixCommandProperties.Setter() |
| 74 | + // 设置降级机制最大并发请求数 |
| 75 | + .withFallbackIsolationSemaphoreMaxConcurrentRequests(15))); |
| 76 | + this.brandId = brandId; |
| 77 | + } |
| 78 | + |
| 79 | + @Override |
| 80 | + protected String run() throws Exception { |
| 81 | + // 这里正常的逻辑应该是去调用一个品牌服务的接口获取名称 |
| 82 | + // 如果调用失败,报错了,那么就会去调用fallback降级机制 |
| 83 | + |
| 84 | + // 这里我们直接模拟调用报错,抛出异常 |
| 85 | + throw new Exception(); |
| 86 | + } |
| 87 | + |
| 88 | + @Override |
| 89 | + protected String getFallback() { |
| 90 | + return BrandCache.getBrandName(brandId); |
| 91 | + } |
| 92 | +} |
| 93 | +``` |
| 94 | + |
| 95 | +`FallbackIsolationSemaphoreMaxConcurrentRequests` 用于设置 fallback 最大允许的并发请求量,默认值是 10,是通过 semaphore 信号量的机制去限流的。如果超出了这个最大值,那么直接 reject。 |
| 96 | + |
| 97 | +### CacheController 调用接口 |
| 98 | +在 CacheController 中,我们通过 productInfo 获取 brandId,然后创建 GetBrandNameCommand 并执行,去尝试获取 brandName。这里执行会报错,因为我们在 run() 方法中直接抛出异常,Hystrix 就会去调用 getFallback() 方法走降级逻辑。 |
| 99 | + |
| 100 | +```java |
| 101 | +@Controller |
| 102 | +public class CacheController { |
| 103 | + |
| 104 | + @RequestMapping("/getProductInfo") |
| 105 | + @ResponseBody |
| 106 | + public String getProductInfo(Long productId) { |
| 107 | + HystrixCommand<ProductInfo> getProductInfoCommand = new GetProductInfoCommand(productId); |
| 108 | + |
| 109 | + ProductInfo productInfo = getProductInfoCommand.execute(); |
| 110 | + Long brandId = productInfo.getBrandId(); |
| 111 | + |
| 112 | + HystrixCommand<String> getBrandNameCommand = new GetBrandNameCommand(brandId); |
| 113 | + |
| 114 | + // 执行会抛异常报错,然后走降级 |
| 115 | + String brandName = getBrandNameCommand.execute(); |
| 116 | + productInfo.setBrandName(brandName); |
| 117 | + |
| 118 | + System.out.println(productInfo); |
| 119 | + return "success"; |
| 120 | + } |
| 121 | + |
| 122 | +} |
| 123 | +``` |
| 124 | + |
| 125 | +关于降级逻辑的演示,基本上就结束了。 |
0 commit comments