1. Redis单线程
- 通常说Redis单线程是指Redis的网络IO和键值对读写是由一个线程完成的
但Redis的其他功能,比如持久化,异步删除,集群数据同步等都是由额外的线程完成的
2. 为什么Redis使用单线程
2.1 多线程的开销
- 问题:
- 通常情况,假如没有良好的设计,在刚开始增加线程数时,吞吐率会有所增加,但进一步增加线程时,吞吐率就会增长迟缓甚至会出现下降情况
- 原因:
- 系统中常会存在被多线程同时访问的共享资源,比如一个共享的数据结构,当多个线程要修改共享资源时,就要有额外的机制来保证共享资源准确性,就会带来额外的开销
- 比如,redis中的lpush和lpop操作,假如两个线程同时对list分别做lpush和lpop操作,就会遇到这个问题
- 问题就是多线程编程面临的共享资源的并发访问控制问题
2.2 其他原因
- 多线程开发一般会引入同步原语来保护共享资源并发访问,会降低系统的调试和维护性
3. 为什么那么快
- 大部分操作内存中完成
- 高效的数据结构设计(如哈希表和跳表)
- 采用多路复用机制
4. 基本IO模型和阻塞点
4.1 SimpleKV处理GET请求,IO示例如下图,依次执行如下操作:
Redis基本IO模型
- 潜在阻塞点如下:
- accept()
- 当 Redis 监听到一个客户端有连接请求,但一直未能成功建立起连接时,会阻塞在 accept() 函数这里,导致其他客户端无法和 Redis 建立连接
- recv()
- 当 Redis 通过 recv() 从一个客户端读取数据时,如果数据一直没有到达,Redis 也会一直阻塞在 recv()。
4.2 socket的非阻塞模式
socket 模型,不同操作调用后会返回不同的套接字类型。
image.png
- socket() 方法会返回主动套接字,
- 调用 listen() 方法,将主动套接字转化为监听套接字,此时,可以监听来自客户端的连接请求。
- 调用 accept() 方法接收到达的客户端连接,并返回已连接套接字。
1. 针对监听套接字,可以设置非阻塞模式:当Redis调用accept()但一直未有连接请求到达时,Redis线程可以返回处理其他操作,不用一直等待。但调用accept()时,已经存在监听套接字了
2. 同理,也可以对已连接套接字设置非阻塞模式,如数据没有到达,可以返回处理其他操作
3. 问题:需要有机制,在监听套接字等待后续连接(有请求时通知Redis),继续监听已连接套接字,在有数据到达时通知Redis
Redis机制:基于多路复用的高性能I/O模型
引言:
- linux中IO多路复用:指一个线程处理多个IO流,也就是select/epoll机制
- Redis中:在一个内核中,同时存在多个监听套接字和已连接套接字。内核会一直监听这些套接字上的连接请求或数据请求。有请求到达,就交给redis线程处理
基于多路复用的Redis高性能IO模型
Redis高性能IO模型
- 图中的多个FD指多个套接字。Redis网络框架调用epoll机制,让内核监听这些套接字。这样,Redis线程不会阻塞在某个特定的监听或已连接套接字(不会阻塞在某一个特定的客户端请求处理)。所以,Redis可以同时和多个客户端连接并处理请求,提升并发性
- 为了在请求到达时通知到Redis,select/epoll提供了基于事件的回调机制(针对不同事件发生,调用相应处理函数)
回调机制
- select/epoll一旦监测到FD上有请求,出发相应事件将事件放进一个队列,Redis单线程对该事件队列不断进行处理
- 以连接请求和读数据请求为例:
- 两个请求分别对应Accept事件和Read事件,Redis分别对这两个事件注册accept和get回调函数。当linux内核监听到有连接请求或读数据请求时,就会触发Accept事件和Read事件,此时,内核就会回调Redis相应的accept和get函数进行处理
总结:
- Redis单线程是指它对网络IO和数据读写的操作采用了一个单线程,核心原因是避免多线程开发的并发控制问题
- Redis单线程获得高性能的原因在:
- 多路复用的IO模型:避免了accept()和send()/recv()潜在的网络IO操作点
网友评论