Go微服務系列 - 第三部分 - 嵌入資料庫和JSON

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

第三部分: Go微服務 - 嵌入資料庫和JSON

在第三部分,我們讓accountservice做一些有意義的事情。

  • 聲明一個Account結構體。
  • 嵌入簡單的key-value儲存,我們可以在裡邊儲存Account結構。
  • 將結構體序列化為JSON, 然後通過HTTP服務來為/accounts/{accountId}提供服務。

原始碼

原始碼位置: https://github.com/callistaen...。

聲明Account結構體

結構體的詳細說明可以參照參考連結部分的相關連結查看。

  1. 在我們的項目根目錄accountservice下面建立一個名為model的目錄。
  2. 在model目錄下面建立account.go檔案。
package modeltype Account struct {    Id string `json:"id"`    Name string `json:"name"`}

Account抽象成包含Id和Name的結構體。結構體的兩個屬性首字母為大寫,表示聲明的是全域範圍可見的(標識符首字母大寫public, 首字母小寫包範圍可見)。

另外結構體中還使用了標籤(Tag)。這些標籤在encoding/json和encoding/xml中有特殊應用。

假設我們定義結構體的時候沒有使用標籤,對於結構體通過json.Marshal之後產生的JSON的key使用結構體欄位名對應的值。

例如:

type Account struct {    Id string    Name string}var account = Account{    Id: 10000,    Name: "admin",}

轉換為json之後得到:

{    "Id": 10000,    "Name": "admin"}

而這種形式一般不是JSON的慣用形式,我們通常更習慣使用json的key首字母為小寫,那麼結構體標籤就可以派上用場了:

type Account struct {    Id string `json:"id"`    Name string `json:"name"`}var account = Account{    Id: 10000,    Name: "admin",}

這個時候轉換為JSON的時候,我們就得到如下結果:

{    "id": 10000,    "name": "admin"}

嵌入一個key-value儲存

為了簡單起見,我們使用一個簡單的key-value儲存BoltDB, 這是一個Go語言的嵌入式key-value資料庫。它主要能為應用提供快速、可信賴的資料庫,這樣我們無需複雜的資料庫,比如MySql或Postgres等。

我們可以通過go get擷取它的原始碼:

go get github.com/boltdb/bolt

接下來,我們在accountservice目錄下面建立一個dbclient的目錄,並在它下面建立boltclient.go檔案。 為了後續類比的方便,我們聲明一個介面,定義我們實現需要履行的合約:

package dbclientimport (    "github.com/callistaenterprise/goblog/accountservice/model")type IBoltClient interface() {    OpenBoltDb()    QueryAccount(accountId string) (model.Account, error)    Seed()}// 真實實現type BoltClient struct {    boltDb *bolt.DB}func (bc *BoltClient) OpenBoltDB() {    var err error    bc.boltDB, err = bolt.Open("account.db", 0600, nil)    if err != nil {        log.Fatal(err)    }}

上面代碼聲明了一個IBoltClient介面, 規定了該介面的合約是具有三個方法。我們聲明了一個具體的BoltClient類型, 暫時只為它實現了OpenBoltDB方法。這種實現介面的方法,突然看起來可能感覺有點奇怪,把函數綁定到一個結構體上。這就是Go語言介面實現的特色。其他兩個方法暫時先跳過。

我們現在有了BoltClient結構體,接下來我們需要在項目中的某個位置有這個結構體的一個執行個體。 那麼我們就將它放到我們即將使用的地方, 放在我們的goblog/accountservice/service/handlers.go檔案中。 我們首先建立這個檔案,然後添加BoltClient的執行個體:

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

然後更新main.go代碼,讓它啟動的時候開啟DB。

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()}

這樣我們的微服務啟動的時候就會開啟資料庫。但是,這裡還是什麼都沒有做。 我們接下來添加一些代碼,讓服務啟動的時候可以為我們引導一些帳號。

啟動時填充一些帳號

開啟boltclient.go代碼檔案,為BoltClient添加一個Seed方法:

// 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)}

上面我們的Seed方法首先使用"AccountBucket"字串建立一個Bucket, 然後連續建立100個初始化帳號。帳號id分別依次為10000~10100, 其Name分別為Person_i(i = 0 ~ 100)。

前面我們在main.go中已經調用了Seed()方法,因此這個時候我們可以運行下當前的程式,看看運行情況:

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

很不錯!那麼我們先暫停執行,使用Ctrl + C讓服務先停下來。

添加查詢方法

接下來我們可以為boltclient.go中添加一個Query方法來完成DB API。

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在我們之前初始化的DB中尋找這個賬戶的相關資訊。如果成功尋找到相關帳號,返回這個帳號的json資料,否則會返回nil。

通過HTTP提供帳號服務

讓我們修改在/service/routes.go檔案中聲明的/accounts/{accountId}路由,讓它返回我們填充的帳號其中一個記錄。代碼修改如下:

package serviceimport "net/http"// Defines a single route, e.g. a human readable name, HTTP method, 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        GetAccount,    },}

接下來,我們更新下/service/handlers.go,建立一個GetAccount函數來實現HTTP處理器函數簽名:

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)}

上面代碼就是實現了處理器函數簽名,當Gorilla檢測到我們在請求/accounts/{accountId}的時候,它就會將請求路由到這個函數。 下面我們運行一下我們的服務。

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

然後另外開一個視窗,curl請求accountId為10000的請求:

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

非常棒,我們微服務現在能夠動態提供一些簡單的資料了。你可以嘗試使用accountId為10000到10100之間的任何數字,得到的JSON都不相同。

佔用空間和效能

(FOOTPRINT在這裡解釋為佔用空間, 記憶體空間)。

第二部分,我們看到在Galtling壓測情況下空間佔用資訊如下:

同樣我們再次對服務做個壓測,得到的空間佔用情況如下:

我們可以看到,在增加了boltdb之後,記憶體佔用由2.1MB變成31.2MB, 增加了30MB左右,還不算太差勁。

每秒1000個請求,每個CPU核大概使用率是10%,BoltDB和JSON序列化的開銷不是很明顯,很不錯!順便說下,我們之前的Java進程在Galting壓測下,CPU使用大概是它的3倍。

平均回應時間依然小於1毫秒。 可能我們需要使用更重的壓測進行測試,我們嘗試使用每秒4K的請求?(注意,我們可能需要增加OS層級的可用檔案處理數)。

佔用記憶體變成118MB多,基本上比原來增加到了4倍。記憶體增加幾乎是因為Go語言運行時或者是因為Gorilla增加了用於服務要求的內部goroutine的數量,因此負載增加。

CPU基本上保持在30%。 我運行在16GB RAM/Core i7的筆記本上的, 我認為I/O或檔案控制代碼比CPU更快成為效能瓶頸。

平均輸送量最後上升到95%的請求在1ms~3ms之間。 確實在4k/s的請求時候,輸送量受到了些影響, 但是個人認為這個小的accountservice服務使用BoltDB,執行還是相當不錯的。

最後的話

下一部分,我們會探討下使用GoConvey和類比BoltDB用戶端來進行單元測試。

參考連結

  • 英文地址
  • Go結構體和介面
  • 微服務系列入口
  • 下一節
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.