Redis定时任务原理的实现

目录
  • 数据结构
  • 常见操作
    • 1.创建定时事件
    • 2.触发定时事件
    • 3.执行定时事件
  • 总结

本文主要是基于 redis 6.2 源码进行分析定时事件的数据结构和常见操作。

数据结构

在 redis 中通过 aeTimeEvent 结构来创建定时任务事件,代码如下:

/* Time event structure */
typedef struct aeTimeEvent {
    // 标识符
    long long id; /* time event identifier. */
    // 定时纳秒数
    monotime when;
    // 定时回调函数
    aeTimeProc *timeProc;
    // 注销定时器时候的回调函数
    aeEventFinalizerProc *finalizerProc;
    void *clientData;
    struct aeTimeEvent *prev;
    struct aeTimeEvent *next;
    int refcount; /* refcount to prevent timer events from being
             * freed in recursive time event calls. */
} aeTimeEvent;

常见操作

1. 创建定时事件

redis 中最重要的定时函数且是周期执行的函数,使用的是 serverCron 函数。在 redis 中由于定时任务比较少,因此并没有严格的按照过期时间来排序的,而是按照 id自增 + 头插法 来保证基本有序。

if (aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
  serverPanic("Can't create event loop timers.");
  exit(1);
}

//创建定时器对象
long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds,
        aeTimeProc *proc, void *clientData,
        aeEventFinalizerProc *finalizerProc)
{
    long long id = eventLoop->timeEventNextId++;
    aeTimeEvent *te;

    te = zmalloc(sizeof(*te));
    if (te == NULL) return AE_ERR;
    te->id = id;
    te->when = getMonotonicUs() + milliseconds * 1000;
    te->timeProc = proc;
    te->finalizerProc = finalizerProc;
    te->clientData = clientData;
    te->prev = NULL;
    // 头插法 
    te->next = eventLoop->timeEventHead;
    te->refcount = 0;
    if (te->next)
        te->next->prev = te;
    eventLoop->timeEventHead = te;
    return id;
}

2. 触发定时事件

redis 中是采用 IO 复用来进行定时任务的。

查找距离现在最近的定时事件,见 usUntilEarliestTimer

​
/* How many microseconds until the first timer should fire.
 * If there are no timers, -1 is returned.
 *
 * Note that's O(N) since time events are unsorted.
 * Possible optimizations (not needed by Redis so far, but...):
 * 1) Insert the event in order, so that the nearest is just the head.
 *    Much better but still insertion or deletion of timers is O(N).
 * 2) Use a skiplist to have this operation as O(1) and insertion as O(log(N)).
 */
static int64_t usUntilEarliestTimer(aeEventLoop *eventLoop) {
    aeTimeEvent *te = eventLoop->timeEventHead;
    if (te == NULL) return -1;

    aeTimeEvent *earliest = NULL;
    while (te) {
        if (!earliest || te->when < earliest->when)
            earliest = te;
        te = te->next;
    }

    monotime now = getMonotonicUs();
    return (now >= earliest->when) ? 0 : earliest->when - now;
}

​

这里时间复杂度可能比较高,实际中需要结合具体场景使用。

更新剩余过期时间,想想为啥呢?因为我们前面提到过,IO 复用有可能因为 IO 事件返回,所以需要更新。

if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT))
  usUntilTimer = usUntilEarliestTimer(eventLoop);

if (usUntilTimer >= 0) {
  tv.tv_sec = usUntilTimer / 1000000;
  tv.tv_usec = usUntilTimer % 1000000;
  tvp = &tv;
} else {
  if (flags & AE_DONT_WAIT) {
    // 不等待
    tv.tv_sec = tv.tv_usec = 0;
    tvp = &tv;
  } else {
    /* Otherwise we can block */
    tvp = NULL; /* wait forever */
  }
}

3. 执行定时事件

一次性的执行完直接删除,周期性的执行完在重新添加到链表。

/* Process time events */
static int processTimeEvents(aeEventLoop *eventLoop) {
  int processed = 0;
  aeTimeEvent *te;
  long long maxId;

  te = eventLoop->timeEventHead;
  maxId = eventLoop->timeEventNextId-1;
  monotime now = getMonotonicUs();
  
  // 删除定时器
  while(te) {
    long long id;
        
    // 下一轮中对事件进行删除
    /* Remove events scheduled for deletion. */
    if (te->id == AE_DELETED_EVENT_ID) {
      aeTimeEvent *next = te->next;
      /* If a reference exists for this timer event,
             * don't free it. This is currently incremented
             * for recursive timerProc calls */
      if (te->refcount) {
        te = next;
        continue;
      }
      if (te->prev)
        te->prev->next = te->next;
      else
        eventLoop->timeEventHead = te->next;
      if (te->next)
        te->next->prev = te->prev;
      if (te->finalizerProc) {
        te->finalizerProc(eventLoop, te->clientData);
        now = getMonotonicUs();
      }
      zfree(te);
      te = next;
      continue;
    }
    
    if (te->id > maxId) {
      te = te->next;
      continue;
    }

    if (te->when <= now) {
      int retval;

      id = te->id;
      te->refcount++;
      // timeProc 函数返回值 retVal 为时间事件执行的间隔
      retval = te->timeProc(eventLoop, id, te->clientData);
      te->refcount--;
      processed++;
      now = getMonotonicUs();
      if (retval != AE_NOMORE) {
        te->when = now + retval * 1000;
      } else {
        // 如果超时了,那么标记为删除
        te->id = AE_DELETED_EVENT_ID;
      }
    }
    // 执行下一个
    te = te->next;
  }
  return processed;
}

总结

优点:实现简单
缺点:如果定时任务很多,效率比较低。

到此这篇关于Redis定时任务原理的实现的文章就介绍到这了,更多相关Redis定时任务内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • PHP使用Redis队列执行定时任务实例讲解

    Redis类: <?php namespace Utils; use Phalcon\Config\Adapter\Ini as ConfigIni; class Redis{ private static $redis1; private static $session; /** * 获取一个单例的redis对象 * @param string $name * @return \Redis */ public static function getObj($name='redis1') { t

  • 基于redis实现定时任务的方法详解

    前言 业务中碰到的需求(抽象描述一下):针对不同的用户能够实现不同时间的间隔循环任务.比如在用户注册成功24小时后给用户推送相关短信等类似需求. 使用crontab?太重,且基本不现实,不可能给每一个用户在服务器上生成一个定时任务. 定时轮询?IO频繁且效率太低 想到经常的使用的redis可以设置缓存时间,应该会有过期的事件通知吧,查了一下文档,果然有相关配置,叫做"键空间事件通知".具体说明可参考官方文档. 技术栈 redis / nodeJs / koa 技术重难点 开启redis

  • Python定时从Mysql提取数据存入Redis的实现

    设计思路: 1.程序一旦run起来,python会把mysql中最近一段时间的数据全部提取出来 2.然后实例化redis类,将数据简单解析后逐条传入redis队列 3.定时器设计每天凌晨12点开始跑 ps:redis是个内存数据库,做后台消息队列的缓存时有很大的用处,有兴趣的小伙伴可以去查看相关的文档. # -*- coding:utf-8 -*- import MySQLdb import schedule import time import datetime import random i

  • Spring Boot监听Redis Key失效事件实现定时任务的示例

    业务场景 我们以订单功能为例说明下: 生成订单后一段时间不支付订单会自动关闭.最简单的想法是设置定时任务轮询,但是每个订单的创建时间不一样,定时任务的规则无法设定,如果将定时任务执行的间隔设置的过短,太影响效率. 还有一种想法,在用户进入订单界面的时候,判断时间执行相关操作.方式可能有很多,在这里介绍一种监听 Redis 键值对过期时间来实现订单自动关闭. 实现思路 在生成订单时,向 Redis 中增加一个 KV 键值对,K 为订单号,保证通过 K 能定位到数据库中的某个订单即可,V 可为任意值

  • Redis定时任务原理的实现

    目录 数据结构 常见操作 1.创建定时事件 2.触发定时事件 3.执行定时事件 总结 本文主要是基于 redis 6.2 源码进行分析定时事件的数据结构和常见操作. 数据结构 在 redis 中通过 aeTimeEvent 结构来创建定时任务事件,代码如下: /* Time event structure */ typedef struct aeTimeEvent {     // 标识符     long long id; /* time event identifier. */     //

  • 详解Redis复制原理

    前言 本文主要介绍Redis复制机制 一.配置与实践 配置 Redis实例分为主节点(master)和从节点(slave),默认情况下都是主节点.每一个从节点只能有一个主节点,但是每一个主节点可以有多个从节点(注意数量,多个从节点会导致主节点写命令多次发送从而过度消耗网络带宽,可用树状结构降低主节点负载).复制是单向的,只能从主节点复制到从节点.配置复制的方式由以下3种: 在redis-slave.conf配置文件中加入slaveof {masterHost} {masterPort} 在red

  • 解析Redis Cluster原理

    目录 一.前言 二.为什么需要Redis Cluster 三.Redis Cluster是什么 四.节点负载均衡 五.什么是一致性哈希 六.虚拟节点机制 七.Redis Cluster采用的什么算法 八.Redis Cluster如何做到高可用 8.1.集群如何进行扩容 8.2.高可用及故障转移 九.简单了解gossip协议 十.gossip协议消息类型 十一.使用gossip的优劣 十二.总结 一.前言 Sentinel集群会对Redis的主从架构中的Redis实例进行监控,一旦发现了mast

  • Redis核心原理与实践之字符串实现原理

    本文分析Redis字符串的实现原理,内容摘自新书<Redis核心原理与实践>.这本书深入地分析了Redis常用特性的内部机制与实现方式,内容源自对Redis源码的分析,并从中总结出设计思路.实现原理.通过阅读本书,读者可以快速.轻松地了解Redis的内部运行机制. Redis是一个键值对数据库(key-value DB),下面是一个简单的Redis的命令: > SET msg "hello wolrd" 该命令将键"msg".值"hell

  • Redis核心原理详细解说

    目录 1.Redis为什么这么快 2.Redis网络模型 3.Redis数据结构 4.Redis持久化 RDB快照(snapshot) AOF(append-only file) RDB与AOF区别 Redis数据备份策略 5.Redis管道(Pipeline) 6.Redis使用lua脚本 7.Redis分布式锁 8.Redis主从架构 9.Redis哨兵架构 10.Redis集群 11.Redis优化 12.Redis问题 缓存穿透 缓存失效(击穿) 缓存雪崩 1.Redis为什么这么快 C

  • Redis Cluster原理及配置详解

    目录 Redis Cluster 原理说的头头是道,这些配置不懂就是纸上谈兵 cluster-enabled cluster-config-file cluster-node-timeout cluster-port cluster-replica-validity-factor cluster-migration-barrier cluster-require-full-coverage cluster-replica-no-failover cluster-allow-reads-when-

  • Java定时任务原理详解

    目录 序章 一.Scheduled 1.1 使用方法 1.2 源码分析 二.QUARTZ 2.1 使用方法 2.2 源码分析 序章 定时任务实现方式 当下,java编码过程中,实现定时任务的方式主要以以下两种为主 spring框架的@Scheduled quzrtz框架 网络上关于这两种框架的实践和配置相关的教程很多,这里不再赘述. 本文主要就二者的框架原理实现做一个入门引导,为了解深层实现细节做一定的铺垫. 本文源码版本 spring-context-3.2.18.RELEASE.jar qu

  • Redis内存碎片原理深入分析

    目录 前言 释放的内存去了哪里? 什么是内存碎片? 什么导致内存碎片? 如何解决? 总结 前言 我们先来看一个问题, 假设Redis实例保存了5GB的数据,现在删除了2GB的数据,那么Redis进程占用的内存会不会减少呢? 答案是:它可能仍然占用大约5GB内存,即使Redis数据只占用大约3GB. 如果maxmemory不设置该参数,Redis不会触发内存淘汰策略删除数据. Redis会继续为新写入的数据分配内存.分配失败会导致应用程序报错,当然不会导致宕机. 注:设置maxmemory参数,执

  • redis主从复制原理的深入讲解

    前言 Redis持久化保证了即使redis服务重启也不会丢失数据,因为redis服务重启后会将硬盘上持久化的数据恢复到内存中,但是当redis服务器的硬盘损坏了可能会导致数据丢失,如果通过redis的主从复制机制就可以避免这种单点故障. 本文主要针对redis主从复制的原理进行了讲解,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧 1.复制过程 2.数据间的同步 3.全量复制 4.部分复制 5.心跳 6.异步复制 1.复制过程 从节点执行 slaveof 命令. 从节点只是保存了

  • Redis字符串原理的深入理解

    前言 来掘进都有两年多了一直当个小透明,今天终于发一次文章了. 最近在看 Redis,感觉收获很多,写篇博客记录一下. Redis 有五种基础数据结构:string,list,set,zset,hash.其中 string是最最最简单的也是最常用的.这个数据类型虽然简单但是内部的结构设计却很是精致. 基本介绍 相比于 Java,在 Redis 中 string 是可以修改的,是动态字符串(Simple Dynamic String 简称 SDS)他的内部结构更像是一个 ArrayList,维护一

随机推荐