【QCon】精华内容上线92%,全面覆盖“人工智能+”的典型案例!>>> 了解详情
写点什么

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

  • 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:281812

评论

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

中移“校企行”专项行动即将开赛,你准备好了吗?

创业邦

学习笔记20210903(python测试类,贝叶斯定理)

姬翔

9月日更

OpenSSL国密爆出8.1分高危漏洞CVE-2021-3711

腾讯安全云鼎实验室

漏洞分析 openssl

🚄【Redis干货领域】从底层彻底吃透RDB原理(基础篇)

洛神灬殇

redis RDB 快照 rdb分析 9月日更

Erda 系列 Meetup「成都站」携手SOFAStack 和你聊聊云原生基础设施建设那点事儿

尔达Erda

开发者 云原生 活动 技术人

Android ABI

Changing Lin

9月日更

高可用 | Xenon 实现 MySQL 高可用架构 常用操作篇

RadonDB

MySQL 数据库 RadonDB

直播回顾 | 做一个有格局的工程师

鉴释

创业 工程师 CEO

PhxSQL设计与实现

OpenIM

BaikalDB在大规模数据场景的挑战和实践

百度开发者中心

最佳实践 方法论 存储系统

filecoin今日价格行情?fil币能涨到1万一枚吗?

区块链 分布式存储 fil币价格预测? filecoin挖矿 filecoin价格行情

谈 C++17 里的 Singleton 模式

hedzr

c++ 算法 设计模式 Singleton

网络攻防学习笔记 Day125

穿过生命散发芬芳

9月日更 互联网安全

LeetCode刷题704-简单-二分查找

ベ布小禅

9月日更

什么是低代码?低代码开发对企业有哪些特殊作用和优势?

优秀

低代码开发

未来10年,C++5个非常有前景的就业方向

hanaper

09. 深度学习携手大数据引领第三AI热潮--何为深度学习?

数据与智能

人工智能

如何使用 Redis 实现后台房间的数据管理?

ZEGO即构

redis 架构 音视频

开源应用中心|这款漂亮的国产开源论坛系统,放着不用太可惜!

开源软件

【得物技术】直播服务监控告警归因实践

得物技术

稳定性 服务 直播 告警 问题排查

Java + opencv 实现性别识别

张音乐

Java OpenCV 9月日更 性别识别

一文读懂 OceanBase 数据库的启动恢复代码解析

OceanBase 数据库

数据库 分布式事务 oceanbase OceanBase 开源

pnpm原理

法医

大前端 npm 9月日更

投资filecoin的最佳选择是?Filecoin挖矿的是如何月入上万的?

区块链 分布式存储 IPFS filecoin挖矿 投资filecoin

人能靠自驱来学习吗?

石云升

学习 9月日更

【网络安全】漏洞复现有多少种方式?

网络安全学海

php 网络安全 信息安全 WEB安全 安全漏洞

爱奇艺本地实时Cache方案

爱奇艺技术产品团队

分布式 高并发 cache

fil矿机组装教程是什么?fil矿机算力单位有哪些?fil矿机冗余是什么?

fil矿机组装教程是什么 fil矿机算力单位有哪些 fil矿机冗余是什么

Python代码阅读(第23篇):将变量名称转换为短横线连接式命名风格

Felix

Python 编程 Code Programing 阅读代码

“吾道一以贯之”:华为Petal One的新格局

脑极体

打一把游戏看一场病:当VR成为“数字新药”

脑极体

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