经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 数据库/运维 » Redis » 查看文章
Redis定时任务原理的实现
来源:jb51  时间:2022/3/7 11:13:16  对本文有异议

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

数据结构

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

  1. /* Time event structure */
  2. typedef struct aeTimeEvent {
  3. ? ? // 标识符
  4. ? ? long long id; /* time event identifier. */
  5. ? ? // 定时纳秒数
  6. ? ? monotime when;
  7. ? ? // 定时回调函数
  8. ? ? aeTimeProc *timeProc;
  9. ? ? // 注销定时器时候的回调函数
  10. ? ? aeEventFinalizerProc *finalizerProc;
  11. ? ? void *clientData;
  12. ? ? struct aeTimeEvent *prev;
  13. ? ? struct aeTimeEvent *next;
  14. ? ? int refcount; /* refcount to prevent timer events from being
  15. ? ?? ??? ? ? * freed in recursive time event calls. */
  16. } aeTimeEvent;

常见操作

1. 创建定时事件

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

  1. if (aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
  2. ? serverPanic("Can't create event loop timers.");
  3. ? exit(1);
  4. }
  5.  
  6. //创建定时器对象
  7. long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds,
  8. ? ? ? ? aeTimeProc *proc, void *clientData,
  9. ? ? ? ? aeEventFinalizerProc *finalizerProc)
  10. {
  11. ? ? long long id = eventLoop->timeEventNextId++;
  12. ? ? aeTimeEvent *te;
  13.  
  14. ? ? te = zmalloc(sizeof(*te));
  15. ? ? if (te == NULL) return AE_ERR;
  16. ? ? te->id = id;
  17. ? ? te->when = getMonotonicUs() + milliseconds * 1000;
  18. ? ? te->timeProc = proc;
  19. ? ? te->finalizerProc = finalizerProc;
  20. ? ? te->clientData = clientData;
  21. ? ? te->prev = NULL;
  22. ? ? // 头插法?
  23. ? ? te->next = eventLoop->timeEventHead;
  24. ? ? te->refcount = 0;
  25. ? ? if (te->next)
  26. ? ? ? ? te->next->prev = te;
  27. ? ? eventLoop->timeEventHead = te;
  28. ? ? return id;
  29. }

2. 触发定时事件

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

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

  1. ?
  2. /* How many microseconds until the first timer should fire.
  3. ?* If there are no timers, -1 is returned.
  4. ?*
  5. ?* Note that's O(N) since time events are unsorted.
  6. ?* Possible optimizations (not needed by Redis so far, but...):
  7. ?* 1) Insert the event in order, so that the nearest is just the head.
  8. ?* ? ?Much better but still insertion or deletion of timers is O(N).
  9. ?* 2) Use a skiplist to have this operation as O(1) and insertion as O(log(N)).
  10. ?*/
  11. static int64_t usUntilEarliestTimer(aeEventLoop *eventLoop) {
  12. ? ? aeTimeEvent *te = eventLoop->timeEventHead;
  13. ? ? if (te == NULL) return -1;
  14.  
  15. ? ? aeTimeEvent *earliest = NULL;
  16. ? ? while (te) {
  17. ? ? ? ? if (!earliest || te->when < earliest->when)
  18. ? ? ? ? ? ? earliest = te;
  19. ? ? ? ? te = te->next;
  20. ? ? }
  21.  
  22. ? ? monotime now = getMonotonicUs();
  23. ? ? return (now >= earliest->when) ? 0 : earliest->when - now;
  24. }
  25.  
  26. ?

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

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

  1. if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT))
  2. ? usUntilTimer = usUntilEarliestTimer(eventLoop);
  3.  
  4. if (usUntilTimer >= 0) {
  5. ? tv.tv_sec = usUntilTimer / 1000000;
  6. ? tv.tv_usec = usUntilTimer % 1000000;
  7. ? tvp = &tv;
  8. } else {
  9. ? if (flags & AE_DONT_WAIT) {
  10. ? ? // 不等待
  11. ? ? tv.tv_sec = tv.tv_usec = 0;
  12. ? ? tvp = &tv;
  13. ? } else {
  14. ? ? /* Otherwise we can block */
  15. ? ? tvp = NULL; /* wait forever */
  16. ? }
  17. }

3. 执行定时事件

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

  1. /* Process time events */
  2. static int processTimeEvents(aeEventLoop *eventLoop) {
  3. ? int processed = 0;
  4. ? aeTimeEvent *te;
  5. ? long long maxId;
  6.  
  7. ? te = eventLoop->timeEventHead;
  8. ? maxId = eventLoop->timeEventNextId-1;
  9. ? monotime now = getMonotonicUs();
  10. ??
  11. ? // 删除定时器
  12. ? while(te) {
  13. ? ? long long id;
  14. ?? ??? ?
  15. ? ? // 下一轮中对事件进行删除
  16. ? ? /* Remove events scheduled for deletion. */
  17. ? ? if (te->id == AE_DELETED_EVENT_ID) {
  18. ? ? ? aeTimeEvent *next = te->next;
  19. ? ? ? /* If a reference exists for this timer event,
  20. ? ? ? ? ? ? ?* don't free it. This is currently incremented
  21. ? ? ? ? ? ? ?* for recursive timerProc calls */
  22. ? ? ? if (te->refcount) {
  23. ? ? ? ? te = next;
  24. ? ? ? ? continue;
  25. ? ? ? }
  26. ? ? ? if (te->prev)
  27. ? ? ? ? te->prev->next = te->next;
  28. ? ? ? else
  29. ? ? ? ? eventLoop->timeEventHead = te->next;
  30. ? ? ? if (te->next)
  31. ? ? ? ? te->next->prev = te->prev;
  32. ? ? ? if (te->finalizerProc) {
  33. ? ? ? ? te->finalizerProc(eventLoop, te->clientData);
  34. ? ? ? ? now = getMonotonicUs();
  35. ? ? ? }
  36. ? ? ? zfree(te);
  37. ? ? ? te = next;
  38. ? ? ? continue;
  39. ? ? }
  40. ? ??
  41. ? ? if (te->id > maxId) {
  42. ? ? ? te = te->next;
  43. ? ? ? continue;
  44. ? ? }
  45.  
  46. ? ? if (te->when <= now) {
  47. ? ? ? int retval;
  48.  
  49. ? ? ? id = te->id;
  50. ? ? ? te->refcount++;
  51. ? ? ? // timeProc 函数返回值 retVal 为时间事件执行的间隔
  52. ? ? ? retval = te->timeProc(eventLoop, id, te->clientData);
  53. ? ? ? te->refcount--;
  54. ? ? ? processed++;
  55. ? ? ? now = getMonotonicUs();
  56. ? ? ? if (retval != AE_NOMORE) {
  57. ? ? ? ? te->when = now + retval * 1000;
  58. ? ? ? } else {
  59. ? ? ? ? // 如果超时了,那么标记为删除
  60. ? ? ? ? te->id = AE_DELETED_EVENT_ID;
  61. ? ? ? }
  62. ? ? }
  63. ? ? // 执行下一个
  64. ? ? te = te->next;
  65. ? }
  66. ? return processed;
  67. }

总结

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

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

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号