Redis的实现四:事件循环和计时器

简介: 这篇文章详细解释了在Redis实现中如何通过引入事件循环和计时器来处理超时情况,包括使用链表数据结构管理计时器、更新事件循环以及处理计时器触发的事件。
   我们的服务器缺少了一个**内容**:**超时**。每个网络应用程序都需要处理超时,因为网络的另一边可能会消失。不要只进行持续的IO操作,如读/写需要超时,但启动空闲的TCP连接也是一个好主意。要实现超时,必须修改事件循环,因为轮询是唯一被阻塞的东西。

我们的代码如下:

int rv=poll(poll_args.data(),(nfds_t)poll_args.size(),1000);
     poll系统调用接受一个timeout参数,该参数规定了用于poll系统调用的时间上限。超时值目前是1000毫秒的任意值。如果我们根据计时器设置超时值,poll应该在过期时唤醒,或在此之前唤醒;然后我们就有机会在适当的时候启动计时器。

    问题是我们可能有多个计时器,poll的超时值应该是最近的计时器的超时值。需要一些数据结构来查找最近的计时器。堆数据结构是查找最小/最大值的常用选择,通常用于此目的。此外,还可以使用任何用于排序的数据结构。例如,我们可以使用AVL树来排序计时器,并可能扩展树来跟踪最小值。

    让我们从添加计时器来踢出空闲的TCP连接开始。对于每个连接都有一个计时器,设置为一个固定的超时,每次在连接上有IO活动时,计时器都会更新为一个固定的超时。注意,当我们更新计时器时,它变成了最遥远的一个;因此,我们可以利用这一事实来简化数据结构;一个简单的链表足以保持计时器的顺序:新的或更新的计时器只是到列表的末尾,列表保持有序的顺序。同样,链表上的操作是O(1),哪个比排序数据结构更好

定义链表是一个微不足道的任务:

struct DList {
   DList *prev = NULL;
   DList *next = NULL;
};
inline void dlist_init(DList *node) {
   node->prev = node->next = node;
}
inline bool dlist_empty(DList *node) {
   return node->next == node;
}
inline void dlist_detach(DList *node) {
   DList *prev = node->prev;
   DList *next = node->next;
   prev->next = next;
   next->prev = prev;
}
inline void dlist_insert_before(DList *target, DList *rookie) {
   DList *prev = target->prev;
   prev->next = rookie;
   rookie->prev = prev;
   rookie->next = target;
   target->prev = rookie;
}

get_monotonic_usec是获取时间的函数。注意时间戳必须是单调的。时间戳向后跳转会给计算机系统带来各种各样的麻烦。

static uint64_t get_monotonic_usec() {
   timespec tv = {0, 0};
   clock_gettime(CLOCK_MONOTONIC, &tv);
   return uint64_t(tv.tv_sec) * 1000000 + tv.tv_nsec / 1000;
}

下一步是将列表添加到服务器和连接结构中。

static struct {
   HMap db;
   // a map of all client connections, keyed by fd
   std::vector<Conn *> fd2conn;
   // timers for idle connections
   DList idle_list;
} g_data;
struct Conn {
   int fd = -1;
   uint32_t state = 0; // either STATE_REQ or STATE_RES
   // buffer for reading
   size_t rbuf_size = 0;
   uint8_t rbuf[4 + k_max_msg];
   // buffer for writing
   size_t wbuf_size = 0;
   size_t wbuf_sent = 0;
   uint8_t wbuf[4 + k_max_msg];
   uint64_t idle_start = 0;
   // timer
   DList idle_list;
};

修改后的事件循环概述:

int main() {
   // some initializations
   dlist_init(&g_data.idle_list);
   int fd = socket(AF_INET, SOCK_STREAM, 0);
   // bind, listen & other miscs
   // code omitted...
   // the event loop
   std::vector<struct pollfd> poll_args;
   while (true) {
       // prepare the arguments of the poll()
       // code omitted...
       // poll for active fds
      int timeout_ms = (int)next_timer_ms();
      int rv = poll(poll_args.data(), (nfds_t)poll_args.size(), timeout_ms);
        if (rv < 0) {
         die("poll");
         }
   // process active connections
for (size_t i = 1; i < poll_args.size(); ++i) {
    if (poll_args[i].revents) {
       Conn *conn = g_data.fd2conn[poll_args[i].fd];
        connection_io(conn);
        if (conn->state == STATE_END) {
        // client closed normally, or something bad happened.
        // destroy this connection
        conn_done(conn);
        }
      }
}
    // handle timers
    process_timers();
    // try to accept a new connection if the listening fd is active
if (poll_args[0].revents) {
(void)accept_new_conn(fd);
     }
 }
  return 0;
}

修改了几件事:
1. poll的超时参数由next_timer_ms函数计算。
2. 销毁连接的代码被移到了conn_done函数中。
3. 添加了用于触发计时器的process_timers函数。
4. 计时器在connection_io中更新,在accept_new_conn中初始化。

目录
相关文章
|
存储 缓存 NoSQL
Redis 事件循环函数serverCron
Redis 事件循环函数serverCron
164 0
Redis 事件循环函数serverCron
|
1月前
|
存储 缓存 NoSQL
数据的存储--Redis缓存存储(一)
数据的存储--Redis缓存存储(一)
|
1月前
|
存储 缓存 NoSQL
数据的存储--Redis缓存存储(二)
数据的存储--Redis缓存存储(二)
数据的存储--Redis缓存存储(二)
|
1月前
|
消息中间件 缓存 NoSQL
Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。
【10月更文挑战第4天】Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。随着数据增长,有时需要将 Redis 数据导出以进行分析、备份或迁移。本文详细介绍几种导出方法:1)使用 Redis 命令与重定向;2)利用 Redis 的 RDB 和 AOF 持久化功能;3)借助第三方工具如 `redis-dump`。每种方法均附有示例代码,帮助你轻松完成数据导出任务。无论数据量大小,总有一款适合你。
77 6
|
1月前
|
缓存 NoSQL 关系型数据库
redis和缓存及相关问题和解决办法 什么是缓存预热、缓存穿透、缓存雪崩、缓存击穿
本文深入探讨了Redis缓存的相关知识,包括缓存的概念、使用场景、可能出现的问题(缓存预热、缓存穿透、缓存雪崩、缓存击穿)及其解决方案。
186 0
redis和缓存及相关问题和解决办法 什么是缓存预热、缓存穿透、缓存雪崩、缓存击穿
|
13天前
|
缓存 NoSQL 关系型数据库
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
本文详解缓存雪崩、缓存穿透、缓存并发及缓存预热等问题,提供高可用解决方案,帮助你在大厂面试和实际工作中应对这些常见并发场景。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
|
15天前
|
存储 缓存 NoSQL
【赵渝强老师】基于Redis的旁路缓存架构
本文介绍了引入缓存后的系统架构,通过缓存可以提升访问性能、降低网络拥堵、减轻服务负载和增强可扩展性。文中提供了相关图片和视频讲解,并讨论了数据库读写分离、分库分表等方法来减轻数据库压力。同时,文章也指出了缓存可能带来的复杂度增加、成本提高和数据一致性问题。
【赵渝强老师】基于Redis的旁路缓存架构
|
8天前
|
缓存 NoSQL PHP
Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出
本文深入探讨了Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出。文章还介绍了Redis在页面缓存、数据缓存和会话缓存等应用场景中的使用,并强调了缓存数据一致性、过期时间设置、容量控制和安全问题的重要性。
24 5
|
23天前
|
缓存 NoSQL Redis
Redis 缓存使用的实践
《Redis缓存最佳实践指南》涵盖缓存更新策略、缓存击穿防护、大key处理和性能优化。包括Cache Aside Pattern、Write Through、分布式锁、大key拆分和批量操作等技术,帮助你在项目中高效使用Redis缓存。
129 22
|
22天前
|
缓存 NoSQL 中间件
redis高并发缓存中间件总结!
本文档详细介绍了高并发缓存中间件Redis的原理、高级操作及其在电商架构中的应用。通过阿里云的角度,分析了Redis与架构的关系,并展示了无Redis和使用Redis缓存的架构图。文档还涵盖了Redis的基本特性、应用场景、安装部署步骤、配置文件详解、启动和关闭方法、systemctl管理脚本的生成以及日志警告处理等内容。适合初学者和有一定经验的技术人员参考学习。
121 7
下一篇
无影云桌面