深入剖析nginx時間緩存
一、背景
在伺服器開發領域,時間的準確度關係到系統能否正常運行,尤其是當系統中存在超時事件需要處理時。但是系統時間的獲取需要一次昂貴的系統調用,作為一款成熟的伺服器軟體,Nginx是如何優化這部分的性能開銷?
二、時間緩存
接觸過系統設計的同學都知道,對於頻繁的數據獲取,在數據未變化的情形下,可以通過增加緩存來優化性能,因為緩存的訪問速度遠高於源數據的訪問速度。這樣的例子有很多,比如CPU設計有二級緩存,在傳統的database基礎上有了我們今天的redis、memcache等nosql。對於系統時間也一樣,既然獲取系統時間開銷較大,可以嘗試著將獲取到的時間緩存起來,需要時直接從緩存中取就可以了。但與此同時,也引入了緩存時間與實際時間不一致的可能,下面看看Nginx是如何解決這一問題。
三、設計與實現
Nginx時間緩存設計
如上圖所示,Nginx時間緩存包括時間讀取和時間寫入者,當需要更新時間時,nginx調用gettimeofday系統調用獲取時間,然後更新緩存。需要獲取時間的代碼直接從time cache中取出即可。
這裡又產生了新的問題,具體包括:
讀寫並發,即讀和寫同時操作時間緩存會造成獲取的時間混亂。
多寫並發,多個執行體同時更新時間緩存,同樣造成時間混亂。
而常見的解決互斥的方案包括:
加鎖保證數據串列化
無鎖化設計
像Nginx這樣對於性能有著極致追求的server來說,自然不會使用系統自帶的鎖機制。其實現的ngx_lock和ngx_unlock的背後都是無鎖化的原子操作。
對於多寫並發,nginx在ngx_time_update函數中通過全局的ngx_time_lock進行互斥,確保同一時刻只會存在一個執行體更新時間緩存。
對於讀寫並發,nginx設計了NGX_TIME_SLOTS個slot,用於隔離讀寫操作的時間緩存。同時引入時間緩存指針,原子地更新當前緩存的指向位置。
Nginx時間緩存實現
下面看具體實現代碼(以nginx-1.13.1為例src/core/ngx_times.c):
void ngx_time_update(void)
{
...
//ngx_time_lock用於互斥,避免同時更新時間
if (!ngx_trylock(&ngx_time_lock)) {
return;
}
//獲取當前時間
ngx_gettimeofday(&tv);
sec = tv.tv_sec;
msec = tv.tv_usec / 1000;
ngx_current_msec = (ngx_msec_t) sec * 1000 + msec;
tp = &cached_time[slot];
//秒值一致則只需要更新當前slot的msec
if (tp->sec == sec) {
tp->msec = msec;
ngx_unlock(&ngx_time_lock);
return;
}
//獲取下一slot
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];
(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_cached_err_log_time.data等
ngx_memory_barrier();
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的流程圖為:
值得一提的是,這裡採用了ngx_memory_barrier來避免指令重排,這樣可以儘可能地保證ngx_cached_time、ngx_cached_http_time.data、ngx_cached_err_log_time.data、ngx_cached_http_log_time.data、ngx_cached_http_log_iso8601.data、ngx_cached_syslog_time.data中存儲的時間數據一致。
slot設計
上面談到了nginx採用slot來從空間上避免讀寫執行體同時操作時間緩存,slot的設計規則為:
獲取時間的執行體採用ngx_timeofday獲取了當前ngx_cached_time的快照,隨後讀取對應的slot中數據,包括sec和msec。
更新時間的執行體通過ngx_time_update原子更新ngx_cached_time指向,這樣更新之後的時間讀取就是新的slot中的時間數據。
這裡,nginx利用了修改指針的原子性,確保讀寫不會造成時間數據混亂。而時間數據本身包括sec和msec,無法完成修改的原子性,這種將非原子性修改操作轉換為原子性修改操作的手法,值得借鑒。


※每個程序員書櫃必備的編程書籍
※可擴展系統的9個性能問題
※快速成長期的雲原生應用架構實踐
※一位程序員工作10年總結了這些忠告
TAG:PHP愛好者 |
※windows緩存
※Flutter圖片緩存 Image.network源碼分析
※redis緩存過期策略,監聽redis緩存
※python的緩存庫:cacheout
※Python + Memcached: 在分散式應用程序中實現高效緩存
※MyBatis中的緩存
※Python + Memcached:在分散式應用程序中實現高效緩存
※int 和 Integer 有什麼區別?談談 Integer 的值緩存範圍
※Python+Memcached:在分散式應用程序中實現高效緩存
※SpringBoot:SpringDataRedis緩存改造
※Bloom Filter如何解決緩存穿透
※spring-boot-2.0.3之redis緩存實現
※緩存架構SpringBoot集成Curator實現zookeeper分散式鎖
※redis緩存和cookie實現Session共享
※Hitachi Vantara升級Skylaking伺服器加入Optane緩存和GPU
※TinyShop緩存文件獲取WebShell之0day
※Linux下搭建高可用Redis緩存
※前端靜態資源緩存最優解以及max-age的陷阱
※HashCache:低內存緩存存儲系統
※開源分散式內存緩存系統 Memcrashed 被利用發起 DDoS 放大攻擊,峰值竟達 500 Gbps