背景
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 Referencefunc (c *HelloWorldServiceClientImpl) Reference() string { return "helloWorldServiceImpl"}// GetDubboStubfunc (c *HelloWorldServiceClientImpl) GetDubboStub(cc *grpc.ClientConn) HelloWorldServiceClient { return NewHelloWorldServiceClient(cc)}// Server interfacetype HelloWorldServiceProviderBase struct { proxyImpl protocol.Invoker}// set invoker proxyfunc (s *HelloWorldServiceProviderBase) SetProxyImpl(impl protocol.Invoker) { s.proxyImpl = impl}// get invoker proxyfunc (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治理模型链路
// ClientDubbotype 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
//原有Grpcvar HelloWorldCli HelloWorldServiceClient //Dubbo-govar 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作出改变。
文|小柯
关注得物技术,携手走向技术的云端