在博客系列的第2部分中,我们将:
· 设置Go工作区
· 构建第一个微服务
· 使用GorillaWeb Toolkit在HTTP上提供一些JSON。
为了专注于Go基础知识,我们会稍后在DockerSwarm上部署它。
虽然通过HTTP提供JSON并不是进行内部服务或外部通信的唯一选择,但我们将在本系列博文中关注HTTP和JSON。当您的服务的外部消费者是另一个系统时,使用RPC机制和协议缓冲区等二进制消息格式可以成为服务间通信或外部通信,是非常有趣的选择。Go内置支持RPC,值得关注。但是,现在我们将重点关注go内置的http软件包和Gorilla Web Toolkit来提供HTTP 。
需要考虑的另一方面是:许多有用的框架(安全性,跟踪......)依赖于HTTP头来传输参与者之间正在进行的请求的状态。稍后的博客文章中将看到的例子是:如何在HTTP标头中传递关联标识和OAuth承载。虽然其他协议肯定支持类似的机制,但许多框架都是以HTTP为基础构建的,但我努力做到保持集成尽可能简单。
如果您已是经验丰富的Go开发人员,请随意跳过本节。在我看来,Go工作空间的结构需要花一些时间适应。在习惯于将项目的根作为工作空间根的地方后,Go约定如何正确地构建工作空间,以便编译器能够找到源代码和依赖关系有点不合常规,将源代码放在/ src文件夹下及子目录下。强烈建议在开始之前阅读官方指南和本文。希望我有。
在编写第一行代码之前(或者查看完整的源代码),需要安装GoSDK。建议遵循官方指南,它应该足够简单。
在博客系列中,将使用刚刚安装的用于构建和运行的内置GoSDK工具,并遵循设置Go工作区的惯用方式。
所有命令都基于OS X或Linux开发环境。如果您正在运行Windows,请根据需要修改说明。
mkdir ~/goworkspace
cd goworkspace
export GOPATH=`pwd`
在这里,我们创建了一个根文件夹,然后将环境变量
GOPATH
指定到该文件夹。这是工作空间的根目录,所编写的
Go
源代码或将使用的第三方库将存放在这。建议将这个
GOPATH
添加到
.bash_profile
或类似的文件中,这样每次打开新的控制台窗口时,都不必重置它。
当前我们位于工作空间的根目录中(例如,与GOPATH env var中指定的文件夹相同),请执行以下语句:
mkdir -p src/github.com/callistaenterprise
如果您想遵循并自己编写代码,请执行以下命令:
cd src/github.com/callistaenterprise
mkdir -p goblog/accountservice
cd goblog/accountservice
touch main.go
mkdir service
或者,您可以克隆包含示例代码的git存储库并切换到分支P2。从上面创建的src /github.com / callistaenterprise文件夹中,执行:
git clone https://github.com/callistaenterprise/goblog.git
cd goblog
git checkout P2
请记住 -$ GOPATH/src/github.com/callistaenterprise/goblog是项目的根文件夹,实际存储在github上。
现在应该有足够的结构来让我们开始。在GoIDE中打开main.go。在为本博客系列编写代码时,我正在使用IntelliJIDEA及其出色的Golang插件。其他流行的选择似乎是Eclipse(带Go插件),Atom,Sublime,vim或JetBrains新的专用商业Gogland IDE。
如您所期望,main函数正是Go程序的入口。让我们创建更多的代码来获得可以真实构建和运行的东西:
package main
import (
"fmt"
)
var appName = "accountservice"
func main() {
fmt.Printf("Starting %v\n", appName)
}
现在,来运行它。确认当前路径为$ GOPATH/src/github.com/callistaenterprise/goblog/accountservice及子文件夹中
> go run *.go
Starting accountservice
>
就是这样!
该程序将打印并退出。
接下来,添加第一个HTTP端点!
注意:这些HTTP示例的基础知识来自一篇出色的博客文章
为了保持整洁,我们将所有HTTP服务相关文件放入service文件夹中。
在/ services文件夹中创建文件webserver.go:
package service
import (
"net/http"
"log"
)
func StartWebServer(port string) {
log.Println("Starting HTTP service at " + port)
err := http.ListenAndServe(":" + port, nil) // Goroutine will block here
if err != nil {
log.Println("An error occured starting HTTP listener at port " + port)
log.Println("Error: " + err.Error())
}
}
使用内置的net / http包来执行ListenAndServe,它在指定的端口上启动HTTP服务器。
修改main.go,指定端口调用StartWebServer函数:
package main
import (
"fmt"
"github.com/callistaenterprise/goblog/accountservice/service" // NEW
)
var appName = "accountservice"
func main() {
fmt.Printf("Starting %v\n", appName)
service.StartWebServer("6767") // NEW
}
再次运行:
> go run *.go
Starting accountservice
2017/01/30 19:36:00 Starting HTTP service at 6767
现在一个简单的HTTP服务器,在本地主机6767端口监听。访问一下:
> curl http://localhost:6767
404 page not found
404正是我们期待的,因为还没有发布路由信息。按Ctrl+ C停止Web服务器。
现在是时候从服务器实际提供服务了。首先使用Go 结构声明我们的第一条路由,然后使用它来填充Gorilla路由器。在 service文件夹中,创建routes.go:
package service
import "net/http"
// Defines a single route, e.g. a human readable name, HTTP method and the
// 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
// Initialize our routes
var routes = Routes{
Route{
"GetAccount", // Name
"GET", // HTTP method
"/accounts/{accountId}", // Route pattern
func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.Write([]byte("{\"result\":\"OK\"}"))
},
},
}
在上面的代码片段中,声明了稍后可以调整的路径/accounts / {accountId}。Gorilla还支持使用正则表达式模式匹配、方案、方法、查询、标题值等复杂路由,当然不仅限于路径和路径参数。
目前,只返回一个指定的JSON消息:
{"result":"OK"}
还需要一些示例代码,将声明的路由注入到Gorilla路由器。在service文件夹中,创建router.go:
package service
import (
"github.com/gorilla/mux"
)
// Function that returns a pointer to a mux.Router we can use as a handler.
func NewRouter() *mux.Router {
// Create an instance of the Gorilla router
router := mux.NewRouter().StrictSlash(true)
// Iterate over the routes we declared in routes.go and attach them to the router instance
for _, route := range routes {
// Attach each route, uses a Builder-like pattern to set each route up.
router.Methods(route.Method).
Path(route.Pattern).
Name(route.Name).
Handler(route.HandlerFunc)
}
return router
}
在router.go的import部分,可以看到已经声明了对github.com/gorilla/mux包的依赖关系。请参阅此处,了解如何使用go get获取go依赖关系。
为了让上面的文件编译和运行,需要使用go get获取声明的包到工作区中:
> go get
这可能需要一些时间,因为Go工具实际上是从https://github.com/gorilla/mux下载了gorilla / mux包所需的所有源代码。此源代码将以本地文件系统中的$ GOPATH/src/github.com/gorilla/mux内部,并将其内置到静态链接二进制文件中。
现在,重新访问webserver.go并在StartWebServer函数的开头添加以下两行:
func StartWebServer(port string) {
r := NewRouter() // NEW
http.Handle("/", r) // NEW
这将刚创建的路由器连接到根路径为/的http.Handle 。再次编译并运行服务器。
> go run *.go
Starting accountservice
2017/01/31 15:15:57 Starting HTTP service at 6767
尝试浏览:
> curl http://localhost:6767/accounts/10000
{"result":"OK"}
太好了!刚刚创建了第一个HTTP服务!
鉴于我们正在探索基于Go的微服务,因为所谓的内存占用很大,而且性能很好,所以最好做一个快速的基准测试,看看它的表现如何。我开发了一个简单的Gatling测试,用GET请求锤击/ accounts / {accountId}。如果您已检出此部分的源代码,则可以在/ goblog/ loadtest文件夹中找到负载测试。或者你可以在github上看看它。
如果您想自己运行负载测试,请确保“accountservice”已启动并在本地主机上运行,并且已经克隆了源并检出了分支P2。您还需要安装Java运行时环境和Apache Maven。
将目录切换到/ goblog / loadtest文件夹并从命令行执行以下命令:
> mvn gatling:execute -Dusers=1000 -Dduration=30 -DbaseUrl=http://localhost:6767
现在应该开始运行测试。参数:
· 用户:测试将模拟的并发用户数量
· 持续时间:测试将运行多少秒
· baseUrl:提供正在测试的服务的主机的基本路径。当使用Docker Swarm时,baseUrl需要更改为Swarm的公有IP。更多的在第5部分。
测试完成后,它将结果写入控制台窗口,并将一个美观的HTML报告写入target /gatling / results /。
注意:稍后,当把构建的服务在DockerSwarm的Docker容器中运行时,将执行捕获的所有基准和指标。在那之前,我的2014年中期MacBookPro已经说明了。
在开始负载测试之前,查看OS X任务管理器,基于Go的“accountservice”的内存消耗如下所示:
1.8 MB,不错!开始运行1K req/s的Gatling测试。请记住,这是一个非常简单的实现,只是用代码的字符串进行响应。
好吧,服务1K req/s的“accountservice”消耗大约28 MB的RAM。这仍然是Spring Boot应用程序启动时的1/10。键入开始添加一些真实的功能,看看这个数字如何变化?将是非常有趣的。
服务1K req/s使用单个Core的8%左右。
注意:Gatling有毫秒级的延迟,但平均延迟报告为0毫秒,其中一项请求需要大约11毫秒。此时,“accountservice”表现出色,在亚毫秒范围内平均服务745〜req/s。
在接下来的部分,我们会真正使的accountservice做一些有用的东西。将添加一个简单的嵌入式数据库,其中包含通过HTTP提供的Account对象。还将看看JSON序列化,并检查这些服务的添加如何影响其内存和性能。
原文地址:http://callistaenterprise.se/blogg/teknik/2017/02/21/go-blog-series-part2/