线程IO模型
- redis是单线程,但它是怎么做到高并发的呢。这就与非阻塞IO和多路复用有关了。
- IO模型的发展史为
阻塞IO]-->非阻塞IO-->非阻塞IO结合select-->非阻塞IO结合epoll
- 阻塞IO,顾名思义就是一个读写操作在没有指定读的字节数前就是阻断性的,要达到高并发高性能这是不可取的。
- 非阻塞IO,并不是异步IO,这是指一个读写操作并不会有读写字节的限制,能读多少读多少,不会一直等待直到字节数读满,这就能达到读写的瞬间完成。
- 非阻塞IO有个问题,就是某个连接只读取了一部分数据,在剩下的数据到来时怎么触发这个连接的读事件呢。这时候就可以用系统函数select来达到事件轮询的效果。select实际上是去遍历linux系统的读写描述符列表来监控每一个读写需不需要进行操作。相当于select维护了一个所有socket连接的集合,遍历这个集合拿到连接然后去触发读看下有没有数据要读取,没有就下一个连接。
- select在连接数量大了后会出现大量的无效遍历,性能并不高效。epoll在select的基础上加上了一个就绪队列,里面存储接收到数据的socket,这样就能准确的遍历有数据的socket提高事件监听的效率。
持久化
- Redis 的数据全部在内存里,如果突然宕机,数据就会全部丢失,因此必须有一种机制
来保证 Redis 的数据不会因为故障而丢失,这种机制就是 Redis 的持久化机制。
- Redis 的持久化机制有两种,第一种是快照,第二种是 AOF 日志。快照是一次全量备份,AOF 日志是连续的增量备份。快照是内存数据的二进制序列化形式,在存储上非常紧凑,而 AOF 日志记录的是内存数据修改的指令记录文本。
- redis快照持久化是不影响客户端继续对数据的修改操作的,这是怎么做到的呢。这是使用操作系统COW(copy on write)写时复制机制来实现的。redis持久化时会fork一个新的进程来负责数据的序列化到磁盘的操作,当fork一个新的进程时那个时间点的数据内存页的数据就会凝固不变了,这时如果客户操作了新的redis数据,会复制一个对应的内存页来保存修改的数据,这就达到了不影响客户操作redis的持久化。
- aof持久化是将每一个操作进行保存下来。保存的间隔配置成操作触发式、每秒保存一次和永远不保存。增量式的持久化数据安全性高(数据缺失少),但数据恢复慢(要重新跑一遍之前的所有操作)。
- 为了解决aof的问题,出现了混合持久化,就是当aof的文件大小达到配置的大小时会对该文件进行序列化,然后接着以aof的形式进行持久化数据,这样就有效的解决了数据恢复慢的问题。
管道
- redis管道是一种能加速redis存取效率的技术,redis管道并不是redis服务器提供的技术,这个技术本质上是由客户端提供的,跟服务器没有什么直接的关系。
- 通常我们进行redis操作时是一个请求一个响应交替的进行的,这样每一个操作都要经历一个网络数据包的来回时间
sequenceDiagram
客户端->>redis服务: request
redis服务->>客户端: response
客户端->>redis服务: request
redis服务->>客户端: response
- 而redis管道就是把这些指令的顺序进行了调整,统一先进行请求再统一进行响应,这样就达到了所有指令大致只经历一次网络来回,降低网络上的消耗
sequenceDiagram
客户端->>redis服务: request
客户端->>redis服务: request
redis服务->>客户端: response
redis服务->>客户端: response
事务
- 为了确保连续多个操作的原子性,一个成熟的数据库通常都会有事务支持,Redis 也不例外。Redis 的事务使用非常简单,不同于关系数据库,我们无须理解那么多复杂的事务模型,就可以直接使用。
- redis的事务由multi/exec/discard三个指令来实现。multi(开启事务)、exec(执行事务中的指令)、discard(撤销前面的指令)。
> multi
OK
> incr books
QUEUED
> incr books
QUEUED
> exec
(integer) 1
(integer) 2
- redis事务不支持原子性,仅仅支持隔离性。redis事务中的一系列指令如果其中有一条报错并不会进行回滚,而是跳过执行下一条指令。
- 为什么redis不支持事务回滚?因为redis命令只会因为错误的语法而报错,也就是程序性错误,这种错误应该在编程方面解决而不是在生产环境解决;再一个就是redis要秉持简单快速的原则。
小对象压缩-ziplist
- Redis 是一个非常耗费内存的数据库,它所有的数据都放在内存里。如果我们不注意节
约使用内存,Redis 就会因为我们的无节制使用出现内存不足而崩溃。
- 如果 Redis 内部管理的集合数据结构很小,它会使用紧凑存储形式压缩存储。这就好比HashMap在数据量比较少时查询效率是不如一维的数组的。
- Redis 的 ziplist 是一个紧凑的字节数组结构,如果它存储的是 hash 结构,那么 key 和 value 会作为两个 entry 相邻存在一起,如果它存储的是 zset,那么 value 和 score 会作为两个 entry 相邻存在一起。
graph LR
A[数据头]-->B[entry]
B[entry]-->F[entry]
F[entry]-->C[--]
C[--]-->D[entry]
D[entry]-->E[数据尾]
hash-max-zipmap-entries 512
hash-max-zipmap-value 64
list-max-ziplist-entries 512
list-max-ziplist-value 64
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
set-max-intset-entries 512
- redis的内存回收机制不是删除key之后立即进行回收,内存数据都是保存在4K的内存页上,只有当这个内存页的数据全部删除后才会进行内存回收。虽然redis无法保证立即回收已经删除key的内存,但redis会重用内存页上空闲的内存。
主从同步
- 分布式存储的理论基石是CAP(一致性,可用性,分区容忍性)。当网络分区发生时,两个分布式节点间无法进行通信,要保证两个节点数据的一致性的话那么两个节点就要都停止服务,这样就怕坏了服务的可用性;要保证数据的可用性的话,就要保证其中一个节点正常服务,另一个停止服务,这样两个节点的数据就会出现大量的不一致。一句话概括 CAP 原理就是——网络分区发生时,一致性和可用性两难全。
- redis的主从同步是异步进行的,这就说明redis的主从同步不符合一致性。redis分布式系统满足的是最终一致性,当网络不通时,主从节点数据会有大量不一致,但网络恢复后,从节点会根据各种策略跟上主节点的数据,达到数据的最终一致性。
- 增量同步:数据差异不多时,redis会将多出来的数据缓存在一个环形数组中,从节点同步环形数组中的数据就行。
- 快照同步:数据差异比较大,把环形数组都存满了时,redis从节点会选择以快照的形式将主节点的所有数据都同步一遍