你的位置:首页 > ASP.net教程

[ASP.net教程]μSOA 架构


白杨 2016-04

baiy.cn

AIO vs. SOA

长久以来,服务器端的高层架构大体被区分为对立的两类:SOA(Service-oriented architecture)以及 AIO(All in one)。SOA 将一个完整的应用分割为相互独立的服务,每个服务提供一个单一标准功能(如:会话管理、交易评价、用户积分等等)。服务间通过 RPC、WebAPI 等 IPC 机制暴露功能接口,并以此相互通信,最终组合成一个完整的应用。

而 AIO 则相反,它将一个应用规约在一个独立的整体中,SOA 中的不同服务在 AIO 架构下呈现为不同的功能组件和模块。AIO应用的所有组件通常都运行在一个地址空间(通常是同一进程)内,所有组件的代码也常常放在同一个产品项目中一起维护。

AIO 的优势是部署简单,不需要分别部署多个服务,并为每个服务实现一套高可用集群。与此同时,由于可避免网络传输、内存拷贝等 IPC 通信所带来的大量开销,因此 AIO 架构的单点效率通常远高于 SOA。

另一方面,由于 AIO 架构中组件依赖性强,组件间经常知晓并相互依赖对方的实现细节,因此组件的可重用性及可替换性差,维护和扩展也较困难。特别是对于刚加入团队的新人来说, 面对包含了大量互相深度耦合之组件和模块的"巨型项目",常常需要花费大量努力、经历很多挫折并且犯很多错误才能真正接手。而即使对于老手来说,由于模块 间各自对对方实现细节错综复杂的依赖关系,也容易发生在修改了一个模块的功能后,莫名奇妙地影响到其它看起来毫不相干功能的情况。

与此相反,SOA 模型部署和配置复杂——现实中,一个大型应用常常被拆分为数百个相互独立的服务,《程序员》期刊中的一份公开发表的论文显示,某个国内 "彻底拥抱" SOA 的著名(中国排名前5)电商网站将他们的 Web 应用拆分成了一千多个服务。可以想象,在多活数据中心的高可用环境内部署成百上千个服务器集群,并且配置他们彼此间的协作关系是多大的工作量。最近的协程网络瘫痪事件也是因为上千个服务组成的庞大 SOA 架构导致故障恢复缓慢。

除了部署复杂以外,SOA 的另一个主要缺点就是低效——从逻辑流的角度看,几乎每次来自客户端的完整请求都需要依次流经多个服务后,才能产生最终结果并返回用户端。而请求(通过消息中间件)每"流经"一个服务都需要伴随多次网络 IO 和磁盘访问,多个请求可累计产生较高的网络时延,使用户请求的响应时间变得不可确定,用户体验变差,并额外消耗大量资源。

此外,无论是每个 Service 各自连接不同的 DBMS 还是它们分别接入同一个后端分布式 DBMS 系统,实现跨服务的分布式事务支持工作都要落到应用层开发者手中。而分布式事务(XA)本身的实现复杂度恐怕就以超过大部分普通应用了,更何况还需要为分布式事务加上高可靠和高可用保证——需要在单个数据切片上使用 Paxos/Raft 或主从+Arbiter之类的高可用、强一致性算法,同时在涉及多个数据切片的事务上使用 2PC/3PC 等算法来保证事务的原子性。因此 SOA 应用中的跨 Service 事务基本都只能退而求其次,做到最终一致性保证,即便如此,也需要增加大量的额外工作——在稍微复杂点的系统里,高可用,并能在指定时间内可靠收敛的最终一致性算法实现起来也不是那么容易。

与此同时,大部分 SOA 系统还经常需要使用消息中间件来实现消息分发服务。如果对消息中间件的可用性(部分节点故障不会影响正常使用)、可靠性(即使在部分节点故障时,也确保消 息不丢失、不重复、并严格有序)、功能性(如:发布/订阅模型、基于轮转的任务分发等)等方面有所要求的话,那么消息中间件本身也容易成为系统的瓶颈。

SOA 架构的优点在于其高内聚、低耦合的天然特性。仅通过事先约定的 IPC 接口对外提供服务,再配合服务间隔离(通常是在独立节点中)运行的特质,SOA 架构划分出了清晰的接口和功能边界,因此可以被非常容易地重用和替换(任何实现了兼容IPC接口的新服务都可替换已有的老服务)。

从软件工程和项目管理的视角来看,由于每个服务本身通常有足够高的内聚性,并且单个服务实现的功能也较独立,因此相对于 AIO 意大利面式的,相互交织的结构来说,SOA 的服务非常便于维护——负责某一服务的开发人员只需要看好自己这一亩三分地即可,只要保持服务对外提供的 API 没有发生不兼容的变化,就不需要担心修改代码、替换组件等工作会影响到其它"消费者"。

同时,由多个独立服务所组成的应用也更容易通过加入新服务和重新组合现有服务来进行功能变更和扩展。

 

μSOA 架构

在经历了大量实际项目中的权衡、思索和实践后,我逐步定义、实现和完善了能够兼两者之长的 "μSOA"(Micro SOA)架构。在 μSOA 架构中,独立运行的服务被替换成了支持动态插拔的跨平台功能插件(IPlugin);而插件则通过(并仅可通过)API Nexus 来动态地暴露(注册)和隐藏(注销)自身所提供的功能接口,同时也使用 API Nexus 来消费其它插件提供服务。

μSOA 完全继承了 SOA 架构高内聚、低耦合的优点,每个插件如独立的服务一样,有清晰的接口和边界,可容易地被替换和重用。在可维护性上,μSOA 也与 SOA 完全一致,每个插件都可以被单独地开发和维护,开发人员只需要管好自己维护的功能插件即可。通过加入新插件以及对现有功能插件的重新组合,甚至可比 SOA 模式更容易地对现有功能进行变更和扩展。

而在性能方面,由于所有功能插件都运行在同一个进程内,因此通过 API Nexus 的相互调用不需要任何网络 IO、磁盘访问和内存拷贝,也没有任何形式的其它 IPC 开销,因此其性能和效率均可与 AIO 架构保持在相同量级。

与此同时,μSOA 的部署与 AIO 同样简单——部署在单个节点即可使用,只需部署一个集群即可实现高可用和横向扩展。在配置方面也远比 SOA 简单,仅需要比 AIO 应用多配置一个待加载模块列表而已,并且这些配置也可通过各种配置管理产品来实现批量维护。简单的部署和配置过程不但简化了运营和维护工作,也大大方便了开发和测试环境的构建。

此外,μSOA 也在极大程度上避免了对消息中间件的依赖,取而代之的是通过 API Nexus 的直接API调用;或是在需要削峰填谷的场合中,使用由内存零拷贝和无锁算法高度优化的线程间消息队列。这一方面大大增加了吞吐,避免了延迟,另一方面也避免了部署和维护一个高可用的消息分发服务集群所带来的巨大工作量——μSOA 集群内的节点间协作和协调通信需求已被将至最低,对消息分发的可靠性、可用性和功能性都没有太高要求。在多数情况下,使用 Gossip Protocol 等去中心化的 P2P 协议即足以满足需要,有时甚至可以完全避免这种集群内的节点间通信。

从 μSOA 的角度看,也可以将 DBC 视作一种几乎所有服务器端应用都需要使用的基础功能插件,由于其常用性,因此他们被事先实现并加进了 libapidbc 中。由此,通过提供 IPlugin、API Nexus 以及 DBC 等几个关键组件,libapidbc 为 μSOA 架构奠定了良好的基础设施。

当然,μSOA 与 SOA 和 AIO 三者间并不是互斥的选择。在实际应用场景中,可以通过三者间的有机组合来达成最合理的设计。例如:对于视频转码等非常耗时并且不需要同步等待其完成并返回结果的异步操作来说,由于其绝大部分开销都耗费在了视频编解码计算上,因此将其作为插件加入其它 App Server 就完全没有必要,将它作为独立的服务,部署在配置了专用加速硬件的服务器集群上应该是更好的选择。

 

消息端口交换服务

白杨消息端口交换服务(BYPSS)设计用于单点支撑十亿量级端口、十万量级节点规模,每秒处理百万至千万量级消息的高可用、强一致、高性能分布式协调和消息交换服务。其中关键概念包括:

    • 连接(Connection):每个客户端(应用集群中的服务器)节点至少与端口交换服务保持一个 TCP 长连接。

    • 端口(Port):每个连接上可以注册任意多个消息端口,消息端口由一个 UTF-8 字符串描述,必须在全局范围内唯一,若其它客户端节点已注册了相同的消息端口,则端口注册失败。

端口交换服务对外提供的 API 原语包括:

    • 等待消息(WaitMsg):客户端集群中的每个节点均应保持一个到端口交换服务的 TCP 长连接,并调用此方法等待消息推送。此方法将当前客户端连接由消息发送连接升级为消息接收连接。

      每个节点号只能对应一个消息接收连接,若一个节点尝试同时发起两个消息接收连接,则较早的那个接收连接将被关闭,并且绑定到当前节点上的所有端口都将被注销。
       
    • 续租(Relet):若端口交换服务在指定的间隔内未收到来自某个消息接收连接的续租请求,则判定该节点已经下线,并释放所有属于该节点的端口。续租操作用来周期性地向端口交换服务提供心跳信号。
       
    • 注册端口(RegPort):连接成功建立后,客户端应向端口交换服务注册所有属于当前节点的消息端口。可以在一个端口注册请求中包含任意多个待注册端口,端口交换服务会返回所有注册失败(已被占用)的端口列表。调用者可以选择是否需要为注册失败的端口订阅端口注销通知。

      需要注意的是,每次调用 WaitMsg 重建消息接收连接后,都需要重新注册当前节点上的所有端口。
       
    • 注销端口(UnRegPort):注销数据当前节点的端口,可一次提交多个端口执行批量注销。
       
    • 消息发送(SendMsg):向指定的端口发送消息(BLOB),消息格式对交换服务透明。若指定的端口为空串,则向端口交换服务上的所有 节点广播此消息。若指定的端口不存在,则安静地丢弃该消息。
       
    • 端口查询(QueryPort):查询当前占用着指定端口的节点号,及其 IP 地址。此操作主要用于实现带故障检测的服务发现,消息投递时已自动执行了相应操作,故无需使用此方法。
       
    • 节点查询(QueryNode):查询指定节点的 IP 地址等信息。此操作主要用于实现带故障检测的节点解析。

 端口交换服务的客户端连接分为以下两类:

    • 消息接收连接(1:1):接收连接使用 WaitMsg 方法完成节点注册并等待消息推送,同时通过 Relet 接口保持属于该节点的所有端口被持续占用。客户端集群中的每个节点应当并且仅能够保持一个消息接收连接。此连接为长连接,由于连接中断重连后需要重新进行服务选举(注册端口),因此应尽可能一直保持该连接有效并及时完成续租。

    • 消息发送连接(1:N):所有未使用 WaitMsg API 升级的客户端连接均被视为发送连接,发送连接无需通过 Relet 保持心跳,仅使用 RegPort、UnRegPort、SendMsg 以及 QueryPort 等原语完成非推送类的客户端请求。集群中的每个服务器节点通常都会维护一个消息发送连接池,以方便各工作线程高效地与端口转发服务保持通信。

 与传统的分布式协调服务以及消息中间件产品相比,端口转发服务主要有以下特点:

    • 功能性:端口转发服务将标准的消息路由功能集成到了服务选举(注册端口)、服务发现(发送消息和查询端口信息) 、故障检测(续租超时)以及分布式锁(端口注册和注销通知)等分布式协调服务中。是带有分布式协调能力的高性能消息转发服务。通过 QueryPort 等接口,也可以将其单纯地当作带故障检测的服务选举和发现服务来使用。
       
    • 高并发、高性能:由 C/C++/汇编实现;为每个连接维护一个消息缓冲队列,将所有端口定义及待转发消息均保存在内存中(Full in-memory);主从节点间无任何数据复制和状态同步开销;信息的发送和接收均使用纯异步 IO 实现,因而可提供高并发和高吞吐的消息转发性能。
       
    • 可伸缩性:在单点性能遭遇瓶颈后,可通过级联上级端口交换服务来进行扩展(类似 IDC 接入、汇聚、核心等多层交换体系)。
       
    • 可用性:两秒内完成故障检测和主备切换的高可用保证,基于多数派的选举算法,避免由网络分区引起的脑裂问题。
       
    • 一致性:保证任意给定时间内,最多只有一个客户端节点可持有某一特定端口。不可能出现多个客户端节点同时成功注册和持有相同端口的情况。
       
    • 可靠性:所有发往未注册(不存在、已注销或已过期)端口的消息都将被安静地丢弃。系统保证所有发往已注册端口消息有序且不重复,但在极端情况下,可能发生消息丢失:
       
      • 端口交换服务宕机引起主从切换:此时所有在消息队列中排队的待转发消息均会丢失;所有已注册的客户端节点均需要重新注册; 所有已注册的端口(服务和锁)均需要重新进行选举/获取(注册)。
         
      • 客户端节点接收连接断开重连:消息接收连接断开或重连后,所有该客户端节点之前注册的端口均会失效并需重新注册。在接收连接断开到重连的时间窗口内,所有发往之前与该客户端节点绑定的,且尚未被其它节点重新注册的端口之消息均被丢弃。

可见,白杨消息端口转发服务本身是一个集成了故障检测、服务选举、服务发现和分布式锁等分布式协调功能的消息路由服务。它通过牺牲极端条件下的可靠性,在保证了 强一致、高可用、可伸缩(横向扩展)的前提下,实现了极高的性能和并发能力。

可以认为消息端口交换服务就是为 μSOA 架构量身定做的集群协调和消息分发服务。μSOA 的主要改进即:将在 SOA 中,每个用户请求均需要牵扯网络中的多个服务节点参与处理的模型改进为大部分用户请求仅需要同一个进程空间内的不同 BMOD 参与处理。

这样的改进除了便于部署和维护,以及大大降低请求处理延迟外,还有两个主要的优点:

    • 将 SOA 中,需要多个服务节点参与的分布式事务或分布式最终一致性问题简化成为了本地 ACID Transaction 问题(从应用视角来看是如此,对于分布式 DBS 来说,以 DB 视角看来,事务仍然可以是分布式的),这不仅极大地简化了分布式应用的复杂度,增强了分布式应用的一致性,也大大减少了节点间通信(由服务间的 IPC 通信变成了进程内的指针传递),提高了分布式应用的整体效率。
       
    • 全对等节点不仅便于部署和维护,还大大简化了分布式协作算法。同时由于对一致性要求较高的任务都已在同一个进程空间内完成,因此节点间通信不但大大减少,而且对消息中间件的可靠性也不再有过高的要求(通常消息丢失引起的不一致可简单地通过缓存超时或手动刷新来解决,可确保可靠收敛的最终一致性)。

在此前提下,消息端口交换服务以允许在极端情况下丢失少量未来得及转发的消息为代价,来避免磁盘写入、主从复制等低效模式,以提供极高效率。这对 μSOA 来说是一种非常合理的选择。

 

极端条件下的可靠性

传统的分布式协调服务通常使用 Paxos 或 Raft 之类基于多数派的强一致分布式算法实现,主要负责为应用提供一个高可用、强一致的分布式元数据 KV 访问服务。并以此为基础,提供分布式锁、消息分发、配置共享、角色选举、服务发现、故障检测等分布式协调服务。常见的分布式协调服务实现包括 Google Chubby(Paxos)、Apache ZooKeeper(Fast Paxos)、etcd(Raft)、Consul(Raft+Gossip)等。

Paxos、Raft 等分布式一致性算法的最大问题在于其极低的访问性能和极高的网络开销:对这些服务的每次访问,无论读写,都会产生两次在集群内部的广播——以投票的方式确 定本次访问经过多数派确认(读也需要如此,因为主节点需要确认本次操作发生时,自己仍拥有多数票支持,仍是集群的合法主节点)。

在实践中,虽可通过降低系统整体一致性或加入租期机制来优化读操作的效率,但其总体性能仍十分低下,并且对网络 IO 有很高的冲击:Google、Facebook、Twitter 等公司的历次重大事故中,很多都是由于发生网络分区或人为配置错误导致 Paxos、Raft 等算法疯狂广播消息,致使整个网络陷入广播风暴而瘫痪。

此外,由于 Paxos、Raft 等分布式一致性算法对网络 IO 的吞吐和延迟等方面均有较高要求,而连接多座数据中心机房(IDC)的互联网络通常又很难满足这些要求,因此导致依赖分布式协调算法的强一致(抗脑裂)多活 IDC 高可用集群架构难以以合理成本实现。作为实例:2015 年 5 月支付宝和 2013 年 7 月微信平台分别长时间无法访问的事故均是由于单个 IDC 因市政施工等原因下线,同时未能成功构建多活 IDC 架构,因此造成 IDC 单点依赖所导致的。

前文也已提到过:由于大部分采用 SOA 架构的产品需要依赖消息中间件来确保系统的最终一致性。因此对其可用性(部分节点故障不会影响正常使用)、可靠性(即使在部分节点故障时,也确保消息不丢 失、不重复、并严格有序)、功能性(如:发布/订阅模型、基于轮转的任务分发等)等方面均有较严格的要求。这就必然要用到高可用集群、节点间同步复制、数 据持久化等低效率、高维护成本的技术手段。因此消息分发服务也常常成为分布式系统中的一大主要瓶颈。

与 Paxos、Raft 等算法相比,BYPSS 同样提供了故障检测、服务选举、服务发现和分布式锁等分布式协调功能,以及相同等级的强一致性、高可用性和抗脑裂(Split Brain)能力。在消除了几乎全部网络广播和磁盘 IO 等高开销操作的同时,提供了数千、甚至上万倍于前者的访问性能和并发处理能力。 可在对网络吞吐和延迟等方面无附加要求的前提下,构建跨多个 IDC 的大规模分布式集群系统。

与各个常见的消息中间件相比,BYPSS提供了一骑绝尘的单点百万至千万条消息每秒的吞吐和路由能力——同样达到千百倍的性能提升,同时保证消息不重复和严格有序。

然而天下没有免费的午餐,特别是在分布式算法已经非常成熟的今天。在性能上拥有绝对优势的同时,BYPSS 必然也有其妥协及取舍——BYPSS 选择放弃极端(平均每年2次,并且大多由维护引起,控制在低谷时段,基于实际生产环境多年统计数据)情形下的可靠性,对分布式系统的具体影响包括以下两方面:

    • 对于分布式协调服务来说,这意味着每次发生 BYPSS 主节点故障掉线后,所有的已注册端口都会被强制失效,所有活动的端口都需要重新注册。

      例如:若分布式 Web 服务器集群以用户为最小调度单位,为每位已登陆用户注册一个消息端口。则当 BYPSS 主节点因故障掉线后,每个服务器节点都会得知自己持有的所有端口均已失效,并需要重新注册当前自己持有的所有活动(在线)用户。

      幸运的是,该操作可以被批量化地完成——通过批量端口注册接口,可在一次请求中同时提交多达数百万端口的注册和注销操作,从而大大提升了请求处理效率和网络利用率:在 2013 年出厂的至强处理器上(Haswell 2.0GHz),BYPSS 服务可实现每核(每线程)100 万端口/秒的处理速度。同时,得益于我方自主实现的并发散列表(每个 arena 都拥有专属的汇编优化用户态读者/写者高速锁),因此可通过简单地增加处理器核数来实现处理能力的线性扩展。

      具体来说,在 4 核处理器+千兆网卡环境下,BYPSS 可达成约每秒 400 万端口注册的处理能力;而在 48 核处理器+万兆网卡环境下,则可实现约每秒 4000 万端口注册的处理能力(测试时每个端口名称的长度均为 16 字节),网卡吞吐量和载荷比都接近饱和。再加上其发生概率极低,并且恢复时只需要随着对象的加载来逐步完成重新注册,因此对系统整体性能几乎不会产生什么波动。

      为了说明这个问题,考虑 10 亿用户同时在线的极端情形,即使应用程序为每个用户分别注册一个专用端口(例如:用来确定用户属主、完成消息分发等),那么在故障恢复后的第一秒内,也不可能出现"全球 10 亿用户心有灵犀地同时按下刷新按钮"的情况。相反,基于 Web 等网络应用的固有特性,这些在线用户通常要经过几分钟、几小时甚至更久才会逐步返回服务器(同时在线用户数=每秒并发请求数x用户平均思考时间)。即使按照比较严苛的 "1分钟内全部返回"(平均思考时间 1 分钟)来计算,BYPSS 服务每秒也仅需处理约 1600 万条端口注册请求。也就是说,一台配备了 16 核至强处理器和万兆网卡的入门级 1U PC Server 即可满足上述需求。

      作为对比实例:官方数据显示,淘宝网 2015 年双十一当天的日活用户数(DAU)为 1.8 亿,同时在线用户数峰值为 4500 万。由此可见,目前超大型站点瞬时并发用户数的最高峰值仍远低于前文描述的极端情况。即使再提高数十倍,BYPSS 也足可轻松支持。
       
    • 另一方面,对于消息路由和分发服务来说,这意味着每次发生 BYPSS 主节点故障掉线后,所有暂存在 BYPSS 服务器消息队列中,未及发出的待转发消息都将永久丢失。可喜的是,μSOA 架构不需要依赖消息中间件来实现跨服务的事务一致性。因此对消息投递的可靠性并无严格要求。

      意即:μSOA 架构中的消息丢失最多导致对应的用户请求完全失败,此时仍可保证数据的全局强一致性,绝不会出现 "成功一半" 之类的不一致问题。在绝大多数应用场景中,这样的保证已经足够——即使支付宝和四大行的网银应用也会偶尔发生操作失败的问题,这时只要资金等帐户数据未出现错误,那么稍候重试即可。

      此外,BYPSS 服务也通过高度优化的异步 IO,以及消息批量打包等技术有效降低了消息在服务器队列中的等待时间。具体来说,这种消息批量打包机制由消息推送和消息发送机制两方面组成:

      BYPSS 提供了消息批量发送接口,可在一次请求中同时提交数以百万计的消息发送操作,从而大大提升了消息处理效率和网络利用率。另一方面,BYPSS 服务器也实现了消息批量打包推送机制:若某节点发生消息浪涌,针对该节点的消息大量到达并堆积在服务器端消息队列中。则 BYPSS 服务器会自动开启批量消息推送模式——将大量消息打包成一次网络请求,批量推送至目的节点。

      通过上述的批量处理机制,BYPSS 服务可大大提升消息处理和网络利用效率,确保在大部分情况下,其服务器端消息队列基本为空,因此就进一步降低了其主服务器节点掉线时,发生消息丢失的概率。

      然而,虽然消息丢失的概率极低,并且 μSOA 架构先天就不怎么需要依赖消息中间件提供的可靠性保证。但仍然可能存在极少数对消息传递要求很高的情况。对于此类情况,可选择使用下列解决方案:
       
      • 自行实现回执和超时重传机制:消息发送方对指定端口发送消息,并等待接收该消息处理回执。若在指定时段内未收到回执,则重新发送请求。
         
      • 直接向消息端口的属主节点发起 RPC 请求:消息发送方通过端口查询命令获取该端口属主节点的IP地址等信息,并直接与该属主节点建立连接、提交请求并等待其返回处理结果。BYPSS 在此过程中仅担当服务选举和发现的角色,并不直接路由消息。对于视频推流和转码、深度学习等有大量数据流交换的节点间通信,也建议使用此方式,以免 BYPSS 成为 IO 瓶颈。
         
      • 使用第三方的可靠消息中间件产品:若需要保证可靠性的消息投递请求较多,规则也较复杂,也可单独搭建第三方的可靠消息分发集群来处理这部分请求。

综上所述,可以认为 BYPSS 服务就是为 μSOA 架构量身定做的集群协调和消息分发服务。BYPSS 和 μSOA 架构之间形成了扬长避短的互补关系:BYPSS 以极端条件下系统整体性能的轻微波动为代价,极大提升了系统的总体性能表现。适合用来实现高效率、高可用、高可靠、强一致的 μSOA 架构分布式系统。

 

BYPSS特性总结

BYPSS 和基于 Paxos、Raft 等传统分布式一致性算法的分布式协调产品特性对比如下:

特性BYPSS ZooKeeper、Consul、etcd…
可用性高可用,支持多活 IDC高可用,支持多活 IDC
一致性强一致,主节点通过多数派选举强一致,多副本复制
并发性 千万量级并发连接,可支持数十万并发节点不超过 5000 节点
容量每 10GB 内存可支持约 1 亿消息端口;每 1TB 内存可支持约 100 亿消息端口;两级并发散列表结构确保容量可线性扩展至 PB 级。通常最高支持数万 KV 对。开启了变更通知时则更少。
延迟相同 IDC 内每次请求延迟在亚毫秒级(阿里云中实测为 0.5ms);相同区域内的不同 IDC 间每次请求延迟在毫秒级(阿里云环境实测 2ms)。由于每次请求需要两次广播和多次磁盘 IO,因此相同 IDC 中的每操作延迟在十几毫秒左右;不同 IDC 间的延迟则更长(详见下文)。
性能每 1Gbps 网络带宽可支持约 400 万次/秒的端口注册和注销操作。在 2013 年出厂的入门级至强处理器上,每核心可支持约 100 万次/秒的上述端口操作。性能可通过增加带宽和处理器核心数量线性扩展。 算法本身的特性决定了无法支持批量操作,不到 100 次每秒的请求性能(由于每个原子操作都需要两次广播和多次磁盘 IO,因此支持批量操作毫无意义,详见下文)。
网络利用率 高:服务器端和客户端均具备端口注册、端口注销、消息发送的批量打包能力,网络载荷比可接近 100%。低:每请求一个独立包(TCP Segment、IP Packet、Network Frame)。
可伸缩性有:可通过级联的方式进行横向扩展。 无:集群中的节点越多(因为广播和磁盘IO的范围更大)性能反而越差。
分区容忍 无多数派分区时系统下线,但不会产生广播风暴。 无多数派分区时系统下线,有可能产生广播风暴引发进一步网络故障。
消息分发 有,高性能,客户端和服务器均包含了消息的批量自动打包支持。无。
配置管理无,BYPSS 认为配置类数据应交由 Redis、MySQL、MongoDB 等专门的产品来维护和管理。当然,这些 CMDB 的主从选举等分布式协调工作仍可由 BYPSS 来完成。有,可当作简单的 CMDB 来使用,这种功能和职责上的混淆不清进一步劣化了产品的容量和性能。
故障恢复 需要重新生成状态机,但可以数千万至数亿端口/秒的性能完成。实际使用中几无波动。不需要重新生成状态机。

上述比较中,延迟和性能两项主要针对写操作。这是因为在常见的分布式协调任务中,几乎全部有意义的操作都是写操作。例如:

操作对服务协调来说对分布式锁来说
端口注册成功:服务选举成功,成为该服务的属主。
失败:成功查询到该服务的当前属主。
成功:上锁成功。
失败:上锁失败,同时返回锁的当前属主。
端口注销放弃服务所有权。释放锁。
注销通知服务已下线,可更新本地查询缓存或参与服务竞选。锁已释放,可重新开始尝试上锁。

上表中,BYPSS 的端口注册对应 ZooKeeper 等传统分布式产品中的“写/创建KV对”;端口注销对应“删除KV对”;注销通知则对应“变更通知”服务。

由此可见,为了发挥最高效率,在生产环境中通常不会使用单纯的查询等只读操作。而是将查询操作隐含在端口注册等写请求中,请求成功则当前节点自身成为属主;注册失败自然会返回请求服务的当前属主,因此变相完成了属主查询( 服务发现/名称解析)等读操作。

需要注意的是,就算是端口注册等写操作失败,其实还是会伴随一个成功的写操作。因为仍然要将发起请求的当前节点加入到指定条目的变更通知列表中,以便在端口注销等变更事件发生时,向各个感兴趣的节点推送通知消息。 因此写操作的性能差异极大地影响了现实产品的实际表现。


 

 

注:本文节选自《白杨应用支撑平台》中的“5.4 μSOA 基础库-libapidbc”小节。