热点数据发现
实验性质的项目,因为之前被问过两次,当时没有太好的思路,这里试想一种解决方案。
代码:hotkey
实际工作中很多场景会面对热数据问题,当QPS一高就不能完全以来数据库应付了。最容易想到的解决办法就是加缓存,但是如果数据非常多呢?难道全部都加缓存吗?
退一步讲,在所有的数据中:
- 其实大部分都是不必要加载到缓存的,符合8-2规律;
- 而且有的数据一会是热的,一会可能会变冷,比如因为流感季节到来,对口罩的搜索量忽然上升,之后随着流感季节过去又降回去;
所以我认为最根本的解决办法还是检测出哪些key是热的并加载到缓存,然后自动将不热的数据从缓存中淘汰掉。
测试
环境搭建
1 | sudo docker run -p 3306:3306 --name mysql -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7 |
- 对Redis、MySQL连接正常;
服务启动成功。 - SQL执行正常
- Redis写入查询正常
RedisTest#testSetGet
验证 - 上报
- 手动调hot-key服务的上报接口
localhost:8081/hot-key/upload
1
2
3
4
5
6
7
8{
"items": [{
"key": "a",
"count": 100
}],
"collectTime": "2021-01-19 10:50:00",
"address": "http://127.0.0.1:8080"
} - 客户端上报热点数据
HotKeyUploader
客户端执行一段时间后自动触发上传 - 访问热点数据并上报
通过接口/test/getset
访问数据,这些数据会被加载到热点数据列表HotKeyStatistic
之后等到HotKeyUploader
执行时就会把数据上报到hot-key服务,再去表里查就能找到这条数据。
验证 - 热点数据汇总及通知
hot-key服务需要告知客户端哪些key成为了热key。
- 手动通知
localhost:8080/hot-key/notify
1
2
3
4
5
6{
"hotKeys": [
"a",
"b"
]
} - 自动通知
hot-key
服务定时任务HotKeyNotifyTask
自动统计热点数据并通知相应的服务。 - 通知完毕后查看客户端是否能识别到热点数据
localhost:8080/test/isHot?key=a
验证 - 10W级别数据量
插入10W条数据,其中数据的分布情况:InitTest
key=1, address=127.0.0.1:8080, count=100000, rate=1000
key=2, address=127.0.0.1:8080, count=99999, rate=999
key=3, address=127.0.0.1:8080, count=99998, rate=998
…
客户端正常发现其中频率最高的。
热key检测功能的实现
需求
在现实中,数据库可能存在热点数据、分布式缓存也可能存在热点数据。
- 数据库产生热点数据后,缓存到分布式缓存,且保证分布式缓存和数据库数据的一致性;
- 分布式缓存产生热点数据后,缓存到本地缓存,且保证本地缓存和分布式缓存的一致性。
其他的非功能性需求,包括:
- 热点数据的发现需要尽量v地快,及时响应;
- 对代码侵入低。
- 能够统计本地缓存、分布式缓存的命中率、热点key等数据,用于后续验证效果。
数据模型
这个场景非常简单,从数据库中查询一个key,我先列出DB和Redis中如何保存数据
DB:
1 | create table data ( |
热点数据是有时效的,因此最好key里带上时间,时间的粒度也可以配置,比如将1秒钟拆成4个时段,统计时统计4个时段,这样就不会出现每一秒都重新计数的情况了。
Redis:<HOT_毫秒_keyname, counter>
架构
- 热点统计和上报是异步执行的,因此不会阻塞业务。
热点统计
记录key被访问的频率,统计不能影响服务本身的执行效率,因此不能直接调其他服务。客户端可以每个key使用一个LongAdder来统计其访问频率。
问题是服务挂掉的话,计数器会丢失,不过试想这台机器挂掉后,对该key的访问请求其实就都转移到其他机器上了,所以总数是不会丢失的,只是挂掉的那台机器在上次上报之后的数据会丢失,需要从头开始统计。
热点上报
热点数据的上报需要将数据上传到一个汇总服务器上,注意每次上传的是全量的统计数据,然后由hot-key服务来计算单位时间内的访问频。
上传数据的格式?
因此上传的数据格式可以如下:
1 | { |
每次要把全量的数据都上报吗?
我们现在线上的Redis平时保持有百万量级的key,如果要把所有这些key的访问数据都上报,每次传输的数据量非常大,而且也没有必要,因为大部分key都不是热key。
因此本地统计数据的时候,用一个PriorityBlockingQueue记录使用频率最高的key。
hot-key服务如何保存数据?
- 因为统计的频率不能太低,不然热key不能被及时检测出来,比如3秒上报一次,这样一天单机就需要上报28800次,假设我们的网站有10个服务,每个服务又有5个副本,一天的数据量级就在100W左右。
- 可以保存到数据库中,虽然一天100W挺多的,但是热点统计数据是有时效性的,完全可以每隔1小时将1小时之前的数据清掉。
判断一个key是否是热key时需要将每台服务器的统计数据都从数据库拿出来然后计算频率。 - 可以保存到Redis中,比如每个key存一个hash格式的数据来保存每个服务器的统计数据。
优点:相对数据库来说查得快些。
缺点:Redis只是缓存,不能持久化数据(有持久化功能,不过那主要用于备份),不便后续的数据分析。
综上所述,我还是选择了数据库来持久化访问频率数据。
1 | CREATE TABLE hot_key ( |
热点探测
热点数据上报后由hot-key服务来保存热点数据,之后定时将数据推给其他服务。
应该推给哪些服务?
上报时客户端会告诉hot-key服务自己的IP地址,之后hot-key再根据IP将热key列表推给客户端。
怎么样才算热点数据?
- 从数据库中查找最后一次统计频率大于阈值的;
- 根据IP分组;
- 分IP推送。
客户端如何保存热点数据?
只需要知道哪些key属于热点数据即可,可以用一个Set存key的集合。
本地缓存
通过热点探测知道哪些key属于热点后,客户端在下次访问数据时,会将这些热点数据保存到本地缓存。
关于热key的其他讨论
缓存穿透
缓存穿透指的是一个热key过期后大量请求回源导致数据库被打爆的情况。