Redis 是典型的内存键值数据库,经常被用于缓存、秒杀、分布式锁等场景。本文的目标是对 Redis 的总体架构和关键模块有一个全局的认知。

本文的学习目标如下:

  1. 了解 Redis 的数据模型,即 Redis 可以存什么样的数据。
  2. 了解 Redis 的操作接口,即 Redis 对数据可以做什么样的操作。
  3. 了解 Redis 的技术选型,即 Redis 将数据保存在内存还是硬盘。
  4. 了解 Redis 的访问模式,即 Redis 如何提供键值对服务。
  5. 了解 Redis 的整体架构,即 Redis 具体分为哪几个模块。

数据模型

对于键值数据库而言,基本的数据模型是 key-value 模型。 例如,“hello”: “world”就是一个基本的 KV 对,其中,“hello”是 key,“world”是 value。

Redis 包括字符串(string)、散列(hash)、列表(list)、集合(set)和有序集合(sorted set)这五种数据类型。

操作接口

数据库的操作接口主要有增删改查。

具体来说,对于 Redis 而言,下表给出了与 Redis 键相关的基本命令:

命令 用途
DEL key 删除已存在的key。
DUMP key 序列化给定 key ,并返回被序列化的值。
EXISTS key 检查给定 key 是否存在。
EXPIRE key seconds 为给定 key 设置过期时间,以秒计。
EXPIREAT key timestamp EXPIREAT 的作用和 EXPIRE 类似,都用于为 key 设置过期时间。 不同在于 EXPIREAT 命令接受的时间参数是 UNIX 时间戳(unix timestamp)。
PEXPIRE key milliseconds 设置 key 的过期时间以毫秒计。
PEXPIREAT key milliseconds-timestamp 设置 key 过期时间的时间戳(unix timestamp) 以毫秒计
KEYS pattern 查找所有符合给定模式( pattern)的 key
MOVE key db 将当前数据库的 key 移动到给定的数据库 db 当中。
PERSIST key 移除 key 的过期时间,key 将持久保持。
PTTL key 以毫秒为单位返回 key 的剩余的过期时间。
TTL key 以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live)。
RANDOMKEY 从当前数据库中随机返回一个 key
RENAME key newkey 修改 key 的名称
RENAMENX key newkey 仅当 newkey 不存在时,将 key 改名为 newkey 。
SCAN cursor [MATCH pattern] [COUNT count] 迭代数据库中的数据库键。
TYPE key 返回 key 所储存的值的类型。

技术选型

对于键值对而言,一个比较重要的问题是:键值对保存在内存还是外存?

  • 保存在内存的好处是读写很快,毕竟内存的访问速度一般都在百 ns 级别。但是,潜在的风险是一旦掉电,所有的数据都会丢失。

  • 保存在外存,虽然可以避免数据丢失,但是受限于磁盘的慢速读写(通常在几 ms 级别),键值数据库的整体性能会被拉低。

因此,如何进行设计选择,我们通常需要考虑键值数据库的主要应用场景。比如,缓存场景下的数据需要能快速访问但允许丢失,那么,用于此场景的键值数据库通常采用内存保存键值数据。Memcached 和 Redis 都是属于内存键值数据库。对于 Redis 而言,缓存是非常重要的一个应用场景。

访问模式

访问模式通常有两种:一种是通过函数库调用的方式供外部应用使用,比如,以动态链接库的形式链接到我们自己的程序中,提供键值存储功能;另一种是通过网络框架以 Socket 通信的形式对外提供键值对操作,这种形式可以提供广泛的键值存储服务。

不同的键值数据库服务器和客户端交互的协议并不相同,我们在对键值数据库进行二次开发、新增功能时,必须要了解和掌握键值数据库的通信协议,这样才能开发出兼容的客户端。

实际的键值数据库也基本采用上述两种方式,例如,RocksDB 以动态链接库的形式使用,而 Memcached 和 Redis 则是通过网络框架访问。

通过网络框架提供键值存储服务,一方面扩大了键值数据库的受用面,但另一方面,也给键值数据库的性能、运行模型提供了不同的设计选择,带来了一些潜在的问题。

举个例子,当客户端发送一个命令 PUT hello world 后,该命令会被封装在网络包中发送给键值数据库。键值数据库网络框架接收到网络包,并按照相应的协议进行解析之后,就可以知道,客户端想写入一个键值对,并开始实际地写入流程。此时,我们会遇到一个系统设计上的问题,简单来说,就是网络连接的处理、网络请求的解析,以及数据存取的处理,是用一个线程、多个线程,还是多个进程来交互处理呢?该如何进行设计和取舍呢?我们一般把这个问题称为 I/O 模型设计。不同的 I/O 模型对键值数据库的性能和可扩展性会有不同的影响。

举个例子,如果一个线程既要处理网络连接、解析请求,又要完成数据存取,一旦某一步操作发生阻塞,整个线程就会阻塞住,这就降低了系统响应速度。如果我们采用不同线程处理不同操作,那么,某个线程被阻塞时,其他线程还能正常运行。但是,不同线程间如果需要访问共享资源,那又会产生线程竞争,也会影响系统效率,这又该怎么办呢?所以,这的确是个“两难”选择,需要我们进行精心的设计。

整体架构

Redis 的整体架构如下所示:

Redis 的整体架构

大体来说,Redis 包括了访问框架、索引模块、操作模块和存储模块四个基本模块,高可用集群支撑模块和高可扩展集群支撑模块两个拓展模块。

  • 查找所要操作的键值对是否存在,这依赖于键值数据库的索引模块。索引的作用是让键值数据库根据 key 找到相应 value 的存储位置,进而执行操作。索引的类型有很多,常见的有哈希表、B+ 树、字典树等。不同的索引结构在性能、空间消耗、并发控制等方面具有不同的特征。Redis 采用哈希表作为 key-value 索引,很大一部分原因在于,其键值数据基本都是保存在内存中的,而内存的高性能随机访问特性可以很好地与哈希表 O(1) 的操作复杂度相匹配。
  • Redis 主要通过网络框架进行访问,使得 Redis 可以作为一个基础性的网络服务进行访问,扩大了 Redis 的应用范围。
  • Redis 的持久化模块能支持两种方式:日志(AOF)和快照(RDB),这两种持久化方式具有不同的优劣势,影响到 Redis 的访问性能和可靠性。
  • Redis 支持高可靠集群和高可扩展集群,因此,Redis 中包含了相应的集群功能支撑模块。

总结

本文简要介绍了键值数据库 Redis 的数据模型、操作接口、技术选型、访问模式和整体架构:

  • Redis 的数据模型主要包括字符串(string)、散列(hash)、列表(list)、集合(set)和有序集合(sorted set)这五种数据类型。
  • Redis 的操作接口主要有增删改查、时间相关的接口。
  • Redis 选择将数据存储在内存中。
  • Redis 通过网络框架以 Socket 通信的形式对外提供键值对操作。
  • Redis 的整体架构包括了访问框架、索引模块、操作模块和存储模块四个基本模块,高可用集群支撑模块和高可扩展集群支撑模块两个拓展模块。

参考