如果是一个简单的需求,那么我们用 Go http、gin、beego 足以

再复杂点呢,接口多起来了,我们寻求团队分工合作,功能解藕,所以按功能、层级将服务进行拆分,服务间使用 http、grpc 进行调用

当服务多起来了,我们需要一个规范统一各个服务的代码,这里的代码是项目层级、中间件调用、日志、配置中心、链路追踪等等,那么这个规范的集合可以沉淀为微服务框架

kratos

应读“奎托斯”,之前读错还带偏面试官,真是尴尬😅

一、使用手册

https://go-kratos.dev/docs/getting-started/start

二、源码功能解析

1. 代码生成

1.1 new

1.2 proto

proto add
proto client
proto server

1.3 wire 依赖注入

2. Metadata 传递

https://go-kratos.dev/docs/component/metadata

使用非常简单,摘一下官网的例子

// server
grpcSrv := grpc.NewServer(
    grpc.Address(":9000"),
    grpc.Middleware(
        metadata.Server(),
    ),
)
httpSrv := http.NewServer(
    http.Address(":8000"),
    http.Middleware(
        metadata.Server(),
    ),
)

// client
conn, err := grpc.DialInsecure(
    context.Background(),
    grpc.WithEndpoint("127.0.0.1:9000"),
    grpc.WithMiddleware(
        metadata.Client(),
    ),
)

// 获取元信息字段的值
if md, ok := metadata.FromServerContext(ctx); ok {
    extra = md.Get("x-md-global-extra")
}

// 设置元信息字段的值
ctx = metadata.AppendToClientContext(ctx, "x-md-global-extra", "2233")

说完使用,来说原理,本质上就是在中间层 做 metadata 的正反序列化,借助 header 这个载体实现(注意,metadata 的middleware 只是把元信息注入到 header,具体正反序列化由 transport 去做)

kratos/metadata/metadata.go 对 metadata 结构体进行抽象定义 type Metadata map[string][]string

kratos/middleware/tracing/metadata.go

对于server端来说,提取 transport 抽象,获取调用方特定 prefix(有默认,也可以追加自定义) 的 header 加载到 ctx 中

func Server(opts ...Option) middleware.Middleware {
	options := &options{
		prefix: []string{"x-md-"}, // x-md-global-, x-md-local
	}
	for _, o := range opts {
		o(options)
	}
	return func(handler middleware.Handler) middleware.Handler {
		return func(ctx context.Context, req interface{}) (reply interface{}, err error) {
			tr, ok := transport.FromServerContext(ctx)
			if !ok {
				return handler(ctx, req)
			}

			md := options.md.Clone()
			header := tr.RequestHeader()
			for _, k := range header.Keys() {
				if options.hasPrefix(k) {
					for _, v := range header.Values(k) {
						md.Add(k, v)
					}
				}
			}
			ctx = metadata.NewServerContext(ctx, md)
			return handler(ctx, req)
		}
	}
}

对于 client 来说,将 ctx 中所有的元信息都注入到 header,也就是说我们可以携带一些元信息传递给被调用方(注意在创建 grpc client 时 要引用如下 middleware)

// Client is middleware client-side metadata.
func Client(opts ...Option) middleware.Middleware {
	options := &options{
		prefix: []string{"x-md-global-"},
	}
	for _, o := range opts {
		o(options)
	}
	return func(handler middleware.Handler) middleware.Handler {
		return func(ctx context.Context, req interface{}) (reply interface{}, err error) {
			tr, ok := transport.FromClientContext(ctx)
			if !ok {
				return handler(ctx, req)
			}

			header := tr.RequestHeader()
			// x-md-local-
			for k, vList := range options.md {
				for _, v := range vList {
					header.Add(k, v)
				}
			}
			if md, ok := metadata.FromClientContext(ctx); ok {
				for k, vList := range md {
					for _, v := range vList {
						header.Add(k, v)
					}
				}
			}
			// x-md-global-
			if md, ok := metadata.FromServerContext(ctx); ok {
				for k, vList := range md {
					if options.hasPrefix(k) {
						for _, v := range vList {
							header.Add(k, v)
						}
					}
				}
			}
			return handler(ctx, req)
		}
	}
}

3. Transport

3.1 传输层

https://go-kratos.dev/docs/component/transport/overview

kratos 可以一份代码,http、grpc 都用,所依托的就是抽象应用层协议,见transport.go

有两个重要抽象

type Server interface {
	Start(context.Context) error
	Stop(context.Context) error
}

// Transporter is transport context value interface.
type Transporter interface {
	// Kind transporter
	// grpc
	// http
	Kind() Kind
	// Endpoint return server or client endpoint
	// Server Transport: grpc://127.0.0.1:9000
	// Client Transport: discovery:///provider-demo
	Endpoint() string
	// Operation Service full method selector generated by protobuf
	// example: /helloworld.Greeter/SayHello
	Operation() string
	// RequestHeader return transport request header
	// http: http.Header
	// grpc: metadata.MD
	RequestHeader() Header
	// ReplyHeader return transport reply/response header
	// only valid for server transport
	// http: http.Header
	// grpc: metadata.MD
	ReplyHeader() Header
}

在看 httpgrpc 各自的实现时,还遇到一些源码中常出现的范式

// 这里括号里的 Server 指的是 http 对应实现的 struct Server,清晰地表明了结构体实现了哪些 interface
var (
	_ transport.Server     = (*Server)(nil)
	_ transport.Endpointer = (*Server)(nil)
	_ http.Handler         = (*Server)(nil)
)

因为 kratos 是以 grpc 代码为准,所以自己需要实现 proto 中定义的 Service,那么如果希望 http 也可以直接用这部分 Service 逻辑,那么就需要将 http 路由到对应 Service 方法下,并且做好 入反参序列化、middleware、context 等抽象

3.2 路由与负载均衡

https://go-kratos.dev/docs/component/selector

4. Config

https://go-kratos.dev/docs/component/config

5. Logger

https://go-kratos.dev/docs/component/log

6. Metrics

https://go-kratos.dev/docs/component/metrics

7. Tracing

https://go-kratos.dev/docs/component/middleware/tracing

8. Encoding

https://go-kratos.dev/docs/component/encoding

9. Registry

https://go-kratos.dev/docs/component/registry