當前位置:
首頁 > 知識 > 深入剖析nginx時間緩存

深入剖析nginx時間緩存

一、背景

在伺服器開發領域,時間的準確度關係到系統能否正常運行,尤其是當系統中存在超時事件需要處理時。但是系統時間的獲取需要一次昂貴的系統調用,作為一款成熟的伺服器軟體,Nginx是如何優化這部分的性能開銷?

二、時間緩存

接觸過系統設計的同學都知道,對於頻繁的數據獲取,在數據未變化的情形下,可以通過增加緩存來優化性能,因為緩存的訪問速度遠高於源數據的訪問速度。這樣的例子有很多,比如CPU設計有二級緩存,在傳統的database基礎上有了我們今天的redis、memcache等nosql。對於系統時間也一樣,既然獲取系統時間開銷較大,可以嘗試著將獲取到的時間緩存起來,需要時直接從緩存中取就可以了。但與此同時,也引入了緩存時間與實際時間不一致的可能,下面看看Nginx是如何解決這一問題。

三、設計與實現

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的流程圖為:

深入剖析nginx時間緩存

值得一提的是,這裡採用了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的設計規則為:

深入剖析nginx時間緩存

獲取時間的執行體採用ngx_timeofday獲取了當前ngx_cached_time的快照,隨後讀取對應的slot中數據,包括sec和msec。

更新時間的執行體通過ngx_time_update原子更新ngx_cached_time指向,這樣更新之後的時間讀取就是新的slot中的時間數據。

這裡,nginx利用了修改指針的原子性,確保讀寫不會造成時間數據混亂。而時間數據本身包括sec和msec,無法完成修改的原子性,這種將非原子性修改操作轉換為原子性修改操作的手法,值得借鑒。

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 PHP愛好者 的精彩文章:

每個程序員書櫃必備的編程書籍
可擴展系統的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