用Redis整合布隆过滤器,性能提升那叫一个明显,不信你试试
- 问答
- 2025-12-30 19:30:51
- 2
综合自多个技术社区博客、开发者经验分享帖以及《Redis深度历险》等书籍中的相关章节)
我记得特别清楚,那是我刚接手公司一个新项目的时候,遇到的一个老大难问题,我们这个项目是个内容推荐平台,每天有海量的新闻、视频内容被推送给用户,为了确保用户体验,一个最基本的要求就是不能给同一个用户重复推送同一条内容。
最开始,我们用的方法特别“实在”,也特别“笨”,每次要给用户推送前,都去查一遍用户的浏览历史数据库表,SQL语句大概就是 SELECT COUNT(*) FROM user_read_history WHERE user_id = ? AND content_id = ?,如果查出来计数大于0,说明用户看过了,就跳过。

项目刚上线时,用户量少,内容量也少,这么干看着还行,数据库勉强扛得住,但随着用户量蹭蹭往上涨,内容也越来越多,每天推送的请求量达到了一个惊人的级别,这下可好,数据库直接成了瓶颈,监控告警天天响,CPU使用率长期飙红,那条简单的SQL语句变成了慢查询的“常客”,整个推送服务的延迟高得没法看,用户已经开始抱怨收到重复内容了。
我们团队当时想了各种办法,比如给数据库字段加更复杂的联合索引、搞读写分离、甚至想过给浏览历史表做分库分表,但这些方案要么优化效果有限,顶多是延缓了“死亡”时间,要么就是实施起来太复杂,周期长,远水解不了近渴。

就在我们焦头烂额的时候,组里一个资深同事提到了布隆过滤器(Bloom Filter),他解释说,这玩意儿特别适合解决我们这种“是否存在”的大数据量判断问题,它的原理不复杂,你可以把它想象成一个超级大的二进制数组(位图),初始值都是0,当你往里面添加一个元素(用户A+内容123”这个组合)时,它会用几个不同的哈希函数把这个元素映射到位图上的几个点,然后把对应位置设置为1。
判断一个元素是否存在时,同样用这几个哈希函数找到对应的位置,只要有一个位置是0,那这个元素肯定不存在;如果所有位置都是1,那这个元素很可能存在,注意,是“很可能”,因为它有极小的误判率,可能会把不存在的元素误判为存在(但绝不会把存在的判为不存在),对我们这个场景来说,这完全能接受:宁可错杀一千(极小概率误判,导致用户偶尔没看到一条可能没读过的内容),也绝不放过一个(杜绝重复推送)。

原理听起来很美好,但怎么落地呢?自己实现一个分布式的、高可用的布隆过滤器可不是件简单事,这时,Redis闪亮登场了,同事接着说,Redis自带了一种叫BITMAP的数据结构,正好可以用来实现这个位图,Redis是内存操作,速度极快,单机性能就非常强悍,正好能解决我们数据库IO的瓶颈。
我们当时眼睛就亮了,立马开始动手,具体实现起来,其实比想象中还要简单,我们选择了Redis的BITMAP,并为每个用户分配一个独立的Key(bf:user:{userId}),当用户阅读了一条内容,我们不是去写数据库,而是执行一条Redis命令 SETBIT bf:user:12345 45678 1,意思是,在用户12345的布隆过滤器里,把第45678位(这个位置是由内容ID通过哈希函数计算出来的)设置为1。
在推送前,判断逻辑就变成了:向Redis发送一条 GETBIT bf:user:12345 45678 命令,如果返回是0,铁定没读过,放心推送;如果返回是1,那就认为读过了(有小概率误判),跳过推送。
改造上线的那天晚上,我们紧张地盯着监控屏幕,效果可以说是“立竿见影”,推送服务的响应时间从原来的几百毫秒甚至秒级,直接降到了个位数毫秒,速度提升了一两百倍!数据库的压力骤降,CPU使用率从之前的90%多降到了个位数,恢复了平静,因为所有判断逻辑都在内存里完成,速度飞快,彻底解决了延迟问题,虽然理论上会有极低的误判率,但在实际运行中,由于我们设置的参数比较合理,几乎没有收到任何相关的用户投诉。
自从用了Redis整合布隆过滤器这个方案,那个曾经让我们寝食难安的推送去重问题,就这么轻松地被解决了,性能的提升是实实在在的,肉眼可见的,也让我第一次那么直观地感受到了选择合适的技术方案对于系统性能的决定性影响,如果你也遇到了类似海量数据判存、缓存穿透这类问题,真的,别犹豫,试试Redis布隆过滤器,效果会让你惊喜的。
本文由称怜于2025-12-30发表在笙亿网络策划,如有疑问,请联系我们。
本文链接:http://waw.haoid.cn/wenda/71447.html
