1、Redis的5种基础数据结构
Redis的5种基础数据结构:string (字符串)、list (列表 )、hash (字典)、 set (集合)、zset (有序集合)。
Redis所有的数据结构都以唯一的key字符串作为名称, 然后通过这个唯一key值来获取相应的value数据。
1.1 string (字符串)
Redis字符串是可以修改的字符串,在内存中是以字节数组的形式存在,字符串最大长度为512MB。
Redis字符串的结构叫“SDS”(Simple Dynamic String),是一个带长度信息的字节数组。
struct SDS<T> {
T capacity; // 数组容量,表示所分配的数组长度
T len; // 内容的实际长度
byte flags; // 特殊标志位,不用理睬它
byte[] content; // 数组的内容
}
1.2 list (列表)
Redis列表的内存结构:在列表元素较少的时候,所有的元素彼此紧挨着—起存储,分配的是一块连续的内存,这个结构是“压缩列表”(ziplist)。
当列表元素较多的时候,会改成“快速链表”(quicklist)。因为普通的链表需要的附加指针空间太大,会浪费空间,还会加重内存的碎片化,比如某普通链表里存的只是int类型的数据,结构上还需要两个额外的指针prev和next。所以Redis将多个ziplist使用双向指针串起来使用,既满足了快速的插入删除性能, 又不会出现太大的空间冗余。
Redis列表插入和删除操作非常快,时间复杂度为O(1),但是索引定位很慢,时间复杂度为O(n)。
1.3 hash(字典)
Redis字典类似于Java语言里面的 HashMap,它是无序字典,内部存储了很多键值对,结构为 “数组+链表” 。
Redis字典的值只能是字符串。
1.4 set (集合)
Redis集合相当于Java 语言里面的 HasbSet,它内部的键值对是无序的、唯一的。它的内部实现相当于一个特殊的字典,字典中所有的value都是一个值NULL。
1.5 zset (有序列表)
Redis有序列表的内部实现用的是 “ 跳跃列表” ,一方面它是一个 set,保证了内部 value 的唯一性,另一方面它可以给每个 value 赋予一个 score,代表这个 value 的排序权重。
1.6
list、set、hash、zset 这四种数据结构是容器型数据结构,它们共享下面两条通用规则:
- create if not exists:如果容器不存在,那就创建一个,再进行操作。比如rpush操作刚开始是没有列表的,Redis就会自动创建一个,然后再rpush进去新元素。
- drop if no elements:如果容器里的元素没有了,那么立即删除容器,释放内存。
Redis所有的数据结构都可以设置过期时间, 时间到了,Redis会自动删除相应的对象。
2、Redis原理
2.1 高性能
- Redis是基于内存的数据库,所有数据都在内存中,内存的读写速度远高于磁盘。
- Redis是单线程操作,避免了不必要的上下文切换和锁竞争机制,也不存在线程频繁切换导致CPU的消耗,所以Redis的操作速度非常快。Redis6.0引入了多线程,但执行请求命令的仍然是单线程。
- Redis采用了非阻塞I/O和多路复用I/O。非阻塞I/O让Redis线程在处理没有读写操作的请求时,不会一直等待,而是去处理其他请求,避免了阻塞,提高了整体的效率;多路复用I/O是利用操作系统的epoll(linux)、kqueue(FreeBSD和macosx)API,让Redis的单个线程可以同时监听多个请求的I/O事件,然后按照轮询的顺序依次进行处理,使得Redis拥有很高的吞吐量。
备注:非阻塞I/O的核心是线程在请求没有I/O事件时不会阻塞;多路复用I/O的核心是允许同一个线程处理多个I/O事件。
2.2 Redis的持久化机制
2.2.1 快照(RDB,Redis Database Backup)
- 快照持久化是在某个特定的时间点,将内存中的所有数据以二进制序列化的形式写入磁盘。在进行快照时,Redis会创建一个子进程,子进程负责将所有数据(子进程产生时那一瞬间的数据)写入磁盘中的临时文件,完成后再替换原来的快照文件,这样就完成了一次全量的备份。
- 执行快照的时间可以通过配置文件来设置,可以设置为在一定时间间隔内或者数据达到一定的修改次数后执行。
- 优点是备份文件小、数据恢复时速度快;缺点是备份耗时、可能会丢失最后一次备份后的数据。
2.2.2 AOF(Append Only File)
- Redis在收到客户端的写入指令后,先执行写入指令,然后按指令的执行顺序存入磁盘,形成AOF日志。如果Redis突然宕机,写入指令可能还没有来得及存入磁盘,就会出现日志丢失。
- AOF备份策略:同步写,Redis 在每个写入指令执行后,将指令立即同步写入到 AOF 日志文件,安全性高、性能底;每秒同步,默认备份策略,每秒将缓冲区中的写入指令同步到 AOF 日志文件安全性较好、性能较高;操作系统控制,Redis 会将写入指令写入操作系统的缓冲区,由操作系统来决定何时将缓冲区的数据同步到 AOF 日志文件,安全性底、性能高。
- AOF日志在长期的运行过程中会变得无比庞大,数据库重启时需要加载AOF日志进行指令重放,这个时间就会无比漫长。
- 优点是数据安全性高;缺点是文件大、数据恢复时间长。
2.2.3 混合持久化
- 重启Redis时,如果用快照恢复内存状态,会丢失大量数据,如果用AOF日志重放,会非常慢。
- 混合持久化(Redis4.0之后提供)是将快照内容和增量的AOF日志文件存在一起,在Redis重启的时候,可以先加载快照的内容,然后再重放增量的AOF日志,重启效率得到大幅提升。
3、Redis集群
3.1 主从复制
主从复制模式是指将一台Redis服务器作为主节点,一台或多台Redis服务器作为从节点。主节点支持数据的写入和读取等各项操作,而从节点只支持与主节点数据同步和数据读取操作。数据是异步同步,并且数据的复制是单向的,只能由主节点到从节点。
优点:
主节点能自动同步数据到从节点,实现读写分离,提高了整体性能;
主节点、从节点之间的同步是以非阻塞的方式进行的,同步期间,仍能为客户端提供服务。
缺点:
不具备自动容错能力,主节点宕机会导致部分数据未同步到从节点,造成数据不一致;
不具备自动恢复能力,主节点或从节点宕机都需要人工手动介入恢复;
难以支持在线扩容,Redis的容量受限于单机配置;
其中,主从数据同步分为了两个阶段,一个是快照同步(即全量同步),一个是增量同步。
3.1.1 快照同步
快照同步首先需要主节点将当前内存的数据全部快照到磁盘文件中,然后再将快照文件的内容全部传送到从节点。从节点将快照文件接受完毕后,立即执行一次全量加载。加载之前先要将当前内存的数据清空,加载完毕后通知主节点继续进行增量同步。在整个快照同步进行的过程中,主节点的复制buffer还在不停地往前移动,如果快照同步的时间过长或者复制buffer太小,都会导致同步期间的增量指令在复制buffer中被覆盖,这样就会导致快照同步完成后无法进行增最复制,然后会再次发起快照同步,如此极有可能会陷入快照同步的死循环,所以需要配置一个合适的复制buffer大小参数。
3.1.2 增量同步
Redis增量同步的是指令流,主节点会将那些对自己的状态产生修改性影响的指令记录在本地的内存buffer中,然后异步将buffer中的指令同步到从节点,从节点一边执行同步的指令流来达到和主节点一样的状态,一边向主节点反馈自己同步到哪里了(偏移量)。因为内存的buffer是有限的,所以Redis主节点不能将所有的指令都记录在内存buffer中。Redis的复制内存buffer是—个定长的环形数组,如果数组内容满了,就会从头开始覆盖前面的内容。如果因为网络状况不好,从节点在短时间内无法和主节点进行同步,那么当网络状况恢复时,Redis的主节点中那些没有同步的指令在buffer中有可能已经被后续的指令覆盖掉了,从节点将无法直接通过指令流来进行同步。
3.2 Redis Sentinel(Redis哨兵)
Sentinel负责持续监控主从节点的健康,当主节点挂掉时,自动选择—个最优的从节点切换成为主节点。客户端来连接集群时, 会首先连接Sentinel,通过Sentinel来查询主节点的地址,然后再连接主节点进行数据交互。当主节点发生故障时, 客户端会重新向Sentinel要地址,Sentinel会将最新的主节点地址告诉客户端。如此应用程序将无须重启即可自动完成节点切换。
优点:
哨兵模式基于主从复制模式,所以主从复制模式有的优点,哨兵模式也有。
哨兵模式下,主节点宕机可以自动进行切换,更高了系统可用性。
缺点:
不具备自动容错能力,主节点宕机会导致部分数据未同步到从节点,造成数据不一致;
难以支持在线扩容,Redis的容量受限于单机配置;
需要额外的Sentinel资源,实现相对复杂一些。
其中,主从数据同步分为了两个阶段,一个是快照同步(即全量同步),一个是增量同步。
3.3 Redis Cluster
Redis Cluster是Redis的“亲儿子”,它是Redis作者自己提供的Redis集群化方案,它去中心化的。
Redis Cluster将所有数据划分为16384个槽位,集群的每个节点负责其中一部分槽位,槽位的信息存储于每个节点中,当Redis Cluster的客户端来连接集群时,也会得到一份集群的槽位配置信息。这样当客户端要查找某个key时,可以直接定位到目标节点。
Redis Cluster可以为每个主节点设置若干个从节点, 当主节点发生故陓时,集群会自动将其中某个从节点提升为主节点。
Redis Cluster是去中心化的,一个节点认为某个节点失联了并不代表所有的节点都认为它失联了,所以集群还得经过一次协商的过程,只有当大多数节点都认定某个节点失联了,集群才认为该节点需要进行主从切换来容错。
Redis集群节点采用Gossip协议来广播自己的状态以及改变对整个集群的认知。
比如一个节点发现某个节点失联了(PFail即Possibly Fail),它会将这条信息向整个集群广播,其他节点就可以收到这点的失联信息。如果收到了某个节点失联的节点数量(PFail Count)已经达到了集群的大多数,就可以标记该失联节点为确定下线状态(Fail),然后向整个集群广播,强迫其他节点也接受该节点已经下线的事实,并立即对该失联节点进行主从切换。
优点:
无中心架构,数据按照slot分布在多个节点。
集群中的每个节点都是平等的关系,每个节点都保存各自的数据和整个集群的状态。每个节点都和其他所有节点连接,而且这些连接保持活跃,这样就保证了我们只需要连接集群中的任意一个节点,就可以获取到其他节点的数据。
在线扩容简单,节点可动态添加或删除。
能够实现自动故障转移,节点之间通过Gossip协议交换状态信息,用投票机制完成slave到master的角色转换。
缺点:
数据通过异步复制,不保证数据的强一致性。
主节点提供读写操作,从节点只作为备份节点不提供服务,只作为故障转移使用,因此不能缓解读压力。
批量操作限制,目前只支持具有相同slot值的key执行批量操作。
不支持多数据库空间。单机下Redis支持16个数据库,集群模式下只能使用一个数据库空间,即db0。