深入理解Redis
Redis阻塞
Redis 是典型的单线程架构,所有的读写操作都是在一条主线程中完成的。当Redis用于高并发场景时,阻塞问题至关重要。
导致阻塞的原因
- 内在原因:不合理地使用API或数据结构、CPU饱和、持久化阻塞等
- 外在原因:CPU竞争、内存交换、网络问题等
CPU饱和
单线程的Redis处理命令时只能使用一个CPU,CPU饱和是指Redis把单核CPU使用率跑到接近100%。CPU饱和导致Redis无法处理更多命令。
解决方法有:
- 采用集群分摊OPS压力(Operations per Second)
- 检查是否存在过度的内存优化,例如为了节省内存使用ziplist代替hash
- 检查是否采用了高算法复杂度的命令
CPU竞争
- 进程竞争:当Redis与其它多核CPU密集型服务部署在一起,会出现竞争CPU资源的情况。
- 绑定CPU:部署Redis时为了充分利用多核CPU,通常一台机器会部署多个实例。常见的一种优化是把Redis进程绑定到CPU上,用于降低CPU频繁上下文切换的开销。
- 但是当Redis父进程创建子进程进行持久化时,如果作了CPU绑定,子进程与父进程就会在同一个CPU上进行,从而使得子进程与父进程抢夺CPU资源。因此对于开启了持久化或参与复制的主节点不建议绑定CPU。
Redis内存
- Redis实际内存消耗主要包括:键值对象、缓冲区内存、内存碎片。
- 缓冲内存包括:客户端缓冲、复制积压缓冲、AOF缓冲区
Redis事件处理
Redis服务器是典型的事件驱动程序。其中,事件分为文件事件和时间事件,事件封装在结构体aeEventLoop中。
Redis底层使用4中I/O多路复用模型(evport, select, kqueue, epoll),并对这4种模型进一步封装。
事件循环执行函数的主要逻辑:
- 查找最早会发生的时间事件,计算超时时间
- 阻塞等待文件事件的产生
- 处理文件事件
- 处理时间事件
文件事件
Redis客户端通过TCP socket与服务端交互,文件事件指的就是socket的可读可写事件。为了同时处理多条网络连接,采用较为成熟的I/O多路复用模型。
epoll
epoll是linux内核为处理大量并发网络连接而提出的解决方案,能显著提升系统CPU利用率。
- epoll_create: 创建一个epoll专用的文件描述符
- epoll_ctl: 注册、修改或删除需要监控的事件
- epoll_wait: 阻塞进程知道监控的若干网络连接有事件发生
时间事件
本质上,Redis只有一个时间事件——serverCron。该函数实现了Redis服务器所有定时任务的周期执行。
Redis Server启动过程
Server初始化
- 初始化配置,包括用户可配置的参数以及命令表的初始化
- 加载并解析配置文件
- 初始化服务端内部变量,其中包括数据库。
- 创建事件循环eventLoop
- 创建socket并启动监听
- 创建文件事件与时间事件
- 开启事件循环
Redis 数据分区
当单台机器的可用内存里无法装载大型数据库或者数据集时,数据分区就成为了一种重要策略,它将键进行分割并指派给特定的Redis实例。通过分区这一方式,单一的Redis实例的计算能力和资源将不再成为限制。
数据分区的方法
客户端分区
分区逻辑包含在客户端代码中,基于算法或者存储的额外信息来选择正确的分区或Redis节点。
常见的策略有:
- 范围分区:为每个Redis实例指定键的范围。会带来额外的成本,包括用户追踪分区的Redis bitstring数据结构,以及定制开发的客户端代码
- 列表分区:为分区指定一个列表值。与范围分区类似,列表分区同样需要中间数据结构来支持数据存储中 键的分配与追踪。
- 哈希分区:根据键和哈希算法计算出值,并基于数据存储中的分区数量或可用实例数对该值进行取模。
- 复合分区:结合不同分区方法进行分区。
辅助代理分区
Redis客户端连接到代理中间件,再由它将客户端请求路由到正确的Redis节点。
Twemproxy:
Twemproxy是一个由Twitter发布的开源项目,旨在创建客户端和由memcache或者Redis实例组成的服务器端间的缓存代理。
Twemproxy通过采用中间件的方式,分离客户端调用和数据存储后端
查询路由
- 任一客户端查询集群中的随即节点将会被路由到包含正确键的正确节点上,这是Redis集群当前的实现方式。
Redis集群
Redis在大规模分布式计算中主要面临两个问题:
- 如何将数据分区到N个Redis实例中去
- 如何优雅地处理某些情况下的节点故障
Redis集群不提供健壮的一致性保证,也就是说在传播数据时数据可能丢失。
Redis集群默认运行的传播模式是异步的流程(在与客户端交互期间,Redis集群会继续写操作)。在主从节点的配置下,集群的性能胜于数据的一致性。如果数据的一致性对于应用程序来说比性能更重要,那么Redis集群提供的WAIT命令可以用来将数据传播的方式变更成同步流程。
当Redis集群在运行时,当中的每个节点会打开两个TCP socket。第一个用于连接客户端的标准Redis协议,默认端口是6379。第二个端口是由第一个端口加上10000所得(默认16379),运行着用于节点间通信的二进制协议。
Redis集群中的节点使用Redis集群总线在网状网络拓扑结构中与其他节点连接。
参考文献
- 《Redis开发与运维》
- 《Redis 5设计与源码分析》
- 《深入理解Redis》