百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

Redis 事件机制详解(redis 事务操作的常用命令)

mhr18 2024-10-23 11:34 27 浏览 0 评论

 来源:公众号程序员历小冰 ,
 作者 历小冰



Redis 采用事件驱动机制来处理大量的网络IO。它并没有使用 libevent 或者 libev 这样的成熟开源方案,而是自己实现一个非常简洁的事件驱动库 ae_event。

Redis中的事件驱动库只关注网络IO,以及定时器。该事件库处理下面两类事件:

  • 文件事件(file event):用于处理 Redis 服务器和客户端之间的网络IO。
  • 时间事件(time eveat):Redis 服务器中的一些操作(比如serverCron函数)需要在给定的时间点执行,而时间事件就是处理这类定时操作的。

事件驱动库的代码主要是在src/ae.c中实现的,其示意图如下所示。


aeEventLoop是整个事件驱动的核心,它管理着文件事件表和时间事件列表,不断地循环处理着就绪的文件事件和到期的时间事件。下面我们就先分别介绍文件事件和时间事件,然后讲述相关的 aeEventLoop源码实现。

文件事件

Redis基于Reactor模式开发了自己的网络事件处理器,也就是文件事件处理器。文件事件处理器使用IO多路复用技术,同时监听多个套接字,并为套接字关联不同的事件处理函数。当套接字的可读或者可写事件触发时,就会调用相应的事件处理函数。

Redis 使用的IO多路复用技术主要有:select、 epoll、 evport和 kqueue等。每个IO多路复用函数库在 Redis 源码中都对应一个单独的文件,比如aeselect.c,aeepoll.c, ae_kqueue.c等。Redis 会根据不同的操作系统,按照不同的优先级选择多路复用技术。事件响应框架一般都采用该架构,比如 netty 和 libevent。


如下图所示,文件事件处理器有四个组成部分,它们分别是套接字、I/O多路复用程序、文件事件分派器以及事件处理器。


文件事件是对套接字操作的抽象,每当一个套接字准备好执行 accept、read、write和 close 等操作时,就会产生一个文件事件。因为 Redis 通常会连接多个套接字,所以多个文件事件有可能并发的出现。

I/O多路复用程序负责监听多个套接字,并向文件事件派发器传递那些产生了事件的套接字。

尽管多个文件事件可能会并发地出现,但I/O多路复用程序总是会将所有产生的套接字都放到同一个队列(也就是后文中描述的 aeEventLoop的 fired就绪事件表)里边,然后文件事件处理器会以有序、同步、单个套接字的方式处理该队列中的套接字,也就是处理就绪的文件事件。


所以,一次 Redis 客户端与服务器进行连接并且发送命令的过程如上图所示。

  • 客户端向服务端发起建立 socket 连接的请求,那么监听套接字将产生 AEREADABLE 事件,触发连接应答处理器执行。处理器会对客户端的连接请求进行应答,然后创建客户端套接字,以及客户端状态,并将客户端套接字的 AEREADABLE 事件与命令请求处理器关联。
  • 客户端建立连接后,向服务器发送命令,那么客户端套接字将产生 AE_READABLE 事件,触发命令请求处理器执行,处理器读取客户端命令,然后传递给相关程序去执行。
  • 执行命令获得相应的命令回复,为了将命令回复传递给客户端,服务器将客户端套接字的 AEWRITEABLE 事件与命令回复处理器关联。当客户端试图读取命令回复时,客户端套接字产生 AEWRITEABLE 事件,触发命令回复处理器将命令回复全部写入到套接字中。

时间事件

Redis 的时间事件分为以下两类:

  • 定时事件:让一段程序在指定的时间之后执行一次。
  • 周期性事件:让一段程序每隔指定时间就执行一次。

Redis 的时间事件的具体定义结构如下所示。

一个时间事件是定时事件还是周期性事件取决于时间处理器的返回值:

  • 如果返回值是 AE_NOMORE,那么这个事件是一个定时事件,该事件在达到后删除,之后不会再重复。
  • 如果返回值是非 AE_NOMORE 的值,那么这个事件为周期性事件,当一个时间事件到达后,服务器会根据时间处理器的返回值,对时间事件的 when 属性进行更新,让这个事件在一段时间后再次达到。

Redis 将所有时间事件都放在一个无序链表中,每次 Redis 会遍历整个链表,查找所有已经到达的时间事件,并且调用相应的事件处理器。

介绍完文件事件和时间事件,我们接下来看一下 aeEventLoop的具体实现。

创建事件管理器

Redis 服务端在其初始化函数 initServer中,会创建事件管理器 aeEventLoop对象。

函数 aeCreateEventLoop将创建一个事件管理器,主要是初始化 aeEventLoop的各个属性值,比如 events、 fired、 timeEventHead和 apidata:

  • 首先创建 aeEventLoop对象。
  • 初始化未就绪文件事件表、就绪文件事件表。 events指针指向未就绪文件事件表、 fired指针指向就绪文件事件表。表的内容在后面添加具体事件时进行初变更。
  • 初始化时间事件列表,设置 timeEventHead和 timeEventNextId属性。
  • 调用 aeApiCreate 函数创建 epoll实例,并初始化 apidata。

aeApiCreate 函数首先创建了 aeApiState对象,初始化了epoll就绪事件表;然后调用 epoll_create创建了 epoll实例,最后将该 aeApiState赋值给 apidata属性。

aeApiState对象中 epfd存储 epoll的标识, events是一个 epoll就绪事件数组,当有 epoll事件发生时,所有发生的 epoll事件和其描述符将存储在这个数组中。这个就绪事件数组由应用层开辟空间、内核负责把所有发生的事件填充到该数组。

创建文件事件

aeFileEvent是文件事件结构,对于每一个具体的事件,都有读处理函数和写处理函数等。Redis 调用 aeCreateFileEvent函数针对不同的套接字的读写事件注册对应的文件事件。

比如说,Redis 进行主从复制时,从服务器需要主服务器建立连接,它会发起一个 socekt连接,然后调用 aeCreateFileEvent函数针对发起的socket的读写事件注册了对应的事件处理器,也就是 syncWithMaster函数。

aeCreateFileEvent的参数 fd指的是具体的 socket套接字, proc指 fd产生事件时,具体的处理函数, clientData则是回调处理函数时需要传入的数据。aeCreateFileEvent主要做了三件事情:

  • 以 fd为索引,在 events未就绪事件表中找到对应事件。
  • 调用 aeApiAddEvent函数,该事件注册到具体的底层 I/O 多路复用中,本例为epoll。
  • 填充事件的回调、参数、事件类型等参数。

如上文所说,Redis 基于的底层 I/O 多路复用库有多套,所以 aeApiAddEvent也有多套实现,下面的源码是 epoll下的实现。其核心操作就是调用 epoll的 epoll_ctl函数来向 epoll注册响应事件。有关 epoll相关的知识可以看一下《Java NIO源码分析》

事件处理

因为 Redis 中同时存在文件事件和时间事件两个事件类型,所以服务器必须对这两个事件进行调度,决定何时处理文件事件,何时处理时间事件,以及如何调度它们。

aeMain函数以一个无限循环不断地调用 aeProcessEvents函数来处理所有的事件。

下面是 aeProcessEvents的伪代码,它会首先计算距离当前时间最近的时间事件,以此计算一个超时时间;然后调用 aeApiPoll函数去等待底层的I/O多路复用事件就绪;aeApiPoll函数返回之后,会处理所有已经产生文件事件和已经达到的时间事件。

与 aeApiAddEvent类似, aeApiPoll也有多套实现,它其实就做了两件事情,调用 epoll_wait阻塞等待 epoll的事件就绪,超时时间就是之前根据最快达到时间事件计算而来的超时时间;然后将就绪的 epoll事件转换到fired就绪事件。aeApiPoll就是上文所说的I/O多路复用程序。具体过程如下图所示。

processFileEvent是处理就绪文件事件的伪代码,也是上文所述的文件事件分派器,它其实就是遍历 fired就绪事件表,然后根据对应的事件类型来调用事件中注册的不同处理器,读事件调用 rfileProc,而写事件调用 wfileProc。

而 processTimeEvents是处理时间事件的函数,它会遍历 aeEventLoop的事件事件列表,如果时间事件到达就执行其 timeProc函数,并根据函数的返回值是否等于 AE_NOMORE来决定该时间事件是否是周期性事件,并修改器到达时间。

删除事件

当不在需要某个事件时,需要把事件删除掉。例如: 如果fd同时监听读事件、写事件。当不在需要监听写事件时,可以把该fd的写事件删除。

aeDeleteEventLoop函数的执行过程总结为以下几个步骤 1、根据 fd在未就绪表中查找到事件 2、取消该 fd对应的相应事件标识符 3、调用 aeApiFree函数,内核会将epoll监听红黑树上的相应事件监听取消。

相关推荐

订单超时自动取消业务的 N 种实现方案,从原理到落地全解析

在分布式系统架构中,订单超时自动取消机制是保障业务一致性的关键组件。某电商平台曾因超时处理机制缺陷导致日均3000+订单库存锁定异常,直接损失超50万元/天。本文将从技术原理、实现细节、...

使用Spring Boot 3开发时,如何选择合适的分布式技术?

作为互联网大厂的后端开发人员,当你满怀期待地用上SpringBoot3,准备在项目中大显身手时,却发现一个棘手的问题摆在面前:面对众多分布式技术,究竟该如何选择,才能让SpringBoot...

数据库内存爆满怎么办?99%的程序员都踩过这个坑!

你的数据库是不是又双叒叕内存爆满了?!服务器监控一片红色警告,老板在群里@所有人,运维同事的电话打爆了手机...这种场景是不是特别熟悉?别慌!作为一个在数据库优化这条路上摸爬滚打了10年的老司机,今天...

springboot利用Redisson 实现缓存与数据库双写不一致问题

使用了Redisson来操作Redis分布式锁,主要功能是从缓存和数据库中获取商品信息,以下是针对并发时更新缓存和数据库带来不一致问题的解决方案1.基于读写锁和删除缓存策略在并发更新场景下,...

外贸独立站数据库炸了?对象缓存让你起死回生

上周黑五,一个客户眼睁睁看着服务器CPU飙到100%——每次页面加载要查87次数据库。这让我想起2024年Pantheon的测试:Redis缓存能把WooCommerce查询速度提升20倍。跨境电商最...

手把手教你在 Spring Boot3 里纯编码实现自定义分布式锁

为什么要自己实现分布式锁?你是不是早就受够了引入各种第三方依赖时的繁琐?尤其是分布式锁这块,每次集成Redisson或者Zookeeper,都得额外维护一堆配置,有时候还会因为版本兼容问题头疼半...

如何设计一个支持百万级实时数据推送的WebSocket集群架构?

面试解答:要设计一个支持百万级实时数据推送的WebSocket集群架构,需从**连接管理、负载均衡、水平扩展、容灾恢复**四个维度切入:连接层设计-**长连接优化**:采用Netty或Und...

Redis数据结构总结——面试最常问到的知识点

Redis作为主流的nosql存储,面试时经常会问到。其主要场景是用作缓存,分布式锁,分布式session,消息队列,发布订阅等等。其存储结构主要有String,List,Set,Hash,Sort...

skynet服务的缺陷 lua死循环

服务端高级架构—云风的skynet这边有一个关于云风skynet的视频推荐给大家观看点击就可以观看了!skynet是一套多人在线游戏的轻量级服务端框架,使用C+Lua开发。skynet的显著优点是,...

七年Java开发的一路辛酸史:分享面试京东、阿里、美团后的心得

前言我觉得有一个能够找一份大厂的offer的想法,这是很正常的,这并不是我们的饭后谈资而是每个技术人的追求。像阿里、腾讯、美团、字节跳动、京东等等的技术氛围与技术规范度还是要明显优于一些创业型公司...

mysql mogodb es redis数据库之间的区别

1.MySQL应用场景概念:关系型数据库,基于关系模型,使用表和行存储数据。优点:支持ACID事务,数据具有很高的一致性和完整性。缺点:垂直扩展能力有限,需要分库分表等方式扩展。对于复杂的查询和大量的...

redis,memcached,nginx网络组件

1.理解阻塞io,非阻塞io,同步io,异步io的区别2.理解BIO和AIO的区别io多路复用只负责io检测,不负责io操作阻塞io中的write,能写多少是多少,只要写成功就返回,譬如准备写500字...

SpringBoot+Vue+Redis实现验证码功能

一个小时只允许发三次验证码。一次验证码有效期二分钟。SpringBoot整合Redis...

AWS MemoryDB 可观测最佳实践

AWSMemoryDB介绍AmazonMemoryDB是一种完全托管的、内存中数据存储服务,专为需要极低延迟和高吞吐量的应用程序而设计。它与Redis和Memcached相似,但具有更...

从0构建大型AI推荐系统:实时化引擎从工具到生态的演进

在AI浪潮席卷各行各业的今天,推荐系统正从幕后走向前台,成为用户体验的核心驱动力。本文将带你深入探索一个大型AI推荐系统从零起步的全过程,揭示实时化引擎如何从单一工具演进为复杂生态的关键路径。无论你是...

取消回复欢迎 发表评论: