在第三部分,咱們讓accountservice作一些有意義的事情。git
源代碼位置: https://github.com/callistaen...。github
結構體的詳細說明能夠參照參考連接部分的相關連接查看。golang
package model type Account struct { Id string `json:"id"` Name string `json:"name"` }
Account抽象成包含Id和Name的結構體。結構體的兩個屬性首字母爲大寫,表示聲明的是全局做用域可見的(標識符首字母大寫public, 首字母小寫包做用域可見)。 數據庫
另外結構體中還使用了標籤(Tag)。這些標籤在encoding/json和encoding/xml中有特殊應用。json
假設咱們定義結構體的時候沒有使用標籤,對於結構體經過json.Marshal以後產生的JSON的key使用結構體字段名對應的值。 app
例如:curl
type Account struct { Id string Name string } var account = Account{ Id: 10000, Name: "admin", }
轉換爲json以後獲得:ide
{ "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存儲BoltDB, 這是一個Go語言的嵌入式key-value數據庫。它主要能爲應用提供快速、可信賴的數據庫,這樣咱們無需複雜的數據庫,好比MySql或Postgres等。
咱們能夠經過go get獲取它的源代碼:
go get github.com/boltdb/bolt
接下來,咱們在accountservice目錄下面建立一個dbclient的目錄,並在它下面建立boltclient.go文件。 爲了後續模擬的方便,咱們聲明一個接口,定義咱們實現須要履行的合約:
package dbclient import ( "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 service import ( "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 funcs func initializeBoltClient() { service.DBClient = &dbclient.BoltClient{} service.DBClient.OpenBoltDb() service.DBClient.Seed() }
這樣咱們的微服務啓動的時候就會打開數據庫。可是,這裏仍是什麼都沒有作。 咱們接下來添加一些代碼,讓服務啓動的時候能夠爲咱們引導一些帳號。
打開boltclient.go代碼文件,爲BoltClient添加一個Seed方法:
// Start seeding accounts func (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 *.go Starting accountservice Seeded 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。
讓咱們修改在/service/routes.go文件中聲明的/accounts/{accountId}路由,讓它返回咱們填充的帳號其中一個記錄。代碼修改以下:
package service import "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 []Route var routes = Routes{ Route{ "GetAccount", // Name "GET", // HTTP method "/accounts/{accountId}", // Route pattern GetAccount, }, }
接下來,咱們更新下/service/handlers.go,建立一個GetAccount函數來實現HTTP處理器函數簽名:
var DBClient dbclient.IBoltClient func 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 *.go Starting accountservice Seeded 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客戶端來進行單元測試。