在两个月前的学习中,我看过一个redis相关的讲解视频,是一个叫诸葛的老师,其中分为几层进行讲述,分别是数据类型、分布式锁、redis常见问题等。当时有记录一些内容,下面将按照顺序进行分享。
(1)String
常用操作:
1)字符串常用操作
set key value;
mset key valye [key value …]; //批量存储字符串键值对
setnx key value; //存入一个不存在的字符串键值对
get key [key …];
del key [key …];
expire key seconds //设置一个键的过期时间
2)原子加减
incr key //将key中存储的数字加1
decr key //将key中存储的数字减1
incrby key increment //将key所存储的值加上increment
decrby key decrement //将key所存储的值减去decrement
3)分布式锁
setnx [key] true //返回1代表取锁成功,返回0代表获取锁失败
del [key] //释放锁,即删除锁
set [key] true ex 10nx //防止程序意外终止导致死锁,即设置过期时间
同时还可以存储对象数据,不过需要将对象数据转Json数据格式。
应用场景:
1)计数器
incr article:readcount:{文章id} //针对文章id的阅读值,做原子性的+1操作
get article:readcount:{文章id} //获取阅读数
2)分布式系统全局序列号
increby orderid 100 //redis批量生成序列号提升性能
解释:一次操作取100个编号,利用自增部分来使用编号。从性能角度分析,原来是操作100次redis进行取值,现在只需要操作一次,从而实现性能提升。
(2)Hash
hset key field value //存储一个hash表key的值
hsetnx key key field value //存储一个不存在的hash表的值
hmset key field value [field value …] //在一个哈希表中存储多个键值对
hget key field //获取哈希表中key对应的field键值
hmget key field [key field …] //批量获取哈希表中的key对应的多个field键值
hdel key field [field …] //删除
hlen key //返回哈希表中key对应的field的数量
hgetall key //返回哈希表中key中所有的键值
hincrby key field increment //为哈希表中的key的field键的值加上增量increment
应用场景:
1)电商购物车
①以用户id为key
②商品id为field
③商品数量为value
2)购物车操作
①添加商品->hset cart:1001 10088 1
②增加数量->hincrby cart:1001 10088 1
③商品总数->hlen cart:1001
④删除商品->hdel cart:1001 10088
⑤获取购物车所有商品->hgetall cart:1001
(3)List
lpush key value [value …] //将一个或多个值value插入key列表的表头(最左边)
rpush key valye [value …] //将一个或多个值value插入key列表的表胃(最右边)
lpop key //移除并返回key列表的头元素
rpop key //移除并返回key列表的尾元素
lrange key start top //返回列表key中指定区间内的元素,区间以偏移量start和stop指定。(分正向索引和负向索引)
blpop key [key …] timeout //从key列表表头弹出一个元素,若列表中没有元素,阻塞等待timeout秒,如果timeout=0,一直阻塞等待
brpop key [key …] timeout //从key列表表尾弹出一个元素,若列表中没有元素,阻塞等待timeout秒,如果timeout=0,一直阻塞等待
常用的分布式数据结构:
Stack(栈) = lpush + lpop = FILO
Queue(队列) = lpush + rpop = FIFO
Blocking MQ(阻塞队列) = lpush + brpop
应用场景:
1)微博和微信公众号消息流
诸葛老师关注了Caoz,备胎说车等大V。
①CaoZ发微博,消息id为10018
lpush msg:{诸葛老师-ID} 10018
②备胎说车发微博,消息id为10086
lpush msg{诸葛老师-id} 10086
③查看最新微博消息
lrange msg:{诸葛老师-id} 0 4
解释:可能存在关注同一个人,但是接收到新推送的消息存在延迟,因为底层实现排队发送,比如优先发送在线人员,在发送不在线的。
(4)Set
应用场景:
1)抽奖程序(微信小程序等)
2)微信微博点赞、收藏、标签
3)集合操作实现电商商品筛选
集合的运算(实现关注模型)
1)交集
{a,b,c} {a,c} => {a,c}
2)差集
{a,b,c} {b,d,f,g} => {a,c}
3)并集
{a,b,c} {c,d,f,g} => {a,b,c,d,f,g}
(5)有序Set
应用场景:
1)实现热榜排行前十等
底层实现结构:
1)压缩列表
(后续补充)
2)跳表
当压缩列表达到一个大小时,会从压缩列表转化为跳表。跳表结构有点类似mysql底层数据结构,B+tree,即冗余排好序的序号作为结点头部,利用多路查询树的结构进行设计。
(1)基于原生的redis命令setnx
在redis中,基于setnx命令的操作,就可以实现分布式锁相关业务,与在Java中的redistemplate中是使用setIfAbsent是一致的。
(2)基于redission组件
该组件就是基于一系列的分布式场景设置了许多类型的锁,比如基础的同步锁,读写锁等,读者可以自行探讨。
分布式锁的设计理念:
①对缓存内存进行设置(单值设置,如果存在多个线程进行设置时,只有一个线程能设置成功,其他线程失败);
②设置缓存失效时间;(解决因程序出错,导致死锁的问题)
③通过lua代码实现一个锁续命操作,底层成为看门狗,负责检测当前锁是否失效,如果没有,则重新设置锁的过期时间;注:lua代码的执行是一个原子性操作,意味着有么就全部操作正常,要么就全部操作失败;(确保原子性)
当业务代码涉及到先查询缓存,如果存在,则取出redis中的缓存值,并且返回,否则查询数据库,取到对应内容,再设置到缓存中,并且返回,此时就会出现下述前三个问题。
(1)缓存击穿(缓存失效)
解释:也叫热点Key问题,就是一个被高并发访问并且缓存重建业务较复杂(意味着对数据库压力相对较大)的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的压力,导致数据库服务挂掉(可以理解为redis的缓存暂时没有,此时高并发情况下,所有线程进到接口时,都会判断当前redis缓存不存在,因此会有大量的请求访问数据库)。
解决方法:
1、互斥锁:当同个业务不同线程访问redis未命中时,先获取一把互斥锁,然后进行数据库操作,此时另外一个线程未命中时,拿不到锁,等待一段时间后重新查询缓存,此时之前的线程已经重新把数据加载到redis之中了,线程二就直接缓存命中。这样就不会使得大量访问进入数据库。
(2)缓存穿透
解释:有一个商品被删掉了,但是在高并发情况下,首先去检查缓存,此时发现缓存没有,就去查询数据库,此时还是查询不到,然后就返回不了任何数据。
解决方案:当查询到数据库发现不存在时,可以设置一个空值,就可以避免所有请求直接访问数据库,减轻数据库压力。
(3)缓存雪崩
解释:当缓存在同一时间内大部分失效,此时导致所有的请求访问接口时,会同时访问数据库,造成数据库服务访问压力过大。
解决方法:在设置过期时间时,利用固定时间+随机数时间进行设置,避免同一时间大面积缓存失效。