登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  数据库 >  Redis

Redis Count-Min Sketch为什么不能提供精确计数

来源:17golang原创

时间:2026-10-13 19:01:37 337浏览 收藏

我第一次把 Redis Count-Min Sketch 接进统计链路时,最容易犯的错是把 CMS.QUERY 的返回值当成数据库里的最终账本。它真正回答的是“这个元素的频次大约是多少”,而不是“这个元素被精确记录了多少次”。原因在于它用固定大小的计数结构换取低内存和近似结果:不同元素可能映射到同一个计数单元,查询时只能从共享计数中取一个估计值。

官方地址:https://redis.io/docs/latest/develop/data-types/probabilistic/count-min-sketch/

在正常的正向累加场景里,Count-Min Sketch 的主要风险是碰撞造成高估,误差参数只能控制概率和规模,不能把共享计数器变成精确账本。要精确对账、退款或逐条审计,应改用 Hash、Sorted Set 或关系型表。

先把“计数”分成精确值和频次估计

精确计数会为每个业务键单独保存数值,读到的 37 就是这个键当前记录的 37。Count-Min Sketch 则把许多键压缩进多行计数器,Redis 文档把它定位为数据流中的频次估计结构。它的优势是空间近似固定、更新和查询都很快,适合找出高频商品、页面访问趋势或流量事件;代价是结果语义变成“可控误差内的估计”。

这也是我现在判断 CMS 的第一条规则:如果结果只用于排序、告警阈值前的粗筛或热门趋势,近似值通常够用;如果数字会进入结算、库存、退款、计费或审计链路,就不能只依赖 CMS。

为什么碰撞会把查询值推高

CMS 为同一个元素准备多行哈希位置,写入时各行对应的计数单元都会增加,查询时取这些位置中的最小值。取最小值可以降低单行碰撞的影响,却不能消除所有碰撞:另一个高频元素如果和目标元素在每一行都发生共享,目标位置就已经被额外累加,最小值仍然可能包含别人的贡献。

Count-Min Sketch 中事件标签、哈希行、共享计数单元与碰撞高估的静态关系说明图
图1:结构说明图,查看多行哈希如何共享计数单元,以及碰撞为什么会让估计值偏高。

所以“取最小值”不是精确修复,而是一个误差控制策略。Redis 文档还提醒,低于某个阈值的查询结果应被忽略或近似看成零,因为这部分数值容易被总数据量和碰撞噪声淹没。若业务把每个低频键都当成准确事件数,CMS 的空间优势反而会变成误判来源。

error 和 probability 不是精确开关

初始化时常见的两个参数分别控制结构规模和碰撞概率。error 是相对于总计数的误差比例,主要影响宽度;probability 是高估概率,主要影响深度。把数值设得更小,会换来更大的结构和更高的资源开销,但它仍然只是让估计更可靠,不会返回逐键精确值。

下面的命令只演示参数和命令之间的关系。代码块中的中文注释说明每组命令的目的,实际结果仍应按你选择的误差预算解释:

# 以 0.1% 误差率和 0.2% 高估概率创建一个频次估计结构
redis-cli CMS.INITBYPROB items:day 0.001 0.002

# 把不同商品的事件增量写入同一个数据流统计结构
redis-cli CMS.INCRBY items:day bread 300 tea 200 coffee 200 beer 100

# 查询的是估计频次,不应直接当作精确账本数值
redis-cli CMS.QUERY items:day bread tea coffee beer

# 查看宽度、深度和当前所有元素的总计数
redis-cli CMS.INFO items:day

CMS.INFO 返回的总计数会随增量变化。Redis 的文档用 threshold = error × total_count 描述可接受误差的尺度:总计数越大,低频元素需要面对的阈值也可能越高。因此,创建时“看起来很小”的误差比例,不等于整个生命周期内每个键都只有很小的绝对误差。

精确计数和 CMS 应该怎么选

我现在不会先问“CMS 能不能更准”,而是先问“这个数字是否必须可对账”。如果必须精确返回每个键的当前值,可以给键单独使用 Hash;如果还要按分值排序,可以使用 Sorted Set;如果只关心大量事件里的高频趋势,CMS 的固定内存和近似查询更有吸引力。

精确计数结构与 Redis Count-Min Sketch 近似结构及动态阈值的选择边界说明图
图2:边界说明图,把精确结果、CMS 参数、动态阈值和高频趋势放在同一张选择地图里。
业务问题更合适的结构原因
结算、退款、库存、审计Hash 或关系型表需要逐键精确值和可追溯变更
精确分值与排名Sorted Set既保存成员分值,又支持排序语义
海量事件的热门趋势Count-Min Sketch接受近似值,优先控制内存和吞吐
低频键是否发生过结合业务去重或存在性结构不要把 CMS 的低频噪声当成事实

几个容易被忽略的边界

  • 不要把误差率写成绝对误差。它和累计总量相关,总量增长后同样的比例会对应更大的绝对范围。
  • 不要把 probability 当成“不会高估”。它表达的是超出目标范围的概率,不是精确保证。
  • 正向事件和负向修正要分开评估。文档允许 CMS.INCRBY 使用负增量;一旦业务包含撤销、冲正或重放,不能机械套用“只会高估”的直觉。
  • 参数要按生命周期估算。应该用预计的累计总量、平均键数和能接受的绝对误差来反推配置,而不是只看某一次小样本查询。

常见问题

CMS.QUERY 返回的值为什么比人工样本大?

最常见原因是不同元素发生了哈希碰撞,共享计数单元把其他元素的增量带进了目标位置。先确认你需要的是趋势估计还是精确值;若后者,换用精确结构。

把 error 设置成 0 就能得到精确计数吗?

不能。参数需要符合命令要求,CMS 的结构本质仍是概率计数器;更小的误差目标通常意味着更大的空间和开销,而不是改变结果语义。

什么时候 Count-Min Sketch 最值得用?

当事件量大、键集合持续变化、内存预算紧,同时业务只需要识别高频项或近似趋势时,它的固定规模和快速更新才真正有价值。

一句话收尾:Count-Min Sketch 不是“不可靠的精确计数器”,而是有明确误差边界的频次估计器。先确认业务能否接受估计,再决定是否用它。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>