总篇116篇 2021年第7篇1. ASF 是什么之家云微服务平台ASF(AutoHome Service Framework),提供服务注册和发现、服务治理、可观测、动态配置等能力,致力于帮助之家用户一站式构建微服务体系以及服务上云的能力。ASF1.0 主要针对 Java 应用,支持使用 Dubbo 和 Spring Cloud 进行接入。随着业务的多元化发展,越来越多的用户选择使用 Go 语言和 gRPC 构建微服务,但是 gRPC 的服务注册/发现、负载均衡、健康检查等功能需要用户进行开发和维护,另外 gRPC 服务治理能力也比较弱。在ASF 2.0 中我们增加了对 Dubbo-go 的支持,将上述复杂的功能沉淀到平台侧,为 Java 和 Go 提供了统一的、标准的服务治理能力,极大的降低了使用 Go 语言构建微服务的成本。本篇文章主要介绍之家云 ASF 在使用 Dubbo-go 构建微服务并进行大规模服务治理时的探索和实践。2. ASF 的架构如下图所示,深蓝色虚线框内为 ASF 提供的主要功能,绿色虚线框是即将要交付的功能。3. ASF 的特点和优势3.1. 从gRPC平滑迁移到ASFASF 支持用户从 gRPC 无缝迁移到 ASF,业务代码基本不需要变化,迁移的风险和成本都控制在非常小的范围之内。迁移到 ASF 之后用户无需关注服务注册/发现等,只需要关心业务逻辑和 ASF 配置即可。接下来我们演示下 gRPC server 如何迁移到 ASF-go server。3.1.1. gRPC server下载 gRPC pb插件,生成 pb.go 文件。编写服务端代码const?(?port?=?":50051")type?SpecialService?struct?{?pb.UnimplementedSpecialServer}func?(s?*SpecialService)?Call(ctx?context.Context,?req?*pb.SpecialRequest)?(reply?*pb.SpecialReply,?err?error)?{?fmt.Printf("req:?%v\n",?req)?return?&pb.SpecialReply{Message:?"Hello?ASF?Go?:?"?+?port},?nil}func?main()?{?lis,?err?:=?net.Listen("tcp",?port)?if?err?!=?nil?{log.Fatalf("failed?to?listen:?%v",?err)?}?//?注册到grpc?s?:=?grpc.NewServer()?protobuf.RegisterGreeterServer(s,?&SpecialService{})?if?err?:=?s.Serve(lis);?err?!=?nil?{log.Fatalf("failed?to?serve:?%v",?err)?}}3.1.2. ASF-go server下载 dubbo-go pb 插件,生成 pb.go 文件(ASF 需要下载 ASF专用 pb 插件)。go?get?-u?github.com/apache/dubbo-go/protocol/grpc/protoc-gen-dubbo@v1.5.7编写服务端代码//?注册到dubbo-gofunc?init()?{?config.SetProviderService(NewSpecialService())}------?迁移部分---type?SpecialService?struct?{?*pb.SpecialProviderBase}func?NewSpecialService()?*SpecialService?{?return?&SpecialService{SpecialProviderBase:?&pb.SpecialProviderBase{},?}}------?迁移部分---//?业务代码func?(s?*SpecialService)?Call(ctx?context.Context,?req?*pb.SpecialRequest)?(reply?*pb.SpecialReply,?err?error)?{?fmt.Printf("req:?%v\n",?req)?return?&pb.SpecialReply{Message:?"Hello?ASF?Go?:?"?+?port},?nil}声明配置services:"specialProvider":?#?服务的唯一标识,pb中自动生成version:?1group:?"demo"protocol:?"grpc"interface:?"asf.smc.cloud.base.special"?cluster:?failover?#?集群容错策略?loadbalance:?random?#?负载均衡策略3.2. Dubbo-go 配置优化Dubbo-go 1.5.7 提供了一种声明式的配置方式,通过yaml文件配置应用信息、注册中心信息、以及导出服务和引入服务,针对 consumer 和 provider 需要单独在各自的配置文件中配置,然而对于大部分服务化应用来说它既是 provider 也是 consumer。这导致一个应用需要配置两份配置文件,对用户不是很友好,增加了用户的接入的复杂度(见issuse#1140)。另外对于一些应用级别的配置,例如应用信息、注册中心信息等需要重复配置,同时在解析yaml文件的时侯会生成两份实例,消耗了额外的内存。基于以上问题,ASF 增加了一个聚合配置CompositeConfig,如下:type?CompositeConfig?struct?{?ProviderConfig?*ProviderConfig?`yaml:"provider"?json:"provider,omitempty"?property:"provider"`?ConsumerConfig?*ConsumerConfig?`yaml:"consumer"?json:"consumer,omitempty"?property:"consumer"`?Services?map[string]*ServiceConfig?`yaml:"services"?json:"services,omitempty"?property:"services"`?References?map[string]*ReferenceConfig?`yaml:"references"?json:"references,omitempty"?property:"references"`}CompositeConfig聚合了 provider 和 consumer 的配置,并且在compositeConfig抽象了 consumer 和 provider语义。consumer:?check:?false?#?关闭启动时检查provider:?filter:?"echo"?#?provider?启动的过滤器此外,dubbo-go 支持使用三种方式加载配置文件以适应不同的场景 :环境变量export?CONF_PROVIDER_FILE_PATH="../profiles/dev/server.yml"export?CONF_CONSUMER_FILE_PATH="../profiles/dev/server.yml"export?APP_LOG_CONF_FILE="../profiles/dev/log.yml"flag 指定##?生产者go?run?.?-proConf?../profiles/dev/server.yml?-logConf?../profiles/dev/log.yml##?消费者go?run?.?-conConf?../profiles/dev/client.yml?-logConf?../profiles/dev/log.ymlConfigPostProcessor?//?服务提供方配置?PostProcessServiceConfig(*common.URL)//?服务消费方配置?PostProcessReferenceConfig(*common.URL)在此基础上 ASF 又提供了通过代码直接指定配置文件路径和默认日志级别的方式,为用户在使用物理机和容器部署时提供了更加多元化的选择和更加易于管理配置的方式。如下:config.LoadWithConfig(&common.AsfConfig{?CompositeConf:?"./asf-grpc/server/server.yml",?Level:?"debug",})3.2.1. 支持使用 Fluent API 的引导类ASF?更进一步地提供了使用 Fluent API 的引导类。providerExportedHook?:=?config.NewProviderExportedHook(func(event?observer.Event)?{?logger.Infof("provider?exported?%s",?event.GetTimestamp().Format("3006-01-02?15:04:05"))})consumerReferencedHook?:=?config.NewConsumerReferencedHook(func(event?observer.Event)?{?logger.Infof("consumer?referenced?%s",?event.GetTimestamp().Format("3006-01-02?15:04:05"))})bootstarp?:=?config.NewAsfBootstarp().?AddProviderExportedHook(providerExportedHook).?AddConsumerReferencedHook(consumerReferencedHook).?ConfigPath("./asf-grpc/server/server.yml").?LoadConfig().?//?加载配置?//?DelayExport(true).?//?加载配置后?配置延迟暴露?Start()3.3. 服务契约dubbo-go 和 dubbo 一样支持导出服务的元数据,通过元数据我们可以查看服务的一些基本信息。通过服务的元数据?ASF 提供了 服务查询、服务Mock、服务测试(开发中)等功能。在元数据格式上 dubbo-go 和 dubbo 有些许不同,ASF 对齐了dubbo-go 和 dubbo 的元数据。例如,通过 ASF 控制台可以查看服务(接口)所属的应用,提供者和消费者数量、语言类型、最后一次发布时间,如下图:查看服务提供者/消费者IP地址、所在集群(Availability Zone)、接口的元数据(方法名、参数列表、返回值,如下图:3.4. 服务治理3.4.1. 注册中心高可用注册中心我们依旧使用的是 Zookeeper(以下简称 ZK),而 Zk 使用的是基于Leader的单点写一致性协议(ZAB 协议),所以无法水平扩展来增加注册中心的写性能。另外集群中 Follow 服务器数量逐渐增多的时候,集群处理创建数据节点等事务性请求操作的性能就会逐渐下降。这是因为集群在处理事务性请求操作时,要在集群中对该事务性的请求发起投票,只有超过半数的 Follow 服务器投票一致,才会执行该条写入操作,然后向客户端做出响应。所以在ZK 跨地域部署上[1]我们采用了?多服务+Partition&单地域部署+Learner的部署方式,结合dubbo-go 和 dubbo 的多注册中心机制(multiple-registry)来保证ZK跨地域部署时较高的可用性和较高的写入效率。3.4.2. 区域优先负载均衡在 dubbo 和 dubbo-go 中利用多注册中心(multiple-registry)集群可以实现注册中心集群间的负载均衡[2],即根据注册中心所在的 AZ 可实现节点的区域优先负载均衡,原理如下图:下面是dubbo在使用多注册中心时provider的配置示例,dubbo.protocol.name=dubbodubbo.protocol.port=12345dubbo.config.multiple=truedubbo.registry.id=mbpdubbo.registry.address=nacos://192.168.1.31:8848dubbo.registry.zone=mbpdubbo.registry.use-as-config-center=false可以看出在 dubbo 中要使用区域优先负载均衡必须依赖多注册中心模式,然而当服务规模比较小时,注册中心并不一定部署了多个集群,注册中心和应用也可能没有在一个可用区当中(Availability Zone,AZ)。在这种情况下节点就没有办法使用同 AZ 优先的负载均衡策略了。此外,一个应用如果部署在不同的AZ中,需要按 AZ 配置好dubbo.registry.zone属性,在发布应用的时候需要按照 AZ 加载不同的配置文件,步骤相当的繁琐。基于以上问题,ASF 通过 CMDB 在应用启动的时候自动获取 IP 所在 AZ 并在注册中心URL中进行标记,无需用户手工配置。将应用所在 AZ 和注册中心解绑(不依赖具体注册中心、注册中心部署方式),灵活性更高。在 dubbo 的负载均衡处理逻辑中,也存在一些问题。上游集群 AZ 节点数量分布不均匀。假设 provider A 部署在 AZ1 和 AZ2 中,但是AZ1 中只有1个可用实例,而 AZ2 中有5个。在这种场景下会导致 AZ1 中的 consumer 的所有流量都路由到 AZ1 中的那一个实例上。ASF 在负载均衡逻辑中增加了恐慌阀值判断,当 AZ / Region