通过 Redis 指令执行的生命周期看 Redis 的底层架构和基本实现


引子

前面学院君通过二十多篇教程的篇幅系统给大家介绍了 Redis 的数据结构和实际应用,从今天这篇教程开始,我们先深入 Redis 底层,看看它是通过哪些数据结构和算法设计来实现高性能的键值对数据库,然后再向外水平扩展,从单机版到 Redis 集群,看看如何实现大型系统中 Redis 服务的高可用。

为了让你对 Redis 的底层架构和基本实现有一个整体的认知,我们以最简单的字符串键值对增删改查指令执行为例给大家介绍下 Redis 指令执行的完整生命周期,以及期间涉及到的 Redis 各组件是如何协同工作的。

通信协议

当 Redis 客户端发送了一条新增(或更新)字符串键值对的指令后,该指令会被封装到网络包中发送给 Redis 服务端:

127.0.0.1:6379> set name '学院君'
OK 

这个通信是基于 TCP 连接的 Socket 通信,采用的通信协议是 RESP(REdis Serialization Protocol,即 Redis 序列化协议,关于这个通信协议的细节我们后面会详细介绍,这里先了解即可)。

Redis 没有使用动态链接库实现客户端与服务端进程之间的通信,而是使用这种基于网络协议的通信,好处是简单、易于实现、解析方便、可读性好(RESP 是一个基于纯文本的协议),但是坏处也是显而易见的,即网络 IO 普遍存在的性能问题,下面我们来介绍 Redis 是如何处理这个问题保证键值对数据库的高性能的。

单线程 IO 模型

Redis 服务端接收到客户端发送过来的指令后,会基于 RESP 协议解析指令信息,然后进行相应的处理。此时需要面临一个系统设计问题:客户端网络连接的处理、指令请求的解析、以及解析后请求数据的存取,是用一个线程还是多个线程来交互处理?通常我们将这个问题称之为 IO 模型设计。

学院君之前在介绍并发编程的时候,曾反复强调过并发执行的程序可以更好地榨取 CPU 的性能,从而提升程序的性能,而并发编程需要借助多进程、多线程、协程等方式来实现。但是,并发也不是万精油,如果涉及到共享资源的操作,需要引入锁机制来保证操作的原子性时,并发程序的性能会大幅降低,因为锁会导致线程竞争和阻塞。

而 Redis 服务端在进行键值对存取的时候,同一个键名对应的正是同一个资源,如果使用多线程来处理多个客户端对同一个键名的并发存取操作,势必导致一个线程在处理的时候,其他线程处于阻塞状态,所以在这里多线程并不是最优解。

因此 Redis 在进行网络 IO 模型设计时,选择了单线程 IO 模型,可不要小瞧单线程 IO 模型哦,Nginx 也是通过这种 IO 模型实现高性能 Web 服务器的。这里的单线程可不同于 PHP 的单进程机制,实际上 PHP 性能不佳主要问题也不是出在处理 Web 请求的单进程上,而是由于动态语言本身边解释边执行带来的运行时成本,以及不能实现网络请求、数据库请求之类的连接池,但是网络连接不能复用,需要反复初始化应用和建立连接带来的开销。

Redis 的单线程 IO 模型是能够处理 10 万级并发请求的,这个性能非常强悍,那单线程 IO 模型是如何做到的如此高性能的呢,这就不得不提及大名鼎鼎的 IO 多路复用机制,关于这个技术学院君在 Socket 编程(下):服务器如何提高并发量 简单介绍过,后面我们还会详细介绍 Redis 底层如何使用它实现高性能的网络 IO。

通过索引快速定位

Redis 服务端通过单线程接收并解析客户端指令后,如果是存储操作,就要将键值对数据存储到内存,在此之前需要先判断键值对是否已经存在;如果是读取操作,就要从内存获取键名对应的键值并返回,无论是存储还是读取,都会涉及到键名对应键值的定位问题。

我们前面在介绍 MySQL 的时候,提到 MySQL 通常会通过 B+ 树索引来优化查询效率,如果使用的存储引擎是内存的话,默认使用的是哈希索引技术。

对于 Redis 这种基于内存的键值对数据库,使用的也是哈希表来快速定位键名对应的键值,毕竟,对于键值对这种数据结构,天然适合哈希索引,性能也达到了非常高的 O(1)(不考虑哈希冲突的情况下)。

当然,除了基本的字符串键值对外,Redis 还支持其他数据结构,比如列表、集合、字典等,这些数据结构内存还存在获取元素性能优化的问题,后面我们还会系统介绍 Redis 底层如何基于哈希索引为高性能保驾护航。

另外,对于存储/删除操作而言,还涉及到分配新的内存空间以及释放内存空间的问题,Redis 底层会有分配器专门完成这些事情。

数据持久化概述

日常使用的时候,Redis 服务端基于内存操作保证了数据存取的高性能和高效率,不过和 Memcached 相比,除了支持更丰富的数据结构外,Redis 还支持将数据持久化到磁盘,这就意味着 Redis 不仅可以用作高性能的缓存系统,还可以用作 NoSQL 数据库。

Redis 支持通过全量备份的 RDB 快照和增量备份的 AOF 日志两种机制进行数据持久化,当系统重启后,内存数据丢失,Redis 会通过 AOF 日志对数据进行重放来恢复内存数据。当然了,对于这种持久化到磁盘涉及到磁盘 IO 的操作,Redis 会通过其他线程后台异步进行处理,并且为了保证 Redis 的高性能,对持久化时机进行了优化,关于 Redis 数据持久化的更多细节,学院君后面也会单独介绍。

小结

到这里,想必你已经对 Redis 服务端如何处理客户端连接、解析并处理客户端请求、返回响应数据以及数据持久化有了整体的了解,并且对 Redis 如何实现高性能键值对数据库有了一定的认知,更多细节学院君将在后续具体教程中一一给大家介绍。


Vote Vote Cancel Collect Collect Cancel

<< 上一篇: 安全地使用 Redis(下):基于 Spiped 代理对通信进行加密

>> 下一篇: Redis 客户端与服务端通信协议 RESP 详解及 predis 扩展实现原理