热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

基于go搭建微服务实践教程(三)

原文地址转载请注明原文及翻译地址在第三节,我们要让我们的accountservice做一些有用的事情。声明一个Account结构嵌入一个键值对的存储,用来存储Account结构序列

原文地址

转载请注明原文及
翻译地址

在第三节,我们要让我们的accountservice做一些有用的事情。

  • 声明一个 Account 结构
  • 嵌入一个键值对的存储,用来存储Account结构
  • 序列化结构为JSON,并且用于我们的accounts/{accountId} HTTP服务

源代码

这篇博客中的所有代码可以从分支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, 小写=包内调用)。
在声明中,我们也用到了内置的json.marshal函数对参数序列化的支持。

嵌入一个键值对的存储

这里,我们会用到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)
}
}

这部分代码可能看起来有点奇怪,其实是我们给一个结构体绑定一个方法函数。我们的结构体隐式的实现了三个方法中的一个。
我们需要一个“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 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 // 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使用率:

启动后内存使用率

《基于go搭建微服务实践教程 (三)》

2.1MB, 不错。加入内嵌的BoltDB和其他一些路由的代码之后增加了300kb,相比于最开始的消耗。让我们用Gatling测试1K req/s。现在我们可是真的返回真正的account数据并且序列化成JSON。

压力测试下的内存使用

《基于go搭建微服务实践教程 (三)》

31.2MB。增加内嵌的数据库并没有消耗太多资源,相比于第二章中简单的返回数据服务

性能和CPU使用

《基于go搭建微服务实践教程 (三)》

1k req/s 用单核的10%左右。BoltDB和JSON序列化并没有增加太多消耗。顺便看一下上面的java程序,用啦三倍多的CPU资源

《基于go搭建微服务实践教程 (三)》

平均应答时间还是小于1ms。
我们再试一下更大的压力测试, 4k req/s。(你有可能需要增加OS层面能处理请求的最大值)

内存使用 4k req/s

《基于go搭建微服务实践教程 (三)》

大约12MB 大约增长4倍。内存增长很可能是由于go运行或者Gorilla增加了内部goroutine的数量来并发处理请求。

4k req/s性能

《基于go搭建微服务实践教程 (三)》

CPU使用率大约30%。当我们运行在16GB RAM/core i7笔记本上,IO或者文件的访问将会先于CPU成为瓶颈。

《基于go搭建微服务实践教程 (三)》

平均延迟升到1ms,95%的请求在3ms之下。我们看到延迟增加了,但是我认为这个accountservice性能不错。

总结

下一篇我们会讨论unit test。我们会用到GoConvey同时mocking BoltDB客户端。

求赞 谢谢


推荐阅读
  • Android系统源码分析Zygote和SystemServer启动过程详解
    本文详细解析了Android系统源码中Zygote和SystemServer的启动过程。首先介绍了系统framework层启动的内容,帮助理解四大组件的启动和管理过程。接着介绍了AMS、PMS等系统服务的作用和调用方式。然后详细分析了Zygote的启动过程,解释了Zygote在Android启动过程中的决定作用。最后通过时序图展示了整个过程。 ... [详细]
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • 解决Cydia数据库错误:could not open file /var/lib/dpkg/status 的方法
    本文介绍了解决iOS系统中Cydia数据库错误的方法。通过使用苹果电脑上的Impactor工具和NewTerm软件,以及ifunbox工具和终端命令,可以解决该问题。具体步骤包括下载所需工具、连接手机到电脑、安装NewTerm、下载ifunbox并注册Dropbox账号、下载并解压lib.zip文件、将lib文件夹拖入Books文件夹中,并将lib文件夹拷贝到/var/目录下。以上方法适用于已经越狱且出现Cydia数据库错误的iPhone手机。 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • 关键词:Golang, Cookie, 跟踪位置, net/http/cookiejar, package main, golang.org/x/net/publicsuffix, io/ioutil, log, net/http, net/http/cookiejar ... [详细]
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • Imtryingtofigureoutawaytogeneratetorrentfilesfromabucket,usingtheAWSSDKforGo.我正 ... [详细]
  • 电话号码的字母组合解题思路和代码示例
    本文介绍了力扣题目《电话号码的字母组合》的解题思路和代码示例。通过使用哈希表和递归求解的方法,可以将给定的电话号码转换为对应的字母组合。详细的解题思路和代码示例可以帮助读者更好地理解和实现该题目。 ... [详细]
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • 本文讨论了使用差分约束系统求解House Man跳跃问题的思路与方法。给定一组不同高度,要求从最低点跳跃到最高点,每次跳跃的距离不超过D,并且不能改变给定的顺序。通过建立差分约束系统,将问题转化为图的建立和查询距离的问题。文章详细介绍了建立约束条件的方法,并使用SPFA算法判环并输出结果。同时还讨论了建边方向和跳跃顺序的关系。 ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
author-avatar
mr.sun
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有