货拉拉服务化实践-为啥都爱“造*”?

时间:2022-12-16 10:09:18

   一、背景

  货拉拉于2013年创立,成长于粤港澳大湾区,是一家从事同城/跨城货运、企业版物流服务、搬家、零担、汽车租售及车后市场服务的互联网物流商城;公司技术经过近十年的发展,内部微服务的数量达到 1000+

  货拉拉最早期的服务化技术架构是基于 PHP (少量 Java )经过域名走 http 协议,进行内部服务之间的相互访问,这种简单的技术方式支撑着货拉拉早期业务的快速迭代开发  

货拉拉服务化实践-为啥都爱“造*”?

  但这种“快”也带来一些隐患,当我们的业务规模、数据规模、服务规模都发生了很大的变化,“快”引发的一些问题变得越来越凸显;具体到服务化架构而言,主要问题体现在:内部服务之间的交互关系越来越复杂,链路的稳定性面临挑战,同时服务之间缺乏基本的治理能力(路由、熔断、降级、限流等等),无法从容的应对多变的业务形态和突发的流量高峰带来的问题

  在这种早期的背景下,货拉拉引入服务化架构刻不容缓,但同时我们无法做到零历史负担和一步改造到位,因此我们把服务化架构建设的核心思路主要聚焦在如下两个方面:

  首先要考虑如何兼容生产环境大量现存的老服务(PHP/Java),同时可以逐步进行改造迁移,不阻塞业务迭代

  其次是要结合货拉拉的自身情况,在相对可控、有保障的前提下去做服务化相关的治理,包括业务情况以及研发人员的特点

   二、为啥选择“造*”

  货拉拉服务化建设初期,团队尝试考虑过多种现有的开源方案,但都或多或少的遇到了无法解决的问题或者无法接受的维护改造成本,最终我们选择了自研的方案,也就是大家熟知的“造*”

  下面将说明我们为啥没有直接使用 dubbo 或 istio 以及背后的考虑因素

  2.1. 为啥不直接使用 Dubbo

  Dubbo 作为国内最成熟、最广为人知的微服务框架,首先进入我们考虑的范围,但在结合货拉拉的现状评估以后,遇到了如下局限性:

  没有直接提供对 PHP 的支持,即使不考虑对 PHP 新服务端的支持,也缺乏在统一的服务发现和服务治理机制下进行 PHP 服务的客户端调用

  扩展性太灵活,扩展点太多(100+),作为开源产品是一种优势,但是对于公司内部来说,很难统一和管理使用姿势,对基础设施的适配要求也变的更高

  要兼容货拉拉的基础设施现状(可观测性、注册中心选型、发布机制等等),Dubbo 无法做到开箱即用,需要进行工作量不小的定制工作

  没有提供直接的服务治理能力(需要结合其他产品或自己扩展)、控制台能力弱、代码中大量处理历史兼容问题等等

  以上局限性有些无法直接解决,有些需要通过二次开发进行定制,但是成本也不低,因此我们最终没有直接采用 Dubbo,但自研方案参考了大量 Dubbo 实现上的优秀设计

  2.2. 为啥不考虑 Istio

  Istio 作为当下最流行的 service mesh 解决方案,也进入了我们的调研范围,他提供的多语言和透明升级的能力对我们非常有吸引力,同时服务治理能力下沉的理念也是我们非常认同的未来趋势;但是与此同时,Istio 方案对我们来说面临的局限性也十分明显:

  内置提供的服务治理能力有限,且 envoy filter 扩展难度成本较大(无论 CPP/Lua/Proxy-Wasm)

  默认采用的透明流量劫持,且不说 iptables 带来的复杂度,还有很多非服务化流量可能带来兼容性问题(遇到过 http1.0、JDK 低版本客户端等等问题)

  K8S 环境是一等公民,货拉拉还存在很多 VM 的环境

  在服务数量较多的情况下,集群服务配置过大、MCP 扩展问题等等

  同时 Istio 架构复杂,调用链路变长,货拉拉技术团队当前也缺乏 Istio 的运维经验 ,加上国内也缺少真正大规模 Istio 落地的大厂经验与案例,所以我们也没有使用 Istio 方案

  2.3. 其他考虑因素

  除了上面两个章节中提到的因素外,还有两个主要因素的共同作用,使得我们最终选择了自研的方案

  公司业务场景的多样性和特殊性,需要对架构和代码都要有决定的掌控能力

  开源的控制面大多比较简洁,无法满足我们的诉求,且我们需要上报更多的信息方便我们进行服务管理和框架运营

   三、核心设计

  3.1. 架构设计

  如下图所示,货拉拉服务化整体架构设计借鉴了 service mesh 的一些理念,主要区分控制面和数据面,并使用开源项目的相关组件进行支撑  

货拉拉服务化实践-为啥都爱“造*”?

  数据面:现阶段主要指的 Java SDK 和 后面提到的 PHP Proxy,提供服务化所需的接入 API,RPC 协议、服务治理等等能力,通常运行在业务进程或者作为业务进程的 sidecar

  控制面:除了作为传统服务化控制面提供服务治理和服务生命周期管理能力外,也充当服务化的工具中心和数据运营中心

  主要支撑组件

  使用 Apollo 作为配置中心,并区别于传统 service mesh 数据面依赖于 Pilot 等组件,我们通过 apollo 解耦数据面与控制面,不额外增加运行时依赖

  使用 Consul 集群作为注册中心组件

  3.2. PHP 与“泛化调用”

  前面提到我们需要考虑如何兼容生产环境大量现存的 PHP 老服务,好在我们内部达成共识,不需要考虑对 PHP 新服务端的支持,因此问题就可以简化为两个主要问题:

  老的 PHP 服务如何被服务发现?

  Java 服务怎么通过类似标准 RPC 的方式调用老的 PHP 服务?

  首先针对老的 PHP 服务如何被服务发现,我们设计了如下图所示的解决方案:通过与一个 sidecar(命名 GoProxy,非 golang 的 GOPROXY) 进程协同,来解决服务注册、动态配置获取等等问题  

货拉拉服务化实践-为啥都爱“造*”?

  其次对于 Java 服务怎么通过类似标准 RPC 的方式调用老的 PHP 服务的问题,我们设计了内部称为“泛化调用”(非传统 RPC 泛化调用)的 RPC 客户端,可以使用和标准 RPC 一致的接入和配置方式进行调用,并且获取统一的服务发现、服务路由和服务治理(仅客户端)能力  

货拉拉服务化实践-为啥都爱“造*”?

  3.3. RPC 协议

  协议是 RPC 的核心,它规范了数据在网络中的传输内容和格式,框架的通用性、性能、穿透性、扩展性等都与 RPC 协议的选择息息相关

  货拉拉结合内部情况同时提供基于 HTTP1.1 的 JSON-RPC 协议和基于 HTTP2.0 的 gRPC 协议,两种协议除了定义和调用方式外,其他框架能力和服务治理能力都是统一的、通用的

  默认使用 JSON-RPC 协议,简单易用,并且能一定程度兼容老服务使用的 HTTP 协议

  有强契约、高性能、流式调用或跨语言需求的选择 gRPC 协议  

货拉拉服务化实践-为啥都爱“造*”?

  3.4. SDK 整体设计

  正如前面所提,SDK(Java) 的整体设计参考了 Dubbo 实现上的优秀设计,这里主要介绍一下 SPI 机制、服务自省和服务调用链

  首先 SPI 机制在 Dubbo 的基础上我们做了一些优化:

  新增 Internal SPI,标记仅仅支持框架本身进行扩展的 SPI,平衡扩展性与使用方法上的收敛

  优化自动包装显示使用 @Wrapper 注解,并提供 Ignore 能力

  优化自动装配显示使用 @Inject 注解  

货拉拉服务化实践-为啥都爱“造*”?

  其次是服务自省机制,解耦了传统臃肿的服务注册中心:通过 Consul 进行应用级服务发现,使用 Apollo 作为动态配置中心,控制面 Admin 接受服务元数据和各种事件上报充当元数据中心;同时服务自省涉及到的核心对象如下所示  

货拉拉服务化实践-为啥都爱“造*”?

  

货拉拉服务化实践-为啥都爱“造*”?

  最后对于 SDK 的服务核心调用链设计,如下图所示,每个核心对象都是框架扩展点,可以实现各种服务治理相关的述求,并提供给业务一定的扩展和自定义能力

  Cluster 集群,用于集群容错

  Directory 服务目录,用于服务发现

  Router 服务路由

  LoadBalance 负载均衡

  Filter 调用拦截器,可以同时作用于服务端和客户端

  ClusterFilter 客户端服务目录之前的调用拦截器  

货拉拉服务化实践-为啥都爱“造*”?

  3.5. 服务治理与CommandKey

  在解决了服务化的基本 RPC 能力之后,服务治理能力建设就是重中之重,货拉拉目前建设的治理能力有如下几类:

  服务容错:客户端 Fallback 机制、服务预热、无损上下线、异常处理机制等等

  服务路由:版本路由、灰度路由、多 AZ 路由等等

  服务流量治理:限流、熔断、降级、动态超时、动态日志、服务鉴权、服务验证等等

  另外对于服务流量治理的设计,我们在借鉴了 Sentinel “资源”的思路,设计了 CommandKey 机制,每一次流量都能转换为对应的 CommandKey,然后整个治理规则的配置和执行都可以与之进行关联和设计

  而且 CommandKey 的设计是结构化,与服务的实际结构相似,可以很自然的实现与服务结构一一对应、规则的层级覆盖等等功能,极大的简化了服务治理的管理和理解成本  

货拉拉服务化实践-为啥都爱“造*”?

   四、建设成果

  经过团队近两年的努力,截止目前(2022年中),货拉拉服务化建设的主要成果如下:  

货拉拉服务化实践-为啥都爱“造*”?

  接入服务数: 700+

  服务实例数: 5000+

  每日调用量: 百亿级

   五、后续演进

  目前货拉拉服务化框架建设初步满足了公司的业务发展和研发诉求,但仍然面临着众多挑战,后续我们将从多个方面持续完善服务化架构的建设与演进  

货拉拉服务化实践-为啥都爱“造*”?

  持续提升接入效率和使用体验:使用 API 调优, 控制面建设等等

  不断完善服务治理能力,覆盖服务从开发、测试一直到线上运行的各阶段对服务治理能力的需求

  Java SDK Proxyless 建设:结合 JavaAgent 技术和版本管理方案,一定程度解决 Java SDK 升级难以及版本碎片化的问题

  多语言(Golang)与 Runtime 建设:在满足公司部分团队逐渐增长的 Golang RPC 需求(多语言)的同时进行 Runtime 建设,下沉并逐步统一服务治理能力,也进一步解决多语言与版本升级难等问题