Redis

来自智得网
跳转至: 导航、​ 搜索

简介

Redis是一个开源软件,遵守BSD 协议,其本质上是一个Key-Value类型的内存数据库。但是和其他的KV缓存例如memcached不同的地方是其功能更加完善,特性更加丰富。

  • Redis支持多种的数据结构,例如String,Hash,List,Set,ZSet等
  • Redis支持数据持久化
  • Redis支持主从结构,支持集群化部署
  • Redis支持原子操作,可以安全地 SET 或 INCR 键,添加和删除集合中的元素等。
  • Redis支持通过Lua脚本语言实现事务,原子操作包装等
  • Redis有较高的性能,支持pipeline等批量操作。

原理

Redis线程模型

线程模型

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。根据初始化的长度决定使用哪种类型,从而减少内存的使用。


相较 C字符串,SDS的优势如下:

  • C语言的字符串不记录自身长度,想要知道一个字符串的长度就必须遍历字符串,复杂度为 O(N),而Redis的字符串同样使用命令 STRLEN 的时候,复杂度为 O(1)。
  • 二进制安全,可以存储非文本数据的,包括视频,音频,图片等。SDS并不是像传统的C字符串(字符数组)一样,而SDS常被称作字节数组,采用以字节为单位的形式存储数据,通过长度判断字节数组的结束,而不会因为C字符串中对 \0作为结束字符带来额外的约束。
  • 可以高效地执行追加操作(append),加快追加操作的速度,并降低内存分配的次数,代价是多占用了一些内存,而且这些内存不会被主动释放。
  • API是安全的,拼接字符串不会造成缓冲区溢出的问题

除了记录实际数据,String 类型还需要额外的内存空间记录数据长度、空间使用、 最后一次访问的时间、被引用的次数 等元数据, 所以,Redis 会用一个 RedisObject 结构体来统一记录这些元数据,同时指向实际数据。

一个 RedisObject 包含了 8 字节的元数据和一个 8 字节指针,这个指针再进一步指向具体数据类型的实际数据所在或者为真实值。

Hash hget

Hset

hgetall

当HashMap的成员较少时,redis会采用zipmap的方式存储,zipmap是一个紧凑的存储结构。

zipmap的主要字段如下:

  • zmlen用来记录 zipmap 的元素个数, 占用一个字节。当元素个数大于等于 254 后,确切的元素个数需要遍历整个字符串。
  • len用来记录下一个字符串(key 或 value)的长度,是可变长度的。当字符串(key 或 value)的长度小于等于 253,则使用一个字节来表示。当字符串(key 或 value)的长度大于等于 254,则使用五个字节表示。第一个固定为 254,用剩下的四个字节表示具体的长度。字节顺序跟着主机一致,即Little-Endian、Big-Endian和主机保持一样。
  • free用来记录随后的 value 后面的空闲未使用字节数。是一个无符号的 8 位数字。free 的值主要是改变 key 的 value 产生的。如把 “age” => “19” 变成了 “1” ,就产生了 1 个字节的空闲空间,free 的值就变成了 1。为了保证字符串尽量紧凑,zipmapSet操作中 zipmap会进行调整以使整个字符串尽可能小。
  • end用来记录 zipmap 的结尾符,占用1个字节,其值固定为255 (0xFF)。


当HashMap的成员较多,Hash则会转为字典类型,Hash结构的主要特点如下:

  • 采用链地址法来处理哈希冲突,整体数据结构与Java中的HashMap极为相似,都是采用数组+链表(Redis中不会将链表改为红黑树)的结构来实现。


字典类型容量变化过程叫做rehash

其中扩容需要满足一定的条件

  • 服务器当前没有进行BGWRITEAOF或者BGSAVE命令,且当前键值对个数超过一维数组的大小(这两个命令是用于Redis持久化的,挖个坑,改天给你们发一篇Redis持久化的)
  • 如果当前键值对个数超过一维数组大小的五倍,无论是否在进行BGWRITEAOF或者BGSAVE命令,都会强制扩容。

缩容机制:如果当前键值对个数少于一维数组大小的十分之一,则触发缩容过程。缩容不会考虑当前服务器是否在进行BGWRITEAOF或者BGSAVE命令

Hash类型扩容后数组的长度为原来的二倍

触发扩容的时候,Redis会首先为ht[1] 分配一块内存空间。如果当前字典是一个比较大的字典,那么整个扩容过程的时间复杂度为O(n),直接完整进行扩容机制可能会导致Redis一段时间内停止服务。为了避免停止服务的情况,Redis的设计团队采用了渐进式rehash的策略,每次只对原哈希表中的一小部分进行搬迁,这样渐进式的进行,直到全部键值对都迁移到新的哈希表中。

扩容过程中,字典结构需要同时查询两个Hash表。

Zipmap的存储结构.png
Redis的hash存储结构.png
List lpush


rpush

lpop

rpop

lrange

Redis 3.2之前:
  • List类型的底层数据结构是由双向链表或压缩列表实现的。
    • 列表元素个数小于512,且列表每个元素的值都小于64个字节,使用压缩列表
    • 不满足上述条件的,使用双向链表

Redis 3.2版本之后:

  • List类型的底层数据结构是quicklist,替代了双向链表和压缩列表。
Set Redis set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的

Set类型的底层数据结构是由哈希表或整数集合实现的:

  • 如果集合中的元素都是整数且元素个数小于 512个,使用整数集合作为 Set 类型的底层数据结构;
  • 如果不满足条件,则使用哈希表作为 Set 类型的底层数据结构。
ZSet 点赞数量 Redis sorted set的使用场景与set类似,区别是set不是自动有序的,而sorted set可以通过用户额外提供一个优先级(score)的参数来为成员排序,并且是插入有序的,即自动排序。当你需要一个有序的并且不重复的集合列表,那么可以选择sorted set数据结构,比如twitter 的public timeline可以以发表时间作为score来存储,这样获取时就是自动按时间排好序的。

Zset 类型的底层数据结构是由压缩列表或跳表实现的

  • 如果有序集合的元素个数小于 126个,并且每个元素的值小于64字节时,使用压缩列表;
  • 如果不满足条件,则使用跳表;

在 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 的数据取模,通过这个值,去对应的插槽查询对应的节点,然后直接自动跳转到这个对应的节点上进行存取操作。

集群模式下除了分片之外,同样存在主从节点。

应用

分布式锁