Nginx的定时事件的实现(timer)

在前面的文章里面就说到了在事件循环中除了要处理所有的从epoll中获取的事件之外,还要处理一些timer事件,这篇文章就讲讲Nginx的timer是如何实现的。

在讲Nginx的实现之前,我们可以先回顾一下linux的定时器的实现。在linux中通过每次系统定时器时钟的中断的中断处理程序来设置相应的软中断位,该软中断的中断处理程序目的就是为了扫描系统中所有挂起的定时器,如果他们已经超时的话,那么就调用他们相应的函数,这样说白了就是利用中断来实现定时器。

而在Nginx中,timer是自己实现的,而且实现的方法完全不同,而是通过红黑树来维护所有的timer节点,在worker进程的每一次循环中都会调用ngx_process_events_and_timers函数,在该函数中就会调用处理定时器的函数ngx_event_expire_timers,每次该函数都不断的从红黑树中取出时间值最小的,查看他们是否已经超时,然后执行他们的函数,直到取出的节点的时间没有超时为止。其实在Nginx的运行环境中,这种实现方式可能比linux本身的实现方式更为高效。

首先我们来看Nginx的定时器的初始化,在ngx_event_process_init函数中( Ngx_event.c):

    /*初始化计时器,此处将会创建起一颗红黑色,来维护计时器。*/
if (ngx_event_timer_init(cycle->log) == NGX_ERROR) {
return NGX_ERROR;
}

嗯,就是这个函数,那么接下来我们来看这个函数吧,

//timer的初始化
ngx_int_t
ngx_event_timer_init(ngx_log_t *log)
{
//初始化红黑树
ngx_rbtree_init(&ngx_event_timer_rbtree, &ngx_event_timer_sentinel,
ngx_rbtree_insert_timer_value); #if (NGX_THREADS) if (ngx_event_timer_mutex) {
ngx_event_timer_mutex->log = log;
return NGX_OK;
} ngx_event_timer_mutex = ngx_mutex_init(log, );
if (ngx_event_timer_mutex == NULL) {
return NGX_ERROR;
} #endif
return NGX_OK;
}

其实该函数非常的简单,说白了就是直接调用ngx_rbtree_init函数来初始化一颗红黑树而已(红黑树是Nginx自己实现的)。

接下来来看如何定义一个timer事件:

//将一个事件加入到红黑树当中,它的超时未timer时间
static ngx_inline void
ngx_event_add_timer
(ngx_event_t *ev, ngx_msec_t timer) //timer说白了就是一个int的值,表示超时的事件,用于表示红黑树节点的key
{
ngx_msec_t key;
ngx_msec_int_t diff; key = ngx_current_msec + timer; //表示该event的超时时间,为当前时间的值加上超时变量 if (ev->timer_set) { /*
* Use a previous timer value if difference between it and a new
* value is less than NGX_TIMER_LAZY_DELAY milliseconds: this allows
* to minimize the rbtree operations for fast connections.
*/ diff = (ngx_msec_int_t) (key - ev->timer.key); if (ngx_abs(diff) < NGX_TIMER_LAZY_DELAY) {
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, ,
"event timer: %d, old: %M, new: %M",
ngx_event_ident(ev->data), ev->timer.key, key);
return;
} ngx_del_timer(ev);
} ev->timer.key = key; ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, ,
"event timer add: %d: %M:%M",
ngx_event_ident(ev->data), timer, ev->timer.key); ngx_mutex_lock(ngx_event_timer_mutex); ngx_rbtree_insert(&ngx_event_timer_rbtree, &ev->timer); //事件的timer域插入到红黑树当中 ngx_mutex_unlock(ngx_event_timer_mutex); ev->timer_set = ;
}

该函数用于将事件加入到红黑树中,首先设置超时时间,也就是当前的时间加上传进来的超时时间。然后再将timer域加入到红黑树中就可以了,这里timer域的定义说白了是一棵红黑树节点。然后还有一个函数ngx_event_del_timer,它用于将某个事件从红黑树当中移除。

接下来我们来看看Nginx到底是如何处理定时事件的。在ngx_process_events_and_timers函数中首先会有如下的代码

    if (ngx_timer_resolution) {
timer = NGX_TIMER_INFINITE;
flags = ; } else {
timer = ngx_event_find_timer(); //找到当前红黑树当中的最小的事件,传递给epoll的wait,保证epoll可以该时间内可以超时,可以使得超时的事件得到处理
flags = NGX_UPDATE_TIME; #if (NGX_THREADS) if (timer == NGX_TIMER_INFINITE || timer > ) {
timer = ;
} #endif
}

该段代码主要是调用ngx_event_find_timer函数获取当前红黑树中超时时间最小的一个节点,该函数的定义如下:

//用于返回当前红黑树当中的超时时间,说白了就是返回红黑树中最左边的元素的超时时间
ngx_msec_t
ngx_event_find_timer(void)
{
ngx_msec_int_t timer;
ngx_rbtree_node_t *node, *root, *sentinel; if (ngx_event_timer_rbtree.root == &ngx_event_timer_sentinel) {
return NGX_TIMER_INFINITE;
} ngx_mutex_lock(ngx_event_timer_mutex); root = ngx_event_timer_rbtree.root;
sentinel = ngx_event_timer_rbtree.sentinel; node = ngx_rbtree_min(root, sentinel); //找到红黑树中key最小的节点 ngx_mutex_unlock(ngx_event_timer_mutex); timer = (ngx_msec_int_t) (node->key - ngx_current_msec); return (ngx_msec_t) (timer > ? timer : );
}

该函数其实还是很简单的,说白了就是找到红黑树中key的值最小的节点,然后返回它还剩下的超时时间就可以了。在ngx_process_events_and_timers函数中之所以要获取这个值,是为了为epoll的wait提供一个超时时间,以防止epoll的wait阻塞时间太长,影响了timer的处理。接着或有如下的代码:

    /*delta是上文对epoll wait事件的耗时统计,存在毫秒级的耗时
就对所有事件的timer进行检查,如果time out就从timer rbtree中,
删除到期的timer,同时调用相应事件的handler函数完成处理。
*/
if (delta) {
ngx_event_expire_timers();
}

在ngx_process_events_and_timers函数中调用ngx_event_expire_timers函数来处理所有的定时事件。嗯,这里可以看到一个比较奇怪的现象,干嘛要判断delta的值呢,嗯,其实这个值是统计处理其余事件的用时,如果用时超过了毫秒,那么才会真正的调用ngx_event_expire_timers函数来处理所有的定时,否则不会,因为距离上次循环间隔太小,完全没有必要。嗯,性能的提升也就是从这一点一滴中获取的吧。接下来来看ngx_event_expire_timers函数吧:

//处理红黑树中所有超时的事件
void
ngx_event_expire_timers
(void)
{
ngx_event_t *ev;
ngx_rbtree_node_t *node, *root, *sentinel; sentinel = ngx_event_timer_rbtree.sentinel; //死循环,找到所有的超时的timer,然后处理他们
for ( ;; ) { ngx_mutex_lock(ngx_event_timer_mutex); root = ngx_event_timer_rbtree.root; if (root == sentinel) {
return;
} node = ngx_rbtree_min(root, sentinel); //获取key最小的节点 /* node->key <= ngx_current_time */ if ((ngx_msec_int_t) (node->key - ngx_current_msec) <= ) { //判断该节点是否超时,如果超时的话,就执行处理函数,否则就可以跳出循环了
//通过偏移来获取当前timer所在的event
ev = (ngx_event_t *) ((char *) node - offsetof(ngx_event_t, timer)); ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, ,
"event timer del: %d: %M",
ngx_event_ident(ev->data), ev->timer.key);
//将当前timer移除
ngx_rbtree_delete(&ngx_event_timer_rbtree, &ev->timer); ngx_mutex_unlock(ngx_event_timer_mutex); ev->timer_set = ; ev->timedout = ; ev->handler(ev); //调用event的handler来处理这个事件 continue;
} break;
} ngx_mutex_unlock(ngx_event_timer_mutex);
}

该函数其实很简单的,一看就看明白了,说白了就是一个死循环,不断的从红黑树中获取key最小的元素,如果超时的话,就通过偏移量来获取其所在的event,然后执行handler,直到找到一个没有超时的timer为止,跳出循环。

嗯,到这里timer就讲完了。

转自:http://www.xuebuyuan.com/2041520.html

Nginx的定时事件的实现(timer)的更多相关文章

  1. Libevent的IO复用技术和定时事件原理

    Libevent 是一个用C语言编写的.轻量级的开源高性能网络库,主要有以下几个亮点:事件驱动( event-driven),高性能;轻量级,专注于网络,不如 ACE 那么臃肿庞大:源代码相当精炼.易 ...

  2. 如何测试 Android 中的定时事件

    测试定时事件不太容易,比如要测试 AlarmManager 中定时明天4点的一个事件,你总不能等到明天4点再看看吧. Roman Nurik 提供了两个用来测试定时事件的命令:adb shell du ...

  3. JavaScript中的定时事件

    这两个函数都是在给定的时间之后开始执行的,并不是立即执行. var timeId = window.setTimeout("method()",1000); //定时执行,还可以这 ...

  4. 【Nginx】定时器事件

    转自:烟雨江南 Nginx事件管理主要是网络事件和定时器事件.下面介绍定时器事件管理,即超时管理. 为什么进行超时管理? Nginx有必要对可能发生超时的事件 进行统一管理,并在事件超时时作出相应的处 ...

  5. 【Nginx】epoll事件驱动模块

    Linux 2.4之前的内核版本号,Nginx事件驱动的方法是使用poll.select功能.过程必须等待一个事件发生在连接上(接收数据)时间,部连接都告诉内核,由内核找出哪些连接上有事件发生.因为须 ...

  6. MySQL定时事件

    1.创建一个测试表 CREATE TABLE aaa (timeline TIMESTAMP); 2.创建一个事件:每秒插入一条记录到数据表 CREATE EVENT e_test_insert SE ...

  7. [日常]nginx与网络事件模型

    Nginx 的特点: 1.处理静态文件 2.反向代理加速 3.fastCGI,简单的负载均衡和容错 4.模块化的结构 5.分阶段资源分配技术,使得它的 CPU 与内存占用率非常低,保持 10,000 ...

  8. .NET 定时执行任务解决方案(Timer & Quartz.Net)

    共有两种方法: 一.使用Timer global.asax <%@ Application Language="C#" %> <%@ import Namespa ...

  9. 【Nginx】ngx_event_core_module事件模块

    功能:创建连接池,决定使用哪些事件驱动机制,以及初始化将要使用的事件模块 该模块定义了ngx_event_core_commands数组处理其感兴趣的7个配置项 ngx_event_conf_t为该模 ...

随机推荐

  1. CSS如何设置列表样式属性

    列表样式属性 在HTML中有2种列表.无序列表和有序列表,在工作中无序列表比较常用,无序列表就是ul标签和li标签组合成的称之为无序列表,那什么是有序列表呢?就是ol标签和li标签组合成的称之为有序列 ...

  2. pat 1035 Password(20 分)

    1035 Password(20 分) To prepare for PAT, the judge sometimes has to generate random passwords for the ...

  3. SpringBoot系列之切换log4j日志框架

    SpringBoot系列之使用切换log4j日志框架 ok,在pom文件右键->Diagrams->show Dependencies....,如图,找到spring-boot-start ...

  4. react-router-dom路由

  5. zip的压缩和解压命令

    以下命令均在/home目录下操作cd /home #进入/home目录 1.把/home目录下面的data目录压缩为data.zip zip -r data.zip data #压缩data目录   ...

  6. Windows 10上源码编译glog和gflags 编写glog-config.cmake和gflags-config.cmake | compile glog and glags on windows from source

    本文首发于个人博客https://kezunlin.me/post/bb64e398/,欢迎阅读! compile glog v0.3.5 and glags on windows from sour ...

  7. Alibaba Nacos 学习(三):Spring Cloud Nacos Discovery - FeignClient,Nacos 服务注册与发现

    Alibaba Nacos 学习(一):Nacos介绍与安装 Alibaba Nacos 学习(二):Spring Cloud Nacos Config Alibaba Nacos 学习(三):Spr ...

  8. 【2018寒假集训Day 7】【最短路径】三种算法的模板

    Luogu单源最短路径模版题 dijkstra #include<cstdio> #include<vector> using namespace std; const int ...

  9. 记一次将本地工程上传到github的过程

    记一次将本地工程上传到github的过程 1.首先,进入本地工程所在文件夹,运行git init将工程初始化为git仓库: XH@DESKTOP-82MT9LU MINGW64 ~/Desktop/t ...

  10. 【数据结构】之串(C语言描述)

    串(字符串)是编程中最常用的结构,但 C语言 中没有“字符串”这种变量,只能通过字符数组的形式表示字符串. C语言 为我们提供了一个 string.h 的头文件,通过这个头文件,我们可以实现对字符串的 ...