面试干货10——聊一聊Redis的应用吧!(实现分布式锁缓存抽奖热搜点赞商品筛选..)
Posted LuckyWangxs
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面试干货10——聊一聊Redis的应用吧!(实现分布式锁缓存抽奖热搜点赞商品筛选..)相关的知识,希望对你有一定的参考价值。
Redis,全称REmote DIctionary Server,是由K-V组织起来的数据结构,其有8种数据类型,其中常用的有5种,分别是
String
、
Hash
、
List
、
Set
、
Sorted Set
。
一、String类型
1. 对String类型的理解
你可以将redis的String
类型理解为Java中的HashMap
,如下:
HashMap<String, Object>
· key 为字符串类型
· value 可以是如下类型
String类型
Numbers数值类型
bit类型(位数组)
2. 常用命令如下
set key value --存入一个字符串键
setnx key value --存入key不存在的字符串键, 若key存在, 则存入失败
get key --获取指定key的字符串
mset k1 v1 k2 v2 --批量存入字符串键
mget k1 k2 --批量获取指定key的字符串
del key --删除指定key-value
3. 应用场景
① 分布式锁
- 分布式锁1:
setnx lockObj true //获取锁
del lockObj //释放锁
但是可能获取锁后由于某种原因没能释放锁就会导致死锁
- 分布式锁2:
// 获取锁, 参数EX过期时间, NX相当于setnx:set not existed
set lockObj true EX 100 NX
del lockObj //释放锁
但是可能拿到锁的线程还未执行完就释放了锁,最终导致两个线程共同执行一段代码 不安全
优化: 设置后台线程,检测当前线程是否执行完,如果未执行完就到了过期时间那么给锁续期
② 缓存设计
通常我们需要对数据库表中的热点数据进行缓存
-
方式一: 将一个对象转为json格式,然后以
String
的方式存入Redis,但是当我们需要对对象的某个属性进行修改时,我们需要先将对象取出,然后再反序列化为java bean
,然后set值,再序列化为json,再放入Redis。但是这样做是比较麻烦的 -
方式二: 用一个合理的key设计并配置批处理set与get
例如 {表明}:{PK}:{字段名} PK为表中数据的唯一标识
mset user:1:name zhangsan user:1:age 18
mset user:2:name lisi user:2:age 19
当我们取值的时候就可以通过关键PK取,那么修改也可以直接替换
③ 全局唯一序列号、计数器(value为Numbers类型)
- 计数器
采用如下原子操作命令
incr key --自增1
decr key --自减1
incrby key incrs --在原来基础上加incrs
decrby key decrs --在原来基础上减decrs
利用上述命令可以实现访问量业务或是接口频率限制等
- 全局序列实现
在分库分表或者分布式系统中,想要保证全局唯一序列,可以利用redis的自增操作,通常情况下会从redis中通过incrby命令拿一段序列,存到当前服务的内存中,用完了再去取,而非每次需要序列时都去redis执行incr操作,这是为了减少跟redis的交互,减少网络IO。
④ 判断大数据量包含(在线用户数量统计、布隆过滤器)(bit类型)
bitmap --------- [0][0][1][0][1][0][0]
getbit key offset --获取key下标为offset的值
setbit key offset --设置key下表为offset的值
bitcount key start end --统计key从start到end位置设置为1的数量
bitops op destkey [key..] --对多个key进行位运算 op(and/or/xor/not)(与或异或非)
- 在线用户数量统计
用户主键为自增,我们可以用主键作为bitmap的下表,当用户上线,将其置为1,用户下线置为0,用bitcount userOnline
即可统计出在线用户数量;此种方式是非常节省内存的,一位即可表示一位用户,那么1KB就可以标记8 * 1024
个用户。此外还有很多应用场景,比如验证码、限时活动等业务,在这里就不再列举。
二、hash哈希结构
1. 对hash结构的理解
依旧可以理解为Java中的HashMap
HashMap<String, HashMap<String, Object>>
属于三维数据结构, key为String
类型, value可以理解为存放的是一个对象, 可以通过属性对值进行操作
2. 常用命令
hset key field value --存入一个key filed散列结构
hsetnx key field value --同hset, 若存在则操作失败
hget key field --获取指定key的field的值
hmset k1 f1 v1 k2 f2 v2 --批量存入
hget k1 f1 k2 f2 --批量获取
hdel key field --删除指定key的field
hincrby key field incrs --对指定的key的field的值加incrs
3. 应用场景
① 缓存设计
String数据类型也可以用于缓存,比如将对象序列化放入缓存,或者是利用上述第二种方式设计,我们会发现,其实hash
结构跟String类型的第二种设计方式很类似,但是为什么还要用hash
结构呢?
- 1)可以用一个key将一整个对象或者是相关的一部分数据聚集起来,便于管理
- 2)可以减少内存/IO/CPU的消耗
因为一些ttl的扫描或者更新,redis内部都是对key进行扫描监控的,如果string类型的第二种方式设计缓存,那么一个对象无疑需要存很多个key,如此一来就比较消耗IO/COU与内存,而hash结构会将String
类型的第二种方式的很多个key聚集没一个key,redis内部只要对一个key进行扫描监控即可,这也是hash结构的主要优势
注意: 但是在某些场景,散列结构是代替不了String
结构的,
- 例如二进制bit类型,
hash
不支持bitmap - 例如当我们需要考量缓存设计当中,数据分布的时候,散列结构就不合适了,key较少 数据太聚集
② 购物车实现
只保存购物车商品数据的逻辑关系
// 添加
hincrby {userId}:shoppingCart {goodsId} {count} //userId用户的购物车的goodsId的商品添加了count件
// 查询
hget {userId}:shoppingCart
三、List结构
1. 对list结构的理解
// 可以理解为
HashMap<String, List<Object>>
2. 常用命令
Redis的List是有序的,类似于Java中的队列,又能像Java中的List
一样用下标取值,只不过Redis的List
有正负下标。
常用0到-1表示列表中的所有值,1到1表示下表为1的值,结合下面的命令可以按下标取单个值或多个值
lpush key value #往key的列表中左边放入一个元素, key不存在则新建
rpush key value #往key的列表右边放入一个元素
lpop key #从key的列表左边弹出一个元素
rpop key #从key的列表右边弹出一个元素
lrange key start end #获取列表键从start到end下表的元素, 如果start=end, 那就是取一个元素
blpop key[key...] timeout #阻塞地从key的列表键最左端弹出一个元素, 若列表没元素, 则等待t秒, 若t为0, 则一直等待
brpop key[key...] timeout #同上, 只不过从最右端弹出一个元素
3. 应用场景
① 消息队列实现
生产者在List
左端lpush
生产,消费者从List
右端brpop
,让timeout为0,一直等待List
中的消息。
但在数据可靠性以及如何防止消息丢失上还是有诟病的,只能说redis在缓存方面有优势,其他的还是交给专用组件去做比较好。
② 关注的最新消息列表(微信朋友圈、抖音我的关注的视频)
以下仅表示数据的逻辑关系,具体实现还要具体思考,但可以借鉴
当你抖音关注的人发了视频,或者你微信的朋友发了动态,那么将消息id从你的消息列表左侧推入,当你上线,或者是浏览的时候,会从最左侧拿到最新数据,如果数据量较大,再进行分页设计
# 消息保存
lpush {userId}:msgList {msgid}
# 获取最新消息, 下面命令可以拿到最新的10条消息
lrange {userId}:msgList 0 10
四、Set数据结构
1. 对Set结构的理解
// 可以理解为
HashMap<String, HashSet>
2. 常用命令
sadd key member[member...] --往集合key中存放元素, 若key不存在则新建
srem key member[member...] --从集合key中删除元素
smembers key --获取集合key中所有的元素
scard key --获取集合key中所有元素的个数
sismember key member --判断member是否存在于集合key中
srandmember key [count] --从集合key中随机选出count个元素, 但不删除
spop key [count] --从集合key中弹出count个元素并删除
3. 应用场景
① 抽奖活动(刷礼物抽奖/拼多多抽奖)
抽奖活动有很多种,常见的有给主播刷礼物,主播会让你参与抽奖活动,抽到的用户将会获得奖品,这是针对用户抽奖,另一种是针对商品,比如拼多多,转动转盘,抽到什么,额…其实也不给什么。可以参考如下逻辑
sadd activeKey userId # 将满足条件的用户添加到activeKye的活动集合中
smembers activeKey # 获取所有满足条件的用户, 然后随机滚动或转动圆盘
# 获取中奖名单
spop activeKey [count] 或者 srandmembers activeKey [count]
像随机的抽奖活动都可以用这种方式实现,但是,你有没有发现拼多多,你每次抽奖都会抽到指定的那个,或者某个人给主播刷了很多礼物,那么我就想让他中奖概率很大,那怎么办呢??我也在思考ing…
② 打卡/点赞/签到
用户1005点赞8888帖子 sadd like:8888 1005
用户1005取消8888帖子的点赞 srem like:8888 1005
检查1005用户是否点赞过 sismember like:8888 1005
获取8888帖子的点赞数 scard like:8888
获取帖子8888的点赞用户 smembers like:8888
③ 共同关注/商品推荐 模型
共同关注模型以及商品推荐模型依赖于set集合的计算,即交集、并集与差集,Redis也给我们提供了命令,如下:
sinter key[key...] --交集
sunion key[key...] --并集
sdiff key[key...] --差集
共同关注模型如抖音里的共同好友,或者是你可能感兴趣的人,又或者是你关注的人也关注了谁。如下:
- 张三:zhangKey 关注了 {li, wang, zhao}
- 李四:liKey 关注了 {zhang, wang, zhao, tian}
- 王五:wangKey 关注了 {zhang, li, zhao, tian}
- 当张三打开李四的抖音主页
- 张三和李四的共同关注则为
sinter zhangKey liKey
--> {wang, zhao} - 张三关注的人也关注了他, 这实际上是对张三关注列表的一个遍历, 判断张三关注的人的关注列表里是否包含李四
sismember wangKey li
、sismember zhaoKey li
- 张三可能认识的人, 即张三关注的人的关注列表可能有张三认识的, 所以在这就是李四关注的, 但是张三没有关注的, 可能就是张三认识的人, 所以取张三关注列表与李四关注列表的差集即可
sdiff zhangKey liKey
④ 商品筛选
每个商品入库的时候即会建立他的静态标签列表,例如品牌、尺寸、处理器、内存大小…每个标签都作为一个key,在每个标签中添加元素例如
sadd brand::apple iphone12
sadd screenSize::5.6-6.0 iphone12
sadd os::ios iphone12
...
那么当我选了筛选条件以后,后台只需要对这个集合做一个交集即可获得筛选后的结果
sinter brand::apple screenSize::5.6-6.0 os::ios --> {iphone12}
五、Sorted Set数据结构
1. 理解
Sorted Set
又叫ZSet
,其一个成员是由分值与元素组成的,分值是用来排序的。跟List
一样,有正负下标,0到-1依旧表示所有元素。
zadd key score element[...]
2. 常用命令
zadd key score element[...] --往有序集合key中存入元素, 若不存在则新建
zrem key element[element...] --从有序集合key中删除元素
zscore key element --获取有序集合key中element元素的score
zincrby key incrment element --对有序集合key中的element元素的score加incrment
zcard key --获取有序集合key中元素的个数
zrange key start end [withscores] --正序获取有序集合key从start到end的元素
zrevrange key start end [withscores] --倒叙获取有序集合key从start到end的元素
zunionstore destkey numkeys key[key...] --并集计算
zinterstore destkey numkeys key[key...] --交集计算
命令应用
// 向工资最高的人的有序集合假如三个元素, 每个元素的分值为工资, 紧跟的是人的姓
zadd "工资最高的人" 100000 wang 20000 li 3000 zhao
// 倒序取出所有元素, 排在第一位的便是工资最高的人
zrevrange "工资最高的人" 0 -1
3. 应用场景
① 单日排行榜/微博热搜
点击量490万可以作为ZSet
元素的分值score,新闻的id可以作为ZSet元素的element,当新闻被点击一次,我就对对应的新闻id的score进行+1操作,最终正序获取前几个就是热搜。命令使用如下:
// 新闻点击量, key为hotnew:当前日期(年月日) 单日排行榜
// 点击一次, 就加一次, 不存在则创建
zincry hotnews:{date} 1 {newsID}
// 获取前6位作为热搜数据
zrevrange hotnews:{date} 0 5
② 周榜/月榜/年榜
通过ZSet
的并集操作即可实现,7天为周榜,4周为月榜,12个月为年棒
以上内容便是Redis在互联网行业的应用,这篇文章应该能帮助你更加理解Redis的数据结构,以及每种数据结构都有什么用处,也拓展了你的见识,面试有可能会问到哦~~
以上是关于面试干货10——聊一聊Redis的应用吧!(实现分布式锁缓存抽奖热搜点赞商品筛选..)的主要内容,如果未能解决你的问题,请参考以下文章