什么是Redis事件和服务器

发布时间:2021-06-23 13:32:38 作者:chen
来源:亿速云 阅读:156

这篇文章主要讲解了“什么是Redis事件和服务器”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“什么是Redis事件和服务器”吧!

Redis事件和服务器

事件

Redis是个单线程的,但是速度非常快,其主要原因是因为它是基于事件的,是一个事件驱动程序,了解NIO的应该都知道这种方式。

Redis服务器需要处理两类事件。

文件事件

Redis基于Reactor模式开发了自己的网络事件处理器,则个处理器被称为文件时间处理器(file event handler):

构成

文件时间处理器由四个部分组成,分别是套接字。I/O多路复用程序,文件事件分发器(dispatcher)和事件处理器。

什么是Redis事件和服务器

文件事件是对套接字操作的抽象,每当一个套接字准备好执行连接应答,写入,读取,关闭等操作时,就会产生一个文件时间。因为一个服务器会连接多个套接字,所有文件事件可能并发出现。

I/O多路复用程序负责监听多个套接字,并向文件事件分发器传送那些产生了时间的套接字。I/O多路复用程序总是将所有产生时间的套接字放到一个队列里面,然后通过这个队列,以有序,同步,每次一个套接字的方式向文件时间分发器传送套接字。

什么是Redis事件和服务器

文件事件分发器接收I/O多路复用程序传来的套接字,并根据套接字产生的时间类型,调用相应的事件处理器。

服务器会为执行不同的任务的套接字关联不同的事件处理器,这些处理器是一个个函数,它们定义某个时间发生时,服务器应该执行的操作。

Tips: Redis的I/O多路复用程序的所有功能都是通过包装常见的select,epoll,evport和kqueue这些I/O多路复用函数库来实现的,并且为每个多路复用函数库都实现了相同的API,因此底层是可以互换的。在编译的时候,会自动选择系统中性能最高的I/O多路复用函数库来作为底层实现。

客户端发起请求示例:

什么是Redis事件和服务器

时间事件

Redis的时间事件也有两类:

所有的时间事件都放在一个无序链表中,每当事件执行器运行时,它就遍历整个链表,查找所有已到达的时间事件,并调用相应的事件处理器。(这里的无序是指事件到达时间无序)

serverCron函数

serverCron函数负责定期对Redis的资源和状态进行检查和调整,主要工作包括:

该函数默认执行时间是每秒10次,可以通过调整配置hz的值来改变,这个值代表的是每秒执行的次数哦!

hz 10

更新服务器时间缓存

Redis服务器中有不少功能要获取系统当前时间,而每次获取都需要执行一次系统调用,为了减少系统调用次数,服务器状态中额unixtime属性和mstime被用作当前时间缓存。

struct redisServer {
    // ...
    // 保存秒级进度的系统当前UNIX时间戳
    time_t unixtime;
    // 保存毫秒级进度的系统当前UNIX时间戳
    long long mstime;
    // ...
};

因为函数每秒运行10次,100毫秒一次,所以这两个属性的精确度不高。

更新LRU时钟

服务器状态中的lruclock属性保存了服务器的LRU始终,这个属性也是服务器时间缓存的一种,用来计算对象空转时长。

struct redisServer {
    // ...
    // 默认每秒更新一次
    unsigned lruclock:22;
    //...
}

每个redis对象有lru属性,这个属性保存了对象最后一次被命令访问的时间。

空转时间=lruclock - lru.

命令:

OBJECT IDLETIME key: 查看对象空转时长
INFO server: 可以输出server中lruclock的值

更新服务器每秒执行命令次数

redis> INFO stats
# Stats
...
instantaneous_ops_per_sec: 558
...

以上命令结果显示,在最近一秒之内,服务器大概处理了558个命令。

更新服务器内存峰值记录

服务器stat_peak_memory属性记录了服务器内存峰值大小。

redis> INFO memory
# Memory
...
used_memory_peak:6180016
used_memory_peak_human:5.89M
...

以上命令查看内存峰值大小。

处理SIGTERM信号

启动服务器时,Redis会为进程的SIGTERM信号关联处理器(一个函数),这个信号处理器负责在服务器接收到SIGTERM信号时,打开服务器状态的shutdown_asap标识。

每次serverCron函数运行时,程序都会对服务器状态的shutdown_asap属性进行检查,如果值为1,则服务器会先进行RDB持久化操作,然后关闭服务器。

管理客户端资源

主要是检查客户端:

管理数据库资源

这个部分就主要是检查服务器中的一部分数据库,删除其中的过期键,并在有需要时,对字典(hash表)进行收缩操作。

执行被延迟的BGREWRTEAOF

在服务器执行BGSAVE命令期间,如果客户端向服务器发来BGREWRITEAOF命令,那么服务器会将BGREWRITEAOF命令的执行时间延迟到BGSAVE命令执行完毕之后。

服务器的aof_rewrite_scheduled属性记录了是否延迟了BGREWRITEAOF命令。

struct redisServer {
    // ...
    // 值为1表示有BGREWRITEAOF命令被延迟了
    int aof_rewrite_scheduled;
    // ...
}

serverCron命令运行时都会检查BGSAVE和BGREWRITEAOF命令是否在执行,如果都没有执行,那么检查服务器的aof_rewrite_scheduled属性,如果值为1,那么就执行延迟的BGREWRITEAOF命令.

检查持久化操作的运行状态

服务器状态使用rdb_child_pid和aof_child_pid属性记录执行BGSAVE命令和BGREWRITEAOF命令的子进程ID,这两个属性也可以用于检查对应命令是否正在执行:

struct redisServer {
    // ...
    // 记录执行BGSAVE命令的子进程ID
    // 如果没有执行,值为-1
    pid_t rdb_child_pid;
    // 记录执行BGREWRITEAOF命令的子进程ID
    // 如果没有执行,值为-1
    pid_t aof_child_pid;
    // ...
}

serverCron函数每次执行时,程序都会检查这两个属性的值,只要其中一个属性不为-1,程序就会检查子进程是否有信号发来服务器进程(调用其他函数)。

如果这两个属性的值都为-1,那么做三个检查:

图解:

什么是Redis事件和服务器

将AOF缓冲区的内容写入AOF文件

如果服务器开启了AOF持久化功能,并且AOF缓冲区里面还有待写入的数据,那么serverCron函数会调用相应的程序,将AOF缓冲区中的内容写入到AOF文件。

关闭异步客户端

这一步,服务器会关闭输出缓冲区大小超出限制的客户端。

客户端的输入缓冲区用于把醋你客户端发送的命令请求:

typedef struct redisClient {
    // ...
    sds querybuf;
    // ...
}

这个缓冲区中保存的是协议的值。

set mKey mValue
querybuf的值为
*3\r\n$3\r\nSET\r\n$4\r\nmKey\r\n$6\r\nmValue\r\n

输入缓冲区的大小会根据内容动态的缩小或者扩大,但它的值不能超过1G,否则服务器将关闭这个客户端。

增加cronloops计数器的值

服务器状态的cronloops属性记录了serverCron函数执行的次数

struct redisServer {
    // ...
    // serverCron函数没执行一次,值就加1
    int cronloops;
    // ...
}

这个属性目前在服务器中的唯一作用,就是在复制模块中实现没执行N次就执行一次指定代码的功能。

if (cronloops % n == 0) {
    // 指定代码,这个指定的代码应该是服务器处理的一些事了,每隔多少次就处理一次
}

服务器

命令请求过程

命令请求的时候,在客户端输入命令的时候,客户端会将这个命令转换成协议,然后将协议内容发送给服务器。

协议格式

*<参数数量> CR LF
$<参数 1 的字节数量> CR LF
<参数 1 的数据> CR LF
...
$<参数 N 的字节数量> CR LF
<参数 N 的数据> CR LF

注意:CRLF就是换行 \r\n

socket发送协议

我们先来试试:

cmd:
set mKey mValue
protocal:
*3\r\n$3\r\nSET\r\n$4\r\nmKey\r\n$6\r\nmValue\r\n

然后我们直接用java socket来发送协议给服务器。

public static void main(String[] args) throws Exception {
	Socket socket = new Socket("192.168.2.11", 16379);
	//获取输出流,向socket写数据
	OutputStream outputStream = socket.getOutputStream();
	PrintWriter printWriter = new PrintWriter(outputStream);
	printWriter.print("*3\r\n$3\r\nSET\r\n$4\r\nmKey\r\n$6\r\nmValue\r\n");
	printWriter.flush();
	//关闭输出流
	socket.shutdownOutput();

	//读取服务器返回的socket的数据
	InputStream inputStream = socket.getInputStream();
	InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
	BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
	String info = "";
	String temp = null;
	while ((temp = bufferedReader.readLine()) != null) {
		info += temp;
		System.out.println("客户端接收服务端发送信息:" + info);
	}

	//关闭相对应的资源
	bufferedReader.close();
	inputStream.close();
	printWriter.close();
	outputStream.close();
	socket.close();
}

执行结果:

客户端接收服务端发送信息:+OK

Process finished with exit code 0

上服务器检查是否有值:

redis> get mKey
"mValue"

服务器初始化过程

初始化服务器状态

第一步就是创建一个strct redisServer类型的实例变量作为服务器状态,并为结构中的各个属性设置默认值。主要包括以下部分:

当初始化之后,就进入下一步,载入配置项。

载入配置项

在启动服务器时,用户可以通过给定配置参数或者指定配置文件来修改服务器的默认配置,比如

redis-server --port 16379

那么我们就通过给定配置参数的方式修改了服务器的运行端口号,当然也可以在配置文件中配置。

如果用户为某些属性设置了新值,那么服务器就使用用户指定的值来更新相应的属性,如果没有设置新值,那么就是用上一步初始化的默认值。

载入配置后,进入下一步,初始化服务器数据结构。

初始化服务器数据结构

在第一步的时候,服务器只创建了命令表一个数据结构,除了命令表之外,服务器还包括其他数据结构,比如server.clients链表,server.db数组等。

除了初始化这些数据结构之外,还需要做一些重要的设置操作,包括:

这步操作完成之后,进入下一步,还原数据库状态。

还原数据库状态

在完成率server变量的初始化之后,服务器还需要载入RDB文件或者AOF文件,并根据文件记录的内容来还原服务器的数据库状态。RDB文件的优先级没有AOF文件高!有AOF文件的话RDB文件就不会被使用了。

数据库状态还原之后,开始执行最后一步,执行事件循环。

执行事件循环

这是初始化的最后一步,服务器开始执行服务器的事件循环(loop),这个时候服务器的初始化就完成了,可以开始接受客户端的连接请求,并处理客户端发来的命令请求了。

感谢各位的阅读,以上就是“什么是Redis事件和服务器”的内容了,经过本文的学习后,相信大家对什么是Redis事件和服务器这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是亿速云,小编将为大家推送更多相关知识点的文章,欢迎关注!

推荐阅读:
  1. 什么是Tap事件和Tap点透原理
  2. HTML5服务器是如何推送事件的

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

redis

上一篇:PHP用Casbin做RBAC和RESTful权限控制的方法

下一篇:jenkin插件下载加速的使用方法

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》