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 框架
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 需要有一个“实现”
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)。对于每一个方法,我们都定义了请求和响应的结构体,捕获输入、输出各自的所有参数。
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,也就是我们服务接口中的一个函数。我们将编写简单的适配器,将我们的服务的每一个方法转换成端点。
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 中。
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(&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(&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)
}
}
}
然后,我们将它加入到每一个处理函数中。
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,让它来执行扩充的记录日志的任务。
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
}
然后,将新的类型引入到下面的代码中:
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 中,仪表化意味着使用 包指标 来记录关于服务运行行为的状态。统计执行的任务的数量,在请求完成后记录消耗的时间,以及跟踪所有正在执行的操作的数量,都被认为是 仪表化。
我们可以使用同样的中间件模式,在记录日志的环节我们曾经用过。
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
}
然后将其引入到服务中:
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 != "" {
return resp.V, errors.New(resp.Err)
}
return resp.V, nil
}
现在,我们为了构造一个代理中间件,我们将一个代理URL字符串转换为一个端点。加入我们使用 HTTP 协议之上的 JSON,我们可以使用 transport/http 包中的一个辅助(helper)函数。
import (
httptransport "github.com/go-kit/kit/transport/http"
)
func proxyingMiddleware(proxyURL string, ctx context.Context) ServiceMiddleware {
return func(next StringService) StringService {
return proxymw{ctx, next, makeUppercaseEndpoint(ctx, proxyURL)}
}
}
func makeUppercaseEndpoint(ctx context.Context, proxyURL string) endpoint.Endpoint {
return httptransport.NewClient(
"GET",
mustParseURL(proxyURL),
encodeUppercaseRequest,
decodeUppercaseResponse,
).Endpoint()
}
服务发现和负载均衡
如果我们只使用一个远程的服务就好了。但是实际上,我们往往 会有很多个服务实例。我们希望通过一些服务发现算法来发现它们,然后将我们的负载分散到它们上面,并且如果这些实例中的任何一个变得糟糕,我们能够处理它,并且不影响我们服务的可用性。
Go kit 为不同的服务发现系统提供了适配器,为了获取最新的服务实例集合,暴露端点个体。这些适配器被称为 发布器(publishers)。
type Publisher interface {
Endpoints() ([]endpoint.Endpoint, error)
}
在发布器内部,它使用一个私有的工厂函数,将被发现的每一个 host:port 字符串 转换成一个可用的端点。
type Factory func(instance string) (endpoint.Endpoint, error)
目前,我们的工程方法,makeUppercaseEndpoint,只是直接请求 URL。但是,在工厂函数中加入一些安全的中间件方法是很重要的,比如 回路断路器 和 限流器。
func factory(ctx context.Context, maxQPS int) loadbalancer.Factory {
return func(instance string) (endpoint.Endpoint, error) {
var e endpoint.Endpoint
e = makeUppercaseProxy(ctx, instance)
e = circuitbreaker.Gobreaker(gobreaker.NewCircuitBreaker(gobreaker.Settings{}))(e)
e = kitratelimit.NewTokenBucketLimiter(jujuratelimit.NewBucketWithRate(float64(maxQPS), int64(maxQPS)))(e)
return e, nil
}
}
现在,我们已经有了一系列的端点,我们需要在其中选择一个。负载均衡器包装了 发布器,并且从端点集合中选择其中的某一个。Go kit 提供了一组基本的负载均衡器,并且,如果你希望更多的高级的算法,也可以很容易的自己来写一个。
type LoadBalancer interface {
Endpoint() (endpoint.Endpoint, error)
}
现在,我们可以根据一下算法来选择端点。我们能够使用它为消费器提供一个单一的、逻辑的可靠的端点。通过重试的策略包装负载均衡器,并且返回一个可用的端点。重试的策略可以将一个失败的请求进行重试,直到达到最大的可重试次数或是达到超时时间。
func Retry(max int, timeout time.Duration, lb LoadBalancer) endpoint.Endpoint
现在,让我们将最后的代理中间件加入到代码中。为了简洁,我们假设用户会为逗号(,)分隔的多个实例端点指定一个标记。
func proxyingMiddleware(proxyList string, ctx context.Context, logger log.Logger) ServiceMiddleware {
return func(next StringService) StringService {
var (
qps = 100 // max to each instance
publisher = static.NewPublisher(split(proxyList), factory(ctx, qps), logger)
lb = loadbalancer.NewRoundRobin(publisher)
maxAttempts = 3
maxTime = 100 * time.Millisecond
endpoint = loadbalancer.Retry(maxAttempts, maxTime, lb)
)
return proxymw{ctx, endpoint, next}
}
}
stringsvc3
截止目前,这个完整的服务是 stringsvc3
$ go get github.com/go-kit/kit/examples/stringsvc3 $ stringsvc3 -listen=:8001 & listen=:8001 caller=proxying.go:25 proxy_to=none listen=:8001 caller=main.go:72 msg=HTTP addr=:8001 $ stringsvc3 -listen=:8002 & listen=:8002 caller=proxying.go:25 proxy_to=none listen=:8002 caller=main.go:72 msg=HTTP addr=:8002 $ stringsvc3 -listen=:8003 & listen=:8003 caller=proxying.go:25 proxy_to=none listen=:8003 caller=main.go:72 msg=HTTP addr=:8003 $ stringsvc3 -listen=:8080 -proxy=localhost:8001,localhost:8002,localhost:8003 listen=:8080 caller=proxying.go:29 proxy_to="[localhost:8001 localhost:8002 localhost:8003]" listen=:8080 caller=main.go:72 msg=HTTP addr=:8080
$ for s in foo bar baz ; do curl -d"{\"s\":\"$s\"}" localhost:8080/uppercase ; done
{"v":"FOO","err":null}
{"v":"BAR","err":null}
{"v":"BAZ","err":null}
listen=:8001 caller=logging.go:28 method=uppercase input=foo output=FOO err=null took=5.168µs
listen=:8080 caller=logging.go:28 method=uppercase input=foo output=FOO err=null took=4.39012ms
listen=:8002 caller=logging.go:28 method=uppercase input=bar output=BAR err=null took=5.445µs
listen=:8080 caller=logging.go:28 method=uppercase input=bar output=BAR err=null took=2.04831ms
listen=:8003 caller=logging.go:28 method=uppercase input=baz output=BAZ err=null took=3.285µs
listen=:8080 caller=logging.go:28 method=uppercase input=baz output=BAZ err=null took=1.388155ms
高级话题
线程上下文
上下文对象用来在单个请求中携带那些需要跨越概念性边界的信息。在我们的例子中,我们还没有在业务逻辑中使用线程上下文。但是,这种方式几乎会永远都是一个好方案。它允许我们在业务逻辑和中间件中传递请求范围内的信息,并且对于复杂的任务(比如,分布式系统的细粒度追踪信息)也是很必要的。
具体点来讲,这也意味着你的业务逻辑接口会这样:
type MyService interface {
Foo(context.Context, string, int) (string, error)
Bar(context.Context, string) error
Baz(context.Context) (int, error)
}
请求跟踪
一旦你的基础设施增长超过一定的规模,在多个服务中跟踪请求是非常必要的,这样,你就能够分析并解决故障热点。参见 package tracing 获取更多信息。
创建一个客户端软件包
使用 Go kit 为你的服务创建一个客户端软件包是很可能的事情,让你的服务能够很容易对其他的 Go 程序进行调用。实际上,你的客户端package会提供你的服务接口,这个接口会使用指定的传输方式来调用远程服务。参见 package addsvc/client 作为参考例子.
其他例子
addsvc
addsvc 是原来的一个例子。它公开 所有支持的传输方式 的系列操作。它完整地做了日志记录、仪表盘化,并且使用 Zipkin 来跟踪请求。同样,它也示范了如何创建和使用客户端package。它是一个非常棒的例子,为 Go kit 服务提供了完整的功能示例。
profilesvc
profilesvc 示范了如何使用 Go kit 来打造 REST 风格的微服务。
apigateway
apigateway 示范了如何实现一个 API 网关模式,通过 Consul 作为服务发现系统。
shipping
shipping 是一个完整的,真实的应用,由多个微服务组成,基于领域驱动设计原则。
标签:string,err,简介,kit,Go,func,go From: https://blog.51cto.com/u_6353447/8023579