go-kit 的根本介绍
go-kit 介绍
go-kit
是一个 Golang 编写的开发框架,能够帮忙开发者更快捷地构建可伸缩的微服务架构。它提供了一系列模块化的组件,能够帮忙开发者更轻松地构建和保护微服务。go-kit
的设计理念是可组合的,它能够与各种服务发现零碎进行集成,如 etcd、consul 和 zookeeper 等,并且能够轻松实现服务熔断和负载平衡。
另外,go-kit
也提供了诸如监控、日志和链路追踪的性能,能够帮忙开发者更好地了解和管制微服务架构。
go-kit
还提供了指标收集和剖析性能,能够帮忙开发者进行性能优化和故障诊断。它还容许用户应用自定义的协定,比方 REST、gRPC 和 GraphQL 等,来实现不同服务之间的通信。
设计哲学
go-kit 是一个合乎 KISS
准则的框架,通过应用 关注点拆散
,让开发者优先集中于业务逻辑的开发。在业务逻辑实现之后,再通过 组合
疾速接入微服务的各种能力。
go-kit 次要能够划分为:
- Service Layer —— 专一于业务逻辑,解决 request,返回 response。
- Endpoint Layer —— 是 Service 的入口,对 Service 进行 wrapper,能够附加各种
rate-limit
metrics
的 middleware,从而加强 Service。 - Transport Layer —— 定义客户端和服务端应该如何通信,负责网络协议转换等,例如 gRPC、HTTP 等协定的解决。
在 go-kit 中,整个我的项目就像是一个洋葱,最内核是 Service,也就是业务逻辑。而后通过一层层 middleware 进行包裹,为我的项目增加各种能力。
入手实际
Service
既然是业务优先,那么开发的程序天然是应该从 Service 业务逻辑开始。
让咱们从一个简略的 用户服务 开始吧!假如咱们须要实现一个 user-service,它须要解决用户的注册、登录的逻辑。基于 面向接口编程
的准则,咱们能够设计一个 Service 如下:
type HelloRequest struct {Name string `json:"name"`}
type HelloResponse struct {Message string `json:"message"`}
type HelloService interface {Hello(ctx context.Context, name string) (HelloResponse, error)
}
type helloService struct{}
func (s *helloService) Hello(ctx context.Context, name string) (HelloResponse, error) {return HelloResponse{Message: "Hello," + name}, nil
}
Endpoint
写完业务逻辑之后,咱们须要对外提供这个接口,能够用 Endpoint
来包裹这个Service
。在 go-kit 中,Endpoint 就是一个interface
:
type Endpoint func(ctx context.Context, request interface{}) (response interface{}, err error)
它次要负责的是:接管内部的 request,交给 Service 解决之后,返回对应的 response。
那么,咱们能够这样实现 HelloService 的 Endpoint:
func MakeHelloEndpoint(svc HelloService) endpoint.Endpoint {return func(ctx context.Context, request interface{}) (interface{}, error) {req := request.(HelloRequest)
res, err := svc.Hello(ctx, req.Name) // 调用理论的 Service 执行业务逻辑
if err != nil {return nil, err}
return res, nil
}
}
Transport
最初,就是须要把这个服务裸露进去,对外提供服务了。在 go-kit 中,这也就是 Transport 须要做的事件,Transport 具体怎么写,取决于我的项目理论的网络计划。如果是 http,那么 Transport 就须要将 http 申请数据转换为 Service 的申请参数。咱们应用 http 做一个示例:
func decodeHelloRequest(_ context.Context, r *http.Request) (interface{}, error) {return HelloRequest{Name: r.FormValue("name")}, nil
}
func encodeResponse(_ context.Context, w http.ResponseWriter, response interface{}) error {return json.NewEncoder(w).Encode(response)
}
其中 decode
负责 http.Request
–> HelloRequest
;encode
负责HelloResponse
–> http.Response
。
Server
最初,将所有的组件装配起来,用一个 http server 来启动服务就好了:
func main() {svc := &helloService{}
ep := MakeHelloEndpoint(svc)
route := mux.NewRouter()
// go-kit 的 http 协定解决
route.Methods("Get").Path("/hello").Handler(kithttp.NewServer(
ep,
decodeHelloRequest,
encodeResponse,
))
log.Fatal(http.ListenAndServe(":8080", route))
}
运行一下就能够看到后果:
❯ curl "http://localhost:8080/hello?name=j"
{"message":"Hello, j"}
其中最外围的一块就是执行 kithttp.NewServer()
这个函数,它会承受 endpoint、decode、encode 几个参数。咱们能够别离再看看这几个参数的作用:
- endpoint —— 承受 request,调用 Service,返回 response
- decode —— 将网络协议数据转换成 request
- encode —— 将 response 转换成网络协议数据返回
兴许,再看看 go-kit 的源码会更加有助于了解整个链路是怎么样的。在 go-kit 中,NewServer
创立的对象最外围的逻辑就是:
func (s Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {request, err := s.dec(ctx, r)
if err != nil {
// error handler
return
}
response, err := s.e(ctx, request)
if err != nil {
// error handler
return
}
if err := s.enc(ctx, w, response); err != nil {
// error handler
return
}
}
实现的示例能够从 GitHub 查看。
引入微服务的能力
为什么须要流量管制
在微服务架构中,服务之间是通过网络调用来实现合作的。如果某个服务的负载高,其它服务申请这个服务时就会期待。这样会导致整个零碎的瓶颈,影响整个零碎的吞吐量和稳定性。因而,对服务进行流量管制是很有必要的。而 ratelimit 就是其中一种流量管制的实现办法。它能够限度一个服务在一段时间内可能承受的申请数量,从而防止一个服务的高负载导致整个零碎的故障。
如何实现 ratelmit
在 go-kit 中,能够很不便地实现一个简略的 ratelimit。
在如果相熟 OOP 的话,应该会听过 装璜器模式
。在 go-kit 中,就是应用了这个思维,用 Endpoint 包裹 Endpoint,从而增加各种不同的能力。例如,在咱们的例子中,想要给微服务增加一个ratelimit
能力的话,就能够这样创立一个装璜器:
type limitMiddleware struct {
timer time.Duration
burst int
}
func (l limitMiddleware) wrap(e endpoint.Endpoint) endpoint.Endpoint {e = ratelimit.NewErroringLimiter(rate.NewLimiter(rate.Every(l.timer), l.burst))(e)
return e
}
limitMiddwware
是一个限速器,timer 是一个工夫周期,burst 是最大并发申请数量。wrap
函数就是咱们的装璜器,承受一个 Endpoint,返回一个 Endpoint,它就能够为 Endpoint 增加 ratelimit 的性能。
相应地,咱们的 main 程序就能够这样应用这个装璜器:
func main() {svc := &helloService{}
ep := MakeHelloEndpoint(svc)
// decorate ratelimit
ratelimit := limitMiddleware{
timer: 5 * time.Second,
burst: 3,
}
ep = ratelimit.wrap(ep)
route := mux.NewRouter()
route.Methods("Get").Path("/hello").Handler(kithttp.NewServer(
ep,
decodeHelloRequest,
encodeResponse,
))
log.Fatal(http.ListenAndServe(":8080", route))
}
下面的例子就为这个服务创立了一个 ratelimit,如果在 5 秒钟内申请数超过 3 个的话,这个 ratelimit 就会拒绝请求。咱们能够看看成果:
❯ date && curl "http://localhost:8080/hello?name=j"
Wed Feb 8 15:25:27 CST 2023
{"message":"Hello, j"}
❯ date && curl "http://localhost:8080/hello?name=j"
Wed Feb 8 15:25:27 CST 2023
{"message":"Hello, j"}
❯ date && curl "http://localhost:8080/hello?name=j"
Wed Feb 8 15:25:28 CST 2023
{"message":"Hello, j"}
❯ date && curl "http://localhost:8080/hello?name=j"
Wed Feb 8 15:25:29 CST 2023
rate limit exceeded% # 触发了 ratelimit
❯ date && curl "http://localhost:8080/hello?name=j"
Wed Feb 8 15:25:30 CST 2023
rate limit exceeded%
❯ date && curl "http://localhost:8080/hello?name=j"
Wed Feb 8 15:25:33 CST 2023 # 复原响应申请
{"message":"Hello, j"}
拓展一下
不同的算法
下面用到的限速器是基于令牌桶算法实现的,相似的还有很多其余的算法实现:
- Token bucket
- Leaky bucket
- Fixed window counter
- Sliding window log
- Sliding window counter
还有开源软件也有各自的实现,比方 Java 生态中的 Hystrix、resillience4,或者是 Nginx 也有本人的实现。
全局限流
换个角度,这些 ratelimit 都是单个服务的限流,如果要做全局限流的话,咱们能够通过引入集中式的数据存储。将本来程序内存的申请计数器放到内部存储,所有服务共享一个计数器来实现。比方 Redis 限流最佳实际。
自适应限流
下面的 ratelimit 解决方案都有一个问题:动态的配置在理论的分布式环境中不好用。在大型的分布式系统中,并发数、零碎负载、可用资源都是动态变化的,咱们很难失去一个动态的值来限流,这就须要咱们实现一种动静的限流算法:依据零碎的状况,动静调整限流阈值。相应的有 aws 限流算法和 netflix 限流算法来实现自适应限流解决。
总之,还是那句话:
零碎设计没有银弹,还是须要依据理论状况做 trade-off。
其余
这只是一个简略的示例,微服务开发中还有很多 服务发现
、 断路器
、 负载平衡
、 重试
等等的惯例性能。就留待之后再进行拓展吧。