Java 王者修炼手册【Redis 篇 - 进阶数据类型】:吃透布隆过滤器 / Redisson 分布式锁 / Geo/Bitmap/HLL 核心原理与实战场景

大家好,我是程序员强子。
今天学点有意思的,Redisson 封装了原生的 redis,改进了非常多的功能点,我们来了解一下呗~
布隆过滤器底层:Bitmap 定义和使用场景
使用 Redisson 实现 布隆过滤器,分布式锁
Geo: 实现附近的人,附近商家功能
HyperLogLog: 网站 / APP 的 UV 统计 /页面独立 IP 访问数统计
干货很多,赶紧上车~
Bitmap
定义
基于字符串(String)类型实现的二进制位操作机制,每个位只能是 0 或 1
常用于高效存储海量二值状态(如用户签到、在线状态、是否阅读等)
使用场景
用户签到:key = user:{uid}:sign,offset = 日期(如当月第几天),1 = 签到,0 = 未签到,BITCOUNT 统计总签到天数。
在线状态:key = online:users,offset = 用户 ID,1 = 在线,0 = 离线,BITOP 统计在线人数
布隆过滤器:通过多个 BITSET 位运算实现海量数据去重(如判断用户是否已注册)。
权限控制:每一位代表一个权限,BITOP 组合权限集。
在工作中,一般在程序上不直接用,而是使用封装好的 Redisson,我们深入了解一下 Redisson
使用 Redisson 实现布隆过滤器
本质
无漏判:若返回 不存在,则元素一定不存在(100% 准确)
有误判:若返回 存在,则元素 “可能存在”(误判率可量化、可控制)
不支持删除:底层设计决定了删除会导致误判率飙升
极致省内存:存储 1 亿个元素,误判率 1% 时,仅需约 12MB 内存(对比 HashSet 需数百 MB)
Redisson 布隆过滤器优势
参数自动优化:根据预期元素数和误判率自动计算最优的位图大小和哈希函数个数,无需手动推导;
分布式:基于 Redis 集群 实现,多实例部署时数据全局一致;
API 简洁:封装了 add、contains 等核心方法,无需手动操作 Bitmap;
序列化支持:内置多种序列化方式,支持任意类型元素(如 String、Long、自定义对象);
高性能:底层基于 Redis Pipeline 批量操作,减少网络开销
案例
通过 RedissonClient 获取 RBloomFilter 实例,封装通用的添加、查询方法
支持自定义布隆过滤器名称、预期元素数、误判率
单元测试
底层原理
位图(Bitmap) + 多个独立的哈希函数
1 个字节 = 8 位,1 亿位仅占 100,000,000 / 8 / 1024 / 1024 ≈ 12MB
计算速度快,使用 MurmurHash3 等高效性哈希函数,而非 MD5/SHA1
初始化
流程图如下:
参数校验
最优参数计算
组件初始化
查询数据流程
哈希计算
偏移量生成
位图校验
结果返回
添加元素
误判原理
不同元素经过哈希函数计算后,恰好生成了完全相同的偏移量组合,导致位图中对应位都被置 1,查询时误判为 存在
举个例子
添加元素 A 后,位图 7、13、11 位置 1;
未添加元素 B,但 h1 (B)=31→7、h2 (B)=57→13、h3 (B)=27→11
查询 B 时会误判为 存在
不能删除底层原因
位图中的一个位可能被多个元素共享,不同元素的哈希偏移量可能重合
若将某元素对应的位置 0,会导致其他共享该位的元素被 误删除,查询时返回 不存在
Redisson 除了布隆过滤器,还有哪些好用的功能呢?我们继续了解一下~
Redisson 实现分布式锁
官方推荐的分布式锁实现方案,其核心是基于 Redis 的命令和数据结构
封装了以下特点的分布式锁体系:
可重入
可自动续期
支持公平 / 读写锁
适配集群部署
原生 Redis 分布式锁常见问题
Redisson 对原生 Redis 锁的改进
解决死锁风险
改进点:Redisson 的看门狗线程会动态续期锁的过期时间,业务执行多久,锁就续期多久(默认每 10 秒续期为 30 秒);
兜底方案:若服务宕机,看门狗消失,锁会在默认 30 秒后自动过期,不会出现永久死锁;
手动过期时间:也可通过 lock(long leaseTime, TimeUnit unit)指定固定过期时间,此时看门狗不会生效(需业务保证在过期前执行完)
解决非可重入问题
原生问题:原生锁用 String 存储,仅能记录锁的持有者,无法记录重入次数;
Redisson 改进:采用 Redis Hash 结构存储锁信息:Key:锁名称(如 lock:seckill:1001);Field:锁标识(UUID:threadId);Value:重入次数(int 类型,初始为 1,同一线程再次加锁则 + 1,释放锁则 - 1);
效果:同一个线程可多次获取同一把锁,解决方法嵌套调用的场景
解决锁释放的原子性问题
原生问题:GET+DEL 非原子,可能释放别人的锁;
Redisson 改进:所有加锁、释放锁、重入计数的操作,都通过 Lua 脚本执行(Redis 中 Lua 脚本是原子执行的),从根本上保证多命令的原子性
解决非公平锁问题
原生问题:所有线程同时竞争锁,无法保证顺序;
Redisson 改进:RFairLock(公平锁)基于 Redis 有序集合(ZSet) 实现请求排队:线程加锁失败时,会将自己的请求信息(线程 ID + 时间戳)存入 ZSet(按时间戳排序);当锁释放时,ZSet 中排名第一的线程会被唤醒并加锁;
效果:保证线程按请求顺序获取锁,避免饥饿问题(代价:性能略低于非公平锁)
解决主从切换锁丢失
原生问题:主从同步延迟导致锁丢失;
Redisson 改进:RedLock(红锁):线程需在至少 N/2+1 个独立 Redis 节点(如 3 个节点,需 2 个节点加锁成功)上加锁成功,才认为整体加锁成功;即使某个节点宕机,剩余节点仍能保证锁的有效性;MultiLock(联锁):将多个节点的锁组合成一个锁,需所有节点加锁成功才生效;
适用场景:对锁一致性要求极高的场景(如金融交易),可选择 RedLock;普通场景用单节点锁 + 哨兵模式即可。
解决阻塞轮询消耗资源
原生问题:线程加锁失败后只能轮询重试,消耗资源;
Redisson 改进:采用 Redis 的发布订阅(Pub/Sub) 机制:线程加锁失败时,订阅该锁的释放通知通道(如 redisson_lock__channel:{lock:seckill:1001});当锁被释放时,Redisson 会向通道发布通知,订阅的线程收到通知后立即重试加锁;
效果:避免无效轮询,大幅降低 CPU 和 Redis 资源消耗
解决读写分离问题
原生问题:原生锁是排他锁,读多写少场景性能差;
Redisson 改进:RReadWriteLock 分为读锁(RLock) 和写锁(WLock):读锁:多个线程可同时获取(共享锁),仅当有写锁时才会阻塞;写锁:排他锁,有读锁或其他写锁时,线程会阻塞;
适用场景:商品详情缓存更新、新闻内容发布等读多写少场景,性能提升数倍
解决集群下锁一致性问题
原生问题:集群下锁 key 散列到不同节点,无法保证锁唯一性;
Redisson 改进:Redisson 支持 Redis 单节点、哨兵、主从、集群等所有部署模式;对于集群模式,Redisson 会将锁 key 映射到同一个槽位(通过{lockName}的哈希标签机制),保证锁 key 在集群的同一个节点上,从而保证锁的唯一性;
效果:在 Redis 集群中,分布式锁仍能正常工作
单节点分布式锁的核心实现原理
基于 Redis 的字符串(String) 和 Lua 脚本
加锁流程
锁标识:采用生成客户端 ID:threadId 的格式,保证不同线程、不同服务实例的锁标识唯一;
Lua 脚本原子性:加锁的 “判断锁是否存在 + 设置锁 + 设置过期时间” 是通过 Lua 脚本执行的,避免多命令的原子性问题;
默认过期时间:加锁时默认设置 30 秒过期时间(可自定义),防止死锁
自动续期机制
看门狗(Watch Dog)自动续期机制
当线程加锁成功后,若业务未执行完成,看门狗线程(后台定时线程)会每隔 10 秒(过期时间的 1/3)自动将锁的过期时间重置为 30 秒;
当线程执行完业务释放锁后,看门狗线程会停止续期;
若服务实例宕机,看门狗线程消失,锁会在 30 秒后自动过期,避免死锁
释放锁
释放锁同样通过 Lua 脚本保证 “判断锁标识 + 删除锁 / 减少重入次数” 的原子性
多节点分布式实现
针对 Redis 主从、哨兵、集群部署的场景,Redisson 提供了两种解决方案
RedLock 算法(红锁):RedissonRedLock,需部署多个独立的 Redis 节点(至少 3 个),线程需在超过半数的节点上加锁成功,才认为整体加锁成功;解决主从切换导致的锁丢失问题,但性能略低,且对节点部署要求高。
MultiLock(联锁):RedissonMultiLock,将多个 RLock(对应不同 Redis 节点 / 服务)组合成一个锁,需所有锁都加锁成功才认为加锁成功;适用于跨 Redis 集群的锁场景
使用注意事项
锁名称的唯一性:锁名称需与业务维度绑定(如 lock:seckill:goods:1001),避免不同业务共用锁导致冲突;
避免长时间占用锁:即使有看门狗,也应尽量缩短业务执行时间,防止锁长期被占用导致其他线程阻塞;
选择合适的锁类型:普通场景用 RLock(非公平锁,性能高),需顺序的场景用 RFairLock,读多写少用 RReadWriteLock;
RedLock 的适用场景:RedLock 需部署多个独立 Redis 节点,运维成本高,普通场景无需使用(哨兵模式 + 单节点锁已足够);
异常处理:加锁后必须在 finally 块中释放锁,避免业务异常导致锁无法释放;
序列化问题:Redisson 的锁标识采用序列化存储,需保证序列化方式的一致性(默认用 JSON 序列化,可自定义)
Redisson 中好用的其他功能
信号量(RSemaphore)
功能:分布式版的「资源计数器」,控制同时访问资源的线程数;
典型场景:限流(如同时允许 10 个请求访问某接口)资源池控制(如同时允许 5 个线程操作数据库连接)
闭锁(RCountDownLatch)
功能:分布式版的「等待多任务完成」;
作用:比如一个主任务,执行到一个环节后需要先等各个子任务全部完全后,再执行
典型场景:服务启动前等待所有依赖服务初始化完成分布式任务的分阶段执行
示例:主任务需要等待 3 个子任务完成后才能执行,子任务可以在不同服务实例中调用
主任务接口 - 等待所有子任务完成
子任务接口 - 完成一个子任务,计数减 1
直到调用完成或者调用超时
RCountDownLatch.await() 只会阻塞当前请求对应的业务线程,不会占用 Tomcat 线程池的所有线程;
只要 Tomcat 线程池还有空闲线程,服务实例就能正常接收和处理其他请求;
GEO
底层依赖两个核心技术:GeoHash 算法 + Redis 有序集合(ZSet)
添加地理空间数据(GEOADD)
向指定的 key 中添加一个或多个经纬度与成员的映射关系
成员唯一,重复添加会覆盖
命令语法
key:地理空间集合的名称(如 store:geo);
longitude:经度(范围:-180° ~ 180°,超出会报错);
latitude:纬度(范围:-90° ~ 90°,超出会报错);
member:地理元素的唯一标识(如门店 ID、用户 ID)
示例
GEOSEARCH
支持按半径查询和按矩形范围查询
命令语法
示例
请看以下的实现案例~
附近的人 / 好友
APP 显示 “附近 1km 内的用户”,支持按距离排序
主要涉及的命令
GEOADD
GEOSearch
实现步骤
用户登录时,调用 GEOADD user:geo 经度 纬度 userID 上传经纬度;
用户点击 附近的人,调用 GEOSearch user:geo 我的经度 我的纬度 1 km ASC COUNT 20,查询 1km 内的 20 个用户,按距离升序返回;
附近的门店 / 商家
外卖 / 生鲜 APP 显示 “附近 3km 内的超市”,支持按距离排序
主要涉及的命令
GEOADD
GEOSearch
实现步骤
商家入驻时,调用 GEOADD store:geo 经度 纬度 storeID 存储门店经纬度;
用户端定位后,调用 GEOSearch store:geo 用户经度 用户纬度 3 km ASC,返回附近门店列表;
结合业务数据(如评分、配送费)二次排序
物流轨迹定位
实时显示快递员的位置,查询 “配送范围内的快递员”
实现步骤
快递员的设备定时(如 5 秒)调用 GEOADD courier:geo 经度 纬度 courierID 更新位置;
用户查询时,调用 GEOSearch courier:geo 用户经度 用户纬度 1 km,获取附近 1km 内的快递员;
结合快递员的配送状态(空闲 / 忙碌)过滤结果
HyperLogLog
简称 HLL,是用于基数统计的概率型数据结构
核心特性
统计集合中不重复元素的数量
基数统计:只关心 集合中有多少个不重复元素,不关心具体是哪些元素;
内存高效:无论统计的元素数量是百万级还是亿级,仅占用 12KB 内存;
概率统计:非精确统计,标准误差为 0.81%,可满足大部分非精确统计场景;
支持合并:多个 HyperLogLog 可以合并为一个,用于统计跨维度的基数(如周 UV = 每日 UV 的合并)
适合场景
适合不需要精确统计、追求内存效率的场景
网站 / APP 的 UV 统计
页面独立 IP 访问数统计
核心命令
PFADD:添加元素
语法:PFADD key element [element ...]
作用:将一个或多个元素添加到指定的 HyperLogLog 中(若 key 不存在,会自动创建);
返回值:1 表示 HyperLogLog 的内部结构发生了更新,0 表示无更新(元素已存在或未改变结构)
示例(日 UV 统计)
PFCOUNT:统计基数
语法:PFCOUNT key [key ...]
作用:统计单个或多个 HyperLogLog 的基数(若传入多个 key,会先合并再统计基数);
返回值:基数的估算值;
示例(统计日 UV)
总结
今天主要介绍了一系列的进阶的数据类型,以及 Redisson 的妙用~
主要希望能挖掘一些比较不错的功能点,加在项目上~
接下来会继续深挖 Redis 其他模块的功能点和底层原理~
如果觉得帮到你们,烦请 点赞关注推荐 三连,感谢感谢~~
熟练度刷不停,知识点吃透稳,下期接着练~
版权声明: 本文为 InfoQ 作者【DonaldCen】的原创文章。
原文链接:【http://xie.infoq.cn/article/e519d3eccada5003917bf2db7】。文章转载请联系作者。







评论