第二部分包含:html
雖然經過HTTP提供JSON服務不是內部服務和外部服務的惟一選擇,但本文聚焦的是HTTP和JSON. 使用RPC機制和二進制消息格式(例如Protocol Buffer)也用於內部通訊或外部通訊也是很是有趣的,特別是當外部消費者屬於另一個系統的時候。Go語言有內置的RPC支持,而且gRPC也是徹底值得看看的。 然而,咱們如今只聚焦基於由http包和Gorilla Web Toolkit提供的HTTP。git
另一個須要考慮的方面是不少有用的框架(安全、跟蹤等等), 依賴於HTTP頭來傳輸參與者正在進行的請求狀態。咱們在本文中將看到的例子是咱們如何在頭中傳遞相關ID和OAuth票據。雖然其餘協議固然也支持相似的機制, 不少框架都是以HTTP構建的,我更願意儘量的保持咱們的集成更加直接。github
若是你是一個經驗豐富的Go開發者,你能夠隨意跳過本節內容。 以我拙見,Go語言工做空間結構須要一些時間來適應。通常來講我習慣使用項目根做爲工做空間的根,Go語言約定了如何恰當的構造工做空間,所以go編譯器能夠查找源代碼和依賴,有點不正統, 將源代碼放在子目錄下源碼控制路徑後以src命名的目錄中.我強烈推薦讀下官方指南和本文,而後再開始。 我但願我就是這樣的。web
在開始寫咱們第一行代碼以前(或check out完整代碼以前), 咱們須要安裝Go語言SDK。建議按照官方指導來操做,直接操做就足夠了。數據庫
在這些博客系列中,咱們將使用咱們安裝的內置的Go SDK工具來構建和運行咱們的代碼,以及按照慣用方式來設置Go的工做空間。json
全部命令都基於OS X或Linux開發環境。 若是你運行的是Windows, 請採用必要的指令。api
mkdir ~/goworkspace cd goworkspace export GOPATH=`pwd`
這裏咱們建立了一個根目錄,而後將GOPATH環境變量賦於那個目錄。這就是咱們的工做空間的根目錄,咱們所寫的全部Go語言代碼和第三方類庫都在它下面。我推薦添加這個GOPATH到.bash_profile文件或相似的配置文件中,這樣不須要每次都爲每一個控制檯窗口重置它。安全
鑑於咱們已經在工做空間的根目錄(例如,和在GOPATH環境變量中指定相同的目錄), 執行下面的語句:bash
mkdir -p src/github.com/callistaenterprise
若是你但願遵循本身的編碼,能夠執行下面的命令:服務器
cd src/github.com/callistaenterprise mkdir -p goblog/accountservice cd goblog/accountservice touch main.go mkdir service
或者你能夠clone這個git倉庫,包含相同代碼,並切換到P2分支。 從上面你建立的src/github.com/callistaenterprise/goblog.git目錄, 執行下面的命令。
git clone https://github.com/callistaenterprise/goblog.git cd goblog git checkout P2
記住: $GOPATH/src/github.com/callistaenterprise/goblog是咱們項目的根目錄,而且實際是存儲在github上面的。
那麼咱們結構已經足夠能夠很方便開始了。 用你喜歡的IDE打開main.go文件。
Go語言中的main函數就是你具體作事的地方 - Go語言應用程序的入口點。 下面咱們看看它的具體代碼:
package main import ( "fmt" ) var appName = "accountservice" func main() { fmt.Printf("Starting %v\n", appName) }
而後運行該程序:
> go run path/to/main.go Starting accountservice
就是這樣的,程序只打印了一個字符串,而後就退出了。是時候添加第一個HTTP端點了。
注意: 這些HTTP示例的基礎是從一個優秀的博客文章派生出來的, 見參考連接。
爲了保持代碼整潔,咱們把全部HTTP服務相關的文件放到service目錄下面。
在service目錄中建立webservice.go文件。
package service import ( "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 main import ( "fmt" "github.com/callistaenterprise/goblog/accountservice/service" // 新增代碼 ) var appName = "accountservice" func main() { fmt.Printf("Starting %v\n", appName) service.StartWebServer("6767") // 新增代碼 }
而後再次運行這個程序,獲得下面的輸出:
> go run *.go Starting accountservice 2017/01/30 19:36:00 Starting HTTP service at 6767
那麼如今咱們就有一個HTTP服務器,它監聽localhost的6767端口。而後curl它:
> curl http://localhost:6767 404 page not found
獲得404徹底是意料之中的,由於咱們尚未添加任何路由呢。
Ctrl+C中止這個web服務器。
是時候讓咱們的服務器提供一些真正的服務了。咱們首先用Go語言結構聲明咱們的第一個路由,咱們將使用它來填充Gorilla路由器。 在service目錄中,建立一個routes.go文件。
package service import ( "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 []Route var 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 service import ( "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來獲取依賴包的源代碼。
咱們能夠再回到webserver.go文件,在函數StartWebServer開始位置加入下面兩行代碼。
func StartWebServer(port string) { r := NewRouter() http.Handle("/", r) }
這就將咱們剛建立的Router綁定到http.Handle對/路徑的處理。而後從新編譯並運行修改後的代碼:
> go run *.go Starting accountservice 2017/01/31 15:15:57 Starting HTTP service at 6767
而後另開一個窗口,curl以下:
> curl http://localhost:6767/accounts/10000 {"result":"OK"}
很好,咱們如今有了咱們第一個HTTP服務。
鑑於咱們正在探索基於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
這樣就會啓動並運行測試。參數以下:
首次運行,mvn會自動安裝一大堆東西。安裝完後,測試完成以後,它會將結果寫到控制檯窗口,同時也會產生一個報告到target/gatling/results中的html中。
注意: 稍後,當咱們的服務運行到Docker Swarm模式的Docker容器中時, 咱們會在那裏作全部基準測試並捕獲度量。
在開始負載測試以前,咱們的基於Go的accountservice內存消耗能夠從macbook的任務管理器中查看到,大概以下:
1.8MB, 不是特別壞。讓咱們使用Gatling測試,運行每秒1000個請求。須要記住一點,咱們使用了很是幼稚的實現,咱們僅僅響應一個硬編碼的JSON響應。
服務每秒1000個請求,佔用的內存也只是增長到28MB。 依然是Spring Boot應用程序啓動時候使用內存的1/10. 當咱們給它添加一些真正的功能時,看這些數字變化會更加有意思。
提供每秒1000個請求,每一個核大概使用8%。
注意,Gatling一回合子微秒延遲如何, 可是平均延遲報告值爲每一個請求0ms, 花費龐大的11毫秒。 在這點上來看,咱們的accountservice執行仍是表現出色的,在子毫秒範圍內大概每秒服務745個請求。
在下一部分, 咱們將真正的讓accountservice作一些有意義的事情。 咱們會添加一個簡單的嵌入數據庫到Account對象,而後提供HTTP服務。咱們也會看看JSON的序列化,並檢查這些增長對於足跡和性能的影響。