|
1 | | -## 基于 timeout 机制为服务接口调用超时提供安全保护 |
| 1 | +## 基于 timeout 机制为服务接口调用超时提供安全保护 |
| 2 | +一般来说,在调用依赖服务的接口的时候,比较常见的一个问题就是**超时**。超时是在一个复杂的分布式系统中,导致系统不稳定,或者系统抖动。出现大量超时,线程资源会被 hang 死,从而导致吞吐量大幅度下降,甚至服务崩溃。 |
| 3 | + |
| 4 | +你去调用各种各样的依赖服务,特别是在大公司,你甚至都不认识开发一个服务的人,你都不知道那个人的技术水平怎么样,对那个人根本不了解。 |
| 5 | + |
| 6 | +Peter Steiner 说过,"[On the Internet, nobody knows you're a dog](https://en.wikipedia.org/wiki/On_the_Internet,_nobody_knows_you%27re_a_dog)",也就是说在互联网的另外一头,你都不知道甚至坐着一条狗。 |
| 7 | + |
| 8 | + |
| 9 | + |
| 10 | +像特别复杂的分布式系统,特别是在大公司里,多个团队、大型协作,你可能都不知道服务是谁的,很可能说开发服务的那个哥儿们甚至是一个实习生。依赖服务的接口性能可能很不稳定,有时候 2ms,有时候 200ms,甚至 2s,都有可能。 |
| 11 | + |
| 12 | +如果你不对各种依赖服务接口的调用做超时控制,来给你的服务提供安全保护措施,那么很可能你的服务就被各种垃圾的依赖服务的性能给拖死了。大量的接口调用很慢,大量的线程被卡死。如果你做了资源的隔离,那么也就是线程池的线程被卡死,但其实我们可以做超时控制,没必要让它们全卡死。 |
| 13 | + |
| 14 | +### TimeoutMilliseconds |
| 15 | +在 Hystrix 中,我们可以手动设置 timeout 时长,如果一个 command 运行时间超过了设定的时长,那么就被认为是 timeout,然后 Hystrix command 标识为 timeout,同时执行 fallback 降级逻辑。 |
| 16 | + |
| 17 | +`TimeoutMilliseconds` 默认值是 1000,也就是 1000ms。 |
| 18 | + |
| 19 | +```java |
| 20 | +HystrixCommandProperties.Setter() |
| 21 | + ..withExecutionTimeoutInMilliseconds(int) |
| 22 | +``` |
| 23 | + |
| 24 | +### TimeoutEnabled |
| 25 | +这个参数用于控制是否要打开 timeout 机制,默认值是 true。 |
| 26 | + |
| 27 | +```java |
| 28 | +HystrixCommandProperties.Setter() |
| 29 | + .withExecutionTimeoutEnabled(boolean) |
| 30 | +``` |
| 31 | + |
| 32 | +## 实例 Demo |
| 33 | +我们在 command 中,将超时时间设置为 500ms,然后在 run() 方法中,设置休眠时间 1s,这样一个请求过来,直接休眠 1s,结果就会因为超时而执行降级逻辑。 |
| 34 | + |
| 35 | +```java |
| 36 | +public class GetProductInfoCommand extends HystrixCommand<ProductInfo> { |
| 37 | + |
| 38 | + private Long productId; |
| 39 | + |
| 40 | + private static final HystrixCommandKey KEY = HystrixCommandKey.Factory.asKey("GetProductInfoCommand"); |
| 41 | + |
| 42 | + public GetProductInfoCommand(Long productId) { |
| 43 | + super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ProductInfoService")) |
| 44 | + .andCommandKey(KEY) |
| 45 | + .andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter() |
| 46 | + .withCoreSize(8) |
| 47 | + .withMaxQueueSize(10) |
| 48 | + .withQueueSizeRejectionThreshold(8)) |
| 49 | + .andCommandPropertiesDefaults(HystrixCommandProperties.Setter() |
| 50 | + .withCircuitBreakerEnabled(true) |
| 51 | + .withCircuitBreakerRequestVolumeThreshold(20) |
| 52 | + .withCircuitBreakerErrorThresholdPercentage(40) |
| 53 | + .withCircuitBreakerSleepWindowInMilliseconds(3000) |
| 54 | + // 设置是否打开超时,默认是true |
| 55 | + .withExecutionTimeoutEnabled(true) |
| 56 | + // 设置超时时间,默认1000(ms) |
| 57 | + .withExecutionTimeoutInMilliseconds(500) |
| 58 | + .withFallbackIsolationSemaphoreMaxConcurrentRequests(30))); |
| 59 | + this.productId = productId; |
| 60 | + } |
| 61 | + |
| 62 | + @Override |
| 63 | + protected ProductInfo run() throws Exception { |
| 64 | + System.out.println("调用接口查询商品数据,productId=" + productId); |
| 65 | + |
| 66 | + // 休眠1s |
| 67 | + TimeUtils.sleep(1); |
| 68 | + |
| 69 | + String url = "http://localhost:8081/getProductInfo?productId=" + productId; |
| 70 | + String response = HttpClientUtils.sendGetRequest(url); |
| 71 | + System.out.println(response); |
| 72 | + return JSONObject.parseObject(response, ProductInfo.class); |
| 73 | + } |
| 74 | + |
| 75 | + @Override |
| 76 | + protected ProductInfo getFallback() { |
| 77 | + ProductInfo productInfo = new ProductInfo(); |
| 78 | + productInfo.setName("降级商品"); |
| 79 | + return productInfo; |
| 80 | + } |
| 81 | +} |
| 82 | +``` |
| 83 | + |
| 84 | +在测试类中,我们直接发起请求。 |
| 85 | + |
| 86 | +```java |
| 87 | +@SpringBootTest |
| 88 | +@RunWith(SpringRunner.class) |
| 89 | +public class TimeoutTest { |
| 90 | + |
| 91 | + @Test |
| 92 | + public void testTimeout() { |
| 93 | + HttpClientUtils.sendGetRequest("http://localhost:8080/getProductInfo?productId=1"); |
| 94 | + } |
| 95 | +} |
| 96 | +``` |
| 97 | + |
| 98 | +结果中可以看到,打印出了降级商品相关信息。 |
| 99 | + |
| 100 | +```c |
| 101 | +ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specification=null, service=null, color=null, size=null, shopId=null, modifiedTime=null, cityId=null, cityName=null, brandId=null, brandName=null) |
| 102 | +{"id": 1, "name": "iphone7手机", "price": 5599, "pictureList":"a.jpg,b.jpg", "specification": "iphone7的规格", "service": "iphone7的售后服务", "color": "红色,白色,黑色", "size": "5.5", "shopId": 1, "modifiedTime": "2017-01-01 12:00:00", "cityId": 1, "brandId": 1} |
| 103 | +``` |
0 commit comments