写点什么

多云架构落地设计和实施方案

  • 2019-10-11
  • 本文字数:7120 字

    阅读完需:约 23 分钟

多云架构落地设计和实施方案

“不要把鸡蛋放在同一个篮子里”是一条知名的商业准则,在云平台选择上,很多公司也遵循这样的准则。基于多云平台构筑“业务中台”并不是一件简单的事情,需要构建一种快速继承、可持续迭代的路径,帮助整体方案落地。本文以实际项目案例为例,分析项目的架构设计、实施步骤,以及多云架构面临的挑战和机遇。

总体思路

不同云厂商提供的云服务不尽相同,相同的云服务在功能、性能上也会有或多或少的差异。越是深度使用某个云厂商的云服务,越是难于迁移到其他云厂商。选择自己构建云服务,则技术门槛,维护成本很高。确定多云架构以后,首先需要在技术栈的选型上做好折中。一个基本的原则是通过业务架构的灵活性,去适配不同的云厂商,尽可能的使用云厂商提供的优秀特性,提升运行于该云平台的业务系统的可靠性,提升整体业务的竞争力。


上面的思路和一些客户常见的思路有显著差别。有些客户选择采用开源软件,搭建自己的 PaaS 平台;有些客户则完全采用云厂商的技术栈,开发两套业务系统。这两种方式是两个极端,前者开发和运维难度高,往往由于技术风险评估不足,项目无法如期交付,或者产品竞争力太弱,没有云厂商提供的服务好。后者则需要维护两套系统,代码重复度高,还会被云厂商完全绑定,失去谈判的筹码,业务发展灵活性降低。还有些客户期望云厂商提供足够兼容性的框架支持,在不改造现有业务系统的逻辑的情况下实现多云部署,云厂商这方面的努力通常由于客户系统的复杂性和多样性得不到落地。

开发框架选择和架构设计

开发框架设计是多云架构的核心,也是抽象程度最高的部分。华为云主推 ServiceComb 作为微服务框架,阿里云主推 HSF、Dubbo 作为微服务框架,其他国外的云厂商,多数选择 Spring Cloud 作为微服务框架,另外还有其他不同的语言和框架选择。虽然 mesher 等技术为多语言协同工作提供了良好的支持,但是为了最大限度的利用框架特性,帮助快速构建稳定可靠的业务系统,选择一个微服务开发框架仍然是必不可少的。


基于总体思路,多云架构期望在华为云上使用 ServiceComb 运行时,在阿里云上使用 HSF 运行时,并且支持 Spring Cloud 运行时。完成这个目标,首先需要对微服务运行框架的运行时和主要组成部分有所了解。对于多数中台系统,对于框架运行时的依赖,一般都是 RPC 框架,以及基于 RPC 框架做的服务治理能力,包括服务注册发现、熔断容错、限流等机制。将业务逻辑核心代码,与微服务框架能力进行解耦,是设计的第一步。



上面的图形展现了基本的逻辑架构。


业务核心:技术选型上使用 Spring、Spring Boot。ServiceComb、HSF 和 Spring Cloud 等微服务框架的技术底座,都可以基于 Spring、 Spring Boot 技术栈来构建。


在逻辑架构下,需要将微服务代码进行分层,包含下面三个主要目录:


  • microservice-api:定义微服务的接口。该目录包含接口定义(interface)、数据结构定义(models)。为了支持不同的微服务框架,对于接口定义和数据结构定义会有一定的要求。

  • microservice-service:业务逻辑实现代码。

  • microservice-endpoint-servicecomb:发布为 ServiceComb 的微服务项目。

  • microservice-endpoint-hsf:发布为 HSF 的微服务项目。

  • microservice-endpoint-springcloud:发布为 Spring Cloud 的微服务项目。


这个代码分层实施的核心关键是 api 设计,以及业务逻辑实现和服务发布解耦。api 设计需要满足不同的微服务框架的设计要求。这里涉及到 RPC 编解码的基础。RPC 编解码通常分为语言无关(跨平台)和语言相关(不跨平台)。比如 HSF、Dubbo 的缺省编解码是语言有关的,只能够支持 JAVA 程序之间的通信,ServiceComb 缺省采用 Jackson 编解码,或者 protobuffer 编解码,这两种方式都基于 Open API 2.0 进行定义,可以做到语言无关,Spring Cloud 则相对复杂一些,它是一种混合型的编码格式,可以通过灵活的序列化定制,满足多样性需要,也可以严格遵循 Jackson 和 Open API 标准。通常语言无关的编解码可以完全包含语言无关的编解码要求,因此 api 定义的时候,需要做到语言无关,才能够保证 api 能够在不同的微服务开发框架下得到最好的实现。基于本文是描述工程实践,不详细探讨关于跨平台设计的原理,下面列出一些接口设计的准则:


  • 使用简单的类型(比如 Integer, String, Boolean 等)定义参数或者返回值。或者使用包含简单类型的,符合 Java Bean 规范的 POJO Bean 定义参数或者返回值。

  • 尽可能不使用 interface, abstract class, 存在多个实现的基类,模板类作为参数或者返回值。

  • 不使用运行环境强相关的对象作为接口参数或者返回值。比如 HttpServletRequest,RpcContext,InvocationContext,ResonseEntity 等。


上面的原则从使用者的视角来看,是非常容易理解的。接口语义清晰,没有歧义,直接通过接口定义就能够理解接口的参数个数以及如何传递,不需要提供额外的文档或者查看源代码。有利于通过接口定义生成文档、swagger 定义。


在实际项目中,开发者需要理解 microservice-api 是微服务之间的接口定义,接口设计需要考虑数据的序列化和反序列化。这个不同于内部接口设计。为了降低业务实现逻辑的重复度,增强内聚性,内部接口设计会更多的使用抽象、继承进行逻辑封装。内部接口的数据结构,还会包含一些额外的控制逻辑。比如数据库访问层的数据结构,提供懒加载等机制,当访问到 getter 方法的时候,实际上调用的是代理对象的 getter 方法。因此,需要有一些数据结构转换逻辑,将内部的数据结构转换为外部接口的数据结构,以保持服务之间接口和内部接口的界限清晰,防止将内部数据结构作为参数或者返回值,导致内部信息泄露,造成不可预期的处理结果。


api 示例


`<pre data-lang="typescript">`<span><span>interface</span> LoginService {</span>``<span>  SessionInfo   login(<span>String</span> username, <span>String</span> password);</span>``<span>}</span>``<span><br></br></span>``<span><br></br></span>``<span><span>public</span> <span>class</span> SessionInfo {</span>``<span>  <span>private</span> <span>String</span>   sessionId;</span>``<span>  <span>private</span> <span>String</span>   username;</span>``<span>}</span>``  
复制代码


service 示例


`<pre data-lang="java">`<span><span>@Service</span></span>``<span><br></br></span>``<span><span>@Primary</span></span>``<span><br></br></span>``<span><span>public</span> <span><span>class</span> <span>LoginServiceImpl</span> <span>implements</span> <span>LoginService</span> </span>{</span>``<span><br></br></span>``<span>  <span><span>public</span> SessionInfo   <span>login</span><span>(String username, String password)</span> </span>{</span>``<span><br></br></span>``<span>         </span>``<span><br></br></span>``<span>  }</span>``<span><br></br></span>``<span>}</span>``
复制代码


ServiceComb Endpoint 示例


服务端:


`<pre data-lang="java">`<span><span>@RpcSchema</span>(schemaId = “LoginServiceEndpoint”)</span>``<span><span>public</span> <span><span>class</span> <span>LoginServiceEndpoint</span> <span>implements</span> <span>LoginService</span>   </span>{</span>``<span>  <span>@Autowired</span></span>``<span>  <span>private</span>   LoginService service;</span>``<span><br></br></span>``<span><br></br></span>``<span>  <span><span>public</span> SessionInfo   <span>login</span><span>(String username, String password)</span> </span>{</span>``<span>         <span>return</span>   service.login(username, password);</span>``<span>  }</span>``<span><br></br></span>``<span><br></br></span>``<span>}</span>``
复制代码


客户端:


`<pre data-lang="kotlin">`<span><span>@Bean</span></span>``<span><span>public</span> LoginService getLoginService() {</span>``<span>  <span>return</span>   Invoker.createProxy(SERVICE_NAME, <span>"LoginServiceEndpoint"</span>, LoginService.<span>class</span>);</span>``<span>}</span>``
复制代码


或者


`<pre data-lang="css">`<span>@<span>RpcReference</span>(<span>microserviceName</span>=<span>SERVICE_NAME</span>, schemaId=”LoginServiceEndpoint”)</span>``<span>private LoginService loginService;</span>``  
复制代码


HSF Endpoint 示例


服务端:


`<pre data-lang="java">`<span><span>@HSFProvider</span>(serviceInterface = LoginService.class,serviceVersion   = <span>"1.0.0"</span>)</span>``<span><span>public</span> <span><span>class</span> <span>LoginServiceEndpoint</span> <span>implements</span> <span>LoginService</span>   </span>{</span>``<span>  <span>@Autowired</span></span>``<span>  <span>private</span>   LoginService service;</span>``<span><br></br></span>``<span><br></br></span>``<span>  <span><span>public</span> SessionInfo   <span>login</span><span>(String username, String password)</span> </span>{</span>``<span>         <span>return</span>   service.login(username, password);</span>``<span>  }</span>``<span><br></br></span>``<span><br></br></span>``<span>}</span>``
复制代码


客户端:


`<pre data-lang="css">`<span>@<span>HSFConsumer</span></span>``<span>private LoginService loginService;</span>``<span><br></br></span>``  
复制代码


从上面的代码示例看,Endpoint 层需要做到尽可能逻辑简单,实现逻辑全部交给 Service 层,只包含接口声明,或者包含必要的数据结构转换逻辑。上面的方式演示了 RPC 的接口声明,还可以加上 REST 标签(Spring MVC,或者 JAX RS),来支持 REST 接口。

遗留系统的改造策略

大部分公司都会存在现有系统运行于某一个云上,把现有系统推倒重来,进行全新设计,通常不是一个好主意。遗留系统的改造需要遵循持续迭代,继承性改造的思路。


以阿里云系统改造为华为云系统为例,将原来基于 HSF 开发的微服务应用改造为基于 ServcieComb 开发的微服务应用。


按照前面的总体思路,首先需要将原来项目强依赖于 HSF 的代码部分分离出来,建立下面的目录结构:


  • microservice-api:定义微服务的接口。该目录包含接口定义(interface)、数据结构定义(models)。为了支持不同的微服务框架,对于接口定义和数据结构定义会有一定的要求。

  • microservice-service:业务逻辑实现代码。

  • microservice-endpoint-hsf:发布为 HSF 的微服务项目。


这个过程相对而言是简单的,不涉及任何业务逻辑的调整,只是代码目录结构的变化和 POM 依赖关系的调整。调整完成后,可以对现有功能进行简单自动化验证,保证项目自动化测试用例能够通过。调整后的项目功能和遗留系统是一致的,拥有一个始终无损的运行系统,对于功能比较、问题发现都是非常有帮助的。


项目调整后,就可以增加华为云的项目:


  • microservice-endpoint-servicecomb:发布为 ServiceComb 的微服务项目。


这个项目可以复制 microservice-endpoint-hsf,将 POM 依赖改为 ServiceComb 的内容,并将发布的接口(Endpoint)调整为 ServiceComb 的发布方式。


项目调整后,就可以一份代码构建出两个可执行 jar 包,两个 jar 包分别在华为云和阿里云部署。


这个过程的工作量需要通过原来代码本身的复杂度、api 接口的规范性、代码规模等进行评估。如果原来代码结构复杂,api 定义不规范,工作量会显著增加。对于良好组织的代码,这个过程则会非常容易。实际改造的一些项目,有些一天时间即可完成,有些则花费了一、两个月时间。获取到产品的业务结构、代码规模和通过简单的识别现有代码的技术栈和开发方式,能够帮助有效的评估工作量。


遗留系统改造的核心工作在于 microservice-api 中接口定义的梳理。由于 HSF 不是一个跨语言的开发框架,因此在接口定义里面使用的数据结构 ServiceComb 可能存在不支持的情况。采用一个支持跨语言的框架的接口定义作为基准(一般的,可以将接口定义通过 IDL、WSDL 或者 Open API 描述的接口定义,比如 gRPC、WebService、ServiceComb,),其他框架都实现这个接口,是微服务架构适配的核心关键。

数据库适配

业务系统都会使用数据库。各个云厂商支持的数据库形式多样,有 MySQL、Postgre、Oracle 等,此外还有分布式数据库,以及分库分表等特性,数据仓库支持等。虽然在连接池管理、事务管理、ORM 框架等方面有大量的开源开发框架,比如 dbcp、spring transaction、MyBatis 等,它们仍然没法覆盖所有的应用场景。适配多云架构对于业务代码开发有如下建议:


  1. 尽可能使用符合 ANSI SQL Standard 的 SQL 语句。比如 MySQL 在大小写、Column 引用、Limit、Group by 语句等方面都有自己的特殊用法。为了更好的适配多云数据库,应该避免使用特殊用法,除非对于业务价值具备明显的价值,而且没有对等的解决方案。

  2. 选用一个被广泛采用的 ORM 框架。比如 MyBatis、JPA 等。这些框架被广泛支持,对于多数据库支持得到比较充分的验证。在使用数据库有差异的地方,提供了非常良好的扩展机制。比如使用 MyBatis,可以使用 DatabaseProvider 接口,来屏蔽语法差异。在使用 JDBC 或者 JDBCTempate 拼接 URL 的地方,则可以通过接口的不同实现,返回不同的 SQL 语句。


    `<pre data-lang="bash">`<span><<span>if</span>   <span>test</span>=<span>"_databaseId == 'mysql' "</span>></span>``<span><span>limit</span>   </span>``<span></<span>if</span>></span>``<span><<span>if</span>   <span>test</span>=<span>"_databaseId == 'postgresql' "</span>></span>``<span><span>limit</span>   </span>``<span></<span>if</span>></span>``

复制代码


  1. 分布式数据库、分库分表、分析型数据库对于业务开发存在不同的限制。比如对于唯一索引使用的限制、对于分库分表键的修改限制等。对于这些场景,尽可能符合这些限制,调整业务实现方式,避免为了满足某个不重要的场景需要,陷入一些分布式场景无法解决的问题当中去,而无法适配其他云厂商的数据库。

缓存适配

各个厂商均支持 Redis 作为缓存,但是在 Redis 发展路径上,有不同的分支。一个分支是 Proxy 集群模式,一个分支是 Redis 的原生集群方式。Redis 提供的客户端 API 也存在两套,一个是 Jedis,一个是 JedisCluster,两套支持的命令集合不尽相同。比如 Proxy 集群模式能够非常好的支持所有的 Jedis 命令,而 Redis 的原生集群方式只支持 JedisCluster 命令。很多客户常用的 pipeline 指令,在 Redis 原生集群方式下不支持。


Proxy 集群能够更好的屏蔽底层服务的差异,在没有特殊需要的情况下,建议用户使用 Proxy 集群模式,云厂商需要通过 Proxy 集群模式提供对于 Jedis 不同指令的支持。用户也可以选择 Redis 的原生集群,这个在不同的云产生也都提供了支持,用户可以在业务场景上避免使用原生集群不支持的命令,这样就可以在多云环境上部署。


总结起来,缓存的多云支持的最佳实践:


及时升级 Client API 版本,使用比较新的 Client API,并且只使用 JedisCluster 提供的指令集合。

消息中间件适配

相对于数据库和缓存,消息中间件的适配的标准性更弱一点。虽然早期 JAVA 提出了 JMS 等标准,但是目前主流的消息中间件都没有提供 JMS 接口的实现。阿里的 RocketMQ、华为基于 Kafka 的 DMS 的接口均不一致。而且消息中间件的服务质量并不一样,比如在消息有序投递、重复投递等方面是通过消息中间件配置提供的,而不受客户端接口控制。有些客户的业务逻辑依赖于特定消息中间件的机制,因此需要对消息中间件使用的接口、接口行为进行抽象,分析其他云厂商的中间件能否提供对应的接口实现并满足对应的服务质量要求,有时候需要业务代码做一些补偿,以弥补不同中间件服务质量的差异。

其他中间件适配

其他中间件包括日志服务器、定时任务服务、对象存储服务等等。适配的总体思路一致,这里不详细描述里面的细节。

多云架构的挑战和建议

和做协议标准一样,多云架构除了给客户带来商业上的灵活性,还会促进业务系统软件架构的改善,提升整体的软件质量。因为多云架构需要对使用的技术点进行权衡,技术选型上更多采用相对中立和标准的方案,这些方案被广泛验证,相对于使用一些私有特性来说,更加稳定,同时可以促进项目开发人员之间的技术交流和能力继承。在给客户做多云架构落地的实践中,通过架构交流,帮助客户梳理了整体的架构演进方向,还发现了很多历史问题,对于客户的代码质量提升起到了很大作用。


多云架构也面临特别多的挑战。首先是短期内企业维护成本的增加和技术成本的增加,需要投入专家解决前期的架构适配问题,为系统持续演进搭好框架。多云系统运行,还会增加业务数据同步,不同云上系统如何进行协同的问题。只有当客户多云系统相对独立,没有数据共享和业务交互的场景才比较简单。多云系统还会影响到客户的交付效率,不同云的持续交付方式存在较大的差异,运维人员的体验不同,会降低日常的效率。


因此,多云架构更多的是准备“备胎”,客户的主要业务还是以单一云厂商提供。多云架构给客户对比不同云厂商的服务质量提供了非常准确的参考,客户能够更加准确的选择优质的供应商。


微服务框架层的抽象是做多云架构最难抽象的部分,也是多数开发者最难把握的一层。ServiceComb 微服务开发框架作为参考接口规范,可以非常好的适配到 HSF、Spring Cloud 等框架。在接口定义的时候,可以采用它作为基线,先测试 ServiceComb,再测试 HSF 和 Spring Cloud 等适配,对于新接口开发、历史系统迁移都是一个非常好的中间产品。


2019-10-11 10:282087

评论

发布
暂无评论
发现更多内容

开源让这位00后逆袭成为各类大奖收割者

OpenI启智社区

开源 OpenI启智社区 免费算力

游戏品类加速回暖,文娱内容持续火热——2022年IAA行业品类发展洞察系列报告·第三期

易观分析

游戏 报告 文娱

“链游版羊了个羊”Matching Game即将登陆Gate

股市老人

云数据库时代,DBA将走向何方?-v4

科技之光

聊聊Mybatis的类型转换注册类TypeHandlerRegistry

急需上岸的小谢

11月月更

三分建设,七分运营|用现代化安全运营应对数据安全风险

爱科技的水月

专利解析|多维建模结合AI识别商品特征的方法

元年技术洞察

AI 数字化转型

ONE 2.0应用场景解读 | 如何通过时序拓扑直观还原故障传导链路?

博睿数据

可观测性 应用场景 智能运维 博睿数据 ONE平台

从手动测试到自动测试,企业该如何选择?

飞算JavaAI开发助手

如何用JavaScripte和HTML 实现一整套的考试答题卡和成绩表

葡萄城技术团队

低代码开发是未来软件开发的主流模式

元年技术洞察

低代码 方舟PaaS

容器服务 ACK 结合 MSE Ingress,让集群入口流量管理更丰富、更容易

阿里巴巴云原生

阿里云 云原生 容器服务

一文了解 Go 的复合数据类型(数组、切片 Slice、Map)

陈明勇

Go golang go基础 11月月更

【Python 基础学习】-基础语法

度假的小鱼

11月月更 Python基础语法

数据监控预警系统,实现不同端信息推送

葡萄城技术团队

前端 数据可视化

帮助中心:培养客户自助服务意识的实用工具

Baklib

阿里 CTO 程立:Severless 化正加速重塑阿里应用架构和研发模式

阿里巴巴云原生

阿里云 Serverless 云原生

感恩每一位 RockStar!

StarRocks

数据库

折叠屏“世界杯”开哨,荣耀Magic Vs踢出关键一球

脑极体

第三章 TCP/IP ip地址概念与应用

我叫于豆豆吖.

11月月更

Linux 文件基本属性

芯动大师

chmod 11月月更 Linux文件属主 charp chown

《数据》杂志 | 浅析《网络安全法》修改对数据合规与隐私计算的影响

洞见科技

华为云区块链三大核心技术国际标准立项通过

科技怪授

成为数字游民,他们为何「All in Web3」?

One Block Community

程序员 web3 数字游民

如何使用Git进行代码托管

我是一个茶壶

git 代码托管 11月月更

广告业务存储神器:华为云GaussDB(for Redis)

秃头也爱科技

聊聊Mybatis的数据源之获取连接

急需上岸的小谢

11月月更

聊聊Mybatis的类型转换接口TypeHandler

急需上岸的小谢

11月月更

kitti数据集在3D目标检测中的入门(二)可视化详解

Studying_swz

人工智能 11月月更

软件架构的定义与分类

穿过生命散发芬芳

架构 11月月更

多云架构落地设计和实施方案_云原生_微服务蜂巢_InfoQ精选文章