背景
2021 年是我司着重关注生产稳定性的一年,得物社区服务晚期是由 PHP 语言构建的单体利用撑持着日活百万用户,随着高速的倒退在性能跟业务上已逐步不能满足将来的需要与布局,在第一阶段上社区与架构团队同学提供了 php + yaf、Java + spring cloud、Go + grpc + K8S 的技术选型计划,思考到服务性能与迁徙老本,最终抉择了 Go + grpc + K8S 作为此项工程的首选为社区微服务构建建设起了里程碑。
随着业务的倒退,对稳定性要求越来越高,为加强业务服务的自治能力,进步集群的稳定性与可控性,且思考最低老本的接入形式,同时思考社区与交易系统 (Dubbo 技术栈) 有着千头万绪的关系,最终心愿能实现两个集群零碎的轻松交融,故选用应用层框架 Dubbo-go 来实 Golang 服务的注册与发现。
Golang 微服务架构,大家可能比拟相熟的是 Go Micro 和 Go Kit(还有 Gizmo),的确,Go Micro 的社区活跃度,Go Kit 的 GitHub Star 数也 18k 以上,但这里并没抉择,次要是 Go Micro 提供了许多的性能,开箱即用,但灵活性受限;go Kit 虽被追捧,然而咱们并非是从新起 Golang 服务,应用层框架限度过于严格,代码迁徙老本将会十分高。思考以上艰难,最终选了一个还在成长期的 Dubbo-go。
Dubbo-go 介绍
Dubbo-go 是目前 Dubbo 多语言生态最炽热的我的项目之一,已随着 Dubbo 生态退出 Apache 基金会,截止目前已有不少一二线互联网公司应用 (钉钉、携程、涂鸦、开课吧等),社区活跃度较高,响应开发者需要较快,有较快且贴合开发着需要的版本迭代速度。
图一 *
Dubbo-go 主我的项目,次要是基于 Dubbo 的分层代码设计,上图是 Dubbo-go 的代码分层,基本上与 Java 版本 Dubbo 现有的分层统一,所以 Dubbo-go 也继承了 Dubbo 的一些低劣个性,比方整洁的代码架构、易于扩大、欠缺的服务治理性能。
目前 Dubbo-go 曾经实现了 Dubbo 的罕用性能(如负责平衡、集群策略、服务多版本多实现、服务多注册核心多协定公布、泛化调用、服务降级熔断等),其中服务注册发现曾经反对 zookeeper/etcd/consul/nacos 支流注册核心。这里不开展具体介绍。
计划实现
依据现有我的项目以 gRPC 为服务调用的背景前提下,思考到对业务代码侵入水平,且做到兼容原有计划失常应用,两套 gRPC 实现下可切换自在,做到生产环境切换 Rpc 治理框架的实时性与可控性,降低生产环境危险,故联合 Dubbo-go 本身反对 gRPC 协定动手满足以上需要。注册核心选型为 Nacos,与目前现有中间件放弃对立,同时满足配置局部配置项需要。
图二 *
这里咱们要先思考两个问题,一个是 Dubbo-go 的集成如何兼容原有 gRPC 计划,放弃两套计划可同时在线反对生产,第二个问题是两套 gRPC 之间如何实现实时切换。
兼容性
在实现此项需要前,咱们先来谈谈 gRPC 本身个性,gRPC 是谷歌开源的一个 RPC 框架,面向挪动和 HTTP/ 2 设计,内容替换格局采纳 ProtoBuf(Google Protocol Buffers),开源已久,提供了一种灵便、高效、主动序列化构造数据的机制,作用与 XML,Json 相似,但应用二进制,(反)序列化速度快,压缩效率高,传输协定采纳 http2,性能比 http1.1 晋升很多。
在依据介绍的 gRPC 的相干个性能够看进去,gRPC 曾经解决了 codec 和 transport 两层的问题,联合图一看,从 cluster 层往上,是没有 gRPC 相干波及的中央,从图 1 外面能够看出要做 gRPC 相干适配,在 protocol 这一层是最合适的,咱们能够如同 DubboProtocol 一样,扩大进去一个 gRPCProtocol,这个 gRPC protocol 大体上相当于一个 Adapter,将底层的 gRPC 的实现和咱们本身的 Dubbo-go 联合在一起。
图三 *
基于上述,Dubbo-go 帮忙咱们解决了 gRPC 的相干整合,相当于在 gRPC 根底之上包装了 Dubbo-go 治理层,而咱们从 gRPC 的 ProtoBuf 批改作为切入点开始,Dubbo-go 官网基于 Google protobuf 扩大插件定义了 Dubbo-go gRPC 所应用的 protobuf 自定义逻辑代码,实现兼容性问题即可。
// HelloWorldServiceClientImpl is the client API for HelloWorldService service.
type HelloWorldServiceClientImpl struct {SayHello func(ctx context.Context, in *SayHelloReq, out *SayHelloResp) error
//...
}
// service Reference
func (c *HelloWorldServiceClientImpl) Reference() string {return "helloWorldServiceImpl"}
// GetDubboStub
func (c *HelloWorldServiceClientImpl) GetDubboStub(cc *grpc.ClientConn) HelloWorldServiceClient {return NewHelloWorldServiceClient(cc)
}
// Server interface
type HelloWorldServiceProviderBase struct {proxyImpl protocol.Invoker}
// set invoker proxy
func (s *HelloWorldServiceProviderBase) SetProxyImpl(impl protocol.Invoker) {s.proxyImpl = impl}
// get invoker proxy
func (s *HelloWorldServiceProviderBase) GetProxyImpl() protocol.Invoker {return s.proxyImpl}
实时切换开关
实时切换,起初是为了在压测环境不便两套不同实现的 gRPC 计划可实时切换做压测数据收集,前期是抱着敬畏生产的态度,在生产环境刚接入 Dubbo-go 时将 Rpc 框架切换反对服务、办法自在切换,从稳定性登程,选择性的切换观测服务稳定性状态。
此项需要的接入,同样是从 gRPC 的 ProtoBuf 批改作为切入点开始,同时基于 Nacos 配置核心实现,咱们将原有 gRPC 的客户端调用和 Dubbo-go 的客户端调用封装成一个对立实例化入口(这里简称 ClinetDubbo),客户端所有办法新增一份继承 ClinetDubbo 具体实现(由 protobuf 扩大插件对立脚本生成),实现内容大抵为获取 ClinetDubbo 中的两套 gRPC 客户端,此时通过 Nacos 配置获取配置核心所关上的客户端是哪一套,依据判断实现走具体的 gRPC 链路。
图四 *
左侧 gRPC 惯例链路,右侧 Dubbo-go 治理模型链路
// ClientDubbo
type HelloWorldServiceClientDubbo struct {
GrpcClient HelloWorldServiceClient
DubboClient *HelloWorldServiceClientImpl
Open bool
Caller string
}
// 具体的办法实现
func (c *HelloWorldServiceClientDubbo) SayHello(ctx context.Context, req *SayHelloReq, opts ...grpc.CallOption) (*SayHelloResp, error) {serverName := c.DubboClient.Reference()
// 获取 nacos 配置源数据
serverCfg := nacosCfg.GetServerCfg()
if !c.Open {c.Open = serverCfg.AllOpen}
cfg := serverCfg.ServiceCfg
// 判断调用链路
if !c.Open &&
!cfg[serverName].Open &&
(cfg[serverName].Consumers == nil || !cfg[serverName].Consumers[c.Caller]) &&
!cfg[serverName].Method["SayHello"].Open &&
(cfg[serverName].Method["SayHello"].Consumer == nil || !cfg[serverName].Method["SayHello"].Consumer[c.Caller]) {
// 原 gRPC 链路
return c.GrpcClient.SayHello(ctx, req, opts...)
}
// Dubbo-go 治理链路
out := new(SayHelloResp)
err := c.DubboClient.SayHello(ctx, req, out)
return out, err
}
我的项目集成
以下是基于现有我的项目构造集成 Dubbo-go 框架示例:
provider
type HelloWorldService struct {
*pb.UnimplementedHelloWorldServiceServer
*pb.HelloWorldServiceClientImpl
*pb.HelloWorldServiceProviderBase
}
func NewHelloWorldService() *HelloWorldService {
return &HelloWorldService{HelloWorldServiceProviderBase: &pb.HelloWorldServiceProviderBase{},
}
}
基于原有服务提供的根底之上退出 Dubbo-go 扩大局部,提供服务注册。
consumer
// 原有 Grpc
var HelloWorldCli HelloWorldServiceClient
//Dubbo-go
var HelloWorldProvider = &HelloWorldServiceClientImpl{}
func GetHelloWorldCli() HelloWorldServiceClient {
if HelloWorldCli == nil {HelloWorldCli = NewHelloWorldClient(grpc_client.GetGrpcClient(...))
}
return &HelloWorldServiceClientDubbo{
GrpcClient: HelloWorldCli,
DubboClient: HelloWorldProvider,
Caller: dubboCfg.Caller,
Open: false,
}
}
GetHelloWorldCli()简略封装了客户端调用,此办法最终返回 HelloWorldServiceClientDubbo 构造体的办法,客户端发动调用进入以 HelloWorldServiceClientDubbo 实现的具体方法中,依据配置项判断执行具体 gRPC 调用链路。
Main
func main() {
//provider
config.SetProviderService(rpc.NewHelloWorldService(), ...)
// 设置服务消费者
config.SetConsumerService(api..., ....)
// 加载 dubbo
config.Load()}
以上就是社区服务集成 Dubbo-go 的整体思路与计划,咱们会发现在现有我的项目中须要改变的代码量很少,且对业务方方代码无任何侵入。
小结
Dubbo-go 集成,加强了业务服务 gRPC 调用过程中治理能力,基于 cluster 减少了服务集群的容错能力,实现了应用服务之间容错能力的可配置性;欠缺且对立了社区服务原始新老架构服务的全链路监控和服务指标监控告警;加强了业务搭档对集群内服务的透明化、可控性,在遇到问题后整体的链路梳理上有了更多的可参考信息。
基于 Dubbo 整体生态,可轻松反对 Golang 与 Java 的 Rpc 互通,做到跨语言 Rpc 调用。
图五 *
最初
Dubbo-go 作为一个微服务框架,本身蕴含治理能力,这部分能力如何与 K8S 融洽联合。
K8S 提供了 pod/endpoint/service 三层维度的资源,能够通过监听 pod/endpoint/service 三层维度资源的事件,作出正当的解决以达到服务治理的目标,不须要引入额定组件,通过监听 k8s 中最细粒度资源 pod 的事件,通过 k8s apiserver 获取 pod 列表,只是通过 apiserver 应用 etcd 的服务注册和服务告诉能力,其余持续应用 Dubbo-go 的服务治理能力,模型简略,不须要实现额定的模块,简直不须要对 Dubbo 作出改变。
文|小柯
关注得物技术,携手走向技术的云端