Kitex 支持 Dubbo 协议:助力多语言云原生生态融合
作者:王宇轩(github: DMwangnima),Kitex Committer
一、背景
Kitex 是字节跳动基础架构服务框架团队推出的 Go 微服务 RPC 框架,支持 Thrift、Kitex Protobuf、gRPC 等消息协议,具有高性能、强可扩展的特点。于 2021 年 9 月正式开源后,已在多家外部企业成功落地,为他们带来了真实的成本、性能和稳定性收益。
很多企业用户在使用 Kitex 改造服务的过程中,需要 Kitex 能与现有的 Dubbo 框架实现的服务进行通信,这与 CloudWeGo 社区积极拓展生态的目标不谋而合,因此 Dubbo 互通项目 codec-dubbo 应运而生。
在社区同学的热情帮助下,目前 codec-dubbo 能做到 Kitex 与 Dubbo-Java,Kitex 与 Dubbo-Go 互通,支持 Dubbo 用户向 Kitex 迁移。
本文将以方正证券利用 Kitex 与 codec-dubbo 成功进行服务改造为例,对改造过程中使用到的 codec-dubbo 主要功能进行阐述,并简要分析其中的实现细节。
二、企业落地案例
方正证券原有的服务采用 Java 和 Dubbo 框架编写,两者稳定且经过了大量场景的验证,符合他们的生产和开发需求。以请求量较大的小方个股详情页为例,高峰期的接口 QPS 在 3-4k,使用 16 台 16 Core 64G 虚拟机进行承载。
随着云原生架构的兴起,凭借内存占用与执行效率的优势以及天然适配云原生,Go 逐渐成为构建企业服务的重要技术选项。为了更好地降本增效,综合考虑成本、性能和稳定性等因素后,他们决定在新建应用上由 Java 转向 Go,引入 Kitex,Hertz 等 CloudWeGo 项目进行服务开发与重构,并整体迁移至 Kubernetes 环境。
在重构过程中,codec-dubbo 凭借接近原生 Kitex + Thrift 的使用体验以及对 Dubbo 概念的良好支持,降低了使用和理解成本,成功帮助他们解决了 Kitex <-> Dubbo 的互通问题,让 Kitex 服务顺利调用原有的 Dubbo 服务。
目前,使用了 codec-dubbo 的 Kitex 服务已成功上线,稳定运行两个月。还是以小方个股详情页为例,Kitex 和 Hertz 承载了该页面一半左右的接口,在 QPS 不变的情况下,只需要提供 12 个 4 Core 4G Pod,降低资源占用效果显著。
三、codec-dubbo 功能特性
Dubbo 协议编解码器
Dubbo 服务主要使用 Dubbo 协议进行通信,为了支持 Kitex <-> Dubbo 互通,我们需要在 Kitex 中实现 Dubbo 协议。得益于 Kitex 优秀的扩展性,codec-dubbo 根据 Kitex 提供的 Codec 接口实现了 DubboCodec 这一核心编解码器,只需在初始化时注入 DubboCodec 便能使用 Dubbo 协议。
类型映射与拓展
类型映射
Dubbo 主要使用 Hessian2 序列化协议进行 Payload 的编解码,它最大的特点是自描述序列化类型,即不依赖外部 Schema 或接口定义。序列化过程依赖编程语言类型和 Hessian2 类型之间的映射,以 Go 类型转化为 Java 类型为例:
经过分析,我们发现 Hessian2 的基础类型系统与 Thrift 基本重合。为了保证 Kitex + codec-dubbo 的使用体验与 Kitex + Thrift 基本一致,我们基于 Thrift IDL 来生成 Kitex Dubbo-Hessian2 脚手架代码,此时类型转化过程如下所示:
参考 Dubbo 官方的 dubbo-go-hessian2 类型映射,codec-dubbo 提供如下类型映射(此处仅包含部分映射,更多注意事项请参考 codec-dubbo Readme ):
根据 codec-dubbo 提供的类型映射,我们能很轻松地将 Dubbo 接口定义转化为 Thrift IDL,并使用 Kitex 命令行工具生成项目脚手架代码,最终注入 DubboCodec 完成 Kitex -> Dubbo 的通信。以下方 Dubbo 接口定义为例:
package org.cloudwego.kitex.samples.api; public interface GreetProvider { GreetResponse Greet(GreetRequest req) throws Exception; } public class GreetRequest implements Serializable { String req; public GreetRequest(String req) { this.req = req; } } public class GreetResponse implements Serializable { String resp; public GreetResponse(String resp) { this.resp = resp; } }
对应的 api.thrift 文件如下所示,需要注意到其中的结构体定义都需要加上JavaClassName 的注解,对应 Dubbo 接口定义中的 package + 类名。
struct GreetRequest { 1: required string req, } (JavaClassName="org.cloudwego.kitex.samples.api.GreetRequest") struct GreetResponse { 1: required string resp, } (JavaClassName="org.cloudwego.kitex.samples.api.GreetResponse") service GreetService { GreetResponse Greet(1: GreetRequest req) }
使用 Kitex 命令行工具,并指定协议为 Hessian2:
kitex -module demo-client -protocol Hessian2 ./api.thrift
之后初始化 DubboCodec 并将其注入 Kitex ,利用生成代码编写以下 Client 端代码即可实现 Kitex -> Dubbo 调用:
javaClass := "org.cloudwego.kitex.samples.api.GreetProvider" cli, err := greetservice.NewClient("helloworld", // 指定想要访问的服务端地址,也支持 ZooKeeper 服务发现 client.WithHostPorts("127.0.0.1:21000"), // 配置 DubboCodec client.WithCodec( // 指定想要调用的 Dubbo Interface dubbo.NewDubboCodec(dubbo.WithJavaClassName(javaClass)) ), ) if err != nil { panic(err) } resp, err := cli.Greet(context.Background(), &hello.GreetRequest{Req: "world"}) if err != nil { klog.Error(err) return } klog.Infof("resp: %s", resp.Resp)
Kitex + codec-dubbo Server 端流程与 Client 端基本类似,具体例子可参考项目主页。
类型拓展
Hessian2 schema-free 的特性导致 Dubbo 的实现“过于灵活”,可以使用任意类型。为了适配 Dubbo Hessian2 的类型使用灵活性,codec-dubbo 支持类型拓展,其中主要包括自定义映射与 Java 常用类型拓展。
自定义映射
Java 的基础类型有与之对应的包装类型,例如 boolean
与 java.lang.Boolean
。类型映射中默认将 Go 的 bool
类型映射到 Java 的 java.lang.Boolean
类型并不能覆盖到使用 boolean
的情况。 为了统一用户使用体验,让他们在 Kitex 侧只需使用 bool
类型,我们可以在 Thrift 的方法定义后面加上 hessian.argsType="boolean"
注解,利用 thriftgo 的 IDL 反射功能,提前生成 IDL 元信息并注入 codec-dubbo,便可以在运行时动态地将默认映射类型 java.lang.Boolean
改写成 boolean
。具体 Thrift 定义如下所示:
service EchoService { bool EchoBoolean(1: bool req) (hessian.argsType="boolean") }
与 boolean
和 java.lang.Boolean
类似,其他 Java 基础类型和包装类型也能通过这种方式进行自定义映射,此时 codec-dubbo 提供的完整类型映射如下:
java 常用类型拓展
由于 Thrift 类型的局限性,我们无法直接使用 Java 类库中提供的常用类型。为此, codec-dubbo 在 codec-dubbo/java 包中维护了 Thrift 不支持的 Java 类型(例如java.lang.Object
、java.util.Date
)以及与之对应的 java.thrift ,同时借助 thriftgo 提供的 idl-ref 功能,我们可以直接在 Thrift IDL 中引用这些类型并生成相应代码。当前的 java.thrift 如下所示:
struct Object {} (JavaClassName="java.lang.Object") struct Date {} (JavaClassName="java.util.Date") struct Exception {} (JavaClassName="java.lang.Exception")
为了启用这些类型,我们需要在 Thrift IDL 中使用 include "java.thrift"
导入它们,并且在使用 Kitex 命令行工具生成代码时添加 -hessian2 java_extension
参数来拉取该拓展包。
Kitex 命令行工具会自动下载 java.thrift ,你也可以手动下载后放到项目的根目录。引用 java.thrift 中类型的 Thrift IDL 示例:
include "java.thrift" service EchoService { // java.lang.Object i64 EchoString2ObjectMap(1: map<string, java.Object> req) // java.util.Date i64 EchoDate(1: java.Date req) }
方法重载
Go 原生不支持方法重载,只能通过定义多个方法来达到类似重载的效果。为了将 Go 中的多个方法映射到 Java 中的重载方法,与自定义映射一节类似,我们在 Thrift 的方法定义后面加上 JavaMethodName标签,借助 thriftgo 的 IDL 反射功能在运行时动态地将 Go 侧原本的方法名改写成JavaMethodName指定的 Java 侧中的重载方法。
以 Java 侧的 EchoMethod 为例:
String EchoMethod(Boolean req); String EchoMethod(Integer req); String EchoMethod(int req); String EchoMethod(Boolean req1, Integer req2);
我们编写如下 Thrift 定义,即可完成 Go 与 Java 间的重载方法映射,注意到JavaMethodName
和hessian.argsType
可以同时使用:
service EchoService { string EchoMethodA(1: bool req) (JavaMethodName="EchoMethod") string EchoMethodB(1: i32 req) (JavaMethodName="EchoMethod") string EchoMethodC(1: i32 req) (JavaMethodName="EchoMethod", hessian.argsType="int") string EchoMethodD(1: bool req1, 2: i32 req2) (JavaMethodName="EchoMethod") }
异常处理
codec-dubbo 将 Java 中的异常映射为 Go 中的错误,这些错误统一实现以下接口:
type Throwabler interface { Error() string JavaClassName() string GetStackTrace() []StackTraceElement }
根据 Dubbo 官方推荐的异常处理实践以及企业用户目前的需求,我们将异常划分为常见异常与自定义异常,同时兼顾用户的基础需求以及可扩展需求。
常见异常
codec-dubbo 在 pkg/hessian2/exception 包中提供了 Java 常见的异常,目前支持 java.lang.Exception 。
常见异常无需 Kitex 命令行工具的支持,直接引用即可,以下是 Client 端提取异常和 Server 端返回异常的示例。
Client端提取异常
resp, err := cli.Greet(context.Background(), &hello.GreetRequest{Req: "world"}) if err != nil { // FromError 返回 Throwabler exceptionRaw, ok := hessian2_exception.FromError(err) if !ok { // 视作常规错误处理 } else { // 若不关心 exceptionRaw 的具体类型,直接调用 Throwabler 提供的方法即可 klog.Errorf("get %s type Exception", exceptionRaw.JavaClassName()) // 若想获得 exceptionRaw 的具体类型,需要进行类型转换,但前提是已知该具体类型 exception := exceptionRaw.(*hessian2_exception.Exception) } }
Server端返回异常
func (s *GreetServiceImpl) Greet(ctx context.Context, req *hello.GreetRequest) (resp *hello.GreetResponse, err error) { return nil, hessian2_exception.NewException("Your detailed message") }
自定义异常
Java 中的自定义异常往往会继承一个基础异常,这里以 CustomizedException
为例,CustomizedException
继承了 java.lang.Exception
:
public class CustomizedException extends Exception { private final String customizedMessage; public CustomizedException(String customizedMessage) { super(); this.customizedMessage = customizedMessage; } }
得益于 thriftgo 支持生成嵌套结构体,为了在 Kitex 侧定义与之对应的异常,我们在 Thrift 中编写如下定义:
exception CustomizedException { // thrift.nested=“true” 注解让 thriftgo 生成嵌套结构体 1: required java.Exception exception (thrift.nested="true") 2: required string customizedMessage }(JavaClassName="org.cloudwego.kitex.samples.api.CustomizedException")
注意 exception
字段的注解thrift.nested="true"
,它让 thriftgo 生成嵌套结构体,达到类似继承的效果。
和 Java 常用类型扩展一样,需要在使用 kitex 脚手架工具生成代码时添加 -hessian2 java_extension
参数来拉取拓展包,生成代码如下:
type EchoCustomizedException struct { java.Exception `thrift:"exception,1,required" frugal:"1,required,java.Exception" json:"exception"` CustomizedMessage string `thrift:"customizedMessage,2,required" frugal:"2,required,string" json:"customizedMessage"` }
使用方法与常见异常一致,此处不再赘述。
服务注册与发现
Dubbo 同时提供接口级与应用级服务注册发现模型,根据企业用户当前的生产环境需要,我们选择优先实现基于 zookeeper 的接口级模型:Dubbo registry-zookeeper。
与我们熟知的应用级模型不同,接口级模型需要维护接口名 => 服务(不同于微服务,更接近 Handler )的映射关系,一个接口名会映射到多个服务 ,这些服务可能会存在于同一个进程中。
考虑到 Dubbo 的接口级服务模型与 Kitex 的服务模型差别较大,且 Dubbo registry-zookeeper 应绑定 codec-dubbo 使用,因此不考虑修改 kitex-contrib 中原有的 registry-zookeeper,让 dubbo registry-zookeeper 成为 codec-dubbo 的一个子 go module 统一进行开发与维护。
综合考虑 Dubbo 接口级服务模型、Kitex API 与用户的使用体验,我们提供以下的配置层次:
- registry/options.go 与 resolver/options.go 中的 WithServers 和 WithRegistryGroup 函数提供注册中心级别的配置,分别指定 zookeeper 的地址和这些 zookeeper 所属的组。用户使用这些函数生成 Kitex 中
registry.Registry
和discovery.Resolver
实例。 - 服务级别的配置由
client.WithTag
与server.WithRegistryInfo
进行传递,registries/common.go 提供 Tag keys ,这些 key 与 Dubbo 中的服务元数据一一对应。
resolver 示例
intfName := "org.cloudwego.kitex.samples.api.GreetProvider" res, err := resolver.NewZookeeperResolver( // 指定 zookeeper 服务器的地址,可指定多个,请至少指定一个 resolver.WithServers("127.0.0.1:2181"), ) if err != nil { panic(err) } cli, err := greetservice.NewClient("helloworld", // 配置 ZookeeperResolver client.WithResolver(res), // 指定想要调用的 dubbo Interface client.WithTag(registries.DubboServiceInterfaceKey, intfName), ) if err != nil { panic(err) } // 使用 cli 进行 RPC 调用
registry 示例
intfName := "org.cloudwego.kitex.samples.api.GreetProvider" reg, err := registry.NewZookeeperRegistry( // 指定 zookeeper 服务器的地址,可指定多个,请至少指定一个 registry.WithServers("127.0.0.1:2181"), ) if err != nil { panic(err) } svr := greetservice.NewServer(new(GreetServiceImpl), server.WithRegistry(reg), // 配置dubbo URL元数据 server.WithRegistryInfo(&kitex_registry.Info{ Tags: map[string]string{ registries.DubboServiceInterfaceKey: intfName, // application请与dubbo所设置的ApplicationConfig保持一致,此处仅为示例 registries.DubboServiceApplicationKey: "application-name", } }), ) // 启动 svr
总结
Kitex 支持了 Dubbo 协议,是 CloudWeGo 助力多语言云原生生态融合的一大步,解决了众多企业用户 Java 转 Go 、 Java 与 Go 并存的痛点,欢迎大家试用和接入;如果在使用过程遇到任何问题,可以加入我们的飞书用户群,或者在 Github 上给我们提反馈。
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
代码+案例,实战解析BeautifulSoup4
本文分享自华为云社区《从HTML到实战:深入解析BeautifulSoup4的爬虫奇妙世界》,作者:柠檬味拥抱。 网络上的信息浩如烟海,而爬虫技术正是帮助我们从中获取有用信息的重要工具。在爬虫过程中,解析HTML页面是一个关键步骤,而BeautifulSoup4正是一款功能强大的解析器,能够轻松解析HTML和XML文档。本文将介绍BeautifulSoup4的基础知识,并通过实际代码示例进行演示。 BeautifulSoup4简介 BeautifulSoup4是Python中一个用于解析HTML和XML文档的库,它提供了许多便捷的方法来浏览、搜索和修改文档树。BeautifulSoup4支持多种解析器,其中最常用的是基于Python标准库的html.parser。 安装BeautifulSoup4: pip install beautifulsoup4 基础知识 解析HTML文档:使用BeautifulSoup4解析HTML文档非常简单,只需要将HTML文档传递给BeautifulSoup类即可。 from bs4 import BeautifulSoup html_doc = ...
- 下一篇
动手实践丨轻量级目标检测与分割算法开发和部署(RK3568)
本文分享自华为云社区《自动驾驶(AIOT) - 轻量级目标检测与分割算法开发和部署(RK3568)【玩转华为云】》,作者:HouYanSong。 本文将在ModelArts平台上开发轻量级目标检测与分割算法,并使用ModelBox框架在RK3568开发板上实现模型推理和部署。 数据准备 我们收集了一份200张由Labelme标注的道路图像分割数据集,之后使用脚本将其转换为VOC格式的数据集并进行数据增强: 处理好的数据集已经分享到AI Gallery上,可以使用OBS下载: 算法简介 像素级的图像分割不仅需要精确,有时还需要高效以便应用到real-time application比如自动驾驶汽车等。现有的方法可能精度较高但往往参数量巨大,为了解决这个问题,我们使用LinkNet作为主干网络[Fig. 1],中间层[Fig. 2]使用LeakyRelu进行激活,最后一层使用Sigmoid做归一化,并在Encoder Block 4后增添目标检测分支,训练过程中进行梯度裁剪防止梯度爆炸。 云端训练 算法详情可以运行我发布的Notebook,并下载转换好的模型文件: ONNXRuntime推...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- CentOS7设置SWAP分区,小内存服务器的救世主
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- Docker安装Oracle12C,快速搭建Oracle学习环境
- CentOS8安装Docker,最新的服务器搭配容器使用
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- Hadoop3单机部署,实现最简伪集群
- Mario游戏-低调大师作品
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- Red5直播服务器,属于Java语言的直播服务器