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. sparkSQL 简介

    一.Spark SQL的特点 1.支持多种数据源:Hive.RDD.Parquet.JSON.JDBC等.2.多种性能优化技术:in-memory columnar storage.byte-code ...

  2. django_4数据库2——表外键

    表关系: many to one many to many one to one many to one 记录是如何创建出来的?      先创建one,在在创建many,创建时加入ForeignKe ...

  3. Grid表格的js触发事件

    没怎么接触过Grid插件: 解决的问题是:点击Grid表行里的内容触发js方法弹出模态框,用以显示选中内容的详细信息. 思路:给准备要触发的列加上一个css属性,通过这个css属性来获取元素并触发js ...

  4. 图解Elasticsearch的核心概念

    本文讲解大纲,分8个核心概念讲解说明: NRT Cluster Node Document&Field Index Type Shard Replica Near Realtime(NRT)近 ...

  5. selenium抓取视频

    今天闲着没事,用selenium抓取视频保存到本地,只爬取了第一页,只要小于等于5分钟的视频... 为什么不用requests,没有为什么,就因为有些网站正则和xpath都提取不出来想要的东西,要么就 ...

  6. F#周报2019年第47期

    新闻 相遇WebWindow,.NET Core上的跨平台webview类库 使用Bolero在WebAssembly中运行F# 用于你团队代码库的AI辅助IntelliSense Jupyter N ...

  7. Oracle数据库 获取CLOB字段存储的xml格式字符串指定节点的值

    参照: Oracle存储过程中使用游标来批量解析CLOB字段里面的xml字符串 背景:在写存储过程时,需要获取表单提交的信息.表单信息是以xml格式的字符串存储在colb类型的字段dataxml中,如 ...

  8. 在ensp上模拟企业网络场景并Access接口加入相应VLAN

    模拟的企业网络大概描述: 公司内网是一个大的局域网,二层交换机S1放置在一楼,在一楼办公的部门有IT部和人事部:二层交换机S2放置在二楼,在二楼办公的部门有市场部和研发部.由于交换机组成的是广播网,交 ...

  9. Java架构师必知:什么是单点登录,主要会应用于哪些场景?

    单点登录在大型网站里使用得非常频繁,例如,阿里旗下有淘宝.天猫.支付宝,阿里巴巴,阿里妈妈,阿里妹妹等网站,还有背后的成百上千的子系统,用户一次操作或交易可能涉及到几十个子系统的协作,如果每个子系统都 ...

  10. Redis的面试问题总结,面试跳槽必备

    1.什么是redis? Redis 是一个基于内存的高性能key-value数据库. 2.Reids的特点 Redis本质上是一个Key-Value类型的内存数据库,很像memcached,整个数据库 ...