欢迎访问网易云社区,了解更多网易技术产品运营经验。html
蜂巢计费系统为网易云计算基础服务(网易蜂巢)提供总体的计费服务,业务范围涵盖完整的产品售卖流程,包含订价、订单、支付、计费、结算、优惠、帐单等主体功能,支持十几种不一样产品的售卖,产品形态上贯穿了IaaS、PaaS和SaaS类别。同时,计费方式还提供了了按量、包年包月、资源包等多种方式。该项目的业务范围之广,玩法种类之多,数据要求之严注定了它将成为一个烫手的山芋,并且仍是一个吃力不讨好的工做。前端
该项目在人员上已经几经易手,就我所知,已经换过两拨完整的开发和测试团队了,并且已经所有离职。不得不说,该项目已经变得使人谈之色变,让人敬而远之。在这样的背景下,后期接手的开发和QA不得不硬着头皮上,踩着雷过河,当心翼翼的应对着不断涌来的业务需求。随之而来的是高居不下的bug率,愈来愈难以维护的代码,没法扩展的架构问题,咱们开始意识到这样下去是不行的。因而咱们从8月份开始了漫漫的架构升级之路。git
在咱们开始优化架构以前,咱们从新梳理了计费系统完整的业务,获得了以下图所示的业务领域:github
梳理之后发现,计费系统承载了太多非计费的业务,包含订单、帐单、结算和代金券等,这些业务代码散落在各处,没有严格地业务边界划分,而是“奇迹般”的融合在了一个工程里面。形成这个局面的缘由在于计费系统第一版设计时,根本没有考虑到这些问题,固然也不可能考虑到,而在后面逐步地迭代过程当中,也未能去及时地调整架构,架构腐化不是一天内完成的。固然,这方面有部分技术的缘由,也有部分人为的缘由所在,由于当时负责计费系统的开发就只有一人,仍是刚毕业的同窗。目前看来,也是难为这位同窗了。web
技术债务的问题不是小事,千里之堤毁于蚁穴。既然咱们找到了问题的症结所在,那么解决的方式也就显而易见了,一个字:拆!咱们分析了全部的业务,订单是最大也是最复杂的一个业务,而结算和帐单考虑到后期有可能迁移到云支付团队,咱们决定优先把订单系统拆分出去!数据库
订单拆分提及来容易,作起来难。套用一句业界常说的话,就是开着飞机换轮胎。由于在咱们拆分的同时,不断地有新的业务需求进来,还有一些bug须要处理,因此不太可能让咱们专门进行拆分的工做。所以,为了避免影响正常的业务迭代,咱们决定拉出独立分支进行开发。咱们分出两人专门处理拆分的工做。缓存
为了最小化风险,订单拆分咱们分了两步进行:一,模块独立;二:系统独立。网络
模块独立是将订单的代码首先在工程内部独立出来,咱们采用独立Module的形式,将订单独立成了一个Order的模块。它拥有彻底独立的服务层、业务层以及持久化层。其余模块能够依赖Order,而Order不能依赖除公共模块外的其余业务模块。总体的模块划分以下图所示。模块的拆分过程当中咱们也发现了原先不少不合理的地方,例如:其余服务直接操做订单的持久化层(DAO)、模块直接依赖关系混乱、Service所在的Pacakge不合理、存在大量无用的代码和逻辑、随意的命名等。咱们边拆分边重构,虽然进度比预期要缓慢一些,但总体上在向着合理的方向进行。架构
模块独立的过程当中咱们遇到了业务层级关系的问题。因为订单模块再也不依赖于其余业务模块,而又有一些业务逻辑是由订单触发的,须要在计费模块完成,咱们又不能直接调用计费模块的Service。针对这个问题,咱们采用了领域事件的方式来解耦,简单来讲就是订单经过发布事件的方式来与其余模块进行通讯,当时实现的代码其实也至关简单。框架
咱们并无独立拆分web层,由于系统尚未独立,web层做为统一的打包入口也承载着订单的流量。并且,Controller层的逻辑相对比较简单,彻底能够在系统独立时再作。经过你们的努力,8月底订单已独立模块的方式上线了,一切正常。
模块拆分完成后,仅接着就是系统独立,此时咱们须要将订单系统独立部署。这里一个关键的问题是,独立部署意味着单独提供服务,而依赖订单系统的业务方很是之多,包含前端、主站、大部分的PaaS业务和计费,都有须要直接依赖订单接口的地方,冒然独立风险很大。针对这个问题,咱们采用使用haproxy七层转发代理来将流量分发到不一样的vip来解决。虽然,在上线过程当中遇到了一些坎坷,但最终仍是成功了。如今看来这个选择是很是对的,由于这样能够在业务方无感知的状况下平滑升级。但长远来看,最终咱们仍是以独立的vip对外保留服务。
订单和计费直接咱们采用RabbitMQ来完成主体通讯,关于采用MQ仍是HTTP调用咱们内部还进行了一番争论。之因此最终仍是采用MQ来进行通讯,是由于咱们发现不少业务流程并不须要计费系统当即响应(大部分流程都是订单触发的),也就是咱们常说的弱依赖。另外,职责上计费系统的响应的质量也不该影响到订单的主体流程,举个例子:用户支付了一个云主机的订单,若是计费系统此时没法响应,业务上相对来讲能够接受过一小会儿计费再处理,而不是把订单直接退款给用户。MQ的引入在技术和职责层面都将订单和计费分的更开了。固然,强依赖的服务是咱们没法避免的,其中之一就是结算模块还留在计费中,订单须要经过接口调用结算服务来完成支付。
前期,咱们在模块独立时采用事件解耦的方式,在此时也得到了收获。咱们经过一个统一的转化层,将那些事件直接转化层RabbitMQ能够识别的消息,这样代码的改造工做就大大减小了。
系统独立后一个直接的表象就是每一个系统的代码行数大大下降了。独立前,总体的代码行数已经达到了12W行以上(包含配置文件),独立后,计费系统下降到了10W如下,订单维持在4W如下。代码行数的下降将直接提升系统的可维护性。我的认为若是一个工程里的代码超过10W行,那么维护性将大大下降,除非是那些有着严格自律意识的团队,不然,我建议仍是尽可能下降代码行数。
通过你们一个月的努力,订单系统终于已独立的姿态提供服务了。过程很艰辛,可是收获良多。
订单独立后,一个直接的好处就是咱们能独立的思考问题了,这在之前是很难作到的一件事情,由于你们不得不当心翼翼的处理那些依赖,作事会畏手畏脚的。另一个好处就是,咱们的工做能够有侧重点的进行了。订单业务能够说是产品最为关注的业务,也是计费对外暴露的主要入口。 下图就是咱们在拆分后规划订单的业务架构,你们对后期的订单规划充满期待。
公有云产商面临的一大挑战就是多Region环境的支持。普通的互联网行业出于高可用的考虑,每每会把核心系统部署到多个机房,而后根据本身的实际应用场景选择冷备、双活甚至三活。咱们常常听到的“两地三中心”、“三地五中心”等等高大上的名词就是代多机房高可用的缩影。这些行业作多机房部署的主要目的是为了提升系统的可用性,不是其业务的必须属性。换句话说,他们不作多机房部署也能够,作了固然更好。而公有云产商不同,多Region部署就是其行业属性之一。若是哪一个云产商不提供多region产品的支持,那么它确定是不完整的。不得不认可,咱们在这方面的经验是比较欠缺的,在多Region的支持上走了一些弯路。
今年上半年的时候,蜂巢开始计划启动北京Region,预计年中交付,当时对咱们横向业务提出了很大地技术挑战。一是在于横向系统设计之初并无考虑到对Region环境的支持,咱们很被动;二是咱们并无跨Region系统设计的经验,咱们很着急。计费系统面临的问题更加严重,由于它对数据的一致性要求更高,并且出错地影响范围也更大。并且当时计费的技术债务已经很高了,产品的需求列表也拍了很长,套用一句很形象的话说,“留给咱们的时间很少了”。
在这种状况下,咱们“胆战心惊”的给出了初版的多Region设计方案,主体架构以下所示:
由于当时计费系统尚未拆分,全部的业务都在一个系统中完成的,就是咱们常说的“大泥球”系统。这种状况下咱们很难作到多Region部署,订单和帐单其实只有在一个Region部署就能够了,而计费的数据采集和请求分发是要下沉到各个Region的,而计算过程能够集中完成。采用"双主"同步复制的方案实则是无奈之举。数据库的同步只能基于实例级别,而没法细分到表,咱们各Region中计费数据库中存在资源的计量表,这个数据须要同步到杭州Region来完成。为了不“脑裂”的问题,咱们特别将该表的主键采用UUID的形式。存量表由于没法作大规模修改,咱们经过限制北京MySQL用户的权限来避免写入和修改全局表。
这个设计很糟糕,可是当时的条件限制,咱们也拿不出更好的设计了。虽然上线的过程有些曲折,当这个架构仍是成功运行了,这是令咱们最为欣慰的事情。由于为了适配这个架构,团队的小伙伴作了不少工做。不能否认,这个架构存在诸多弊端,其中最大的隐患就在于数据库的“双主”同步,这就像一颗随时会爆的炸弹萦绕在咱们心头。当时专线尚未搭建好,全部的流量均经过外网隧道代理,糟糕的网络质量无疑放大了这个风险。为此,DBA们向咱们吐槽了很久,幸亏咱们抗打击能力很强。
在作完双Region的支持之后,计费团队就继续作产品需求了,由于架构调整致使需求列表已经很长了。并且当时也说的是,短时间内(至少今年)不会再有第三个Region了,咱们也想着快点作完,多花点精力投入到重构中。可是计划赶不上变化,9月底咱们被通知到第三个Region来了,并且已经被提升到第一优先级支持了。
有了初版双Region的经验,这一次咱们淡定了不少。固然,咱们不可能在沿用初版的设计了,由于DBA就会跟咱们拼命的。回过头来梳理多Region支持面临的问题时,我发现一开始咱们就本身给本身挖了一个坑,而后往里面跳。横向支撑系统显然都须要对全部Region提供支持,但这并不表明其须要在各个Region内部署(我还与团队其余的小伙伴分享了这方面的想法,网上应该还能找到这一次分享的ppt——《跨Region实践初探》)。由于公有云产商常常会提供多个Region的服务,有得甚至达到几十个Region,若是横向支持系统每一个Region都要全量部署的话,那么咱们花在运维上的精力就能够拖垮咱们,更不要说还有最为困难的数据的一致性问题。
其实多Region的支持的问题咱们总结出主要表如今一下两个方面,一是应用层面的接口互通;二是底层数据库的同步。
咱们先说底层数据库的同步,对计费系统而言,数据的一致性是相当重要的,但多机房部署是在挑战CAP定律。是否是就没有了这样的数据库方案了呢,有,那就是Google的Spanner,号称能够在全球作到强一致的数据库。可是咱们没有这样的数据库。其实咱们也考虑使用NoSQL数据库——Cassandra,可是这个数据库运维起来太复杂,咱们也没有这方面的经验,也就放弃了。仍是回归到MySQL,受限于传统关系型数据库在扩展性方面的问题,咱们不可能把整个库在各个Region都同步一份。可是计费原始数据又必须在各个Region内收集,因而咱们决定——拆,把计费拆层两个部分,分为bill-agent(数据采集)和bill-central(数据计算)两个部分。
Bill-Agent负责Region内日志的收集和简单聚合。
Bill-Central负责日志收集外的全局事务处理。
经过这样的拆分,架构就清晰多了。再多加Region,咱们只须要部署Bill-Agent就能够了。Bill-Agent将处理过的计费数据写入本地库的一张资源表,利用NDC(马进在网上分享过关于这个中间件的介绍)将资源表单向同步到Bill-Central的中央库,而后Bill-Central统一在对计费数据进行处理。有意思的是,这张资源表就是咱们在初版设计中新建的资源表,由于咱们将主键修改成UUID,全部使用NDC同步表的方案是至关顺利的。固然,NDC在咱们其余项目的跨Region支持上也发挥了重要做用,好比:跨机房缓存更新的问题。这一版的数据库方案在技术评审时你们都比较满意,DBA也确定了咱们的方案。
如今再来看跨Region调用的问题。在多Region的横向系统中,咱们发现或多或少的存在着机房间的接口调用问题。这些问题有多是某些Region的库不能写须要路由到主库来写致使的,也有多是全局缓存的问题,还有就是Global业务向Region内服务发送指令。计费属于最后一种场景,咱们有一些业务场景须要由杭州Region触发,而后调用各个Region内的服务的接口。在初版的实现中,计费系统本身实现了跨Region代理部分,可是实现的不是很好,代码的可维护性比较差,加剧了调试的难度。这一版的设计中,咱们决定把跨Region接口代理单独拿出来从新作,结合多Region的应用场景,而后封装一些非功能性的特性,这就成了后面咱们很重要的一个组件——RegionProxy。
RegionProxy最开始是为了解决跨Region调用的非功能性问题,简化应用系统处理的成本。可是设计上经历了比较大的调整。最开始的设计咱们是但愿Region内全部跨Region的HTTP调用都能经过RegionProxy来代理,RegionProxy之间可以发现对方而且相互通讯,那么Region内的应用系统就只须要与本Region的RegionProxy通讯就能够调到任意一个Region的应用系统了。可是在方案评审的过程当中,咱们发现若是都用RegionProxy代理,可能会致使跨Region调用多出一跳或者两跳,调试可能会比较困难。后来,咱们放弃了这个方案。再后来,咱们发现ServiceMesh的方案和咱们最初RegionProxy的方案是十分类似的。
在RegionProxy的设计上咱们进行了简化处理,咱们将全部Region的业务系统录入到一个全局的配置中心(咱们本身开发的ConfigCenter)中,而后经过一个本身开发的一个HttpProxy的Java库来与ConfigCenter通讯来完成跨Region的调用。这样作的好处就是使用方用起来比较轻量,可是在网络连通性方面咱们须要与全部Region的系统作到互通。在开发Proxy库的时候,咱们不只对跨Region的HTTP调用进行了封装,并且对普通的HTTP调用也加入了非功能性的封装,这样系统能够经过Proxy库完成全部的HTTP调用请求,极大的简化了代码的维护成本。后面,咱们使用RegionProxy来代理请求后,确实删除了不少之前的无用代码,总体流程上也清晰了许多。
通过两版多Region的改造,咱们确实收货了不少宝贵的经验,很是可贵。实际上,在多Region的支持上,你们须要清晰地认识到为何要支持多Region,以何种方式去支持多Region,多Region支持与高可用的关系等基本问题。若是这些问题回到很差,或者不清楚,那么很容易就会掉到陷阱中去。另一个感悟就是结合业务的实际场景,第二版的多Region架构咱们之因此可以这么设计,就在于计费系统不须要实时出帐,咱们彻底能够把数据保存下来,离线计算之后再出帐,这是能够接受的。但这并不适用与所用状况,有些性能要求很高的横向业务就不适合这种场景。
前面提过几回技术债务的问题,有些问题是能够经过工具来解决了,有些只能经过内部重构来解决。左耳朵耗子曾经说过一句话对我感触很大,大意是说有些公司在解决问题时偏流程,有些公司偏技术。我想咱们既然是技术团队,在解决问题时能经过技术方式解决的就应该尽可能用技术解决,流程和人都是不可靠的。
计费项目面临的诸多问题之一就有配置文件的管理,由于业务流程的缘由,计费系统有着大量的各类各样的配置。之前咱们把配置文件放到工程里面,经过自动化部署平台来指定使用不一样的配置文件。这样作的一个显著问题就是代码和配置耦合起来了,每次修改什么配置都得提交代码,而咱们提交又有着一套严格地流程,致使总体效率不高。另一个问题就是可视化的问题。每每QA在线下环境测试都是经过的,而上线之后出了问题,基本上都是配置致使的问题。针对这几个的问题,咱们决定使用Apollo来管理咱们的配置,经过整合Apollo,咱们的两个项目(订单和计费)都作到了工程零配置,全部的配置都放到Apollo上进行管理,好处良多。
计费系统严重依赖于定时任务,有许多流程须要经过定时任务来推进。之前咱们使用QUARTZ+MYSQL来做为咱们分布式定时任务框架,可是这种作法的可维护性太差,并且对数据库侵入很高,对测试也不友好。在QA的不断吐槽中,咱们决定替换掉现有的定时任务框架。在调研开源的定时任务框架后咱们决定使用Elastic-Job来做为咱们的分布式定时任务框架。目前,咱们的两个项目的全部定时任务(除bill-agent外)都已迁移到Elastic-Job上来了。
若是你要问我作蜂巢计费最困难的地方是什么?个人回答确定是业务太复杂了。这种复杂性不是由于咱们架构设计的很差致使的复杂,而是业务自己就是十分复杂的。如今计费系统须要支持十几种产品的售卖形式,涵盖IaaS、PaaS和SaaS的绝大部分产品,同时各个产品的售卖和计费模式都存在或多或少的差别,这让咱们很难经过一个统一的模型就涵盖全部的场景。咱们找到了一条缓解这个问题的方式——抽象化。
横向系统或者支持系统若是须要服务多个产品,那么抽象化设计是不可或缺的一个缓解。若是越早进行抽象化,那么后期对接和维护的成本也就会越低,还能把系统的边界划分得更清晰。计费系统早期的设计在抽象化方面没有过多的规划,在后期的对接方面又处于比较弱势的一方,致使计费系统出现了大量的特化代码。这些特化代码对一个服务十几个产品的支持系统无疑是伤害巨大的。如今咱们已经意识到了问题的严重性,也着手在作这方面的重构工做了。可是挑战依然很大,由于业务的复杂性是没法经过技术手段就能下降的,这方面咱们只有和产品、运营和销售各方面一块儿努力,打造一个合理、灵活、稳定的新计费。
抽象化设计由于咱们还在进行中,后期有机会再分享。
从八月底加入计费团队以来,收获良多,不管是在技术上,仍是在对业务的理解上,都有了许多新的认识。最为给力的仍是团队的小伙伴们,由于计费自己的需求很是多,处理这些需求的人都只刚刚够。后来咱们又作了两版跨Region改造、订单拆分、框架替换、抽象化等优化工做,迭代周期从两周一次压缩到了一周一次,开发和QA的小伙伴也都是不辞辛苦。固然,你们能在这个过程当中有所收获才是最关键的。
计费系统能够说是我接触过的最为复杂的一个系统,越是复杂的系统越须要清晰的头脑和良好的设计。云计算产商的博弈已经到了白热化阶段了,你们拼的不光光是每一个产品的质量和体验,还有整个云平台的内功。公有云平台自己就是一个庞大、复杂的系统,如何把这个系统建设好,用户体验作好、服务质量提升、稳定性获得保障,这自己就是极为有难度的一件事情。计费系统做为公有云平台一个重要的组成部分,能够说扮演着一个极为关键的角色。作得好能够对整个平台提供助力,而作的差则会拖慢总体的发展进程。咱们已经找到了适合本身的一条道路,相信会走上正轨!
本文来自网易实践者社区,经做者蒋文康受权发布。
原文:蜂巢计费系统架构升级之路
相关文章:
【推荐】 四步走查智能硬件异常Case
【推荐】 从互联网+角度看云计算的现状与将来