Redis原理篇 ---- 《Redis深度历险》读书笔记

1 单线程

Redis4.0之前是单线程的,Redis4.0修改了原来的单线程模型,变成了多线程。但是这个多线程模型仅仅是新增了几个处理后台任务的异步线程。提供服务执行指令的依然是单线程,被称为主线程。所以还称得上是单线程服务。因此依然可以当做单线程去看。
Node.js和Nginx都是单线程的。
Redis虽然是单线程,但是Redis的运算都是内存级别的,且单线程避免了线程切换的消耗,因此能够保持较快的速度。
又是因为单线程,所以对于一些耗时的命令,特别是一些时间复杂度是O(n)的命令,需要谨慎使用,否则可能会因为这个命令时间时间较长,阻塞后续的命令,导致卡顿甚至后续命令超时异常。

1.1 多路复用IO

Redis是单线程,那么为什么能处理多个客户端的并发连接呢?这是因为Redis的IO模型为多路复用IO。多路复用IO用一个线程不断的去轮询socket,只有socket有实际的读写事件时,才真正调用实际的IO操作。而且多路复用IO与非阻塞IO不同的是多路复用IO轮询socket是在内核中进行的,而不是用户线程进行轮询的。因此多路复用IO较于非阻塞IO有更低的资源占用率,更高的效率。但是因为单线程的轮询,如果一个事件响应时间过长,会导致后续的事件迟迟得不到响应,影响后续的轮询。JAVA中的NIO就是采用的多路复用IO模型。另外提一下,现在的操作系统多路复用的API用epoll代替了传统的select,带来了更大性能提升。

1.2 指令队列与响应队列

Redis为每个客户端都关联了一个指令队列,客户端的指令通过队列来排队进行处理,先到先服务。
对于响应,Redis同样为每个客户端都关联了一个响应队列,通过响应队列将响应回复到客户端。如果响应队列为空,说明连接处于空闲状态,就是Redis现在不需要回复数据,此时会把这个客户端的描述符从write_fds拿出来,这样多路复用IO轮询时间就不会轮询这个客户端了,等到响应队列有数据的时间,就把描述符放回去,重新轮询。这样避免Redis数据还没准备好时间轮询到写事件,结果读不到数据,拉高CPU。
指令队列与响应队列这一部分的内容没有在百度上搜到相关内容进行交叉验证,所以不保证理解正确,如果读者了解这个,欢迎评论区教我,感谢。

1.3 定时任务

除了响应指令,Redis还有其他的事情要做,比如定时任务。而Redis又是单线程,而在没有指令时多路复用IO的轮询会阻塞线程节省资源,指令到的时间才会被唤醒,但是定时任务到点时没有指令过来岂不是定时任务就无法准时执行了?Redis的解决方案是这样的:首先多路复用IO的轮询系统有一个参数timeout,就是空闲状态下轮询线程阻塞超过这个时间,就会终止阻塞自动唤醒。然后定时任务记录在一个称为最小堆的数据结构中,这个堆中,离执行时间越短,越在上面。Redis每次轮询时都会将堆中到点的定时任务执行掉,如果没有事件,线程要阻塞时就会计算下一个定时任务还有多久要执行,然后设置这个时间为timeout,这样就保证了到点时即使没有事件唤醒线程处理,也会被timeout唤醒。
Nginx和Node的事件处理原理和Redis是类似的。
Redis4.0新增了后台线程,可以配置相应处理的异步处理开关,如果打开,那此时主线程的任务仅仅是生成异步任务放入异步队列,而不参与处理,避免影响服务。

2 通信协议

Redis的开发者认为数据库系统的瓶颈不在网络流量,而在自身的处理逻辑上。所以Redis即使使用了浪费流量的文本协议,依然有很高的性能。Redis单线程+纯内存在跑满一个CPU的情况下QPS可达10W/S。

2.1 RESP(Redis Serialization Protocol)

RESP是Redis序列化协议的缩写,它是一种直观的文本协议,优势在于实现异常简单,解析性能极好。
Redis协议将传输的结构数据分为5种最小单元类型,单元结束时统一加上回车换行符\r\n。

  1. 单行字符串以 + 符号开头
  2. 多行字符串以 $ 符号开头,后跟字符串长度
  3. 整数值以 : 符号开头,后跟整数的字符串形式
  4. 错误消息以 - 符号开头
  5. 数组以 * 符号开头,后跟数组的长度

示例:
单行字符串hello world

+ hello world\r\n

多行字符串hello world,单行字符串也可以用多行形式表示

$11\r\nhello world\r\n

整数1024

:1024\r\n

错误:参数类型错误

-WRONGTYPE Operation against a key holding the wrong kind of value

数组[1,2,3]

*3\r\n:1\r\n:2\r\n:3\r\n

NULL 用多行字符串表示,不过长度要写成-1

$-1\r\n

空串用多行字符串表示,长度为0:注意是两个\r\n,不是一个

$0\r\n\r\n

2.2 客户端到服务端

客户端到服务端都是指令,指令只有一种形式,就是多行字符串数组,比如一个指令 set key val 序列化之后就是:

*3\r\n$3\r\nset\r\n$3\r\nkey\r\n$3\r\nval\r\n

看着很复杂,但是放到控制台展示就很清晰了:

*3
$3
set
$3
key
$3
val

后面黑背景的都默认是控制台的显示模式了。

2.3 服务端到客户端

服务端到客户端就比较复杂了,不过再复杂也是上面5种基本类型的组合。

ip> set key val
OK

这个OK没有被引号括起来,就是单行字符串响应

+OK

错误响应:

ip> incr key
(error) ERR value is not an integer or out of range

这个错误响应时这样的:

-ERR value is not an integer or out of range 

整数响应:

ip> incr key
(integer) 1

这个1就是整数响应:

:1

多行字符串响应:

ip> get key
"val"

这个val被引号括起来,就是多行字符串响应:

$8
val

数组响应:

ip> hgetall key
1) "name"
2) "yh"
3) "age"
3) "3"

hgetall返回的是一个文本,客户端会把这个文本解析成字典再返回:

*4
$4
name
$2
yh
$3
age
$1
3

嵌套响应:

ip> scan 0
1) "0"
2) 1) "yh"
   2) "stu"
   3) "bk"

整体是一个数组,第一个元素是多行字符串响应,第二个是嵌套的多行字符串数组响应:

*2
$1
0
*3
$2
yh
$3
stu
$2
bk

Redis的序列化协议传输过程中虽然有大量的\r\n回车换行符,但是并不影响它成为互联网技术领域非常受欢迎的一个文本协议,有很多项目都采用了RESP协议,技术领域性能并不是一切,还有简单性,易理解和易使用性,需要根据实际场景进行评估。

3 持久化

Redis的高效在于其纯内存运算,但是有得就有失,数据全部存在内存中意味着一旦宕机,数据将会全部丢失,因此必须需要一种机制来保证Redis中的数据不会因为故障而丢失,这就需要Redis拥有数据持久化的能力。
Redis的持久化机制有两种,一种是快照,也就是RDB(Redis DataBase),一种是AOF(Append Only File)日志。

3.1 快照(RDB)

快照是一次性的全量备份,将某一时刻的全量数据以二进制序列化的形式存储,在空间上非常紧凑,能大大缩小存储所用的空间。
Redis是单线程,而文件IO操作是不支持多路复用的。这难道意味着在进行内存快照时Redis需要停止服务?这当然是不行的,那有指令时服务,没指令时持久化这样边持久化边服务?可是这样的话持久化的同时内存数据还在被指令修改,如果在持有化一个大的Hash字典时,过来一个指令把这个字段删了,这个可怎么办? 显然这样也不行。
为此,Redis使用操作系统的多进程COW(Copy On Write)机制来实现快照持久化。
Redis在持久化时会调用glibc的函数fork产生一个子进程,快照持久化交给子进程处理,父进程继续提供服务。子进程生成时和父进程共用代码段和数据段。也就是说这时间父子进程共享内存数据,因此在分离的一瞬间,内存消耗几乎没有。接下来子内存进行数据持久化,他仅仅是读取,不会修改内存。而父进程对外提供服务,修改数据,但是操作系统的COW机制会进行数据段页面的分离,数据段由操作系统的页面组合而成,父进程修改数据时,COW机制就将数据所在页复制一份出来,父进程在这个复制出来的数据也修改,此时原数据页也就是子线程访问的数据页还是原样,也就是子进程所看到的数据在子进程产生的一瞬间就已经凝固了,可以安心复制,这也是为什么这种持久化方法称为快照原因。
随着父进程的修改,会有越来越多的页面被赋值,但是最多也就是全复制,达到原内存空间的二倍,但是这在大数据量情况下很难发生,因为总会有冷数据存在,而且可能占据多数,所以复制的一般只会是其中的一部分。另外提一下:一个页面的大小是4K。

3.2 AOF日志

AOF日志是连续性的增量备份,记录的是修改内存数据的指令记录文本。这样就可以通过对一个空的Redis实例顺序执行记录的命令,也就是重放,来复原实例。Redis在收到修改指令后,会先进行校验,如果没问题,会首先把指令追加记录磁盘上的AOF日志中,然后再执行指令,这样即使突发宕机,重放时也能重放到这个指令。
AOF日志随着运行时间的增长会变的越来越庞大,Redis重启时需要加载AOF日志进行指令重放所需的时间也会更加漫长,所以需要定期对AOF重写,进行瘦身。

3.2.1 AOF重写

AOF重写原理就是开辟一个子进程,然后将内存数据遍历并转换成指令,再记录到一个新的AOF文件中,完毕后再将期间发生的增量AOF日志追加到新的AOF日志中,替换旧的AOF文件,就完成了AOF重写的工作,完成了瘦身。

3.2.2 fsync

AOF日志是以文件方式存在的,程序对AOF日志进行操作时,实际上是先将内容写到内核为文件描述符分配的一块内存缓存上,然后内核异步将数据写入磁盘的。
但是如果机器突然宕机,内存缓存中的数据还没来的及写入磁盘,就会出现日志的丢失。Linux的gilbc提了了fsync(int fd)函数来强制把指定文件的内存缓存数据写入到磁盘中,实时使用fsync就能保证AOF日志不丢失。但是fsync涉及到磁盘写入,相较于内存操作会慢很多,如果每一个指令都fsync一次,Redis纯内存操作所带来的优势就不存在了。
因此目前主流的做法是Redis每隔1s执行一个fsync,1s是可配置的,可以根据需要配置。这样就在保持高效能的同时尽可能的减少日志丢失。Redis也提供了另外两种策略:一种是永不fsync,由操作系统决定什么时间将内存缓存同步到磁盘,这样无法掌控,很不安全。另一种是一次指令fsync一次,然后不会丢日志,单缺点上面也说过了,生产并不推荐。
落盘策略由appendfssync选项控制:

  • always:服务端每接收一个更新指令都刷到磁盘,不会发生数据丢失,但是io太过频繁效率很低。
  • everysecond:每隔1秒钟刷一次盘,有可能丢失一秒钟的数据,效率适中。
  • no:服务端不主动刷盘,数据何时落盘完全取决于操作系统,只有当buffer满了才会刷盘,丢失数据量不确定,效率最高。

Redis4.0新增了异步模型,可以打开fsync的异步处理开关,此时主线程不进行fsync,而是生成任务放到专门的fsync队列中去,由专门的fsync异步线程处理。

3.3 持久化选在从节点

无论是快照还是AOF,都比较消耗资源。快照需要遍历整个内存,大块磁盘读写加重系统负载。AOF的fsync是一个耗时的IO操作,也会影响Redis性能,加重系统IO负担。因此Redis的持久化一般并不安排在主节点,而是在从节点进行,从节点没有客户端请求的压力,资源比较充足。但是如果出现网络分区,从节点连不上主节点,而主节点又宕机了,就会出现数据丢失产生数据一致性的问题。因此生产环境需要做好网络连通性检测,保证出现问题时能快速修复,除此之外可以再挂一个从节点,这样只要有一个从节点数据同步正常,数据就不会丢失。

3.4 Redis4.0的混合持久化

Redis重启时,很少使用RDB来恢复数据,因为会丢失最后一次快照之后的数据。但是使用AOF日志重放,效率上又会慢很多。因此Redis4.0提供了混合持久化的策略,就是RDB和AOF同时使用。RDB正常持久化,而AOF不在记录全量指令,而是记录每次RDB快照之后的增量AOF,这样Redis重启时就可以先加载RDB的内容,然后再重放AOF日志,效率大大提升。

3.5 为什么不直接使用Redis做数据库

  • 内存重启需要重放,这个需要时间,而数据库重启可以直接使用,但是这个算不上什么问题,毕竟有了混合持久化后重放效率提升很大,而且都重启了,也不差那一点时间。
  • 持久化一般在从节点,主从同步需要时间,所以还是会导致数据丢失,虽然很少。而即使是主节点做持久化,数据持久化使用AOF模式,也要fsync always才能实现实时持久,但是这会使得redis纯内存操作的优势荡然无存,而不使用fsync always则必然要承担无法实时持久化带来的数据丢失可能性。
  • Redis的数据结构比较简单,无法存储复杂的关系结构(比如外键),或者实现起来较为麻烦。
  • 查询机制较为简单,无法数据库那样完善的查询引擎,不能高效的完成复杂查询。
  • 事务上没有数据库强大。虽然有WATCH,但是也仅仅算是一个锁定作用。MULTI/EXEC也并不是严格意义上的事务,因为其没有原子性,且并没有数据库那样多样的隔离性,关于事务下面会有一章单独写。
  • 内存容量有限,存储的数据也有限。而且内存贵!1G的内存和1G的磁盘价格根本没有可比性。冷数据放在内存里可太浪费了。

4 管道

管道的原理很简单,就是把多次请求合并为一次发送。但是有一点需要注意,管道(Pipeline)本身并不是Redis服务器直接提供的技术,而是由客户端提供的,和服务器没有关系。一般各个Redis的工具Jar包中都提供了管道了相关方法。
下图是执行一次指令所产生的交互,客户端发请求过去,Redis返回响应:
在这里插入图片描述
那两条指令就是这样的了
在这里插入图片描述
那对于客户端来说经历的是:
在这里插入图片描述
但是如果我们改成这样结果也不会受到影响,而且服务端也并没有区别对待,同样是接到一条指令,然后执行完将结果发送回去:
在这里插入图片描述
那改成这样会有好处吗?来看一下请求交互的本质:
在这里插入图片描述
客户端和服务器都是将数据写入sendBuffer,然后从receiveBuffer里面读数据。但是注意,客户端写入sendBuferr(也就是write操作)之后并不停在那里等着receiveBuffer(也就是read操作)的数据,而是写入之后就立刻返回做自己的事情了,剩下的事情就由操作系统处理了,read操作不是从目标机器拉取数据,而是等待数据有数据到达receiveBuffer后直接读取。write的操作很快,耗费不了多少时间,因此客户端执行一条指令的主要耗时就在read等待数据的这段时间了,也就是write之后,等待数据发送到服务端的receiveBuffer,然后服务端read之后写到服务端的sendBuffer,再发送到客户端的receiveBuffer回来这段时间。
这时间再看上面两条指令的步骤,就变成了这样的:
在这里插入图片描述
和这样的:
在这里插入图片描述
管道的本质就是这样,他并不是服务端的什么特性,而是通过改变读写顺序,将多个指令等待响应的时间整合成一个,以此带来巨大的性能提升。 指令越多,带来的效果越明显,当然并不是没有限制的,毕竟服务端本身的处理能力也是有瓶颈的。

5 事务

为了确保原子性,成熟的数据库都有事务机制支持,事务拥有ACID的特性。Redis也有类似的事务机制,但是Redis事务机制相当有限,严格意义上来说甚至称不上事务。

5.1 multi/exec/discard

数据库事务上的begin/commit/rollback对应redis事务中的multi/exec/discard。multi表示事务开始,exec表示执行事务,discard表示丢弃事务。
指令上的使用:

>multi
OK
>incr name
QUEUED
>incr name
QUEUED
>exec
(integer) 1
(integer) 2

这就是一个事务的使用过程,纳入事务的指令在exec之前不执行,而是缓存在服务器上的一个事件队列中,QUEUED标识放入成功,服务器收到exec指令后,执行整个事件队列,然后一次性返回结果,因为Redis的单线程特性,所以不会有打扰,看似是保证了原子性。。为什么是看似呢?接下来再看:

>multi
OK
>set name zhangsan
QUEUED
>incr name
QUEUED
>set name lisi
QUEUED
>exec
1) OK
2)(error) ERR value is not an integer or out of range
3)OK
>get name
"lisi"

因为现将name设置成字符串,不能自增,很明显incr name这个指令出错了,但是后续的set name lisi却依然执行了。也就是说事务中的某一条指令出错,后续的指令依然会继续执行,而且成功了,这也是为什么说Redis事务只是看似保证了原子性。Redis的事务仅仅是保证这些指令执行过程中不会被打断而已。
Redis事务可以通过discard丢弃:

>multi
OK
>incr name
QUEUED
>incr name
QUEUED
>discard
OK

这样,这个事务的指令就不是执行而是被丢弃了,事务也结束了。
结合上一部分的管道知识,是不是发现管道和事务很配,因此使用事务时一般执行事务时都会搭配管道一起使用,将多次IO的等待时间整合成一次,提高性能。Python的Redis客户端执行事务更是强制要求使用管道。

5.2 watch

有这样一个场景,需要先获得redis内的值,然后根据这个值作出计算再重新赋值进去。如果是加算,比如说存款新增100,那可以用incrby完成,但是如果是乘算呢?Redis可没有multiplyby这样的指令,此时就需要先取,再算,再赋值几个步骤。此时就会有并发问题,显然multi/exec解决不了这个场景,毕竟multi/exec只保证指令集一起执行,但是不保证你塞指令时其它指令不会操作。
或许可以使用加锁的方案,这确实是一种悲观锁的解决方案,那有没有乐观锁的解决方案呢?此时就需要watch出马了。
watch会在事务开始前盯住一个或多个变量,当事务执行时,也就是服务器收到exec指令时,Redis会先检查watch关注的变量在watch之后是否被修改过,如果被修改过,那么exec指令就不会执行指令集,而且返回null告知调用者:

>watch name
OK
>incr name
OK
>multi
OK
>incr name
QUEUED
>exec
(nil)

此时调用者就可以根据此返回决定采用何种方式处理(重试还是抛异常)。而在客户端方面,返回可以有所不同,redis-py是抛出WatchError的错误,Jedis返回的是null。
但是有一点要注意,watch要在multi之前执行。Redis禁止watch在multi和exec之间执行,这种场景会报错。

5.3 为什么Redis事务不支持回滚呢

Redis不支持回滚是出于导致事务失败的原因及保证性能考虑的,Redis指令失败仅会是错误的语法导致的,这些都是编程时可以避免的,对于错误的语法即使回滚也没有任何用处,这种错误应该在上线之前被发现。且不支持这种没有必要的回滚机制还能保持Redis内部的简单和快捷。

6 PubSub

可以用list实现消息队列,但是使用list无法实现消息的多播机制,PubSub就是Redis为了实现多播的一个模块。但是十分遗憾的是PubSub如果找不到消费者会直接丢弃消息,比如3个消费者,忽然有一个宕机,那属于这个消费者的消息都会丢掉,即使后面这个消费重启成功,消息也回不来了。而且PubSub没有持久化机制,一旦重启,所有消息全部清空,这些特性使得PubSub几乎没有使用场景。虽然Redis作者后面单独开了一个项目Disque来做消息多播,但是至今没有正式版上线。
而在Redis5.0,Redis新增了Stream数据结构,这个功能带来了持久化消息队列。也许PubSub和Disque慢慢的就会淡化掉,成为历史的尘埃,所以就不去仔细了解了。后面单独了解一下Stream。

7 小对象压缩

Redis是一个非常耗费内存的数据库,因为所有的数据都放在内存里,因此Redis在内存占用上做了很多优化。对于一些小对象上,redis并不会采用标准结构存储,比如hash,元素较少时,用一维数组遍历可能比树查询更快,而且还省去了存储指针的空间。还有list,通过zplist即提高了随机访问效率,也同样省去了存储指针的空间。
具体的就先不深入分析了,记录一些存储边界,以后用到的话可以做个参考:

  • hash-max-zipmap-entries 512 # hash 的元素个数超过 512 就必须用标准结构存储
  • hash-max-zipmap-value 64 # hash 的任意元素的 key/value 的长度超过 64就必须用标准结构存储
  • list-max-ziplist-entries 512 # list 的元素个数超过 512就必须用标准结构存储
  • list-max-ziplist-value 64 # list 的任意元素的长度超过 64就必须用标准结构存储
  • zset-max-ziplist-entries 128 # zset 的元素个数超过 128就必须用标准结构存储
  • zset-max-ziplist-value 64 # zset 的任意元素的长度超过 64就必须用标准结构存储
  • set-max-intset-entries 512 # set 的整数元素个数超过 512就必须用标准结构存储

根据数量不同采取不同存储策略是一个很好的节省资源的手段。比如HashMap也采用了小对象压缩策略,他有一个树化阈值,元素数量小于树化阈值时,进行扩容而不是树化。

8 主从同步

主从同步能够在master挂掉之后,让从节点接管,从而快速恢复。避免因重启所需要的时间较长而影响业务。主从同步是Redis分布式的基石,Redis失去主从复制,高可用也就无法进行了,Redis的集群模式也都依赖于主从复制。如果涉及到Redis的持久化,那主从复制是必不可少的,需要认真对待。但是如果仅仅仅仅是用来做缓存,那主从复制就不是必要的了,挂了就重新加载一遍就行了。

8.1 网络分区与CAP原则

CAP原则是现代分布式系统的理论基石。
分布式系统的节点往往都是分布在不同机器上的,这意味着必然会有网络断开的风险,这个网络断开的场景就是网络分区。
CAP原则:CAP原则又称CAP定理,指的是在一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)最多能实现2个,不能3者兼顾。

  • 一致性(Consistency):所有数据节点在任意时刻数据都是一致的。
  • 可用性(Availability):在任何时间都处于可用状态,总是能对请求进行响应,而不阻塞或报错。
  • 分区容忍性(Partition tolerance):系统中分区间通信出现问题时或部分分区宕机时,活动的分区依然能够独立提供服务。

这三要素最多只能实现两点,不可能三者兼顾。但是对于分布式系统来说P是必须的,因为如果要保证AC,那么只能单机运行,就不属于分布式系统了。 因此分布式系统也就分为了AP和CP系统。
AP系统放弃了一致性,保证可用性,即网络分区发生后即使各个分区数据已经不一致依然提供服务。
CP系统放弃了可用性,保证了一致性,即网络分区发生后停止服务,以此来保证数据的一致性。
提高分区容忍性的方法就是复制更多的节点,但是节点越多一致性就越难保证。

8.2 最终一致

Redis的主从同步是异步的,所以分布式的Redis系统并不满足一致性要求。客户端在Redis主节点修改完成后可以立即获得结果,即使主从网络已经断开,所以Redis是满足可用性的。
Redis会保证最终一致性,也就是说从节点会努力追赶主节点,力图与其保持一致,当网络分区出现并结束后,Redis会采用多种策略追赶主节点,继续努力保持与主节点一致。
Redis即支持主从同步,也支持从从同步。

8.2.1 增量同步

主节点将修改性指令记录在本地的buffer中,然后异步同步给从节点,从节点读取buffer中的指令进行同步,并向主节点反馈同步到的位置(偏移量)。
Redis的同步buffer是一个定长的环形数组,大小是有限的,如果数组满了就会覆盖前面的内容从头开始。如果出现网络分区,那么恢复后的buffer可能已经有指令被覆盖掉了,丛节点无法获得这些指令就会出现数据差异,此需要更复杂的同步机制:快照同步

8.2.2 快照同步

主节点进行一次bgsave(就是做一次RDB),然后将RDB文件传输给从节点,从节点清空内存,按照主节点传过来的RDB文件完成一次全量加载。此过程中,增量同步继续进行,从节点按照RDB重加载之后,就走增量同步路子进行追赶。但是如果快照同步时间过长或者buffer过小,就会导致从节点加载完RDB后发现又有覆盖情况了,继续执行快照同步,成了死循环。 因此需要合理设置buffer的大小避免这种情况发生。
当一个新节点加入集群之后,必须先完成一次快照同步,完成之后才能继续走增量同步。

8.2.3 无盘复制

无盘复制是Redis 2.8.18开始支持的。主要原因是快照同步需要将RDB文件写入磁盘,这是一个很重的IO操作,对系统负载的影响比较大,对主节点的服务效率产生影响。无盘复制就是不再生成RDB文件,而是直接将快照内容发送给从节点,也就是主节点一边遍历内容,一边将序列化的内容发送给从节点,从节点将接收到的内容写到磁盘上,接收完毕后再一次性加载。 总的来说就是将磁盘写入的压力由主节点转移到了从节点上面。

8.2.4 wait指令

Redis是异步同步的,一致性也是保持的最终一致性。wait指令则是尝试完成一次强一致同步。有两个参数,第一个参数是从节点数量,第二个几点是最大等待时间,单位是ms:
wait nodeNum waitTime: 就是完成nodeNum个从节点的强一致同步,完成同步则结束阻塞,否则阻塞到最大等待时间waitTime,如果超过waitTime还没完成就不管了。如果waitTime设置为0,则表示无限等待,此时如果出现网络分区导致无法同步,主节点的就会一直阻塞,失去可用性。

PS:
【JAVA核心知识】系列导航 [持续更新中…]
关联导航:Redis应用篇
关联导航:Redis基础篇
关联导航:Redis数据底层存储原理
关联导航:Redis集群篇
关联导航:Redis的过期删除策略与淘汰策略
欢迎关注…

参考资料:
《Redis深度历险》

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yue_hu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值