开课啦 dubbo-go 微服务升级实战

简介: 杭州开课啦教育科技有限公司是一家致力于为中小学生提供学习辅导的在线教育公司,目前公司后端服务基础设施主要依托于阿里云原生,其中包含计算、网络、存储以及 Kubernetes 服务。

技术选型背景

2020 年是开课啦公司发展壮大的一年,整个公司团队由原来的几百人扩充至如今的几千人,在集中使用的时候基本上会有几千人同时在运营后台进行操做,公司原有的内部后台运营系统是用 PHP 搭建起来的,性能跟业务上已逐渐不能知足公司的需求规划,加上目前开课啦公司开发部已经作了微服务拆分,主体对外服务是 java 语言的 Dubbo 集群,后台系统须要无缝对接 java 的 Dubbo 服务,因此 PHP 已经逐渐不能知足开课啦公司的需求。java

当时本身也调研过 PHP 的 Dubbo 项目,因为项目已基本无人更新维护因此 pass 掉,后面本身对简洁高性能的 go 语言感兴趣,而后就关注到了 Dubbo-go 项目,通过一段时间的调研以后发现 Dubbo Go 符合咱们的业务须要,而且社区很是的活跃,后面便决定选用 Dubbo-go 做为后台的 pc 业务框架。node

可能也有同窗会问为何不使用跨言支持程度更好的 gRPC 呢,由于不少公司最开始的 RPC 服务集群都是基于 Dubbo 生态构建的,若是换框架成本太大,因此基本不会考虑,gRPC 虽然跨语言支持程度更好可是不少东西都须要本身造轮子,好比服务注册,服务发现,日志监控等。linux

当时在决定选用 Dubbo-go 的时候开发内部也有一些反对的声音的,为何不直接转 java,转 java 的话就没有跨语言通讯的问题了,转 java 的问题在于入门成本高,并且对于整个公司的技术栈来讲,保持语言的多样性,才能更加从容的应对将来的业务变化,Go 自己是一个不弱于 Java 的高性能语言,很是适合微服务架构。程序员

面临的挑战

肯定了框架选型后,我接到的首要任务即是要搭建出一套可快速建立业务项目的脚手架,开发出基于 HTTP 协议的 RPC 代理服务,部署须要接入公司的容器化部署平台,一切都是从零开始,在网上基本上找不到能够借鉴的资料。docker

首先是要进行 Dubbo-go 项目的架构的规划,肯定项目目录结构,通过参考 Dubbo-go Demo 以及其它的 Go 项目最终肯定了项目的目录结构,如下目录结构可做为参考。数据库

为了与 Java 服务注册中心保持一致,Dubbo-go 在项目选型上选用以下组件:后端

  • 使用 zookeeper 做为注册中心
  • nacos 做为配置中心
  • 数据库 orm 采用 gorm
  • 消息队列使用 RocketMQ

为了增长开发的效率咱们在 provider 服务初始化前能够对配置进行精简只保留最基础的配置就能够相似下面这种,provider 服务的编码参考 Dubbo-go demo 就能够了。centos

下面是服务启动的 main 方法代码:缓存

Dubbo-go RPC 服务网关设计

通常使用 Dubbo,provider 端须要暴露出接口和方法,consumer 端要十分明确服务使用的接口定义和方法定义,还有入参返参类型等等信息,还须要基于 provider 端提供的 API,两端才能正常通讯调用。bash

然而网关的使用场景是并不关心要调用的接口的详细定义,网关只关注要调用的方法、传递的参数、能接收返回结果就能够了,实现网关代理的基础是 Dubbo/Dubbo-go 的泛化调用特性。

下面是 Dubbo-go 官方给的 demo,泛化服务加载后须要等待 3 秒才能完成调用,然而在实际使用的时候确定是不能实时加载服务去等待 3 秒,因此在网关应用启动时就须要加载缓存好须要泛化调的服务。

通过对 Dubbo-go 泛化调用 demo 的研究,发现用该特性设计 dubbo-go 网关是可行的,难点在于咱们须要把每个须要网关代理 RPC 服务方法的参数以及服务的路径等配置获取到并缓存起来,这样才能在调用前初始化好泛化调用服务,一个服务的配置以下。

因为是用 go 语言作的网关代理,因此不能经过 Java 的 jar 包来获取到 Java RPC 服务配置,若是经过人工维护的话工做量太大,并且易出错,显然是不可接受的。通过一段时间的了解,Java 服务能够经过注解来实现配置的获取,Java 端在方法上加上注解后启动服务的时候会将配置信息经过消息发送到 MQ,网关消费这些消息来实现获取 Java RPC 服务的配置。

Dubbo Go 的 RPC 服务因为 go 语言不支持注解,因此我通过思考本身写了一个扫描代码的小工具,在每一个 RPC 服务方法前加上对应的注释,经过对注释的扫描来获取 RPC 服务的配置,获取到配置后在项目目录内生成 RPC 服务配置,启动应用的时候读取配置发送到 MQ。

网关代理实现以后还能够在网关的基础实现更多的功能,好比 token 验证、白名单、限流、熔断、日志监控功能,网关代理请求实现效果以下:

容器化部署

公司内部的容器化部署环境为阿里云的 K8s,部署至 K8s 平台只须要提供镜像文件,因为 Dubbo-go 编译后是一个二进制的文件,不需任何额外的第三方库,能在 Docker 环境下稳定运行。有 docker 镜像文件以下图所示,能够用 centos 等任一 linux 发行版做为 base 镜像。

LABEL maintainer="<xxx@xx.com>"
LABEL version="1.0"
LABEL description="KKL-GO-NKO-BASE"`

ARG envType=stable
#设置环境变量
ENV envType ${envType}
#编译打包好的压缩包
ADD ./target/nko-base-${envType}.tar.gz /app/

WORKDIR /app
EXPOSE 20000

镜像写好后提供给发布平台,发布平台机器启动镜像并解压打包文件,执行 Dubbo-Go 程序 。

Container entrypoint set to [bash, -c, tar -zxf nko-base-stable.tar.gz && SERVER_ENV=kubernetes && sh ./nko-base/bin/load.sh start -group=stable]

因为开发测试到生产通常是有多个部署环境的,因此咱们须要改动的dubbo-go samples demo 里的编译脚本,让其支持多环境打包。

另外,Dubbo-go 默认注册的 IP 是 K8s pod 的虚拟 IP,不一样 K8s 集群之间网络是不能互通的,因此若是须要跨集群调用就须要修改默认注册 IP,将默认注册的 pod IP + 端口 修改成 Kubernetes 实体机的 IP 加对应端口,Kubernetes 会在 pod 内写入实体机的 IP 加对应端口环境变量,应用程序能够经过读取环境变量获取实体机的 IP加端口,若是须要实现此功能须要修改 Dubbo-go 的注册逻辑。例如以 zookeeper 注册中心为例,咱们能够经过扩展
registery/zookeeper/registry.go的 registerTempZookeeperNode 方法来实现修改注册 IP 跟端口,代码以下图,Dubbo-go 官方将在后面的版本以配置的形式支持自定义注册 IP 跟端口的功能。

func (r *zkRegistry) registerTempZookeeperNode(root string, node string) error {
  ...
  regIp = os.Getenv(constant2.RegistryEnvIP) //实体机的ip
  regPort = os.Getenv(constant2.RegistryEnvPort) //实体机的端口
  urlNode, _ := common.NewURL(node)
  role, _ := strconv.Atoi(urlNode.GetParam(constant.ROLE_KEY, ""))
  if role == common.PROVIDER && regIp != "" && regPort != "" {
    urlNode.Ip = regIp
    urlNode.Port = regPort
    node = url.QueryEscape(urlNode.String())
  }

  zkPath, err = r.client.RegisterTemp(root, node)
  ...
}

做者:曾凡维, 一个有 9 年服务端业务开发经验的一线程序员,曾在腾讯阅文等多家公司担任后端开发工程师,目前就任杭州开课啦教育科技有限公司,从事 go 语言服务基础架构和中间件及部分业务开发工做。
原文连接本文为阿里云原创内容,未经容许不得转载