這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
第二部分: Go微服務 - 構建我們的第一個服務
第二部分包含:
- 設定我們的Go工作空間。
- 構建我們第一個微服務。
- 通過HTTP使用Gorilla Web Toolkit來提供一些JSON服務。
介紹
雖然通過HTTP提供JSON服務不是內部服務和外部服務的唯一選擇,但本文聚焦的是HTTP和JSON. 使用RPC機制和二進位訊息格式(例如Protocol Buffer)也用於內部通訊或外部通訊也是非常有趣的,特別是當外部消費者屬於另外一個系統的時候。Go語言有內建的RPC支援,並且gRPC也是完全值得看看的。 然而,我們現在只聚焦基於由http包和Gorilla Web Toolkit提供的HTTP。
另外一個需要考慮的方面是很多有用的架構(安全、跟蹤等等), 依賴於HTTP頭來傳輸參與者進行中的請求狀態。我們在本文中將看到的例子是我們如何在頭中傳遞相關ID和OAuth票據。雖然其他協議當然也支援類似的機制, 很多架構都是以HTTP構建的,我更願意儘可能的保持我們的整合更加直接。
設定Go工作空間
如果你是一個經驗豐富的Go開發人員,你可以隨意跳過本節內容。 以我拙見,Go語言工作空間結構需要一些時間來適應。一般來說我習慣使用項目根作為工作空間的根,Go語言約定了如何恰當的構造工作空間,因此go編譯器可以尋找原始碼和依賴,有點不正統, 將原始碼放在子目錄下源碼控制路徑後以src命名的目錄中.我強烈推薦讀下官方指南和本文,然後再開始。 我希望我就是這樣的。
安裝SDK
在開始寫我們第一行代碼之前(或check out完整代碼之前), 我們需要安裝Go語言SDK。建議按照官方指導來操作,直接操作就足夠了。
設定開發環境
在這些部落格系列中,我們將使用我們安裝的內建的Go SDK工具來構建和運行我們的代碼,以及按照慣用方式來設定Go的工作空間。
1. 建立工作空間的根目錄
所有命令都基於OS X或Linux開發環境。 如果你啟動並執行是Windows, 請採用必要的指令。
mkdir ~/goworkspacecd goworkspaceexport GOPATH=`pwd`
這裡我們建立了一個根目錄,然後將GOPATH環境變數賦於那個目錄。這就是我們的工作空間的根目錄,我們所寫的所有Go語言代碼和第三方類庫都在它下面。我推薦添加這個GOPATH到.bash_profile檔案或類似的設定檔中,這樣不需要每次都為每個控制台視窗重設它。
2. 為我們第一個項目建立檔案夾和檔案
鑒於我們已經在工作空間的根目錄(例如,和在GOPATH環境變數中指定相同的目錄), 執行下面的語句:
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/goblog.git目錄, 執行下面的命令。
git clone https://github.com/callistaenterprise/goblog.gitcd gobloggit checkout P2
記住: $GOPATH/src/github.com/callistaenterprise/goblog是我們項目的根目錄,並且實際是儲存在github上面的。
那麼我們結構已經足夠可以很方便開始了。 用你喜歡的IDE開啟main.go檔案。
建立服務 - main.go
Go語言中的main函數就是你具體做事的地方 - Go語言應用程式的進入點。 下面我們看看它的具體代碼:
package mainimport ( "fmt")var appName = "accountservice"func main() { fmt.Printf("Starting %v\n", appName)}
然後運行該程式:
> go run path/to/main.goStarting accountservice
就是這樣的,程式只列印了一個字串,然後就退出了。是時候添加第一個HTTP端點了。
構建HTTP web伺服器
注意: 這些HTTP樣本的基礎是從一個優秀的部落格文章派生出來的, 見參考連結。
為了保持代碼整潔,我們把所有HTTP服務相關的檔案放到service目錄下面。
啟動HTTP伺服器
在service目錄中建立webservice.go檔案。
package serviceimport ( "log" "net/http")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代碼:
package mainimport ( "fmt" "github.com/callistaenterprise/goblog/accountservice/service" // 新增代碼)var appName = "accountservice"func main() { fmt.Printf("Starting %v\n", appName) service.StartWebServer("6767") // 新增代碼}
然後再次運行這個程式,得到下面的輸出:
> go run *.goStarting accountservice2017/01/30 19:36:00 Starting HTTP service at 6767
那麼現在我們就有一個HTTP伺服器,它監聽localhost的6767連接埠。然後curl它:
> curl http://localhost:6767404 page not found
得到404完全是意料之中的,因為我們還沒有添加任何路由呢。
Ctrl+C停止這個web伺服器。
添加第一個路由
是時候讓我們的伺服器提供一些真正的服務了。我們首先用Go語言結構聲明我們的第一個路由,我們將使用它來填充Gorilla路由器。 在service目錄中,建立一個routes.go檔案。
package serviceimport ( "net/http")// Define 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 []Routevar 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/{accountId}, 我們後面會用curl來訪問它。Gorilla也支援使用正則模式比對、schemes, methods, queries, headers值等等的複雜路由。因此不限於路徑和路徑參數。
我們在響應的時候,寫入程式碼了一個小的JSON訊息:
{ "result": "OK"}
我們還需要一些模式化的程式碼片段,將我們聲明的路由掛鈎到實際的Gorilla Router上。 在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) // Iterator 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}
匯入依賴包
在router.go中的import地區, 我們聲明了依賴github.com/gorilla/mux包。 我們可以通過go get來擷取依賴包的原始碼。
WRAPPING UP
我們可以再回到webserver.go檔案,在函數StartWebServer開始位置加入下面兩行代碼。
func StartWebServer(port string) { r := NewRouter() http.Handle("/", r)}
這就將我們剛建立的Router綁定到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"}
很好,我們現在有了我們第一個HTTP服務。
資訊及效能(FOOTPRINT AND PERFORMANCE)
鑒於我們正在探索基於Go的微服務,由於驚人的記憶體佔用和良好的效能,我們最好能快速進行基準測試來看看它們如何執行的。
我已經開發了一個簡單的Gatling測試, 可以使用GET請求對/accounts/{accountId}進行捶打。 如果之前你是直接從https://github.com/callistaen...,那麼你的原始碼中就包含有負載測試代碼goblog/loadtest。或者可以直接查看https://github.com/callistaen...。
你自己運行一下負載測試
如果你需要自己運行負載測試工具,確保accountservice服務已啟動,並且運行在localhost的6767連接埠上。並且你已經checkout我們的P2分支的代碼。你還需要Java的運行環境以及需要安裝Apache Maven。
改變目錄到goblog/loadtest目錄下面,在命令列中執行下面的命令。
mvn gatling:execute -Dusers=1000 -Dduration=30 -DbaseUrl=http://localhost:6767
這樣就會啟動並運行測試。參數如下:
- users: 類比測試的並發使用者數.
- duration: 測試要啟動並執行秒數.
- baseUrl: 我們要測試的服務的基礎路徑。當我們把它遷移到Docker Swarm後,baseUrl修改修改為Swarm的公用IP. 在第5部分會介紹。
首次運行,mvn會自動安裝一大堆東西。安裝完後,測試完成之後,它會將結果寫到控制台視窗,同時也會產生一個報告到target/gatling/results中的html中。
結果
注意: 稍後,當我們的服務運行到Docker Swarm模式的Docker容器中時, 我們會在那裡做所有基準測試並捕獲度量。
在開始負載測試之前,我們的基於Go的accountservice記憶體消耗可以從macbook的工作管理員中查看到,大概如下:
1.8MB, 不是特別壞。讓我們使用Gatling測試,運行每秒1000個請求。需要記住一點,我們使用了非常幼稚的實現,我們僅僅響應一個硬式編碼JSON響應。
服務每秒1000個請求,佔用的記憶體也只是增加到28MB。 依然是Spring Boot應用程式啟動時候使用記憶體的1/10. 當我們給它添加一些真正的功能時,看這些數字變化會更加有意思。
效能和CPU使用率
提供每秒1000個請求,每個核大概使用8%。
注意,Gatling一回合子微秒延遲如何, 但是平均延遲報告值為每個請求0ms, 花費龐大的11毫秒。 在這點上來看,我們的accountservice執行還是表現出色的,在子毫秒範圍內大概每秒服務745個請求。
下一章
在下一部分, 我們將真正的讓accountservice做一些有意義的事情。 我們會添加一個簡單的嵌入資料庫到Account對象,然後提供HTTP服務。我們也會看看JSON的序列化,並檢查這些增加對於足跡和效能的影響。
參考連結
- http://callistaenterprise.se/...
- http://thenewstack.io/make-a-...
- https://github.com/gorilla/
- 專題首頁
- 下一節