记一篇REDIS布隆过滤器的使用

https://zhuanlan.zhihu.com/p/89883126

开场: 如何判断一个大集合中是否含有某个元素?

背景:

为了最大化提升广告转化效果,业务方决定对接巨量引擎,广点通以及快手RTA服务。并针对自身情况决定是否出广告(新用户出,旧用户不出)。

大致示意图

要求: 1. QPS至少要能撑住30W。2. 接口响应不能超过60ms

面临的问题:

1.高并发——> 负载均衡这块交由中台完成(部署到k8s, 由40个pod分摊掉流量)。

2.低延迟 ——> 响应要快,计算不能太耗时。

调研:

对于低延迟,快速判断请求侧用户信息是不是老用户,需要对旧用户池进行检索。存储方面自然想到redis。

检索示意图

数据量级: 目前旧用户池约有6亿条数据, 考虑到设备标识多样性(IDFA, IMEI, Android_id..), 以及大小写问题,这个数据量可以估算为15亿左右(需要多预估一些空间)。

挑选数据结构: String。 每一个设备都是一个key, 请求方只需要看这个key在不在即可

实现:

      key:  key前缀+md5(device)。     
      例:   app:old_users:202cb962ac59075b964b07152d234b70
      值: 1
      写命令:  SET app:old_users:202cb962ac59075b964b07152d234b70 1
      读命令:   EXISTS app:old_users:202cb962ac59075b964b07152d234b70

空间预估: size = key.length * 16亿 = 46B * 16 * 10^8 = 68GB

这种方案成本较高,且会随着用户增加线性提升,直接pass。需要找到一种查找快,且占用空间小的方案。

抽象:

判断一个设备是否在旧用户池,如果把用户池看成集合,把设备看做元素,那么就可以回到开场的问题——如何判断一个大集合中是否含有某个元素? 布隆过滤器似乎是最佳的解决方案。

使用布隆过滤器的好处:

省空间: 不需要存储完整的元素,只需要对元素进行hash然后将bit向量表中的某个位设为1即可(先不考虑碰撞问题)

查找快: 只需要对查找的元素进行hash然后看bit向量表中对应的位是否为1。

缺点:

1.因为碰撞,会有一定的误报率( 不在集合的一定不在, 在的不一定在 )。这个可以通过使用多个hash函数减少误报,但无法完全消除。

2.不支持删除操作(还是因为碰撞,会出现误删的问题)。

有关布隆过滤器的详解可以参考

Bloom Filters by Example​llimllib.github.io/bloomfilter-tutorial/

结合REDIS:

redis原生并不自带布隆过滤器, 需要专门下载并自行编译和加载,使用方法见

RedisBloom Documentation​oss.redislabs.com/redisbloom/

这里用到布隆过滤器两个API

1.读方: BF.EXISTS KEY element

2.写方: BF.ADD KEY element

两个API即可完成布隆过滤器元素的增加和查找操作(注: 这里的KEY就是布隆过滤器)。

拆KEY:

阿里云的redis单机只能经受10WQPS,再往上就不行了;所以需要选择集群版redis, 用集群来承担读方的查询压力。那么需要将key打散并分摊到不同的节点上。

拆key的规则: 取md5(device)头四位拼接,这样头四位相同的设备会落到相同的key里(布隆过滤器)

例:  deviceArray = [
        "202cb962ac59075b964b07152d234b70",
        "202cb35dac59075b964b07152d234b95",
        "202cb35dac09875b964b07152d234b88",
        ....
    ]
    
对应的写命令:
     BF.ADD app:old_users:202c 202cb962ac59075b964b07152d234b70
     BF.ADD app:old_users:202c 202cb35dac59075b964b07152d234b95
     BF.ADD app:old_users:202c 202cb35dac09875b964b07152d234b88
     ....

空间预估: key的数量最多为 16^4(十六进制) 。

size = key.length 16^4 = 18B * 65536 = 1.125MB

这样key所占的空间可以忽略。

上线效果:

线上接口QPS: 峰值26W, 谷底4W,平响9ms。

redis使用情况:key数: 13w+(因为一些原因要增加其它的key), 占用内存:3.19GB(布隆过滤器本身以及bit向量都需要占用空间)。

总结:

项目做完后,有一些值得思考的地方,整理一下

1.提前预估(要为项目的以后做打算)

QPS预估: 最开始是按最小峰值预估的(10W左右),没有考虑业务发展的情况(后期接近30W)。如果不是领导提醒,自己就傻乎乎的按10W的量去做了,那redis的选型就会有问题(主从版redis只能支持10w,后来改为集群版),以后要估高一些,若资源使用率不高,再降配也不晚。

2.布隆过滤器使用(技术方案要尽可能通用)

开发时使用redis布隆过滤器的时候,用的是本地加载官方组件并启动redis,并没有考虑到阿里云是否支持;结果默认是不支持的... 好在后来跟对方协商,对方可以提供定制化版的redis,可以支持布隆过滤器的API。 这个事后还是有点后怕的, 如果它不支持呢? 如果要迁移到别的云呢?这个其实是留下了隐患的。 技术方案要有备选(用bitmap+偏移量实现, 但是hash算法要找轮子或自己实现)

3.抽象能力

这个非常重要,最终的技术方案并不是自己想到的,而是经过领导指点的。在特定的业务场景下,用合适的数据结构解决业务上的问题,为什么别人能抽象到具体的方案,而自己没想到? 归根结底还是对数据结构和算法理解的不够透彻, 这块是需要深耕的地方。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值