go-kit微服务

650 阅读5分钟

1. 微服务的定义:

可以理解为单个程序员可以设计、实现、部署和维护的应用程序。在一个整体应用程序中,组件通过语言级的方法或函数互相调用。相反,基于微服务的应用程序是运行在多台机器上的分布式系统,每个服务实例是不同的进程。因此,这些服务需要使用进程间通信进行交互。

服务间通信最简单的解决方案是基于HTTP协议使用JSON格式的数据进行交互,此外,还有如:gRPC、pub/sub等。

微服务也带来了很多挑战:

  • 序列化
  • 日志记录
  • 熔断
  • 请求跟踪
  • 服务发现

2. go-kit

go-kit是一个go语言相关的微服务工具包,它自称为toolkit,并不是框架。也就是go-kit是将一系列的服务集合在一起,提供接口,从而让开发者自由搭建自己的微服务项目。

2.1 go-kit组件

2.1.1 Service

具体业务处理逻辑放在这里,服务一般以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")
2.1.2 Endpoint

endpoint是go-kit最重要的一个层,是一个抽象的接收请求返回响应的函数类型。在这个定义的类型里面会去调用service层的方法,组装成response返回。而gokit中的所有中间件组件都是通过装饰者设计模式注入的。endpoint还提供了对日志、限流、熔断、链路追踪、服务监控等方面的扩展能力。

type Endpoint func(ctx context.Context, request interface{}) (response interface{}, err error)
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
    }
}
2.1.3 Transport

transport主要是将服务通过某种传输方式暴露非外界,这样这些服务才能被调用。go-kit支持多种开箱即用的传输方式,一般如:http、rpc。这里使用HTTP的JSON。

import (
    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)
}
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)
}
2.1.4 请求跟踪Tracing

参考:segmentfault.com/a/119000001…

微服务是一个分布式架构,按照业务划分单元,一个分布式系统往往有很多个服务单元。由于服务单元数量众多,业务的复杂性,如果出现了错误和异常,很难定位。主要体现在,一个请求可能需要调用很多个服务,而内部服务的调用复杂性,决定了问题难以定位。所以微服务架构中,必须实现分布式链路跟踪,去跟进一个请求到底有哪些服务参与,参与的顺序,从而达到每个请求的步骤清晰可见。

go-kit提供两种链路跟踪:opentracing和zipkin

这里简单介绍一下zipkin:

zipkin可以帮助收集时间数据,同时提供了分布式系统时间数据的收集和查询功能,zipkin架构:

Zipkin由Collector、Storage、API、UI共4个组件构成,Reporter由应用系统提供并收集数据,其工作原理大概如下:

  • 在应用程序中嵌入追踪器(Tracer),它使用Span记录应用程序动作的时间和元数据信息;
  • Reporter把Span发送至Zipkin的数据收集器Collector;
  • Collector通过Storage组件把数据存储至数据库;
  • UI组件通过API接口查询追踪数据并显示;

zipkin通过trace结构表示对一次请求的跟踪,一次请求由若干服务处理,每个服务生成一个span,同一个请求的span之间存在关联关系,在UI组件中以树形展示。span的主要数据模型如下:

2.1.5 API监控

微服务中,API几乎是服务与外界的唯一交互渠道,API的稳定性、可靠性越来越成为不可忽视的部分。我们需要实时了解API的运行状况(请求次数、时延、失败等),需要通过对历史数据的分析了解哪些API存在性能瓶颈以便后期优化。所以,为了确保系统良好的提供服务,绝大多数的微服务框架也都集成了API监控组件。 Prometheus(普罗米修斯)是一套开源的系统监控报警框架。作为新一代的监控框架,有如下特点:

  • 提供强大的多维度数据模型,如Counter、Gauge、Histogram、Summary;
  • 强大而灵活的查询语句,可方便的实现对时间序列数据的查询、聚合操作;
  • 易于管理与高效;
  • 提供pull模式、push gateway方式实现时间序列数据的采集;
  • 支持多种可视化图形界面:Grafana、Web UI、API clients;
  • 报警规则管理、报警检测和报警推送功能

修改main.go,创建指标采集对象:请求次数采集和请求时延采集对象。

count := kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{
	Namespace: "51world",
	Subsystem: "stringService",
	Name:      "request_count",
	Help:      "Number of requests received.",
}, fieldKeys)
latency := kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
	Namespace: "51world",
	Subsystem: "stringService",
	Name:      "request_latency_seconds",
	Help:      "Total duration of requests in seconds.",
}, fieldKeys)