全文采用苏格拉底逐层追问模式:核心本质→独有特性→为什么适配业务→执行流程→反问自查,全程大白话,无晦涩书面语,适合面试背诵+源码复盘。
覆盖结构:Bitmap、HyperLogLog、Geo、Stream、布隆过滤器
一、Bitmap 位图
1. 核心本质是什么?
Bitmap 底层就是 Redis String 字符串,区别在于:它不以整串字符为操作单位,而是以单个二进制 bit 位作为最小操作单元。
-
1 字节 = 8 个 bit,每个 bit 只有
0和1两种取值 -
本质:用大量二进制位,批量存储二值状态数据
2. 它具备哪些独有特性?
-
内存占用极低:1 个 bit 保存 1 条状态,亿级数据仅占用十几 MB
-
仅支持 0/1 两种值,只适合「是/否」类场景
-
原生支持批量统计、位运算
-
偏移位可自动扩容,无需提前申请内存空间
3. 为什么能解决签到、在线状态、已读标记这类场景?
这类业务有明确共性:
-
每条数据只有两种结果:签到/未签到、在线/离线、已读/未读
-
用户体量庞大,对内存占用要求高
-
经常需要统计总数
Bitmap 可以完美匹配:
-
二值数据用单个 bit 存储,完全不浪费空间
-
海量数据场景下,内存开销大幅降低
-
内置统计命令,可快速统计状态为 1 的总数量
4. 它是如何一步步实现业务逻辑的?
以用户每日签到为例
规则定义:
-
Key:
sign:日期,按天拆分数据 -
偏移量 offset = 用户ID,一个用户独占一个 bit 位
-
bit=1 代表已签到,bit=0 代表未签到
执行流程:
-
用户签到
SETBIT sign:20260529 10001 1定位到对应用户的 bit 位,设置为 1。重复签到offset一致,不会重复计数,天然去重。
-
查询单个用户是否签到
GETBIT sign:20260529 10001读取对应 bit 位,0=未签到,1=已签到。
-
统计当日总签到人数
BITCOUNT sign:20260529遍历所有 bit 位,累加值为 1 的数量,得到签到总人数。
5. 结构示意图
#mermaid-svg-Xje34A9FeZdRmpj1{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Xje34A9FeZdRmpj1 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Xje34A9FeZdRmpj1 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Xje34A9FeZdRmpj1 .error-icon{fill:#552222;}#mermaid-svg-Xje34A9FeZdRmpj1 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Xje34A9FeZdRmpj1 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Xje34A9FeZdRmpj1 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Xje34A9FeZdRmpj1 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Xje34A9FeZdRmpj1 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Xje34A9FeZdRmpj1 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Xje34A9FeZdRmpj1 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Xje34A9FeZdRmpj1 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Xje34A9FeZdRmpj1 .marker.cross{stroke:#333333;}#mermaid-svg-Xje34A9FeZdRmpj1 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Xje34A9FeZdRmpj1 p{margin:0;}#mermaid-svg-Xje34A9FeZdRmpj1 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Xje34A9FeZdRmpj1 .cluster-label text{fill:#333;}#mermaid-svg-Xje34A9FeZdRmpj1 .cluster-label span{color:#333;}#mermaid-svg-Xje34A9FeZdRmpj1 .cluster-label span p{background-color:transparent;}#mermaid-svg-Xje34A9FeZdRmpj1 .label text,#mermaid-svg-Xje34A9FeZdRmpj1 span{fill:#333;color:#333;}#mermaid-svg-Xje34A9FeZdRmpj1 .node rect,#mermaid-svg-Xje34A9FeZdRmpj1 .node circle,#mermaid-svg-Xje34A9FeZdRmpj1 .node ellipse,#mermaid-svg-Xje34A9FeZdRmpj1 .node polygon,#mermaid-svg-Xje34A9FeZdRmpj1 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Xje34A9FeZdRmpj1 .rough-node .label text,#mermaid-svg-Xje34A9FeZdRmpj1 .node .label text,#mermaid-svg-Xje34A9FeZdRmpj1 .image-shape .label,#mermaid-svg-Xje34A9FeZdRmpj1 .icon-shape .label{text-anchor:middle;}#mermaid-svg-Xje34A9FeZdRmpj1 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Xje34A9FeZdRmpj1 .rough-node .label,#mermaid-svg-Xje34A9FeZdRmpj1 .node .label,#mermaid-svg-Xje34A9FeZdRmpj1 .image-shape .label,#mermaid-svg-Xje34A9FeZdRmpj1 .icon-shape .label{text-align:center;}#mermaid-svg-Xje34A9FeZdRmpj1 .node.clickable{cursor:pointer;}#mermaid-svg-Xje34A9FeZdRmpj1 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Xje34A9FeZdRmpj1 .arrowheadPath{fill:#333333;}#mermaid-svg-Xje34A9FeZdRmpj1 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Xje34A9FeZdRmpj1 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Xje34A9FeZdRmpj1 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Xje34A9FeZdRmpj1 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Xje34A9FeZdRmpj1 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Xje34A9FeZdRmpj1 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Xje34A9FeZdRmpj1 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Xje34A9FeZdRmpj1 .cluster text{fill:#333;}#mermaid-svg-Xje34A9FeZdRmpj1 .cluster span{color:#333;}#mermaid-svg-Xje34A9FeZdRmpj1 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Xje34A9FeZdRmpj1 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Xje34A9FeZdRmpj1 rect.text{fill:none;stroke-width:0;}#mermaid-svg-Xje34A9FeZdRmpj1 .icon-shape,#mermaid-svg-Xje34A9FeZdRmpj1 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Xje34A9FeZdRmpj1 .icon-shape p,#mermaid-svg-Xje34A9FeZdRmpj1 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Xje34A9FeZdRmpj1 .icon-shape .label rect,#mermaid-svg-Xje34A9FeZdRmpj1 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Xje34A9FeZdRmpj1 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Xje34A9FeZdRmpj1 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Xje34A9FeZdRmpj1 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 底层依托
String 字符串
字节1
字节2
bit0 bit1 bit2 bit3 bit4 bit5 bit6 bit7
bit0 bit1 bit2 bit3 bit4 bit5 bit6 bit7
6. 延伸思考(反问自查)
为什么不用 String/Hash 单独存储每个用户的签到状态?
千万级用户会产生海量 Key/Field,内存会膨胀上千倍,不适合海量二值状态场景。
二、HyperLogLog (HLL) 基数统计
1. 核心本质是什么?
HLL 是基于概率估计算法实现的数据结构。
-
不存储原始数据,仅通过哈希采样、概率模型,计算不重复元素的总数量
-
内存固定:单个 HLL 对象最大占用 12KB,和存入数据量无关
2. 它具备哪些独有特性?
-
内存大小恒定,存 10 条数据和存 1 亿条数据,最多只占 12KB
-
统计结果为估算值,标准误差约 0.81%,并非绝对精确
-
只能查询去重总数,无法取出存入的原始数据
-
支持多key合并统计,可以把多天、多页面数据合并汇总
3. 为什么能解决 UV、日活、海量去重计数这类场景?
这类业务有明确共性:
-
核心需求:统计去重数量,同一用户多次访问只计算一次
-
数据量极大,动辄千万、亿级
-
多用于运营统计,允许微小误差
-
不需要查询单条数据明细
传统方案痛点:
使用 Set 存储所有用户ID,会完整保存全部原始数据,亿级数据会占用 GB 级内存,成本极高。
HLL 匹配逻辑:
-
只算数量、不存明细,极致节省内存
-
微小误差在运营统计中完全可接受
-
支持数据合并,轻松实现多日期、多页面汇总统计
4. 它是如何一步步实现业务逻辑的?
以页面 UV 统计为例
规则定义:
-
Key:
uv:page:页面ID:日期 -
存入元素:用户唯一ID
执行流程:
-
用户访问页面
PFADD uv:page:1:20260529 10001对用户ID做哈希、采样计算,更新内部统计结构,不保存原始ID。重复添加同一个用户ID自动去重。
-
查询当日页面独立访客数
PFCOUNT uv:page:1:20260529基于内部采样数据,通过算法估算出不重复用户总数。
-
合并多天 UV 数据
PFMERGE uv:page:total uv:page:1:day1 uv:page:1:day2合并多个 HLL 的统计数据,再通过
PFCOUNT获取汇总结果。
5. 结构示意图
#mermaid-svg-vZCrbnzo1SNkLUMS{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-vZCrbnzo1SNkLUMS .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-vZCrbnzo1SNkLUMS .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-vZCrbnzo1SNkLUMS .error-icon{fill:#552222;}#mermaid-svg-vZCrbnzo1SNkLUMS .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-vZCrbnzo1SNkLUMS .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-vZCrbnzo1SNkLUMS .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-vZCrbnzo1SNkLUMS .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-vZCrbnzo1SNkLUMS .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-vZCrbnzo1SNkLUMS .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-vZCrbnzo1SNkLUMS .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-vZCrbnzo1SNkLUMS .marker{fill:#333333;stroke:#333333;}#mermaid-svg-vZCrbnzo1SNkLUMS .marker.cross{stroke:#333333;}#mermaid-svg-vZCrbnzo1SNkLUMS svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-vZCrbnzo1SNkLUMS p{margin:0;}#mermaid-svg-vZCrbnzo1SNkLUMS .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-vZCrbnzo1SNkLUMS .cluster-label text{fill:#333;}#mermaid-svg-vZCrbnzo1SNkLUMS .cluster-label span{color:#333;}#mermaid-svg-vZCrbnzo1SNkLUMS .cluster-label span p{background-color:transparent;}#mermaid-svg-vZCrbnzo1SNkLUMS .label text,#mermaid-svg-vZCrbnzo1SNkLUMS span{fill:#333;color:#333;}#mermaid-svg-vZCrbnzo1SNkLUMS .node rect,#mermaid-svg-vZCrbnzo1SNkLUMS .node circle,#mermaid-svg-vZCrbnzo1SNkLUMS .node ellipse,#mermaid-svg-vZCrbnzo1SNkLUMS .node polygon,#mermaid-svg-vZCrbnzo1SNkLUMS .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-vZCrbnzo1SNkLUMS .rough-node .label text,#mermaid-svg-vZCrbnzo1SNkLUMS .node .label text,#mermaid-svg-vZCrbnzo1SNkLUMS .image-shape .label,#mermaid-svg-vZCrbnzo1SNkLUMS .icon-shape .label{text-anchor:middle;}#mermaid-svg-vZCrbnzo1SNkLUMS .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-vZCrbnzo1SNkLUMS .rough-node .label,#mermaid-svg-vZCrbnzo1SNkLUMS .node .label,#mermaid-svg-vZCrbnzo1SNkLUMS .image-shape .label,#mermaid-svg-vZCrbnzo1SNkLUMS .icon-shape .label{text-align:center;}#mermaid-svg-vZCrbnzo1SNkLUMS .node.clickable{cursor:pointer;}#mermaid-svg-vZCrbnzo1SNkLUMS .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-vZCrbnzo1SNkLUMS .arrowheadPath{fill:#333333;}#mermaid-svg-vZCrbnzo1SNkLUMS .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-vZCrbnzo1SNkLUMS .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-vZCrbnzo1SNkLUMS .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vZCrbnzo1SNkLUMS .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-vZCrbnzo1SNkLUMS .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vZCrbnzo1SNkLUMS .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-vZCrbnzo1SNkLUMS .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-vZCrbnzo1SNkLUMS .cluster text{fill:#333;}#mermaid-svg-vZCrbnzo1SNkLUMS .cluster span{color:#333;}#mermaid-svg-vZCrbnzo1SNkLUMS div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-vZCrbnzo1SNkLUMS .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-vZCrbnzo1SNkLUMS rect.text{fill:none;stroke-width:0;}#mermaid-svg-vZCrbnzo1SNkLUMS .icon-shape,#mermaid-svg-vZCrbnzo1SNkLUMS .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vZCrbnzo1SNkLUMS .icon-shape p,#mermaid-svg-vZCrbnzo1SNkLUMS .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-vZCrbnzo1SNkLUMS .icon-shape .label rect,#mermaid-svg-vZCrbnzo1SNkLUMS .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vZCrbnzo1SNkLUMS .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-vZCrbnzo1SNkLUMS .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-vZCrbnzo1SNkLUMS :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 用户ID 10001
哈希运算 & 采样
用户ID 10002
HLL 内部统计结构
仅保存特征,不存原始数据
PFCOUNT 估算 → 输出去重总数
6. 延伸思考(反问自查)
哪些场景绝对不能使用 HLL?
支付、订单、库存等要求绝对精确计数的场景;需要查询数据明细的场景也禁止使用。
补充:Bitmap VS HyperLogLog 相似度 + 核心区别
1. 二者相似度
-
都适合大数据量统计:都为海量统计场景设计,内存极小
-
都天然自动去重:同一个用户重复上报数据,都不会重复计数
-
都不存储原始业务数据:都拿不到具体是哪些用户签到/访问,只能拿到总数
-
都只能做统计,不能存详细业务信息
2. 核心本质区别
-
精度区别 :Bitmap 100%精确;HLL 有 0.81% 固定误差,只能做运营大屏
-
内存上限区别 :Bitmap 内存随用户ID最大值线性上涨;HLL 永远固定12KB,亿级数据内存无变化
-
合并能力 :HLL支持PFMERGE多key合并汇总;Bitmap无法跨key合并统计
-
附加能力 :Bitmap除了计数,还能查询单个人是否签到;HLL只能看总数,查不了单个人状态
3. 选型口诀
-
需要精准统计 + 需要查单个用户 状态 → 用Bitmap(签到)
-
只需要大盘总数、不在乎微小误差、需要多天合并UV → 用HLL(页面访客)
三、Geo 地理位置
1. 核心本质是什么?
Geo 底层完全复用 ZSet(有序集合),是基于 ZSet 的业务封装:
-
通过 Geohash 算法,将二维的「经度+纬度」坐标,转换为一维字符串
-
把 Geohash 编码转为 ZSet 的 score,位置标识作为 ZSet 的 member
-
依托 ZSet 自带的排序、范围查询能力,实现地理位置检索
2. 它具备哪些独有特性?
-
二维坐标转为一维有序值,地理位置相近的点位,编码前缀基本一致
-
支持范围检索、两点距离计算、数据排序
-
支持单个点位新增、删除
-
存在Geohash边界缺陷:地理上紧紧相邻的两个点,编码前缀可能完全不一样,导致搜不到附近的人
3. 通俗易懂举例:什么是Geohash边界缺陷?
以国境线/海岸线场景举例,最直观:
-
A点:在中国最东边,坐标紧贴国境线
-
B点:仅仅隔一条马路,就在境外,物理距离相隔只有 50米(现实超级近)
-
但是:两个点刚好落在Geohash网格的两个不同格子里
-
最终现象:物理距离只有50米,但是geohash编码前缀完全不同
最终业务bug :你站在边界搜附近1公里的人,死活搜得到不到马路对面紧挨着的人。
解决方案:业务层同时查询当前格子 + 周边8个相邻格子,兜底解决边界遗漏问题。
4. 为什么能解决附近的人、附近门店、LBS 距离计算这类场景?
这类业务有明确共性:
-
核心需求:根据当前坐标,查找指定半径范围内的其他点位
-
需要实时计算两个坐标之间的直线距离
-
点位支持动态新增、删除
普通结构痛点:
String、Hash、Set 无法高效实现范围+距离检索;全量遍历计算距离,执行效率极低。
Geo 匹配逻辑:
-
Geohash 完成降维,让相近位置在 ZSet 中天然相邻
-
借助 ZSet 范围查询,快速筛选指定半径内的点位
-
内置距离计算命令,无需手动实现算法
5. 它是如何一步步实现业务逻辑的?
以查询附近门店为例
规则定义:
-
Key:
geo:shop,统一管理所有门店坐标 -
member:门店ID;score:经纬度转换后的 Geohash 值
执行流程:
-
录入门店经纬度
GEOADD geo:shop 116\.481028 39\.921983 shop\_001将经纬度转为 Geohash 编码,存入 ZSet。
-
查询当前坐标 1000 米内的门店
GEORADIUS geo:shop 116\.480000 39\.920000 1000 m以当前坐标为中心,筛选出指定半径内的所有门店。
-
计算两个门店之间的距离
GEODIST geo:shop shop\_001 shop\_002 km解析坐标并计算两点直线距离,单位为千米。
6. 结构示意图
#mermaid-svg-NL1sfmjq9GUf66WE{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-NL1sfmjq9GUf66WE .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-NL1sfmjq9GUf66WE .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-NL1sfmjq9GUf66WE .error-icon{fill:#552222;}#mermaid-svg-NL1sfmjq9GUf66WE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-NL1sfmjq9GUf66WE .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-NL1sfmjq9GUf66WE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-NL1sfmjq9GUf66WE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-NL1sfmjq9GUf66WE .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-NL1sfmjq9GUf66WE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-NL1sfmjq9GUf66WE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-NL1sfmjq9GUf66WE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-NL1sfmjq9GUf66WE .marker.cross{stroke:#333333;}#mermaid-svg-NL1sfmjq9GUf66WE svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-NL1sfmjq9GUf66WE p{margin:0;}#mermaid-svg-NL1sfmjq9GUf66WE .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-NL1sfmjq9GUf66WE .cluster-label text{fill:#333;}#mermaid-svg-NL1sfmjq9GUf66WE .cluster-label span{color:#333;}#mermaid-svg-NL1sfmjq9GUf66WE .cluster-label span p{background-color:transparent;}#mermaid-svg-NL1sfmjq9GUf66WE .label text,#mermaid-svg-NL1sfmjq9GUf66WE span{fill:#333;color:#333;}#mermaid-svg-NL1sfmjq9GUf66WE .node rect,#mermaid-svg-NL1sfmjq9GUf66WE .node circle,#mermaid-svg-NL1sfmjq9GUf66WE .node ellipse,#mermaid-svg-NL1sfmjq9GUf66WE .node polygon,#mermaid-svg-NL1sfmjq9GUf66WE .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-NL1sfmjq9GUf66WE .rough-node .label text,#mermaid-svg-NL1sfmjq9GUf66WE .node .label text,#mermaid-svg-NL1sfmjq9GUf66WE .image-shape .label,#mermaid-svg-NL1sfmjq9GUf66WE .icon-shape .label{text-anchor:middle;}#mermaid-svg-NL1sfmjq9GUf66WE .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-NL1sfmjq9GUf66WE .rough-node .label,#mermaid-svg-NL1sfmjq9GUf66WE .node .label,#mermaid-svg-NL1sfmjq9GUf66WE .image-shape .label,#mermaid-svg-NL1sfmjq9GUf66WE .icon-shape .label{text-align:center;}#mermaid-svg-NL1sfmjq9GUf66WE .node.clickable{cursor:pointer;}#mermaid-svg-NL1sfmjq9GUf66WE .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-NL1sfmjq9GUf66WE .arrowheadPath{fill:#333333;}#mermaid-svg-NL1sfmjq9GUf66WE .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-NL1sfmjq9GUf66WE .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-NL1sfmjq9GUf66WE .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NL1sfmjq9GUf66WE .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-NL1sfmjq9GUf66WE .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NL1sfmjq9GUf66WE .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-NL1sfmjq9GUf66WE .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-NL1sfmjq9GUf66WE .cluster text{fill:#333;}#mermaid-svg-NL1sfmjq9GUf66WE .cluster span{color:#333;}#mermaid-svg-NL1sfmjq9GUf66WE div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-NL1sfmjq9GUf66WE .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-NL1sfmjq9GUf66WE rect.text{fill:none;stroke-width:0;}#mermaid-svg-NL1sfmjq9GUf66WE .icon-shape,#mermaid-svg-NL1sfmjq9GUf66WE .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NL1sfmjq9GUf66WE .icon-shape p,#mermaid-svg-NL1sfmjq9GUf66WE .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-NL1sfmjq9GUf66WE .icon-shape .label rect,#mermaid-svg-NL1sfmjq9GUf66WE .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NL1sfmjq9GUf66WE .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-NL1sfmjq9GUf66WE .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-NL1sfmjq9GUf66WE :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Geohash降维
经度 + 纬度
二维坐标
一维hash编码
转为 ZSet Score
门店ID
ZSet Member
ZSet 有序集合
范围查询 → 获取附近点位
7. 延伸思考(反问自查)
为什么不单独开发新结构,而是复用 ZSet?
ZSet 已成熟支持排序、范围查询,复用现有结构可以降低开发复杂度,同时保证性能稳定。
四、Stream 流
1. 核心本质是什么?
Redis 5.0 及以上版本新增结构,专为消息队列、数据流设计,属于增强型有序日志结构。
-
每条消息拥有全局唯一、自增的 ID,格式:
时间戳\-序列号 -
消息支持持久化,消费完成后不会自动删除
-
原生支持消费组、消息 ACK、断点续读、异常重试
2. 它具备哪些独有特性?
-
消息持久化,服务宕机后数据不丢失
-
支持消费组,多消费者分摊任务,适配集群消费
-
ACK 确认机制,手动标记消费完成,避免消费者宕机丢失消息
-
支持消息回溯、重复消费
-
和 List 队列不同,消息不会被取出后直接删除
3. List队列有什么痛点?Stream是如何一一解决的?
| List消息队列痛点 | Stream解决方案 |
|---|---|
| 消息弹出立刻删除,消费者宕机消息直接丢失 | 消息持久化留存,消费不删除,ACK之后才清除待消费标记 |
| 无消费组,无法集群多消费者负载均衡 | 原生消费组,天然支持分布式负载均衡 |
| 消费失败无法重试,不能回溯历史消息 | 无ACK可以一直重读,支持任意位置消息回溯 |
| 只能一头进一头出,消息顺序简单,无全局唯一ID | 自带时间戳全局ID,支持多维度消息管理 |
4. 延伸对比:Redis Stream VS 专业MQ(RocketMQ/Kafka)
很多面试官追问:既然Stream补齐了List所有短板,为什么还要用RocketMQ?这里直白分层对比,不长篇大论:
✅ Stream优势
-
无需额外部署中间件,项目本来就有Redis,零运维成本
-
轻量易用,代码简单,适合小型异步解耦
❌ Stream硬伤(不如专业MQ)
-
消息堆积能力弱:Redis内存数据库,消息大量堆积会打爆内存;Kafka/RocketMQ磁盘存储,无惧海量堆积
-
没有消息重试队列、死信队列、消息延时队列:业务异常重试、延时订单完全没法原生支持
-
没有消息轨迹、消息重试次数、消息回溯时间粒度粗糙
-
分区/分片能力弱,单机上限明显,无法支撑百万级TPS大流量
📌 最终选型标准
-
内部小系统、低TPS、简单异步解耦、不想部署MQ → Stream
-
交易订单、高并发流量、需要死信/延时/重试、消息轨迹 → 必须RocketMQ/Kafka
5. 它是如何一步步实现业务逻辑的?
以订单异步通知队列为例
规则定义:
- Key:
stream:order:notify,消息队列名称
执行流程:
-
生产者发送订单消息
XADD stream:order:notify \* userId 1001 orderId 2026052901 money 99\*表示由 Redis 自动生成消息ID,消息写入队列并持久化。 -
创建消费组
XGROUP CREATE stream:order:notify group1 0创建消费组,从队列第一条消息开始消费。
-
消费者阻塞拉取消息
XREADGROUP GROUP group1 consumer1 COUNT 1 BLOCK 0 STREAMS stream:order:notify \>阻塞等待新消息,拉取待处理数据。
-
消费完成,手动确认
XACK stream:order:notify group1 1789000000000\-0告知队列当前消息已正常处理,清除待消费标记。
6. 架构示意图
#mermaid-svg-1OlZMNNTwt5zfaga{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-1OlZMNNTwt5zfaga .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-1OlZMNNTwt5zfaga .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-1OlZMNNTwt5zfaga .error-icon{fill:#552222;}#mermaid-svg-1OlZMNNTwt5zfaga .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-1OlZMNNTwt5zfaga .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-1OlZMNNTwt5zfaga .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-1OlZMNNTwt5zfaga .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-1OlZMNNTwt5zfaga .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-1OlZMNNTwt5zfaga .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-1OlZMNNTwt5zfaga .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-1OlZMNNTwt5zfaga .marker{fill:#333333;stroke:#333333;}#mermaid-svg-1OlZMNNTwt5zfaga .marker.cross{stroke:#333333;}#mermaid-svg-1OlZMNNTwt5zfaga svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-1OlZMNNTwt5zfaga p{margin:0;}#mermaid-svg-1OlZMNNTwt5zfaga .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-1OlZMNNTwt5zfaga .cluster-label text{fill:#333;}#mermaid-svg-1OlZMNNTwt5zfaga .cluster-label span{color:#333;}#mermaid-svg-1OlZMNNTwt5zfaga .cluster-label span p{background-color:transparent;}#mermaid-svg-1OlZMNNTwt5zfaga .label text,#mermaid-svg-1OlZMNNTwt5zfaga span{fill:#333;color:#333;}#mermaid-svg-1OlZMNNTwt5zfaga .node rect,#mermaid-svg-1OlZMNNTwt5zfaga .node circle,#mermaid-svg-1OlZMNNTwt5zfaga .node ellipse,#mermaid-svg-1OlZMNNTwt5zfaga .node polygon,#mermaid-svg-1OlZMNNTwt5zfaga .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-1OlZMNNTwt5zfaga .rough-node .label text,#mermaid-svg-1OlZMNNTwt5zfaga .node .label text,#mermaid-svg-1OlZMNNTwt5zfaga .image-shape .label,#mermaid-svg-1OlZMNNTwt5zfaga .icon-shape .label{text-anchor:middle;}#mermaid-svg-1OlZMNNTwt5zfaga .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-1OlZMNNTwt5zfaga .rough-node .label,#mermaid-svg-1OlZMNNTwt5zfaga .node .label,#mermaid-svg-1OlZMNNTwt5zfaga .image-shape .label,#mermaid-svg-1OlZMNNTwt5zfaga .icon-shape .label{text-align:center;}#mermaid-svg-1OlZMNNTwt5zfaga .node.clickable{cursor:pointer;}#mermaid-svg-1OlZMNNTwt5zfaga .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-1OlZMNNTwt5zfaga .arrowheadPath{fill:#333333;}#mermaid-svg-1OlZMNNTwt5zfaga .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-1OlZMNNTwt5zfaga .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-1OlZMNNTwt5zfaga .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1OlZMNNTwt5zfaga .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-1OlZMNNTwt5zfaga .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1OlZMNNTwt5zfaga .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-1OlZMNNTwt5zfaga .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-1OlZMNNTwt5zfaga .cluster text{fill:#333;}#mermaid-svg-1OlZMNNTwt5zfaga .cluster span{color:#333;}#mermaid-svg-1OlZMNNTwt5zfaga div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-1OlZMNNTwt5zfaga .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-1OlZMNNTwt5zfaga rect.text{fill:none;stroke-width:0;}#mermaid-svg-1OlZMNNTwt5zfaga .icon-shape,#mermaid-svg-1OlZMNNTwt5zfaga .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1OlZMNNTwt5zfaga .icon-shape p,#mermaid-svg-1OlZMNNTwt5zfaga .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-1OlZMNNTwt5zfaga .icon-shape .label rect,#mermaid-svg-1OlZMNNTwt5zfaga .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1OlZMNNTwt5zfaga .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-1OlZMNNTwt5zfaga .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-1OlZMNNTwt5zfaga :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 生产者
Stream 消息队列
消息持久化留存,消费不删除
消费组 group1
消费者 1
消费者 2
执行业务逻辑
XACK确认消费完成,消息才真正标记已处理
7. 延伸思考(反问自查)
Stream 和 Kafka、RocketMQ 如何选型?
中小流量、简单异步任务选用 Stream;高并发交易、海量消息堆积、需要延时/死信队列,必须使用专业消息中间件。
五、布隆过滤器 Bloom Filter
1. 核心本质是什么?
布隆过滤器是概率型存在判断结构 ,Redis 无原生命令,业界标准实现方案:Bitmap + 多个哈希函数。
-
开辟一段连续的 bit 内存空间
-
使用多个不同哈希函数,将一个元素映射到多个不同 bit 位
-
根据 bit 位状态,判断元素是否存在
2. 它具备哪些独有特性?
-
判断规则:判定不存在 ,则一定不存在;判定存在,有可能误判(假阳性)
-
内存占用极小,海量数据场景下,开销远低于 Set、Hash
-
普通布隆过滤器无法删除元素,删除操作会干扰其他元素的判断
-
增大 bit 空间、增加哈希函数数量,可以降低误判概率
3. 为什么能解决缓存穿透、黑名单、URL 去重这类场景?
这类业务有明确共性:
-
核心需求:快速拦截绝对不存在的数据
-
数据体量庞大,不适合存储全部数据明细
-
允许极低概率的误判
传统方案痛点:
大量请求查询不存在的数据,会直接穿透到数据库,压垮 DB;使用 Set 存储全量数据,内存开销巨大。
布隆过滤器匹配逻辑:
-
先做前置拦截,判定不存在则直接返回,不查询缓存与数据库
-
判定存在才走正常查询流程
-
极小的内存开销,即可拦截绝大部分无效请求
4. 它是如何一步步实现业务逻辑的?
以**防缓存穿透(用户查询)**为例
规则定义:
-
底层依托 Bitmap,Key:
bloom:user -
使用 3 个独立哈希函数,一个元素对应 3 个 bit 位
执行流程:
- 预热阶段:将数据库已存在的用户写入过滤器
plaintext
// 对用户ID计算3个哈希偏移量
h1 = hash1\(userId\)
h2 = hash2\(userId\)
h3 = hash3\(userId\)
plaintext
//将对应 3 个 bit 位全部置 1。
SETBIT bloom:user h1 1
SETBIT bloom:user h2 1
SETBIT bloom:user h3 1`
- 线上查询:判断用户是否存在
plaintext
b1 = GETBIT bloom:user h1
b2 = GETBIT bloom:user h2
b3 = GETBIT bloom:user h3
if b1 && b2 && b3:
// 大概率存在 → 先查Redis缓存,缓存未命中再查DB
else:
// 一定不存在 → 直接返回,不查询缓存与DB`
5. 原理示意图
#mermaid-svg-94GzJ5yBhm6TNi9e{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-94GzJ5yBhm6TNi9e .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-94GzJ5yBhm6TNi9e .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-94GzJ5yBhm6TNi9e .error-icon{fill:#552222;}#mermaid-svg-94GzJ5yBhm6TNi9e .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-94GzJ5yBhm6TNi9e .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-94GzJ5yBhm6TNi9e .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-94GzJ5yBhm6TNi9e .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-94GzJ5yBhm6TNi9e .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-94GzJ5yBhm6TNi9e .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-94GzJ5yBhm6TNi9e .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-94GzJ5yBhm6TNi9e .marker{fill:#333333;stroke:#333333;}#mermaid-svg-94GzJ5yBhm6TNi9e .marker.cross{stroke:#333333;}#mermaid-svg-94GzJ5yBhm6TNi9e svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-94GzJ5yBhm6TNi9e p{margin:0;}#mermaid-svg-94GzJ5yBhm6TNi9e .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-94GzJ5yBhm6TNi9e .cluster-label text{fill:#333;}#mermaid-svg-94GzJ5yBhm6TNi9e .cluster-label span{color:#333;}#mermaid-svg-94GzJ5yBhm6TNi9e .cluster-label span p{background-color:transparent;}#mermaid-svg-94GzJ5yBhm6TNi9e .label text,#mermaid-svg-94GzJ5yBhm6TNi9e span{fill:#333;color:#333;}#mermaid-svg-94GzJ5yBhm6TNi9e .node rect,#mermaid-svg-94GzJ5yBhm6TNi9e .node circle,#mermaid-svg-94GzJ5yBhm6TNi9e .node ellipse,#mermaid-svg-94GzJ5yBhm6TNi9e .node polygon,#mermaid-svg-94GzJ5yBhm6TNi9e .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-94GzJ5yBhm6TNi9e .rough-node .label text,#mermaid-svg-94GzJ5yBhm6TNi9e .node .label text,#mermaid-svg-94GzJ5yBhm6TNi9e .image-shape .label,#mermaid-svg-94GzJ5yBhm6TNi9e .icon-shape .label{text-anchor:middle;}#mermaid-svg-94GzJ5yBhm6TNi9e .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-94GzJ5yBhm6TNi9e .rough-node .label,#mermaid-svg-94GzJ5yBhm6TNi9e .node .label,#mermaid-svg-94GzJ5yBhm6TNi9e .image-shape .label,#mermaid-svg-94GzJ5yBhm6TNi9e .icon-shape .label{text-align:center;}#mermaid-svg-94GzJ5yBhm6TNi9e .node.clickable{cursor:pointer;}#mermaid-svg-94GzJ5yBhm6TNi9e .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-94GzJ5yBhm6TNi9e .arrowheadPath{fill:#333333;}#mermaid-svg-94GzJ5yBhm6TNi9e .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-94GzJ5yBhm6TNi9e .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-94GzJ5yBhm6TNi9e .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-94GzJ5yBhm6TNi9e .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-94GzJ5yBhm6TNi9e .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-94GzJ5yBhm6TNi9e .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-94GzJ5yBhm6TNi9e .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-94GzJ5yBhm6TNi9e .cluster text{fill:#333;}#mermaid-svg-94GzJ5yBhm6TNi9e .cluster span{color:#333;}#mermaid-svg-94GzJ5yBhm6TNi9e div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-94GzJ5yBhm6TNi9e .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-94GzJ5yBhm6TNi9e rect.text{fill:none;stroke-width:0;}#mermaid-svg-94GzJ5yBhm6TNi9e .icon-shape,#mermaid-svg-94GzJ5yBhm6TNi9e .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-94GzJ5yBhm6TNi9e .icon-shape p,#mermaid-svg-94GzJ5yBhm6TNi9e .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-94GzJ5yBhm6TNi9e .icon-shape .label rect,#mermaid-svg-94GzJ5yBhm6TNi9e .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-94GzJ5yBhm6TNi9e .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-94GzJ5yBhm6TNi9e .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-94GzJ5yBhm6TNi9e :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
元素ID
哈希函数1 → 偏移位1
哈希函数2 → 偏移位2
哈希函数3 → 偏移位3
Bitmap 位图
所有bit位都为1?
可能存在
一定不存在
6. 延伸思考(反问自查)
如何解决普通布隆过滤器不能删除元素的问题?
改用计数布隆过滤器,将单个 bit 位改为计数器,支持数值递减,实现安全删除。
六、全文汇总对比表
| 数据结构 | 底层依赖 | 核心能力 | 设计取舍 | 典型业务场景 |
|---|---|---|---|---|
| Bitmap | String | bit 级二值状态存储、精准批量统计 | 内存随ID增大上涨,无法跨key合并 | 用户签到、在线状态、权限标记 |
| HyperLogLog | 独立结构 | 海量数据去重大盘统计 | 牺牲微小精度,永久固定12KB内存,支持多key合并 | 页面UV、日活运营大盘 |
| Geo | ZSet | 地理位置检索、距离计算 | 存在Geohash网格边界bug | 附近门店、附近的人、外卖LBS |
| Stream | 独立结构 | 可靠轻量级消息队列 | 内存存储,消息堆积能力差,无死信/延时队列 | 内部系统简单异步解耦 |
| 布隆过滤器 | Bitmap | 快速拦截不存在请求,防缓存穿透 | 存在假阳性误判,普通版本不能删除 | 缓存穿透防护、黑名单过滤 |
七、Redis所有高级结构统一设计思想(面试升华回答)
-
不重复造轮子:能复用String/ZSet就复用底层结构,降低内核复杂度
-
业务定向取舍:根据场景主动牺牲精度、功能,换取极致内存和性能
-
只做单一专精:每个高级结构只解决一类垂直业务问题,不做大而全