20种 Redis 的妙用场景

你做的项目,用Redis干了些啥?大部分人的回答都会是:缓存;他能实现的功能并不仅限于缓存,而是可以运用到各种业务场景中,开发出既简洁、又高效的系统;
结构类型结构存储的值结构的读写能力String字符串可以是字符串、整数或浮点数对整个字符串或字符串的一部分进行操作;对整数或浮点数进行自增或自减操作;List列表一个链表,链表上的每个节点都包含一个字符串对链表的两端进行push和pop操作,读取单个或多个元素;根据值查找或删除元素;Set集合包含字符串的无序集合字符串的集合,包含基础的方法有:看是否存在、添加、获取、删除;还包含计算交集、并集、差集等Hash散列包含键值对的无序散列表包含方法有:添加、获取、删除单个元素Zset有序集合和散列一样,用于存储键值对字符串成员与浮点数分数之间的有序映射;元素的排列顺序由分数的大小决定;包含方法有:添加、获取、删除单个元素以及根据分值范围或成员来获取元素
20种 Redis 的妙用场景
1、缓存
2、抽奖
曾几何时,抽奖是互联网APP热衷的一种推广、拉新的方式,节假日没有好的策划,那就抽个奖吧!一堆用户参与进来,然后随机抽取几个幸运用户给予实物/虚拟的奖品;此时,开发人员就需要写上一个抽奖的算法,来实现幸运用户的抽取;其实我们完全可以利用Redis的集合(Set),就能轻松实现抽奖的功能;
功能实现需要的API
SADD key member1 [member2]:添加一个或者多个参与用户;
SRANDMEMBER KEY [count]:随机返回一个或者多个用户;
SPOP key:随机返回一个或者多个用户,并删除返回的用户;
SRANDMEMBER 和 SPOP 主要用于两种不同的抽奖模式,SRANDMEMBER 适用于一个用户可中奖多次的场景(就是中奖之后,不从用户池中移除,继续参与其他奖项的抽取);而 SPOP 就适用于仅能中一次的场景(一旦中奖,就将用户从用户池中移除,后续的抽奖,就不可能再抽到该用户); 通常 SPOP 会用的会比较多。
127.0.0.1:6379> SADD raffle user1
(integer) 1
127.0.0.1:6379> SADD raffle user2 user3 user4 user5 user6 user7 user8 user9 user10
(integer) 9
127.0.0.1:6379> SRANDMEMBER raffle 2

  1. “user5”
  2. “user2”
    127.0.0.1:6379> SPOP raffle 2
  3. “user3”
  4. “user4”
    127.0.0.1:6379> SPOP raffle 2
  5. “user10”
  6. “user9”
    3、点赞/关注/收藏
    有互动属性APP一般都会有点赞/收藏/喜欢等功能,来提升用户之间的互动。
    传统的实现:用户点赞之后,在数据库中记录一条数据,同时一般都会在主题库中记录一个点赞/收藏汇总数,来方便显示;
    Redis方案:基于Redis的集合(Set),记录每个帖子/文章对应的收藏、点赞的用户数据,同时set还提供了检查集合中是否存在指定用户,用户快速判断用户是否已经点赞过
    功能实现需要的API
    SADD key member1 [member2]:添加一个或者多个成员(点赞)
    SCARD key:获取所有成员的数量(点赞数量)
    SISMEMBER key member:判断成员是否存在(是否点赞)
    SREM key member1 [member2] :移除一个或者多个成员(点赞数量)
    127.0.0.1:6379> sadd like:article:1 user1
    (integer) 1
    127.0.0.1:6379> sadd like:article:1 user2
    (integer) 1

获取成员数量(点赞数量)

127.0.0.1:6379> SCARD like:article:1
(integer) 2

判断成员是否存在(是否点在)

127.0.0.1:6379> SISMEMBER like:article:1 user1
(integer) 1
127.0.0.1:6379> SISMEMBER like:article:1 user3
(integer) 0

移除一个或者多个成员(取消点赞)

127.0.0.1:6379> SREM like:article:1 user1
(integer) 1
127.0.0.1:6379> SCARD like:article:1
(integer) 1
4、排行榜
排名、排行榜、热搜榜是很多APP、游戏都有的功能,常用于用户活动推广、竞技排名、热门信息展示等功能;

比如上面的热搜榜,热度数据来源于全网用户的贡献,但用户只关心热度最高的前50条。
常规的做法:就是将用户的名次、分数等用于排名的数据更新到数据库,然后查询的时候通过Order by + limit 取出前50名显示,如果是参与用户不多,更新不频繁的数据,采用数据库的方式也没有啥问题,但是一旦出现爆炸性热点资讯(比如:大陆收复湾湾,xxx某些绿了等等),短时间会出现爆炸式的流量,瞬间的压力可能让数据库扛不住;
Redis方案:将热点资讯全页缓存,采用Redis的有序队列(Sorted Set)来缓存热度(SCORES),即可瞬间缓解数据库的压力,同时轻松筛选出热度最高的50条;
功能实现需要的命令
ZADD key score1 member1 [score2 member2]:添加并设置SCORES,支持一次性添加多个;
ZREVRANGE key start stop [WITHSCORES] :根据SCORES降序排列;
ZRANGE key start stop [WITHSCORES] :根据SCORES降序排列;

单个插入

127.0.0.1:6379> ZADD ranking 1 user1
(integer) 1

批量插入

127.0.0.1:6379> ZADD ranking 10 user2 50 user3 3 user4 25 user5
(integer) 4

降序排列 不带SCORES

127.0.0.1:6379> ZREVRANGE ranking 0 -1

  1. “user3”
  2. “user5”
  3. “user2”
  4. “user4”
  5. “user1”

降序排列 带SCORES

127.0.0.1:6379> ZREVRANGE ranking 0 -1 WITHSCORES

  1. “user3”
  2. “50”
  3. “user5”
  4. “25”
  5. “user2”
  6. “10”
  7. “user4”
  8. “3”
  9. “user1”
  10. “1”

升序

127.0.0.1:6379> ZRANGE ranking 0 -1 WITHSCORES

  1. “user1”
  2. “1”
  3. “user4”
  4. “3”
  5. “user2”
  6. “10”
  7. “user5”
  8. “25”
  9. “user3”
  10. “50”
    5、PV统计
    Page View(PV)指的是页面浏览量,是用来衡量流量的一个重要标准,也是数据分析很重要的一个依据;通常统计规则是页面被展示一次,就加一功能所需命令
    INCR:将 key 中储存的数字值增一
    127.0.0.1:6379> INCR pv:article:1
    (integer) 1
    127.0.0.1:6379> INCR pv:article:1
    (integer) 2
    6、UV统计
    前面,介绍了通过(INCR)方式来实现页面的PV;除了PV之外,UV(独立访客)也是一个很重要的统计数据;
    但是如果要想通过计数(INCR)的方式来实现UV计数,就非常的麻烦,增加之前,需要判断这个用户是否访问过;那判断依据就需要额外的方式再进行记录。
    你可能会说,不是还有Set嘛!一个页面弄个集合,来一个用户塞(SADD)一个用户进去,要统计UV的时候,再通过SCARD汇总一下数量,就能轻松搞定了;此方案确实能实现UV的统计效果,但是忽略了成本;如果是普通页面,几百、几千的访问,可能造成的影响微乎其微,如果一旦遇到爆款页面,动辄上千万、上亿用户访问时,就一个页面UV将会带来非常大的内存开销,对于如此珍贵的内存来说,这显然是不划算的。
    此时,HeyperLogLog数据结构,就能完美的解决这一问题,它提供了一种不精准的去重计数方案,注意!这里强调一下,是不精准的,会存在误差,不过误差也不会很大,标准的误差率是0.81%,这个误差率对于统计UV计数,是能够容忍的;所以,不要将这个数据结构拿去做精准的去重计数。
    另外,HeyperLogLog 是会占用12KB的存储空间,虽然说,Redis 对 HeyperLogLog 进行了优化,在存储数据比较少的时候,采用了稀疏矩阵存储,只有在数据量变大,稀疏矩阵空间占用超过阈值时,才会转为空间为12KB的稠密矩阵;相比于成千、上亿的数据量,这小小的12KB,简直是太划算了;但是还是建议,不要将其用于数据量少,且频繁创建 HeyperLogLog 的场景,避免使用不当,造成资源消耗没减反增的不良效果。
    功能所需命令:
    PFADD key element [element …]:增加计数(统计UV)
    PFCOUNT key [key …]:获取计数(货物UV)
    PFMERGE destkey sourcekey [sourcekey …]:将多个 HyperLogLog 合并为一个 HyperLogLog(多个合起来统计)

添加三个用户的访问

127.0.0.1:6379> PFADD uv:page:1 user1 user2 user3
(integer) 1

获取UV数量

127.0.0.1:6379> PFCOUNT uv:page:1
(integer) 3

再添加三个用户的访问 user3是重复用户

127.0.0.1:6379> PFADD uv:page:1 user3 user4 user5
(integer) 1

获取UV数量 user3是重复用户 所以这里返回的是5

127.0.0.1:6379> PFCOUNT uv:page:1
(integer) 5
7、去重
通过上面HeyperLogLog的学习,我们掌握了一种不精准的去重计数方案,但是有没有发现,他没办法获取某个用户是否访问过;理想中,我们是希望有一个PFEXISTS的命令,来判断某个key是否存在,然而HeyperLogLog并没有;要想实现这一需求,就得 BloomFiler 上场了。
什么是Bloom Filter?

Bloom Filter是由Bloom在1970年提出的一种多哈希函数映射的快速查找算法。通常应用在一些需要快速判断某个元素是否属于集合,但是并不严格要求100%正确的场合。基于一种概率数据结构来实现,是一个有趣且强大的算法。
举个例子:假如你写了一个爬虫,用于爬取网络中的所有页面,当你拿到一个新的页面时,如何判断这个页面是否爬取过?
普通做法:每爬取一个页面,往数据库插入一行数据,记录一下URL,每次拿到一个新的页面,就去数据库里面查询一下,存在就说明爬取过;
普通做法的缺点:少量数据,用传统方案没啥问题,如果是海量数据,每次爬取前的检索,将会越来越慢;如果你的爬虫只关心内容,对来源数据不太关心的话,这部分数据的存储,也将消耗你很大的物理资源;
此时通过 BloomFiler 就能以很小的内存空间作为代价,即可轻松判断某个值是否存在。
同样,BloomFiler 也不那么精准,在默认参数情况下,是存在1%左右的误差;但是 BloomFiler 是允许通过error_rate(误差率)以及initial_size(预计大小)来设置他的误差比例
error_rate:误差率,越低,需要的空间就越大;
initial_size:预计放入值的数量,当实际放入的数量大于设置的值时,误差率就会逐渐升高;所以为了避免误差率,可以提前做好估值,避免再次大的误差;
127.0.0.1:6379> bf.add web:crawler baidu
(integer) 1
127.0.0.1:6379> bf.madd web:crawler tencent bing

  1. (integer) 1
  2. (integer) 1
    127.0.0.1:6379> bf.exists web:crawler baidu
    (integer) 1
    127.0.0.1:6379> bf.mexists web:crawler baidu 163
  3. (integer) 1
  4. (integer) 0
    8、用户签到
    很多APP为了拉动用户活跃度,往往都会做一些活动,比如连续签到领积分/礼包等等

传统做法:用户每次签到时,往是数据库插入一条签到数据,展示的时候,把本月(或者指定周期)的签到数据获取出来,用于判断用户是否签到、以及连续签到情况;此方式,简单,理解容易;
Redis做法:由于签到数据的关注点就2个:是否签到(0/1)、连续性,因此就完全可以利用BitMap(位图)来实现;
一个月的签到情况,4个字节就记录了(图源:网络)

如上图所示,将一个月的31天,用31个位(4个字节)来表示,偏移量(offset)代表当前是第几天,0/1表示当前是否签到,连续签到只需从右往左校验连续为1的位数;
由于String类型的最大上限是512M,转换为bit则是2^32个bit位。
所需命令:
SETBIT
key offset value:向指定位置offset存入一个0或1
GETBIT
key offset:获取指定位置offset的bit值
BITCOUNT
key [start] [end]:统计BitMap中值为1的bit位的数量
BITFIELD
: 操作(查询,修改,自增)BitMap中bit 数组中的指定位置offset的值
这里最不容易理解的就是:BITFIELD,详情可参考:https://deepinout.com/redis-cmd/redis-bitmap-cmd/redis-cmd-bitfield.html 而且这部分还必须理解了,否则,该需求的核心部分就没办法理解了;
需求:假如当前为8月4号,检测本月的签到情况,用户分别于1、3、4号签到过

9、搜索附近
很多生活类的APP都具备一个搜索附近的功能,比如美团搜索附近的商家;
网图

如果自己想要根据经纬度来实现一个搜索附近的功能,是非常麻烦的;但是Redis 在3.2的版本新增了Redis GEO,用于存储地址位置信息,并对支持范围搜索;基于GEO就能轻松且快速的开发一个搜索附近的功能;

10、简单限流
为了保证项目的安全稳定运行,防止被恶意的用户或者异常的流量打垮整个系统,一般都会加上限流,比如常见的sential、hystrix,都是实现限流控制;如果项目用到了Redis,也可以利用Redis,来实现一个简单的限流功能;
功能所需命令
INCR:将 key 中储存的数字值增一
Expire:设置key的有效期
127.0.0.1:6379> INCR r:f:user1
(integer) 1

第一次 设置一个过期时间

127.0.0.1:6379> EXPIRE r:f:user1 5
(integer) 1
127.0.0.1:6379> INCR r:f:user1
(integer) 2

等待5s 再次增加 发现已经重置了

127.0.0.1:6379> INCR r:f:user1
(integer) 1
11、物流信息(时间线)
寄快递、网购的时候,查询物流信息,都会给我们展示xxx时候,快递到达什么地方了,这就是一个典型的时间线列表;

数据库的做法,就是每次变更就插入一条带时间的信息记录,然后根据时间和ID(ID是必须的,如果出现两个相同的时间,单纯时间排序,会造成顺序不对),来排序生成时间线;
我们也可以通过 Redis 的 List 来实现时间线功能,由于 List 采用的是双向链表,因此升序,降序的时间线都能正常满足;
RPUSH key value1 [value2]:在列表中添加一个或多个值,(升序时间线)
LPUSH key value1 [value2]:将一个或多个值插入到列表头部(降序时间线)
LRANGE key start stop:获取列表指定范围内的元素
127.0.0.1:6379> RPUSH time:line:asc 20220805170000
(integer) 1
127.0.0.1:6379> RPUSH time:line:asc 20220805170001
(integer) 2
127.0.0.1:6379> RPUSH time:line:asc 20220805170002
(integer) 3
127.0.0.1:6379> LRANGE time:line:asc 0 -1

  1. “20220805170000”
  2. “20220805170001”
  3. “20220805170002”
    12、定时取消订单
    13、购物车
    14、商品筛选
    15、Session共享(数据共享)
    16、消息队列
    17、发布/订阅
    18、认识的人/用户推荐
    19、简单分布式锁
    在分布式系统中,很多操作是需要用到分布式锁,防止并发操作带来一些问题;因为redis是独立于分布式系统外的其他服务,因此就可以利用redis,来实现一个简单的不完美分布式锁;
    功能所需命令
    SETNX
    key不存在,设置;key存在,不设置

加锁

127.0.0.1:6379> SETNX try_lock 1
(integer) 1

释放锁

127.0.0.1:6379> del try_lock
(integer) 1
20、全局ID
在分布式系统中,很多场景下需要全局的唯一ID,由于Redis是独立于业务服务的其他应用,就可以利用Incr的原子性操作来生成全局的唯一递增ID
功能所需命令
INCR
127.0.0.1:6379> incr g:uid
(integer) 1
127.0.0.1:6379> incr g:uid
(integer) 2
127.0.0.1:6379> incr g:uid
(integer) 3

:将 key 中储存的数字值增一

你可能感兴趣的:(Redis,redis)