Go微服务 - 第二部分 - 构建第一个Go微服务

第二部分: Go微服务 - 构建咱们的第一个服务

第二部分包含:html

  • 设置咱们的Go工做空间。
  • 构建咱们第一个微服务。
  • 经过HTTP使用Gorilla Web Toolkit来提供一些JSON服务。

介绍

虽然经过HTTP提供JSON服务不是内部服务和外部服务的惟一选择,但本文聚焦的是HTTP和JSON. 使用RPC机制和二进制消息格式(例如Protocol Buffer)也用于内部通讯或外部通讯也是很是有趣的,特别是当外部消费者属于另一个系统的时候。Go语言有内置的RPC支持,而且gRPC也是彻底值得看看的。 然而,咱们如今只聚焦基于由http包和Gorilla Web Toolkit提供的HTTP。git

另一个须要考虑的方面是不少有用的框架(安全、跟踪等等), 依赖于HTTP头来传输参与者正在进行的请求状态。咱们在本文中将看到的例子是咱们如何在头中传递相关ID和OAuth票据。虽然其余协议固然也支持相似的机制, 不少框架都是以HTTP构建的,我更愿意尽量的保持咱们的集成更加直接。github

设置Go工做空间

若是你是一个经验丰富的Go开发者,你能够随意跳过本节内容。 以我拙见,Go语言工做空间结构须要一些时间来适应。通常来讲我习惯使用项目根做为工做空间的根,Go语言约定了如何恰当的构造工做空间,所以go编译器能够查找源代码和依赖,有点不正统, 将源代码放在子目录下源码控制路径后以src命名的目录中.我强烈推荐读下官方指南和本文,而后再开始。 我但愿我就是这样的。web

安装SDK

在开始写咱们第一行代码以前(或check out完整代码以前), 咱们须要安装Go语言SDK。建议按照官方指导来操做,直接操做就足够了。数据库

设置开发环境

在这些博客系列中,咱们将使用咱们安装的内置的Go SDK工具来构建和运行咱们的代码,以及按照惯用方式来设置Go的工做空间。json

1. 建立工做空间的根目录

全部命令都基于OS X或Linux开发环境。 若是你运行的是Windows, 请采用必要的指令。api

mkdir ~/goworkspace
cd goworkspace
export GOPATH=`pwd`

这里咱们建立了一个根目录,而后将GOPATH环境变量赋于那个目录。这就是咱们的工做空间的根目录,咱们所写的全部Go语言代码和第三方类库都在它下面。我推荐添加这个GOPATH到.bash_profile文件或相似的配置文件中,这样不须要每次都为每一个控制台窗口重置它。安全

2. 为咱们第一个项目建立文件夹和文件

鉴于咱们已经在工做空间的根目录(例如,和在GOPATH环境变量中指定相同的目录), 执行下面的语句:bash

mkdir -p src/github.com/callistaenterprise

若是你但愿遵循本身的编码,能够执行下面的命令:服务器

cd src/github.com/callistaenterprise
mkdir -p goblog/accountservice
cd goblog/accountservice
touch main.go
mkdir service

或者你能够clone这个git仓库,包含相同代码,并切换到P2分支。 从上面你建立的src/github.com/callistaenterprise/goblog.git目录, 执行下面的命令。

git clone https://github.com/callistaenterprise/goblog.git
cd goblog
git checkout P2

记住: $GOPATH/src/github.com/callistaenterprise/goblog是咱们项目的根目录,而且实际是存储在github上面的。

那么咱们结构已经足够能够很方便开始了。 用你喜欢的IDE打开main.go文件。

建立服务 - main.go

Go语言中的main函数就是你具体作事的地方 - Go语言应用程序的入口点。 下面咱们看看它的具体代码:

package main

import (
    "fmt"
)

var appName = "accountservice"

func main() {
    fmt.Printf("Starting %v\n", appName)
}

而后运行该程序:

> go run path/to/main.go
Starting accountservice

就是这样的,程序只打印了一个字符串,而后就退出了。是时候添加第一个HTTP端点了。

构建HTTP web服务器

注意: 这些HTTP示例的基础是从一个优秀的博客文章派生出来的, 见参考连接。

为了保持代码整洁,咱们把全部HTTP服务相关的文件放到service目录下面。

启动HTTP服务器

在service目录中建立webservice.go文件。

package service

import (
    "log"
    "net/http"
)

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代码:

package main

import (
    "fmt"
    "github.com/callistaenterprise/goblog/accountservice/service" // 新增代码
)

var appName = "accountservice"

func main() {
    fmt.Printf("Starting %v\n", appName)
    service.StartWebServer("6767") // 新增代码
}

而后再次运行这个程序,获得下面的输出:

> go run *.go
Starting accountservice
2017/01/30 19:36:00 Starting HTTP service at 6767

那么如今咱们就有一个HTTP服务器,它监听localhost的6767端口。而后curl它:

> curl http://localhost:6767
404 page not found

获得404彻底是意料之中的,由于咱们尚未添加任何路由呢。

Ctrl+C中止这个web服务器。

添加第一个路由

是时候让咱们的服务器提供一些真正的服务了。咱们首先用Go语言结构声明咱们的第一个路由,咱们将使用它来填充Gorilla路由器。 在service目录中,建立一个routes.go文件。

package service

import (
    "net/http"
)

// Define 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

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}, 咱们后面会用curl来访问它。Gorilla也支持使用正则模式匹配、schemes, methods, queries, headers值等等的复杂路由。所以不限于路径和路径参数。

咱们在响应的时候,硬编码了一个小的JSON消息:

{
    "result": "OK"
}

咱们还须要一些模式化的代码片断,将咱们声明的路由挂钩到实际的Gorilla Router上。 在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)

    // Iterator 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来获取依赖包的源代码。

WRAPPING UP

咱们能够再回到webserver.go文件,在函数StartWebServer开始位置加入下面两行代码。

func StartWebServer(port string) {
    r := NewRouter()
    http.Handle("/", r)
}

这就将咱们刚建立的Router绑定到http.Handle对/路径的处理。而后从新编译并运行修改后的代码:

> go run *.go
Starting accountservice
2017/01/31 15:15:57 Starting HTTP service at 6767

而后另开一个窗口,curl以下:

> curl http://localhost:6767/accounts/10000
{"result":"OK"}

很好,咱们如今有了咱们第一个HTTP服务。

信息及性能(FOOTPRINT AND PERFORMANCE)

鉴于咱们正在探索基于Go的微服务,因为惊人的内存占用和良好的性能,咱们最好能快速进行基准测试来看看它们如何执行的。
我已经开发了一个简单的Gatling测试, 可使用GET请求对/accounts/{accountId}进行捶打。 若是以前你是直接从https://github.com/callistaen...,那么你的源代码中就包含有负载测试代码goblog/loadtest。或者能够直接查看https://github.com/callistaen...

你本身运行一下负载测试

若是你须要本身运行负载测试工具,确保accountservice服务已启动,而且运行在localhost的6767端口上。而且你已经checkout咱们的P2分支的代码。你还须要Java的运行环境以及须要安装Apache Maven。

改变目录到goblog/loadtest目录下面,在命令行中执行下面的命令。

mvn gatling:execute -Dusers=1000 -Dduration=30 -DbaseUrl=http://localhost:6767

这样就会启动并运行测试。参数以下:

  • users: 模拟测试的并发用户数.
  • duration: 测试要运行的秒数.
  • baseUrl: 咱们要测试的服务的基础路径。当咱们把它迁移到Docker Swarm后,baseUrl修改修改成Swarm的公共IP. 在第5部分会介绍。

首次运行,mvn会自动安装一大堆东西。安装完后,测试完成以后,它会将结果写到控制台窗口,同时也会产生一个报告到target/gatling/results中的html中。

结果

注意: 稍后,当咱们的服务运行到Docker Swarm模式的Docker容器中时, 咱们会在那里作全部基准测试并捕获度量。

在开始负载测试以前,咱们的基于Go的accountservice内存消耗能够从macbook的任务管理器中查看到,大概以下:

clipboard.png

1.8MB, 不是特别坏。让咱们使用Gatling测试,运行每秒1000个请求。须要记住一点,咱们使用了很是幼稚的实现,咱们仅仅响应一个硬编码的JSON响应。

clipboard.png

服务每秒1000个请求,占用的内存也只是增长到28MB。 依然是Spring Boot应用程序启动时候使用内存的1/10. 当咱们给它添加一些真正的功能时,看这些数字变化会更加有意思。

性能和CPU使用率

clipboard.png

提供每秒1000个请求,每一个核大概使用8%。

clipboard.png

注意,Gatling一回合子微秒延迟如何, 可是平均延迟报告值为每一个请求0ms, 花费庞大的11毫秒。 在这点上来看,咱们的accountservice执行仍是表现出色的,在子毫秒范围内大概每秒服务745个请求。

下一章

在下一部分, 咱们将真正的让accountservice作一些有意义的事情。 咱们会添加一个简单的嵌入数据库到Account对象,而后提供HTTP服务。咱们也会看看JSON的序列化,并检查这些增长对于足迹和性能的影响。

参考连接