什么是Redis?
Redis 和 Memcached 有什么区别?
为什么用 Redis 作为 MySQL 的缓存?
主要是因为Redis具备高性能和高并发两种特性。
高性能:MySQL中数据是从磁盘读取的,而Redis是直接操作内存,速度相当快。
高并发:单台设备的Redis的QPS(每秒钟处理完请求的次数)是MySQL的十倍,Redis单机的QPS能轻松破10w,而MySQL单机的QPS很难破1w。所以,直接访问Redis能够承受的请求是远远大于直接访问MySQL的。
Redis数据类型
介绍Redis数据结构之前,先来介绍以下Redis对象系统。
什么是Redis对象系统?
Redis中基于双向链表、简单动态字符串、字典、跳跃表、整数集合、压缩列表、快速列表等数据结构实现了一个对象系统,并且实现了5种不同的对象,每种对象都使用了至少一种前面的数据结构,优化对象在不同场合下的使用效率。
对象结构 RedisObject 包含:type、encoding、lru、refcount、*ptr,下面逐一介绍:
type:表示对象的数据类型,包括字符串类型、列表类型、集合类型、有序集合类型、哈希类型,占4位。
encoding:对象的编码类型,占4位,标识了该对象使用了哪种底层的数据结构。
lru:该字段是一个时间戳,表示对象最近一次被访问的时间,用于实现内存管理。当 Redis 设置了最大内存限制并需要进行逐出策略时,LRU 被用来决定哪些数据应该被淘汰。
refcount:表示对该 RedisObject 的引用计数,当某个操作引用这个对象时,refcount 会加1;当引用被释放时,refcount减1。引用计数为0时,对象会被销毁,释放内存。
*ptr:指向底层数据实现的指针。
对象结构的功能?
- 为5种不同的对象类型提供同一的表示形式。
- 针对不同的场景,Redis 支持同一种对象类型使用多种不同的数据结构。
- 支持引用计数,实现对象共享机制。
- 记录对象的访问时间,便于删除对象。
字符串对象底层实现:
列表对象底层实现:
集合对象底层实现:
哈希对象底层实现:
有序集合对象底层实现:
Redis常见数据类型和应用场景
String
介绍
内部实现
具体如下:
常用命令
普通字符串的基本操作:
批量设置:
计数器(字符串的内容为整数的时候可以使用):
过期(默认为永不过期):
不存在就插入:
应用场景
1.缓存对象
2.常规计数
因为 Redis 处理命令是单线程,所以执行命令的过程是原子的。因此 String 数据类型适合计数场
景,比如计算访问次数、点赞、转发、库存数量等等。
比如计算文章的阅读量:
3.分布式锁
4.共享 Session 信息
List
介绍
内部实现
常用命令
应用场景
消息队列
2.如何处理重复的消息?
3.如何保证消息可靠性?
总结:
list 作为消息队列有什么缺陷?
list 不支持多个消费者消费同一条消息。Redis 从 5.0 版本开始提供 Stream 数据类型,同样满足消息队列三大需求,而且它还支持多个消费者消费同一条消息。
Hash
介绍
内部实现
常用命令
应用场景
1.缓存对象
2.购物车
以用户 id 为 key,商品 id 为 field,商品数量为 value,恰好构成了购物车的3个要素。
Set
介绍
内部实现
常用命令
Set 常用操作
Set 运算操作
交集运算:
并集运算:
差集运算:
应用场景
1.点赞
Set 类型可以保证一个用户只能点一个赞。
2.共同关注
Set 类型支持交集运算,所以可以用来计算共同关注的好友、公众号等。
3.抽奖活动
存储某活动中中奖的用户名 ,Set 类型因为有去重功能,可以保证同一个用户不会中奖两次。
Zset
介绍
内部实现
常用命令
Zset 常用操作
Zset 运算操作(相比于 Set 类型,ZSet 类型没有支持差集运算)
应用场景
1.排行榜
有序集合比较典型的使用场景就是排行榜。例如学生成绩的排名榜、游戏积分排行榜、视频播放排名、电商系统中商品的销量排名等。
2.电话、姓名排序
获取所有号码:
ZRANGEBYLEX phone - +
获取 132 号段的号码:
ZRANGEBYLEX phone [132 (133
获取132、133号段的号码:
ZRANGEBYLEX phone [132 (134
BitMap
介绍
内部实现
常用命令
bitmap 基本操作
bitmap 运算操作
# BitMap间的运算
# operations 位移操作符,枚举值AND 与运算 &OR 或运算 |XOR 异或 ^NOT 取反 ~
# result 计算的结果,会存储在该key中
# key1 … keyn 参与运算的key,可以有多个,空格分割,not运算只能一个key
# 当 BITOP 处理不同长度的字符串时,较短的那个字符串所缺少的部分会被看作 0。返回值是保存到 destkey 的字符串的长度(以字节byte为单位),和输入 key 中最长的字符串长度相等。BITOP [operations] [result] [key1] [keyn…]
# 返回指定key中第一次出现指定value(0/1)的位置
BITPOS [key] [value]
应用场景
1.签到统计
2.判断用户登录状态
3.连续签到的用户总数
如何统计出连续 7 天打卡的用户总数呢?
我们把每天的日期作为 Bitmap 的 key,userId 作为 offset,若是打卡则将 offset 位置的 bit 设置成 1。key 对应的集合的每个 bit 位的数据则是一个用户在该日期的打卡记录。
一共有 7 个这样的 Bitmap,对这 7 个 Bitmap 对应的 bit 位做与运算。如果最终 userId 对应的 bit 位为 1,就说明该用户 7 天连续打卡。将结果保存到一个新 Bitmap 中,我们再通过 BITCOUNT 统计 bit = 1 的个数便得到了连续打卡 7 天的用户总数了。
HyperLogLog
介绍
内部实现
常用命令
127.0.0.1:6379>pfadd uv1 a b c d e#uv1中5个元素:[a,b,c,d,e]
(integer) 1
127.0.0.1:6379>pfcount uv1 #uv1中数量为5
(integer)5
127.0.0.1:6379>pfadd uv2 b c d e f #uv2中5个元素:[b,c,d,e,f]
(integer) 1
127.0.0.1:6379>pfcount uv2 #uv2中数量为5
(integer)5
127.0.0.1:6379>pfcount uv1 uv2#获取uv1和uv2去重之后数量合集:[a,b,c,d,e,f], 数量为6
(integer)6
应用场景
百万级网页 UV(独立访客)计数
Redis HyperLogLog 优势在于只需要花费 12 KB 内存,就可以计算接近 2^64 个元素的基数,和元
素越多就越耗费内存的 Set 和 Hash 类型相比,HyperLogLog 就非常节省空间。
在统计 UV 时,可以用 PFADD 命令(用于向 HyperLogLog 中添加新元素)把访问页面的每个用户都添加到 HyperLogLog 中。
PFADD page1:uv user1 user2 user3 user4 user5
接下来,就可以用 PFCOUNT 命令直接获得 page1 的 UV 值了,这个命令的作用就是返回
HyperLogLog 的统计结果。
PFCOUNT page1:uv
GEO
介绍
内部实现
常用命令
# 存储指定的地理空间位置,可以将一个或多个经度(longitude)、纬度(latitude)、位置名称(member)添加到指定的 key 中。
GEOADD key longitude latitude member [longitude latitude member ...]# 从给定的 key 里返回所有指定名称(member)的位置(经度和纬度),不存在的返回 nil。
GEOPOS key member [member ...]# 返回两个给定位置之间的距离。
GEODIST key member1 member2 [m|km|ft|mi]# 根据用户给定的经纬度坐标来获取指定范围内的地理位置集合。单位:[m|km|ft|mi]->[米|干米|英里|英尺]
GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
应用场景
滴滴叫车
Stream
介绍
内部实现
常用命令
应用场景
消息队列
注:前面介绍的这些操作 List 也支持。
Stream 特有功能
Stream 可以使用 XGROUP 创建消费组,创建消费组之后,Stream 可以使用 XREADGROUP 命
令让消费组内的消费者读取消息。
到消费者使用 XACK 命令通知 Streams“消息已经处理完成”。
消费完成。如果消费者没有成功处理消息,它就不会给 Streams 发送 XACK 命令,消息仍然会留存。此时,消费者可以在重启后,用 XPENDING 命令查看已读取、但尚未确认处理完成的消息。
一旦消息被消费者处理了,消费者就可以使用 XACK 命令通知 Streams,然后这条消息就会被删除。
- 消息不丢
- 消息可堆积
总结
Redis数据结构
SDS
C语言 char* 字符数组的缺陷
SDS 做了哪些优化?
总结:Redis 的 SDS 结构是在原本字符数组基础上,增加了三个元数据:len、alloc、flags,用来解决 C 语言字符串的缺陷。
具体来说 SDS 的优势:
SDS 扩容规则:
比如 sdshdr16 和 sdshdr32 这两个类型,它们的定义分别如下:
这样设计有什么好处?
举例:使用编译优化,由原来的 8 字节变为 5 字节。
链表
Redis 的 List 对象的底层实现之一就是链表。下面我们来看一下链表的结构设计:
链表的优点
1.listNode 链表节点的结构里带有 prev 和 next 指针,获取某个节点的前置节点或后置节点的时
间复杂度只需O(1),而且这两个指针都可以指向 NULL,所以链表是无环链表;
2.list 结构因为提供了表头指针 head 和表尾节点 tail,所以获取链表的表头节点和表尾节点的时
间复杂度只需O(1);
3.list 结构因为提供了链表节点数量 len,所以获取链表中的节点数量的时间复杂度只需O(1);
4.listNode 链表节点使用 void* 指针保存节点值,并且可以通过 list 结构的 dup、free、match 函数
指针为节点设置该节点类型特定的函数,因此链表节点可以保存各种不同类型的值;
链表的缺陷
压缩列表
• zlbytes:占4个字节,记录整个压缩列表占用的内存字节数(总字节数)。
• zltail:占4个字节,记录压缩列表尾节点 entryN 距离压缩列表的起始地址的字节数。
• zllen:占2个字节,记录了压缩列表的节点数量。
• entry[1-N]:长度不定,保存数据。
• zlend:占1个字节,标记压缩列表的末端,固定值 0xFF(十进制255)。
举例:假如有三个节点:
注:如果 prevlen 属性的长度为 5 字节,那么第一字节会被设置为0xFE(十进制值254),而之后的四个字节则用于保存前一节点的长度。
举例说明:
连锁更新
总结压缩列表的缺陷
quicklist
typedef struct quicklist {//quicklist的链表头quicklistNode* head;//quicklist的链表尾quicklistNode* tail;//所有压缩列表中的总元素个数unsigned long count;//quicklistNodes的个数unsigned long len;...
} quicklist;
typedef struct quicklistNode {//前一个quicklistNodestruct quicklistNode* prev;//后一个quicklistNodestruct quicklistNode* next;//quicklistNode指向的压缩列表unsigned char* zl;//压缩列表ziplist的总长度unsigned int sz;//压缩列表ziplist中的元素个数unsigned int count : 16;....
} quicklistNode;
用一张图更好的理解一下:
listpack
listpack 结构设计
哈希表
Redis哈希表结构如下:
哈希表节点结构如下:
dictEntry 结构里不仅包含指向键和值的指针,还包含了指向下一个哈希表节点的指针,这个指针
可以将多个哈希值相同的键值对连接起来,以此来解决哈希冲突的问题,这就是链式哈希。
除此之外,dictEntry 结构里键值对中的值是一个「联合体 v」定义的,因此,键值对中的值可以是一个指向实际值的指针,或者是一个无符号的 64 位整数或有符号的 64 位整数或 double 类型的值。这么做的好处是可以节省内存空间,因为当「值」是整数或浮点数时,就可以将值的数据内嵌在 dictEntry 结构里,无需再用一个指针指向实际的值,从而节省了内存空间。
哈希冲突
什么是哈希冲突?
哈希表实际上是一个数组,数组里每一个元素就是一个哈希桶。当一个键值对的键经过 Hash 函数计算后得到哈希值,再经过(哈希值 % 哈希表大小),得到的结果值就是该 key-value 对应的数组元素位置,也就是第几个哈希桶。当有两个以上数量的 key 被分到哈希表中同一个哈希桶上时,此时称这些 key 发生了冲突。
链式哈希
Redis 采用了「链式哈希」的方法来解决哈希冲突。实现的方式就是每个哈希表节点都有一个 next 指针,用于指向下一个哈希表节点,因此多个哈希表节点可以用 next 指针构成一个单向链表,被分配到同一个哈希桶上的多个节点可以用这个单向链表连接起来,这样就解决了哈希冲突。
链式哈希的缺陷:
随着链表长度的增加,在查询该位置上的数据的耗时就会增加,最差情况下时间复杂度为O(n)。
要想解决这一问题,就需要进行 rehash,也就是对哈希表的大小进行扩展。
rehash
在实际使用哈希表时,Redis 定义了一个 dict 结构体,这个结构体里定义了两个哈希表(ht[2]),原因就是 rehash 的时候需要两个哈希表。
渐进式 rehash
rehash 触发条件
整数集合
整数集合是 Set 对象的底层实现之一。当一个 Set 对象只包含整数值元素,并且元素数量不大时,
就会使用整数集合这个数据结构作为底层实现。
整数集合结构设计
整数集合本质上是一块连续内存空间,它的结构定义如下:
整数集合的升级操作
整数集合升级有什么好处?
整数集合支持降级操作吗?
跳表
跳表结构体
跳表节点结构体
跨度是用来干什么的?
首先明确的是,跨度与遍历操作无关,遍历操作只需要用前向指针(struct zskiplistNode *forward)就可以完成,跨度实际是为了计算这个节点在跳表中的排位。具体怎么做的呢?因为跳表中的节点都是按序排列的,那么计算某个节点排位的时候,从头节点到该节点的查询路径上,将沿途访问过的所有层的跨度累加起来,得到的结果就是目标节点在跳表中的排位。
跳表节点查询过程
跳表节点层数设置
如何维持相邻两层的节点数量的比例为 2 : 1 呢?
对于跳表最高的层数,Redis 7.0 定义为 32,Redis 5.0 定义为 64, Redis 3.0 定义为 32。
面试题:为什么 Zset 的实现用跳表而不用平衡树(如 AVL树、红黑树等)?
Redis线程模式
Redis是单线程吗?
Redis采用单线程为什么还这么快?
Redis6.0之前为什么使用单线程?
1.官方回答:CPU并不是制约Redis性能表现的瓶颈(即使单线程的程序无法利用多核CPU),更多情况下是受内存大小和网络I/O的限制,所以Redis核心网络模型采用单线程没有问题,如果想使用服务器的多核CPU,可以在一台服务器上启动多个节点或者采用分片集群的方式。
2.使用单线程后,可维护性高,多线程可能会导致并发读写问题,增加系统复杂性,同时存在线程切换、锁带来的性能损耗。
Redis 6.0 之后为什么引入了多线程?
随着网络硬件性能提升,Redis性能瓶颈有时会出现在网络I/O处理上。为了提高网络I/O并行度,Redis6.0对于网络I/O采用多线程处理(对于命令的执行,仍然采用单线程处理)。引入多线程I/O特性对性能提升至少一倍以上。
Redis 持久化
Redis 如何实现数据不丢失?
AOF 持久化是如何实现的?
三种写回策略优缺点:
AOF日志过大,会触发什么机制?
AOF采用文件追加方式,文件会越来越大,当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF重写机制,对AOF文件的内容压缩,只保留可以恢复数据的最小指令集。
重写 AOF 日志的过程是怎样的?
触发重写机制后,主进程会创建重写AOF的子进程,重写AOF子进程会读取数据库中所有数据,并逐一把内存数据的键值对转换成一条命令,再将命令写入新的AOF文件。
问题:在重写过程中,主进程依然可以正常处理命令,如果在子进程重写AOF日志过程中,主进程修改了已经存在的 key-value,那么会发生写时复制,此时会导致主进程与子进程数据不一致,如何解决?
RDB 持久化是如何实现的呢?
RDB 持久化是把当前进程数据生成时间点快照保存到硬盘的过程(默认保存到dump.rdb中),避免数据意外丢失(Redis默认的持久化方式)。相比于AOF日志,RDB快照记录的是实际数据,因此在Redis恢复数据时,RDB恢复数据效率更高,只需将RDB文件读入内存即可,不需要像AOF那样需要额外执行操作命令才能恢复。
RDB做快照时会阻塞线程吗?
注:Redis的快照是全量快照,也就是每次执行快照,都是把内存中所有数据都记录到磁盘中。如果太频繁,会对Redis性能造成影响;如果频率太低,服务器故障时,丢失的数据会更多。
为什么会有混合持久化?
混合持久化工作流程:
Redis的大Key对持久化有什么影响?
如何避免大Key?
Redis 集群
Redis 如何实现服务高可用?
主从复制
在Redis中,用户可以通过执行 SLAVEOF 命令或者设置 slaveof 选项,让一个服务器去复制另一个服务器,我们称被复制的服务器为主服务器(master),而对主服务器进行复制的服务器则被称为从服务器(slave)。进行复制中的主从服务器双方的数据库将保存相同的数据,且主从服务器之间采用读写分离的方式,主服务器只写,从服务器只读。
为什么进行主从复制?
1.读写分离,性能扩展,降低主服务器的压力
2.容灾,快速恢复,主机挂掉时,从机变为主机
如何确定谁是主服务器,谁是从服务器?
命令传播:
主从服务器在完成第一次同步后,双方之间就会维护一个TCP长连接,目的是避免频繁的TCP连接和断开带来的性能开销。后续主服务器可以通过这个长连接继续将写操作命令传输给从服务器,然后从服务器执行该命令,使主从一致。
分摊主服务器的压力:
从服务器也可以拥有自己的从服务器,此时该从服务器不仅可以接受主服务器的同步数据,自己也可以作为主服务器将数据同步给自己所拥有的从服务器。通过这种方式,主服务器生成RDB和传输RDB的压力可以分摊到该从服务器上。
增量复制:
如果在命令传输中,TCP长连接断开,后续又恢复正常,怎么保证主从数据一致性?
在Redis2.8之前做法是主从服务器重新进行一次全量复制,从Redis2.8开始,主从服务器采用增量复制的方式继续进行同步,即只把网络断开期间的主服务器的写操作同步给从服务器。
面试题
Redis主从节点是长连接还是短连接?
长连接
怎么判断 Redis 某个节点是否正常工作?
主从复制架构中,过期key如何处理?
Redis 是同步复制还是异步复制?
Redis 主节点每次收到写命令之后,先写到内部的缓冲区,然后异步发送给从节点。
主从复制中两个 Buffer(replication buffer 、repl backlog buffer)有什么区别?
为什么会出现主从数据不一致?
如何应对主从数据不一致?
主从切换如何减少数据丢失?
主从切换中,产生数据丢失的情况有两种:
1.异步复制导致的数据丢失
解决:对于主节点,一旦所有从节点数据复制和同步的延迟超过 min-slaves-max-lag 定义的值,此时主节点拒绝接受任何请求。对于客户端,当发现 master 不可写后,采用降级措施,将数据暂时写入本地缓存和磁盘中。待 master 恢复正常后再重新写入。
2.集群产生脑裂导致数据丢失
解决:设置配置文件中参数,如果与主节点连接的从节点数量小于指定值或者主从复制延迟超过指定值,则主节点禁止写数据。
主从如何做到故障自动切换?
由哨兵自动完成故障发现和故障转移。
哨兵模式
为什么有哨兵机制?
哨兵机制是如何工作的?
1.监控:哨兵每隔1秒给所有主从节点发送PING命令,如果主从节点没有在规定时间响应哨兵的PING命令,哨兵会将它们标记为主观下线。为了减少误判,会部署多个哨兵节点组成哨兵集群(最少需要三台机器组成哨兵集群)。当一个哨兵判断主节点为主观下线后,就会向其他哨兵发起命令,其他哨兵收到命令后,根据自身和主节点的网络状况,做出赞成投票或拒绝投票的响应。当这个哨兵的赞同票数达到哨兵配置文件中的 quorum 设定的值后,此时主节点被哨兵标记为客观下线。(客观下线只适用于主节点)
由哪个哨兵做故障转移?
先选举候选者:哪个哨兵判断主节点为客观下线哪个就是候选者。
候选者选举成为leader: 2.选主+通知:
主从故障转移包含以下步骤:
步骤一:选出新主节点
步骤二:将从节点指向新主节点
步骤三:通知客户端主节点已更换
步骤四:将旧主节点变为从节点
哨兵继续监视旧主节点,当旧主节点重新上线时,哨兵集群就会向它发送 SLAVEOF 命令,让它成为新主节点的从节点。至此,故障转移完成。
哨兵集群是如何组成的?
切片集群模式
哈希槽如何映射到具体的Redis节点上?
注意:如果采用手动分配哈希槽,需要把 16384 个槽都分配完,否则Redis集群无法正常工作。
集群脑裂导致数据丢失怎么办?
什么是集群脑裂?
如何解决?
Redis 过期删除与内存淘汰
过期删除策略
Redis是可以对key设置过期时间的,过期删除策略用于将已过期的键值对删除。
如何设置过期时间?
如果想查看某个 key 剩余的存活时间,可以使用 TTL <key>
命令。
如果想取消 key 的过期时间,则可以使用 PERSIST <key>
命令。
如何判定 key 已过期了?
过期删除策略有哪些?
定时删除、惰性删除、定期删除
Redis 过期删除策略是什么?
Redis采用惰性删除+定期删除这两种策略配合使用。
惰性删除:Redis在访问或者修改key之前,会检查key是否过期,如果过期,则删除该key,返回null给客户端;如果没过期,直接返回正常结果给客户端。
定期删除:默认每100ms进行一次过期检查(该值可在Redis配置文件中修改,默认hz 10,即每秒进行十次过期检查),每次从过期字典中随机抽取20个key(固定值),检查是否过期,删除过期的key;如果本轮检查已过期的key的超过5个(即占比超过25%),则继续抽取;如果已过期的key比例小于25%,则停止删除,等待下一轮再检查。
可见,定期删除是一个循环的过程。为了保证定期删除不会出现循环过度,导致线程卡死的现象,为此增加了定期删除循环流程的时间上限,默认不会超过25ms。
内存淘汰策略
当Redis运行内存超过最大运行内存时,就会触发内存淘汰策略删除符合条件的key,以此保障Redis的高效运行。
如何设置 Redis 最大运行内存?
Redis 内存淘汰策略有哪些?
LRU 算法和 LFU 算法有什么区别?
什么是LRU算法?
Redis是如何实现LRU算法的?
什么是LFU算法?
Redis是如何实现LFU算法的?
Redis 缓存设计
什么是缓存雪崩?
大量缓存数据在同一时间过期或者Redis故障宕机时,如果此时有大量的用户请求,都无法在Redis中处理,于是全部请求都直接访问数据库,从而导致数据库压力骤增,甚至崩溃。
如何解决?
针对大量数据同时过期:
1.均匀设置过期时间:避免将大量数据设置成同一过期时间,在对缓存数据设置过期时间时,给这些数据的过期时间加上一个随机数,保证数据不会在同一时间过期。
2.互斥锁:如果发现数据不在Redis中,就加一个互斥锁,保证同一时间只有一个请求来构建缓存(从数据库读取数据,再将数据更新到Redis中),当缓存构建完成后,再释放锁。
3.监控缓存过期,提前更新
针对Redis故障宕机:
1.服务熔断或请求限流机制:服务熔断即暂停对缓存的访问,直接返回错误,不再访问数据库。
请求限流机制只将少部分请求发送到数据库进行处理。
2.构建Redis集群:当Redis主节点故障宕机,从节点切换成为主节点,继续提供缓存服务。
什么是缓存击穿?
如果缓存中某个热点数据过期了,此时大量的请求访问该热点数据,将无法从缓存中获取,直接访问数据库,导致数据库容易被高并发的请求击垮。
如何解决?
1.互斥锁
2.不给热点数据设置过期时间
什么是缓存穿透?
如何解决?
布隆过滤器如何工作?
如何设计一个缓存策略,可以动态缓存热点数据呢?
说说常见的缓存更新策略?
Cache Aside策略:
Read/Write Through策略:
Write Back策略:
数据库和缓存如何保证一致性?
先更新数据库,再更新缓存:
解决:
先更新缓存,再更新数据库:
Cache Aside 策略:
该策略可以细分为读策略与写策略:
先删除缓存,再更新数据库:
A请求先删除缓存,在A更新数据库之前,B请求到来,B先访问缓存,发现缓存未命中,于是访问数据库,读取数据库的值并更新到缓存,此时A更新数据库为新值,但缓存中还是旧值,造成二者不一致。所以,此策略在读写并发时,存在缓存与数据库不一致问题。
先更新数据库,再删除缓存:
存在问题: 如果删除缓存失败,导致缓存中的数据还是旧值,而数据库中的数据是新值。
解决:利用消息队列进行删除的补偿。在删除缓存时,将数据加入到消息队列,由消费者从消息队列中读数据,执行删除操作,删除成功后,将数据从消息队列中移除。
Redis 实战
Redis 如何实现延迟队列?
Redis 的大 key 如何处理?
什么是 Redis 大 key?
如何查找大 key?
1.redis-cli --bigkeys 查找大key
2.使用 SCAN 命令查找大 key
3.使用 RdbTools 工具查找大 key
如何删除大 key?
删除操作的的本质是释放键值对所占用的内存空间,在释放内存时,操作系统会将释放的内存块插入到空闲内存块的链表中,以便后续进行管理和再分配。如果一下释放大量内存,操作空闲内存块链表时间就会增加,进而造成Redis主线程阻塞,导致其他请求无法响应。
1.分批次删除:先获取一定数量的大key,再将这些大key分批次删除。
2.异步删除:
Redis 管道有什么用?
管道技术是客户端提供的一种批处理技术,用于一次处理多个Redis命令,从而提高交互的性能。
Redis 事务支持回滚吗?
不支持。对于MySQL事务中,只要有一条命令执行失败,事务就会回滚到之前的状态,但Redis事务中,一条命令失败不会影响其他正确的命令,其他正确的命令仍然可以执行成功。
如何用 Redis 实现分布式锁的?
如何使用分布式锁?
如何解锁?
基于 Redis 实现分布式锁有什么优缺点?
优点:
1.性能高效:redis读写速度快,适合高并发的锁操作场景。
2.实现方便:可直接使用setnx方法实现。
3.避免单点故障:Redis是跨集群部署的,自然就避免了单点故障。
缺点:
1.超时时间不好设置:时间过长,影响性能;时间过短,起不到保护共享资源的目的。
2.Redis主从复制中数据是异步复制的,导致分布式锁的不可靠性: