go-kit 入门
1. microservice
Go-Kit
go kit 是一个分布式的开发工具集,在大型的组织(业务)中可以用来构建微服务。其解决了分布式系统中的大多数常见问题,因此,使用者可以将精力集中在业务逻辑上。
2. go-kit 组件介绍
2.1 Endpoint(端点)
Go kit首先解决了RPC消息模式。其使用了一个抽象的 endpoint 来为每一个RPC建立模型。
endpoint通过被一个server进行实现(implement),或是被一个client调用。这是很多 Go kit组件的基本构建代码块。
2.2 Circuit breaker(回路断路器)
Circuitbreaker(回路断路器) 模块提供了很多流行的回路断路lib的端点(endpoint)适配器。回路断路器可以避免雪崩,并且提高了针对间歇性错误的弹性。每一个client的端点都应该封装(wrapped)在回路断路器中。
2.3 Rate limiter(限流器)
ratelimit模块提供了到限流器代码包的端点适配器。限流器对服务端(server-client)和客户端(client-side)同等生效。使用限流器可以强制进、出请求量在阈值上限以下。
2.4 Transport(传输层)
transport 模块提供了将特定的序列化算法绑定到端点的辅助方法。当前,Go kit只针对JSON和HTTP提供了辅助方法。如果你的组织使用完整功能的传输层,典型的方案是使用Go在传输层提供的函数库,Go kit并不需要来做太多的事情。这些情况,可以查阅代码例子来理解如何为你的端点写一个适配器。目前,可以查看 addsvc的代码来理解Transport绑定是如何工作的。我们还提供了针对Thirft,gRPC,net/rpc,和http json的特殊例子。对JSON/RPC和Swagger的支持在计划中。
2.5 Logging(日志)
服务产生的日志是会被延迟消费(使用)的,或者是人或者是机器(来使用)。人可能会对调试错误、跟踪特殊的请求感兴趣。机器可能会对统计那些有趣的事件,或是对离线处理的结果进行聚合。这两种情况,日志消息的结构化和可操作性是很重要的。Go kit的 log 模块针对这些实践提供了最好的设计。
2.6 Metrics(Instrumentation)度量/仪表盘
直到服务经过了跟踪计数、延迟、健康状况和其他的周期性的或针对每个请求信息的仪表盘化,才能被认为是“生产环境”完备的。Go kit 的 metric 模块为你的服务提供了通用并健壮的接口集合。可以绑定到常用的后端服务,比如 expvar 、statsd、Prometheus。
2.7 Request tracing(请求跟踪)
随着你的基础设施的增长,能够跟踪一个请求变得越来越重要,因为它可以在多个服务中进行穿梭并回到用户。Go kit的 tracing 模块提供了为端点和传输的增强性的绑定功能,以捕捉关于请求的信息,并把它们发送到跟踪系统中。(当前支持 Zipkin,计划支持Appdash
2.8 Service discovery and load balancing(服务发现和负载均衡)
如果你的服务调用了其他的服务,需要知道如何找到它(另一个服务),并且应该智能的将负载在这些发现的实例上铺开(即,让被发现的实例智能的分担服务压力)。Go kit的loadbalancer模块提供了客户端端点的中间件来解决这类问题,无论你是使用的静态的主机名还是IP地址,或是 DNS的 SRV 记录,Consul,etcd 或是 Zookeeper。并且,如果你使用定制的系统,也可以非常容易的编写你自己的 Publisher,以使用 Go kit 提供的负载均衡策略。(目前,支持静态主机名、etcd、Consul、Zookeeper)
3 目标
- 在各种SOA架构中操作–预期会与各种非Go kit服务进行交互
- 使用RPC作为最主要的消息模式
- 可插拔的序列化和传输–不仅仅只有JSON和HTTP
- 简单便可融入现有的架构–没有任何特殊工具、技术的相关指令
4 目标之外(不考虑做的事情)
- 支持除RPC之外的消息模式(至少目前是)–比如 MPI、pub/sub,CQRS,等
- 除适配现有软件外,重新实现一些功能
- 在运维方面进行评论:部署、配置、进程管理、服务编排等
5 依赖管理
Go kit 是一个函数库,设计的目标是引入到二进制文件中。对于二进制软件包的作者来讲,Vendoring是目前用来确保软件可靠、可重新构建的最好的机制。因此,我们强烈的建议我们的用户使用vendoring机制来管理他们软件的依赖,包括Go kit。
为了避免兼容性和可用性的问题,Go kit没有vendor它自己的依赖,并且并不推荐使用第三方的引用代理。
有一些工具可以让vendor机制更简单,包括 gb、glide、gvt、 govendor 和 vendetta。另外,Go kit使用了一系列的持续集成的机制来确保在尽快地修复那些复杂问题。
5 相关项目
标注有 ★ 的项目对 Go kit 的设计有着特别的影响 (反之亦然)
服务框架
- gizmo, a microservice toolkit from The New York Times ★
- go-micro, a microservices client/server library ★
- gocircuit, dynamic cloud orchestration
- gotalk, async peer communication protocol & library
- h2, a microservices framework ★
- Kite, a micro-service framework
独立组件
afex/hystrix-go, client-side latency and fault tolerance library
armon/go-metrics, library for exporting performance and runtime metrics to external metrics systems
codahale/lunk, structured logging in the style of Google’s Dapper or Twitter’s Zipkin
eapache/go-resiliency, resiliency patterns
sasbury/logging, a tagged style of logging
grpc/grpc-go, HTTP/2 based RPC
inconshreveable/log15, simple, powerful logging for Go ★
mailgun/vulcand, programmatic load balancer backed by etcd
mattheath/phosphor, distributed system tracing
pivotal-golang/lager, an opinionated logging library
rubyist/circuitbreaker, circuit breaker library
Sirupsen/logrus, structured, pluggable logging for Go ★
sourcegraph/appdash, application tracing system based on Google’s Dapper
spacemonkeygo/monitor, data collection, monitoring, instrumentation, and Zipkin client library
streadway/handy, net/http handler filters
vitess/rpcplus, package rpc + context.Context
gdamore/mangos, nanomsg implementation in pure Go
Web 框架
Beego
Gin
Goji
Gorilla
Martini
Negroni
Revel (considered harmful)
- ###其他参考
Architecting for the Cloud — Netflix
Dapper, a Large-Scale Distributed Systems Tracing Infrastructure — Google
Your Server as a Function (PDF) — Twitter
go-kit 入门 (二) 第一个 Go kit 程序
下面让我来们创建一个非常精简的 Go kit 服务
业务逻辑逻辑
服务(Service)是从业务逻辑开始的,在 Go kit 中,我们将服务以 interface 作为模型
|
// StringService provides operations on strings.
type
StringService
interface
{
Uppercase
(
string
)
(
string
,
error
)
Count
(
string
)
int
}
|
这个 interface 需要有一个“实现”
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
type
stringService
struct
{
}
func
(
stringService
)
Uppercase
(
s
string
)
(
string
,
error
)
{
if
s
==
""
{
return
""
,
ErrEmpty
}
return
strings
.
ToUpper
(
s
)
,
nil
}
func
(
stringService
)
Count
(
s
string
)
int
{
return
len
(
s
)
}
// ErrEmpty is returned when input string is empty
var
ErrEmpty
=
errors
.
New
(
"Empty string"
)
|
请求和响应
在 Go kit 中,主要的消息模式是 RPC。因此,接口( interface )的每一个方法都会被模型化为远程过程调用(RPC)。对于每一个方法,我们都定义了请求和响应的结构体,捕获输入、输出各自的所有参数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
type
uppercaseRequest
struct
{
S
string
`
json
:
"s"
`
}
type
uppercaseResponse
struct
{
V
string
`
json
:
"v"
`
Err
string
`
json
:
"err,omitempty"
`
// errors don't define JSON marshaling
}
type
countRequest
struct
{
S
string
`
json
:
"s"
`
}
type
countResponse
struct
{
V
int
`
json
:
"v"
`
}
|
端点 (endpoint)
Go kit 通过 endpoint 提供了非常丰富的功能。
一个端点代表一个RPC,也就是我们服务接口中的一个函数。我们将编写简单的适配器,将我们的服务的每一个方法转换成端点。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
import
(
"golang.org/x/net/context"
"github.com/go-kit/kit/endpoint"
)
func
makeUppercaseEndpoint
(
svc
StringService
)
endpoint
.
Endpoint
{
return
func
(
ctx
context
.
Context
,
request
interface
{
}
)
(
interface
{
}
,
error
)
{
req
:
=
request
.
(
uppercaseRequest
)
v
,
err
:
=
svc
.
Uppercase
(
req
.
S
)
if
err
!=
nil
{
return
uppercaseResponse
{
v
,
err
.
Error
(
)
}
,
nil
}
return
uppercaseResponse
{
v
,
""
}
,
nil
}
}
func
makeCountEndpoint
(
svc
StringService
)
endpoint
.
Endpoint
{
return
func
(
ctx
context
.
Context
,
request
interface
{
}
)
(
interface
{
}
,
error
)
{
req
:
=
request
.
(
countRequest
)
v
:
=
svc
.
Count
(
req
.
S
)
return
countResponse
{
v
}
,
nil
}
}
|
传输(Transports)
现在我们需要将服务暴露给外界,这样它们才能被调用。对于服务如何与外界交互,你的组织可能已经有了定论。可能你会使用 Thrift、基于 HTTP 的自定义 JSON。Go kit支持多种开箱即用的 传输 方式。(Adding support for new ones is easy—just 对新方式的支持是非常简单的。参见 这里
针对我们现在的这个微型的服务例子,我们使用基于 HTTP 的 JSON。Go kit 中提供了一个辅助结构体,在 transport/http 中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
import
(
"encoding/json"
"log"
"net/http"
"golang.org/x/net/context"
httptransport
"github.com/go-kit/kit/transport/http"
)
func
main
(
)
{
ctx
:
=
context
.
Background
(
)
svc
:
=
stringService
{
}
uppercaseHandler
:
=
httptransport
.
NewServer
(
ctx
,
makeUppercaseEndpoint
(
svc
)
,
decodeUppercaseRequest
,
encodeResponse
,
)
countHandler
:
=
httptransport
.
NewServer
(
ctx
,
makeCountEndpoint
(
svc
)
,
decodeCountRequest
,
encodeResponse
,
)
http
.
Handle
(
"/uppercase"
,
uppercaseHandler
)
http
.
Handle
(
"/count"
,
countHandler
)
log
.
Fatal
(
http
.
ListenAndServe
(
":8080"
,
nil
)
)
}
func
decodeUppercaseRequest
(
_
context
.
Context
,
r *
http
.
Request
)
(
interface
{
}
,
error
)
{
var
request
uppercaseRequest
if
err
:
=
json
.
NewDecoder
(
r
.
Body
)
.
Decode
(
&
amp
;
request
)
;
err
!=
nil
{
return
nil
,
err
}
return
request
,
nil
}
func
decodeCountRequest
(
_
context
.
Context
,
r *
http
.
Request
)
(
interface
{
}
,
error
)
{
var
request
countRequest
if
err
:
=
json
.
NewDecoder
(
r
.
Body
)
.
Decode
(
&
amp
;
request
)
;
err
!=
nil
{
return
nil
,
err
}
return
request
,
nil
}
func
encodeResponse
(
_
context
.
Context
,
w
http
.
ResponseWriter
,
response
interface
{
}
)
error
{
return
json
.
NewEncoder
(
w
)
.
Encode
(
response
)
}
|
go-kit 入门(三)日志和仪表化
日志和仪表化
任何服务在日志和仪表化没有就绪的情况下,都不能说是生产环境就绪的。
传输日志
任何需要日志记录的组件都需要将 logger 作为依赖,就像数据库连接一样。因此,我们在 main 函数中构造 logger 对象,然后将其传入需要使用它的组件中。我们始终不去使用一个全局的 logger 对象。
我们可以直接将 logger 传入到 stringService 的实现代码中,但是,还有一个更好的方式。我们可以使用 中间件 (middleware) ,也常常被称为 装饰者。
middleware 是一个函数,它接收一个 endpoint 作为参数,并且返回一个 endpoint。
|
type
Middleware
func
(
Endpoint
)
Endpoint
|
在函数中,它可以做任何事情。下面就让我们来创建一个基本的日志中间件。
|
func
loggingMiddleware
(
logger
log
.
Logger
)
Middleware
{
return
func
(
next
endpoint
.
Endpoint
)
endpoint
.
Endpoint
{
return
func
(
ctx
context
.
Context
,
request
interface
{
}
)
(
interface
{
}
,
error
)
{
logger
.
Log
(
"msg"
,
"calling endpoint"
)
defer
logger
.
Log
(
"msg"
,
"called endpoint"
)
return
next
(
ctx
,
request
)
}
}
}
|
然后,我们将它加入到每一个处理函数中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
logger
:
=
log
.
NewLogfmtLogger
(
os
.
Stderr
)
svc
:
=
stringService
{
}
var
uppercase
endpoint
.
Endpoint
uppercase
=
makeUppercaseEndpoint
(
svc
)
uppercase
=
loggingMiddleware
(
log
.
NewContext
(
logger
)
.
With
(
"method"
,
"uppercase"
)
)
(
uppercase
)
var
count
endpoint
.
Endpoint
count
=
makeCountEndpoint
(
svc
)
count
=
loggingMiddleware
(
log
.
NewContext
(
logger
)
.
With
(
"method"
,
"count"
)
)
(
count
)
uppercaseHandler
:
=
httptransport
.
Server
(
// ...
uppercase
,
// ...
)
countHandler
:
=
httptransport
.
Server
(
// ...
count
,
// ...
)
|
事实证明,这项技术是非常有价值的,远远不止于记录日志,Go kit 的很多模块都被实现为端点中间件。
应用日志
那么,在我们的应用中,应该如何记录日志呢?比如那些需要被传入的参数等。事实上,我们能够为我们的服务定义一个中间件,从而获得同样好的组合效果。由于我们的 StringService被定义为一个接口,我们只需要作出一个新的类型,来包装先有的 StringService,让它来执行扩充的记录日志的任务。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
type
loggingMiddleware
struct
{
logger
log
.
Logger
StringService
}
func
(
mw
loggingMiddleware
)
Uppercase
(
s
string
)
(
output
string
,
err
error
)
{
defer
func
(
begin
time
.
Time
)
{
mw
.
logger
.
Log
(
"method"
,
"uppercase"
,
"input"
,
s
,
"output"
,
output
,
"err"
,
err
,
"took"
,
time
.
Since
(
begin
)
,
)
}
(
time
.
Now
(
)
)
output
,
err
=
mw
.
StringService
.
Uppercase
(
s
)
return
}
func
(
mw
loggingMiddleware
)
Count
(
s
string
)
(
n
int
)
{
defer
func
(
begin
time
.
Time
)
{
mw
.
logger
.
Log
(
"method"
,
"count"
,
"input"
,
s
,
"n"
,
n
,
"took"
,
time
.
Since
(
begin
)
,
)
}
(
time
.
Now
(
)
)
n
=
mw
.
StringService
.
Count
(
s
)
return
}
|
然后,将新的类型引入到下面的代码中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
import
(
"os"
"github.com/go-kit/kit/log"
httptransport
"github.com/go-kit/kit/transport/http"
)
func
main
(
)
{
logger
:
=
log
.
NewLogfmtLogger
(
os
.
Stderr
)
svc
:
=
stringService
{
}
svc
=
loggingMiddleware
{
logger
,
svc
}
uppercaseHandler
:
=
httptransport
.
NewServer
(
// ...
makeUppercaseEndpoint
(
svc
)
,
// ...
)
countHandler
:
=
httptransport
.
NewServer
(
// ...
makeCountEndpoint
(
svc
)
,
// ...
)
}
|
在传输环节使用端点中间件,比如回路断路器和速率限制。在业务环节使用服务中间件,比如日志和仪表化。
仪表化
在 Go kit 中,仪表化意味着使用 包指标 来记录关于服务运行行为的状态。统计执行的任务的数量,在请求完成后记录消耗的时间,以及跟踪所有正在执行的操作的数量,都被认为是 仪表化。
我们可以使用同样的中间件模式,在记录日志的环节我们曾经用过。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
type
instrumentingMiddleware
struct
{
requestCount
metrics
.
Counter
requestLatency
metrics
.
TimeHistogram
countResult
metrics
.
Histogram
StringService
}
func
(
mw
instrumentingMiddleware
)
Uppercase
(
s
string
)
(
output
string
,
err
error
)
{
defer
func
(
begin
time
.
Time
)
{
methodField
:
=
metrics
.
Field
{
Key
:
"method"
,
Value
:
"uppercase"
}
errorField
:
=
metrics
.
Field
{
Key
:
"error"
,
Value
:
fmt
.
Sprintf
(
"%v"
,
err
)
}
mw
.
requestCount
.
With
(
methodField
)
.
With
(
errorField
)
.
Add
(
1
)
mw
.
requestLatency
.
With
(
methodField
)
.
With
(
errorField
)
.
Observe
(
time
.
Since
(
begin
)
)
}
(
time
.
Now
(
)
)
output
,
err
=
mw
.
StringService
.
Uppercase
(
s
)
return
}
func
(
mw
instrumentingMiddleware
)
Count
(
s
string
)
(
n
int
)
{
defer
func
(
begin
time
.
Time
)
{
methodField
:
=
metrics
.
Field
{
Key
:
"method"
,
Value
:
"count"
}
errorField
:
=
metrics
.
Field
{
Key
:
"error"
,
Value
:
fmt
.
Sprintf
(
"%v"
,
error
(
nil
)
)
}
mw
.
requestCount
.
With
(
methodField
)
.
With
(
errorField
)
.
Add
(
1
)
mw
.
requestLatency
.
With
(
methodField
)
.
With
(
errorField
)
.
Observe
(
time
.
Since
(
begin
)
)
mw
.
countResult
.
Observe
(
int64
(
n
)
)
}
(
time
.
Now
(
)
)
n
=
mw
.
StringService
.
Count
(
s
)
return
}
|
然后将其引入到服务中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
import
(
stdprometheus
"github.com/prometheus/client_golang/prometheus"
kitprometheus
"github.com/go-kit/kit/metrics/prometheus"
"github.com/go-kit/kit/metrics"
)
func
main
(
)
{
logger
:
=
log
.
NewLogfmtLogger
(
os
.
Stderr
)
fieldKeys
:
=
[
]
string
{
"method"
,
"error"
}
requestCount
:
=
kitprometheus
.
NewCounter
(
stdprometheus
.
CounterOpts
{
// ...
}
,
fieldKeys
)
requestLatency
:
=
metrics
.
NewTimeHistogram
(
time
.
Microsecond
,
kitprometheus
.
NewSummary
(
stdprometheus
.
SummaryOpts
{
// ...
}
,
fieldKeys
)
)
countResult
:
=
kitprometheus
.
NewSummary
(
stdprometheus
.
SummaryOpts
{
// ...
}
,
[
]
string
{
}
)
)
svc
:
=
stringService
{
}
svc
=
loggingMiddleware
{
logger
,
svc
}
svc
=
instrumentingMiddleware
{
requestCount
,
requestLatency
,
countResult
,
svc
}
uppercaseHandler
:
=
httptransport
.
NewServer
(
// ...
makeUppercaseEndpoint
(
svc
)
,
// ...
)
countHandler
:
=
httptransport
.
NewServer
(
// ...
makeCountEndpoint
(
svc
)
,
// ...
)
http
.
Handle
(
"/metrics"
,
stdprometheus
.
Handler
(
)
)
}
|
stringsvc2
目前位置,完整的服务是 stringsvc2.
|
$
go
get
github
.
com
/
go
-
kit
/
kit
/
examples
/
stringsvc2
$
stringsvc2
msg
=
HTTP
addr
=
:
8080
|
|
$
curl
-
XPOST
-
d
'{"s":"hello, world"}'
localhost
:
8080
/
uppercase
{
"v"
:
"HELLO, WORLD"
,
"err"
:
null
}
$
curl
-
XPOST
-
d
'{"s":"hello, world"}'
localhost
:
8080
/
count
{
"v"
:
12
}
|
|
method
=
uppercase
input
=
"hello, world"
output
=
"HELLO, WORLD"
err
=
null
took
=
2.455µ
s
method
=
count
input
=
"hello, world"
n
=
12
took
=
743ns
|
Go-kit 入门(四)服务调用
调用服务
存在“真空”(即极其独立,与其他任何服务无互相调用的关系)中的服务是罕见的。而我们常见的是,我们需要调用其他的服务。这也是 Go kit 的闪光点 ,我们提供了 传输中间件机制来解决可能出现的很多问题。
下面我们将实现一个代理功能的中间件,作为一个服务中间件。在这里我们只代理一个方法,Uppercase。
|
// proxymw implements StringService, forwarding Uppercase requests to the
// provided endpoint, and serving all other (i.e. Count) requests via the
// embedded StringService.
type
proxymw
struct
{
context
.
Context
StringService
// Serve most requests via this embedded service...
UppercaseEndpoint
endpoint
.
Endpoint
// ...except Uppercase, which gets served by this endpoint
}
|
客户端端点
我们已经有了一个跟我们所知道的完全相同的端点,但是我们将使用它来调用一个请求,而不是提供服务。按照这种方式来使用它的时候,我们称它为客户端端点。为了调用客户端端点,我们需要做一些简单的转换。
|
func
(
mw
proxymw
)
Uppercase
(
s
string
)
(
string
,
error
)
{
response
,
err
:
=
mw
.
UppercaseEndpoint
(
mw
.
Context
,
uppercaseRequest
{
S
:
s
}
)
if
err
!=
nil
{
return
""
,
err
}
resp
:
=
response
.
(
uppercaseResponse
)
if
resp
.
Err
!=
""
|