DSP系統是互聯網廣告需求方平臺,用於承接媒體流量,投放廣告。業務特色是併發度高,平均響應低(百毫秒)。算法
爲了可以有效提升DSP系統的性能,美團平臺引入了一種帶有清退機制的緩存結構LruCache(Least Recently Used Cache),在目前的DSP系統中,使用LruCache + 鍵值存儲數據庫的機制將遠端數據變爲本地緩存數據,不只可以下降平均獲取信息的耗時,並且經過必定的清退機制,也能夠維持服務內存佔用在安全區間。數據庫
本文將會結合實際應用場景,闡述引入LruCache的緣由,並會在高QPS下的挑戰與解決方案等方面作詳細深刻的介紹,但願能對DSP感興趣的同窗有所啓發。後端
LruCache採用的緩存算法爲LRU(Least Recently Used),即最近最少使用算法。這一算法的核心思想是當緩存數據達到預設上限後,會優先淘汰近期最少使用的緩存對象。緩存
LruCache內部維護一個雙向鏈表和一個映射表。鏈表按照使用順序存儲緩存數據,越早使用的數據越靠近鏈表尾部,越晚使用的數據越靠近鏈表頭部;映射表經過Key-Value結構,提供高效的查找操做,經過鍵值能夠判斷某一數據是否緩存,若是緩存直接獲取緩存數據所屬的鏈表節點,進一步獲取緩存數據。LruCache結構圖以下所示,上半部分是雙向鏈表,下半部分是映射表(不必定有序)。雙向鏈表中value_1所處位置爲鏈表頭部,value_N所處位置爲鏈表尾部。安全
LruCache讀操做,經過鍵值在映射表中查找緩存數據是否存在。若是數據存在,則將緩存數據所處節點從鏈表中當前位置取出,移動到鏈表頭部;若是不存在,則返回查找失敗,等待新數據寫入。下圖爲經過LruCache查找key_2後LruCache結構的變化。性能優化
LruCache沒有達到預設上限狀況下的寫操做,直接將緩存數據加入到鏈表頭部,同時將緩存數據鍵值與緩存數據所處的雙鏈表節點做爲鍵值對插入到映射表中。下圖是LruCache預設上限大於N時,將數據M寫入後的數據結構。數據結構
LruCache達到預設上限狀況下的寫操做,首先將鏈表尾部的緩存數據在映射表中的鍵值對刪除,並刪除鏈表尾部數據,再將新的數據正常寫入到緩存中。下圖是LruCache預設上限爲N時,將數據M寫入後的數據結構。架構
線程安全的LruCache在讀寫操做中,所有使用鎖作臨界區保護,確保緩存使用是線程安全的。併發
在美團DSP系統中普遍應用鍵值存儲數據庫,例如使用Redis存儲廣告信息,服務能夠經過廣告ID獲取廣告信息。每次請求都從遠端的鍵值存儲數據庫中獲取廣告信息,請求耗時很是長。隨着業務發展,QPS呈現巨大的增加趨勢,在這種高併發的應用場景下,將廣告信息從遠端鍵值存儲數據庫中遷移到本地以減小查詢耗時是常看法決方案。另外服務自己的內存佔用要穩定在一個安全的區間內。面對持續增加的廣告信息,引入LruCache + 鍵值存儲數據庫的機制來達到提升系統性能,維持內存佔用安全、穩定的目標。機器學習
在實際應用中,LruCache + Redis機制實踐分別經歷了引入LruCache、LruCache增長時效清退機制、HashLruCache知足高QPS應用場景以及零拷貝機制四個階段。各階段的測試機器是16核16G機器。
在較低QPS環境下,直接請求Redis獲取廣告信息,能夠知足場景需求。可是隨着單機QPS的增長,直接請求Redis獲取廣告信息,耗時也會增長,沒法知足業務場景的需求。
引入LruCache,將遠端存放於Redis的信息本地化存儲。LruCache能夠預設緩存上限,這個上限能夠根據服務所在機器內存與服務自己內存佔用來肯定,確保增長LruCache後,服務自己內存佔用在安全範圍內;同時能夠根據查詢操做統計緩存數據在實際使用中的命中率。
下圖是增長LruCache結構先後,且增長LruCache後命中率高於95%的狀況下,針對持續增加的QPS得出的數據獲取平均耗時(ms)對比圖:
根據平均耗時圖顯示能夠得出結論:
下圖是增長LruCache結構先後,且增長LruCache後命中率高於95%的狀況下,針對持續增加的QPS得出的數據獲取Top999耗時(ms)對比圖:
根據Top999耗時圖能夠得出如下結論:
引入LruCache結構,在實際使用中,在必定的QPS範圍內,確實能夠有效減小數據獲取的耗時。可是QPS超出必定範圍後,平均耗時和Top999耗時都很高。因此LruCache在更高的QPS下性能還須要進一步優化。
在業務場景中,Redis中的廣告數據有可能作修改。服務自己做爲數據的使用方,沒法感知到數據源的變化。當緩存的命中率較高或者部分數據在較長時間內屢次命中,可能出現數據失效的狀況。即數據源發生了變化,但服務沒法及時更新數據。針對這一業務場景,增長了時效清退機制。
時效清退機制的組成部分有三點:設置緩存數據過時時間,緩存數據單元增長時間戳以及查詢中的時效性判斷。緩存數據單元將數據進入LruCache的時間戳與數據一塊兒緩存下來。緩存過時時間表示緩存單元緩存的時間上限。查詢中的時效性判斷表示查詢時的時間戳與緩存時間戳的差值超過緩存過時時間,則強制將此數據清空,從新請求Redis獲取數據作緩存。
在查詢中作時效性判斷能夠最低程度的減小時效判斷對服務的中斷。當LruCache預設上限較低時,按期作全量數據清理對於服務自己影響較小。但若是LruCache的預設上限很是高,則一次全量數據清理耗時可能達到秒級甚至分鐘級,將嚴重阻斷服務自己的運行。因此將時效性判斷加入到查詢中,只對單一的緩存單元作時效性判斷,在服務性能和數據有效性之間作了折中,知足業務需求。
LruCache引入美團DSP系統後,在一段時間內較好地支持了業務的發展。隨着業務的迭代,單機QPS持續上升。在更高QPS下,LruCache的查詢耗時有了明顯的提升,逐漸沒法適應低平響的業務場景。在這種狀況下,引入了HashLruCache機制以解決這個問題。
線程安全的LruCache中有鎖的存在。每次讀寫操做以前都有加鎖操做,完成讀寫操做以後還有解鎖操做。在低QPS下,鎖競爭的耗時基本能夠忽略;可是在高QPS下,大量的時間消耗在了等待鎖的操做上,致使耗時增加。
針對大量的同步等待操做致使耗時增長的狀況,解決方案就是儘可能減少臨界區。引入Hash機制,對全量數據作分片處理,在原有LruCache的基礎上造成HashLruCache,以下降查詢耗時。
HashLruCache引入某種哈希算法,將緩存數據分散到N個LruCache上。最簡單的哈希算法即便用取模算法,將廣告信息按照其ID取模,分散到N個LruCache上。查詢時也按照相同的哈希算法,先獲取數據可能存在的分片,而後再去對應的分片上查詢數據。這樣能夠增長LruCache的讀寫操做的並行度,減少同步等待的耗時。
下圖是使用16分片的HashLruCache結構先後,且命中率高於95%的狀況下,針對持續增加的QPS得出的數據獲取平均耗時(ms)對比圖:
根據平均耗時圖能夠得出如下結論:
下圖是使用16分片的HashLruCache結構先後,且命中率高於95%的狀況下,針對持續增加的QPS得出的數據獲取Top999耗時(ms)對比圖:
根據Top999耗時圖能夠得出如下結論:
引入HashLruCache結構後,在實際使用中,平均耗時和Top999耗時都有很是明顯的降低,效果很是顯著。
根據以上分析,進一步提升HashLruCache性能的一個方法是肯定最合理的分片數量,增長足夠的並行度,減小同步等待消耗。因此分片數量能夠與CPU數量一致。因爲超線程技術的使用,能夠將分片數量進一步提升,增長並行性。
下圖是使用HashLruCache機制後,命中率高於95%,不一樣分片數量在不一樣QPS下得出的數據獲取平均耗時(ms)對比圖:
平均耗時圖顯示,在較高的QPS下,平均耗時並無隨着分片數量的增長而有明顯的減小,基本維持穩定的狀態。
下圖是使用HashLruCache機制後,命中率高於95%,不一樣分片數量在不一樣QPS下得出的數據獲取Top999耗時(ms)對比圖:
Top999耗時圖顯示,QPS爲750時,分片數量從8增加到16再增加到24時,Top999耗時有必定的降低,並不顯著;QPS爲1000時,分片數量從8增加到16有明顯降低,可是從16增加到24時,基本維持了穩定狀態。明顯與實際使用的機器CPU數量有較強的相關性。
HashLruCache機制在實際使用中,能夠根據機器性能並結合實際場景的QPS來調節分片數量,以達到最好的性能。
線程安全的LruCache內部維護一套數據。對外提供數據時,將對應的數據完整拷貝一份提供給調用方使用。若是存放結構簡單的數據,拷貝操做的代價很是小,這一機制不會成爲性能瓶頸。可是美團DSP系統的應用場景中,LruCache中存放的數據結構很是複雜,單次的拷貝操做代價很大,致使這一機制變成了性能瓶頸。
理想的狀況是LruCache對外僅僅提供數據地址,即數據指針。使用方在業務須要使用的地方經過數據指針獲取數據。這樣能夠將複雜的數據拷貝操做變爲簡單的地址拷貝,大量減小拷貝操做的性能消耗,即數據的零拷貝機制。直接的零拷貝機制存在安全隱患,即因爲LruCache中的時效清退機制,可能會出現某一數據已通過期被刪除,可是使用方仍然經過持有失效的數據指針來獲取該數據。
進一步分析能夠肯定,以上問題的核心是存放於LruCache的數據生命週期對於使用方不透明。解決這一問題的方案是爲LruCache中存放的數據添加原子變量的引用計數。使用原子變量不只確保了引用計數的線程安全,使得各個線程讀取的引用計數一致,同時保證了併發狀態最小的同步性能開銷。不管是LruCache中仍是使用方,每次獲取數據指針時,即將引用計數加1;同理,再也不持有數據指針時,引用計數減1。當引用計數爲0時,說明數據沒有被任何使用方使用,且數據已通過期從LruCache中被刪除。這時刪除數據的操做是安全的。
下圖是使零拷貝機制後,命中率高於95%,不一樣QPS下得出的數據獲取平均耗時(ms)對比圖:
平均耗時圖顯示,使用零拷貝機制後,平均耗時降低幅度超過60%,效果很是顯著。
下圖是使零拷貝機制後,命中率高於95%,不一樣QPS下得出的數據獲取Top999耗時(ms)對比圖:
根據Top999耗時圖能夠得出如下結論:
引入零拷貝機制後,經過拷貝指針替換拷貝數據,大量下降了獲取複雜業務數據的耗時,同時將臨界區減少到最小。線程安全的原子變量自增與自減操做,目前在多個基礎庫中都有實現,例如C++11就提供了內置的整型原子變量,實現線程安全的自增與自減操做。
在HashLruCache中引入零拷貝機制,能夠進一步有效下降平均耗時和Top999耗時,且在高QPS下對於穩定Top999耗時有很是好的效果。
下圖是一系列優化措施先後,命中率高於95%,不一樣QPS下得出的數據獲取平均耗時(ms)對比圖:
平均耗時圖顯示,優化後的平均耗時僅爲優化前的20%之內,性能提高很是明顯。優化後平均耗時對於QPS的增加敏感度更低,更好的支持了高QPS的業務場景。
下圖是一系列優化措施先後,命中率高於95%,不一樣QPS下得出的數據獲取Top999耗時(ms)對比圖:
Top999耗時圖顯示,優化後的Top999耗時僅爲優化前的20%之內,對於長尾請求的耗時有很是明顯的下降。
LruCache是一個很是常見的數據結構。在美團DSP的高QPS業務場景下,發揮了重要的做用。爲了符合業務須要,在本來的清退機制外,補充了時效性強制清退機制。隨着業務的發展,針對更高QPS的業務場景,使用HashLruCache機制,下降緩存的查詢耗時。針對不一樣的具體場景,在不一樣的QPS下,不斷嘗試更合理的分片數量,不斷提升HashLruCache的查詢性能。經過引用計數的方案,在HashLruCache中引入零拷貝機制,進一步大幅下降平均耗時和Top999耗時,更好的服務於業務場景的發展。
王粲,2018年11月加入美團,任職美團高級工程師,負責美團DSP系統後端基礎架構的研發工做。
崔濤,2015年6月加入美團,任職資深廣告技術專家,期間一手指導並從0到1搭建美團DSP投放平臺,具有豐富的大規模計算引擎的開發和性能優化經驗。
霜霜,2015年6月加入美團,任職美團高級工程師,美團DSP系統後端基礎架構與機器學習架構負責人,全面負責DSP業務廣告召回和排序服務的架構設計與優化。
美團在線營銷DSP團隊誠招工程、算法、數據等各方向精英,發送簡歷至cuitao@meituan.com,共同支持百億級流量的高可靠系統研發與優化。