涂鸦智能 dubbo-go 亿级流量的实践与探索-阿里云开发者社区

开发者社区> 创业头条-吴松> 正文

涂鸦智能 dubbo-go 亿级流量的实践与探索

简介: dubbo 是一个基于 Java 开发的高性能的轻量级 RPC 框架,dubbo 提供了丰富的服务治理功能和优秀的扩展能力。而 dubbo-go 在 java 与 golang 之间提供统一的服务化能力与标准,是涂鸦智能目前最需要解决的主要问题。
+关注继续查看

image.png

dubbo 是一个基于 Java 开发的高性能的轻量级 RPC 框架,dubbo 提供了丰富的服务治理功能和优秀的扩展能力。而 dubbo-go 在 java 与 golang 之间提供统一的服务化能力与标准,是涂鸦智能目前最需要解决的主要问题。本文分为实践和快速接入两部分,分享在涂鸦智能的 dubbo-go 实战经验,意在帮助用户快速接入 dubbo-go RPC 框架,希望能让大家少走些弯路。另外,文中的测试代码基于 dubbo-go版本 v1.4.0。

dubbo-go 网关实践

image.png

dubbo-go 在涂鸦智能的使用情况如上图,接下来会为大家详细介绍落地细节,希望这些在生产环境中总结的经验能够帮助到大家。

1. 背景

在涂鸦智能,dubbo-go 已经作为了 golang 服务与原有 dubbo 集群打通的首选 RPC 框架。其中比较有代表性的 open-gateway 网关系统(下文统一称 gateway,开源版本见 https://github.com/dubbogo/dubbo-go-proxy)。该 gateway 动态加载内部 dubbo 接口信息,以HTTP API 的形式对外暴露。该网关意在解决上一代网关的以下痛点。

  • 通过页面配置 dubbo 接口开放规则,步骤繁琐,权限难以把控;
  • 接口非 RESTful 风格,对外部开发者不友好;
  • 依赖繁重,升级风险大;
  • 并发性能问题。

2. 架构设计

针对如上痛点,随即着手准备设计新的 gateway 架构。首先就是语言选型,golang 的协程调用模型使得 golang 非常适合构建 IO 密集型的应用,且应用部署上也较 java 简单。

经过调研后我们敲定使用 golang 作为 proxy 的编码语言,并使用 dubbo-go 用于连接 dubbo provider 集群。provider 端的业务应用通过使用 java 的插件,以注解形式配置 API 配置信息,该插件会将配置信息和 dubbo 接口元数据更新到元数据注册中心(下图中的 redis )。这样一来,配置从管理后台页面转移到了程序代码中。开发人员在编码时,非常方便地看到 dubbo 接口对外的 API 描述,无需从另外一个管理后台配置 API 的使用方式。

image.png

3. 实践

从上图可以看到,网关能动态加载 dubbo 接口信息,调用 dubbo 接口是基于 dubbo 泛化调用。泛化调用使 client 不需要构建 provider 的 interface 代码,在 dubbo-go 中表现为无需调用 config.SetConsumerService 和 hessian.RegisterPOJO 方法,而是将请求模型纯参数完成,这使得 client 动态新增、修改接口成为可能。在 apache / dubbo-sample / golang / generic / go-client 中的有泛化调用的演示代码。

func test() {

var appName = "UserProviderGer"
var referenceConfig = config.ReferenceConfig{
    InterfaceName: "com.ikurento.user.UserProvider",
    Cluster:       "failover",
    Registry:      "hangzhouzk",
    Protocol:      dubbo.DUBBO,
    Generic:       true,
}
referenceConfig.GenericLoad(appName) // appName is the unique identification of RPCService
time.Sleep(3 * time.Second)
resp, err := referenceConfig.GetRPCService().(*config.GenericService).
    Invoke([]interface{}{"GetUser", []string{"java.lang.String"}, []interface{}{"A003"}})
if err != nil {
    panic(err)
}

}

泛化调用的实现其实相当简单。其功能作用在 dubbo 的 Filter 层中。Generic Filter 已经作为默认开启的 Filter 加入到 dubbo Filter 链中。其核心逻辑如下:

func (ef *GenericFilter) Invoke(ctx context.Context, invoker protocol.Invoker, invocation protocol.Invocation) protocol.Result {

if invocation.MethodName() == constant.GENERIC && len(invocation.Arguments()) == 3 {
    oldArguments := invocation.Arguments()
    if oldParams, ok := oldArguments[2].([]interface{}); ok {
        newParams := make([]hessian.Object, 0, len(oldParams))
        for i := range oldParams {
            newParams = append(newParams, hessian.Object(struct2MapAll(oldParams[i])))
        }
        newArguments := []interface{}{
            oldArguments[0],
            oldArguments[1],
            newParams,
        }
        newInvocation := invocation2.NewRPCInvocation(invocation.MethodName(), newArguments, invocation.Attachments())
        newInvocation.SetReply(invocation.Reply())
        return invoker.Invoke(ctx, newInvocation)
    }
}
return invoker.Invoke(ctx, invocation)

}

Generic Filter 将用户请求的结构体参数转化为统一格式的 map(代码中的 struct2MapAll ),将类( golang 中为 struct )的正反序列化操作变成 map 的正反序列化操作。这使得无需 POJO 描述通过硬编码注入 hessain 库。

从上面代码可以看到,泛化调用实际需要动态构建的内容有 4 个,ReferenceConfig 中需要的 InterfaceName、参数中的 method、ParameterTypes、实际入参 requestParams。

那么这些参数是如何从 HTTP API 匹配获取到的呢?

这里就会用到上文提到的 provider 用于收集元数据的插件。引入插件后,应用在启动时会扫描需要暴露的 dubbo 接口,将 dubbo 元数据和 HTTP API 关联。插件使用方法大致如下,这里调了几个简单的配置作为示例,实际生产时注解内容会更多。

image.png

最终获得的 dubbo 元数据如下:

{

"key": "POST:/hello/{uid}/add",
"interfaceName": "com.tuya.hello.service.template.IUserServer",
"methodName": "addUser",
"parameterTypes": ["com.tuya.gateway.Context", "java.lang.String", "com.tuya.hello.User"],
"parameterNames": ["context", "uid", "userInfo"],
"updateTimestamp": "1234567890",
"permissionDO":{},
"voMap": {
    "userInfo": {
        "name": "java.lang.String",
        "sex": "java.lang.String",
        "age": "java.lang.Integer"
    }
},
"parameterNameHumpToLine": true,
"resultFiledHumpToLine": false,
"protocolName": "dubbo",

.......
}

Gateway 从元数据配置中心订阅到以上信息,就能把一个 API 请求匹配到一个 dubbo 接口。再从 API 请求中抓取参数作为入参。这样功能就完成了流量闭环。

以上内容,大家应该对此 gateway 的项目拓扑结构有了清晰的认知。我接着分享项目在使用 dubbo-go 过程中遇到的问题和调优经验。19 年初,当时的 dubbo-go 项目还只是构建初期,没有什么用户落地的经验。我也是一边参与社区开发,一边编码公司内部网关项目。在解决了一堆 hessain 序列化和 zookeeper 注册中心的问题后,项目最终跑通了闭环。但是,作为一个核心应用,跑通闭环离上生产环境还有很长的路要走,特别是使用了当时稳定性待测试的新框架。整个测试加上功能补全,整整花费了一个季度的时间,直到项目趋于稳定,压测效果也良好。单台网关机器( 2C 8G )全链路模拟真实环境压测达到 2000 QPS。由于引入了比较重的业务逻辑(单个请求平均调用 3 个 dubbo 接口),对于这个压测结果,是符合甚至超出预期的。

总结了一些 dubbo-go 参数配置调优的经验,主要是一些网络相关配置。

大家在跑 demo 时,应该会看到配置文件最后有一堆配置,但如果对 dubbo-go 底层网络模型不熟悉,就很难理解这些配置的含义。目前 dubbo-go 网络层以 getty 为底层框架,实现读写分离和协程池管理。getty 对外暴露 session 的概念,session 提供一系列网络层方法注入的实现,因为本文不是源码解析文档,在这里不过多论述。读者可以简单的认为 dubbo-go 维护了一个 getty session池,session 又维护了一个 TCP 连接池。对于每个连接,getty 会有读协程和写协程伴生,做到读写分离。这里我尽量用通俗的注释帮大家梳理下对性能影响较大的几个配置含义:

protocol_conf:
# 这里是协议独立的配置,在dubbo协议下,大多数配置即为getty session相关的配置。
dubbo:

  # 一个session会始终保证connection_number个tcp连接个数,默认是16,
# 但这里建议大家配置相对小的值,一般系统不需要如此多的连接个数。
# 每隔reconnect_interval时间,检查连接个数,如果小于connection_number,
# 就建立连接。填0或不填都为默认值300ms
reconnect_interval: 0
connection_number: 2
# 客户端发送心跳的间隔
heartbeat_period: "30s"
# OnCron时session的超时时间,超过session_timeout无返回就关闭session
session_timeout: "30s"
# 每一个dubbo interface的客户端,会维护一个最大值为pool_size大小的session池。
# 每次请求从session池中select一个。所以真实的tcp数量是session数量*connection_number,
# 而pool_size是session数量的最大值。测试总结下来一般程序4个tcp连接足以。
pool_size: 4
# session保活超时时间,也就是超过session_timeout时间没有使用该session,就会关闭该session
pool_ttl: 600
# 处理返回值的协程池大小
gr_pool_size: 1200
# 读数据和协程池中的缓冲队列长度,目前已经废弃。不使用缓冲队列
queue_len: 64
queue_number: 60
getty_session_param:
  compress_encoding: false
  tcp_no_delay: true
  tcp_keep_alive: true
  keep_alive_period: "120s"
  tcp_r_buf_size: 262144
  tcp_w_buf_size: 65536
  pkg_wq_size: 512
  tcp_read_timeout: "1s"  # 每次读包的超时时间
  tcp_write_timeout: "5s" # 每次写包的超时时间
  wait_timeout: "1s" 
  max_msg_len: 102400     # 最大数据传输长度
  session_name: "client"

dubbo-go 快速接入

前文已经展示过 dubbo-go 在涂鸦智能的实践成果,接下来介绍快速接入 dubbo-go 的方式。

第一步:hello world

dubbo-go 使用范例目前和 dubbo 一致,放置在 apache/dubbo-samples 项目中。在 dubbo-sample/golang 目录下,用户可以选择自己感兴趣的 feature 目录,快速测试代码效果。

tree dubbo-samples/golang -L 1
dubbo-samples/golang
├── README.md
├── async
├── ci.sh
├── configcenter
├── direct
├── filter
├── general
├── generic
├── go.mod
├── go.sum
├── helloworld
├── multi_registry
└── registry

我们以 hello world 为例,按照 dubbo-samples/golang/README.md 中的步骤,分别启动 server 和 client 。可以尝试 golang 调用 java 、 java 调用 golang 、golang 调用 golang 、java 调用 java。dubbo-go 在协议上支持和 dubbo 互通。

我们以启动 go-server 为例,注册中心默认使用 zookeeper 。首先确认本地的 zookeeper 是否运行正常。然后执行以下命令,紧接着你就可以看到你的服务正常启动的日志了。

export ARCH=mac
export ENV=dev
cd dubbo-samples/golang/helloworld/dubbo/go-server
sh ./assembly/$ARCH/$ENV.sh
cd ./target/darwin/user_info_server-2.6.0-20200608-1056-dev/
sh ./bin/load.sh start

第二步:在项目中使用 dubbo-go

上面,我们通过社区维护的测试代码和启动脚本将用例跑了起来。接下来,我们需要在自己的代码中嵌入 dubbo-go 框架。很多朋友往往是在这一步遇到问题,这里我整理的一些常见问题,希望能帮到大家。

1)环境变量

目前 dubbo-go 有 3 个环境变量需要配置:

  • CONF_CONSUMER_FILE_PATH:Consumer 端配置文件路径,使用 consumer 时必需;
  • CONF_PROVIDER_FILE_PATH:Provider 端配置文件路径,使用 provider 时必需;
  • APP_LOG_CONF_FILE:Log 日志文件路径,必需;
  • CONF_ROUTER_FILE_PATH:File Router 规则配置文件路径,使用 File Router 时需要。

2)代码注意点

  • 注入服务 : 检查是否执行以下代码

image.png

  • 注入序列化描述 :检查是否执行以下代码

image.png

3)正确理解配置文件

  • references / services 下的 key ,如下面例子的 "UserProvider" 需要和服务 Reference() 返回值保持一致,此为标识改接口的 key。

references:
"UserProvider":
registry: "hangzhouzk"
protocol : "dubbo"
interface : "com.ikurento.user.UserProvider"
cluster: "failover"
methods :

  • name: "GetUser"
    retries: 3
  • 注册中心如果只有一个注册中心集群,只需配置一个。多个 IP 用逗号隔开,如下:

registries :
"hangzhouzk":
protocol: "zookeeper"
timeout : "3s"
address: "172.16.120.181:2181,172.16.120.182:2181"
username: ""
password: ""

4)java 和 go 的问题

go 和 java 交互的大小写 :golang 为了适配 java 的驼峰格式,在调用 java 服务时,会自动将 method 和属性首字母变成小写。很多同学故意将 java 代码写成适配 golang 的参数定义,将首字母大写,最后反而无法序列化匹配。

第三步:拓展功能

dubbo-go 和 dubbo 都提供了非常丰富的拓展机制。可以实现自定义模块代替 dubbo-go 默认模块,或者新增某些功能。比如实现 Cluster、Filter 、Router 等来适配业务的需求。这些注入方法暴露在 dubbo-go/common/extension 中,允许用户调用及配置。

本文转自<阿里巴巴云原生技术圈>——阿里巴巴云原生小助手

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
涂鸦智能 dubbo-go 亿级流量的实践与探索
dubbo 是一个基于 Java 开发的高性能的轻量级 RPC 框架,dubbo 提供了丰富的服务治理功能和优秀的扩展能力。而 dubbo-go 在 java 与 golang 之间提供统一的服务化能力与标准,是涂鸦智能目前最需要解决的主要问题。
229 0
阿里云服务器ECS远程登录用户名密码查询方法
阿里云服务器ECS远程连接登录输入用户名和密码,阿里云没有默认密码,如果购买时没设置需要先重置实例密码,Windows用户名是administrator,Linux账号是root,阿小云来详细说下阿里云服务器远程登录连接用户名和密码查询方法
3509 0
阿里云服务器端口号设置
阿里云服务器初级使用者可能面临的问题之一. 使用tomcat或者其他服务器软件设置端口号后,比如 一些不是默认的, mysql的 3306, mssql的1433,有时候打不开网页, 原因是没有在ecs安全组去设置这个端口号. 解决: 点击ecs下网络和安全下的安全组 在弹出的安全组中,如果没有就新建安全组,然后点击配置规则 最后如上图点击添加...或快速创建.   have fun!  将编程看作是一门艺术,而不单单是个技术。
4624 0
基础设施助力双11(三):阿里智能流量调度(NetO)
为此阿里网络攻城狮还专门打造了阿里智能流量调度(NetO)系统来保障丝般顺滑的用户体验,下面就让我们一起来了解一下它是怎么来保障的吧。
818 0
阿里云服务器安全组设置内网互通的方法
虽然0.0.0.0/0使用非常方便,但是发现很多同学使用它来做内网互通,这是有安全风险的,实例有可能会在经典网络被内网IP访问到。下面介绍一下四种安全的内网互联设置方法。 购买前请先:领取阿里云幸运券,有很多优惠,可到下文中领取。
9519 0
阿里云服务器远程登录用户名和密码的查询方法
阿里云服务器远程连接登录用户名和密码在哪查看?阿里云服务器默认密码是什么?云服务器系统不同默认用户名不同
850 0
如何设置阿里云服务器安全组?阿里云安全组规则详细解说
阿里云安全组设置详细图文教程(收藏起来) 阿里云服务器安全组设置规则分享,阿里云服务器安全组如何放行端口设置教程。阿里云会要求客户设置安全组,如果不设置,阿里云会指定默认的安全组。那么,这个安全组是什么呢?顾名思义,就是为了服务器安全设置的。安全组其实就是一个虚拟的防火墙,可以让用户从端口、IP的维度来筛选对应服务器的访问者,从而形成一个云上的安全域。
4274 0
阿里资深专家专访:流量清洗与人工智能
阿里巴巴资深技术专家 铁花 铁花,06年加入阿里巴巴,08年开始从事安全相关工作,淘宝最早SDL的建立及实施人、淘宝第一代web安全解决方案及开发框架的主要开发、安全静态代码扫描平台的创建者。All in无线曾负责来往事业部整体服务端团队及整体技术业务安全,内部IM即时通讯云平台主要设计者之一。
2759 0
36
文章
0
问答
来源圈子
更多
云栖TechDay暨云栖技术分享日,以云栖小镇为主阵地,由阿里云集合阿里巴巴集团、蚂蚁金服集团、菜鸟网络及众多合作伙伴主办,云栖TechDay技术圈拥有针对云计算、大数据、智能制造、移动开发、网络安全等多个领域顶级技术专家的技术分享,让创业者第一时间了解世界前沿技术!
+ 订阅
文章排行榜
最热
最新
相关电子书
更多
文娱运维技术
立即下载
《SaaS模式云原生数据仓库应用场景实践》
立即下载
《看见新力量:二》电子书
立即下载