https://bbs.gameres.com/thread_703118_1_1.htmlhtml
彈指一揮間從事遊戲相關的開發工做已經十多年。在開發了六年多幾經波折差點放棄的starrydb.com項目也迎來了1.0版本的上線。雖然演示版本以及集羣功能早在17年1月份就開發完畢但修改和完善計劃目前仍然排的滿滿當當,致使1.0上線一推再推至今仍然不甚滿意。提及最初開發這個項目的初衷就像那盞迷霧中的綠光讓人興奮不已,在此也但願由於我的執著而被傷害的人幸福。如下引至《thegreat gatsby》「Gatsby believed in the green light, the orgastic future that year byyearrecedes before us.It eluded us then, but that's no matter--tomorrow we will runfaster, stretch outour arms farther.... And one fine morning---- So we beat on,boats against thecurrent, borne back ceaselessly into the past.」或許人們尚未意識到,遊戲服務器將是繼計算機操做系統以後,現代軟件工程中最復雜的計算系統。通過wow和無數遊戲公司的崛起讓這個系統獲得了飛速發展。最初的遊戲服務器引擎能夠追述到1992年的mudos。這是一個簡單的文字對話系統,但能夠多人在線。由於交互簡單對性能要求不高,普通的服務器就能夠支持上千人同時在線。在接下來的不少年時間內,mudos表明着遊戲服務器引擎的經典原形。咱們能夠把這類服務器引擎統稱爲經典遊戲服務器引擎。 mysql
經典的遊戲服務器引擎的核心構建就邏輯服務器,所謂邏輯服務器就是對用戶的輸入數據進行處理,產生輸出數據返回給用戶。見圖1
由於遊戲邏輯服務器的崩潰問題和內存使用量不穩定,出現服務器不穩定進而致使數據丟失。服務器急需一個穩定的硬盤存儲服務。MySQL出如今了邏輯服務器後面替代邏輯服務器的硬盤。見下圖2
由於mysql數據庫寫入和讀取得速度不快,數據操做須要等待時間的過長,在硬盤數據庫以前又加入了內存數據庫做爲快速讀取的緩存服務器。也許你看出了其中的問題,爲了解決邏輯服務器不穩定丟失數據的問題,引入了緩存服務器和數據存儲服務器,但增長了系統的複雜性,致使開發難度增長進而致使邏輯服務器更加複雜更加容易崩潰。人們花了很長的時間和想了不少的辦法提升邏輯服務器的穩定性。見下圖3![]()
由於服務器崩潰的主要緣由是內存指針的泄漏,爲了解決崩潰的問題產生了各類腳本語言替代指針型語言。可服務器功能愈來愈多,愈來愈複雜,崩潰,卡死,內存泄漏像邏輯服務器上空的陰雲揮之不去。服務器第一要求是穩定,但仍然不能阻止人們對服務器功能無盡的渴求。因而產生了新的想法拆分邏輯服務器擴展服務器承載能力。見下圖4
到這裏經典遊戲服務器發展到了巔峯,其數據處理的複雜度遠高於同期的任何服務器系統。爲什麼說其處理數據的複雜度高於同期的任何服務器系統,由於同期的服務器系統都是服務於現實生活,而遊戲服務器是服務於純粹的虛擬世界。例如說郵寄物品這個事情,在遊戲服務器就是一個指令,也許在聊天過程,也許在戰鬥過程。沒有現實中郵寄的等待過程,需要遊戲服務器馬上就返回。而且能夠發生在任何功能服務器上。例如聊天的過程當中給對方一個物品,這在現實中是絕對不可能發生的。由於功能和功能之間不是絕對的獨立,經典的服務器系統隨着功能的劃分愈來愈細,每個服務器之間的通訊就愈來愈複雜。這樣讓人聯想到了複雜低效的官僚系統。有大量的請求都浪費在了通訊和複雜的溝通上,並且每一個開發人員要當心奕奕,不知道哪一個邏輯會搭錯崩潰掉。在六年前的一個夜晚,看着眼前這愈來愈龐大複雜的怪獸,我像極了唐吉坷德向着風車揮舞着長矛。在某一瞬間一個想法進入大腦,爲何不能一個服務器就把一個玩家的全部請求都處理好呢?這樣就像給每一個玩家配備了一個專職的祕書,任何需求都交給祕書去作,涉及和其餘玩家溝通的事情,祕書會找到其餘玩家的祕書進行溝通。見下圖5
以購買物品爲例1, 玩家A向服務器C發送購買物品的請求,服務器C扣除玩家A的貨幣;2, 服務器C將購買的請求發送給服務器D;3, 服務器D扣除玩家B的物品並添加貨幣並返回結果給玩家B;4, 返回交易結果給玩A;
這個設計模式稱爲「one object do something」,這個設計模式第一個好處就是下降了軟件開發的難度。在按功能分割的服務器中,每一個服務器上都是不一樣功能的代碼。每一個功能之間的聯繫是一種耦合關係。存在耦合關係的代碼就存在不肯定性的風險,每一個功能看似單獨修改都沒有問題,但在一塊兒互相配合就會出現各類不肯定性。Starrydb的「one object do something」的模式中服務器C和服務器D使用代碼是同樣的。這種買賣關係不管是A向B購買仍是B向A購買過程都是同樣的。這樣高內聚的工程在開發調試的過程當中不容易出現錯誤。提升了開發速度和軟件質量,下降了軟件工程的不肯定性。Starrydb的「one objectdo something」的模式的另外一個好處就是極大的方便服務器的擴展。由於每一個服務器的配置都是同樣的,當用戶增長的時候只要添加新的服務器,就能夠知足數據處理的需求。傳統的經典服務器架構,按功能劃分服務器結構的方式就會出現瓶頸,功能不能一直不斷細分。見下圖6
人類是社會動物,咱們每一個人都維繫的着本身的社交圈子。每一個星期給媽媽打電話,每一年一次的家族聚會,晚上去酒吧認識新朋友,和老朋友去海邊度假,在公司與客戶談判等等。現在社交的方式更是多種多樣,經過網絡和視頻連接,咱們更能夠不受地域的限制。對於咱們每一個人來講,社交能夠用以下幾個方面來度量,聯繫人的總數量,每人的總次數,每次的時長,這三方面的乘積獲得咱們社交所投入總時間。假設咱們在單位時間內投入的熱情不變。那麼咱們在必定時間內投入到社交活動的熱情是,人數*次數*時長*熱情。遊戲服務器引擎也是一個小的虛擬社會,每一個玩家都是這個社會的一份子。分佈式的遊戲服務器引擎的對象的性能也能夠套用這個公式。某個對象訪問其餘對象(或被其餘對象訪問)的對象數量,每一個對象的訪問次數,每次訪問的時常和cpu單位時間的運算能力。就能夠推算出這個對象在集羣中消耗的cpu運算能力。若是再乘以這個對象平均佔用的內存數量就一個推算出一個對象在集羣中總的消耗。假定服務器每次處理的時長很是小並較爲平均,cpu單位時間的運算能力也相對固定。那麼每一個對象的與其它對象的通訊次數和通訊對象的數量就成爲決定服務集羣承載能力的關鍵。在不限制對象間通訊的範圍和每一個對象通訊的次數的極端狀況下集羣承載。服務器內玩家的數量爲x,假設每一個玩家都和剩餘的全部玩家有社交關係,那麼服務器每秒鐘處理的消息數量爲x²。假設服務器處理消息數量的上限爲n,那麼須要的服務器數量y=x²/n,承載的人數x=√(ny)。見下圖7:
限制用戶社交頻率的狀況下承載能力與cpu數量成正相關,直到到達單個cpu處理上限。假設單個cpu處理上限是10人,服務器到10以後處理能力會陡然降低出現拒絕服務。見下圖8:
在經典的遊戲服務器引擎中,按功能劃分的模塊分別運行在不一樣的服務器上。這雖然也能夠稱呼爲分佈式,但咱們知道拆分不一樣功能到不一樣的硬件上運行,須要對原有軟件系統進行很大的改動。這種擴展不是線性的而是階梯性的。見下圖10:
系統承載能力的上限是硬件限制,隨着系統功能的增長承載能力逐步降低。但每次提升硬件上限就是拆分功能,都會讓承載能力獲得提升。隨着硬件數量愈來愈多開發複雜度也越來越高。
「Donot , for one repulse , give up the purpose that you resolved to effect .(WilliamShakespeare , British dramatist)」咱們看到想要經過添加服務器達到無限擴展必需要遵循基本的數學準則。限制單位時間處理的人數或者減小數據交換的頻次。這個結果雖然讓人沮喪,但從另外一個方面也讓咱們看到分佈式服務器無限擴展的可能性。到這裏咱們有了一個評價遊戲服務器引擎的數學標準。那麼只要遵循這個標準寫出一個穩定,高性能,可擴展的遊戲服務器引擎就成爲可能。見下圖11:
理想的遊戲服務器引擎的承載能力和硬件性能成正相關,開發難度趨於平行。要達到這樣理想的狀態,下降開發複雜度須要在三個層次上的保證。從下到上分別是穩定,高性能,可擴展。見下圖12![]()
穩定是遊戲服務器引擎的基石。這個穩定的含義也是儘量的減小耦合性,讓計算運行在最簡單可靠的環境內。只有儘量把數據的計算存儲都放在同一臺計算機上。減小每一個計算機節點間的數據交換。用最快的時間處理完每條數據請求。這就是數據和計算越近效率越高系統越穩定。經典的遊戲服務器引擎的數據被保存有三份。邏輯服務器一份,數據緩存一份,硬盤數據庫一份。在系統運行過程當中確認數據一致性和完整性的工做就消耗了大半。而且出現斷線和宕機,很容易破壞數據的一致性和完整性。是潛在破壞服務器穩定性的因素。高性能是服務器穩定以後追求的第二個目標。爲了無限的接近硬件處理的上限,不浪費硬件資源。這彷佛和分佈式系統是相互矛盾的,由於分佈式系統就是要把數據和任務分配給集羣內的每臺服務器,分配的過程必然帶來損耗。但也不能轉頭把高性能寄託在優化開發庫上。由於咱們知道服務器受限於cpu的處理能力。開發庫中添加功能就會影響軟件運行效率,開發庫分割功能就會增長數據複製成本。減小軟件開發難度的根本仍是增強內聚減小耦合,減小工程的複雜度。擴展性是遊戲服務器引擎追求的最高目標,雖然數學準則告訴咱們這樣的追求對於分佈式系統有限制。但創建在線人數和功能的擴展與服務器硬件數量的線性關係仍是很是的必要。經典的遊戲服務器引擎對地圖服務器的擴展不能準確地稱爲創建了線性關係。由於玩家未必會按開發者的意願平均分配到每臺地圖服務器上。在一個如此龐大的系統裏對於數據的安全性又是如何保障的呢?見下圖13
能保障數據安全的並非starrydb,starrydb只是一個系統功能的實現,對於數據安全我認爲任何系統都是不可靠的。Node能夠宕機,link可斷線只要假設硬件是不可靠的,那麼系統就是不可靠的。咱們就要想辦法用系統可靠的一方面去彌補不可靠的一方面。就目前看真正能可靠的就是明確假定不可靠的前提下,在數據邏輯上作到互相檢查互相校驗。實現操做數據邏輯的任務可重入可檢查。任務的可重入是檢查數據完整性一致性的一種安全寫法,可重入的意思是任務的相關數據是記錄式的,例如存入1塊錢,那麼銀行的記錄是存入1塊錢,當前餘額爲2塊錢。對應就是兩條記錄,當前存入的數據,累加的數據。累加數據是爲了方便讀取。那麼對應key-value就應當是3條數據。Key: 任務號 ,value: 順序id;任務號是客戶端生成順序id是服務器生成的由小到大的順序號,方便查詢最後的任務。Key :順序id ,value: 加減值;當前順序id加減的數值。Key :順序id ,value :加減以後的總值;根據上一個id計算出的總值方便查詢。可重入的意識是能夠拿客戶端的任務號從新檢查當前任務是否已經產生順序id是否已經寫入正確的加減值,是否已經產生正確的總值,若是沒有就從新產生記錄。這樣只要客戶端的任務號不變同一個任務不會產生屢次重複操做,保證數據的一致性和完整性。若是任何狀況斷線致使任務中斷,這個交易也會記錄在冊。在用戶界面就會顯示這條交易失敗。能夠由發起方從新手動繼續交易,直到交易完成。一個正常的交易有中斷,失敗,成功3種狀態。這個系統對崩潰的應對措施,如同是雙向交易扣錢的同時得到物品,或則扣物品的同時得到錢。假設兩個對象的數據都同一臺計算機,這個計算機出現回滾。那麼兩個對象的交易數據同時消失,這筆交易就不存在。即不損失錢也不會被扣除物品。若是兩個對象的數據分別在不一樣的計算機,其中一個計算機的運行失敗。每一個用戶都是扣掉錢得到物品,或則扣掉物品獲得錢,至關收支平衡。對於單向交易的A對象扣錢,B對象得到錢。若是A所在服務器回滾到沒有扣錢,那麼B將憑空得到一筆錢。那麼A在扣款以後要等待一段時間,服務器寫入硬盤成功以後,這段時間估計10秒分鐘到30秒。而後再發起給B對象得到金錢的操做。這樣若是A沒有將數據寫入硬盤時,服務器宕機回滾數據,B也不會得到金錢。A能夠從新發起流程完成匯款。到這裏咱們有了一個分佈式的,扁平開發複雜度的,可重入數據的,可線形擴展的遊戲服務器引擎。