Go微服務 - 第二部分 - 構建第一個Go微服務

第二部分: Go微服務 - 構建咱們的第一個服務

第二部分包含:html

  • 設置咱們的Go工做空間。
  • 構建咱們第一個微服務。
  • 經過HTTP使用Gorilla Web Toolkit來提供一些JSON服務。

介紹

雖然經過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語言約定了如何恰當的構造工做空間,所以go編譯器能夠查找源代碼和依賴,有點不正統, 將源代碼放在子目錄下源碼控制路徑後以src命名的目錄中.我強烈推薦讀下官方指南和本文,而後再開始。 我但願我就是這樣的。web

安裝SDK

在開始寫咱們第一行代碼以前(或check out完整代碼以前), 咱們須要安裝Go語言SDK。建議按照官方指導來操做,直接操做就足夠了。數據庫

設置開發環境

在這些博客系列中,咱們將使用咱們安裝的內置的Go SDK工具來構建和運行咱們的代碼,以及按照慣用方式來設置Go的工做空間。json

1. 建立工做空間的根目錄

全部命令都基於OS X或Linux開發環境。 若是你運行的是Windows, 請採用必要的指令。api

mkdir ~/goworkspace
cd goworkspace
export GOPATH=`pwd`

這裏咱們建立了一個根目錄,而後將GOPATH環境變量賦於那個目錄。這就是咱們的工做空間的根目錄,咱們所寫的全部Go語言代碼和第三方類庫都在它下面。我推薦添加這個GOPATH到.bash_profile文件或相似的配置文件中,這樣不須要每次都爲每一個控制檯窗口重置它。安全

2. 爲咱們第一個項目建立文件夾和文件

鑑於咱們已經在工做空間的根目錄(例如,和在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文件。

建立服務 - 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 web服務器

注意: 這些HTTP示例的基礎是從一個優秀的博客文章派生出來的, 見參考連接。

爲了保持代碼整潔,咱們把全部HTTP服務相關的文件放到service目錄下面。

啓動HTTP服務器

在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來獲取依賴包的源代碼。

WRAPPING UP

咱們能夠再回到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服務。

信息及性能(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的任務管理器中查看到,大概以下:

clipboard.png

1.8MB, 不是特別壞。讓咱們使用Gatling測試,運行每秒1000個請求。須要記住一點,咱們使用了很是幼稚的實現,咱們僅僅響應一個硬編碼的JSON響應。

clipboard.png

服務每秒1000個請求,佔用的內存也只是增長到28MB。 依然是Spring Boot應用程序啓動時候使用內存的1/10. 當咱們給它添加一些真正的功能時,看這些數字變化會更加有意思。

性能和CPU使用率

clipboard.png

提供每秒1000個請求,每一個核大概使用8%。

clipboard.png

注意,Gatling一回合子微秒延遲如何, 可是平均延遲報告值爲每一個請求0ms, 花費龐大的11毫秒。 在這點上來看,咱們的accountservice執行仍是表現出色的,在子毫秒範圍內大概每秒服務745個請求。

下一章

在下一部分, 咱們將真正的讓accountservice作一些有意義的事情。 咱們會添加一個簡單的嵌入數據庫到Account對象,而後提供HTTP服務。咱們也會看看JSON的序列化,並檢查這些增長對於足跡和性能的影響。

參考連接

相關文章
相關標籤/搜索