nginx的時間管理
gettimeofday()的開銷
在linux中,nginx透過gettimeofday()獲取系統當前時間;
gettimeofday是C庫提供的函式(不是系統呼叫),它封裝了核心裡的sys_gettimeofday系統呼叫。
Linux的系統呼叫透過int 80h實現,用系統呼叫號來區分入口函式,步驟大致如下:
1 API將系統呼叫號存入EAX,然後透過中斷呼叫使系統進入內核態;
2 核心中的中斷處理函式根據系統呼叫號,呼叫對應的核心函式(系統呼叫);
3 系統呼叫完成相應功能,將返回值存入EAX,返回到中斷處理函式;
4 中斷處理函式返回到API中;
5 API將EAX返回給應用程式
然而除了基本的系統呼叫外,x86_64還提供了sysenter/vsyscall方式獲取核心態資料,vsyscall在記憶體中建立核心態的共享頁面,使用者態也有權訪問,可不經過系統中斷和陷入核心獲取核心資訊;
gettimeofday()便是透過vsyscall實現了系統呼叫。
更新時間快取
為避免每次都呼叫OS的gettimeofday,nginx採用時間快取,每個worker程式都能自行維護;
為控制併發訪問,每次更新時間快取前需申請鎖,而讀時間快取無須加鎖;
為避免分裂讀,即某worker程式讀時間快取過程中接受中斷請求,期間時間快取被其他worker更新,導致前後讀取時間不一致;nginx引入時間快取陣列(共64個成員),每次都更新陣列中的下一個元素;
更新時間透過ngx_time_update()實現
typedef struct {
time_t sec;
ngx_uint_t msec;
ngx_int_t gmtoff;
} ngx_time_t;
volatile ngx_time_t *ngx_cached_time;
volatile ngx_str_t ngx_cached_err_log_time;
volatile ngx_str_t ngx_cached_http_time;
volatile ngx_str_t ngx_cached_http_log_time;
volatile ngx_str_t ngx_cached_http_log_iso8601;
static ngx_time_t cached_time[NGX_TIME_SLOTS];
static u_char cached_err_log_time[NGX_TIME_SLOTS][sizeof("1970/09/28 12:00:00")];
static u_char cached_http_time[NGX_TIME_SLOTS][sizeof("Mon, 28 Sep 1970 06:00:00 GMT")];
static u_char cached_http_log_time[NGX_TIME_SLOTS][sizeof("28/Sep/1970:12:00:00 +0600")];
static u_char cached_http_log_iso8601[NGX_TIME_SLOTS][sizeof("1970-09-28T12:00:00+06:00")];
static u_char cached_syslog_time[NGX_TIME_SLOTS][sizeof("Sep 28 12:00:00")];
void
ngx_time_update(void)
{
u_char *p0, *p1, *p2, *p3, *p4;
ngx_tm_t tm, gmt;
time_t sec;
ngx_uint_t msec;
ngx_time_t *tp;
struct timeval tv;
if (!ngx_trylock(&ngx_time_lock)) {--更新快取前需獲取ngx_time_lock
return;
}
ngx_gettimeofday(&tv);--宏定義,呼叫os的gettimeofday(tp, null)
sec = tv.tv_sec;
msec = tv.tv_usec / 1000;
ngx_current_msec = (ngx_msec_t) sec * 1000 + msec;
tp = &cached_time[slot]; --讀當前時間快取
if (tp->sec == sec) { --如果快取的時間秒=當前時間秒,直接更新當前slot元素的msec並返回,否則更新下一個slot陣列元素;
tp->msec = msec;
ngx_unlock(&ngx_time_lock);
return;
}
if (slot == NGX_TIME_SLOTS - 1) {
slot = 0;
} else {
slot++;
}
tp = &cached_time[slot];
tp->sec = sec;
tp->msec = msec;
ngx_gmtime(sec, &gmt);
p0 = &cached_http_time[slot][0];
--ngx_sprintf讀取所有引數並呼叫ngx_vslprintf,將後續引數以第二個引數的格式複製到P0開始的記憶體區,即給cached_http_time[slot]賦值,
--後續的cached_err_log_time[slot] & cached_http_log_time[slot] & cached_http_log_iso8601[slot] & cached_syslog_time[slot]也同理
(void) ngx_sprintf(p0, "%s, %02d %s %4d %02d:%02d:%02d GMT",
week[gmt.ngx_tm_wday], gmt.ngx_tm_mday,
months[gmt.ngx_tm_mon - 1], gmt.ngx_tm_year,
gmt.ngx_tm_hour, gmt.ngx_tm_min, gmt.ngx_tm_sec);
..................
ngx_memory_barrier();--禁止編譯器對後面的語句最佳化,如果沒有這個限制,編譯器可能將前後兩部分程式碼合併,可能導致這6個時間更新出現間隔,期間若被讀取會出現時間不一致的情況
ngx_cached_time = tp;
ngx_cached_http_time.data = p0;
ngx_cached_err_log_time.data = p1;
ngx_cached_http_log_time.data = p2;
ngx_cached_http_log_iso8601.data = p3;
ngx_cached_syslog_time.data = p4;
ngx_unlock(&ngx_time_lock);
}
ngx_time_update()呼叫最頻繁的是在worker程式處理事件時
ngx_worker_process_cycle -- ngx_process_events_and_timers -- ngx_process_events
#define ngx_process_events ngx_event_actions.process_events
以epoll為例,其對應API為ngx_epoll_process_events
ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)
events = epoll_wait(ep, event_list, (int) nevents, timer);
err = (events == -1) ? ngx_errno : 0;
if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) {
ngx_time_update();
}
epoll_wait()阻塞時可以被三種事件喚醒:讀寫事件發生、等待時間超時和事件訊號中斷。
當epoll_wait()返回時,會更新一次時間快取,然後呼叫處理函式;事件處理函式是non-block的,本身執行時間極短(毫秒級),故即便當前時間是快取的,誤差很小可以接受。
如何控制時間更新頻率
nginx提供引數timer_resolution,設定快取時間更新的間隔;
配置該項後,nginx將使用中斷機制,而非使用定時器紅黑樹中的最小時間為epoll_wait的超時時間,即此時定時器將定期被中斷。
timer_resolution指令的使用將會設定epoll_wait超時時間為-1,這表示epoll_wait將永遠阻塞直至讀寫事件發生或訊號中斷。
ngx_process_events_and_timers(ngx_cycle_t *cycle)
if (ngx_timer_resolution) {
timer = NGX_TIMER_INFINITE;
flags = 0;
} else {
timer = ngx_event_find_timer();
flags = NGX_UPDATE_TIME;
}
1.設定timer_resolution時,flags=0,只有當ngx_event_timer_alarm=1時epoll_wait()返回時才執行ngx_time_update(更新後會把ngx_event_timer_alarm置零)
2.沒有設定timer_resolution,flags = NGX_UPDATE_TIME,timer為定時器紅黑樹中最小定時時間,將作為epoll_wait的超時時間(timeout)
ngx_event_process_init(ngx_cycle_t *cycle)
if (ngx_timer_resolution && !(ngx_event_flags & NGX_USE_TIMER_EVENT)) {
ngx_memzero(&sa, sizeof(struct sigaction));
sa.sa_handler = ngx_timer_signal_handler;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGALRM, &sa, NULL) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"sigaction(SIGALRM) failed");
return NGX_ERROR;
}
itv.it_interval.tv_sec = ngx_timer_resolution / 1000;
itv.it_interval.tv_usec = (ngx_timer_resolution % 1000) * 1000;
itv.it_value.tv_sec = ngx_timer_resolution / 1000;
itv.it_value.tv_usec = (ngx_timer_resolution % 1000 ) * 1000;
if (setitimer(ITIMER_REAL, &itv, NULL) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"setitimer() failed");
}
}
每隔ngx_timer_resolution時間發出訊號SIGALRM,執行ngx_timer_signal_handler,後者僅僅將ngx_event_timer_alarm = 1,用於epoll_wait()返回後的ngx_time_update()呼叫
參考資料
在linux中,nginx透過gettimeofday()獲取系統當前時間;
gettimeofday是C庫提供的函式(不是系統呼叫),它封裝了核心裡的sys_gettimeofday系統呼叫。
Linux的系統呼叫透過int 80h實現,用系統呼叫號來區分入口函式,步驟大致如下:
1 API將系統呼叫號存入EAX,然後透過中斷呼叫使系統進入內核態;
2 核心中的中斷處理函式根據系統呼叫號,呼叫對應的核心函式(系統呼叫);
3 系統呼叫完成相應功能,將返回值存入EAX,返回到中斷處理函式;
4 中斷處理函式返回到API中;
5 API將EAX返回給應用程式
然而除了基本的系統呼叫外,x86_64還提供了sysenter/vsyscall方式獲取核心態資料,vsyscall在記憶體中建立核心態的共享頁面,使用者態也有權訪問,可不經過系統中斷和陷入核心獲取核心資訊;
gettimeofday()便是透過vsyscall實現了系統呼叫。
更新時間快取
為避免每次都呼叫OS的gettimeofday,nginx採用時間快取,每個worker程式都能自行維護;
為控制併發訪問,每次更新時間快取前需申請鎖,而讀時間快取無須加鎖;
為避免分裂讀,即某worker程式讀時間快取過程中接受中斷請求,期間時間快取被其他worker更新,導致前後讀取時間不一致;nginx引入時間快取陣列(共64個成員),每次都更新陣列中的下一個元素;
更新時間透過ngx_time_update()實現
typedef struct {
time_t sec;
ngx_uint_t msec;
ngx_int_t gmtoff;
} ngx_time_t;
volatile ngx_time_t *ngx_cached_time;
volatile ngx_str_t ngx_cached_err_log_time;
volatile ngx_str_t ngx_cached_http_time;
volatile ngx_str_t ngx_cached_http_log_time;
volatile ngx_str_t ngx_cached_http_log_iso8601;
static ngx_time_t cached_time[NGX_TIME_SLOTS];
static u_char cached_err_log_time[NGX_TIME_SLOTS][sizeof("1970/09/28 12:00:00")];
static u_char cached_http_time[NGX_TIME_SLOTS][sizeof("Mon, 28 Sep 1970 06:00:00 GMT")];
static u_char cached_http_log_time[NGX_TIME_SLOTS][sizeof("28/Sep/1970:12:00:00 +0600")];
static u_char cached_http_log_iso8601[NGX_TIME_SLOTS][sizeof("1970-09-28T12:00:00+06:00")];
static u_char cached_syslog_time[NGX_TIME_SLOTS][sizeof("Sep 28 12:00:00")];
void
ngx_time_update(void)
{
u_char *p0, *p1, *p2, *p3, *p4;
ngx_tm_t tm, gmt;
time_t sec;
ngx_uint_t msec;
ngx_time_t *tp;
struct timeval tv;
if (!ngx_trylock(&ngx_time_lock)) {--更新快取前需獲取ngx_time_lock
return;
}
ngx_gettimeofday(&tv);--宏定義,呼叫os的gettimeofday(tp, null)
sec = tv.tv_sec;
msec = tv.tv_usec / 1000;
ngx_current_msec = (ngx_msec_t) sec * 1000 + msec;
tp = &cached_time[slot]; --讀當前時間快取
if (tp->sec == sec) { --如果快取的時間秒=當前時間秒,直接更新當前slot元素的msec並返回,否則更新下一個slot陣列元素;
tp->msec = msec;
ngx_unlock(&ngx_time_lock);
return;
}
if (slot == NGX_TIME_SLOTS - 1) {
slot = 0;
} else {
slot++;
}
tp = &cached_time[slot];
tp->sec = sec;
tp->msec = msec;
ngx_gmtime(sec, &gmt);
p0 = &cached_http_time[slot][0];
--ngx_sprintf讀取所有引數並呼叫ngx_vslprintf,將後續引數以第二個引數的格式複製到P0開始的記憶體區,即給cached_http_time[slot]賦值,
--後續的cached_err_log_time[slot] & cached_http_log_time[slot] & cached_http_log_iso8601[slot] & cached_syslog_time[slot]也同理
(void) ngx_sprintf(p0, "%s, %02d %s %4d %02d:%02d:%02d GMT",
week[gmt.ngx_tm_wday], gmt.ngx_tm_mday,
months[gmt.ngx_tm_mon - 1], gmt.ngx_tm_year,
gmt.ngx_tm_hour, gmt.ngx_tm_min, gmt.ngx_tm_sec);
..................
ngx_memory_barrier();--禁止編譯器對後面的語句最佳化,如果沒有這個限制,編譯器可能將前後兩部分程式碼合併,可能導致這6個時間更新出現間隔,期間若被讀取會出現時間不一致的情況
ngx_cached_time = tp;
ngx_cached_http_time.data = p0;
ngx_cached_err_log_time.data = p1;
ngx_cached_http_log_time.data = p2;
ngx_cached_http_log_iso8601.data = p3;
ngx_cached_syslog_time.data = p4;
ngx_unlock(&ngx_time_lock);
}
ngx_time_update()呼叫最頻繁的是在worker程式處理事件時
ngx_worker_process_cycle -- ngx_process_events_and_timers -- ngx_process_events
#define ngx_process_events ngx_event_actions.process_events
以epoll為例,其對應API為ngx_epoll_process_events
ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)
events = epoll_wait(ep, event_list, (int) nevents, timer);
err = (events == -1) ? ngx_errno : 0;
if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) {
ngx_time_update();
}
epoll_wait()阻塞時可以被三種事件喚醒:讀寫事件發生、等待時間超時和事件訊號中斷。
當epoll_wait()返回時,會更新一次時間快取,然後呼叫處理函式;事件處理函式是non-block的,本身執行時間極短(毫秒級),故即便當前時間是快取的,誤差很小可以接受。
如何控制時間更新頻率
nginx提供引數timer_resolution,設定快取時間更新的間隔;
配置該項後,nginx將使用中斷機制,而非使用定時器紅黑樹中的最小時間為epoll_wait的超時時間,即此時定時器將定期被中斷。
timer_resolution指令的使用將會設定epoll_wait超時時間為-1,這表示epoll_wait將永遠阻塞直至讀寫事件發生或訊號中斷。
ngx_process_events_and_timers(ngx_cycle_t *cycle)
if (ngx_timer_resolution) {
timer = NGX_TIMER_INFINITE;
flags = 0;
} else {
timer = ngx_event_find_timer();
flags = NGX_UPDATE_TIME;
}
1.設定timer_resolution時,flags=0,只有當ngx_event_timer_alarm=1時epoll_wait()返回時才執行ngx_time_update(更新後會把ngx_event_timer_alarm置零)
2.沒有設定timer_resolution,flags = NGX_UPDATE_TIME,timer為定時器紅黑樹中最小定時時間,將作為epoll_wait的超時時間(timeout)
ngx_event_process_init(ngx_cycle_t *cycle)
if (ngx_timer_resolution && !(ngx_event_flags & NGX_USE_TIMER_EVENT)) {
ngx_memzero(&sa, sizeof(struct sigaction));
sa.sa_handler = ngx_timer_signal_handler;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGALRM, &sa, NULL) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"sigaction(SIGALRM) failed");
return NGX_ERROR;
}
itv.it_interval.tv_sec = ngx_timer_resolution / 1000;
itv.it_interval.tv_usec = (ngx_timer_resolution % 1000) * 1000;
itv.it_value.tv_sec = ngx_timer_resolution / 1000;
itv.it_value.tv_usec = (ngx_timer_resolution % 1000 ) * 1000;
if (setitimer(ITIMER_REAL, &itv, NULL) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"setitimer() failed");
}
}
每隔ngx_timer_resolution時間發出訊號SIGALRM,執行ngx_timer_signal_handler,後者僅僅將ngx_event_timer_alarm = 1,用於epoll_wait()返回後的ngx_time_update()呼叫
參考資料
http://blog.csdn.net/russell_tao/article/details/7185588
http://blog.csdn.net/marcky/article/details/7623335來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/15480802/viewspace-1344713/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- php 和 nginx 的幾個超時時間PHPNginx
- 時間管理
- 時間管理:不要讓時間偷走你的餅乾
- 巧用時間管理工具,讓時間管理更高效
- 時間管理系列-偷走我生命的賊
- c++時間管理大師C++
- 《Lua-in-ConTeXt》07:時間管理Context
- 企業如何規範管理員工時間 工時管理系統的作用
- 高效管理 Elasticsearch 中基於時間的索引Elasticsearch索引
- 如何利用工時表軟體管理員工時間 避免時間浪費
- 8個“時間管理黑客”教你更好利用時間獲得成功黑客
- 關於問問題和時間管理的感悟
- Nginx核心知識100講-陶輝-極客時間Nginx
- OmniFocus Pro mac - 最佳GTD時間管理Mac
- 學會高效管理時間,健康工作
- wnr for Mac計時和時間管理工具Mac
- 職場中人如何做好時間管理提高工作效率?高效時間管理軟體
- Phpcms找回管理員密碼及管理員解鎖時間的方法PHP密碼
- Asp.Net Core中利用過濾器控制Nginx的快取時間ASP.NET過濾器Nginx快取
- mysql時間操作(時間差和時間戳和時間字串的互轉)MySql時間戳字串
- Nginx學習筆記3--(極客時間-陶輝)Nginx筆記
- nginx中access日誌如何做到按時間完美切割Nginx
- 超越datetime:Arrow,Python中的日期時間管理大師Python
- 總是感覺時間不夠用?程式設計師如何管理時間?程式設計師
- 獲取時間戳,幾個時間點的時間戳時間戳
- 兩個時間戳的時間差時間戳
- 《筆記女王的超效率時間管理攻略》讀後感筆記
- 朱贇的技術管理課-朱贇-極客時間
- 讓免費OA系統做你的時間管理大師
- 直播軟體搭建,當前時間、既定時間後的時間及時間比較大小
- 80/20時間管理法則全面解析
- Redis 過期時間與記憶體管理Redis記憶體
- OmniFocus 3 for Mac(GTD時間管理工具)Mac
- GTD時間管理工具OmniFocus Pro 3
- 【時間戳轉普通時間格式的方法】時間戳
- 處理nginx訪問日誌,篩選時間大於1秒的請求Nginx
- Nginx的程式管理與過載原理Nginx
- 時間戳與時間字串的多時區轉換時間戳字串
- 專案經理的時間管理秘籍,你真的瞭解嗎?