在数据量动辄上亿的场景下,如果想统计某个元素出现过多少次,直接用Hash或String存储计数虽然精确,但内存开销会随元素种类线性增长,最终往往难以承受。Count-Min Sketch(简称CMS)正是为解决这类问题而生的概率型数据结构:它以固定的、可预先计算的内存空间,换取一个带误差上下界的频率估计值。Redis通过RedisBloom模块原生支持了Count-Min Sketch,使得我们可以在Redis中直接使用这一能力,而无需自己实现。

Count-Min Sketch的底层原理
Count-Min Sketch的核心是一个二维计数数组,假设有d行、每行w列,同时配套d个相互独立的哈希函数。当元素到来时,用d个哈希函数分别计算它落在每一行的列位置,并将对应位置上的计数器全部加一。查询时,同样计算d个哈希位置,取出d个计数值,取其中的最小值作为该元素的频率估计。
为什么取最小值?因为哈希冲突只会让计数变大,绝不会变小。也就是说,CMS的估计值永远满足两个特性:第一,估计值不会小于真实值,即只高不低;第二,误差有明确的上界。根据论文结论,如果将列数w设置为e除以epsilon向上取整,哈希函数个数d设置为ln(1除以delta)向上取整,则估计误差超过真实值加epsilon乘以总插入次数的概率不超过delta。
举个例子:设置width为2000、depth为5,用一个64位整数的计数器数组,总内存约为2000乘5乘8等于80000字节,也就是不到80KB。用不到80KB的空间就能支撑百万级不同元素的频率估计,这就是CMS相对于精确计数方案最大的魅力所在。
在Redis中使用CMS命令
RedisBloom模块从Redis 4.0起可以通过模块机制加载,其中包含了Count-Min Sketch的实现。常用命令有四个:CMS.INITBYPROB按误判概率初始化、CMS.INITBYDIM按维度初始化、CMS.INCRBY增加计数、CMS.QUERY查询估计值。使用前需确认模块已加载,可以用MODULE LIST命令查看。
# 按误差和置信度初始化,误差0.001,误判概率0.01 CMS.INITBYPROB cms:hot 0.001 0.01 # 或者直接指定宽度和深度 CMS.INITBYDIM cms:hot 2000 5 # 对元素增加计数,item1加5次,item2加3次 CMS.INCRBY cms:hot item1 5 item2 3 # 查询元素估计频率 CMS.QUERY cms:hot item1 item2
上面这段命令在redis-cli中执行后,CMS.QUERY会返回两个估计值。需要注意的是,CMS只支持增加计数,不支持减少。RedisBloom还提供了CMS.MERGE命令,可以把多个Sketch合并成一个新的,这在分片统计、多机房汇总的场景下非常实用。合并时可以指定每个来源的权重,例如把两个节点的热点数据加权汇总。
# 合并两个Sketch到新key,权重分别为1和3 CMS.MERGE cms:merged 2 cms:a cms:b WEIGHTS 1 3 # 查看sketch信息,返回width、depth和总计数 CMS.INFO cms:merged
与HyperLogLog及精确计数的对比
不少初学者容易把CMS和HyperLogLog混为一谈,实际上二者解决的问题完全不同。HyperLogLog回答的是基数问题,即有多少个不同的元素;CMS回答的是频率问题,即某个元素出现了多少次。前者无法告诉你某个具体元素的次数,后者可以近似做到。两者的内存都可以控制在KB级别,但CMS的内存由你指定的精度参数决定,HLL固定约12KB(标准误差约0.81%)。
| 方案 | 内存 | 结果特性 | 支持删除 |
|---|---|---|---|
| Hash精确计数 | 随元素数线性增长 | 精确 | 支持 |
| HyperLogLog | 约12KB | 基数估计,误差约0.81% | 不支持 |
| Count-Min Sketch | 由精度参数决定,通常几十KB | 频率估计,只高不低 | 不支持 |
精确Hash计数的优势是零误差且支持任意增删,适合元素种类可控、需要精确对账的业务,比如账户余额。而CMS适合元素种类极多、只需要近似值的统计场景。还要注意一个关键限制:CMS没有删除操作。如果业务存在元素下线或撤销计数的逻辑,CMS会带来无法修正的正向偏差,这种场景要么换精确方案,要么定期重建Sketch。
典型应用场景与选型建议
第一个典型场景是热点参数限流。比如对接口按用户ID或IP维度限流,不可能为每个IP都维护精确计数,用CMS估计访问频率,超过阈值就触发限流,即使有少量正向误差,也只是让限流略微偏严格,业务上完全可以接受。
第二个场景是热点key探测。在缓存架构中,及时发现访问量暴涨的key对容量规划很重要。可以在客户端埋点,把key访问写入CMS,再结合TopK扩展,就能低成本地识别出热点key并做本地缓存或分片处理。第三个场景是实时报表中的高频词、热门商品统计,这类场景天然接受近似值,且数据是只增的,与CMS的特性完美契合。
选型时有几点建议:第一,delta和epsilon要根据业务容忍度设置,精度每提高一个数量级,内存大约也增长一个数量级,不要盲目追求高精度;第二,depth一般取5到10即可,过多只会增加CPU开销而收益甚微;第三,如果数据流存在明显的时段性,建议按小时或按天滚动创建新的Sketch,用键名后缀区分,既避免了计数只增不减带来的长期偏差,也方便通过CMS.MERGE做周期性汇总。只要理解了它只高不低的误差特性和不可删除的限制,Count-Min Sketch就是海量频率统计场景下性价比极高的选择。
Redis CountMinSketchCount-Min Sketch布隆过滤器修改时间:2026-09-02 05:16:39