用Go語言寫遊戲服務器也有一個多月了,也可以明顯的感覺到二者的區別。這篇文章就是想具體的聊聊其中的區別。固然,在瞭解區別之間,咱們先簡單的瞭解一下Go語言自己。java
PS:這裏僅拿SLG手遊舉例子git
Go語言跟其餘的語言例如Java比起來,算得上一門很年輕的語言。Go語言是由Robert Griesemer、Rob Pike和Ken Thompson於2007年在Google開發。並於2009年正式發佈。程序員
Go語言的設計理念圍繞着簡潔這兩個字,認爲少便是多。若是你熟悉Java,用Java那一套語法命名跟Go作對比,能夠很明顯的體會到這種感受。golang
Go的特色能夠簡單的歸納成如下幾個點。web
首先Go是靜態類型,靜態類型就是編譯時就知道每個變量的類型,得益於此,在編譯的階段就可以發現不少問題。而若是是動態語言,例如JavaScript,有些問題直到運行時才能發現。數據庫
Go是編譯型語言,看到編譯型你們腦子裏可能會想到另一個詞解釋型。二者的區別從字面上來理解其實已經能夠看出來,我用一個簡單的例子來類比一下。編程
顧名思義,你寫的Go源碼在全部的系統都可以運行。瀏覽器
這點其實很好理解,例如Java的口號是"Write once, run anywhere"。咱們都知道Java是編譯型的語言,可是Java在編譯的時候生成的是字節碼,這個字節碼與當前的操做系統無關,與CPU也無關。安全
這種字節碼必須依賴Java虛擬機才能運行,而虛擬機會將操做系統和CPU之間的差別與用戶屏蔽。對於編程的人來講這個過程其實無感知的。而對Java來講,語言自己的跨平臺並不能表明代碼能夠跨平臺。服務器
Go的跨平臺從某種方面來講,與Java類型,咱們須要安裝與當前操做系統相對應版本的Go。編譯出來的可執行文件會根據操做系統的不一樣而有所不一樣。
與JVM同樣,Go在運行時的內存管理(GC)由Go語言自己來管理,不須要程序員的參與,可是咱們能夠干預。
何爲原生?咱們都知道,在Java中若是要實現併發, 須要外部的類庫支持(Thread),而Go不須要從外部再引入任何依賴。支持使用關鍵字go
便可。並且Java中是經過共享內存進行通訊的,熟悉Go的應該都看過一句話「不要經過共享內存來通訊,而應該經過通訊來共享內存」
從獲取、編譯、測試、安裝、運行和分析等一系列流程都有本身的內置工具。例如獲取可使用go get
命令來下載更新指定的代碼包,而且對它們進行編譯和安裝,可使用go build
對源碼進行編譯,用go run
命令來運行Go的程序,用go fmt
來快速格式化代碼,統一代碼風格。
目前主流的編程範式有命令式編程、函數式編程和咱們最熟悉的面向對象編程。在編寫Go的代碼的時候,咱們能夠選擇使用面向對象的方法,也可使用函數式編程的思想,相互結合,相輔相成。
例如,在Go裏面也能夠用接口來描述行爲,也可使用純函數來避免出現反作用。所以,多範式編程就是指這個語言支持多種編程範式的。
使用Go的內置工具go fmt
便可快速的將代碼格式化成官方統一的標準,以此來達到代碼風格統一的目的。甚至能夠用golangci-lint來檢測你的語法跟內置的標準語法是否有衝突,徹底能夠將這個檢測工具掛在git的鉤子上,以此來達到強制的代碼風格統一的目的。
還有一個很重要的特色是,國內的Go的社區十分的活躍,這對於Go在國內的普及起到了很大的做用。
先說一下我對Go語言的見解,我認爲Go在服務器這塊是很是有優點的。之後若是有高併發的應用場景,那麼大機率這個服務就是用Go寫的。不知道你們有沒有發現,摩爾定律正在失效。近十年內,硬件的原始處理能力都沒有太大的提高。顯然,一味的增長晶體管的數量已經不是解決問題最好的方法。
NASA前不久發佈到官網而後又迅速刪掉的文章透露了,Google可能已經實現了量子霸權,通俗一點說就是擁有超越全部傳統計算機的計算能力。而放置更多的晶體管的代價也愈來愈高,因此如今廠商都在向處理器中添加更多的內核來提高性能。
就像你們熟悉的Java,雖然Java自己支持多線程,可是在Java上使用多線程編程代碼算是比較昂貴的。在Java中建立一個新的線程就會消耗接近1M左右的內存。假如你真的須要支持運行上千個線程,那麼服務極可能運行着就OOM了。除了內存消耗外,還會存在因爲支持多線程帶來的併發和死鎖等問題。
而Go中,使用協程來代替線程。並且一個協程所消耗的內存比線程少了不少倍。一樣的物理設備限制,你可能只能啓動最多幾千個線程,而協程可以啓動上百萬個。並且不一樣的Goroutine能夠經過信channel進行安全的通訊。
有些對遊戲服務器的介紹可能會說,遊戲服務器是一個須要長期運行的程序,而後怎麼怎麼樣。我我的認爲Web服務器同樣的須要長期運行,也須要響應不定點不定時來自用戶的請求。二者從宏觀上來看其實沒有本質的區別。同時Web服務器也會對於穩定性和性能有要求,遊戲服通常分爲大小服,咱們這裏都按照小服舉例子。
首先要提到的就是狀態。可能你會據說過一個概念,遊戲服務器是有狀態的,而Web服務器是無狀態的。什麼意思呢?Web服務器的數據流大多直接會到數據庫中。而遊戲服務器的數據流首先會到內存中,而後按期的寫入數據庫(落地)。
換句話說,遊戲服務器自己的數據與數據庫中的數據在運行期間會存在一個數據不一致的窗口。若是此時遊戲服務器宕機了,那麼就會形成數據首先到的內存數據與數據庫存的數據不一致。
而Web服務器則不會有這樣的問題,Web全部的數據狀態都會落地,並且能夠針對操做加上事務,不用擔憂由於操做失敗而引入髒數據。正由於有了狀態的約束,遊戲服務器就會很慎重的使用內存、CPU。以求在資源有限的狀況下,最大化的提升的承載量,而且下降服務延遲。固然,Web服務器會爲了下降某個接口的響應時間而去作對應的優化。
在Web服務器中,若是你不能評估一個服務所面臨的壓力,又不想由於瞬時的熱點訪問致使服務直接不可用的話,徹底能夠設置成自動擴容,由於每一個服務只是單純的接收請求,而後處理請求、返回結果,不會將數據保存在服務器的內存中。要有數據存到內存,那也是在Redis中。而Redis數據丟失對數據的一致性基本沒有影響。
可是在遊戲服務器這邊很難作到像Web那樣靈活。首先,數據的流向不是數據庫,而是內存。
舉個很簡單的例子,玩家的主城被攻打着火了,若是有了自動擴容,頗有可能在落地的窗口內,玩家再請求一次,請求到了另外一個實例。主城又沒有着火了。由於數據都會先存在內存中。
再舉一個例子,玩家氪金買了一個禮包。而後退出遊戲,落地窗口內再次上線沒了。這就不是單純的數據問題了,玩家這是花了真金白銀買的道具,忽然就沒了,一兩個還好處理,若是多個玩家都出現這樣的問題,那這就屬於嚴重的線上事故了。修復數據的工做量十分的大。
因此,對於一個遊戲服務器,所能使用的內存和CPU的資源是很是有限的,不像Web服務器能夠不用花很大的代價作到橫向擴展。這也就是爲何遊戲服務器會十分十分的注重代碼的性能以及穩定性。
就像上面說的例子,若是遊戲服務器運行中出了BUG,致使服務直接不可用,或者說經過這個BUG刷到了大量的道具,將是一個很是嚴重的線上事故。
而對於Web服務器來講,若是是管理系統之類的,有可能會有髒數據值得一提的是,髒數據對於Web來講,排查起來也是一件很頭疼的事情。若是沒有髒數據,只是服務暫且不可用,並且若是用的是微服務架構,重啓服務的代價是相對來講比較小的,只有正在重啓的服務的業務是不可用的,其他的部分則能夠正常的訪問。
而對於遊戲服務器來講,服務器重啓影響的是全服的玩家。玩家在停服期間,甚至連遊戲都進不了,特別的影響玩家體驗。並且,若是停服以前服務器的數據落地出現了問題,服務重啓以後會將數據從數據庫load到內存中,此時一樣會形成數據不一致的問題。
從個人經驗來看,在作Web服務器的時候,沒有爲了減小GC的壓力,爲了少佔用內存去作過多的優化。固然這是由於項目自己的體量不大,若是QPS很高的話,Web服務器一樣很須要注重性能,只不過遊戲服務器須要一直特別注意這個方面。
不過在Web,若是訪問量很大的話致使單個服務不能扛住壓力,大部分人首先想到的解決方案應該就是搞多個實例,畢竟能夠作到很輕鬆的橫向擴展。
在遊戲服務器裏,會把服務器的資源看的至關的寶貴。例如,能不落地的字段就絕對不要落地,某個字段的值能夠經過已知的條件算出來的,就儘可能不要定義在代碼裏。不過這也要看具體狀況權衡運算量和調用的頻率。由於上線以後,若是遇到了數據不一致,維護的數據越少,修復數據的難度就越小。
這一點上來講,我認爲是二者都很關注的一個重點。只不過,在遊戲服務器的某些狀況中,若是服務器拋出異常或者panic。其形成的後果會被遊戲特殊的環境放大。
例如,召回你的在外部隊失敗了,那麼部隊就會一直在外面且不可用。這跟在瀏覽器中點一個按鈕沒有反應比起來,影響相對較小。並且使用微服務架構,在修復問題以後能夠以很低的成原本重啓對應的服務,而遊戲服務器中還要修復一次數據。
再舉一個很極端的例子,點擊商店,玩家要準備氪金了。可是卻發現進不了商店,也可能不能獲取商品列表。這些會直接影響到遊戲的體驗,甚至收入。
而對於Web來講,服務器的穩定性一樣很重要。否則根據業務的不一樣,形成後果的嚴重性也有可能不一樣。影響了用戶體驗,就會直接影響到產品的口碑。
熟悉Web的都知道,數據傳輸格式是JSON。而在遊戲服務器中是Protobuf,是由Google開發的數據傳輸格式,與JSON相似。Protobuf是二進制的,二進制數據量會比JSON更小一點。並且,若是傳輸的字段是空值,就不會被傳輸。而JSON若是是空值,同樣的也會被傳輸。
不管是在什麼樣的環境中,舉個例子,Node.js和Java中,Protobuf的性能表現都比JSON好。在Java中,Protobuf甚至要比JSON快了接近80%。若是Java的服務之間通訊有了性能瓶頸, 能夠考慮服務之間使用RPC來通訊。
可是凡事都具備兩面性。Protobuf的缺點仍然存在:
以上就是這兩個月以來,總結的二者的區別。只是從大致上作了一個對比,並無具體深刻細節。細節的話有可能會在之後單獨的來介紹。
往期文章:
- Go中使用Seed獲得重複隨機數的問題
- 從Web轉到遊戲以後
- go源碼解析-Println的故事
- 用go-module做爲包管理器搭建go的web服務器
- WebAssembly徹底入門——瞭解wasm的前世今身
- 小強開飯店-從單體應用到微服務
相關:
- 微信公衆號: SH的全棧筆記(或直接在添加公衆號界面搜索微信號LunhaoHu)