Redis 单线程服务器
一、基础铺垫
1.1网络编程
TCP网络编程在Java中封装成Socket和SocketServer,最简单的TCP网络编程:
TCP客户端:
public class ClientDemo {
public static void main(String[] args) throws IOException {
Socket s = new Socket("192.168.1.106",8888);
OutputStream os = s.getOutputStream();
os.write("hello,tcp,我来了".getBytes());
s.close();
}
}
TCP服务端:
public class ServerDemo {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(8888);
Socket s = ss.accept();
InputStream is = s.getInputStream();
byte[] bys = new byte[1024];
int len = is.read(bys);
String str = new String (bys,0,len);
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip + " ---" +str);
s.close();
}
}
1.2IO多路复用
I/O多路复用的特点是通过一种机制一个进程能同时等待多个文件描述符,而这些文件描述符其中的任意一个进入读就绪状态,select()函数就可以返回。
select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。
二、Redis事件
Redis服务器是一个事件驱动程序,主要处理以下两类事件:
- 文件事件:对Socket操作的抽象,Redis服务器与Redis客户端的通信会产生文件事件
- 时间事件:对定时操作的抽象,定时删除键等操作通过时间事件实现
2.1文件事件
Redis开发了自己的网络事件处理器,被称为文件事件处理器。
文件事件处理器由四部分组成:
- Socket
- I/O多路复用程序
- 文件事件分派器
- 事件处理器
Redis中的I/O多路复用程序会将所有产生事件的Socket放到一个队列里边,然后通过这个队列以有序、同步、每次一个Socket的方式向文件事件分派器传送套接字。
2.2时间事件
持续运行的Redis服务器会定期对自身的资源和状态进行检查和调整,这些定期的操作由serverCron函数负责执行,它的主要工作包括:
- 更新服务器的统计信息(时间、内存占用、数据库占用)
- 清理数据库的过期键值对
- AOF、RDB持久化
- 如果是主从服务器,对从服务器进行定期同步
- 如果是集群模式,对集群进行定期同步和连接
2.3时间事件和文件事件
文件事件和时间事件之间是合作关系,服务器会轮流处理这两种事件,并且处理事件的过程中不会发生抢占。
三、Redis单线程为什么快?
- 纯内存操作
- 核心是基于非阻塞的IO多路复用机制
- 单线程避免了多线程的频繁上下文切换问题
四、客户端与服务器
Redis服务器使用单线程单进程的方式处理命令请求。
4.1客户端
typedef struct redisClient{
//客户端状态的输入缓冲区用于保存客户端发送的命令请求,最大1GB
sds querybuf;
// 命令的参数
robj **argv;
// 客户端要执行命令的实现函数
struct redisCommand *cmd, *lastcmd;
// 记录客户端是否通过了身份验证
int authenticated;
// 固定大小的缓冲区用于保存那些长度比较小的回复
char buf[REDIS_REPLY_CHUNK_BYTES];
// 可变大小的缓冲区用于保存那些长度比较大的回复
list *reply;
}redisClient;
4.2服务端
服务器从启动到能够处理客户端的命令请求需要执行以下的步骤:
- 初始化服务器状态
- 载入服务器配置
- 初始化服务器的数据结构
- 还原数据库状态
- 执行事件循环
从客户端发送命令到完成主要包括的步骤:
- 客户端将命令请求发送给服务器
- 服务器读取命令请求,分析出命令参数
- 命令执行器根据参数查找命令的实现函数,执行实现函数并得出命令回复
- 服务器将命令回复返回给客户端