作者 | 腾讯云泛互云原生团队
责编 | 屠敏
头 图 | CSDN 下载自东方 IC
出品 | CSDN(ID:CSDNnews)
Dubbo 是一款高性能的 Java RPC 框架,它除了拥有卓越的 RPC 能力,也同时具有微服务的一些治理能力,如: 注册发现,负载均衡等,目前 Dubbo 是国内使用较多的微服务框架之一。
在云原生到来的今天,Service Mesh 的服务治理模式彻底解耦了业务逻辑和控制逻辑,通过 Sidecar 将服务发现,流量控制逻辑下沉到 iPaaS 层面,这种方式逐渐得到大家的关注和青睐。 而 Dubbo 在流量治理方面存在一些短板,如灰度发布等目前没有完美的方式实现。
在这种环境下,国内有很多使用 Dubbo 的公司诞生了将 Dubbo 应用迁移到 Service Mesh 中的想法。 本文便是对 Dubbo 迁移到 Service Mesh/Istio 的探讨。
Dubbo to Mesh 改造难点
在 Dubbo 应用向 Service Mesh 应用改造的过程中,可能会遇到以下这些难点:
服务名调用问题 : Istio 通过对 K8S 服务名调用的拦截,实现了无侵入式的流量治理功能,因此 Isito 要求不同服务间的调用必须以服务名的方式进行。 现有项目是否为服务名调用,成了不同类型的项目向 Istio 改造的最大障碍之一,例如 Dubbo 项目就不是服务名调用,而是 Interface 调用,这是第一个痛点问题。
注册中心问题 : 由于 Istio 目前只支持 K8S etcd、Consul 两种服务注册中心,其他注册中心(例如: Zookeeper)的对接以及跟 Istio 配置文件的集成及 xDS 协议数据的下发,成为了第二个痛点问题。
私有协议问题 : 由于当前 Istio 目前只支持 http、gRPC、tcp 三种协议,私有协议适配难度较高,即使在新版 Envoy 已经支持了 Dubbo 协议的情况下,还是需要通过 EnvoyFilter 下发专属 xDS 协议数据来支持 Dubbo 的服务调用及流量治理,这是第三个痛点问题。
相比于自研 sidecar 或修改 Dubbo SDK 等重度方式,本文从不同的角度尝试两种改造方案:
方案1: Dubbo to Mesh 轻量化改造方案 : 该方案基于 Dubbo 本身提供的直连功能,通过修改 dubbo provider / consumer 配置文件,禁止 dubbo 的注册发现功能,通过配置 provider url 实现基于服务名发送请求。 由于 Istio 1.5+ 版已提供了 dubbo 协议的支持,因而数据面 envoy 可对 dubbo 流量进行治理。
方案2: 使用 SpringBoot 重构 Dubbo 的改造方案 : 该方案抛弃 Dubbo 框架,直接使用 SpringBoot 进行重构。 脱离了框架的限制,项目代码更轻量,同时更自然的拥抱云原生。
利用 Dubbo 本身的能力,修改配置文件以禁用 Dubbo 本身的注册发现,并通过指定服务名调用 Dubbo 服务。使用这种方法可以将Dubbo 应用快速部署到 Istio 集群中,不改变通信协议,并实现基于服务名的流量控制。本方案在 Istio 1.5+ 中实验通过。
Step 1. 改造 provider
禁用 registry,不订阅,不注册,也不进行检查。如下修改程序配置:
<beans>
<dubbo:application name="hello-dubbo-provider"/>
<dubbo:protocol name="dubbo" port="20880" />
<dubbo:registry register="false" subscribe="false" check="false" address="none" />
<bean id="demoService" class="tencent.demo.provider.DemoServiceImpl"/>
<dubbo:service interface="tencent.demo.DemoService" ref="demoService" />
</beans>
现在无需部署 zk (或其他注册服务),这个应用可以直接启动。
Step 2. 改造 consumer
同样禁用 registry。 由于没有注册发现服务了,所以需要在消费端的配置文件中显式指定接口的服务地址,通常我们会指定为 provider 部署所在的服务器 ip,但在部署到 Istio 中,我们需要指定为部署的 Service 名称,由 CoreDNS 解析为实际的 IP,从而实现让 Istio 来管理路由。本示例中的 provider 应用部署的服务名是 hello-dubbo-provider, 所以我们显式指定 url="dubbo://hello-dubbo-provider:20880"。
<beans>
<dubbo:application name="demo-consumer"/>
<dubbo:registry address="none" register="false" subscribe="false" check="false" />
<dubbo:reference id="demoService" check="false" interface="tencent.demo.DemoService" url="dubbo://hello-dubbo-provider:20880" />
</beans>
除了以上通过配置文件修改调用地址,我们还可以通过在 JVM 启动参数中加入参数映射服务地址:
java -Dtencent.demo.DemoService=dubbo://hello-dubbo-provider:20880 ...
如果服务较多,可以使用文件映射服务和地址:
java -Ddubbo.resolve.file=services.properties ...
在 services.properties 中可指定多个服务:
tencent.demo.DemoService=dubbo://hello-dubbo-provider:20880
tencent.demo.DemoService2=dubbo://hello-dubbo-provider:20880
tencent.demo.DemoService3=dubbo://hello-dubbo-provider3:20880
本地调试一下,在 hosts 中映射一下。
127.0.0.1 hello-dubbo-provider
现在启动 consumer,不出意外,完美运行。
现在我们可以通过 yaml 的部署,轻松将应用部署到 Istio 集群中。本方案在 Isito 1.5.6 和 1.6.1 中实验通过。 详细的实验过程可以参考 腾讯云“云+社区”的文章《 Dubbo to Istio / Dubbo Mesh 极简改造指南 》。
通过简单的两处的修改,我们便实现禁用 Dubbo本身的注册发现,并交由 Istio 来管理。 通过 Dubbo 本身的能力,修改配置文件和依赖包,我们还可以将 Dubbo 应用的通信方式修改为 http。
实验证明:不改造 dubbo 框架和业务逻辑的情况下(只改配置文件和依赖包),可以实现 如下目标:1 基于 TCP 的流量操控,可以根据 Service 来控制流量。2 基于 HTTP 的流量操控,可以控制到 Service 和 interfaceName(Dubbo 中 interfaceName 是写在 url 里面的,可以通过 uri 匹配规则进行流量操控)。
在调用链追踪方面, Dubbo 本身并没有 OpenTracing,在不修改 Dubbo SDK 的情况下,仍需要使用 Dubbo 原来的方式来实现。
此方案充分利用 Dubbo 项目原有的代码结构,删除Dubbo原有的注册发现、Dubbo协议、Dubbo服务配置等功能,将一个 Dubbo 项目改造成一个 SpringBoot + K8S + Istio 的项目,代码修改范围可控,改造方式最彻底。
由于改造 Dubbo SDK、Isito控制面、Envoy 数据面,让 Dubbo 去适配 Service Mesh 的技术难度较大,而且即使改造成功也需要通过 EnvoyFilter 下发专属 xDS 协议数据的形式来支持 Dubbo 服务间调用的流量治理,使得这种方式与原生 Istio 的使用方式差距较大。
所以我们选择了一条将 Dubbo 项目改造成 SpringBoot + K8S + Istio 项目的更简单的路,充分利用现有 Dubbo 项目的代码结构,将代码修改量降到一个可控的范围内。
由于 Dubbo 项目 facade 模块的作用与 Spring Cloud Feign 模块的作用十分相似(模块内都是一些 interface,需要服务端 xxxServiceImpl 去实现各个 interface,消费端通过 @Resource 注解的方式引入 interface 并直接调用),使得 Dubbo 最复杂的服务间调用方式有了解决的方案。
此次改造只是利用了 Dubbo 项目的代码结构,Dubbo 原有的注册中心、Dubbo 协议等功能全部都会被去掉,也就是改造后的项目跟 Dubbo 已经没有任何关系了,所以注册中心、Dubbo私有协议这两个痛点问题也就不存在了。
K8S 会接管服务注册发现、服务编排等工作,Istio 会接管服务治理、调用链监控、服务安全等工作,改造后的项目是一个标准的 Service Mesh 项目。
在根 pom.xml 中引入 SpringBoot parent,增加 spring-cloud-dependencies import 引用。
删除所有 dubbo 相关引用。
虽然 pom 文件改动很大,但属于一次性改动,改造工作量较小。
删除所有 Dubbo 相关引用、Dubbo 相关配置文件。
Dubbo 原有 facade 接口是标准的 JAVA 接口定义,与 Feign Restful 接口定义十分类似。这里可以在原有的 facade 接口基础上增加 @FeignClient、@RequestMapping 等注解,将一个普通的 facade 接口改造成一个 Feign Restful 接口,后续会使用 Feign 这个 Restful 框架来处理服务间调用等问题。
由于 Feign 本身是自带了 Ribbon 负载均衡,服务访问者经过负载均衡后会找到服务提供者的一个 IP+Port 进行调用,这与 K8S Service 要求的服务名调用的方式相冲突,所以必须想办法去掉 Feign 自带的负载均衡。好在 @FeignClient 可以手工指定一个固定的调用地址,这里可以把这个地址设置成 K8S Service 的 name 名称,从而实现了通过 Feign 对 K8S Service 服务名调用的能力。此部分需要每个 facade 接口增加注解一次,改造工作量相对可控。
由于 Feign 要求接口使用 Restful 格式,所以接口中的每个抽象方法都必须添加 @RequestMapping、@GetMapping、@PostMapping 等注解暴露成一个 Restful 资源地址。此部分改造涉及到每个 facade 接口的每个抽象方法,是整个方案里改动量最大的一部分。
此部分整体改造工作量取决于原有的 Dubbo 项目包含多少个 facade 接口,以及每个 facade 包含多少个抽象方法。
pom.xml 增加 spring-boot-starter-web、spring-cloud-starter-openfeign 等引用,同时增加 SpringBoot mainClass 标准启动项配置。
删除所有 Dubbo 相关引用、Dubbo 相关配置文件。
增加 SpringBoot 启动类,增加 @SpringBootApplication、@EnableFeignClients 两个注解,配置 dubbo-provider 服务端口号。
xxxServiceImpl 服务实现类上增加 @RestController 注解,提供 consumer Restful 访问的能力。这个需要每个服务实现类都加上 @RestController 注解,不要遗漏。
此部分大都属于一次性改动,改造工作量相对可控。
pom.xml 增加 spring-boot-starter-web、spring-cloud-starter-openfeign 等引用,同时增加 SpringBoot mainClass 标准启动项配置。
删除所有 Dubbo 相关引用、Dubbo 相关配置文件。
增加 SpringBoot 启动类,增加 @SpringBootApplication、@EnableFeignClients(需要配置 basePackages 扫描包路径) 两个注解,并配置 dubbo-consumer 服务端口号。
此部分大都属于一次性改动,改造工作量相对可控。
创建 dubbo-provider K8S Deployment、K8S Service(ClusterIP),提供集群内访问服务。
创建 dubbo-consumer K8S Deployment、K8S Service(ClusterIP),提供集群内访问服务。
创建 istio ingressgateway、VirtualService,提供公网访问入口,并进行流量治理测试。
经过上面几步操作,我们成功的将一个 Dubbo 项目改造成了一个 Service Mesh 项目,并在 K8S + Istio 集群中部署成功、测试通过。
详细的实验过程可以参考 腾讯云“云+社区”的文章《 如何将一个 Dubbo 项目改造成一个 Service Mesh 项目? 》:https://cloud.tencent.com/developer/article/1635721。
业务网关会在 dubbo 注册中心进行注册和订阅,向 mesh 外提供 dubbo-rpc 调用服务,向 mesh 内提供 http/rest 接口服务。
当然,真实业务系统中的架构复杂度是远高于这个 Demo 的,实际改造的工作量要比改造这个 Demo 大得多。但笔者以为:Dubbo 的服务治理功能和 Istio 是重复的,并且随着云原生的 Java 框架兴起,以及 Spring 对云原生/Native 的支持,最终的 Dubbo 迁移到 Mesh 的改造都会走到这个路径。
最后总结一下本文中两种方案各自的适用场景及优缺点:
作者简介:腾讯云泛互云原生团队基于腾讯云的技术和产品能力,致力于帮助企业客户推广及落地微服务、容器、服务网格、大数据、DevOps、AI等技术产品,有效提升客户的研发及运维效率。目前服务的客户主要有:快手、知乎、KEEP、小红书、微盟等。
更多精彩推荐
☞ TensorFlow 2.0 发布以来,又有哪些最新进展?| AI ProCon 2020