内容简介:早前发表关于“黑天鹅与云计算”系列文章,主要从云计算历史的上下文陈述云计算尤其是容器化技术出现后,对于证券领域建造拥抱改变、无惧变更的“反脆弱”系统的帮助,同时阐述软件开发者需要在技术架构观念、开发理念上作必要的调整 – 容器技术不是“黑科技”,需要用Cloud-native的架构模式去构建“云感知”的系统。本文则把基于容器技术进行交易系统开发过程中的一些实践总结,作进一步的分享 – 所有的理论源于实践。
作者介绍:谭成鑫,凡泰极客金融科技研发团队创始成员、主任工程师。北京航空航天大学计算机工学硕士,曾就职于百度后台高级研发工程师岗位,后加入某大型券商信息技术部,主要从事交易系统研发,对Docker、Rancher等技术在交易系统的应用作了很多探索尝试。目前在凡泰极客负责金融级通讯协同平台、自然语言处理(NLP)、智能机器人等技术的研究与应用。
大型券商日成交额高达千亿级,交易系统既需要满足动辄要求独占数千QPS的单个机构客户,又要服务数百万至千万级的散户;市场效应性的集体瞬间抛出或买入、毫秒必争的抢购涨停板或跌停前止损、行业需求的频繁功能发布及例如配合交易所技术升级或合规风控要求而近乎强制性的上线deadline、严格的行业监管,使每一笔交易的出错均有可能“牵一发而动全身”导致严重,这样的交易环境不仅要求交易系统具备高可靠、低延时、高吞吐的特点,而且支持自监控、自伸缩、自修复、自学习等“响应式”特性。通俗地讲,一个复杂的大型证券交易系统,应该是一个以守为攻,并在适应复杂、易变、“苛刻”的交易环境过程中,不断学习成长的技术体现。
一、传统的交易系统所面临的一些问题
单体式(monolithic)的交易系统,风险集中,难以水平扩容,以传统关系型数据库为核心使得架构难以松散耦合,无法实现精细化业务治理与运维以分散和降低风险。补丁式的升级,往往需要消耗一至数月的测试来确保补丁不对整体服务产生影响和上线环境的兼容。一方面常态性面临频繁变的行业、市场变化导致的上线要求,另一方面高昂的部署成本无法下降,令IT被动地“疲于奔命” – 无论IT人力资源还是技术资源,均无法做到scalable。
部署一套交易环境包含各种自动和手动环节,是一项同时要求技术经验和工作量的劳动,部署环节的复杂和对环境的兼容要求,使得低成本部署难以实现。传统的技术架构也无法轻易水平伸缩,系统从上线那一刻就交出了主动权;接手运维职责的运维团队,则往往只能是作被动监控,出现问题手工救火,既无法有效衡量甚至预测技术风险的工具、也无紧急故障出现时高效的灭火武器。
运维的被动,根源在于开发者所设计、开发的系统,往往只专注于业务功能,而没有把“可运维性”当作系统开发的第一属性,导致了系统交付后,运维团队处处被动。
二、容器技术促进了交易系统的微服务化
容器技术的出现,对解决很多问题有帮助,但是首先是促进“可运维性” – 通过合理的技术架构,系统的可运维能力可以得到根本提升,我们交付到运维伙伴们手里的系统,更加“智能”、更加“可控”。“微服务化”,是向这个方向努力的一个重要步骤。
网上关于微服务优劣的讨论有很多,包括Martin Fowler本人也就微服务的合理应用提出一些警告,详情不在此讨论。碎片化的服务一方面让精准运维、精准调优成为可能,另一方面也带来运维复杂度和各种耗损。然而我们平衡取舍之下,还是采用了微服务的架构,因为交易系统里的微服务,让功能点的粒度变小,如果有好的监控工具、部署工具,对于交易过程中各种市场行情导致的系统瓶颈更容易被定位、更精细的局部优化。虽然微服务本身不一定依赖于容器,但是容器作为微服务载体却让微服务变的非常的可管理、易运维。一定程度上,容器本身弥补了细碎服务的可维护性,让我们敢于运用微服务架构。
容器技术带来的一个巨大便利,是将服务本身和所依赖的运行环境打包起来,以统一标准的镜像作为服务的交付物,实现“一次构建,随处运行”,并结合容器各种编配技术(如:docker-compose),使得架构复杂的大规模应用服务实现一键部署变得简单、快速,重新部署一套服务接近“0”成本。
在容器技术出现之前,“大系统小做”、“复杂系统简单做”等架构设计思想更多地被运用于基础资源充足、研发团队庞大的大公司,对于中小规模的团队,难以承担划分出来的众多微服务所带来的成倍增加的维护成本。在容器技术出现之后,容器技术推动了计算资源的数字化,容器的轻量、秒级部署和丰富的编程API,解决的微服务架构一键便捷部署的问题,重新部署一整套服务的成本接近于“0”,通过“immutable infrastructure”解决了服务灰度发布、补丁升级所带来的问题,使”divide and conquer”思想更容易体现到系统架构设计当中,是微服务走入交易系统的重要原因。
总结我们的思路,是这样的:
•传统单体的交易系统,可运维性低,惧怕改变
•证券业的市场复杂性、合规风控监管需求变化、交易所等生态环境的技术与标准持续变化等等,导致证券交易系统本身就既需要像一切金融系统一样高度可靠高度稳定同时又像互联网系统一样密集迭代。这是一个矛盾到变态的特点,恐怕在一般IT系统中罕见
•解决这个矛盾,我们把下一代交易系统微服务化,因为我们需要精准监控、精准运维、粒度可控
•容器,在微服务化过程中对我们至关重要,因为它帮助我们解决细碎微服务的易部署、可管理、弹性伸缩
单体应用与微服务化应用这两个点之间的平衡如何拿捏,则是下文准备探讨的问题。
三、木桶的短板以及如何解决它
传统交易系统,其实也是由很多模块、服务组成,但这不等于它们就是“服务化”的、SOA的,我们姑且“武断”的把它们称之为单体应用 – 从架构设计角度看,它们内部各模块、环节之间的耦合度非常高,称之为“单体”架构,并不为过(哪怕它们的组件用不同语言、异构技术实现)。这样一个系统,也许可以用“铁桶一个“来比喻,当交易量(或其他任何指标)像水一样高涨、溢出的时候,几乎没有什么好的办法应对。
微服务化的交易系统,则可以比作一个由多块木板组成的木桶,服务的性能瓶颈适用木桶理论来描述:当外界的访问量上升至服务的性能瓶颈时,目前绝大多数的做法是找到服务的最短板,通过扩容来增加板的长度,当外界的访问量再次达到服务的性能瓶颈,再增加最短板的长度,如此反复。
这种扩容方式也并未完全适用于交易系统:(1)交易系统所面临的请求量突然剧增的情形会导致需要同时修补多块短板,难以定位出所有导致服务瓶颈的短板,并且多块短板同时修补的力度难以把握;(2)随着请求量的不断增加,不断地修补短板,会导致木桶结构越来越复杂,给服务带来了不确定性和运维难度的剧增。
我们的方案:绝对不能做铁桶,但是也不能靠一个由很多短板组成的木桶。我们要实现的,是“轻易低成本构建多个木桶”。
(1)业务级原语的载体 - 木桶
容器技术带来的服务一键、秒级、“0”成本部署,解决了微服务架构的一部分运维问题,让我们“轻易低成本”构建一个“木桶”。例如对于我们的内存交易系统,仅普通股票的委托功能就由46个docker镜像(image,不是instance)构成,通过docker、rancher的工具,这46种木板构成的木桶(装载“委托管理”功能的木桶),瞬间即成。
但是,如果系统功能扩展到基金、债券和其他各种金融衍生品的全资产交易,如果需要支持夜间委托、组合委托等各种复杂的策略交易,如果还支持多租户(交易客户或应用),如果每种容器镜像又要支持冗余高可用和分片… 试想一下一套由上千个容器组成的微服务架构交易系统运行在线上,当出现异常时,要在上千个服务中快速定位问题,修复问题服务,确保没有引入其他问题的情形与难度。这和在上千块木板组成的木桶里找到漏水的那块一样挑战。
单纯的微服务架构,并没有从专业领域的视角去梳理各个微服务的领域属性和彼此之间的领域关系,基于微服务之间的逻辑关系去对微服务进行物理上编配组合,显然很难达到交易系统成为一个反脆弱系统(见《黑天鹅与云计算》一文)的要求。交易系统作为一个服务于专业领域的系统,不仅应该对单一服务的代码进行领域建模,架构的设计应该同时基于业务。容器编配技术让我们可以将基于类和接口层面的领域建模(domain modeling)运用到容器化的服务层面,让局限于单一容器粒度的架构设计和运维能力上升至业务功能的粒度,给我们交易系统的架构设计和服务运维带来了一个新的视角,是交易系统在微服务架构基础上融入“combine and conquer”思想的体现,是我们交易系统在容器化技术弯道上超车的加速度。
我们采用rancher中提供的stack去描述我们的业务服务,如下图所示。
概括的说,用了容器编排技术的技术语义(例如Rancher Stack),对应于业务层面的语义(例如OMS – OrderManagement Service),形成一个装载某一强类型业务的“木桶”。一个复杂交易系统,本身由很多这样的“低成本构建”的木桶组成。
(2)完整的业务
大型券商的日成交额动辄高达千亿、大量用户不约而同在某一时刻集体抛出或买入、开盘和收盘瞬间买入或卖出,要求我们研发的交易系统必须对外部环境的变化是高度响应(reactive)的。基于容器及其编配技术的多容器一键部署容许我们以整套业务单元为粒度进行扩缩容,从而避免了随着业务服务内部各个服务的扩容导致业务服务容器规模越来越大所带来的不确定性和运维难度的提升。
而基于业务服务的扩容是指,当外界的请求量达到服务的性能瓶颈时,我们的做法是通过容器编排技术的一键部署,再轻易启动一套一样的服务组合(另一个新木桶),而不是补丁式地去更改业务服务的组成架构(这也符合“immutable infrastructure”理念)。对应到我们所使用的rancher当中,便是对同一个catalog再起一个stack。当订单服务接近性能瓶颈时,我们只需再起一个订单服务stack即可。
每一套业务服务组合单元实例(木桶)从启动到关闭作为一个不可变的整体运行,业务服务内部的容器设计仅需考虑fast-fail、heart-beat、retry等cloud native特性,保证了从业务服务层面去应对外界的变化是简单可控的。木桶成只构建、成只销毁,我们永远不去修补里面的短板。
(3)业务语义下的监控
传统交易系统的技术,基于最底层的技术指标去监控系统性能,例如我们的运维工程师必须时刻监控交易委托主站的CPU和内存消耗、关系型数据库所运行机器的压力状况等等,然后基于人脑、经验,去判断潜在的风险。
但显然这是低效的、不可靠的、后知后觉的。交易系统为用户提供的是业务服务,相应的监控系统应该从业务的层面去监控和预测各种已发生或可能发生的异常场景,监控系统只有站在用户的视角,具备和用户一样的甚至先于用户的业务服务感知,才能最直接有效地反应交易系统的问题。准确、全面的异常反馈和预测,是交易系统实施自适应、自修复操作的前提,是一个reactive系统不可或缺的一项能力。
业务异常场景的监控反应的是业务服务的异常,而每一项业务异常场景又可以从顶向下描述为一颗叶子节点为基础异常(比如程序直接上报的异常)的异常树。
上述的业务场景异常树中的每一个异常节点的监控通过例如riemann这样的工具实现,以容器化的riemann为粒度,自底向上进行容器编配,构建业务异常场景的监控拓扑,从监控数据的收集,到监控数据的计算和分析,最终输出一颗包含业务异常场景完整信息的监控结果树,并结合基于业务服务的扩容和修正,为交易系统的自动运维提供了可能性。
通过强大的、丰富的、无孔不入的监控,结合低成本的、敏捷快速的“木桶”构建手段,我们远在水快要溢出之前,即可迅速定位哪类木桶(对应于系统中的某类业务原语、或者说服务组合单元)需要复制部署,从而防范于未然。漏水的木桶?随它去吧,当市场上流动着数千亿上万亿的交易订单“洪水”,谁有空去修理旧木桶呢?