Redis
简介
Redis是一个开源软件,遵守BSD 协议,其本质上是一个Key-Value类型的内存数据库。但是和其他的KV缓存例如memcached不同的地方是其功能更加完善,特性更加丰富。
- Redis支持多种的数据结构,例如String,Hash,List,Set,ZSet等
- Redis支持数据持久化
- Redis支持主从结构,支持集群化部署
- Redis支持原子操作,可以安全地 SET 或 INCR 键,添加和删除集合中的元素等。
- Redis支持通过Lua脚本语言实现事务,原子操作包装等
- Redis有较高的性能,支持pipeline等批量操作。
原理
线程模型
Redis基于Reactor模式实现了网络处理模块,该模块分为4部分
套接字管理,Redis是Client-Server架构,Client请求数据的时候需要通过网络链接,所以Redis Server需要对这些链接进行管理,比如移除超时的连接等。
IO多路复用程序,Redis的I/O多路复用有多套实现,分别利用select、epoll、evport和kqueue这些I/O多路复用函数库来实现。
事件分发,多路复用会根据IO事件的类型,例如链接事件,读事件,写事件进行不同的处理逻辑,所以需要事件分发器分到不同的处理器。
事件处理器,不同的事件处理器分别处理连接、读、写等事件。
Redis的IO模型是基于Reactor的单线程,但是可以承担较高的QPS,原因如下:
- Redis的操作都是基于内存的,所以速度非常快。
- Redis使用哈希作为索引,对于大部分查询只需要O(1)的事件复杂度就可以找到key对应的数据。
- Redis是基多路复用模型中的epoll模型进行网络通讯的,epoll模型的事件监听机制,所有的数据都是异步操作,不会阻塞主线程
- 单线程减少了线程切换的成本
数据类型
Redis支持多种的数据结构,例如String,Hash,List,Set,ZSet等。
数据类型 | API | 使用场景 | 实现原理 | 架构图 |
---|---|---|---|---|
String | set
get desc incr mget |
String是最基本的KV结构,Key是唯一标识,Value是Key关联的值。Value可以表达数字(整数或者浮点数)以及字符串。
String的底层实现主要是int和SDS「简单动态字符串」。 当存储的数据是整数类型时候,String 类型会使用 int 编码方式来存储。具体为使用一个 8字节的 Long 类型来实现。 当存储的数据中包含字符串时候,String 类型会使用 SDS 结构体来存储。 SDS 有五种实现方式 SDS_TYPE_5、 SDS_TYPE_8、 SDS_TYPE_16、 SDS_TYPE_32,SDS_TYPE_64。根据初始化的长度决定使用哪种类型,从而减少内存的使用。
除了记录实际数据,String 类型还需要额外的内存空间记录数据长度、空间使用、 最后一次访问的时间、被引用的次数 等元数据, 所以,Redis 会用一个 RedisObject 结构体来统一记录这些元数据,同时指向实际数据。 一个 RedisObject 包含了 8 字节的元数据和一个 8 字节指针,这个指针再进一步指向具体数据类型的实际数据所在或者为真实值。 |
||
Hash | hget
Hset hgetall |
当HashMap的成员较少时,redis会采用zipmap的方式存储,zipmap是一个紧凑的存储结构。
zipmap的主要字段如下:
其中扩容需要满足一定的条件
缩容机制:如果当前键值对个数少于一维数组大小的十分之一,则触发缩容过程。缩容不会考虑当前服务器是否在进行BGWRITEAOF或者BGSAVE命令 Hash类型扩容后数组的长度为原来的二倍 触发扩容的时候,Redis会首先为ht[1] 分配一块内存空间。如果当前字典是一个比较大的字典,那么整个扩容过程的时间复杂度为O(n),直接完整进行扩容机制可能会导致Redis一段时间内停止服务。为了避免停止服务的情况,Redis的设计团队采用了渐进式rehash的策略,每次只对原哈希表中的一小部分进行搬迁,这样渐进式的进行,直到全部键值对都迁移到新的哈希表中。 扩容过程中,字典结构需要同时查询两个Hash表。 |
||
List | lpush
lpop rpop lrange |
Redis 3.2之前:
Redis 3.2版本之后:
|
||
Set | Redis set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的
Set类型的底层数据结构是由哈希表或整数集合实现的:
|
|||
ZSet | 点赞数量 | Redis sorted set的使用场景与set类似,区别是set不是自动有序的,而sorted set可以通过用户额外提供一个优先级(score)的参数来为成员排序,并且是插入有序的,即自动排序。当你需要一个有序的并且不重复的集合列表,那么可以选择sorted set数据结构,比如twitter 的public timeline可以以发表时间作为score来存储,这样获取时就是自动按时间排好序的。
Zset 类型的底层数据结构是由压缩列表或跳表实现的
在 Redis 7.0 中,压缩列表数据结构已经废弃了,交由 listpack 数据结构来实现了。 |
持久化
Redis的持久化机制分为两种,分别是AOF和RDB机制。
RDB是数据快照的持久化方式,采用该模式的Redis会定期生成RDB文件,RDB文件是结构紧凑的数据文件,从RDB文件可以快速的进行数据恢复。
RDB因为需要同步全量数据,所以需要更多的IO消耗,所以RDB同步的间隔往往较长,在系统宕机的时候会丢失更多的数据。
AOF采用的是日志模式,Redis会将每次操作数据记录的明细保存到AOF文件,AOF文件记录了所有的日志,所以系统宕机的时候只会丢失少数还是持久化到硬盘的数据。
采用AOF进行数据恢复,需要重放AOF文件,所以数据恢复时间更长。
集群技术
Redis的集群技术分为三种,分别是主从模式,哨兵模式,集群模式。
主从模式
复制的概念中将数据库分为两类,主数据库(master)和从数据库(slave)。主数据库可以进行读写操作,从数据库一般是只读的,主数据库执行写操作之后会把数据同步给从数据库。主数据库和从数据库是一对多的关系。
哨兵模式
主从架构当发生主节点宕机时恢复故障需要手工切换。
Redis引入了哨兵架构模式的自动故障恢复,哨兵模式的复杂度较高,需要引入分布式协调组件例如zookeeper等,这样能保证Redis哨兵节点始终能够感知到Redis集群的状态。Redis哨兵监控主节点的健康,当主节点不可用时,会自动选择一个从节点切换为主节点。客户端在请求主节点时访问失败会通过Redis哨兵查询主节点的地址,成功后再将新的主节点列表缓存到客户端中。故障主节点恢复后会作为一个新的只读从节点加入集群。
集群模式
集群模式中单个节点不再有所有的全量数据。
节点会通过分片存储部分的数据, Redis 的每一个节点都有插槽(slot)和 cluster, slot 的的取值范围是:0-16383。 cluster是一个集群管理的插件。当对 Key 进行操作时,Redis 会根据 CRC16 的算法得出结果然后把结果对 slot 的数据取模,通过这个值,去对应的插槽查询对应的节点,然后直接自动跳转到这个对应的节点上进行存取操作。
集群模式下除了分片之外,同样存在主从节点。