使用Go 語言開發大型 MMORPG 游戲伺服器怎么樣
主要從事網頁設計、PC網站建設(電腦版網站建設)、wap網站建設(手機版網站建設)、響應式網站開發、程序開發、微網站、微信小程序開發等,憑借多年來在互聯網的打拼,我們在互聯網網站建設行業積累了豐富的成都做網站、成都網站設計、成都外貿網站建設、網絡營銷經驗,集策劃、開發、設計、營銷、管理等多方位專業化運作于一體,具備承接不同規模與類型的建設項目的能力。
如果是大型網路游戲的話,我覺得是不合適的。現階段go語言的執行效率還是太低了。在底層編譯器的優化方面做得和c++相比還是差了不少。go語言也是比較適合快速開發的專案比較合適
從2013年起,經朋友推薦開始用Golang編寫游戲登陸伺服器, 配合C++做第三方平臺驗證. 到編寫獨立工具導表工具GitHub - davyxu/tabtoy: 跨平臺的高效能便捷電子表格匯出器. 以及網路庫GitHub - davyxu/cell: 簡單,方便,高效的Go語言的游戲伺服器底層. 最終使用這些工具及庫編寫整個游戲伺服器框架, 我的感受是很不錯的
細節看來, 有如下的幾個點:
語言, 庫
Golang語言特性和C很像, 簡單, 一張A4紙就能寫完所有特性. 你想想看, C++到了領悟階段, 也只用那幾個簡單特性, 剩下的都是一大堆解決各種記憶體問題的技巧. 而Golang一開始就簡單, 何必浪費生命去研究那一大堆的奇技淫巧呢?
Golang的坑只有2個:1. interface{}和nil配合使用, 2. for回圈時, 將回圈變數引入閉包(Golang, Lua, C#閉包變數捕獲差異) 完全不影響正常使用, 復合語言概念, 只是看官方后面怎么有效的避免
用Golang就忘記繼承那套東西, 用組合+介面
用Golang伺服器如何保證解決游戲伺服器存檔一致性問題? s the world是肯定的, 但是Golang可以從語言層并發序列化玩家資料, 再通過后臺存檔
channel是goroutine雖然是Golang的語言特性. 但是在編寫伺服器時, 其實只有底層用的比較多.
Golang的第三方庫簡直多如牛毛, 好的也很多
不要說模板了, C#的也不好用, 官方在糾結也不要加, 使用中, 沒模板確實有點不方便. 用interface{}/反射做泛型對于Golang這種強型別語言來說,還是有點打臉
執行期
Golang和C++比效能的話, 這是C++的優勢, Golang因為沒虛擬機器, 只有薄薄的一層排程層. 因此效能是非常高的, 用一點效能犧牲換開發效率, 妥妥的
1.6版后的GC優化的已經很好了, 如果你不是高效能,高并發Web應用, 非要找出一堆的優化技巧的話. 只用Golang寫點游戲伺服器, 那點GC損耗可以忽略不計
和其他現代語言一樣, 崩潰捕捉是標配功能, 我用Golang的伺服器線上跑, 基本沒碰到過崩潰情況
熱更新: 官方已經有plugin系統的提交, 跨平臺的. 估計很快就可以告別手動cgo做so熱更新
開發, 除錯, 部署, 優化
LiteIDE是我首選的Golang的IDE, 雖然有童鞋說B格不高. 但這估計實在是找不到缺點說了, 別跟我說Visual Studio, 那是宇宙級的...
曾經聽說有人不看好Golang, 我問為啥: 說這么新的語言, 不好招人,后面打聽到他是個策劃... 好吧
真實情況是這樣的: Golang對于有點程式設計基礎的新人來說, 1周左右可以開始貢獻程式碼. 老司機2~3天.
開發效率還是不錯的, 一般大的游戲功能, 2*2人一周3~4個整完. 這換C++時代, 大概也就1~2個還寫不完. 對接伺服器sdk的話, 大概1天接個10多個沒問題
Golang自帶效能調優工具, 從記憶體, CPU, 阻塞點等幾個方面直接出圖進行分析, 非常直觀, 可以參考我部落格幾年前的分析: 使用Golang進行效能分析(Profiling)
Golang支 *** 叉編譯, 跨平臺部署, 什么概念? linux是吧? 不問你什么版本, 直接windows上編譯輸出一個elf, 甩到伺服器上開跑.不超過1分鐘時間..
1.為什么golang的開發效率高?
golang是一編譯型的強型別語言,它在開發上的高效率主要來自于后發優勢,不用考慮舊有惡心的歷史,又有一個較高的工程視角。良好的避免了程式設計師因為“ { 需不需要獨占一行 ”這種革命問題打架,也解決了一部分趁編譯時間找產品妹妹搭訕的階級敵人。
它有自己的包管理機制,工具鏈成熟,從開發、除錯到釋出都很簡單方便;
有反向介面、defer、coroutine等大量的syntactic sugar;
編譯速度快,因為是強型別語言又有gc,只要通過編譯,非業務毛病就很少了;
它在語法級別上支援了goroutine,這是大家說到最多的內容,這里重點提一下。首先,coroutine并不稀罕,語言并不能超越硬體、作業系統實現神乎其神的功能。golang可以做到事情,其他語言也可以做到,譬如c++,在boost庫里面自己就有的coroutine實現(當然用起來跟其他boost庫一樣惡心)。golang做的事情,是把這一套東西的使用過程簡化了,并且提供了一套channel的通訊模式,使得程式設計師可以忽略諸如死鎖等問題。
goroutine的目的是描述并發程式設計模型。并發與并行不同,它并不需要多核的硬體支援,它不是一種物理執行狀態,而是一種程式邏輯流程。它的主要目的不是利用多核提高執行效率,而是提供一種更容易理解、不容易出錯的語言來描述問題。
實際上golang預設就是執行在單OS程序上面的,通過指定環境變數GOMAXPROCS才能轉身跑在多OS程序上面。有人提到了網易的pomelo,開源本來是一件很不錯的事情,但是基于自己對callback hell的偏見,我一直持有這種態度:敢用nodejs寫大規模游戲伺服器的人,都是真正的勇士 : ) 。
2、Erlang與Golang的coroutine有啥區別,coroutine是啥?
coroutine本質上是語言開發者自己實現的、處于user space內的執行緒,無論是erlang、還是golang都是這樣。需要解決沒有時鐘中斷;碰著阻塞式i\o,整個程序都會被作業系統主動掛起;需要自己擁有排程控制能力(放在并行環境下面還是挺麻煩的一件事)等等問題。那為啥要廢老大的勁自己做一套執行緒放user space里面呢?
并發是伺服器語言必須要解決的問題;
system space的程序還有執行緒排程都太慢了、占用的空間也太大了。
把執行緒放到user space的可以避免了陷入system call進行上下文切換以及高速緩沖更新,執行緒本身以及切換等操作可以做得非常的輕量。這也就是golang這類語言反復提及的超高并發能力,分分鐘給你開上幾千個執行緒不費力。
不同的是,golang的并發排程在i/o等易發阻塞的時候才會發生,一般是內封在庫函式內;erlang則更夸張,對每個coroutine維持一個計數器,常用語句都會導致這個計數器進行reduction,一旦到點,立即切換排程函式。
中斷介入程度的不同,導致erlang看上去擁有了preemptive scheduling的能力,而golang則是cooperative shceduling的。golang一旦寫出純計算死回圈,程序內所有會話必死無疑;要有大計算量少i\o的函式還得自己主動叫runtime.Sched()來進行排程切換。
3、golang的執行效率怎么樣?
我是相當反感所謂的ping\pong式benchmark,執行效率需要放到具體的工作環境下面考慮。
首先,它再快也是快不過c的,畢竟底下做了那么多工作,又有排程,又有gc什么的。那為什么在那些benchmark里面,golang、nodejs、erlang的響應效率看上去那么優秀呢,響應快,并發強?并發能力強的原因上面已經提到了,響應快是因為大量非阻塞式i\o操作出現的原因。這一點c也可以做到,并且能力更強,但是得多寫不少優質程式碼。
然后,針對游戲伺服器這種高實時性的執行環境,GC所造成的跳幀問題確實比較麻煩,前面的大神 @達達 有比較詳細的論述和緩解方案,就不累述了 。隨著golang的持續開發,相信應該會有非常大的改進。一是遮蔽記憶體操作是現代語言的大勢所趨,它肯定是需要被實現的;二是GC演算法已經相當的成熟,效率勉勉強強過得去;三是可以通過incremental的操作來均攤cpu消耗。
用這一點點效率損失換取一個更高的生產能力是不是值得呢?我覺得是值得的,硬體已經很便宜了,人生苦短,讓自己的生活更輕松一點吧: )。
4、基于以上的論述,我認為采用go進行小范圍的MMORPG開發是可行的。
如果跟C語言比,大部分指令碼都勝出啊。Go, Node.js, Python ......
網易弄過一個Node.js的開源伺服器框架。
至于IDE, 不重要,做伺服器開發很少會要開著IDE除錯的。最常用的手段就是打Log. 設定了斷點也很難調,多個客戶端并發。
那種單客戶端連線進來就可以重現的bug倒是可以用IDE調,但是這種bug本來就容易解決。
用指令碼語言,有一個很大的好處是容易做自動測試,可以更好地保證程式碼質量。
--------------------------
開發效率當然是指令碼高。執行效率,其實更重要的是并發,框架合理的話增加機器就可以直接提高效率增加人數。
用Go開發大型mmorpg服務端不會有問題的,如果掉坑里肯定不會是語言的問題。
唯一比較可能掉進去的坑就只有GC,其實很容易預防和調整的,具體細節可以看我部落格分享的文章。
但是技術選型不只是選語言,如果當時我手頭有一套效能滿意,開發效率OK,人員補給不會有問題的技術方案,不管是什么語言的,我肯定不會放棄它而選擇冒險的。
public void actionPerformed(ActionEvent e)
{
if(e.getSource()==xinjian)
{
text.setText("");
}
if(e.getSource()==dakai)
{
openFD.show();
String s;
Go和java產生的背景和語言的定義不一樣,比較他們的好壞沒多大意義,適合就是最好的,當你去做一個大型的項目的時候,使用java能夠更清晰地展示出你的業務。自身提供了比較完善的庫。
Go語言也有面向函數和面向對象的變成方案,其自身的性能決定了go語言更適合做中間件,底層的各種框架。
語言對企業來說,是制造生產力的,哪種語言能夠給我們帶來更好的收益,我們才選擇它們。
一、關于連接池
一個數據庫服務器只擁有有限的資源,并且如果你沒有充分使用這些資源,你可以通過使用更多的連接來提高吞吐量。一旦所有的資源都在使用,那么你就不 能通過增加更多的連接來提高吞吐量。事實上,吞吐量在連接負載較大時就開始下降了。通常可以通過限制與可用的資源相匹配的數據庫連接的數量來提高延遲和吞 吐量。
如何在Go語言中使用Redis連接池
如果不使用連接池,那么,每次傳輸數據,我們都需要進行創建連接,收發數據,關閉連接。在并發量不高的場景,基本上不會有什么問題,一旦并發量上去了,那么,一般就會遇到下面幾個常見問題:
性能普遍上不去
CPU 大量資源被系統消耗
網絡一旦抖動,會有大量 TIME_WAIT 產生,不得不定期重啟服務或定期重啟機器
服務器工作不穩定,QPS 忽高忽低
要想解決這些問題,我們就要用到連接池了。連接池的思路很簡單,在初始化時,創建一定數量的連接,先把所有長連接存起來,然后,誰需要使用,從這里取走,干完活立馬放回來。 如果請求數超出連接池容量,那么就排隊等待、退化成短連接或者直接丟棄掉。
二、使用連接池遇到的坑
最近在一個項目中,需要實現一個簡單的 Web Server 提供 Redis 的 HTTP interface,提供 JSON 形式的返回結果。考慮用 Go 來實現。
首先,去看一下 Redis 官方推薦的 Go Redis driver。官方 Star 的項目有兩個:Radix.v2 和 Redigo。經過簡單的比較后,選擇了更加輕量級和實現更加優雅的 Radix.v2。
Radix.v2 包是根據功能劃分成一個個的 sub package,每一個 sub package 在一個獨立的子目錄中,結構非常清晰。我的項目中會用到的 sub package 有 redis 和 pool。
由于我想讓這種被 fork 的進程最好簡單點,做的事情單一一些,所以,在沒有深入去看 Radix.v2 的 pool 的實現之前,我選擇了自己實現一個 Redis pool。(這里,就不貼代碼了。后來發現自己實現的 Redis pool 與 Radix.v2 實現的 Redis pool 的原理是一樣的,都是基于 channel 實現的, 遇到的問題也是一樣的。)
不過在測試過程中,發現了一個詭異的問題。在請求過程中經常會報 EOF 錯誤。而且是概率性出現,一會有問題,一會又好了。通過反復的測試,發現 bug 是有規律的,當程序空閑一會后,再進行連續請求,會發生3次失敗,然后之后的請求都能成功,而我的連接池大小設置的是3。再進一步分析,程序空閑300秒 后,再請求就會失敗,發現我的 Redis server 配置了 timeout 300,至此,問題就清楚了。是連接超時 Redis server 主動斷開了連接。客戶端這邊從一個超時的連接請求就會得到 EOF 錯誤。
然后我看了一下 Radix.v2 的 pool 包的源碼,發現這個庫本身并沒有檢測壞的連接,并替換為新server{location/pool{content_by_lua_block{localredis=require"resty.redis"localred=redis:new()localok,err=red:connect("127.0.0.1",6379)ifnotokthenngx.say("failedtoconnect:",err)returnendok,err=red:set("hello","world")ifnotokthenreturnendred:set_keepalive(10000,100)}}}
發現有個 set_keepalive 的方法,查了一下官方文檔,方法的原型是 syntax: ok, err = red:set_keepalive(max_idle_timeout, pool_size) 貌似 max_idle_timeout 這個參數,就是我們所缺少的東西,然后進一步跟蹤源碼,看看里面是怎么保證連接有效的。
function_M.set_keepalive(self,...)localsock=self.sockifnotsockthenreturnnil,"notinitialized"endifself.subscribedthenreturnnil,"subscribedstate"endreturnsock:setkeepalive(...)end
至此,已經清楚了,使用了 tcp 的 keepalive 心跳機制。
于是,通過與 Radix.v2 的作者一些討論,選擇自己在 redis 這層使用心跳機制,來解決這個問題。
四、最后的解決方案
在創建連接池之后,起一個 goroutine,每隔一段 idleTime 發送一個 PING 到 Redis server。其中,idleTime 略小于 Redis server 的 timeout 配置。連接池初始化部分代碼如下:
p,err:=pool.New("tcp",u.Host,concurrency)errHndlr(err)gofunc(){for{p.Cmd("PING")time.Sleep(idelTime*time.Second)}}()
使用 redis 傳輸數據部分代碼如下:
funcredisDo(p*pool.Pool,cmdstring,args...interface{})(reply*redis.Resp,errerror){reply=p.Cmd(cmd,args...)iferr=reply.Err;err!=nil{iferr!=io.EOF{Fatal.Println("redis",cmd,args,"erris",err)}}return}
其中,Radix.v2 連接池內部進行了連接池內連接的獲取和放回,代碼如下:
//Cmdautomaticallygetsoneclientfromthepool,executesthegivencommand//(returningitsresult),andputstheclientbackinthepoolfunc(p*Pool)Cmd(cmdstring,args...interface{})*redis.Resp{c,err:=p.Get()iferr!=nil{returnredis.NewResp(err)}deferp.Put(c)returnc.Cmd(cmd,args...)}
這樣,我們就有了 keepalive 的機制,不會出現 timeout 的連接了,從 redis 連接池里面取出的連接都是可用的連接了。看似簡單的代碼,卻完美的解決了連接池里面超時連接的問題。同時,就算 Redis server 重啟等情況,也能保證連接自動重連。
網站欄目:go語言qps Go語言Web框架對比
本文來源:http://vcdvsql.cn/article30/ddijcpo.html
成都網站建設公司_創新互聯,為您提供微信公眾號、網站導航、定制開發、搜索引擎優化、、品牌網站制作
聲明:本網站發布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創新互聯