基於go搭建微服務實踐教程 (三)

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
原文地址
轉載請註明原文及翻譯地址

在第三節,我們要讓我們的accountservice做一些有用的事情。

  • 聲明一個 Account 結構
  • 嵌入一個索引值對的儲存,用來儲存Account結構
  • 序列化結構為JSON,並且用於我們的accounts/{accountId} HTTP服務

原始碼

這篇部落格中的所有代碼可以從分支p3中得到。

git checkout P3

聲明一個Account結構

在我們的項目中,在accountservice檔案夾中建立一個model檔案夾

mkdir model

在model檔案夾下建立一個檔案名稱字為account.go並寫入以下代碼:

package modeltype Account struct {    Id string `json:"id"`    Name string `json:"name"`}

這裡面聲明了Account,包含id和name。第一個字母的大小寫表示範圍(大寫=public, 小寫=包內調用)。
在聲明中,我們也用到了內建的json.marshal函數對參數序列化的支援。

嵌入一個索引值對的儲存

這裡,我們會用到BoltDB來儲存索引值對。這個包簡單快速容易整合。我們可以用go get來得到這個包

go get github.com/boltdb/boltdb/b

之後,在/goblog/accountservice檔案夾中,建立一個檔案夾dbclient,在dbclient中建立檔案boltclient.go。為了使mocking更容易,我們先聲明一個介面,用來制定實現者需要遵從的方法。

package dbclientimport (        "github.com/callistaenterprise/goblog/accountservice/model")type IBoltClient interface {        OpenBoltDb()        QueryAccount(accountId string) (model.Account, error)        Seed()}

在同一個檔案中,我們會實現這個介面。先定義一個封裝了bolt.DB的指標的結構

// Real implementationtype BoltClient struct {        boltDB *bolt.DB}

這裡是OpenBoltDb()的實現,我們之後會加入剩下的兩個函數。

func (bc *BoltClient) OpenBoltDb() {        var err error        bc.boltDB, err = bolt.Open("accounts.db", 0600, nil)        if err != nil {                log.Fatal(err)        }}

這部分代碼可能看起來有點奇怪,其實是我們給一個結構體綁定一個方法函數。我們的結構體隱式的實現了三個方法中的一個。
我們需要一個“bolt client”執行個體在某些地方。讓我們聲明在它會用到的地方, 建立/goblog/accountservice/service/handlers.go,並且建立我們結構體的執行個體:

package service    import (          "github.com/callistaenterprise/goblog/accountservice/dbclient"  )    var DBClient dbclient.IBoltClient

更新main.go,讓他開始時候就開啟資料庫:

func main() {        fmt.Printf("Starting %v\n", appName)        initializeBoltClient()                 // NEW        service.StartWebServer("6767")}// Creates instance and calls the OpenBoltDb and Seed funcsfunc initializeBoltClient() {        service.DBClient = &dbclient.BoltClient{}        service.DBClient.OpenBoltDb()        service.DBClient.Seed()}

我們的微服務現在在啟動時建立一個資料庫。然而,在運行前我們還需要完善代碼:

啟動時seed一些accounts

開啟boltclient加入下面代碼:

// Start seeding accountsfunc (bc *BoltClient) Seed() {        initializeBucket()        seedAccounts()}// Creates an "AccountBucket" in our BoltDB. It will overwrite any existing bucket of the same name.func (bc *BoltClient) initializeBucket() {        bc.boltDB.Update(func(tx *bolt.Tx) error {                _, err := tx.CreateBucket([]byte("AccountBucket"))                if err != nil {                        return fmt.Errorf("create bucket failed: %s", err)                }                return nil        })}// Seed (n) make-believe account objects into the AcountBucket bucket.func (bc *BoltClient) seedAccounts() {        total := 100        for i := 0; i < total; i++ {                // Generate a key 10000 or larger                key := strconv.Itoa(10000 + i)                // Create an instance of our Account struct                acc := model.Account{                        Id: key,                        Name: "Person_" + strconv.Itoa(i),                }                // Serialize the struct to JSON                jsonBytes, _ := json.Marshal(acc)                // Write the data to the AccountBucket                bc.boltDB.Update(func(tx *bolt.Tx) error {                        b := tx.Bucket([]byte("AccountBucket"))                        err := b.Put([]byte(key), jsonBytes)                        return err                })        }        fmt.Printf("Seeded %v fake accounts...\n", total)}

想瞭解Bolt api的update函數如何工作。可以參看BoltDB的文檔

現在我們加入Query函數:

func (bc *BoltClient) QueryAccount(accountId string) (model.Account, error) {        // Allocate an empty Account instance we'll let json.Unmarhal populate for us in a bit.        account := model.Account{}        // Read an object from the bucket using boltDB.View        err := bc.boltDB.View(func(tx *bolt.Tx) error {                // Read the bucket from the DB                b := tx.Bucket([]byte("AccountBucket"))                // Read the value identified by our accountId supplied as []byte                accountBytes := b.Get([]byte(accountId))                if accountBytes == nil {                        return fmt.Errorf("No account found for " + accountId)                }                // Unmarshal the returned bytes into the account struct we created at                // the top of the function                json.Unmarshal(accountBytes, &account)                // Return nil to indicate nothing went wrong, e.g no error                return nil        })        // If there were an error, return the error        if err != nil {                return model.Account{}, err        }        // Return the Account struct and nil as error.        return account, nil}

注釋讓你更容易理解。這段函數將用一個提供的accountId來搜尋BoltDB,之後返回一個Account結構或者error

現在你可以試一下運行:

> go run *.goStarting accountserviceSeeded 100 fake accounts...2017/01/31 16:30:59 Starting HTTP service at 6767

通過HTTP提供account服務

讓我們修改在/service/routes.go中的/accounts/{accountId}路由,讓他返回一個seeded Account結構體。開啟routes.go用GetAccount函數替換func(w http.ResponseWriter, r *http.Request)。我們之後會建立GetAccount函數:

Route{        "GetAccount",             // Name        "GET",                    // HTTP method        "/accounts/{accountId}",  // Route pattern        GetAccount,},

之後,更新service/handlers.go。加入GetAccount函數:

var DBClient dbclient.IBoltClientfunc GetAccount(w http.ResponseWriter, r *http.Request) {    // Read the 'accountId' path parameter from the mux map    var accountId = mux.Vars(r)["accountId"]        // Read the account struct BoltDB    account, err := DBClient.QueryAccount(accountId)        // If err, return a 404    if err != nil {        w.WriteHeader(http.StatusNotFound)        return    }        // If found, marshal into JSON, write headers and content    data, _ := json.Marshal(account)    w.Header().Set("Content-Type", "application/json")    w.Header().Set("Content-Length", strconv.Itoa(len(data)))    w.WriteHeader(http.StatusOK)    w.Write(data)}

這個GetAccount函數符合Gorilla中定義的handler函數格式。所以當Gorilla發現有請求/accounts/{accountId}時,會路由到GetAccount函數。讓我們跑一下試試:

> go run *.goStarting accountserviceSeeded 100 fake accounts...2017/01/31 16:30:59 Starting HTTP service at 6767

用curl來請求這個api。記住,我們加入啦100個accounts.id從10000開始。

> curl http://localhost:6767/accounts/10000{"id":"10000","name":"Person_0"}

不錯,我們的微服務現在通過HTTP應答JSON資料了

效能

讓我們分別看一下記憶體和CPU使用率:

啟動後記憶體使用量率

2.1MB, 不錯。加入內嵌的BoltDB和其他一些路由的代碼之後增加了300kb,相比於最開始的消耗。讓我們用Gatling測試1K req/s。現在我們可是真的返回真正的account資料並且序列化成JSON。

壓力測試下的記憶體使用量

31.2MB。增加內嵌的資料庫並沒有消耗太多資源,相比於第二章中簡單的返回資料服務

效能和CPU使用

1k req/s 用單核的10%左右。BoltDB和JSON序列化並沒有增加太多消耗。順便看一下上面的java程式,用啦三倍多的CPU資源

平均應答時間還是小於1ms。
我們再試一下更大的壓力測試, 4k req/s。(你有可能需要增加OS層面能處理請求的最大值)

記憶體使用量 4k req/s

大約12MB 大約增長4倍。記憶體增長很可能是由於go運行或者Gorilla增加了內部goroutine的數量來並發處理請求。

4k req/s效能

CPU使用率大約30%。當我們運行在16GB RAM/core i7筆記本上,IO或者檔案的訪問將會先於CPU成為瓶頸。

平均延遲升到1ms,95%的請求在3ms之下。我們看到延遲增加了,但是我認為這個accountservice效能不錯。

總結

下一篇我們會討論unit test。我們會用到GoConvey同時mocking BoltDB用戶端。

求贊 謝謝

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.