1. 程式人生 > 程式設計 >golang-gin-mgo高併發伺服器搭建教程

golang-gin-mgo高併發伺服器搭建教程

gin-mgo伺服器搭建

該伺服器實現簡單接收請求並將請求引數封裝儲存在mongodb資料庫中,本文將講述gin-mgo的使用方法。

專案完整程式碼地址: https://github.com/wayne-yhp/golang-gin-mgo

gin web框架使用介紹

首先獲取gin框架依賴

go get gopkg.in/gin-gonic/gin.v1

func main() {
  server = gin.Default()
  app.server.GET("/do",IndexRouter)//創造一個GET請求的路由地址,並指定處理函式為IndexRouter函式
  app.server.Run(":8080")
}
func IndexRouter(c *gin.Context) {
  if c.Request.Form == nil {   //獲取所有請求引數名和值的預處理
    c.Request.ParseMultipartForm(32 << 20)
  }
  params = c.Request.Form   //獲取所有引數列表
  fmt.Println(params)     //列印輸出引數
  c.String(http.StatusOK,"hello gin")//返回給頁面hello gin字串
  //c.HTML(http.StatusOK,"index.html",nil)   //頁面跳轉
}

mgo 持久層框架使用介紹

前提條件mongodb環境已經搭建好了,首先安裝mgo框架依賴

go get labix.org/v2/mgo

type User struct{
  username string
  pwd string
}
func main() {
  mgo_session,err = mgo.Dial("127.0.0.1") //獲取連線物件session
  if err != nil {
    panic(err)
  }
  defer session.Close()    //方法執行完後關閉連線
  mgo_db = oper.mgo_session.DB("test")  //獲取資料庫物件,資料庫名為test
  //如果沒有mongodb沒有開啟許可權認證,則跳過這一步
  mgo_db.Login("test1","test1")  //使用者認證,使用者名稱賬戶和密碼都是test1
  mgo_c = oper.mgo_db.C("coll")  //獲取資料庫某個集合物件
  //插入操作
  mgo_c.insert(&User{
    username: "xxx",pwd: "xxx",})
}

提高伺服器高併發效能講解(針對文章開頭地址中的專案)

該專案主要實現接收請求,解析封裝引數,插入資料庫的簡單操作,這裡只涉及插入操作,故不涉及資料快取的知識,整個服務處於單機下,故不涉及分散式服務架構,叢集的知識。

注意以下

1、開啟一個協程獨自監聽訪問數量,進行插入操作

2、實現批量插入

3、實現定時插入

4、加鎖解決併發資源競爭

開啟一個協程獨自監聽訪問數量,進行插入操作

如果將插入操作放在主執行緒,那麼接收http請求和邏輯處理,資料庫插入操作都必須要順序執行,大大降低了插入效率,因此要開啟一個協程,獨自監聽訪問數量,進行資料庫插入。

實現批量插入

假想一下如果每次有人訪問你的資料庫你就進行一次插入操作,那麼你的資料庫將會是一個什麼樣的情況?我們都知道資料庫操作相對伺服器其他操作是一件相對很耗時的事情,所以每次訪問就操作一次資料庫,會大大降低伺服器效能,更別說有幾千上萬的人同時訪問你的伺服器了。

實現定時插入

在實現了批量插入的基礎上,如果沒有達到一定的訪問量,那麼就不會執行插入操作,剛好在兩個訪問請求中間隔了很長時間,那麼前面的請求就會等待很久才會更新到資料庫中,為了防止這種情況,我們必須要設定一個時間,定時插入。

加鎖解決併發資源競爭

在併發量幾千上萬的情況下,可能一秒可以執行很多次資料庫的插入操作,這個時候很有可能上一個插入還沒執行完,第二個就已經執行了,這時候可能出現數據冗餘,伺服器癱瘓等問題,因此要給批量插入操作加上一個讀寫鎖。

具體實現細節可以去上述地址中檢視。

補充:Golang號稱高併發,但高併發時效能不高

1.管道chan吞吐極限10,000,000,單次Put,Get耗時大約100ns/op,無論是採用單Go程,還是多Go程併發(併發數:100,10000,100000),耗時均沒有變化,Go核心這對chan進行優化。

解決之道:

在系統設計時,避免使用管道chan傳遞主業務資料,避免將業務流程處理流程分割到對個Go程中執行,這樣做減少chan傳輸耗時,和Go程排程耗時,效能會有很大的提升。

案例分析:nsq和nats都是實時訊息佇列,nsq在客戶端端和服務端大量使用chan轉發訊息,導致效能不佳,只有100,000/s;而nats服務端在分發訊息流程中,沒有使用chan,只在客戶端接收時使用chan,效能可達到1,000/s。

2.互斥鎖Mutex在單Go程時Lock,Unlock耗時大約20ns/op,但是採用多Go程時,效能急劇下降,併發越大耗時越長,在Go1.5併發數達到1024耗時900ns/op,Go1.6優化到300ns/op,究其原因,是構建在CPU的原子操作之上,搶佔過於頻繁將導致,消耗大量CPU時鐘,進而CPU多核無法並行。

解決之道:

採用分割槽,將需要互斥保護的資料,分成多個固定分割槽(建議是2的整數倍,如256),訪問時先定位分割槽(不互斥),這樣就可降低多個Go程競爭1個數據分割槽的概率。

案例分析:Golang的Go程排程模組,在管理大量的Go程,使用的就是資料分割槽。

3.select非同步操作在單管道時耗時120ns/op,但是隨著管道數增加,效能線性下降,每增加1個管道增加100ns/op,究其原因,slelect時當chan數超過1後,Go內部是建立一個Go程,有它每1ms輪訓的方式檢查每個chan是否可用,而不是採用事件觸發。

解決之道:

在select中避免使用過多的管道chan分支,或者把無法用到的chan置為nil;解決select超時,避免使用單獨的超時管道,應與資料返回管道共享。

案例分析:nsq和nats都是實時訊息佇列,由於nsq大量使用chan,這就必然導致大量使用select對多chan操作,結果是效能不高。

4.Go排程效能低下,當出現1,000Go程時,Go的排程器的效能急劇下降。

解決之道:

避免動態建立Go程,服務端收到資料並處理的流程中,避免使用chan傳遞業務資料,這樣會引起Go程排程。

案例分析:nsq和nats都是實時訊息佇列,由於nsq大量使用chan,這就必然導致在服務過程中,引起Go排程,結果是效能不高。

5.defer效能不高,每次defer耗時100ns,,在一個func內連續出現多次,效能消耗是100ns*n,累計出來浪費的cpu資源很大的。

解決之道:

除了需要異常捕獲時,必須使用defer;其它資源回收類defer,可以判斷失敗後,使用goto跳轉到資源回收的程式碼區。

6.記憶體管理器效能低下,申請16位元組的記憶體,單次消耗30ns,64位元組單次消耗70ns,隨著申請記憶體尺寸的增長,耗時會迅速增長。加上GC的效能在1.4,1.5是都不高,直到1.6,1.7才得到改善。

解決之道:

建議使用pool,單次Put,Get的耗時大約在28ns,在併發情況下可達到18ns,比起每次建立,會節省很多的CPU時鐘。

以上為個人經驗,希望能給大家一個參考,也希望大家多多支援我們。如有錯誤或未考慮完全的地方,望不吝賜教。