基于go搭建微服务实践教程 (三)
原文地址 在第三节,我们要让我们的accountservice做一些有用的事情。
源代码这篇博客中的所有代码可以从分支p3中得到。 git checkout P3 声明一个Account结构在我们的项目中,在accountservice文件夹中创建一个model文件夹 mkdir model 在model文件夹下创建一个文件名字为account.go并写入以下代码: package model type Account struct { Id string `json:"id"` Name string `json:"name"` } 这里面声明了Account,包含id和name。第一个字母的大小写表示作用域(大写=public, 小写=包内调用)。 嵌入一个键值对的存储这里,我们会用到BoltDB来存储键值对。这个包简单快速容易集成。我们可以用go get来得到这个包 go get github.com/boltdb/boltdb/b 之后,在/goblog/accountservice文件夹中,建立一个文件夹dbclient,在dbclient中创建文件boltclient.go。为了使mocking更容易,我们先声明一个接口,用来制定实现者需要遵从的方法。 package dbclient import ( "github.com/callistaenterprise/goblog/accountservice/model" ) type IBoltClient interface { OpenBoltDb() QueryAccount(accountId string) (model.Account,error) Seed() } 在同一个文件中,我们会实现这个接口。先定义一个封装了bolt.DB的指针的结构 // Real implementation type 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) } } 这部分代码可能看起来有点奇怪,其实是我们给一个结构体绑定一个方法函数。我们的结构体隐式的实现了三个方法中的一个。 package service import ( "github.com/callistaenterprise/goblog/accountservice/dbclient" ) var DBClient dbclient.IBoltClient 更新main.go,让他开始时候就打开数据库: func main() { fmt.Printf("Starting %vn",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() } 我们的微服务现在在启动时创建一个数据库。然而,在运行前我们还需要完善代码: 启动时seed一些accounts打开boltclient加入下面代码: // 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) } 想了解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 *.go Starting accountservice Seeded 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.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) } 这个GetAccount函数符合Gorilla中定义的handler函数格式。所以当Gorilla发现有请求/accounts/{accountId}时,会路由到GetAccount函数。让我们跑一下试试: > go run *.go Starting accountservice Seeded 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
大约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客户端。 求赞 谢谢 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |