某百萬DAU遊戲的服務端優化工作
作者:水風 本文首發知乎
https://zhuanlan.zhihu.com/p/341855913
現在在一款百玩DAU的遊戲專案中工作,由於怕玩家找上來聊一些非技術的工作,就不報名字了,只討論技術問題。
我們遊戲是一款基於skynet的通服遊戲,開房間的遊戲架構,預計單服可承載60w同時線上。
遊戲目前已經上線兩年了,上線後我們做了不少服務端優化的工作。
這篇文章主要介紹遊戲服務端優化的一些方法,主要以介紹思想為主,為了舉例更容易理解,很多實現思路並不是我們遊戲的,這裡也是作為例子說明。
1、所有的程式碼都要能熱更
所有的程式設計師都寫過bug,bug是難免的,但是我們可以儘量降低bug對玩家的影響。
而當程式碼上線後,若發現bug,為了不影響玩家體驗,不能通過重啟只能通過熱更去修復。
我們在skynet的基礎上,做了多種熱更方案,基本上能保證所有的程式碼都能被熱更。而且,可熱更,是我們新開發功能時的一個必須考慮的事情。
我們基於skynet、lua和服務端架構,做了以下熱更方案:
- sharedata.update,熱更策劃配置表。(skynet支援)
- lua模組的熱更:我們在玩家邏輯中,將狀態統一放在一個資料模組中,將邏輯放在其他模組。這樣,邏輯模組就可以被隨時替換。在skynet中通過cache.clear()清理快取程式碼,然後將所有的玩家服務的指定邏輯模組替換即可。
- service滾動更新:若一個service是不斷銷燬並且不斷建立新的,就可以使用這種方案。比如一場戰鬥的房間,或者玩家service在玩家登陸時建立玩家登出後銷燬(我們其實是有記憶體池的,但是可以通過清空記憶體池並且回收現有的服務)。在skynet中通過cache.clear()清理快取程式碼即可,新的service會自動使用新的程式碼。這個更新方案有個壞處,就是無法實時的立刻更新已建立的service,而新的service會立刻生效。
- 程式級別的重啟或滾動更新:若程式重啟不會影響到整體遊戲叢集,那麼可以通過重啟程式來更新程式碼。比如我們的登陸伺服器,可以先通過負載均衡器將流量從某一個登陸服轉給其他的,然後等沒有流量後重啟,然後依次重啟其他的。玩家邏輯程式也是如此,可以先不在分配新玩家登陸某玩家程式,然後將當前程式玩家遷移到其他程式,然後重啟之,依次執行滾動更新即可。
- inject:skynet也支援了替換某函式,所以可以通過寫一個新的函式替換老的函式。此方案理論上可以熱更所有程式碼,但inject程式碼寫起來麻煩一些,尤其是很長的函式。
通過以上熱更方案,基本能覆蓋全部情況。
一般只有我們發現只能通過inject但想修改的函式非常長或者修改非常複雜覺得不穩妥的時候,我們才會去通過重啟修復線上問題(此情況在我們遊戲中極少)。
2、儘量詳盡的日誌
完善的日誌非常重要,為處理線上問題定位線上bug提供基礎,也為運營查問題提供支援。此外,也可以將日誌用於下文要說的監控和報警。
日誌這東西,不用時沒感覺,用的時候就後悔:為啥不在這裡加一條日誌,為啥不把這個資訊列印出來。
如何記錄日誌,沒有一個統一的標準,我這裡說一下我的經驗和思考,這裡主要介紹非戰鬥邏輯的日誌。
哪些地方寫日誌:
- 玩家一個行為對玩家狀態產生改變,這個邏輯期間至少一條日誌。
- 玩家某些行為投放物品,一般投放前。
- 一些重要的狀態切換,比如一個戰鬥房間由準備進入開戰狀態。
- 任何錯誤和異常,都需要加一條錯誤日誌,防禦性程式設計檢測到錯誤也需要。
日誌資訊,日誌需要記錄什麼:
- 日誌的基本業務描述,一般幾個關鍵字。比如投放武器
- 邏輯涉及的上下文關鍵資訊。比如投放武器的 武器資訊,比如武器所屬的玩家ID
- 其他資訊,比如服務端程式資訊、時間戳、日誌所屬entity等。
日誌等級常見的又debug、info、error、fatal。一般表示的含義是:
- debug:開發環境列印,線上環境不列印。一般用於程式開發。(這裡有個坑,debug日誌一般的實現是線上進行過濾,但這個函式的引數還是會執行,所以如果是超級長的字串操作要注意效能問題)
- info:線上環境列印,我們說的日誌大部分都是這個級別。
- error:和log類似,只是是錯誤資訊。一般是業務預期內的錯誤,不會影響系統正常執行。
- fatal:業務預期外的錯誤,表示這類錯誤資訊需要開發人員去關注和排查。
對於戰鬥,最好的方案是回放錄影,日誌一般是次選項。
3、監控和報警
通過監控,我們要對線上伺服器的情況儘量的瞭解,並且能提前發現問題,防止問題擴大化後才知道。報警和監控相輔相成,監控到異常後,馬上報警,我們就能立刻對線上問題進行處理。
我們用到的監控/報警有:
- 慢響應:監控客戶端請求的平均響應速度,一般能整體評估遊戲伺服器是否有卡頓。
- FATAL日誌:有些特殊情況需要馬上通知開發人員,比如伺服器開服失敗等,服務端會列印FATAL日誌。這時候就需要報警通知。
- 線上traceback:一般用指令碼寫的服務端邏輯,都會有一些線上traceback,這個是需要重點關注的問題。發現有trace就需要去確認traceback的影響,若影響玩家功能需要熱更修復。
- 投放監控:我們遊戲對物品投放也有監控,防止玩家因為某些bug可以刷某類物品。由策劃對每類物品設定一個報警上限,玩家當日獲取此類物品超過閾值會通知到我們。我們會去檢查玩家行為看是否符合預期。
- 玩家行為監控:除了物品獲取,也有些玩家行為需要監控。比如我們遊戲中每天打懸賞令的次數,按照策劃的設計每天不會太多。超過某個數量說明可能有問題,需要人工審查。
- 服務端效能監控:我們會監控單點服務cpu使用率,skynet中一個服務只使用一個執行緒,單點服務cpu佔用上限是一個核,所以比較容易出現問題。我們若發現單點服務的cpu使用率超過閾值,就會報警,可以提前進行效能優化。
- 服務端機器監控:監控機器、資料庫的硬體資訊,比如cpu、記憶體等。超過閾值進行報警。
- 阿里雲費用監控:對按量付費的專案,我們會對其進行監控,若某一天的消費超過某一閾值,則進行報警。
總之,通過監控以及與之對應的報警,能提前發現線上問題,降低影響。大事化小,小事化無。
4、容錯:保底邏輯
大型分散式叢集必須要考慮容錯問題,容錯分為幾個層面:
- 架構容錯(機器當機、程式crash):主要通過消除單點等方式,後文會介紹。
- DB等Saas服務卡頓/閃斷:做好斷線重連、重試等容錯邏輯。
- 邏輯容錯:有些邏輯系統中難免出現異常、bug或者超出預期的情況,在服務端中,對於這些問題儘量寫一些保底邏輯,當出現問題時,能降低問題造成的影響。
下面主要介紹一下邏輯容錯的相關情況:
- 系統異常:某些非核心服務出現異常(卡死、crash、網路中斷)可以之關閉異常服務,保證系統整體正常執行。
- bug:比如某款遊戲中的反外掛邏輯特別複雜,有時候會因為改動了某些戰鬥機製造成外掛的誤判,把正常玩家判為外掛玩家,這種bug比較難完全避免並且會造成大規模玩家封號等較大的影響。因此,可以增加了一個保底邏輯,每小時因外掛封號的玩家數量超過一個閾值以後,就不再直接封號,而是報警並且把這些玩家記錄下來,去人工檢查後確認是否有問題,有問題再人工操作封號。這個保底邏輯一方面可以報警讓我們快速發現外掛檢測bug,另一方面即使出現了bug也能降低影響人數。
- 玩家行為超出預期:比如,有些遊戲會記錄玩家的歷史好友(刪掉的),這個記錄會隨著玩家行為變得越來越長。若完全沒有限制,當客戶端請求或者邏輯遍歷歷史好友列表時,會造成卡頓。因此,這種情況最好設一個上限,歷史好友數量超過一個閾值就刪掉之前的。
- 功能開關:開發新功能一定要做好開關,可以隨時線上關閉功能。若出了問題,可以關閉功能後慢慢修復,不影響玩家玩其他功能。
5、非同步提交
玩家有些操作不需要等待等待邏輯執行完成返回響應,這種操作可以將任務提交到佇列中然後非同步執行。這種方案的好處是即使任務處理能力不足,不會影響到玩家造成玩家卡頓。
我們曾經開發過一個副本成績排行榜,排行榜的上榜規則比較複雜,當玩家打完副本後會將戰鬥成績提交到排行榜,排行榜通過一系列的邏輯將成績插入到排行榜中。當我們開服後,玩家大量湧入這個新玩法,此外,由於排行榜是空的,大量成績都會進入排行榜中,造成排行榜卡頓,導致玩家完成戰鬥後提交成績時卡死。
後來,我們將其改為玩家打完副本後將成績提交到排行榜中,但不等待排行榜的響應。這樣,當排行榜邏輯卡頓的時候,只是有可能成績上榜會延遲,但不影響玩家體驗。
以skynet為例,儘量用skynet.send替代skynet.call。若發現skynet.call沒有返回值時,就去判斷一下call的邏輯和下文邏輯是否有順序依賴關係,若沒有依賴關係就可以改為send。
這其實就是訊息佇列的思想,通過非同步處理提高系統效能和削峰、降低系統耦合性,大家可以去百度“訊息佇列”詳細瞭解。
6、消除單點和水平擴充套件
一個遊戲伺服器叢集的承載上限,就是叢集中的邏輯單點的承載上限。所以,在遊戲伺服器架構設計中,要儘量的消除單點,改為支援水平擴充套件。
伺服器叢集中存在單點的常見原因是因為資料需要統一管理,比如玩家管理器、家族管理器等,需要管理所有玩家或者所有家族。
這種情況有兩種解決方案:
- 加一層分發邏輯:比如我們之前家族管理器管理所有家族的資訊以及相關的邏輯,後來就扛不住了。然後我們抽象出來了familymaster和familynode,每個familynode管理部分家族,familynode可以無限的水平擴充套件。familymaster依然是單點,但是他只是記錄每個家族在哪個familynode上面,所以承載上限很高。
- 使用無狀態:將資料和邏輯分離,資料放在redis/db中,邏輯執行都去讀寫db。這種方案理論上是可以無限擴充套件的,因為db是支援無限擴充套件的,但是要求狀態(資料)相對比較簡單,容易存在db中並且可以高效讀寫。比如上文提到的familymaster依然是單點,但只管理familyid到familynode的資訊,這個資訊我們就可以存在redis中,然後每次讀寫都去操作redis,這樣就達到了理論上的無限擴充套件。
一般來說,遊戲伺服器並不要求完全的消除單點,因為需要做很多額外的事情,要麼增加開發成本,要麼增加運維成本。所以,只要我們的單點承載上限超出遊戲玩家量的需求,就可以了。不要過度優化。
消除單點一方面可以帶來承載量的提升(高併發),另一方面可以提高可用性(高可用)。通過消除單點,一個功能可能分佈在多個程式/機器上,即使某個程式掛了,其他程式也可以使用,仍然可以提供服務。當然,寫程式碼時需要處理這種異常才可以獲得高可用性。
我們遊戲的服務端簡化版架構如下圖所示,我們的玩家邏輯、戰鬥邏輯和家族邏輯都是可以水平擴充套件的。而只有一些管理全服資訊的邏輯(比如維護玩家再哪個程式上)才會放在管理器裡,管理器是伺服器的單點,也是伺服器承載量的瓶頸。
伺服器架構(簡化版)
7、功能解耦和隔離
根據KISS(Keep It Stupid Simple)原則,應該將功能儘量的拆分成小的程式碼模組。這個原則對應到遊戲伺服器就是要將功能儘量的拆分成一個個服務,每個服務都只負責一小塊功能。
Skynet提供了比較好的模組解耦模式:service模式,skynet中每個service就可以對應一個物理意義上的服務,而每個service就是一個執行緒,同程式service之間具有一定的隔離。而不同service可以放在一個程式,也可以放在不同的程式,提供了不同的隔離級別。
KISS原則我是基本贊成的,但是我認為遊戲的玩家個人邏輯應該放在一個服務中,若拆為多個服務會造成服務間耦合嚴重。比如玩家升級,往往涉及到揹包、屬性、代幣等不同模組。這個地方更合適用程式碼模組來區分開,但執行時屬於一個服務。
除了玩家個人邏輯,其他功能可以適當的拆分,比如好友服務、聊天服務、排行榜服務等。
將功能拆為一個個服務以後,就需要考慮如何隔離。隔離方式skynet支援執行緒隔離和程式隔離,有的單執行緒伺服器可能只支援程式隔離。
- 執行緒隔離的優點在於不同服務執行在同一程式,呼叫是函式呼叫,不存在失敗的概念,缺點是一定程度上違反了KISS原則,並且服務之間隔離度低某些情況仍會互相影響
- 程式隔離優點在於程式功能更單一明確,隔離度高不會互相影響,但服務間通訊變為網路通訊更復雜,此外,每個服務一個程式,會造成程式數量龐大,管理和維護成本高。
以前我曾基於python寫過遊戲微服務,因為python只支援單執行緒,所以每個程式只能承載一個服務。這種模式主要存在兩個問題,一、服務間的呼叫請求都是網路rpc,都存在失敗的可能,給業務開發造成了很大的成本。二、程式數量很多,因為一類服務往往又多個例項,每個例項都是一個程式,程式數量為N*M,程式數量多造成治理困難。
skynet這種模式就比較好,一個程式可以承載很多服務例項,每個服務例項一個執行緒,服務之間基於執行緒進行隔離。不同的服務可以放在一個程式中,一個程式也可以承載多個相同或者不同型別的服務例項。
那麼,在skynet模式中,什麼情況使用執行緒隔離,什麼情況使用程式隔離呢?
- 首先根據物理含義,將服務進行分組,同組服務放在同一程式。比如玩家服務、家族服務、登陸服務等。這個主要是將不同的核心服務進行隔離,也考慮容易管理。
- 對於效能消耗高的服務,進行隔離。防止打滿CPU影響其他服務。
- 對於不穩定的服務,進行隔離。比如某服務使用了沒有被廣泛驗證的C擴充套件,crash概率就會高很多。
8、引入超時
通過上文介紹的服務拆分和隔離,我們將服務端進行了拆分,拆分後我們希望對某些服務中的異常進行進一步的隔離。
skynet把叢集看作一個整體,所以通過skynet.call呼叫其他程式函式並等待返回預設是無限等待的,沒有timeout。
這樣就導致若某個模組卡頓或者出現了異常,就會導致叢集雪崩,影響到所有的功能。
比如我們遊戲的chat模組,曾因為某些問題導致程式卡頓,而玩家登入都會去註冊和拉取聊天訊息,進而導致玩家無法登入,也無法正常遊戲。
我們的聊天功能在前期設計的時侯設計的比較複雜,所以實現方案比較複雜,我看了一遍程式碼後覺得重構的成本和風險都太高。於是,我們希望即使chat卡頓或異常,也不要影響玩家的正常遊戲,只是讓玩家不能聊天而已。
因此,我們在skynet中增加了timeout機制,支援skynet.call超時。
引入了超時後,也需要增加超時後的邏輯處理。超時可能有三種情況,1.接收方沒有收到請求。2.接收方收到了但是出trace沒有返回響應。3.請求方沒有收到接收方發出的響應。
業務需要處理超時問題,一般有兩種方案:重試或忽略。對於有些關鍵邏輯,需要寫重試邏輯,重試要保證冪等性。對於不重要的邏輯,可以忽略,比如發一個聊天訊息。建議儘量忽略,重試邏輯寫起來很麻煩,而且容易出問題。具體可以參考“分散式事務”相關資訊。
在遊戲的大部分的模組間耦合還是比較重的,所以skynet將叢集認為是一個整體,我覺得是合理的,所以不應該過份解耦。只有一些相對獨立的模組,可以通過解耦防止問題擴散和雪崩。
引入超時後,應該將遊戲系統進行分割,核心業務不使用超時,不然寫超時處理邏輯會非常麻煩。非核心業務加入超時,將核心業務和非核心業務進行解耦。
9、部分資料轉存redis
大部分遊戲都把持久化資料存在mysql或者mongo中。而redis常用於cache等場景,比較少用於持久化儲存。
但redis本身支援RDB和AOF持久化,其實有作為持久化儲存的能力。而有些遊戲資料很小,但存在mysql裡面麻煩。
比如玩家的好友關係資料,一個好友關係涉及兩個玩家,存在任何一個玩家身上都不合理。而如果存在mysql裡面,如果設計不好,可能載入時需要訪問很多次mysql。
這類資料存在redis就很方便,佔用不了多少空間,而且大大提高了訪問速度。我們遊戲千萬量級的註冊玩家,玩家的好友關係資料也不過小几十G。
一般來說,業務上存mysql/Mongo覺得比較麻煩,資料量又不大,訪問頻率很高的,都可以存在redis中。
將Redis作為持久化儲存其實是沒有資料可靠性保證的,所以需要考慮異常問題對遊戲系統的影響。若系統不能接受任何的異常情況,建議還是使用mysql。
此外,還需要考慮回檔問題(雖然永遠不希望遇到)。因為一個玩家的資料分散在了不同的地方,有的在mysql,有的在redis,所以回檔的時候要想辦法回檔到一個點。(阿里雲的企業版Redis也就是Tair,支援精準時間點恢復資料)
10、灰度測試環境
對於一個線上專案,任何的修改都是有風險的,而有些底層的修改(比如資料儲存相關程式碼)可能會涉及到所有的業務邏輯。這種情況若只是讓QA測試某些情況其實是非常不穩的。
因此,我們將某些玩家邏輯程式設為灰度環境,只有指定的玩家可以進入。這樣,我們就可以將某些涉及範圍較大的改動,先在灰度環境中上線,選取某些玩家進入。即使出現問題,也隻影響選區的測試玩家。測試一段時間後,若測試玩家沒有反饋問題,就可以將改動正式上線了。
灰度環境是線上環境,和測試服具有本質區別。因為直接承載線上玩家,所以應用場景和測試服相比限制更多,比如我們只應用於玩家個人邏輯節點,也只測試底層程式碼邏輯,不測試業務邏輯。和測試服比起來優點是比較靈活,不需要部署測試服並且安排玩家進來測試。
我們的灰度環境可以分為多級,比如第一級灰度只能公司內部測試人員進入,新功能剛開始上線時就先放到這個環境。第二級灰度我們線上隨機選取幾百到幾千的玩家進入,一般是經過第一級灰度驗證過的功能。
灰度測試
第一級灰度環境的業務邏輯可以和線上有些許差別,但是第二級灰度因為直接面向外部玩家,所以要求業務上完全一致,一般都是底層的修改。
一級灰度因為只有內部玩家,所以理論上來說可以隨時重啟更新程式碼,所以可以隨時將程式碼上線測試,不用等周版本,比較靈活。
一級灰度還有一些特殊用法,比如線上某個活動出了問題暫時關閉了入口,然後通過熱更修復了。為了驗證線上的修復結果,可以先在灰度環境開啟入口,驗證修復結果。
總之,有了灰度測試環境,可以相對大範圍的驗證一些底層修改,對於線上專案非常重要。而且,可以比較靈活的線上上做一些事情。
11、壓測
一款遊戲上線前應該經過比較詳細的壓測,並且在後續的開發新功能和架構迭代過程中需要持續的進行壓測。
- 壓測主要是為了評估三個內容:
- 驗證在大規模併發請求的環境下邏輯執行的正確性。
- 查詢在大規模併發請求的環境下功能的效能瓶頸和效能熱點。
評估遊戲或功能的承載能力和需求,規劃機器部署需求。
壓測中需要關注的功能點(常出現效能問題的場景):
- 開服:關注登陸和建立賬號,這兩塊邏輯一般都比較複雜。可以增加排隊系統處理這個問題。
- 廣播:比如全服聊天。可以分頻道,也可以服務降級。
- MMO遊戲中玩家聚集:比如國戰類遊戲中的同屏大量玩家聚集。可以優化同步策略,也可以邏輯分線。
- 定時(同時)功能:比如某個活動會同時拉大量玩家進入某個場景。
- 單點服務:最多隻能跑滿一個CPU的服務。
- 資料上限:比如某遊戲曾經因為大量玩家申請某頭部主播好友,導致主播好友申請列表增加了近10w,導致機器直接卡死。
- 資料庫相關:考慮資料庫的承載。
- 全服玩家操作:比如通過命令給全服玩家發郵件。
為了方便壓測,我們做了一套壓測工具,可以支援在容器中快速部署壓測叢集、執行壓測任務並彙總壓測結果。
12、動態擴容和縮容
對於大部分遊戲,都會有玩家線上人數的波動,比如某些活動期間人數很多,但每日凌晨都人數比較少。
我們遊戲週末晚上會搞一些活動,週末晚上活動期間和平時相同時間段相比同時線上上升一倍。如果我們按照最大同時線上部署機器,會造成較大的浪費。
比如下圖,常駐機器承載可以滿足平時的需求,但是到了某些活動期間,就無法滿足需求。這時候,如果支援動態擴容,就可以將機器在活動前增加,活動後回收,既節省了成本,又給玩家更流暢的遊戲體驗。
動態擴容縮容
我們遊戲可以將玩家個人邏輯和戰鬥邏輯程式做到了動態擴容縮容,這類程式佔比最大價效比最高,其他程式沒有支援。
動態擴容縮容需要注意一些點:
- 對於我們這種大DAU遊戲,阿里雲在某些可用區的備用庫存不夠,導致無法啟動動態機器。所以需要考慮跨可用區的支援。
- 動態擴容比較容易,動態縮容需要做一些邏輯處理,需要達到優雅退出的效果。戰鬥服比較容易,戰鬥結束後關閉程式即可,對於我們這種玩家個人邏輯程式需要處理的事情多一些。我們關程式時會分步執行,先將此程式標記為新玩家不可進入,過段時間後再將非戰鬥狀態的玩家踢下線(此步驟玩家無感知),最後強制踢下線所有玩家(此時玩家已經極少),基本做到了玩家無感知。
- 需要有較好的運維流程支援自動化,手動做的話人力成本太高而且容易出錯。
- 最佳的方案是根據線上的情況(比如線上玩家)自動化擴容縮容。
- 這個方法不適合用於自建機房,對於阿里雲/AWS這種按量付費機器支援的較好的雲提供商比較適合。
13、cache
大部分效能問題都可以通過cache來解決,空間換時間,多買點記憶體,讓玩家玩的爽一點,很值。
增加cache,需要考慮兩個點:cache存放位置和cache更新策略。
13.1 cache存放位置
常見的存放cache的位置有:
- 貼近讀取資料的實體(消費者)
- 貼近生產資料的實體(生產者)
- 生產者和消費者之間
- 第三方,比如redis
假設一個場景:玩家需要去拉取全服的一個排行榜,而這個排行榜的計算可能是很重度的計算,所以每次拉取都重新計算不可取。
服務端架構如下圖所示,全服排行榜負責計算生成排行榜,每個玩家程式中管理很多個玩家entity,每個玩家都會去全服排行榜中請求排行榜資訊。
上面說的四種位置,在這個場景下的對應關係如下:
- 貼近消費者:存在玩家entity中,每個玩家都有自己的cache。
- 貼近生產者:存在全服排行榜,cache全服有效,所有的玩家共享cache。
- 消費者和生產者之間:存在玩家程式中,每個玩家程式中的所有玩家共享cache。不同玩家程式之間的玩家不共享。
- redis:將生成的排行榜資料存在redis,全服玩家共享。
說一下四種存放位置的優缺點和應用場景:
- 貼近消費者:若消費者消費頻率特別高,且不同消費者資料不同,可以存在消費者這邊。這種情況其實比較少。
- 貼近生產者:這種情況比較多,一般是為了通過空間換事件,是常見的方案。
- 消費者和生產者之間:這種情況一般是全部消費者的整體消費頻率特別高,為了防止給單點壓力太大,所以存在中間,降低壓力。
- redis:這個和貼近生產者差不多,最大的區別在於,redis可以與伺服器解耦,伺服器重啟,redis的資料也存在。常見的情況比如存玩家的簡要資訊(供其他玩家檢視)。
當然,cache也可以在不同的地方同時存在,也就是多級cache。這種情況一般可以獲得更好的效率,但需要針對每一級cache定義維護和更新策略,邏輯更加複雜,bug更難查。
13.2 cache更新/失效策略
cache的引入一般是為了解決效能問題,但也並不是沒有成本。成本就在於需要管理cache,也就是決定cache什麼時侯失效和更新,增加了程式設計的複雜性。
生存時間(ttl,time to live)
cache最常見的更新策略是使用生存時間ttl,即快取超過一定的時間後自動失效,然後重新計算或者去資料來源拉取。比如域名解析中就是用ttl控制DNS伺服器中域名解析資訊快取失效。
這種策略最簡單,建議優先使用這種策略。
主動更新cache
這種策略是cache的生產者主動去更新cache,這種更新策略思想類似寫擴散。
比如遊戲常見的玩家簡要資訊cache,這種cache一般是玩家更新自己的資訊時,就去更新自己的簡要資訊。(當然,不一定完全實時)
這種策略一般是要求cache的實時性要求比較高,但是又不希望所有的請求都打到資料生產者中執行。
關於這類思想,大家可以去搜尋“讀擴散/寫擴散”來了解更多的內容。
固定cache空間
某些場景下,cache可用的空間是有限的, 在有限空間的前提下,我們希望儘量的提升cache空間的利用效率。當可用空間沒有用盡時,cache一直不會失效,當可用空間用盡後,以一定的策略去將某些cache失效,以獲得空間給新的cache。最常見的是LRU策略。
因為硬體資源是有限的,這種策略也常見於硬體和系統層,比如虛擬記憶體的管理,比如mysql等資料庫將部分資訊快取在記憶體中以提高查詢效率,比如Redis記憶體空間用盡後記憶體淘汰。
這種cache的管理方式業務邏輯中用的比較少,偶爾配合其他策略一起使用,增加保底機制防止cache所佔用的記憶體空間過大。
常見的策略有LRU和LFU。比如若redis佔用記憶體接近記憶體上限時,會使用類LRU策略淘汰資料。
其他各類策略
cache也可以根據不同的業務場景設定更新和失效策略,比如可以在一個副本中將某些cache設為永不失效,只有在副本結束時才去統一清理。
具體策略根據具體需求可以使用各種花式方案。
後記
一款DAU百萬級的遊戲,而且是已經上線的遊戲,其實優化起來非常困難,真*為一輛高速行駛的汽車換零件。
為了給玩家帶來更好的遊戲體驗,我們做優化計劃時並不保守,但非常謹慎的執行。
如臨深淵,如履薄冰。
附:
公司招人,在杭州,一線薪水,不輸任何其他遊戲公司。
公司快速發展中,機會多多!
服務端、客戶端都要,技術專家、主程都要~
投簡歷請發 yangpengwei@pandadastudio.com
相關文章
- 高效能服務端優化之路服務端優化
- 百萬DAU小遊戲必備7大關鍵流程遊戲
- 人人都能掌握的Java服務端效能優化方案Java服務端優化
- 記一次服務端系統效能優化服務端優化
- 效能優化|Tomcat 服務優化優化Tomcat
- Android效能優化篇之服務優化Android優化
- 簡單優化容器服務優化
- eCPM超130元 百萬DAU的《山海經異變》如何廣告調優與設定
- go語言遊戲服務端開發(三)——服務機制Go遊戲服務端
- 服務端指南 資料儲存篇 | MySQL(05) 索引的排序優化方案服務端MySql索引排序優化
- Swift Perfect服務端的自動化部署Swift服務端
- 百萬資料的對賬優化優化
- 遊戲服務端的高併發和高可用遊戲服務端
- 儲存服務質量優化優化
- 「分散式技術專題」去中心化服務與中心化服務的優劣分散式中心化
- 單頁應用SEO優化非Nodejs服務端渲染的處理方案優化NodeJS服務端
- SimpleRpc-客戶端與服務端工作模型探討RPC客戶端服務端模型
- 小遊戲產品的新風向:經典IP+品牌聯動 次留30% DAU百萬遊戲
- redis自學(47)服務端最佳化Redis服務端
- 遊戲服務端架構發展史(中)遊戲服務端架構
- 遊戲服務端架構發展史(上)遊戲服務端架構
- 百萬資料 mysql count(*)優化MySql優化
- 百萬級資料庫優化資料庫優化
- 服務端指南 服務端概述 | 微服務架構概述服務端微服務架構
- 搜狐服務架構優化實踐架構優化
- 企業級nginx服務優化(一)Nginx優化
- XP服務優化批處理.bat優化BAT
- YApi 服務端測試新增 globalCookie ,相容自動化觸發服務端測試功能API服務端Cookie
- 用 Golang 實現百萬級 Websocket 服務GolangWeb
- 部落格的服務端服務端
- Cheerio,服務端的JQuery。服務端jQuery
- 服務端渲染到前端渲染,再到“服務端渲染”服務端前端
- 高併發服務的幾條優化經驗優化
- Linux中Vsftpd服務的部署及優化LinuxFTP優化
- 客戶端,服務端客戶端服務端
- 服務端,客戶端服務端客戶端
- TCP服務端TCP服務端
- 服務端 unity服務端Unity