1.webp

作者:高磊(世忠)

来源:云时代的寻路者

微服务的架构,大多可能只会想到我们把业务拆分成一个一个互相协作的服务“模块”,但是并非那么想当然地这么简单,这里我先不谈业务拆分的复杂性,因为它足够需要很多文章来分析了,我的后续文章会跟上,我这里先从执行架构角度来分析微服务的基础设施、通信以及应用框架的各种考量和思考。

首先我们先不看那种财大气粗的大公司的资源体量下的设计思路,假设我是一家创业型公司的CTO,老板只给我小于20台的物理机(当然这看起来也相当豪华了),而且业务持续快速的发展,IT部门的系统压力越来越大,CEO要求是对业务快速跟进、高压强下没有宕机风险等等,我们怎么作这个方案?

如果按照传统的方式:

2.webp

去做,比如上面的架构:

  • 所有东西在物理机上跑,在设计架构是不得不考虑现有的资源范围。
  • 依靠监控进行手动故障迁移,导致监控系统异常复杂。
  • 想象可以通过安装新的机器,得到性能拓展,但是均衡负载器实际成了最大的性能瓶颈,一旦崩溃,进群不可用。单机资源利用率不高!
  • 异地双活架构侵入性强,不得不在各种服务器节点上,尤其是集群治理以及数据库服务器上要保持同步策略。

明显我们不得不在这有限的机器中不停的取舍和决策,更加可怕的是物理的资源数量限制了我们架构的设计思路,这反过来是限制我们平台的发展的,如果添加新的东西,也会影响其他已有的部分,真是狗血!居然让硬件限制了我们的想象力!怎么办呐?另外还有硬件水平拓展无法自动管理、容灾治理等等的问题,还有服务自理的问题,应用部署的问题等等,如何搭建基础设施以便简化日后的升级、维护和获得高可用性?

先给一个整体架构:

3.webp

建议先不要做微服务(SAAS)这一层,先将物理机、网络、存储进行虚拟化,这一层提供IAAS的能力,这里推荐openstack来做这个事情,因为它非常成熟并且社区、文档齐全,但是它的设计是要涵盖所有业务场景,所以组件非常庞大,所以要用openstack的话,先要做减法并和其他组织内因素做平衡:

1.    成本:财务问题对任何组织来说都是头等大事,可能我的决策最终会死在这个上面,因为CEO可能会卡我的脖子。所以需要先设计一个通用性的方案,以此为基准设计其他类型的架构,比如计算型(给大数据用的)、业务型的等等,有了基准,成本核算相对容易。最后,一个很靠谱的建议就是不必使用openstack所有的组件(这一点请查看官方的文档),做出一个最小化、简单、可用的集群架构就可以了,先up上去,然后慢慢添加资源,成本则为可以控制的,并且是有依据的添加资源。

2.    上线时间:如果都把时间用在了搭建IAAS上,明显项目就要完蛋,因为老板给的时间是有要求的,所以也是上面提出来的要先建立最小化、简单、可用的集群的原因,并且长远来看,IAAS基础设施为减少后期在基础上花的成本是有很大好处的。

3.    受利益空间:将来还可以将业务产品云化,变成公众产品变现,这是一个挺好的想法,峰值底下的时段,让外部用户也可以接入他们的应用,变现对超出预算的硬件投资是个很好的补偿措施,说不定会变成一个新的生意,这是公司喜欢看到的结果。

4.    技术因素:

规划计算资源容量

当设计计算资源池时,有很多情形会影响到用户的设计决策。例如,和每种hypervisor相关的处理器、内存和存储仅仅是设计计算资源时考虑的一个因素。另外,将计算资源用户单一的池还是用户多个池也是有必要考虑的,我们建议用户设计将计算资源设计为资源池,实现按需使用。一个计算设计在多个池中分配资源,会使云中运行的应用资源利用的最为充分。每个独立的资源池应该被设计为特定的类型的实例或一组实例提供服务,设计多个资源池可帮助确保这样,当实例被调度到hypervisor节点,每个独立的节点资源将会被分配到最合适的可用的硬件。这就是常见的集装箱模式。

使用一致的硬件来设计资源池中的节点对装箱起到积极作用。选择硬件选择硬件节点当作计算资源池的一部分最好是拥有一样的处理器、内存以及存储类型。选择一致的硬件设计,将会在云的整个生命周期展现出更加容易部署、支持和维护的好处。

OpenStack支持配置资源超分配比例,即虚拟资源比实际的物理资源,目前支持CPU和内存。默认的CPU超分配比例是16:1,默认的内存超分配比例是1.5:1。在设计阶段就要想要是否决定调整此超分配比例,因为这会直接影响到用户计算节点的硬件布局。

但是最终的问题是,任何合理的设计在经过一段时间之后就变得不合理,原因是业务的发展以及外部用户使用频率、方式的变化导致容量规划退化了,所以需要做阶段性的计划,不断根据监控反馈的负荷增加物理硬件或者调整执行架构(物理拓扑)!

网络虚拟化规划:

也就是所谓的SDN,IAAS的成败大都集中于此,网络通则一切都通,网络虚拟化包括链路虚拟化、路由器虚拟化、交换机虚拟化等等(具体如何做虚拟化大家可以查看Openstack的具体做法即可),举一个现实中的例子,日前有个飞机场的路由器挂掉了,结果整个服务不可用,飞机全部停飞,这个损失可是够大的!

如果我们虚拟化了网络,并且在物理层提供多台路由器做冷备,那么当主路由器挂掉的时候,虚拟网络可以为我们切换到一台备机上,服务就不会间断!网络规划是个细活,我建议第一层网络作为外部API访问使用,第二层作为管理网络,用于管理员部署VM、监控等使用,第三层内部通信网络,作为内部节点和服务通信的网络。这样划分之后流量在专属网络中流淌,不会互相影响,也最大减少网络风暴的影响程度。

存储虚拟化规划:

传统的方式是一组机器负责数据库服务,一组服务应用服务器,但是数据库系统的底层是基于文件的,也就是说数据库服务器的根本结构是上层的数据库引擎+数据文件,如果数据文件都存储在这一组机器上,明显浪费了其他机器的资源,不过这只是一个例子哈,不要对号入座!

我们虚拟化存储,就可以磨平大集群的存储的文件存储策略,有存储虚拟化组件管理文件存储,文件可能存储在任何一个地方,上层设施不必关心,但是需要采用何种存储虚拟化方案,是需要考量的,比如LVM还是cinder,另外就是我们需要把计算和存储虚拟化在同样的物理节点上,形成所谓“超融合架构”,最大化物理机资源的利用!

综上我们会有这样一个适当可控的规模、简单、可用的IAAS架构出来:

 4.webp

  • 集群中一种是计算存储节点,另一种是网络控制节点,由于网络节点做了HA的高可用,所以任何一个计算节点宕机,只会影响其上面承载的虚拟机,不会影响其他节点,如果是一个可以预知的宕机,可以先将其上的虚拟机迁移到其他机器,这样就可以将对服务的影响降到最低。
  • 控制节点是主备模式,并且采用冷备的方式,但是数据库保持实时同步。因为这种私有云的架构对控制节点的依赖非常小,控制节点宕机,在不重启计算节点的OpenVswitch-Aagent的情况下,几乎不会影响虚拟机的正常运行。
  • 在网络的架构上,虚拟机网络通过网桥,采用Trunk模式,直接连接到交换机,具有较好的性能和极高的稳定性。管理网络是OpenStack各个组件通信的网络,包括镜像分发,虚拟机迁移等都是走这个网络。存储网络是虚拟机访问共享存储GlusterFS或Ceph的网络(超融合架构中,存储网络一般不独立使用)。
  • 超融合架构的目的,是最小化硬件集群的规模,充分利用硬件资源、在保证最小单点失败可能性下,保证IAAS建设的顺利实施,把存储层虚拟化成统一的资源池,并且存储离计算很“近”,文件和数据本地化的优势就是性能得到了很大的提升,有比与Hadoop的datanode会把数据本地化一样,提高了效率。

当然亲的openstack实施更需要谨慎从事,以上的架构建议,您可以在一个隔离的最小化硬件集群中作实验,一步一步验证,逐步建立一个恰当的IAAS,openstack的组件太多了,并且还在发展,所以给您的建立就是先要做减法,分步实施!

接着,我们有了IAAS之后,就相当于我之后很少考量物理层的问题了,它为我们的CAAS云保驾护航,我们所要做的是针对N个VM来考量(不可能是N个,越多可能越慢,这个需要一个规划的红线,主要是监控要跟得上去)我们的CAAS的架构,这样就极有很少的限制了微服务架构的发展!

我们在IAAS上加入容器云之后,就有这样的一个私有云能力平台架构了(有关CAAS应当具备哪些能力,后面有表格说明):

5.webp

接下来,我们先把CAS-k8s本体放在一旁,我们来看看应用架构,它也影响了我们对CAAS设计的决策,正所谓业务驱动技术,这一点有一定道理!

假设我们现在有这样一个传统应用架构(烟筒型应用):

情形一:单一块状设计和部署方式

 6.webp

情形二:比第一种好一些

 7.webp

因为单元功能被解耦了,但是依然是整体化单一化部署模式,无法很容易的拓展,性能弹性也比较差,开发缠绕也比较厉害,开发不得不得依赖于模块关系,整体系统无法做到性能和容灾和合理的分布。

情形三:比之前的都好

8.webp

但是,均衡负载挂了,怎么办?好,那就搞个热备!DB如何保持同步?好,再搞个同步复制,性能上不了怎么办?好,再搞个读写分离……,似乎没有问题?

单个服务器挂掉,还有另外几个!上了新服务器,手动改一下配置表…..

一看监控,同时只有一个服务器在提供工作,单进程Web服务器程序无法充分利用系统资源!成本越来越高!

情形四:SOA!

9.webp

这个接近很优的情况了,为什么哪?因为SOA将业务拆分出来,并且每个进程一个承载一个SOA服务,很像我们之后要做的微服务架构,每一个服务自治发展不会影响其他服务的设计、高度容错、提供弹性!

  • 单体出现问题,或者某个机器出现问题,因为不同物理节点部署不同的SOA服务,所以整体只是失效一小部分,大部分可用。
  • 每个SOA独立部署,可以同步开发。
  • SOA调用其他SOA,协同完成业务任务,这一点和微服务一样的。

但是与我们将来的微服架构有什么差别哪?微服务更加讲求较小的业务API粒度、内建环境自治、异构技术栈,可以说它是更加灵活的SOA:

  • 较小的粒度,但是也不能太小,调试和测试就比较麻烦了,要适度,这是业务拆分的难点。
  • 内建环境自治:这不是微服务必须的,但是有了它,我们就可以将一个服务背后的小体系结构打包,针对每个服务面对的不同业务场景在技术架构上进行针对性的设计,这就是为什么我们说Docker是微服务很好的宿主环境,但是微服务和Docker不一定是强绑定的关系,Docker有那么多好处,干嘛不用?
  • 异构技术栈:微服务推荐采用轻量级通信协议,比如REST,可以采用不同的技术栈所建立的微服务进行相互调用。

前面说了我们有个IAAS,用openStack实现了,我们决定paas用CAAS作为PAAS层,为我们的微服务业务框架提供基础环境(有关CAAS-k8s提供的分布式和服务治理能力,请参考后面的表),这样我们就有了一个完整的运行时治理环境,接下来可以考虑业务架构了,不过我这里要提醒一下:

  • 应该最先从业务场景考量基础架构
  • 确定基础架构之后,继续考量业务架构
  • 不断重复,直到把技术规划和业务建模精细化。

假设我们将来有一个微服务业务架构:

10.webp

一个框代表一个微服务,红线代表调用关系,和SOA很类似,我们看一下某一个微服务块中的治理结构:

11.webp

  • 提供一个服务抽象Gate,透明化一个微服务内的Docker集群,此Docker集群可能分布在不同host节点上,服务调用方并不知道这些,容灾自动迁移、服务治理全部透明化和自动化,docker有独天独后的条件,就是拉起来运行很快,销毁也很快,调用方几乎感觉不到。
  • 还应该把这种能力基础设施和应用服务本身的代码全部隔离起来
  • CAAS能力指标可以参考后面有关容器治理能力的表
  • 比SOA更加可封装不同的内部架构,更加自治,更加快速
  • 在CAAS SDN的架构下,通信更加高效和透明。

基础业务服务需要考量它的通用性,上层服务基本都是功能编排和流程编排,我们拿商品中心基础服务为例子,来说明基础服务的设计(这里就不画图了,这个具体业务细节相关,不过下面给出思路):

  • 不同商品可能会有不同处理流程,订单的模式也可能不同,所以首先要有一个定义流程的机制,比如JBPM
  • 然后在生成订单的节点绑定订单模板,以便不同流程下生成不同订单格式
  • 对商品打标,并在标上绑定某个流程,因为标签可以重用,流程必然可以重用。
  • 商品本身进行拓展模式的设计。
  • ….

总结出思路:

  • 为商品进行抽象,也就是把业务实体、流程、功能进行抽象和分离,提高它的独立性,减少因为业务变化引起的波动。
  • 考虑通用性,不同业务场景可能需要不同的商品schema,需要设计好拓展结构。
  • 商品实体被抽象出来之后,还要考虑如何维护它的数据一致性。
  • 设计好数据存储层面结构的设计,主要是共同字段和拓展机制的设计,可以适应各种场景的要求,这就是抽象的意义所在。

以上种种实现了基础服务的通用性。

我们再深入下去,看一下更细的基础服务实现架构:

1.因为上层业务服务应该向稳定的一方依赖过去,防止业务变化的扩散化,所以我们考量在基础服务上采用命令与查询分离的架构,具体如下:

 12.webp

写侧:

  • 将基础服务API抽象成命令模式,并由CommandBus负责路由到一个具体的CommandHandler进行业务逻辑处理(有关路由的实现设计,可以参考后面有关应用框架的设计思路),commandHandler负责进行业务逻辑处理。为了简化它,它只需要针对Domain实例进行setter操作,Domain从数据Service(请看图)“水化”出来的,带有业务数据,我们只需要在commandHandler中根据业务逻辑进行判断和对domain的setter(就是通过其setter设定数据的意思)操作即可,最后交给Repository。
  • Repository负责将Domain包装成Event类型,并将事件记录到EventStore(有可能是DB,也可以是文件,如果是文件最好保存在分布式存储中,比如HDFS中),同时通过EventPublisher发布事件,事件中心queue(一般采用kafka)会对订阅者进行分发。
  • 事件订阅者是一群EventHandler,它们可以根据Event类型以及包含的Domain数据进行解析和保存到业务数据库中,此时一个上层的业务功能可能会通过基础服务层激发一群EventHandler的工作,每个EventHandler有可能在不同的微服务Node上,提高了效率,并且提供事务群的机能,将一群EventHandler的写入活动用事务群包裹起来,保证一致性,所有激活的事件都有一个EventId关联起来。

读侧:业务数据查询引擎的设计:

记得前篇的一片文章分析过,我们如果不把查询做得更加通用,就会有一个尴尬的局面,1:1针对业务设计查询的话,那就是会有API膨胀的问题,您的架构这会朝不保昔,逐渐腐化,所以查询部分要设计成“逻辑后推”模式,做成查询引擎,基本结构如下:

13.webp

有人可能会问,为什么基础服务会这么设计?这里隐藏着好几个维度的考量:

    • 暴露给上层调用者的是data API,它们不需要关心数据的问题,也就是数据一致性、异常处理、数据完整性逻辑等等,上层微服务变成了单纯的功能、流程编排。
    • 写侧与读侧彻底分离,世界上的软件系统存在这样一个规律,就是写的少,读得多,一般读写比是10:1,甚至更大,具体读写比这个和业务场景有关,大家都清楚我们要做数据库的读写分离,也是这个原因,但是往往应用层没有分离,也造成热点集中了,所以也要分离。
    • 写侧通过记录两个库的数据来维持完整性,event库保存着所有对系统副作用的记录,这些记录由着统一的格式,可以认为是一种log,和mysql的binlog一样,它记录所有东西,另外一个业务库,它负责保存业务数据,它基本可以使用分布式数据库中间件,比如mycat来进行集群透明化处理,它会自动分库分表和读写分离,进一步保全数据和保证性能。
    • 读侧通过插件式查询引擎从业务数据库抽取上层想要的数据。
    • 上面的设计还带来了一些很好的副产品:

 

    • 通过监控系统返回的数据所指明的热点用户群,比如1%的热点用户流量隔离到一个独立的业务系统域当中去,我们可以在CAAS层(使用docker几乎是非常快的,这是它的很大的优点)使用诸如K8s名字空间的办法快速隔离出一个容器集群并拉取代码重建一个相同的业务功能系统,并从原先的系统中的event库中拉取event发送给新隔离出来的系统的事件中心,快速重建一个克隆的系统,并发热点用户流量隔离到这个系统中。保证热点系统分配更多的系统资源,保证热点不扩散。
    • 多版本回滚,当我们做错事情的时候,我们利用是event库中的某个版本的事件记录回滚业务数据库的数据,做到多版本推送更新和快速业务错误止血的作用。
    • 快速错误定位,一旦是写侧出问题,那么一定是写侧有bug,如果读侧有问题,那么一定是读侧有问题,再加上log,可以很容易定义问题所在!

因为有了基础数据微服务层,上层就不需要考虑太多有关数据的事情,专心关注功能和流程编排的设计和开发。

为了开发落地,需要设计和开发一种应用框架,提高应用或者业务开发效率,减少开发人员风格以及思想不一致导致的错误(这个进程内模型,以及进程间通信模型,采用SpringMVC拓展而来,如果有人对框架感兴趣,我有现成的源码,我自己开发的):

14.webp

  • 接入层:负责转换通信协议或者格式,负责暴露API提供给外界,负责接入安全、吐出和端相关的schema数据,做到多端适配。
  • bizhandler容器:容器提供根据版本+服务名称+API名称的路由机制,提供多版本混排的能力,定位具体API服务实例并通过反射调用方法提供服务。Biahandler是一个普通的POJO,它是按插件的方式插入到微内核的
  • 插入的方式采用SPI的方式,动态加载到内存中,并且自注册到本地版本注册表当中,我们可以通过一个微服务管理portal来部署和卸载某些bizhandler,做到业务插件化,升级动态化。
  • 服务集成器层:可以通过统一接口将内部和外部服务,比如推送、短信等等集成到Context通道中,bizhandler通过context获取服务,屏蔽不同服务差异性和实现细节,我们的基础层数据服务也是这样集成到上层微服务当中去的。
  • MEX:元数据交换机制,调用方与本方基于契约进行通信,方式是调用方先访问MEX节点获取json-sechma表示的接口契约,并解析去调用目标服务API,而它自己会进行缓存,而不必每次都要查询MEX,每次契约升级,比如依赖的一侧的接口定义变化了,就直接通知上游微服务更新接口契约缓存,达到解决因为接口升级而造成的兼容性问题。
  • 异常处理统一化处理:为bizhandler以及集成服务层提供统一的异常处理器,它们使用traceId关联,并发送到log模块以及接入层异常处理模块。
  • 日志统一化处理机构:采用客户端产生的traceId并使用AOP埋点异步发送日志到日志服务器(比如open-flacon)完成日志收集,日志收集侧使用traceId关联分析出来整个调用链以及RT情况。
  • 将基础数据层的数据包装成一个一个和领域业务相关的工具库,简化业务开发的难度。
  • 分布式缓存集成:可以透明化的进行数据缓存,而开发人员不必关心,失效策略是,为在查询数据时根据一定规则生成一个key,并写入缓存代理中,并在底层基础数据服务层的增加EventHandler,在写入事件发生时根据key更新或者失效缓存项。

有了以上这套东西,基本上可以解除业务变化快、修改频繁、业务开发人员容易出错等等的问题,不过世界没有银弹,必须要不断改进和沉淀才可以!

其实实际情况要设计一个电商是超级复杂的,所以上面的例子只是为了说明微服务业务架构的一种思路罢了,具体情况还得具体分析。

但是仅仅在应用级别上有一套框架,依然解决不了很多问题的,我们需要有一套业务服务治理的策略和运行时服务治理策略,其中运行时治理策略,诸如k8s这样的CAAS平台可以提供,这里我们着重讨论一下业务服务治理的话题:

  • 缓存失败重试:在接入层或者RPC层设计一套可配置的缓存重发机制,方调用失败它能够根据配置重试N次,失败后通知集群管理器重新调度。
  • 异步与同步依赖治理:在分布式配置系统上建立开关系统,其中建立一套异步和同步治理的开关,对同步调用的服务(只发不回的API)可以切换到异步,在特定场景优化链路调用T时间,但是需要是没有业务逻辑依赖的调用才可以这么做。
  • 组件依赖关系治理:依赖关系可视化,优化依赖关系拓扑。
  • 任务型计算并行度自动划分:根据让TASK自动记录自己的执行时间,并由框架计算其执行时间KPI,给任务定义打标,逐渐动态达到准确化,通过打标,区别长任务和短任务,短任务就地计算,长任务推入工作线程池执行计算,提供任务拓扑设计器,告知执行器哪些任务可以并行执行,哪些必须顺序执行。
  • 幂等性:提供幂等性支持组件,防止某些业务场景下,重复提交造成的问题,比如“重复发货”
  • 接入端防刷机制,如果同一个调用被重试了N回,并且成功,说明有刷单的嫌疑,对此用户进行标注,由接入层阻拦。
  • 对微服务进行优先级设定,高峰时期,低优先级服务进行优雅降级,由CAAS平台动态平衡资源,保持系统稳定。
  • 热点隔离:基于log以及KPI实时计算,进行热点隔离,并为它提供更多的系统资源(比如水平拓展更多的docker,这些docker将被自动分布在不同node上),保证整体服务稳定。
  • 建立业务逻辑以及规则的元数据化机制,提供类似git方式多版本推送的能力,当业务出现问题,快速回滚到上一个版本,止血。

15.webp

*有关元数据管理平台,之后的文章会有讲解

  • 内部通信采用RPC,不过你要关注它的码流、序列化效率、线程模型是否高效、地址透明化等问题,减少内部通信的延迟!

16.webp

那么最后我们需要把这些部署到k8s上,那么k8s本身的考量和设计就非常重要了,接下来我们看一下k8s本身:我们需要考量什么东西和作什么样的架构决策。

17.webp

k8s-运行时服务治理能力说明:

18.webp

640.webp

K8s-CAAS执行架构(物理拓扑)如下:

19.webp

以上是简化图,后续有关执行架构的文章会详细说明。

异地多活技术:

20.webp

有关运维、监控、客户端架构设计、安全方面、异地多活等话题,因为这些话题也是好大好大,所以我会在后续文章讲到。

总结

我们要把控全局,关键因素要心里有数,关键部分逐步验证,分步实施,不仅仅是要有大的架构规划,还要有细到应用框架的设计,保证全栈形成有机整体,而不是脚痛医脚,头痛一头,做到统一化治理,注意成本平衡,相信我们会越做越好!

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐