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

Redis不光是缓存,还有那些你可能没注意到的奇怪用法和隐藏功能

很多人对Redis的第一印象就是一个速度飞快的内存缓存系统,用来存点会话、热点数据什么的,但其实Redis的作者给了它一堆不同数据结构,脑洞大开的程序员们早就把这些数据结构玩出了花,搞出了很多意想不到的用法,有些甚至有点“不务正业”。

用List当消息队列(MQ),虽然简陋但高效

这是最经典的非缓存用法之一,Redis的List数据结构,支持从左边和右边进行插入(LPUSH/RPUSH)和弹出(LPOP/RPOP),这个特性天然就符合消息队列“先进先出”的模式,可以让生产者用LPUSH命令把任务消息塞进一个List,然后多个消费者用RPOP命令不停地从这个List里取出任务来处理。

有知乎用户提到,虽然和Kafka、RabbitMQ这种专业的消息队列比起来,它没有ACK确认机制、消息持久化可能没那么可靠、也没有各种高级的路由模式,但在一些对消息可靠性要求不是极高、但非常追求速度和简单性的场景下,比如系统内部的通知、统计点日志这种丢了几个也无所谓的任务,用Redis List做消息队列简直不要太方便,性能杠杠的。

用Sorted Set搞排行榜,这是它的主场

这个用法可能不算太“隐藏”,但确实是用得恰到好处,Sorted Set(有序集合)每个元素都有一个分数(score),Redis会根据分数自动排序,这个特性简直就是为排行榜量身定做的。

比如做游戏排行榜,玩家的ID作为元素,他的分数(比如游戏得分)作为score,玩家每得一次分,就用ZADD命令更新一下,排行榜实时就变了,要查TOP 10?用ZREVRANGE命令一下就拿回来了,想查某个玩家排第几名?用ZREVRANK命令,甚至还可以搞区间查询,比如查分数在1000到2000之间的玩家有哪些,微博的热搜、游戏的战力榜,很多底层都是这么干的,简单几行命令就搞定了一个核心功能。

用HyperLogLog做基数统计,省内存到极致

这个功能就有点“黑科技”的味道了,比如老板让你统计一下网站每天有多少个独立用户访问(UV),你可能会想到用Set,把每个用户的ID放进去,最后算一下Set的大小就行了,但如果用户量巨大,比如上亿,这个Set会非常非常占内存。

Redis提供了HyperLogLog这种数据结构,就是专门用来解决这种“基数统计”(统计一个集合中不重复元素的个数)问题的,它的特点是:占用内存极小,每个HyperLogLog键只需要12KB内存,就能统计接近2^64个不同元素的基数! 代价是它是个概率算法,有一定的误差率,大约0.81%,但这个误差率对于很多需要“估摸”一下大体量的场景(比如UV统计)完全是可以接受的,用PFADD添加元素,用PFCOUNT获取统计值,轻松搞定海量数据去重计数。

Redis不光是缓存,还有那些你可能没注意到的奇怪用法和隐藏功能

用GeoHash存储地理位置,实现“附近的人”

Redis在3.2版本之后,直接内置了地理位置处理能力,其底层是用Sorted Set实现的,用一种叫GeoHash的编码方式将经纬度转换成一个分数值。

有了这个功能,实现“附近的人”、查找某个地点周边的餐馆之类的功能就简单多了,你只需要用GEOADD命令把地点的ID和经纬度加进去,然后用GEORADIUS命令,指定一个圆心坐标和半径,就能直接把范围内的所有地点搜出来,不用自己再去折腾复杂的地理空间计算和数据库查询了,非常方便。

用BitMap做大量布尔统计,比如用户签到

BitMap(位图)不是一种单独的数据类型,它实际上是基于String类型的一些位操作命令实现的,一个bit位只有0和1两种状态,所以特别适合用来表示布尔值。

最典型的场景就是用户签到,比如你可以把一年的365天做成365个bit位,初始化一个很长的二进制串,用户每一天签到,就把对应位置的bit位设为1,要判断某天是否签到、统计连续签到了多少天、计算这个月总共签到了几天,都可以通过高效的位运算(GETBIT, SETBIT, BITCOUNT, BITOP等命令)来完成,因为用bit位来存储,所以极其节省空间,统计速度也快。

Redis不光是缓存,还有那些你可能没注意到的奇怪用法和隐藏功能

用Pub/Sub做简单的发布订阅

Redis还提供了一套简单的发布订阅(Publish/Subscribe)模型,客户端可以订阅(SUBSCRIBE)一个或多个频道(channel),然后另一个客户端可以向这个频道发布(PUBLISH)消息,所有订阅了这个频道的客户端都会收到消息。

这可以用于一些简单的实时消息推送,比如聊天室、系统广播通知等,不过需要注意的是,Redis的Pub/Sub是“非持久化”的,消息即发即弃,如果客户端当时不在线,就永远收不到这条消息了,所以它不适合用在需要消息可靠传递的场景。

用Lua脚本保证原子性操作

Redis支持使用Lua脚本,你可以把一系列复杂的Redis命令写成一个Lua脚本,然后一次性发送给Redis服务器执行。关键是,整个Lua脚本的执行是原子性的,在执行过程中不会被其他命令打断。

这解决了需要“先get后set”这种组合操作时的并发问题,比如实现一个简单的限流器:需要先读取当前的计数器,判断是否超限,如果没超限再增加,如果没有原子性保证,两个请求可能同时读到同一个值,然后都判断没超限,导致实际超限,用Lua脚本把这几步操作写在一起,就能完美避免这个问题。

总结一下,Redis的这些“奇怪用法”其实都是开发者们根据其丰富的数据结构和高效的特点,因地制宜挖掘出来的,它就像一个多功能的瑞士军刀,虽然每项专业功能可能都比不上专门的工具(比如专业MQ、专业空间数据库),但在很多场景下,用它来实现能大大简化系统架构,提高开发效率,正所谓“杀鸡焉用牛刀”,了解这些用法,能让你在设计和开发系统时,多出很多灵活、巧妙的解决方案。