# 083. 将热点缓存自动降级解决方案的代码运行后观察效果以及调试和修复 bug

# 热点感知测试

测试:

  1. nginx 需要 /usr/servers/nginx/sbin/nginx -s reload

  2. 在一些关键处增加日志打印,方便查看调试结果

  3. 后面都在本地运行 storm 了,由于 gradle 打包太麻烦了。 HotProductTopology

  4. 修改应用层 nginx 上的模板 html

    因为需要观察是否被降级为随机路由了,在模板上写上自己本机的 hostanme 即可

调试日志如下:

商品 1,次数 1
Thread-35:[1=1, null, null]
Thread-36:[null, null, null]
商品 2,次数 1
Thread-36:[2=1, null, null]
Thread-35:[1=1, null, null]
商品 3,次数 1
商品 4,次数 1
51677 [Thread-37] INFO  c.m.c.e.s.ProductCountBolt$HotProductFindThread - 后 95% 商品数量 1 个,平均访问值为 1
51677 [Thread-37] INFO  c.m.c.e.s.ProductCountBolt$HotProductFindThread - 热点商品列表:[]
51688 [Thread-38] INFO  c.m.c.e.s.ProductCountBolt$HotProductFindThread - 后 95% 商品数量 1 个,平均访问值为 1
51688 [Thread-38] INFO  c.m.c.e.s.ProductCountBolt$HotProductFindThread - 热点商品列表:[]
Thread-36:[2=1, 4=1, null]
Thread-35:[1=1, 3=1, null]
商品 5,次数 1
56678 [Thread-37] INFO  c.m.c.e.s.ProductCountBolt$HotProductFindThread - 后 95% 商品数量 2 个,平均访问值为 1
56678 [Thread-37] INFO  c.m.c.e.s.ProductCountBolt$HotProductFindThread - 热点商品列表:[]
56689 [Thread-38] INFO  c.m.c.e.s.ProductCountBolt$HotProductFindThread - 后 95% 商品数量 1 个,平均访问值为 1
56689 [Thread-38] INFO  c.m.c.e.s.ProductCountBolt$HotProductFindThread - 热点商品列表:[]
商品 6,次数 1
Thread-36:[2=1, 4=1, 6=1]
Thread-35:[1=1, 3=1, 5=1]
商品 6,次数 2
商品 6,次数 3
商品 6,次数 4
商品 6,次数 5
商品 6,次数 6
商品 6,次数 7
商品 6,次数 8
商品 6,次数 9
61679 [Thread-37] INFO  c.m.c.e.s.ProductCountBolt$HotProductFindThread - 后 95% 商品数量 2 个,平均访问值为 1
61679 [Thread-37] INFO  c.m.c.e.s.ProductCountBolt$HotProductFindThread - 热点商品列表:[]
61690 [Thread-38] INFO  c.m.c.e.s.ProductCountBolt$HotProductFindThread - 后 95% 商品数量 2 个,平均访问值为 1
61690 [Thread-38] INFO  c.m.c.e.s.ProductCountBolt$HotProductFindThread - 发现一个热点商品:6=9  // 倍数设置的 5 。9 满足条件称为热点了
商品 6,次数 10
商品 6,次数 11
商品 6,次数 12
商品 6,次数 13
商品 6,次数 14
商品 6,次数 15
商品 6,次数 16
62209 [Thread-38] INFO  c.m.c.e.s.ProductCountBolt$HotProductFindThread - 热点商品列表:[6]
商品 6,次数 17
商品 6,次数 18
Thread-36:[6=18, 2=1, 4=1]
Thread-35:[1=1, 3=1, 5=1]
66680 [Thread-37] INFO  c.m.c.e.s.ProductCountBolt$HotProductFindThread - 后 95% 商品数量 2 个,平均访问值为 1
66681 [Thread-37] INFO  c.m.c.e.s.ProductCountBolt$HotProductFindThread - 热点商品列表:[]
商品 6,次数 19
商品 6,次数 20
商品 6,次数 21
商品 6,次数 22
商品 6,次数 23
67211 [Thread-38] INFO  c.m.c.e.s.ProductCountBolt$HotProductFindThread - 后 95% 商品数量 2 个,平均访问值为 1
67211 [Thread-38] INFO  c.m.c.e.s.ProductCountBolt$HotProductFindThread - 发现一个热点商品:6=23
67301 [Thread-38] INFO  c.m.c.e.s.ProductCountBolt$HotProductFindThread - 热点商品列表:[6]
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
45
46
47
48
49
50
51
52
53
54
55
56

通过日志观察到该商品,当成为热点的时候,触发了往 nginx 上推送标志, 进行策略降级(这个可以通过)不停的访问 http://eshop-cache03/product?method=product&productId=6&shopId=1 商品 id 为 6 的这个商品,当不是热点商品的时候,只会被路由到指定机器上,当成为热点之后,就会随机路由了

# 热点消失感知测试

怎么测试热点消失呢?看上面的日志有一条很重要的信息

Thread-36:[6=18, 2=1, 4=1]
66680 [Thread-37] INFO  c.m.c.e.s.ProductCountBolt$HotProductFindThread - 后 95% 商品数量 2 个,平均访问值为 1
1
2

商品 id 为 6 能被计算为瞬时热点商品是因为,后两个商品平均访问次数为 1,大于 5 倍的阈值, 那么让 商品 id 为 6 的取消热点的方案就出来了:选择商品 id 为 2 的狂刷,把 6 的顶下来

测试日志如下

Thread-36:[6=13, 2=2, 4=1]
85287 [Thread-37] INFO  c.m.c.e.s.ProductCountBolt$HotProductFindThread - 后 95% 商品数量 2 个,平均访问值为 1
85287 [Thread-37] INFO  c.m.c.e.s.ProductCountBolt$HotProductFindThread - 发现一个热点商品:6=13
85364 [Thread-37] INFO  c.m.c.e.s.ProductCountBolt$HotProductFindThread - 热点商品列表:[6]
...
Thread-36:[2=25, 6=13, 4=1]
90364 [Thread-37] INFO  c.m.c.e.s.ProductCountBolt$HotProductFindThread - 后 95% 商品数量 2 个,平均访问值为 7
90364 [Thread-37] INFO  c.m.c.e.s.ProductCountBolt$HotProductFindThread - 热点商品列表:[]
90364 [Thread-37] INFO  c.m.c.e.s.ProductCountBolt$HotProductFindThread - 一个热点商品消失了:6
1
2
3
4
5
6
7
8
9

这个时候再访问 id=6 的商品,发现一直被路由到 eshop-eache01 上了

# 优化

根据日志来看,当商品 id=6 被定为热点的时候,如果没有其他热点商品进来,那么按照现在休眠 5 秒的时间, 每 5 秒就会获取一次缓存并推送到 nginx 上。可以针对这一点进行优化

// 3. 计算热点商品
for (int i = 0; i < avg95Count; i++) {
   Map.Entry<Long, Long> entry = countList.get(i);
   if (entry.getValue() > avg95Avg * threshold) {
       logger.info("发现一个热点商品:" + entry);
       hotPidList.add(entry.getKey());
       if (!lastTimeHotPids.contains(entry.getKey())) {
           // 如果该商品已经是热点商品了,则不推送,新热点商品才推送
           // 这里根据具体业务要求进行定制
           // 推送热点商品信息到 所有 nginx 上
           pushHotToNginx(entry.getKey());
       }
   }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 小结

本小结是为了解决:热点商品在路由 hash 策略下,大流量打到同一台机器上扛不住 方案思路如下:

  1. 通过 storm 实时统计访问次数

  2. 热点商品感知:对每个 task 中的访问列表排序,前 5% 的商品与后 95% 商品平均访问值进行阀值比较,达到到具体设定倍数即认为是热点商品

  3. 热点商品消失感知:记录上一次的热点商品,当它跌出前 5% 时,被感知到,通过两次热点列表比较能得到

  4. 感知到热点商品时通知流量分发层 nginx 改变路由策略

    分发到更多的 nginx 上去,同时 storm 需要反推该商品详情到更多的 nginx 上去, 本列是所有 nginx,随机分发策略

  5. 感知到热点消失时,通知流量分发层取消降级策略

    之前推送到 nginx 上的缓存可以不用理睬,因为设置了缓存过期时间。 只需要再流量分发层上取消掉随机分发策略即可