博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Go微服务系列 - 第三部分 - 嵌入数据库和JSON
阅读量:5813 次
发布时间:2019-06-18

本文共 7708 字,大约阅读时间需要 25 分钟。

第三部分: Go微服务 - 嵌入数据库和JSON

在第三部分,我们让accountservice做一些有意义的事情。

  • 声明一个Account结构体。
  • 嵌入简单的key-value存储,我们可以在里边存储Account结构。
  • 将结构体序列化为JSON, 然后通过HTTP服务来为/accounts/{accountId}提供服务。

源代码

源代码位置: 。

声明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存储, 这是一个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压测情况下空间占用信息如下:

clipboard.png

同样我们再次对服务做个压测,得到的空间占用情况如下:

clipboard.png

我们可以看到,在增加了boltdb之后,内存占用由2.1MB变成31.2MB, 增加了30MB左右,还不算太差劲。

clipboard.png

每秒1000个请求,每个CPU核大概使用率是10%,BoltDB和JSON序列化的开销不是很明显,很不错!顺便说下,我们之前的Java进程在Galting压测下,CPU使用大概是它的3倍。

clipboard.png

平均响应时间依然小于1毫秒。 可能我们需要使用更重的压测进行测试,我们尝试使用每秒4K的请求?(注意,我们可能需要增加OS级别的可用文件处理数)。

clipboard.png

占用内存变成118MB多,基本上比原来增加到了4倍。内存增加几乎是因为Go语言运行时或者是因为Gorilla增加了用于服务请求的内部goroutine的数量,因此负载增加。

clipboard.png

CPU基本上保持在30%。 我运行在16GB RAM/Core i7的笔记本上的, 我认为I/O或文件句柄比CPU更快成为性能瓶颈。

clipboard.png

平均吞吐量最后上升到95%的请求在1ms~3ms之间。 确实在4k/s的请求时候,吞吐量受到了些影响, 但是个人认为这个小的accountservice服务使用BoltDB,执行还是相当不错的。

最后的话

下一部分,我们会探讨下使用GoConvey和模拟BoltDB客户端来进行单元测试。

参考链接

转载地址:http://dvtbx.baihongyu.com/

你可能感兴趣的文章
USB 通信原理
查看>>
7zZip zip RAR iOS
查看>>
date命令的详细用法!
查看>>
UiAutomator源码分析之UiAutomatorBridge框架
查看>>
python 开发之selenium
查看>>
Xcode3.2.5中找不到Mac OS X - Command Line Utility -...
查看>>
css的div垂直居中的方法,百分比div垂直居中
查看>>
如何理解EM算法
查看>>
nginx 域名跳转一例~~~(rewrite、proxy)
查看>>
linux用户家目录无损迁移到独立硬盘
查看>>
文件查找
查看>>
shell编程前言(一)
查看>>
5、centos7.*配置yum的EPEL源及其它源
查看>>
JSON前后台简单操作
查看>>
shell中一些常见的文件操作符
查看>>
CentOS 7 装vim遇到的问题和解决方法
查看>>
JavaScript基础教程1-20160612
查看>>
使用第三方类、库需要注意的正则类RegexKitLite的使用
查看>>
iOS \U7ea2 乱码 转换
查看>>
FCN图像分割
查看>>