Redis应用场景(二)String字符串

浮生半日闲 发布于 2022-11-06 8 次阅读


1、缓存对象

作为key-value形式的内存数据库,Redis最常用的应用场景就是数据缓存。

缓存的实现通过命令set key value来实现:

127.0.0.1:6379> set cacheKey cacheValue
OK

上面设置的缓存对象除非调用del key命令删除,不然会一直存在于Redis中;可以通过命令SETEX key seconds value来设置一个有过期时间的缓存数据,当到达了指定的seconds(秒)后,Redis中的缓存对象会直接被删除掉:

127.0.0.1:6379> SETEX expireCacheKey 300 cachevalue
OK

推荐使用带有过期时间的缓存方法,防止一些临时缓存一直存在于Redis中,占用内存。

2、计数器/限流

由于Redis是单线程的,所以命令执行过程是具有原子性的。因此,适合作为计数场景,如果计算访问次数,点赞数、转发数、库存数量等等。

计数器可以通过INCR key命令或者DECR key命令来实现;其中INCR代表的是数量增加,DECR代表的是数据减少。不管是INCR,还是DECR,在调用命令时,如果key不存在,那么key的值会先被初始化为0,然后再执行相应的命令。执行完命令后,会返回key的值。

127.0.0.1:6379> set read 10
OK
127.0.0.1:6379> INCR read
(integer) 11
127.0.0.1:6379> DECR read
(integer) 10

INCR(DECR)命令每次执行后,数量都是增加(减少)1,如果需要增加(减少)指定的数量,可以使用INCRBY key increment或者DECRBY key decrement命令。

127.0.0.1:6379> set read 10
OK
127.0.0.1:6379> INCRBY read 20
(integer) 30
127.0.0.1:6379> DECRBY read 15
(integer) 15

3、分布式锁

分布式锁有如下几点特点:

  • 互斥性:锁的目的是获取资源的使用权,因此,同一时间不同节点的不同线程,只允许有一个线程获取资源
  • 安全性:避免死锁的发生。当锁的持有者在持有时间内,由于意外情况未能主动解锁,其持有的锁也能够正常被释放,保证后续其他线程也能持有到锁;
  • 可重入性:同一个锁,加锁和解锁必须是同一个线程,不能被其他线程将锁释放掉;
  • 高效性:加锁和解锁需要高效
  • 可靠性:需要一定的异常处理能力和容灾性

当了解了分布式锁需要的特点后,可以发现Redis可以非常适用于分布式锁的实现:

  • Redis是内存型数据库,因此非常高效;
  • Redis本身就带有持久化操作和集群功能,因此具有一定的容灾能力;
  • Redis是原子性的,因此可以保证线程的互斥性和安全性;
  • 通过Redis带有过期机制,可以防止死锁的发生;

加锁通过SET key value NX EX seconds命令来实现:

127.0.0.1:6379> SET lock lockValue NX EX 10
OK
127.0.0.1:6379> SET lock lockValue NX EX 10
(nil)
127.0.0.1:6379> GET lock
"lockValue"

对于缓存命令:

  • SET lock lockValue:表示设置锁,lock是锁的名称,lockValue是锁的值,使用客户端生成的唯一性标识,方便后面进行解锁判断,防止其他线程(非加锁线程)进行解锁,当lock存在时,则表示已经加锁;
  • NX:表示只有当lock不存在时,才设置值;如果lock存在,则会返回null;
  • EX 10:表示锁的过期时间为10秒,当加锁进行没有主动进行解锁时,10秒后其他线程也可以获取到锁。这里锁的过期时间不能随便设置,设置时间过长,那么由于意外没有主动解锁后,其他线程需要等待的时间过长,如果设置过短,那么有可能加锁进程还没有执行完成,其他的线程就能拿到锁了,因此具体的设置时长需要根据业务来进行确定

解锁的过程,就是将lock键删除掉,在解锁的时候,需要先判断lockValue是否是当前客户端的,如果是才允许解锁,否则,不允许(这里是2个操作,严格意义下需要使用lua脚本来执行)。

127.0.0.1:6379> GET lock
"lockValue"
127.0.0.1:6379> del lock
(integer) 0

4、签到统计

在签到打卡场景下,我们只有签到或者未签到2种情况,因此可以使用bitmap来进行签到统计。在进行统计时,每个用户一天的签到用1个bit位就能表示,一个月最多也就31个bit位,一年数据365位。

使用SETBIT key offset value来进行签到数据的记录。其中:

  • key:使用用户标识+签到年份/月份来进行区分
  • offset:偏移量,即对应的日期天数,从0开始
  • value:记录是否签到,如果签到,设置为1

现需要统计ID为1的用户的签到数据,下面为具体的实现方式:

(1)插入签到的天数,如11月2日,11月4日,11月5日,11月6日;

127.0.0.1:6379> SETBIT user:1:202211 5 1
(integer) 0
127.0.0.1:6379> SETBIT user:1:202211 4 1
(integer) 0
127.0.0.1:6379> SETBIT user:1:202211 3 1
(integer) 0
127.0.0.1:6379> SETBIT user:1:202211 1 1
(integer) 0

(2)使用GETBIT key offset来检查是否签到,取值为1则表示签到;

127.0.0.1:6379> GETBIT user:1:202211 2
(integer) 0
127.0.0.1:6379> GETBIT user:1:202211 4
(integer) 1

(3)使用BITCOUNT key来统计全部签到的天数;

127.0.0.1:6379> BITCOUNT user:1:202211
(integer) 4

(4)使用BITPOS key bitValue统计第一次签到所在的天数;

127.0.0.1:6379> BITPOS user:1:202211 1
(integer) 1

注意:offset是从0开始的,所以具体的天数需要在返回值的基础上加1。

5、用户留存率

用户留存率同样使用的是Bitmap来实现。使用bitop op destkey key [key ...]来进行统计。其中:

  • op:表示需要进行的操作,交集(and)、并集(or)、非(not)、异或(xor);
  • destKey:存储操作结果
  • key [key ...]:需要处理的操作key

现对用户留存率进行统计,下面为具体的实现方式:

(1)插入用户每天的登录情况,其中key为日期,offset为用户ID,value表示是否登录;

127.0.0.1:6379> SETBIT user:view:20221106 5 1
(integer) 0
127.0.0.1:6379> SETBIT user:view:20221106 10 1
(integer) 0
127.0.0.1:6379> SETBIT user:view:20221106 15 1
(integer) 0
127.0.0.1:6379> SETBIT user:view:20221105 15 1
(integer) 0
127.0.0.1:6379> SETBIT user:view:20221105 14 1
(integer) 0
127.0.0.1:6379> SETBIT user:view:20221105 10 1
(integer) 0
127.0.0.1:6379> SETBIT user:view:20221104 10 1
(integer) 0
127.0.0.1:6379> SETBIT user:view:20221104 5 1
(integer) 0

(2)获取今天日活用户数;

127.0.0.1:6379> BITCOUNT user:view:20221106
(integer) 3

(3)统计4号,5号,6号3天都登录过的用户;

127.0.0.1:6379> BITOP and three:and user:view:20221104 user:view:20221105 user:view:20221106
(integer) 2
127.0.0.1:6379> BITCOUNT three:and
(integer) 1

(4)统计4号,5号,6号3天登录过的用户;

127.0.0.1:6379> BITOP or three:or user:view:20221104 user:view:20221105 user:view:20221106
(integer) 2
127.0.0.1:6379> BITCOUNT three:or
(integer) 4

注意:以下几种情况,需要对Bitmap进行额外处理

  • 如果网站活跃用户比较少,但是用户量又比较大的时候,需要酌情进行处理,比如:网站用户有1个亿,但只有最新的10万用户是活跃用户,这样使用bitmap的时候,就会存储大部分的0,造成内存的浪费
  • 用户ID不规范情况,比如用户ID是从10000开始的,那么可以对ID进行截取处理,避免前面都是0的情况