Redis是单线程吗?
Redis 的单线程主要是指Redis的网络IO和键值对读写是由一个线程来完成的,这也是Redis对外提供键值存储服务的主要流程。但Redis 的其他功能,比如持久化、异步删除、集群数据同步等,其实是由额外的线程执行的。
Redis 单线程如何处理那么多的并发客户端连接?
Redis的IO多路复用:redis利用epoll来实现IO多路复用,将连接信息和事件放到队列中,依次放到文件事件分派器,事件分派器将事件分发给事件处理器。
redis持久化(RDB快照、AOF)
RDB持久化策略共两种save、bgsave
命令 | save | bgsave |
---|---|---|
IO类型 | 同步 | 异步 |
是否阻塞redis其他命令 | 是 | 否(在生成子进程执行调用fork函数时会有短暂阻塞) |
复杂度 | O(n) | O(n) |
优点 | 不会消耗额外内存 | 不阻塞客户端命令 |
缺点 | 阻塞客户端命令 | 需要fork子进程,消耗内存 |
AOF(append-only file)
快照功能并不是非常耐久(durable):如果Redis因为某些原因而造成故障停机,那么服务器将丢失最近写入、且仍未保存到快照中的那些数据。
可以通过配置文件打开AOF
# appendonly yes
从现在开始,每当Redis执行一个改变数据集的命令时(比如SET),这个命令就会被追加到AOF文件的末尾。这样的话,当Redis重新启动时,程序就可以通过重新执行AOF文件中的命令来达到重建数据集的目的。
也可以配置多久执行
ppendfsync always:每次有新命令追加到AOF 文件时就执行一次fsync,非常慢,也非常安全。
appendfsync everysec:每秒 fsync 一次,足够快,并且在故障时只会丢失1秒钟的数据。
appendfsync no:从不fsync,将数据交给操作系统来处理。更快,也更不安全的选择。
其中,everysec为默认策略,可以兼顾速度和安全性
RDB 和 AOF怎么选
命令 | RDB | AOF |
---|---|---|
启动优先级 | 低 | 高 |
体积 | 小 | 大 |
恢复速度 | 快 | 慢 |
数据安全性 | 容易丢数据 | 根据决策决定 |
Redis数据备份策略:
1.写crontab定时调度脚本,每小时都copy一份rdb或aof的备份到一个目录中去,仅仅保留最近48小时的备份
2.每天都保留一份当日的数据备份到一个目录中去,可以保留最近1个月的备份
3.每次copy备份的时候,都把太旧的备份给删了
4.每天晚上将当前机器上的备份复制一份到其他机器上,以防机器损坏
主从复制
主从工作原理
【首次】当从节点连接后
- 【从】发送psync命令(同步数据),发送命令之前会跟master建立socket长连接
- 【主】接受psync命令后,bgsave生成最新rdb快照数据(仅生成截至到那一刻的快照数据,之后再生成会放到主节点内部的一个缓存
repl buffer
当中去,就是一些写命令) - 【主】发送rdb快照数据到从节点
- 【从】清空老数据并加载主节点发送的rdb快照数据
- 【主】与此同时,将之前缓存
repl buffer
中的命令再发送到从节点当中去 - 【从】执行刚接受的buffer数据
- 【主】同步之后,通过socket把命令发给从节点,保证主从数据一致性
【非首次】从节点因某种原因断开连接,再次恢复后
- 【主】会保存一些最近写命令到缓存区
repl backlog buffer
中(大概1mb左右),目的用于恢复 - 【从】重新建立到master建立socket
- 【从】发送psync命令并携带offset偏移量
- 【主】根据从节点发送的offset到缓存区
repl backlog buffer
中查找,如果查找到会将offset之后的数据一次性同步到从节点去,否则全量同步 - 【主】通过socket命令发给从节点,保证主从数据一致性
如果有很多从节点,万一网络波动会导致大批从节点down掉,这时会有多个节点从主节点进行复制(会对主节点产生很大的压力)会产生主从复制风暴
解决方案:将部分从节点与从节点同步数据
批量执行:管道(Pipeline)、Lua脚本(推荐)
Redis在2.6推出了脚本功能,允许开发者使用Lua语言编写脚本传到Redis中执行。使用脚本的好处如下:
1、减少网络开销:本来5次网络请求的操作,可以用一个请求完成,原先5次请求的逻辑放在redis服务器上完成。使用脚本,减少了网络往返时延。这点跟管道类似。
2、原子操作:Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。管道不是原子的,不过redis的批量操作命令(类似mset)是原子的。
3、替代redis的事务功能:redis自带的事务功能很鸡助,报错不支持回滚,而redis的lua脚本几乎实现了常规的事务功能,支持报错回滚操作,官方推荐如果要使用redis的事务功能可以用redis lua替代。
哨兵架构
哨兵架构是特殊的redis服务,不提供读写服务,主要用来监控redis实例节点
工作流程:
- 哨兵集群监控所有redis节点,时刻感知redis各节点的变化
- 客户端第一次连接首先连入哨兵集群,哨兵通知客户端当前master节点
- 客户端再次连接,直接连接到master节点
- 如果这期间master节点down了,重新进行选举产生新的master节点
- 哨兵监控到变化后第一时间通知客户端
- 客户端更换连接
Redis Cluster高可用集群模式
这里的Redis 集群是由多个主从节点组成的分布式服务器群,具有复制、高可用和分片特性。Redis 集群不需要哨兵也能完成节点移除和故障转移的功能。将每个解答设置成集群模式,这种集群模式没有中心节点,可水平扩展,官方文档称可以线性扩展到上万个节点(官方推荐不超过1000个节点)【默认,某一个小集群down了,整个集群都会瘫痪,可以配置修改】
redis有16384个哈希槽(slot),会均分给redis集群中的分布式服务器群
核心数据结构
String
- 内部实现(int 和 SDS)
优点:- SDS 不仅可以保存文本数据,还可以保存二进制数据
- SDS 获取字符串长度的时间复杂度是 O(1)
- Redis 的 SDS API 是安全的,拼接字符串不会造成缓冲区溢出
- 内部编码(int、embstr和raw)
int
如果一个字符串对象保存的是整数值,并且这个整数值可以用long类型来表示,那么字符串对象会将整数值保存在字符串对象结构的ptr属性里面(将void*转换成 long),并将字符串对象的编码设置为int
embstr
如果字符串对象保存的是一个字符串,并且这个字符申的长度小于等于 32 字节,字符串对象将使用一个SDS来保存这个字符串,并将对象的编码设置为embstr, embstr编码是专门用于保存短字符串的一种优化编码方式
raw
如果字符串对象保存的是一个字符串,并且这个字符串的长度大于 32 字节,字符串对象将使用一个SDS来保存这个字符串,并将对象的编码设置为raw
embstr 编码和 raw 编码的边界在 redis 不同版本中是不一样的
- redis 2.+ 是 32 字节
- redis 3.0-4.0 是 39 字节
- redis 5.0 是 44 字节
另外,embstr和raw编码都会使用SDS来保存值,区别在于embstr会通过一次内存分配函数来分配一块连续的内存空间来保存redisObject和SDS,而raw编码会通过调用两次内存分配函数来分别分配两块空间来保存redisObject和SDS。
redis之所以使用embstr编码处理小字符有以下优点:
- embstr编码将创建字符串对象所需的内存分配次数从 raw 编码的两次降低为一次
- 释放 embstr编码的字符串对象同样只需要调用一次内存释放函数
- 因为embstr编码的字符串对象的所有数据都保存在一块连续的内存里面可以更好的利用 CPU 缓存提升性能
embstr编码同时也存在一下缺点
- 如果字符串的长度增加需要重新分配内存时,整个redisObject和sds都需要重新分配空间,所以embstr编码的字符串对象实际上是只读的,redis没有为embstr编码的字符串对象编写任何相应的修改程序。当我们对embstr编码的字符串对象执行任何修改命令(例如append)时,程序会先将对象的编码从embstr转换成raw,然后再执行修改命令
- 应用场景
缓存对象、常规计数、分布式锁、 共享 Session 信息
hash
list
- 内部实现(双向链表或压缩列表)