Redis
Redis底层原理
基本数据类型
内部数据结构
https://zhuanlan.zhihu.com/p/70174863
https://baijiahao.baidu.com/s?id=1642948039877235233&wfr=spider&for=pc
string
Simple dynamic string (SDS)
struct sdshdr{
//记录buf数组中已使用字节的数量
//等于 SDS 保存字符串的长度
int len;
//记录 buf 数组中未使用字节的数量
int free;
//字节数组,用于保存字符串
char buf[];
}
- 常数级时间复杂度 获取串长度
- 采用了空间预分配策略避免C字符串每一次修改时都需要进行内存重分配(C是修改N次就必然分配N次,SDS是修改N次最多分配N次)
- 惰性空间释放用于优化SDS的 字符串缩短 操作:当SDS的API需要缩短SDS保存的字符串时,程序并不立即使用内存重分配来回收缩短后多出来的字节,而是使用free属性见这些字节的数量记录起来,并等待将来使用。
- 字符串扩展 时并不是必然进行内存分配(有free的存在)
- 可以保存二进制数据(’\0’不作为结束符)
list
双向链表
hash
字典在Redis中应用非常广泛,Redis数据库本身就是使用字典来作为底层实现的,比如我们执行命令:SET msg “hello world”,redis就会在数据库中创建一个键为msg值为"hello world"的字典。
字典在实现上包含两个hash表,常规状态下,只有第一个hash表存储数据,当发生rehash时,会给第二个hash表分配空间,并且把第一个hash表中的数据拷贝到第二个hash表中。如果hash表中的数据量比较大时,会采用渐进式rehash,分多次完成,字典中有个rehashindex字段记录rehash进度(如果当前未进行rehash值为-1),这样的话在字典中查询数据时,可能会查找两个hash表,先查第一个表,如果查不到再查第二个,但是新增的数据都会添加到第二个hash表中,发生rehash之后会由第二hash表负责查询客户端请求数据。
set
set是一个无序的、自动去重的集合数据类型,Set底层用两种数据结构存储,一个是hashtable,一个是inset。
inset为可以理解为数组,使用inset数据结构需要满足下述两个条件:
- 元素个数不少于默认值512
- 元素可以用整型表示
zset
- zset为有序(有限score排序,score相同则元素字典序),自动去重的集合数据类型,其底层实现为 字典(dict) + 跳表(skiplist)
- 当数据比较少的时候用ziplist编码结构存储。
ziplist
字典(dict) + 跳表(skiplist)
字典:保证数据是唯一,并且查询的时间复杂度为O(1)
跳表:保持有序,并且便于数据的插入
跳表
可看做是二叉树的一个变种,在原有有序链表上面添加了多级索引,然后通过索引来实现快速查找。
它在性能上和红黑树,AVL树相差无几。
平衡二叉树也可以实现以上效果,那么为什么选择跳表呢?
范围查找:跳表的范围查找更加简单(只需要找到最小值之后,对最底层进行若干步数的遍历)
插入删除逻辑简单:平衡树的插入和删除操作可能引发子树的调整,逻辑复杂,而skiplist的插入和删除只需要修改相邻节点的指针,操作简单又快速。
内存占用:从内存占用上来说,与平衡树相差无几,甚至更灵活一些。
一般来说,平衡树每个节点包含2个指针(分别指向左右子树),而skiplist每个节点包含的指针数目平均为1/(1-p),具体取决于参数p的大小。
如果像Redis里的实现一样,取p=1/4,那么平均每个节点包含1.33个指针,比平衡树更有优势。
应用场景
String | 分布式锁:使用setnx("lock", "Redis Lock!!!") 计数器(帖子阅读量) 共享Session |
List | 消息队列 redis的lpush+brpop命令组合即可实现阻塞队列,生产者客户端是用lupsh从列表左侧插入元素,多个消费者客户端使用brpop命令阻塞时的“抢”列表尾部的元素,多个客户端保证了消费的负载均衡 和高可用性 栈 队列 |
Hash | 放置对象: Student:{ ID1:{Name,age,sex,…} ID2:{Name,age,sex,…} ID3:{Name,age,sex,…} …} |
Set | 共同好友 |
Zset | 排行榜:有序集合经典使用场景。例如视频网站需要对用户上传的视频做排行榜,榜单维护可能是多方面:按照时间、按照播放量、按照获得的赞数等。 |
需要统计某个商家商品的实时销量排行,可以使用
SQL
语句,根据销量字段排序,但是这个方法需要进行全表扫描,当数据量非常大的时候,效率很低
- 当存在点赞的时候,更新zset对应记录,并更新Mysql
- 使用reverseRangeWithScores获取对应排行榜
单线程
Redis客户端对服务端的每次调用都经历了发送命令,执行命令,返回结果三个过程;
其中执行命令阶段,由于Redis是单线程来处理命令的,所有每一条到达服务端的命令不会立刻执行,所有的命令都会进入一个队列中,然后逐个被执行。并且多个客户端发送的命令的执行顺序是不确定的。但是可以确定的是不会有两条命令被同时执行,不会产生并发问题,这就是Redis的单线程基本模型。
Redis只是在执行客户端提交的命令时会进行排队然后逐条执行,其他的操作,比如持久化、异步删除、集群同步都是由其它的额外线程执行的。
所以我们常常说的Redis单线程其实是命令执行的时候的单线程
单线程的优势
- 不存在多线程情况中的上下文切换而消耗CPU
- 编码逻辑更简单
- 不需要考虑各种锁的问题,也不需要考虑死锁问题
Redis为什么很快
- 绝大部分是纯粹的内存操作
- 采用单线程,不存在上下文切换和多线程中线程竞争的问题
- 高效的数据结构
- 非阻塞式IO,多路复用IO,采用epoll作为IO多路复用技术的实现
BIO,NIO,IO复用,AIO,同步,异步,阻塞,非阻塞
多路复用之前,有必要讲一下什么是阻塞IO、非阻塞IO、同步IO、异步IO这几个东西;linux的五种IO模型
- 阻塞I/O(blocking I/O)
- 非阻塞I/O(nonblocking I/O)
- I/O复用(select和poll)(I/O multiplexing)
- 信号驱动I/O(signal driven I/O (SIGIO))
- 异步I/O(asynchronous I/O (the POSIX aio_functions))。
**同步:**等待应答结果时,主动的获取消息结果;
可以什么都不干一直等待结果(阻塞);也可以先干其他事情,但是每隔一段时间进行询问应答结果(非阻塞,轮询)
**异步:**等待应答结果时,等待别人通知;
I/O模型
网络IO的本质是socket的读取,socket在linux系统被抽象为流,IO可以理解为对流的操作。刚才说了,对于一次IO访问(以read举例),数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。所以说,当一个read操作发生时,它会经历两个阶段:
-
第一阶段:等待数据准备 (Waiting for the data to be ready)。
-
第二阶段:将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)。
对于socket流而言:
- 第一步:通常涉及等待网络上的数据分组到达,然后被复制到内核的某个缓冲区
- 第二步:把数据从内核缓冲区复制到应用进程缓冲区。
阻塞式I/O
最广泛的模型是阻塞I/O模型,默认情况下,所有套接口都是阻塞的。 进程调用recvfrom系统调用,*整个过程是阻塞的*,直到数据复制到进程缓冲区时才返回(当然,系统调用被中断也会返回)
非阻塞式I/O
当我们把一个套接口设置为非阻塞时,就是在告诉内核,当请求的I/O操作无法完成时,不要将进程睡眠,而是返回一个错误。当数据没有准备好时,内核立即返回EWOULDBLOCK错误,第四次调用系统调用时,数据已经存在,这时将数据复制到进程缓冲区中。这其中有一个操作时轮询(polling)。
I/O多路复用
阻塞I/O只能阻塞一个I/O操作,而I/O复用模型能够阻塞多个I/O操作,所以才叫做多路复用
从流程上来看,使用select函数进行IO请求和同步阻塞模型没有太大的区别,甚至还多了添加监视socket,以及调用select函数的额外操作,效率更差。但是,*使用select以后最大的优势是用户可以在一个线程内同时处理多个socket的IO请求*。用户可以注册多个socket,然后不断地调用select读取被激活的socket,即可达到在同一个线程内同时处理多个IO请求的目的。而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的。
Select, Poll, Epoll
select,poll机制:
当触发IO事件之后,会无差别轮询所有的流,找出可出发IO事件的流,并进行处理!
select | poll | |
---|---|---|
最大文件描述符数量 | 有限制 | 无限制 |
底层结构 | 数组 | 链表 |
select 有最大连接数目的限制(数组实现)
poll没有最大连接数目的约束(链表实现)
Epoll机制:
当触发IO事件,Epoll方式会直接处理触发IO事件的流的集合,而不需要遍历所有的流。
select | poll | epoll | |
---|---|---|---|
最大文件描述符数量 | 有限制 | 无 | 无 |
底层结构 | 数组 | 链表 | 红黑树 |
处理方式 | 遍历所有fd,找出活跃FD | 遍历所有fd,找出活跃fd | 只处理活跃可用的fd |
内存拷贝 | 内核需要将消息传递到用户空间,都需要内核拷贝动作 | 都需要内核拷贝动作 | 利用mmap()文件映射内存加速与内核空间的消息传递(内核和用户空间共享一块内存) |
polling
while true { for i in stream[] // 忙轮询的方式,stream代表不同流组成的数组 { if i has data read until unavailable } }
要把所有流从头到尾查询一遍,就可以处理多个流了,但这样做很不好,因为如果所有的流都没有I/O事件,白白浪费CPU时间片。
— 待确认
Select
while true
{
select(streams[]) //这一步死在这里,知道有某个流有I/O事件时,才往下执行
for i in streams[]
{
if i has data
read until unavailable
}
}
从select那里仅仅能知道有I/O事件发生,却并不知道是哪几个流(可能有一个,多个,甚至全部),我们只能无差别轮询所有流,找出能读出数据,或者写入数据的流,对他们进行操作。所以***select具有O(n)的无差别轮询复杂度***,同时处理的流越多,无差别轮询时间就越长。
epoll
while true
{
active_stream[] = epoll(epollfd) // epoll会把具体哪些流发生了I/O事件通知我们
for i in active_stream[]
{
read or write till
}
}
****epoll可以理解为event poll****,不同于忙轮询和无差别轮询,epoll会把哪个流发生了怎样的I/O事件通知我们。
select和epoll这2个系统调用都可以在内核准备好数据(网络数据到达内核)时告知用户进程
信号驱动I/O
等待数据报到达(第一阶段)期间,进程可以继续执行,不被阻塞。免去了select的阻塞与轮询,当有活跃套接字时,由注册的handler处理。
异步I/O
告诉内核启动某个操作,并让内核在整个操作(包括第二阶段,即将数据从内核拷贝到进程缓冲区中)完成后通知我们。
总结
Redis缓存问题
![img](https://i-blog.csdnimg.cn/blog_migrate/a0124e4778b859f18cc8dc5781b0c86a.png)
缓存击穿
定义:热点数据失效,大量请求打到MySQL层面。
**解决方案1:**互斥锁 + 阻塞
在并发的多个请求中,只有第一个请求线程能拿到锁并执行数据库查询操作,其他的线程拿不到锁就阻塞,等到第一个线程将数据写入缓存后,其他请求直接走缓存。
锁:redis的setnx方法
**解决方案2:**分级缓存 + 锁
采用两级缓存(L1、L2),L1缓存失效时间短,L2缓存失效时间长。
请求优先从L1缓存获取数据,如果L1缓存未命中则加锁,
只有一个线程获取到锁,获取到锁的线程从数据库中读取数据,并将数据更新到L1和L2缓存中,在数据更新到L1和L2缓存之前,其他请求从L2缓存中获取数据。
避免缓存同时失效。所以当数据更新时候,智能淘汰L1缓存,不能将 L1 和 L2 同时淘汰。
L2中可能存在脏数据,需要业务能够容忍这种短时间的不一致。并且存在额外的空间浪费
缓存雪崩
**定义:**大量缓存同时失效
解决方案1:随机值设置
设置缓存失效时间时候,加上随机值,让失效时间更加均匀
解决方案2:排队访问
采用队列或者加锁的方式,排队访问数据库
解决方案3:分级缓存
设置二级缓存机制,这样就能够保证即时第一层缓存失效了,还有第二级缓存的保障;不过就增加了系统复杂性。
缓存穿透
数据本身就不存在(Redis、MySQL),一般来说存储层查不到的数据则不会写入缓存,这将导致这个不存在的数据会一直访问MySQL。
解决方案1:不存在的数据缓存 NULL
将数据库中的空值也缓存到缓存层中,这样查询该空值就不会再访问数据库,而是直接在缓存层访问就行。
问题:占用太多缓存!
解决:设置较短的过期时间!
解决方案2:布隆过滤器
当一个查询请求过来时,先经过布隆过滤器进行查,如果判断请求查询值存在,则继续查;如果判断请求查询不存在,直接丢弃。
将所有可能存在的数据通过 K 个 hash函数,然后将K个hash值在 位数组 中对应位置设置为 1(默认为0)。
布隆过滤器 :K 组哈希函数、位数组
当判断 数据X 是否存在时,首先使用 K 个 hash函数 计算哈希,然后分别查看对应的位置是否都为1;若全为1,则表示可能存在该数据,若不全为1,则表示一定不存在该数据。
可能存在:可能刚好另外几个数据的hash值就刚好覆盖了 数据X 的对应位置。
一定不存在:只要有一个Hash对应位置为0,表示没有数据可以映射到对应位置。
优势:
- 使用bit存储,节省空间
- 查询复杂度为 O(K),K为Hash函数的数目
缺陷:
- 假阳性问题,判断存在的不一定存在
- 只能插入和查询元素,不能删除元素
Redis事务
持久化
Redis的数据放置在内存中,一旦服务器断电,进程异常退出等情况发生,Redis中的数据久会丢失!
为了防止以上情况的发生,所以提出了持久化方法!
启动:
- Redis服务器启动之后,会读取持久化文件
- 优先使用 AOF (Why:AOF更新的频率更高,数据更加完整)
- AOF未开启, 使用RDB
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uBmJTrOT-1638362285418)(Redis.assets/webp.webp)]
RDB(Redis DataBase)
功能:保存Redis数据库的快照,用于进行数据恢复,与主从复制
形式:经过压缩的二进制文件(保存在 磁盘 中)
方法:save和bgsave
- 前者会阻塞当前的业务,而去进行saveRDB
- 后者通过fork构建子进程,异步的进行savaRDB
触发方式
**1. 自动触发:**可以进行设置save 方法,让其定期进行持久化。
save 600 1 表示 600 秒以内进行了至少 1 次修改,则进行持久化
save 60 10000 表示 60 秒以内进行了至少 10000 次修改,则进行持久化
2. 手动触发:
主动调用 save,bgsave方法
save
阻塞服务进程,直到RDB持久化完成,期间不能处理任何请求!
bgsave
功能:fork一个子进程,并由子进程将内存中的数据生成快照并且存储到硬盘介质上。
bgsave流程:
- fork子进程(拷贝一个新的自己)
- 子进程将内存中数据写到临时文件中
- 持久化完成将临时文件替换之前的持久化文件
- 销毁子进程,释放资源
// 伪代码
def SAVE():
# 创建RDB文件
rdbSave()
def BGSAVE():
# 创建子进程
pid = fork()
if pid == 0: ## 这个if语句块中,是子进程执行
# 子进程负责创建RDB文件
rdbSave()
# 完成之后向父进程发送信号
signal_parent()
elif pid > 0:
# 父进程继续处理命令请求,并通过轮询等待子进程的信号
handle_request_and_wait_signal()
else:
# 处理出错情况
handle_fork_error()
在此之前需要了解 fork()
功能: fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等)数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程
一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间以及
内存数据
。相当于把原来的进程的所有资源都复制到了新的新进程中。相当于克隆了一个自己。也就是说此时内存使用量是存储之前的2倍
fork返回后,子进程和父进程都从调用fork函数的下一条语句开始运行。
注意:
内存开销:fork的方式需要双倍于Redis的内存,内存开销大。若内存不足,则使用save的方式(阻塞服务进程),直到持久化结束释放内存。
IO频繁
save与bgsave的区别
save:阻塞服务进程。
bgsave:双倍内存消耗。
AOF(Append Only File)
作用:将redis执行过程中的所有写指令以 追加的方式 写到一个文件!
形式:类似于指令的方式存储,可读性强
步骤:
- 命令追加:所有写入的命令会追加到
AOF缓冲区
- 文件同步:根据对应的策略,将
AOF缓冲区
的数据同步到AOF文件
中 - 文件重写:压缩
AOF文件
1. 命令追加
所有
的写入命令会追加到AOF 缓冲区
的中。
大家可能会有疑问了,直接将写命令写到aof文件不就行了嘛,为什么还要写入到一个aof缓冲区呢?缓冲区的作用是什么呢?
- 减少IO次数,提高效率。redis使用单线程响应命令,如果每次写入AOF文件都直接加到硬盘,每次IO都会阻塞主进程,那么性能完全取决于当前的硬盘负载了。
- 为文件同步策略提供基础。写入AOF缓冲区还有另一个好处,redis可以提供多种缓冲区同步硬盘策略也就是文件同步的同步策略,在性能和安全性方面作出权衡
2. 文件同步
AOF缓冲区 同步到 AOF文件
redis.conf文件中对应的策略一共有三个选择:
- 命令:每执行一个写命令进行一次保存
- 时间:每秒钟保存一次
- 不保存
appendfsync always:每次执行完一个写命令之后,直接进行文件同步,而不需要写入到aof缓冲区内
appendfsync everysec:每秒从aof的缓冲区内执行一次文件同步
appendfsync no:不保存。
3. 文件重写
**目的:**压缩AOF文件
随着aof文件越来越大,需要定期清理aof文件进行重写,达到压缩的目的。
**执行时机:**此过程只有在AOF文件达到重写的标准时才会执行,并不是每次aof持久化时都执行。
默认的重写标准是:AOF文件到达 64 M 时候,进行重写
通过该功能来创建一个新的 AOF 文件来代替旧文件。并且两个文件所保存的数据库状态一样(就是redis的数据是一样的),但新文件不会包含任何冗余命令,所以新文件要比旧文件小得多。
Question:为什么新文件不会包含任何冗余命令呢?
一般可以这样理解:旧文件保存了对某个 key 有 4 个 set 命令,经过重写之后,新文件只会记录最后一次对该 key 的 set 命令!!!
但是,实际上,重写之后并不是key最后一条命令,而是类似rdb存储的二进制文件。重写功能是通过读取服务器当前的数据库状态来实现的(类似rdb的快照)。
这个重写功能和rdb持久化存储很像:
也是fork一个子进程,在子进程内创建一个临时文件,然后读取当前数据库状态后以命令行的形式写入到新的AOF文件中,没有冗余命令。
写入新AOF文件的所有IO操作都是在子进程内操作,不会阻塞主进程。注意RDB写入临时文件的内容和AOF写入临时文件的内容都是一些二进制文件。
当新的AOF文件全部写入成功后,替换旧的AOF文件,销毁子进程。重新完成
所以这个重写过程非常类似rdb的存储过程
**Question:重写过程中可能导致 Redis 与 AOF文件 不一致!!!**那么这里又会存在一个问题,子进程在重写过程中,服务器还在继续处理命令请求,新命令可能会对数据库进行修改,这会导致当前数据库状态和重写后的 AOF 文件,所保存的数据库状态不一致
Redis 设置了一个 AOF 重写缓冲区
。在子进程执行 AOF 重写期间,主进程需要执行以下三个步骤:
- 执行客户端的请求命令
- 将执行后的写命令追加到 AOF 缓冲区
- 将执行后的写命令追加到 AOF 重写缓冲区
当子进程结束重写后,会向主进程发送一个信号,主进程接收到之后会调用信号处理函数执行以下步骤:
- 将 AOF 重写缓冲区内容写入新的 AOF 文件中。此时新文件所保存的数据库状态就和当前数据库状态一致了。
- 对新文件进行改名,原子地覆盖现有 AOF 文件,完成新旧文件的替换。
- 当函数执行完成后,主进程就继续处理客户端命令。
因此,在整个 AOF 重写过程中,只有在执行信号处理函数时才会阻塞主进程,其他时候都不会阻塞。
有时aof文件的大小还没有触发重写机制,但是文件已经很大了,我们想手动触发重写,可以使用下面的命令:
bgrewriteaof
主从复制
哨兵
集群
Redis没有使用一致性Hash,而是引入了哈希槽的概念。
Hash槽
Redis-集群中有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽。
slot = CRC16(key)/16384
Cluster中的每个节点负责一部分 hash槽(hash slot),比如集群中存在三个节点,则可能存在的一种分配如下:
- 节点A 包含0到5500号哈希槽;
- 节点B 包含5501到11000号哈希槽;
- 节点C 包含11001 到 16384号哈希槽。
CRC16校验:检查数据是否OK
16384取模:计算Hash值
集群扩容
-
准备新节点
-
加入集群
-
迁移槽和数据
- 首先需要为新节点制定槽的迁移计划,确定原有节点的哪些槽需要迁移到新节点。迁移计划需要确保每个节点负责相似数量的槽,从而保证各节点的数据均匀。
- 数据传输
如果扩充一个节点D,只需要将A、B、C节点中的部分槽放置在D上;
集群收缩
- 节点下线,迁移槽
- 集群内的节点不停地通过Gossip消息彼此交换节点状态,因此需要通过一种健壮的机制让集群内所有节点忘记下线的节点。
如果想移除节点A,只需要将A的slot转移到B和C节点上。
分布式锁
setnx在集群模式下会有什么问题?
多个客户端拿到同一把锁!
- 客户端A 从 主节点 拿到锁X
- 在主节点将锁X同步到 从节点 之前怠机
- 从节点被选为 新的主节点
- 这时候客户端B 从新的主节点 获取到锁X
解决方案:RedLock
XMIND
淘汰、过期策略
单线程
持久化
事务
缓存问题
分布式锁
主从复制
集群
面试题
redis mysql数据一致性解决方案?
单个请求的情况一般不会出现一致性问题,要考虑多个请求的情况!!!
读取缓存步骤一般没有什么问题,但是一旦涉及到数据更新:数据库和缓存更新,就容易出现缓存(Redis)和数据库(MySQL)间的数据一致性问题。
- 设置过期时间
- 先更新数据库,再更新缓存**(不考虑)**
- 先删除缓存,再更新数据库**(延时双删)**
- 先更新数据库,再删除缓存
先更新数据库,再更新缓存: (不考虑)
- (1)线程A更新了数据库
- (2)线程B更新了数据库
- (3)线程B更新了缓存
- (4)线程A更新了缓存
缺陷:
- 理想的顺序应该是1423,但是因为网络等原因,B却比A更早更新了缓存。这就导致了脏数据;
- 写操作较多的情况下,数据还没读,缓存就会频繁的更新
难 以 处 理 !!!!
先删缓存,再更新数据库:
- (1) A 进行写操作,删除了缓存
- (2) B 来读数据,发现缓存不存在, B请求数据库,查到 旧值 并写入缓存
- (3) A 将新值写入数据库
缺陷:
这时候缓存和数据库不一致,并且不采用给缓存设置过期时间策略,该数据永远都是脏数据。
解决方案 1 : 延时双删策略
- 先删除缓存
- 写数据库
- 休眠 y 秒,再次删除缓存
public void write(String key,Object data){
redis.delKey(key);
db.updateData(data);
Thread.sleep(1000);
redis.delKey(key);
}
y 秒:这个时间如何确定?
根据业务中读数据的耗时确定,比如:读请求耗时 x,我们可设置睡眠时间 y = x+几百ms;确保读请求结束,写操作可以删除读请求产生的脏数据
方案一,第二次删除缓存,如果删除失败怎么办
提供一个用于保障的重试机制;
流程如下所示:
- (1)更新数据库数据
- (2)数据库会将操作信息写入binlog日志当中
- (3)订阅程序提取出所需要的数据以及key
- (4)另起一段非业务代码,获得该信息
- (5)尝试删除缓存操作,发现删除失败
- (6)将这些信息发送至消息队列
- (7)重新从消息队列中获得该数据,重试操作。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-En0eiOdL-1638362285425)(Redis.assets/image-20210811122936550.png)]
上述的订阅binlog程序在mysql中有现成的中间件叫canal,可以完成订阅binlog日志的功能。
解决方案 2:异步更新缓存(基于订阅binlog的同步机制)
上述的订阅binlog程序在mysql中有现成的中间件叫canal,可以完成订阅binlog日志的功能。
Redis怎么实现高可用性?
Redis主从复制机制,简单地实现了Redis高可用。然后,如果Master服务器宕机,会导致整个Redis瘫痪,这种方式的高可用性较低。正常会采用多台Redis服务器构成一个集群,即使某台,或者某几台Redis宕机,Redis集群仍能正常运行,从而提高其高可用性。