這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
原文地址
轉載請註明原文及翻譯地址
第二部分,我們會:
- 建立go項目
- 寫我們的第一個微服務
- 使用Gorilla組件為HTTP請求提供JSON應答
我們從微服務基礎開始,之後會搭建在我們的docker swarm上
介紹
對於內部請求還是外部請求,通過HTTP提供JSON應答不是唯一的選擇。但我們在這裡會主要講解這一方法。當內部請求或者外部請求也是一個系統時,用RPC和二進位資訊格式作為請求方式也是一個不錯的選擇,例如protocol buffers。go有內建的RPC支援,gRPC值得研究一下。然而,這裡我們先講解用內建的http package和Gorilla web tookit。
許多有用的架構(安全,追蹤)依賴於HTTP頭資訊來傳遞請求的狀態,這也是用HTTP的好處。在我們之後的部落格中也會看到我們傳遞相關的ID和OAuth頭資訊。雖然其他的協議也支援相似的功能,但是許多架構實在HTTP上開發的,所以我盡量用他們來讓我們的整合更直接。
建立go項目
如果你已經熟悉go開發,你可以略過這一段。
我覺得go的工作區需要一點時間來熟悉他。我習慣於用我的項目的根目錄作為工作區的根目錄。然而go組織一個工作區的方法有點奇怪,這和go編譯器找尋原始碼和依賴關係的方式有關。
推薦閱讀:
官方文檔
go path and workspace
安裝SDK
在我們寫代碼之前,我們需要安裝go SDK。你可以參照官方文檔
1. 建立根目錄
所有命令都是基於OS X或者Linux開發環境。如果你是在windows上開發,請調整這些命令。
mkdir ~/goworkspacecd goworkspaceexport GOPATH=`pwd`
這裡我們建立一個根目錄並且讓環境變數GOPATH指向這個檔案。這個根目錄會包含所有go的代碼和第三方庫。我建議你把GOPATH加入到.bash_profile中,所以你不需要每次都設定。
2. 建立第一個項目的檔案夾和檔案
現在我們在根目錄,執行下面操作:
mkdir -p src/github.com/callistaenterprise
如果你想自己跟著打代碼,執行下面操作:
cd src/github.com/callistaenterprisemkdir -p goblog/accountservicecd goblog/accountservicetouch main.gomkdir service
或者你可以直接clone這個git倉庫,轉到p2分支。在檔案夾src/github.com/callistaenterprise中,執行
git clone https://github.com/callistaenterprise/goblog.gitcd gobloggit checkout P2
現在我們可以開始了。
建立服務-main.go
main函數和其他語言中的一樣,程式的存取點。讓我們寫一些代碼來運行一下
package mainimport ( "fmt" ) var appName = "accountservice"func main() { fmt.Printf("Starting %v\n", appName)}
現在,運行它。確定你在這個檔案夾下:
$GOPATH/src/github.com/callistaenterprise/goblog/accountservice
> go run *.goStarting accountservice>
完成,這段代碼列印之後退出,現在讓我們加上HTTP終端
建立一個HTTP服務
讓項目更整潔,我們會把所有的HTTP服務放進service檔案夾中
啟動HTTP服務
建立一個檔案webserve.go在/services檔案夾中
package serviceimport ( "net/http" "log")func StartWebServer(port string) { log.Println("Starting HTTP service at " + port) err := http.ListenAndServe(":" + port, nil) // Goroutine will block here if err != nil { log.Println("An error occured starting HTTP listener at port " + port) log.Println("Error: " + err.Error()) }}
我們用內建的net/http包來執行ListenAndServe,從而在指定連接埠開啟HTTP服務。
更新main.go用一個寫死的連接埠去請求StartWebServer函數
package mainimport ( "fmt" "github.com/callistaenterprise/goblog/accountservice/service" // NEW)var appName = "accountservice"func main() { fmt.Printf("Starting %v\n", appName) service.StartWebServer("6767") // NEW}
運行程式:
> go run *.goStarting accountservice2017/01/30 19:36:00 Starting HTTP service at 6767
現在我們有一個簡單的HTTP服務監聽6767連接埠,curl這個服務
curl http://localhost:6767404 page not found
404是我們預料到的,因為我們還沒有定義任何routes
停止這個服務-Ctrl+C
加入第一個路由
我們要開始搞事情了,我們定義第一個路由。在service檔案夾中,建立routes.go
package serviceimport "net/http"// Defines a single route, e.g. a human readable name, HTTP method and the// pattern the function that will execute when the route is called.type Route struct { Name string Method string Pattern string HandlerFunc http.HandlerFunc}// Defines the type Routes which is just an array (slice) of Route structs.type Routes []Route// Initialize our routesvar routes = Routes{ Route{ "GetAccount", // Name "GET", // HTTP method "/accounts/{accountId}", // Route pattern func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=UTF-8") w.Write([]byte("{\"result\":\"OK\"}")) }, },}
上面代碼,我們定義路徑/accounts/{accountsId},之後我們可以curl這個路徑。Gorilla也支援複雜的路由包括正則匹配,schemes,方法,queries,頭資訊值等。所以不只限於路徑和路徑參數
現在,我們會返回一個JSON資訊:
{"result":"OK"}
我們現在需要一些樣板代碼來啟動Gorilla路由。在service檔案夾中,建立router.go
package serviceimport ( "github.com/gorilla/mux")// Function that returns a pointer to a mux.Router we can use as a handler.func NewRouter() *mux.Router { // Create an instance of the Gorilla router router := mux.NewRouter().StrictSlash(true) // Iterate over the routes we declared in routes.go and attach them to the router instance for _, route := range routes { // Attach each route, uses a Builder-like pattern to set each route up. router.Methods(route.Method). Path(route.Pattern). Name(route.Name). Handler(route.HandlerFunc) } return router}
引入依賴
在import中我們定義一個依賴 github.com/gorilla/mux。
為了讓以上檔案編譯及運行,我們需要用go get來擷取定義的package
> go get
go工具會下載所有的原始碼。之後這些代碼會儲存在$GOPATH/src/github.com/gorilla/mux的本地檔案中。並編譯到你的靜態連結二進位檔案。
小結
現在,重新看webserver.go並加入下面代碼:
func StartWebServer(port string) { r := NewRouter() // NEW http.Handle("/", r) // NEW
這段代碼吧我們剛剛寫的路由加入http.Handle中。讓我們運行代碼:
> go run *.goStarting accountservice2017/01/31 15:15:57 Starting HTTP service at 6767
curl這個地址
> curl http://localhost:6767/accounts/10000 {"result":"OK"}
我們第一個服務就做好了!
資源消耗和效率
由於我們在探索基於go的微服務的資源消耗和效率,我們做一個基測。我開發了一個簡單的Gatling測試會用get方法請求accounts/{accountId}。如果你check out這部分的代碼,你能看到測試在goblog/loadtest檔案夾中.
運行測試
如果你想自己測試,確保accountservice正在你的localhost中運行。並且你已經checked out分支P2。你需要有Jave runtime和Apache Maven.
去檔案夾/goblog/loadtest中執行下面的命令
> mvn gatling:execute -Dusers=1000 -Dduration=30 -DbaseUrl=http://localhost:6767
這將開始測試,輸入參數是
- user: 類比的並發使用者數
- duration: 測試持續秒數
- baseUrl: 我們測試服務的基礎路徑。當我們用docker swarm時,baseUrl將會變成swarm的公用IP.
測試結束後,結果會寫入terminal中,同時一個漂亮的HTML報告會存入target/gatling/results/中
結果
之後我們的基測將會在docker swarm中進行,現在,我的2014年的舊mac還是要負重運行。
下面是記憶體消耗,在OS X的工作管理員中顯示
1.2MB,還不錯。讓我們用Gatling測試1K req/s.記住這是一個非常簡單的,只返回hard code的字串的程式
1k req/s讓accountservice消耗大約28MB的記憶體。這仍然是spring boot應用在開啟時的十分之一。這將是有趣的去看一下這個數字怎麼變當我們加入一些真的處理函數進去。
效能和CPU使用
1k req/s 消耗單核的8%左右
不清楚Gatling怎樣處理毫秒的延遲,但是平均延遲是0ms.有一個請求用掉11ms。總體來看,accountservice效能相當好,能處理745 req/s。
接下來。。
下一部分,我們要讓我們的accountserive做一些有用的事。我們要加入簡單的資料庫。我們也會嘗試JSON序列化。同時檢查這些會怎樣影響消耗和效能。