新聞中心
注:陌陌爭霸的數(shù)據(jù)庫部分我沒有參與具體設(shè)計(jì),只是參與了一些討論和提出一些意見。 在出現(xiàn)問題的時(shí)候,也都是由肥龍、曉靖、Aply 同學(xué)判斷研究解決的。所以我對 Redis 的判斷大多也從他們的討論中聽來,加上自己的一些猜測,并沒有去仔細(xì)閱讀 Redis 文檔和閱讀 Redis 代碼。

10余年的壽縣網(wǎng)站建設(shè)經(jīng)驗(yàn),針對設(shè)計(jì)、前端、開發(fā)、售后、文案、推廣等六對一服務(wù),響應(yīng)快,48小時(shí)及時(shí)工作處理。成都營銷網(wǎng)站建設(shè)的優(yōu)勢是能夠根據(jù)用戶設(shè)備顯示端的尺寸不同,自動(dòng)調(diào)整壽縣建站的顯示方式,使網(wǎng)站能夠適用不同顯示終端,在瀏覽器中調(diào)整網(wǎng)站的寬度,無論在任何一種瀏覽器上瀏覽網(wǎng)站,都能展現(xiàn)優(yōu)雅布局與設(shè)計(jì),從而大程度地提升瀏覽體驗(yàn)。成都創(chuàng)新互聯(lián)公司從事“壽縣網(wǎng)站設(shè)計(jì)”,“壽縣網(wǎng)站推廣”以來,每個(gè)客戶項(xiàng)目都認(rèn)真落實(shí)執(zhí)行。
雖然我們最終都解決了問題,但本文中說描述的技術(shù)細(xì)節(jié)還是很有可能與事實(shí)相悖,請閱讀的同學(xué)自行甄別。
在陌陌爭霸之前,我們并沒有大規(guī)模使用過 Redis 。只是直覺上感覺 Redis 很適合我們的架構(gòu):我們這個(gè)游戲不依賴數(shù)據(jù)庫幫我們處理任何數(shù)據(jù),總的數(shù)據(jù)量雖然較大,但增長速度有限。由于單臺服務(wù)機(jī)處理能力有限,而游戲又不能分服, 玩家在任何時(shí)間地點(diǎn)登陸,都只會看到一個(gè)世界。
所以我們需要有一個(gè)數(shù)據(jù)中心獨(dú)立于游戲系統(tǒng)。而這個(gè)數(shù)據(jù)中心只負(fù)責(zé)數(shù)據(jù)中轉(zhuǎn)和數(shù)據(jù)落地就可以了。Redis 看起來就是最佳選擇,游戲系統(tǒng)對它只有按玩家 ID 索引出玩家的數(shù)據(jù)這一個(gè)需求。
我們將數(shù)據(jù)中心分為 32 個(gè)庫,按玩家 ID 分開。不同的玩家之間數(shù)據(jù)是完全獨(dú)立的。在設(shè)計(jì)時(shí),我堅(jiān)決反對了從一個(gè)單點(diǎn)訪問數(shù)據(jù)中心的做法,堅(jiān)持每個(gè)游戲服務(wù)器節(jié)點(diǎn)都要多每個(gè)數(shù)據(jù)倉庫直接連接。因?yàn)樵谶@里制造一個(gè)單點(diǎn)毫無必要。
根據(jù)我們事前對游戲數(shù)據(jù)量的估算,前期我們只需要把 32 個(gè)數(shù)據(jù)倉庫部署到 4 臺物理機(jī)上即可,每臺機(jī)器上啟動(dòng) 8 個(gè) Redis 進(jìn)程。一開始我們使用 64G 內(nèi)存的機(jī)器,后來增加到了 96G 內(nèi)存。實(shí)測每個(gè) Redis 服務(wù)會占到 4~5 G 內(nèi)存,看起來是綽綽有余的。
由于我們僅僅是從文檔上了解的 Redis 數(shù)據(jù)落地機(jī)制,不清楚會踏上什么坑,為了保險(xiǎn)起見,還配備了 4 臺物理機(jī)做為從機(jī),對主機(jī)進(jìn)行數(shù)據(jù)同步備份。
Redis 支持兩種 BGSAVE 的策略,一種是快照方式,在發(fā)起落地指令時(shí),fork 出一個(gè)進(jìn)程把整個(gè)內(nèi)存 dump 到硬盤上;另一種喚作 AOF 方式,把所有對數(shù)據(jù)庫的寫操作記錄下來。我們的游戲不適合用 AOF 方式,因?yàn)槲覀兊膶懭氩僮鲗?shí)在的太頻繁了,且數(shù)據(jù)量巨大。
第一次事故出在 2 月 3 日,新年假期還沒有過去。由于整個(gè)假期都相安無事,運(yùn)維也相對懈怠。
中午的時(shí)候,有一臺數(shù)據(jù)服務(wù)主機(jī)無法被游戲服務(wù)器訪問到,影響了部分用戶登陸。在線嘗試修復(fù)連接無果,只好開始了長達(dá) 2 個(gè)小時(shí)的停機(jī)維護(hù)。
在維護(hù)期間,初步確定了問題。是由于上午一臺從機(jī)的內(nèi)存耗盡,導(dǎo)致了從機(jī)的數(shù)據(jù)庫服務(wù)重啟。在從機(jī)重新對主機(jī)連接,8 個(gè) Redis 同時(shí)發(fā)送 SYNC 的沖擊下,把主機(jī)擊毀了。
這里存在兩個(gè)問題,我們需要分別討論:
問題一:從機(jī)的硬件配置和主機(jī)是相同的,為什么從機(jī)會先出現(xiàn)內(nèi)存不足。
問題二:為何重新進(jìn)行 SYNC 操作會導(dǎo)致主機(jī)過載。
問題一當(dāng)時(shí)我們沒有深究,因?yàn)槲覀儧]有估算準(zhǔn)確過年期間用戶增長的速度,而正確部署數(shù)據(jù)庫。數(shù)據(jù)庫的內(nèi)存需求增加到了一個(gè)臨界點(diǎn),所以感覺內(nèi)存不足 的意外發(fā)生在主機(jī)還是從機(jī)都是很有可能的。從機(jī)先掛掉或許只是碰巧而已(現(xiàn)在反思恐怕不是這樣, 冷備腳本很可能是罪魁禍?zhǔn)祝T缙谖覀兪嵌〞r(shí)輪流 BGSAVE 的,當(dāng)數(shù)據(jù)量增長時(shí),應(yīng)該適當(dāng)調(diào)大 BGSAVE 間隔,避免同一臺物理機(jī)上的 redis 服務(wù)同時(shí)做 BGSAVE ,而導(dǎo)致 fork 多個(gè)進(jìn)程需要消耗太多內(nèi)存。由于過年期間都回家過年去了,這件事情也被忽略了。
問題二是因?yàn)槲覀儗χ鲝耐降臋C(jī)制了解不足:
仔細(xì)想想,如果你來實(shí)現(xiàn)同步會怎么做?由于達(dá)到同步狀態(tài)需要一定的時(shí)間。同步最好不要干涉正常服務(wù),那么保證同步的一致性用鎖肯定是不好的。所以 Redis 在同步時(shí)也觸發(fā)了 fork 來保證從機(jī)連上來發(fā)出 SYNC 后,能夠順利到達(dá)一個(gè)正確的同步點(diǎn)。當(dāng)我們的從機(jī)重啟后,8 個(gè) slave redis 同時(shí)開啟同步,等于瞬間在主機(jī)上 fork 出 8 個(gè) redis 進(jìn)程,這使得主機(jī) redis 進(jìn)程進(jìn)入交換分區(qū)的概率大大提高了。
在這次事故后,我們?nèi)∠?slave 機(jī)。因?yàn)檫@使系統(tǒng)部署更復(fù)雜了,增加了許多不穩(wěn)定因素,且未必提高了數(shù)據(jù)安全性。同時(shí),我們改進(jìn)了 bgsave 的機(jī)制,不再用定時(shí)器觸發(fā),而是由一個(gè)腳本去保證同一臺物理機(jī)上的多個(gè) redis 的 bgsave 可以輪流進(jìn)行。另外,以前在從機(jī)上做冷備的機(jī)制也移到了主機(jī)上。好在我們可以用腳本控制冷備的時(shí)間,以及錯(cuò)開 BGSAVE 的 IO 高峰期。
第二次事故最出現(xiàn)在最近( 2 月 27 日)。
我們已經(jīng)多次調(diào)整了 Redis 數(shù)據(jù)庫的部署,保證數(shù)據(jù)服務(wù)器有足夠的內(nèi)存。但還是出了次事故。事故最終的發(fā)生還是因?yàn)閮?nèi)存不足而導(dǎo)致某個(gè) Redis 進(jìn)程使用了交換分區(qū)而處理能力大大下降。在大量數(shù)據(jù)擁入的情況下,發(fā)生了雪崩效應(yīng):曉靖在原來控制 BGSAVE 的腳本中加了行保底規(guī)則,如果 30 分鐘沒有收到 BGSAVE 指令,就強(qiáng)制執(zhí)行一次保障數(shù)據(jù)最終可以落地(對這條規(guī)則我個(gè)人是有異議的)。結(jié)果數(shù)據(jù)服務(wù)器在對外部失去響應(yīng)之后的半小時(shí),多個(gè) redis 服務(wù)同時(shí)進(jìn)入 BGSAVE 狀態(tài),吃光了內(nèi)存。
花了一天時(shí)間追查事故的元兇。我們發(fā)現(xiàn)是冷備機(jī)制惹的禍。我們會定期把 redis 數(shù)據(jù)庫文件復(fù)制一份打包備份。而操作系統(tǒng)在拷貝文件時(shí),似乎利用了大量的內(nèi)存做文件 cache 而沒有及時(shí)釋放。這導(dǎo)致在一次 BGSAVE 發(fā)生的時(shí)候,系統(tǒng)內(nèi)存使用量大大超過了我們原先預(yù)期的上限。
這次我們調(diào)整了操作系統(tǒng)的內(nèi)核參數(shù),關(guān)掉了 cache ,暫時(shí)解決了問題。
經(jīng)過這次事故之后,我反思了數(shù)據(jù)落地策略。我覺得定期做 BGSAVE 似乎并不是好的方案。至少它是浪費(fèi)的。因?yàn)槊看?BGSAVE 都會把所有的數(shù)據(jù)存盤,而實(shí)際上,內(nèi)存數(shù)據(jù)庫中大量的數(shù)據(jù)是沒有變更過的。一目前 10 到 20 分鐘的保存周期,數(shù)據(jù)變更的只有這個(gè)時(shí)間段內(nèi)上線的玩家以及他們攻擊過的玩家(每 20 分鐘大約發(fā)生 1 到 2 次攻擊),這個(gè)數(shù)字遠(yuǎn)遠(yuǎn)少于全部玩家數(shù)量。
我希望可以只備份變更的數(shù)據(jù),但又不希望用內(nèi)建的 AOF 機(jī)制,因?yàn)?AOF 會不斷追加同一份數(shù)據(jù),導(dǎo)致硬盤空間太快增長。
我們也不希望給游戲服務(wù)和數(shù)據(jù)庫服務(wù)之間增加一個(gè)中間層,這白白犧牲了讀性能,而讀性能是整個(gè)系統(tǒng)中至關(guān)重要的。僅僅對寫指令做轉(zhuǎn)發(fā)也是不可靠的。因?yàn)槭ズ妥x指令的時(shí)序,有可能使數(shù)據(jù)版本錯(cuò)亂。
如果在游戲服務(wù)器要寫數(shù)據(jù)時(shí)同時(shí)向 Redis 和另一個(gè)數(shù)據(jù)落地服務(wù)同時(shí)各發(fā)一份數(shù)據(jù)怎樣?首先,我們需要增加版本機(jī)制,保證能識別出不同位置收到的寫操作的先后(我記得在狂刃中,就發(fā)生過數(shù)據(jù)版本錯(cuò) 亂的 Bug );其次,這會使游戲服務(wù)器和數(shù)據(jù)服務(wù)器間的寫帶寬加倍。
最后我想了一個(gè)簡單的方法:在數(shù)據(jù)服務(wù)器的物理機(jī)上啟動(dòng)一個(gè)監(jiān)護(hù)服務(wù)。當(dāng)游戲服務(wù)器向數(shù)據(jù)服務(wù)推送數(shù)據(jù)并確認(rèn)成功后,再把這組數(shù)據(jù)的 ID 同時(shí)發(fā)送給這個(gè)監(jiān)護(hù)服務(wù)。它再從 Redis 中把數(shù)據(jù)讀回來,并保存在本地。
因?yàn)檫@個(gè)監(jiān)護(hù)服務(wù)和 Redis 1 比 1 配置在同一臺機(jī)器上,而硬盤寫速度是大于網(wǎng)絡(luò)帶寬的,它一定不會過載。至于 Redis ,就成了一個(gè)純粹的內(nèi)存數(shù)據(jù)庫,不再運(yùn)行 BGSAVE 。
這個(gè)監(jiān)護(hù)進(jìn)程同時(shí)也做數(shù)據(jù)落地。對于數(shù)據(jù)落地,我選擇的是 unqlite ,幾行代碼就可以做好它的 Lua 封裝。它的數(shù)據(jù)庫文件只有一個(gè),更方便做冷備。當(dāng)然 levelDB 也是個(gè)不錯(cuò)的選擇,如果它是用 C 而不是 C++ 實(shí)現(xiàn)的話,我會考慮后者的。
和游戲服務(wù)器的對接,我在數(shù)據(jù)庫機(jī)器上啟動(dòng)了一個(gè)獨(dú)立的 skynet 進(jìn)程,監(jiān)聽同步 ID 的請求。因?yàn)樗恍枰幚砗芎唵螏讉€(gè) Redis 操作,我特地手寫了 Redis 指令。最終這個(gè)服務(wù) 只有一個(gè) lua 腳本 ,其實(shí)它是由三個(gè) skynet 服務(wù)構(gòu)成的,一個(gè)監(jiān)聽外部端口,一個(gè)處理連接上的 Redis 同步指令,一個(gè)單點(diǎn)寫入數(shù)據(jù)到 unqlite 。為了使得數(shù)據(jù)恢復(fù)高效,我特地在保存玩家數(shù)據(jù)的時(shí)候,把恢復(fù)用的 Redis 指令拼好。這樣一旦需要恢復(fù),只用從 unqlite 中讀出玩家數(shù)據(jù),直接發(fā)送給 Redis 即可。
有了這個(gè)東西,就一并把 Redis 中的冷熱數(shù)據(jù)解決了。長期不登陸的玩家,我們可以定期從 Redis 中清掉,萬一這個(gè)玩家登陸回來,只需要讓它幫忙恢復(fù)。
曉靖不喜歡我依賴 skynet 的實(shí)現(xiàn)。他一開始想用 python 實(shí)現(xiàn)一個(gè)同樣的東西,后來他又對 Go 語言產(chǎn)生了興趣,想借這個(gè)需求玩一下 Go 語言。所以到今天,我們還沒有把這套新機(jī)制部署到生產(chǎn)環(huán)境。
原文地址。獲作者授權(quán)轉(zhuǎn)載。
文章標(biāo)題:談?wù)勀澳盃幇栽跀?shù)據(jù)庫方面踩過的坑(Redis篇)
轉(zhuǎn)載注明:http://m.5511xx.com/article/djcogcd.html


咨詢
建站咨詢
