1、Circuit breaker(熔断器)
2、Rate limiter(限流器)
3、Logging(日志)
4、Metrics(Prometheus统计)
5、Request tracing(请求跟踪)
6、Service discovery and load balancing(服务发现和负载均衡)
功能 |
组件 |
circuit breaker断路器 |
hystrix-go gobreaker handy breaker |
Metrics 指标 |
prometheus, dogstatsd, influx,graphite 等多个平台 |
服务发现 |
consul, dnssrv, etcd, eureka, lb, zookeeper |
Request Tracing |
Opentracing, LightStep, AppDash, Zipkin |
另外日志和限流器组件, 可轻松与常见的日志库集成, 如zap, logrus 等
go-kit 使用一个抽象Endpoint 来表示每一个服务提供的方法。Endpoint通过一个service实现具体功能。
go-kit 组件围绕Endpoint来构建, 包括断路器, 限流器,日志, Metrics, 请求追踪, 服务发现和负载均衡。比如你可以使用etcd, consul, zookeeper实现你的服务注册, 甚至如果你的团队之前使用的是用的Spring Cloud家族的Eureka, go-kit 也提供支持;
以下就是Endpoint的实现, 其他go-kit组件全部通过装饰者模式注入
type Endpoint func(ctx context.Context, request interface{}) (response interface{}, err error)
type Middleware func(Endpoint) Endpoint
如日志中间件, 仅需实现gokit的Logger interface即可
type Endpoint func(ctx context.Context, request interface{}) (response interface{}, err error)
func(log Logger, in endpoint.Endpoint) endpoint.Endpoint {
return func(ctx context.Context, req interface{}) (interface{}, error) {
logger.Log("input", toJSON(req))
resp, err := in(ctx, req)
logger.Log("output", toJSON(resp), "err", err)
return resp, err
}
}
看了这么多,gokit是什么?怎么用还是有点晕,如果有SpringBoot的使用经验倒是可以类比一下,在SpringBoot中,我们先看下常用的层次结构
而go-kit也分为三层,我们可以根据每层职责的不同进行重新组合,如下图所示,从上到下依次为:transport层,endpoint层,service层,dao层。
Transport就相当于Controller控制层;Endpoint就相当于Service业务逻辑层,而gokit的Service层则相当于业务实现层,例如我们经常写的CRUD操作就是业务实现层
接下来,我们写一个小栗子来说明一下
按照gokit的设计理念,Service将作为核心业务逻辑实现部分。所以,我们写一个Service用于实现两个整数之间的加减乘除运算。
在GOPATH下新建一个项目,然后新建go文件service.go,定义接口Service,代码如下所示:
// Service Define a service interface
type Service interface {
// Add calculate a+b
Add(a, b int) int
// Subtract calculate a-b
Subtract(a, b int) int
// Multiply calculate a*b
Multiply(a, b int) int
// Divide calculate a/b
Divide(a, b int) (int, error)
}
接下来创建结构ArithmeticService实现Service接口。加减乘除的实现非常简单,只有除法运算需要做下异常判断,代码如下所示:
//ArithmeticService implement Service interface
type ArithmeticService struct {
}
// Add implement Add method
func (s ArithmeticService) Add(a, b int) int {
return a + b
}
// Subtract implement Subtract method
func (s ArithmeticService) Subtract(a, b int) int {
return a - b
}
// Multiply implement Multiply method
func (s ArithmeticService) Multiply(a, b int) int {
return a * b
}
// Divide implement Divide method
func (s ArithmeticService) Divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("the dividend can not be zero!")
}
return a / b, nil
}
请求模型:接收http客户端的请求后,把请求参数转为请求模型对象,用于后续业务逻辑处理。观察Service接口可以发现四个接口方法的输入参数均为两个整数,区别在于运算类型不同,所以请求模型只需包含三个字段,即:请求类型、第一个整数、第二个整数。
响应模型:用于向客户端响应结果。对于响应模型可以设置两个字段:一是结果,用于表示正常情况下的运算结果;二是错误描述,用于表示异常时的错误描述。
创建go文件endpoints.go,编写如下代码:
// ArithmeticRequest define request struct
type ArithmeticRequest struct {
RequestType string `json:"request_type"`
A int `json:"a"`
B int `json:"b"`
}
// ArithmeticResponse define response struct
type ArithmeticResponse struct {
Result int `json:"result"`
Error error `json:"error"`
}
在gokit中Endpoint是可以包装到http.Handler中的特殊方法,gokit采用装饰着模式,把Service应该执行的逻辑封装到Endpoint方法中执行。Endpoint的作用是:调用Service中相应的方法处理请求对象(ArithmeticRequest),返回响应对象(ArithmeticResponse)。接下来在endpoints.go文件中增加以下代码:
// MakeArithmeticEndpoint make endpoint
func MakeArithmeticEndpoint(svc Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
req := request.(ArithmeticRequest)
var (
res, a, b int
calError error
)
a = req.A
b = req.B
if strings.EqualFold(req.RequestType, "Add") {
res = svc.Add(a, b)
} else if strings.EqualFold(req.RequestType, "Substract") {
res = svc.Subtract(a, b)
} else if strings.EqualFold(req.RequestType, "Multiply") {
res = svc.Multiply(a, b)
} else if strings.EqualFold(req.RequestType, "Divide") {
res, calError = svc.Divide(a, b)
} else {
return nil, ErrInvalidRequestType
}
return ArithmeticResponse{Result: res, Error: calError}, nil
}
}
Transport层用于接收用户网络请求并将其转为Endpoint可以处理的对象,然后交由Endpoint执行,最后将处理结果转为响应对象向用户响应。为了完成这项工作,Transport需要具备两个工具方法:
下面创建新的go文件,命名为transports.go,并添加如下代码:
// decodeArithmeticRequest decode request params to struct
func decodeArithmeticRequest(_ context.Context, r *http.Request) (interface{}, error) {
vars := mux.Vars(r)
requestType, ok := vars["type"]
if !ok {
return nil, ErrorBadRequest
}
pa, ok := vars["a"]
if !ok {
return nil, ErrorBadRequest
}
pb, ok := vars["b"]
if !ok {
return nil, ErrorBadRequest
}
a, _ := strconv.Atoi(pa)
b, _ := strconv.Atoi(pb)
return ArithmeticRequest{
RequestType: requestType,
A: a,
B: b,
}, nil
}
// encodeArithmeticResponse encode response to return
func encodeArithmeticResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error {
w.Header().Set("Content-Type", "application/json;charset=utf-8")
return json.NewEncoder(w).Encode(response)
}
decodeArithmeticRequest从用户请求中解析请求参数type、a、b,并将三个参数转换为请求对象ArithmeticRequest;encodeArithmeticResponse把响应内容转为json结构,向用户回写响应内容。完成以上工作后,就可以使用解码器、编码器创建HTTP处理方法了,代码如下所示:
var (
ErrorBadRequest = errors.New("invalid request parameter")
)
// MakeHttpHandler make http handler use mux
func MakeHttpHandler(ctx context.Context, endpoint endpoint.Endpoint, logger log.Logger) http.Handler {
r := mux.NewRouter()
options := []kithttp.ServerOption{
kithttp.ServerErrorLogger(logger),
kithttp.ServerErrorEncoder(kithttp.DefaultErrorEncoder),
}
r.Methods("POST").Path("/calculate/{type}/{a}/{b}").Handler(kithttp.NewServer(
endpoint,
decodeArithmeticRequest,
encodeArithmeticResponse,
options...,
))
return r
}
到目前为止,我们已经为该服务完成了Service、Endpoint、Transport三个层次的构建工作,只需要通过main方法将它们按照gokit的要求组织起来,然后使用http库将服务发布即可。组织步骤如下:
创建go文件main.go添加以下代码:
func main() {
ctx := context.Background()
errChan := make(chan error)
var svc Service
svc = ArithmeticService{}
endpoint := MakeArithmeticEndpoint(svc)
var logger log.Logger
{
logger = log.NewLogfmtLogger(os.Stderr)
logger = log.With(logger, "ts", log.DefaultTimestampUTC)
logger = log.With(logger, "caller", log.DefaultCaller)
}
r := MakeHttpHandler(ctx, endpoint, logger)
go func() {
fmt.Println("Http Server start at port:9000")
handler := r
errChan <- http.ListenAndServe(":9000", handler)
}()
go func() {
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
errChan <- fmt.Errorf("%s", <-c)
}()
fmt.Println(<-errChan)
}
接下来直接在idea中运行项目
通过postman调用一下
请求流程:
请求->decodeArithmeticRequest-> endPoint -> encodeArithmeticResponse-> 返回输出
gRPC的Transport会比http稍微麻烦一些,主要是gRPC还需要实现一个Handler接口,除此之外与http的实现几乎差不多。
我们还利用已经编写好的service,代码就不再粘贴了
创建请求、响应模型的方式和使用http有一些不同,我们首先定义proto文件及生成pb文件
在实现grpcServer之前先得定义接口:
syntax = "proto3";
message GrpcResponse {
int32 Result = 1;
string Error = 2;
}
message GrpcRequest {
string RequestType=1;
int32 A = 2;
int32 B = 3;
}
service GrpcService {
rpc Add(GrpcRequest) returns (GrpcResponse) {}
rpc Subtract(GrpcRequest) returns (GrpcResponse) {}
rpc Multiply(GrpcRequest) returns (GrpcResponse) {}
rpc Divide(GrpcRequest) returns (GrpcResponse) {}
}
进入proto文件目录下执行
protoc service.proto --go_out==plugins=grpc:. *.proto
生成的文件如下:
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.25.0-devel
// protoc v3.14.0
// source: arithmetic.proto
package mygrpc
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type GrpcResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Result int32 `protobuf:"varint,1,opt,name=Result,proto3" json:"Result,omitempty"`
Error string `protobuf:"bytes,2,opt,name=Error,proto3" json:"Error,omitempty"`
}
......
type GrpcRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
RequestType string `protobuf:"bytes,1,opt,name=RequestType,proto3" json:"RequestType,omitempty"`
A int32 `protobuf:"varint,2,opt,name=A,proto3" json:"A,omitempty"`
B int32 `protobuf:"varint,3,opt,name=B,proto3" json:"B,omitempty"`
}
func (c *grpcServiceClient) Add(ctx context.Context, in *GrpcRequest, opts ...grpc.CallOption) (*GrpcResponse, error) {
out := new(GrpcResponse)
err := c.cc.Invoke(ctx, "/GrpcService/Add", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *grpcServiceClient) Subtract(ctx context.Context, in *GrpcRequest, opts ...grpc.CallOption) (*GrpcResponse, error) {
out := new(GrpcResponse)
err := c.cc.Invoke(ctx, "/GrpcService/Subtract", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *grpcServiceClient) Multiply(ctx context.Context, in *GrpcRequest, opts ...grpc.CallOption) (*GrpcResponse, error) {
out := new(GrpcResponse)
err := c.cc.Invoke(ctx, "/GrpcService/Multiply", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *grpcServiceClient) Divide(ctx context.Context, in *GrpcRequest, opts ...grpc.CallOption) (*GrpcResponse, error) {
out := new(GrpcResponse)
err := c.cc.Invoke(ctx, "/GrpcService/Divide", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// GrpcServiceServer is the server API for GrpcService service.
type GrpcServiceServer interface {
Add(context.Context, *GrpcRequest) (*GrpcResponse, error)
Subtract(context.Context, *GrpcRequest) (*GrpcResponse, error)
Multiply(context.Context, *GrpcRequest) (*GrpcResponse, error)
Divide(context.Context, *GrpcRequest) (*GrpcResponse, error)
}
......
在gokit中Endpoint是可以包装到grpc.Handler中的特殊方法,gokit采用装饰着模式,把Service应该执行的逻辑封装到Endpoint方法中执行。Endpoint的作用是:调用Service中相应的方法处理请求对象(GrpcRequest ),返回响应对象(GrpcResponse )。接下来在myendpoint.go文件中增加以下代码:
func MakeArithmeticEndpoint(svc myhttp.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
req := request.(*GrpcRequest)
var (
a, b, res int
calError error
)
a = int(req.A)
b = int(req.B)
if strings.EqualFold(req.RequestType, "Add") {
res = svc.Add(a, b)
} else if strings.EqualFold(req.RequestType, "Substract") {
res = svc.Subtract(a, b)
} else if strings.EqualFold(req.RequestType, "Multiply") {
res = svc.Multiply(a, b)
} else if strings.EqualFold(req.RequestType, "Divide") {
res, calError = svc.Divide(a, b)
} else {
return nil, errors.New("the dividend can not be zero!")
}
if calError != nil {
return &GrpcResponse{Result: int32(res), Error: calError.Error()}, nil
}
return &GrpcResponse{Result: int32(res), Error: ""}, nil
}
}
Transport层用于接收用户网络请求并将其转为Endpoint可以处理的对象,然后交由Endpoint执行,最后将处理结果转为响应对象向用户响应。
package mygrpc
import (
"context"
"github.com/go-kit/kit/endpoint"
"github.com/go-kit/kit/transport/grpc"
)
func decodeRequest(_ context.Context, req interface{}) (interface{}, error) {
return req, nil
}
func encodeResponse(_ context.Context, req interface{}) (interface{}, error) {
return req, nil
}
type GrpcServer struct {
grpcHandler grpc.Handler
}
func (g *GrpcServer) Add(ctx context.Context, in *GrpcRequest) (*GrpcResponse, error) {
_, rsp, err := g.grpcHandler.ServeGRPC(ctx, in)
if err != nil {
return nil, err
}
return rsp.(*GrpcResponse), err
}
func (g *GrpcServer) Subtract(ctx context.Context, in *GrpcRequest) (*GrpcResponse, error) {
_, rsp, err := g.grpcHandler.ServeGRPC(ctx, in)
if err != nil {
return nil, err
}
return rsp.(*GrpcResponse), err
}
func (g *GrpcServer) Multiply(ctx context.Context, in *GrpcRequest) (*GrpcResponse, error) {
_, rsp, err := g.grpcHandler.ServeGRPC(ctx, in)
if err != nil {
return nil, err
}
return rsp.(*GrpcResponse), err
}
func (g *GrpcServer) Divide(ctx context.Context, in *GrpcRequest) (*GrpcResponse, error) {
_, rsp, err := g.grpcHandler.ServeGRPC(ctx, in)
if err != nil {
return nil, err
}
return rsp.(*GrpcResponse), err
}
func MakeGRPCHandler(ctx context.Context, endpoint endpoint.Endpoint) *GrpcServer {
return &GrpcServer{
grpcHandler: grpc.NewServer(
endpoint,
decodeRequest,
encodeResponse,
),
}
}
main方法也要做一些修改,代码如下:
var svc myhttp.Service
svc = myhttp.ArithmeticService{}
endpoint = mygrpc.MakeArithmeticEndpoint(svc)
grpcServer := mygrpc.MakeGRPCHandler(ctx, endpoint)
go func() {
//启动grpc服务
ls, _ := net.Listen("tcp", ":9002")
gs := grpc.NewServer()
mygrpc.RegisterGrpcServiceServer(gs, grpcServer)
gs.Serve(ls)
}()
写一个client来测试一下我们的代码
package main
import (
"context"
"fmt"
"gokitdemo/pkg/mygrpc"
"google.golang.org/grpc"
)
func main() {
serviceAddress := "127.0.0.1:9002"
conn, err := grpc.Dial(serviceAddress, grpc.WithInsecure())
if err != nil {
panic("connect error")
}
defer conn.Close()
grpcClient:=mygrpc.NewGrpcServiceClient(conn)
response,err:=grpcClient.Add(context.Background(),&mygrpc.GrpcRequest{A:5,B:4,RequestType:"Add"})
fmt.Println(response.Result)
}
运行结果:
参考:
https://www.jianshu.com/p/0c34a75569b1
https://www.jianshu.com/p/cffe039fa060
https://www.jianshu.com/p/44e48a8fa0de
https://juejin.cn/post/6844903780933173261
https://blog.csdn.net/weixin_42117918/article/details/89186723
https://lattecake.com/post/20140#