赞
踩
这里所说的三驾马车是指微服务,消息队列,定时任务。如下图所示,这里是一个三驾马车共同驱动的一个立体的互联网项目的架构。不管项目是大是小,这个架构模板的形态一旦定型了之后就不太会变,区别只是我们有更多的服务有更复杂的调用,更复杂的消息流转,更多的Job。整个架构整体是可扩展的,而且不会变形,这个架构可以在很长的一段时间内无需有大的调整。
图上画了虚线框的都代表这个模块或项目是不包含太多业务逻辑的,纯粹是一层皮(会调用服务但是不会触碰数据库)。黑色线的箭头代表依赖关系,绿色和红色箭头分别是 MQ 的发送和订阅消息流的方向。具体在后文都会进一步详细说明。
微服务并不是一个很新的概念,在 10 年前的时候我就开始实践这个架构风格,在四个公司的项目中全面实现了微服务,越来越坚信这是非常适合互联网项目的一个架构风格。不是说我们的服务一定要跨物理机器进行远程调用,而是我们通过进行有意的设计让我们的业务在一开始的时候就按照领域进行分割,这能让我们对业务有更充分的理解,能让我们在之后的迭代中轻易在不同的业务模块上进行耕耘,能让我们的项目开发越来越轻松,轻松来源于几个方面:
这里也要求我们做到几个方面的原则:
服务的粒度划分需要把控好。我的习惯是先按照领域来分不会错,随着项目的进展慢慢进行更细粒度的拆分。比如对于互联网金融 P2P 业务,一开始可以分为:
服务一定是立体的,不是在一个层次上的,如上图,我们的服务有三个层次:
a~d
这些服务都是此类服务。这个层次的服务的业务逻辑更多是在表达业务流程的复杂性和差异性,不会涉及到具体怎么处理账户信息、账务信息、用户信息,不会涉及到怎么处理具体的投资人和借款人的交易。比如对于预约这类业务形态,它关注的是先要预约资产,然后再由系统进行自动投资,底层完全依赖于投资人交易服务来做整个交易的过程。l~n
这些服务都是此类服务。如果以后和外部的合作有变动,因为我们已经定义了对外的服务契约,可以轻易替换这个服务来更换合作的第三方,系统其余的地方几乎不需要修改。所有的三方对接都建议独立出公共基础服务,如果同一个业务对接多个三方渠道,比如推送对接了极光和个推,甚至公共基础服务还可以由一个抽象聚合的推送服务,下面再路由到具体的极光推送和个推推送服务。希望在这里把这个事情说清楚了,怎么来划分服务怎么划分三个层次的服务是一个很有意思很有必要的事情,在服务划分之后最好有一个明确的文档来描述每一个服务的职责,这样我们在无需阅读 API 的情况下可以大概定位到业务所在的服务,整个复杂的系统就变得很直白了。
说白了就是我的数据我做主,我想怎么搞外面管不着,在重构或是做一些高层次技术架构(比如异地多活)的时候,没有底层数据被依赖,这太重要了。当然,坏处或是麻烦的地方就是跨服务的调用使得数据操作无法在一个数据库事务中完成,这并不是什么大问题,一是因为我们这种拆分方式并不会让粒度太细,大部分的业务逻辑是在一个业务服务里完成的,二是后面会提到跨服务的调用不管是通过 MQ 进行的还是直接调用进行的,都会有补偿来实现最终一致性。
如果你说,这么多服务,我在实现的时候很难考虑到这些点,我完全不去考虑分布式事务、幂等性、补偿(毫不夸张地说,有的时候我们花了 20%的时间实现了业务逻辑,然后花 80%的时间在实现这些可靠性方面的外围逻辑),行不行?也不是不可以,那么业务在线上跑的时候一定会是千疮百孔的,如果整个业务的处理对可靠性方面的要求不高或是业务不面向用户不会受到投诉的话,这部分业务的是可以暂时不考虑这些点,但是诸如订单业务这种核心的不允许有不一致性的业务还是需要全面考虑这些点的。
考虑到跨机器跨进程调用服务数据传输方面的显著差异,对于本地方法的调用,如果参数和返回值传的是对象,那么对于大部分语言来说,传的是指针(或指针的拷贝),指针指向的是堆中分配的对象,对象在数据传输上的成本几乎忽略不计,也没有序列化和反序列化的开销,对于跨进程的服务调用,这个成本往往不能忽略不计,如果我们需要返回很多数据,往往接口的定义需要进行特殊的改造:
这里还引申出方法粒度的问题,比如我们可以定义GetUserInfo 通过传入不用的参数来返回不同的数据组合,也可以分别定义GetUserBasicInfoGetUserVIPInfo、GetUserInvestData 等等细粒度的接口,接口的粒度定义取决于使用者会怎么来使用数据,更趋向于一次使用单种类型数据还是复合类型的数据等等。
然后我们需要考虑接口升级的问题,接口的改动最好是兼容之前的接口,如果接口需要淘汰下线,需要先确保调用方改造到了新接口,确保调用方流量为0观察一段时间后方能从代码下线老接口,一旦服务公开出去,要进行接口定义调整甚至下线往往就没有那么容易了,不是自己说了算了,所以对外的API的设计需要慎重点。
最后不得不说,在整个公司都搞起了微服务后,跨部门的一些服务调用在商定API的时候难免会有一些扯皮的现象发生,到底是我传给你呢还是你自己来拉,这个数据对我没用为什么要在我这里留一下呢?抛开非技术层面的事情不说,这些扯皮也是有一些技术手段来化解的:
你可能看到这里觉得很头晕,为什么微服务需要额外考虑这么多东西,实现的复杂度一下子上
升了。我想说的是我们需要换一个角度来考虑这个事情:
但是,如果服务粒度划分的不合理,层次划分的不合理,底层数据源有交叉,没考虑到网络调用失败,没考虑到数据量,接口定义不合理,版本升级过于鲁莽,整个系统会出各种各样的扩展问题性能问题和 Bug,这是很头痛的,这也就需要我们有一个完善的服务框架来帮助我们定位各种不合理,在之后说到中间件的文章中会再具体着重介绍服务治理这块。
消息队列 MQ 的使用有下面几个好处,或者说我们往往处于这些目的来考虑引入 MQ:
这些需求互联网项目中基本都存在,所以消息队列的使用是非常重要的一个架构手段。在使用上有几个注意点:
定时任务的需求有那么几类:
详细说明一下任务驱动是怎么一回事。其实在数据库中做一些任务表,以这些表驱动作为整个数据处理的核心体系,这套被动的运作方式是最最可靠的,比 MQ 驱动或服务驱动两种形态可靠多,天生必然是可负载均衡的+幂等处理+补偿到底的,任务表可以设计下面的字段:
除了这些字段之外,还可能会加一些业务自己的字段,比如订单状态,用户 ID 等等信息作为冗余。任务表可以进行归档减少数据量,任务表扮演了消息队列的性质,我们需要有监控可以对数据积压,出入队不平衡处理不过来,死信数据发生等等情况进行报警。如果我们的流程处理是任务 ABCD 顺序来处理的话,每一个任务因为有自己的检查间隔,这套体系可能会浪费一点时间,没有通过 MQ 实时串联这么高效,但是我们要考虑到的是,任务的处理往往是批量数据获取+并行执行的,和 MQ 基于单条数据的处理是不一样的,总体上来说吞吐上不会有太多的差异,差的只是单条数据的执行时间,考虑到任务表驱动执行的被动稳定性,对于有的业务来说,这不失为一种选择。
这里再说明一下 Job 的几个设计原则:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。