当前位置:首页 > 问答 > 正文

Redis里那些你没试过但能改变体验的自定义操作和玩法

说到Redis,很多人可能就用它来存个缓存,记个会话,或者搞个简单的排行榜,但其实Redis就像一个多功能瑞士军刀,你只用了开瓶器那个功能,它里面还藏着很多意想不到的小工具,用好了能让你的应用变得特别巧妙和顺手,下面这些玩法,可能你没试过,但真的能改变体验。

用布隆过滤器挡住大部分无效请求

这个功能听起来有点学术,但用起来特别实在,想象一下,你的应用有个功能是查询某个商品是否存在,如果直接去数据库查,每次查询都很重,尤其当有人恶意用不存在的ID疯狂查询时,数据库压力就很大。

布隆过滤器(Bloom Filter)就能完美解决这个问题,你可以把它理解成一个很聪明的“预检员”,你先把所有存在的商品ID都告诉这个“预检员”,当有查询请求来时,这个“预检员”会先看一眼,它会说两种话:

  1. “这个商品肯定不存在。”
  2. “这个商品可能存在。”

关键在于第一句,它说“不存在”那就是百分百不存在,你可以直接给用户返回“查无此物”,根本不用劳烦数据库,虽然它偶尔会说“可能存在”(有极小的误判率),但这种情况你再放行去数据库查询也无妨,这样一来,90%以上的无效请求在Redis这一层就被拦住了,数据库瞬间轻松很多,Redis通过BF.ADDBF.EXISTS等命令提供了这个功能,用起来就像操作普通的SET一样简单。

用HyperLogLog做低成本、高精度的基数统计

Redis里那些你没试过但能改变体验的自定义操作和玩法

另一个反直觉的玩法是HyperLogLog,比如老板让你统计一下网站今天有多少个独立用户访问(UV),你可能会想到用SET,把每个用户的ID存进去,最后算一下集合大小,用户量少没问题,但如果每天有几百万上千万用户,这个SET会变得非常庞大,占用很多内存。

HyperLogLog就是为了这种“数人头”的场景而生的,它的最大优点就是占用空间极小,无论你来多少亿的用户,它只需要大概12KB的内存,就能估算出总数,并且误差率还不到1%,你只需要用PFADD命令把每个用户ID“扔”进去,最后用PFCOUNT命令就能得到估算的独立用户数,对于不需要100%精确,但需要大数据量统计的场景,比如热门帖子的阅读量、大型活动的参与人数估算,这个功能简直是神器,用极小的代价解决了大问题。

用GEO功能轻松实现“附近的xxx”

现在很多应用都有“附近的人”、“附近的餐厅”这种功能,如果自己实现,需要计算经纬度、搞地理索引,非常复杂,但Redis内置了GEO地理信息功能,让这件事变得异常简单。

Redis里那些你没试过但能改变体验的自定义操作和玩法

你可以把每个地点(比如一个商家)的经纬度和它的ID,通过GEOADD命令存到一个Redis的集合里,你想查询当前位置附近5公里内所有的咖啡馆,只需要一个GEORADIUS命令,把当前位置的经纬度和半径5公里传给它,它瞬间就能把符合条件的商家ID返回给你,甚至还能帮你算好每个商家离你有多远,这个功能性能极高,完全避免了复杂的数学计算和数据库查询,用来做简单的LBS(基于位置的服务)应用核心功能绰绰有余。

用BitMap做精细化的用户行为标记

BitMap,就是位图,听起来很底层,但玩法很灵活,它其实就是一串二进制位(0和1),你可以把每一个位想象成一个开关,对应一个用户的一种状态。

举个最经典的例子:统计用户的签到情况,假设你的应用有100万用户,你需要记录他们今年是否签到过,如果为每个用户每天存一个“已签到/未签到”的字段,数据量会很大,用BitMap就特别省地方,你可以把一年365天看成365个位,为每个用户创建一个BitMap,用户第100天签到了,就把第100个位设为1,这样,记录一个用户一整年的签到情况,只需要365个比特位,也就是大概46个字节,100万用户也就几十兆内存,非常节省,你还可以用BITOP命令对多个BitMap进行“与”、“或”运算,轻松实现“统计连续签到7天的用户”这种复杂查询,除了签到,还可以用来做用户标签、特征标记等,实现精细化的用户管理。

Redis里那些你没试过但能改变体验的自定义操作和玩法

用Stream构建简单的消息队列

虽然很多人用Redis的List做简单的队列,但Redis 5.0推出的Stream类型才是更强大的消息队列工具,它更像是Kafka那样的专业消息队列的简化版。

它支持多消费者组(Consumer Group),也就是说,同一条消息可以被不同的服务组各自消费一遍,互不干扰,一条“用户下单成功”的消息,可以同时被“发送短信的服务组”和“更新库存的服务组”消费,它还支持消息的持久化,可以回溯历史消息,如果你需要一个轻量级、高性能的内部消息队列,又不想引入Kafka、RabbitMQ这样相对重型的组件,Redis Stream是一个非常值得尝试的选择,它能很好地解耦你的服务。

用Lua脚本保证复杂操作的原子性

最后这个不算是单独的数据类型,而是一种方法,但能极大提升体验,有时候你需要对Redis执行一连串的操作,而且要求这些操作要么全部成功,要么全部失败,不能被打断。

在一个秒杀场景里,你需要:1. 判断库存是否大于零,2. 如果大于零,则减少库存,3. 将用户加入抢购成功名单,这三个步骤必须是一个不可分割的原子操作,否则可能出现超卖,Redis支持你写一段Lua脚本,把这几个命令打包在一起发送给Redis服务器,服务器会一次性执行完整个脚本,期间不会执行其他客户端的命令,这样就完美保证了原子性,避免了复杂的锁竞争,大大提升了数据一致性。

这些功能都证明了Redis远不止一个缓存那么简单,当你遇到一些特定的、需要高性能解决的业务难题时,不妨想想Redis这把“瑞士军刀”里,是不是正好有这样一个顺手的小工具在等着你。