# 115. 基于 hystrix 限流完成源服务的过载保护以避免流量洪峰打死 MySQL

前面讲解了 redis 集群彻底崩溃的时候,对 redis本身做资源隔离、超时控制、熔断策略;

那么此时的流量肯定都会转义到源服务去(提供商品数据的商品服务),源服务中又直接查询 mysql, 如果此时有 QPS 10000 转移到 mysql 上?那么 mysql 可能就凶多吉少了。

本章就是要解决对商品服务这种源服务的访问和增加限流措施

hystr 的限流机制这里就不再复述了,难的还是在业务场景上, 那么这里线程池相关参数设置多大合适?

这个可以通过简单的计算一下:TP99 的商品访问能在 200ms 内响应,1个 线程每秒可以响应 5次, 假设我们一个缓存实例对这个商品服务的访问在每秒 150次。那么需要 30个线程,就可以满足需求;

另外一个,可以考虑在非正常情况下,比如网络短时间抖动,这时候 30 个线程 1 秒内肯定就处理不过来了, 这个时候就可以是适当的设置下等待队列的值。

重要

关于等待队列的值本人到现在都没有搞明白,需要怎么计算才算合适, 通过测试发现:假设 2秒 可以执行 1个 请求,我们设置超时时间为 3 秒,等待队列为 1个, 那么在 2 秒后,等待队列中的会释放出来执行,但是由于已经等待了 2 秒,他自身需要 2 秒才能响应, 这个时候其实第二个请求就会失败;如果是这样的话,那么我是不是就可以认为只要有等待队列,超时时间是否应该设置为双倍呢?

.andThreadPoolPropertiesDefaults(
        HystrixThreadPoolProperties.Setter()
                .withCoreSize(30)
                .withMaxQueueSize(5)
)
1
2
3
4
5

业务代码如下,同样这里也只展示商品获取服务

package cn.mrcode.cachepdp.eshop.cache.command;

import com.alibaba.fastjson.JSONObject;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixThreadPoolProperties;

import cn.mrcode.cachepdp.eshop.cache.model.ProductInfo;

/**
 * @author : zhuqiang
 * @date : 2019/6/23 15:17
 */
public class GetProductInfoOfMysqlCommand extends HystrixCommand<ProductInfo> {
    private Long productId;

    public GetProductInfoOfMysqlCommand(Long productId) {
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("GetProductInfoOfMysqlCommand"))
                .andThreadPoolPropertiesDefaults(
                        HystrixThreadPoolProperties.Setter()
                                .withCoreSize(30)
                                .withMaxQueueSize(5)
                )
        );
        this.productId = productId;
    }

    @Override
    protected ProductInfo run() throws Exception {
        String productInfoJSON = "{\"id\": 1, \"name\": \"iphone7手机\", \"price\": 5599, \"pictureList\":\"a.jpg,b.jpg\", \"specification\": \"iphone7的规格\", \"service\": \"iphone7的售后服务\", \"color\": \"红色,白色,黑色\", \"size\": \"5.5\", \"shopId\": 1," +
                "\"modifyTime\":\"2019-05-13 22:00:00\"}";
        ProductInfo productInfo = JSONObject.parseObject(productInfoJSON, ProductInfo.class);
        return productInfo;
    }

    @Override
    protected ProductInfo getFallback() {
        // 至于降级怎么做,下一章节会讲解
        // 本人也希望能在下一章能讲解到稍微真实的一点场景处理
        // 业务在本缓存架构代码中,假定是能百分比获取到商品信息的,如果被 reject 了,那么该怎么办?
        return null;
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

调用处

@RequestMapping("/getProductInfo")
@ResponseBody
public ProductInfo getProductInfo(Long productId) {
    ProductInfo productInfo = cacheService.getProductInfoOfRedisCache(productId);
    log.info("从 redis 中获取商品信息");
    if (productInfo == null) {
        productInfo = cacheService.getProductInfoFromLocalCache(productId);
        log.info("从 ehcache 中获取商品信息");
    }
    if (productInfo == null) {
        // 两级缓存中都获取不到数据,那么就需要从数据源重新拉取数据,重建缓存
        // 假设这里从数据库中获取的数据
//            String productInfoJSON = "{\"id\": 1, \"name\": \"iphone7手机\", \"price\": 5599, \"pictureList\":\"a.jpg,b.jpg\", \"specification\": \"iphone7的规格\", \"service\": \"iphone7的售后服务\", \"color\": \"红色,白色,黑色\", \"size\": \"5.5\", \"shopId\": 1," +
//                    "\"modifyTime\":\"2019-05-13 22:00:00\"}";
//            productInfo = JSONObject.parseObject(productInfoJSON, ProductInfo.class);
        GetProductInfoOfMysqlCommand command = new GetProductInfoOfMysqlCommand(productId);
        productInfo = command.execute();
        rebuildCache.put(productInfo);
    }
    return productInfo;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21