下面是我要比较的框架:
Go Micro[1] Go Kit[2] Gizmo[3] Kite[4]
Go Micro
我认为最流行的框架之一。有很多博客文章和简单的例子。您可以在 medium 上关注 microhq[5] 或 @MicroHQ[6] 以获取 Go-Micro 中的最新更新。
好吧,什么是 Go Micro?它是一个可插入的 RPC 框架,用于在 Go 中编写微服务。开箱即用,您将收到:
服务发现 - 应用程序自动注册到服务发现系统。 负载平衡 - 客户端负载平衡,用于平衡服务实例之间的请求。 同步通信 - 提供请求 / 响应传输层。 异步通信 - 内置发布 / 订阅功能。 消息编码 - 基于消息的内容类型头的编码 / 解码。 RPC 客户机 / 服务器包 - 利用上述功能并公开接口来构建微服务。
Go 微体系结构可以描述为三层堆栈。
顶层由客户端 - 服务器模型和服务抽象组成。服务器是用于编写服务的构建块。客户端提供了向服务请求的接口。
底层由以下类型的插件组成:
代理 - 为异步发布 / 订阅通信提供消息代理的接口。 编解码器 - 用于编码 / 解码消息。支持的格式包括 json,bson,protobuf,msgpack 等。 注册表 - 提供服务发现机制(默认为 Consul)。 选择器 - 建立在注册表上的负载平衡抽象。它允许使用诸如随机,轮循,最小康等算法来 “选择” 服务。 传输 - 服务之间同步请求 / 响应通信的接口。 Go Micro 还提供了 Sidecar 等功能。这使您可以使用以 Go 以外的语言编写的服务。Sidecar 提供服务注册,gRPC 编码 / 解码和 HTTP 处理程序。它支持多种语言。
Go Kit
Go Kit 是一个用于在 Go 中构建微服务的编程工具包。与 Go Micro 不同,它被设计为一个用于导入二进制包的库。
Go Kit 遵循简单的规则,例如:
没有全局状态 声明式组合 显式依赖关系 接口即约定 领域驱动设计
在 Go Kit 中,您可以找到以下的包:
认证 - Basic 认证和 JWT 认证 传输 - HTTP、Nats、gRPC 等等。 日志记录 - 用于结构化服务日志记录的通用接口。 指标 - CloudWatch、Statsd、Graphite 等。 追踪 - Zipkin 和 Opentracing。 服务发现 - Consul、Etcd、Eureka 等等。 断路器 - Hystrix 的 Go 实现。
在 Peter Bourgon 的文章和 幻灯片中,你可以找到关于 Go 工具包最好的描述:
Go kit: 在现代企业中使用 Go[7] Go + 微服务[8]
此外,在「Go + 微服务」幻灯片中,您将看到一个使用 Go Kit 构建的服务架构示例。为了快速入门,这里有一个服务架构图。
图 2。使用 Go Kit 构建的服务架构示例 (原始图片在 「Go + 微服务[9]」 幻灯片)
Gizmo
Gizmo 是《纽约时报》开源的一个微服务工具包。它提供了将服务器和 pubsub 组合在一起的包。
功能如下:
server[10] - 提供两种服务器实现:SimpleServer(over HTTP),RPCServer(在 gRPC 上)。 server/kit[11] - 基于 Go-kit 的包,目前试验阶段。 config[12] - 包含配置 JSON 文件的函数,Consul k/v 中的 JSON blob,或环境变量。 pubsub[13] - 提供用于发布和使用队列中数据的通用接口。 pubsub/pubsubtest[14] - 包含发布者和订阅者接口的测试实现。 web[15] - 公开了解析请求查询和有效负载类型的函数。
Pubsub 包提供了与以下驱动:
pubsub/aws[16] - Amazon SNS/SQS. pubsub/gcp[17] - Google Pubsub. pubsub/kafka[18] - Kafka . pubsub/http[19] - HTTP 队列
configpubsub
Kite
Kite 是一个在 Go 微服务框架。它公开了 RPC 客户端和服务器包。创建的服务将自动注册到服务发现系统 Kontrol 中。Kontrol 使用 Kite 构建的,它本身就是一种 Kite 服务。这意味着 Kite 微服务可以在自己的环境中正常工作。如果你需要连接 Kite 微服务到另一个服务发现系统,它将需要定制。这是我不看好此框架的主要原因。
我将使用四个类别比较框架:
GitHub 统计 文档和示例 用户和社区 代码品质
GitHub statistics
表 1. Go 微服务框架统计(2018 年 4 月收集)
文档和代码示例
简单来说,没有框架会提供可靠的文档,通常来说,唯一正式的文档是项目首页的 readme。
对 Go Micro 来说很多信息和公告可以在 micro.mu[20] ,microhq[21] 看到,还有 @MicroHQ[22] 作为他们的公共媒体。
对 Go Kit 来说最好的文档可以在 Peter Bourgon’s blog[23] 找到。最好的示例代码之一可以在 ru-rocker blog[24] 找到。
如果是 Gizmo 的话,它的源码提供了最好的文档和代码示例。
综上所述,如果你是 NodeJS 的忠实用户,期望看到和 ExpressJS 类似的教程,那你可能要失望了,但是从另一方面来说,这是一个你编写自己教程的好机会。
用户和社区
根据 GitHub 统计数据,Go Kit 是最受欢迎的微服务框架 —— 在这篇文章发布前超过 10k 颗 star。它有很多贡献者 (122 人) 和 1000 多个 fork。最后,Go Kit 得到了 DigitalOcean[25] 的支持。
拥有 3600 多颗 star、27 个贡献者和 385 个 fork 的 Go Micro 位居第二。Go Micro 的最大赞助商之一是 Sixt[26]。
Gizmo 位居第三。超过 2200 颗 star,31 个贡献者和 137 个 fork。由《纽约时报》支持和创建。
代码质量
Go Kit 在代码质量类别中排名第一。它拥有几乎 80% 的代码覆盖率和出色的 Go 评级报告[27] 报告评级。Gizmo 的 Go 评级报告[28] 也很高。但是它的代码覆盖率只有 46%。Go Micro 不提供覆盖信息,但它的 Go 评级报告[29]很高。
好啦,理论的东西讲的差不多了,接下来开始编码。为了更好地理解框架,我创建了三个简单的微服务:
图 3. 实际示例架构
name
服务应在服务发现系统中自动发现 服务应带有运行状况检查的 endpoint 服务应至少支持 HTTP 和 gRPC 传输
对于喜欢阅读源代码的用户。您可以在这里停下来阅读 托管在 GitHub 上的源码[30]。
Go 微框架问候器
使用 Go Micro 创建服务首先需要定义 protobuf 描述。接下来,这三个服务都使用了相同的 protobuf 定义。我创建了以下服务描述:
*go-micro-greeter.proto *
Greetingnamegreeting
greeter
*go-micro-greeter-grpc-main.go *
package main
import (
'log'
pb 'github.com/antklim/go-microservices/go-micro-greeter/pb'
'github.com/micro/go-micro'
'golang.org/x/net/context'
)
// greeter 实现问候服务。
type Greeter struct{}
// 问候方法的实现
func (g *Greeter) Greeting(ctx context.Context, in *pb.GreetingRequest, out *pb.GreetingResponse) error {
out.Greeting = 'GO-MICRO Hello ' + in.Name
return nil
}
func main() {
service := micro.NewService(
micro.Name('go-micro-srv-greeter'),
micro.Version('latest'),
)
service.Init()
pb.RegisterGreeterHandler(service.Server(), new(Greeter))
if err := service.Run(); err != nil {
log.Fatal(err)
}
}
protobuf
go-micro-greeter-http-main.go
非常简单和直接。很多事情都是由 Go Micro 在幕后处理的,例如在服务发现系统中注册。如果你自己创建一个纯净的 HTTP 服务器,这些开发起来还是挺费劲的 。
Go Kit greeter
Endpoint
最后,我为以下内容创建了四个包:
服务逻辑实现。 与传输无关的服务端点。 与传输特定的端点(gRPC, HTTP) 服务发现注册器
go-kit-greeter-service.go
package greeterservice
// Service 描述了 greetings 这个服务
type Service interface {
Health() bool
Greeting(name string) string
}
// GreeterService 是 Service 接口的实现
type GreeterService struct{}
// Service 的 Health 接口实现
func (GreeterService) Health() bool {
return true
}
// Service 的 Greeting 接口实现
func (GreeterService) Greeting(name string) (greeting string) {
greeting = 'GO-KIT Hello ' + name
return
}
如您所见,代码没有任何依赖关系。它只是实现逻辑。下面的代码片段是端点 Endpoint 的定义:
go-kit-greeter-endpoints.go
定义服务和端点后,我们接下来通过不同的传输协议暴露端点。我从 HTTP 开始:
go-kit-greeter-http.go
package greetertransport
import (
'context'
'encoding/json'
'errors'
'net/http'
'github.com/antklim/go-microservices/go-kit-greeter/pkg/greeterendpoint'
'github.com/go-kit/kit/log'
httptransport 'github.com/go-kit/kit/transport/http'
'github.com/gorilla/mux'
)
var (
// 当缺少预期的路径变量时,将返回 ErrBadRouting。
ErrBadRouting = errors.New('inconsistent mapping between route and handler')
)
// NewHTTPHandler返回一个使一组端点在预定义路径上可用的HTTP处理程序。
func NewHTTPHandler(endpoints greeterendpoint.Endpoints, logger log.Logger) http.Handler {
m := mux.NewRouter()
options := []httptransport.ServerOption{
httptransport.ServerErrorEncoder(encodeError),
httptransport.ServerErrorLogger(logger),
}
// GET /health 获取服务健康信息
// GET /greeting?name 获取 greeting
// NewServer 方法需要端点,解码器,编码器作为参数
m.Methods('GET').Path('/health').Handler(httptransport.NewServer(endpoints.HealthEndpoint, DecodeHTTPHealthRequest, EncodeHTTPGenericResponse, options...))
m.Methods('GET').Path('/greeting').Handler(httptransport.NewServer(endpoints.GreetingEndpoint, DecodeHTTPGreetingRequest, EncodeHTTPGenericResponse, options...))
return m
}
func EncodeHTTPGenericResponse(ctx context.Context, writer http.ResponseWriter, response interface{}) error {
if f, ok := response.(greeterendpoint.Failer); ok && f.Failed() != nil {
encodeError(ctx, f.Failed(), writer)
return nil
}
writer.Header().Set('Content-Type', 'application/json; charset=utf-8')
return json.NewEncoder(writer).Encode(response)
}
// 解码 Health HTTP 请求的方法
func DecodeHTTPHealthRequest(_ context.Context, _ *http.Request) (interface{}, error) {
return greeterendpoint.HealthRequest{}, nil
}
// 解码 Greeting HTTP 请求的方法
func DecodeHTTPGreetingRequest(_ context.Context, r *http.Request) (interface{}, error) {
vars := r.URL.Query()
names, exists := vars['name']
if !exists || len(names) != 1 {
return nil, ErrBadRouting
}
req := greeterendpoint.GreetingRequest{Name: names[0]}
return req, nil
}
// errorWrapper 将 error 封装为一个 json 结构体方便转换为 json
type errorWrapper struct {
Error string `json:'error'`
}
// 编码错误的方法
func encodeError(_ context.Context, err error, w http.ResponseWriter) {
w.WriteHeader(err2code(err))
json.NewEncoder(w).Encode(errorWrapper{Error: err.Error()})
}
// err2code 函数将 error 转换为对应的 http 状态码
func err2code(err error) int {
switch err {
default:
return http.StatusInternalServerError
}
}
protobufprotobuf
go-greeter-gen.sh
go-kit-grpc.go
package greetertransport
import (
'context'
'github.com/antklim/go-microservices/go-kit-greeter/pb'
'github.com/antklim/go-microservices/go-kit-greeter/pkg/greeterendpoint'
'github.com/go-kit/kit/log'
grpctransport 'github.com/go-kit/kit/transport/grpc'
oldcontext 'golang.org/x/net/context'
)
type (
grpcServer struct {
greeter grpctransport.Handler
}
)
// NewGRPCServer 使一组端点可用作 gRPC Greeting 服务器。
func NewGRPCServer(endpoints greeterendpoint.Endpoints, logger log.Logger) pb.GreeterServer {
options := []grpctransport.ServerOption{
grpctransport.ServerErrorLogger(logger),
}
return &grpcServer{greeter: grpctransport.NewServer(endpoints.GreetingEndpoint, decodeGRPCGreetingRequest, encodeGRPCGreetingResponse, options...)}
}
// encodeGRPCGreetingResponse 是一个 transport/grpc.EncodeResponseFunc 将用户域
// 问Greeting响应转换为 gRPC Greeting 响应。
func encodeGRPCGreetingResponse(i context.Context, i2 interface{}) (response interface{}, err error) {
res := response.(greeterendpoint.GreetingResponse)
return &pb.GreetingResponse{Greeting: res.Greeting}, nil
}
// decodeGRPCGreetingRequest 是一个 transport/grpc.DecodeRequestFunc 将 gRPC Greeting 请求转换为用户域 Greeting 请求
func decodeGRPCGreetingRequest(context context.Context, grpcReq interface{}) (request interface{}, err error) {
req := grpcReq.(*pb.GreetingRequest)
return greeterendpoint.GreetingRequest{Name: req.Name}, nil
}
// 实现 GreeterService.Greeting 接口
func (s *grpcServer) Greeting(ctx context.Context, req *pb.GreetingRequest) (*pb.GreetingResponse, error) {
_, res, err := s.greeter.ServeGRPC(ctx, req)
if err != nil {
return nil, err
}
return res.(*pb.GreetingResponse), nil
}
最后,我注册了服务自动发现器:
go-kit-sd.go
我在服务启动程序中将它们合并在一起:
go-kit-service-starter.go
package main
import (
'flag'
'fmt'
'net'
'net/http'
'os'
'os/signal'
'syscall'
'text/tabwriter'
'github.com/antklim/go-microservices/go-kit-greeter/pb'
'google.golang.org/grpc'
'github.com/antklim/go-microservices/go-kit-greeter/pkg/greeterendpoint'
'github.com/antklim/go-microservices/go-kit-greeter/pkg/greetersd'
'github.com/antklim/go-microservices/go-kit-greeter/pkg/greeterservice'
'github.com/antklim/go-microservices/go-kit-greeter/pkg/greetertransport'
'github.com/go-kit/kit/log'
'github.com/oklog/oklog/pkg/group'
)
func main() {
fs := flag.NewFlagSet('greetersvc', flag.ExitOnError)
var (
debugAddr = fs.String('debug.addr', ':9100', 'Debug and metrics listen address')
consulAddr = fs.String('consul.addr', '', 'Consul Address')
consulPort = fs.String('consul.port', '8500', 'Consul Port')
httpAddr = fs.String('http.addr', '', 'HTTP Listen Address')
httpPort = fs.String('http.port', '9110', 'HTTP Listen Port')
grpcAddr = fs.String('grpc-addr', ':9120', 'gRPC listen address')
)
fs.Usage = usageFor(fs, os.Args[0]+' [flags]')
fs.Parse(os.Args[1:])
var logger log.Logger
{
logger = log.NewLogfmtLogger(os.Stderr)
logger = log.With(logger, 'ts', log.DefaultTimestampUTC)
logger = log.With(logger, 'caller', log.DefaultCaller)
}
var service greeterservice.Service
{
service = greeterservice.GreeterService{}
service = greeterservice.LoggingMiddleware(logger)(service)
}
var (
endpoints = greeterendpoint.MakeServerEndpoints(service, logger)
httpHandler = greetertransport.NewHTTPHandler(endpoints, logger)
registar = greetersd.ConsulRegister(*consulAddr, *consulPort, *httpAddr, *httpPort)
grpcServer = greetertransport.NewGRPCServer(endpoints, logger)
)
var g group.Group
{
// The debug listener mounts the http.DefaultServeMux, and serves up
// stuff like the Go debug and profiling routes, and so on.
debugListener, err := net.Listen('tcp', *debugAddr)
if err != nil {
logger.Log('transport', 'debug/HTTP', 'during', 'Listen', 'err', err)
os.Exit(1)
}
g.Add(func() error {
logger.Log('transport', 'debug/HTTP', 'addr', *debugAddr)
return http.Serve(debugListener, http.DefaultServeMux)
}, func(error) {
debugListener.Close()
})
}
{
// The service discovery registration.
g.Add(func() error {
logger.Log('transport', 'HTTP', 'addr', *httpAddr, 'port', *httpPort)
registar.Register()
return http.ListenAndServe(':'+*httpPort, httpHandler)
}, func(error) {
registar.Deregister()
})
}
{
// The gRPC listener mounts the Go kit gRPC server we created.
grpcListener, err := net.Listen('tcp', *grpcAddr)
if err != nil {
logger.Log('transport', 'gRPC', 'during', 'Listen', 'err', err)
os.Exit(1)
}
g.Add(func() error {
logger.Log('transport', 'gRPC', 'addr', *grpcAddr)
baseServer := grpc.NewServer()
pb.RegisterGreeterServer(baseServer, grpcServer)
return baseServer.Serve(grpcListener)
}, func(error) {
grpcListener.Close()
})
}
{
// This function just sits and waits for ctrl-C.
cancelInterrupt := make(chan struct{})
g.Add(func() error {
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
select {
case sig := <-c:
return fmt.Errorf('received signal %s', sig)
case <-cancelInterrupt:
return nil
}
}, func(error) {
close(cancelInterrupt)
})
}
logger.Log('exit', g.Run())
}
func usageFor(fs *flag.FlagSet, short string) func() {
return func() {
fmt.Fprintf(os.Stderr, 'USAGE\n')
fmt.Fprintf(os.Stderr, ' %s\n', short)
fmt.Fprintf(os.Stderr, '\n')
fmt.Fprintf(os.Stderr, 'FLAGS\n')
w := tabwriter.NewWriter(os.Stderr, 0, 2, 2, ' ', 0)
fs.VisitAll(func(f *flag.Flag) {
fmt.Fprintf(w, '\t-%s %s\t%s\n', f.Name, f.DefValue, f.Usage)
})
w.Flush()
fmt.Fprintf(os.Stderr, '\n')
}
}
service/endpoints
go-kit-greeter-service-middleware.go
go-kit-greeter-endpoints-middleware.go
package greeterendpoint
import (
'context'
'time'
'github.com/go-kit/kit/endpoint'
'github.com/go-kit/kit/log'
)
// 日志中间件返回 endpoint 中间件,记录每次调用的持续时间,
// 以及产生的错误(如果有)。
func LoggingMiddleware(logger log.Logger) endpoint.Middleware {
return func(next endpoint.Endpoint) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
defer func(begin time.Time) {
logger.Log('transport_error', err, 'took', time.Since(begin))
}(time.Now())
return next(ctx, request)
}
}
}
Gizmo greeter
我以类似 Go Kit. 的方式创建了 Gizmo 服务。我为服务、端点、传输和服务发现注册器定义了四个包。
endpoints
gizmo-greeter-endpoints.go
如您所见,代码类似于 Go-Kit。主要区别在于应该返回的接口类型:
gizmo-greeter-http.go
package greetertransport
import (
'context'
'github.com/NYTimes/gizmo/server'
'google.golang.org/grpc'
'errors'
'net/http'
'github.com/NYTimes/gziphandler'
pb 'github.com/antklim/go-microservices/gizmo-greeter/pb'
'github.com/antklim/go-microservices/gizmo-greeter/pkg/greeterendpoint'
'github.com/sirupsen/logrus'
)
type (
// TService 将实现 server.RPCService 并处理对服务器的所有请求。
TService struct {
Endpoints greeterendpoint.Endpoints
}
// Config 包含 JSONService 所需的所有配置的结构。
Config struct {
Server *server.Config
}
)
// NewTService 将用给定配置实例化 RPCService
func NewTService(cfg *Config, endpoints greeterendpoint.Endpoints) *TService {
return &TService{Endpoints: endpoints}
}
// Prefix 返回此服务中所有端点使用的字符串前缀
func (s *TService) Prefix() string {
return ''
}
// Service 向 TService 提供要服务的描述和实现
func (s *TService) Service() (*grpc.ServiceDesc, interface{}) {
return &pb.Greeter_serviceDesc, s
}
// 中间件提供了一个 http.Handler 钩子,它包装了所有请求。
// 在这个实现中,我们使用 GzipHandler 中间件来压缩我们的响应。
func (s *TService) Middleware(h http.Handler) http.Handler {
return gziphandler.GzipHandler(h)
}
// ContextMiddleware 为所有请求提供一个 server.ContextHAndler 钩子。
// 如果需要修饰请求上下文,这可能很方便。
func (s *TService) ContextMiddleware(h server.ContextHandler) server.ContextHandler {
return h
}
// JSONMiddleware 为所有请求提供一个 JSONEndpoint 钩子。
// 我们使用它来提供日志记录和检查错误,并提供一般响应。
func (s *TService) JSONMiddleware(j server.JSONContextEndpoint) server.JSONContextEndpoint {
return func(ctx context.Context, r *http.Request) (int, interface{}, error) {
status, res, err := j(ctx, r)
if err != nil {
server.LogWithFields(r).WithFields(logrus.Fields{
'error': err,
}).Error('problems with serving request')
return http.StatusServiceUnavailable, nil, errors.New('sorry, this service is unavailable')
}
server.LogWithFields(r).Info('success!')
return status, res, nil
}
}
// ContextEndpoints may be needed if your server has any non-RPC-able
// endpoints. In this case, we have none but still need this method to
// satisfy the server.RPCService interface.
func (s *TService) ContextEndpoints() map[string]map[string]server.ContextHandlerFunc {
return map[string]map[string]server.ContextHandlerFunc{}
}
// JSONEndpoints is a listing of all endpoints available in the TService.
func (s *TService) JSONEndpoints() map[string]map[string]server.JSONContextEndpoint {
return map[string]map[string]server.JSONContextEndpoint{
'/health': map[string]server.JSONContextEndpoint{
'GET': s.Endpoints.HealthEndpoint,
},
'/greeting': map[string]server.JSONContextEndpoint{
'GET': s.Endpoints.GreetingEndpoint,
},
}
}
gizmo-greeter-grpc.go
Go-Kit 与 Gizmo 只要区别在于传输实现。Gizmo 提供了几种您可以使用的服务类型。我所要做的就是将 HTT P 路径映射到端点定义。底层 HTTP 请求 / 响应处理由 Gizmo 处理。
Go Micro 是启动微服务系统的最快方法。框架提供了许多功能。因此,您无需重新发明轮子。但是,这种舒适性和速度会带来牺牲–灵活性。更改或更新系统部件并不像 Go Kit 那样容易。并且将 gRPC 强制为默认通信类型。
您可能需要一些时间来熟悉 Go Kit。它需要你具备 Go 功能的丰富知识和软件架构方面的经验。灵活是他的另一个优势,没有框架限制, 所有部件均可独立更改和更新。
Gizmo 位于 Go Micro 和 Go Kit 之间。它提供了一些更高级别的抽象,例如 Service 包。但是缺少文档和示例,这意味着我不得不通读源代码以了解不同服务类型的工作方式。使用 Gizmo 比使用 Go Kit 容易。但是它不像 Go Micro 那样流畅。
今天就这些了。谢谢阅读。请查看微服务 code repository[33] 如果您对 Go 和微服务框架有任何经验,请留言分享。
译文地址:/go/t/36973
参考
Go Micro: https://micro.mu/
[2]Go Kit: /
[3]Gizmo: https://github.com/NYTimes/gizmo
[4]Kite: https://github.com/koding/kite
[5]microhq: /microhq
[6]@MicroHQ: https://twitter.com/MicroHQ
[7]Go kit: 在现代企业中使用 Go: /go-kit/
[8]Go + 微服务: https://github.com/peterbourgon/go-microservices
[9]Go + 微服务: https://github.com/peterbourgon/go-microservices
[10]server: /github.com/NYTimes/gizmo/server
[11]server/kit: /github.com/NYTimes/gizmo/server/kit
[12]config: /github.com/NYTimes/gizmo/config
[13]pubsub: /github.com/NYTimes/gizmo/pubsub
[14]pubsub/pubsubtest: /github.com/NYTimes/gizmo/pubsub/pubsubtest
[15]web: /github.com/NYTimes/gizmo/web
[16]pubsub/aws: /github.com/NYTimes/gizmo/pubsub/aws
[17]pubsub/gcp: /github.com/NYTimes/gizmo/pubsub/gcp
[18]pubsub/kafka: /github.com/NYTimes/gizmo/pubsub/kafka
[19]pubsub/http: /github.com/NYTimes/gizmo/pubsub/http
[20]micro.mu: https://micro.mu/
[21]microhq: /microhq
[22]@MicroHQ: https://twitter.com/MicroHQ
[23]Peter Bourgon’s blog: /go-kit/
[24]ru-rocker blog: /2017/04/17/micro-services-using-go-kit-service-discovery/
[25]DigitalOcean: /
[26]Sixt: /
[27]Go 评级报告: /report/github.com/go-kit/kit
[28]Go 评级报告: /report/github.com/NYTimes/gizmo
[29]Go 评级报告: /report/github.com/micro/go-micro
[30]托管在 GitHub 上的源码: https://github.com/antklim/go-microservices
[31]protoc: https://github.com/micro/protoc-gen-micro
[32]example: /2017/04/17/micro-services-using-go-kit-service-discovery/
[33]code repository: https://github.com/antklim/go-microservices