加入收藏 | 设为首页 | 会员中心 | 我要投稿 新余站长网 (https://www.0790zz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 站长资讯 > 外闻 > 正文

屡试不爽的架构三架马车

发布时间:2019-07-04 01:50:49 所属栏目:外闻 来源:Dockone.in
导读:这里所说的三架马车是指微服务、消息队列和定时任务。如下图所示,这里是一个三驾马车共同驱动的一个立体的互联网项目的架构。不管项目是大是小,这个架构模板的形态一旦定型了之后就不太会变,区别只是我们有更多的服务有更复杂的调用,更复杂的消息流转

最后不得不说,在整个公司都搞起了微服务后,跨部门的一些服务调用在商定API的时候难免会有一些扯皮的现象发生,到底是我传给你呢还是你自己来拉,这个数据对我没用为什么要在我这里留一下呢?抛开非技术层面的事情不说,这些扯皮也是有一些技术手段来化解的:

  1. 明确服务职责,也就明确了服务应该感知到什么不应该感知到什么。
  2. 跨部门的服务交互的接口定义可以定的很轻,采用只有一个订单号的接口或MQ通知+数据回拉的策略(谁数据多谁提供数据接口,不用把数据一次性推给下游)。

数据提供方可以构建一套通用数据接口,这样可以满足多个部门的需求,无需做定制化的处理。甚至在接口上可以提供落地和不落地两种性质的透传。

你可能看到这里觉得很头晕,为什么微服务需要额外考虑这么多东西,实现的复杂度一下子上升了。我想说的是我们需要换一个角度来考虑这个事情:

  1. 我们不需要在一开始的时候对所有逻辑都进行严密的考虑,先覆盖核心流程核心逻辑。因为跨服务成为了服务的提供方和使用方,相当于除了我自己,还有很多其它人会来关系我的服务能力,大家会提出各种问题,这对设计一个可靠的方法是有好处的。
  2. 即使在不跨服务调用的时候我们把所有逻辑堆积在一起,也不意味着这些逻辑一定是事务性的,实现严密的,跨服务调用往往是一定程度放大了问题产生的可能性。
  3. 我们还有服务框架呢,服务框架往往会在监控跟踪层次和运维系统结合在一起提供很多一体化的功能,这将封闭在内部的方法逻辑打散暴露出来,对于有一个完善的监控平台的微服务系统,在排查问题的时候你往往会感叹这是一个远程服务调用就好了。
  4. 最大的红利还是之前说的,当我们以清晰的业务逻辑形成了一个立体化的服务体系之后,任何需求可以解剖为很少量的代码修改和一些组合的服务调用,而且你知道我这么做是不会有任何问题的,因为底层的服务ABCDEFG都是经过历史考验的,这种爽快感体验过一次就会大呼过瘾。

但是,如果服务粒度划分的不合理,层次划分的不合理,底层数据源有交叉,没考虑到网络调用失败,没考虑到数据量,接口定义不合理,版本升级过于鲁莽,整个系统会出各种各样的扩展问题性能问题和Bug,这是很头痛的,这也就需要我们有一个完善的服务框架来帮助我们定位各种不合理,在之后说到中间件的文章中会再具体着重介绍服务治理这块。

消息队列

消息队列MQ的使用有下面几个好处,或者说我们往往处于这些目的来考虑引入MQ:

  1. 异步处理:类似于订单这样的流程一般可以定义出一个核心流程,这个流程用于处理核心订单的状态机,需要尽快同步落库完成,然后围绕订单会衍生出一系列和用户相关的库存相关的后续的业务处理,这些处理完全不需要卡在用户点击提交订单的那刹那进行处理。下单只是一个确认合法受理订单的过程,后续的很多事情都可以慢慢在几十个模块中进行流转,这个流转过程哪怕是消耗5分钟,用户也无需感受到。
  2. 流量洪峰:互联网项目的一个特点是有的时候会做一些toC的促销,免不了有一些流量洪峰,如果我们引入了消息队列在模块之间作为缓冲,那么backend的服务可以以自己既有的舒服的频次来被动消耗数据,不会被强压的流量击垮。当然,做好监控是必不可少的,下面再细说一下监控。
  3. 模块解耦:随着项目复杂度的上升,我们会有各种来源于项目内部和外部的事件(用户注册登陆、投资、提现事件等),这些重要事件可能不断有各种各样的模块(营销模块、活动模块)需要关心,核心业务系统去调用这些外部体系的模块,让整个系统在内部纠缠在一起显然是不合适的,这个时候通过MQ进行解耦,让各种各样的事件在系统中进行松耦合流转,模块之间各司其职也相互没有感知,这是比较适合的做法。
  4. 消息群发:有一些消息是会有多个接收者的,接收者的数量还是动态的(类似指责链的性质也是可能的),在这个时候如果上下游进行一对多的耦合就会更麻烦,对于这种情况就更适用使用MQ进行解耦了。上游只管发消息说现在发生了什么事情,下游不管有多少人关心这个消息,上游都是没有感知的。

这些需求互联网项目中基本都存在,所以消息队列的使用是非常重要的一个架构手段。在使用上有几个注意点:

  1. 我更倾向于独立一个专门的listener项目(而不是合并在server中)来专门做消息的监听,然后这个模块其实没有过多的逻辑,只是在收到了具体的消息之后调用对应的service中的API进行消息处理。listener是可以启动多份做一个负载均衡的(取决于具体使用的MQ产品),但是因为这里几乎没有什么压力,不是100%必须。注意,不是所有的service都是需要有一个配到的listener项目的,大多数公共基础服务因为本身很独立不需要感知到外部的其它业务事件,所以往往是没有listener的,基础业务服务也有一些是类似的原因不需要有listener。
  2. 对于重要的MQ消息,应当配以相应的补偿线作为备份,在MQ集群一切正常作为补漏,在MQ集群瘫痪的时候作为后背。我在日千万订单的项目中使用过RabbitMQ,虽然QPS在几百上千,远远低于RabbitMQ压测下来能抗住的数万QPS,但是整体上有那么十万分之一的丢消息概率(我也用过阿里的RocketMQ,但是因为单量较小目前没有观察到有类似的问题),这些丢掉的消息马上会由补偿线进行处理了。在极端的情况下,RabbitMQ发生了整个集群宕机,A服务发出的消息无法抵达B服务了,这个时候补偿Job开始工作,定期从A服务批量拉取消息提供给B服务,虽然消息处理是一批一批的,但是至少确保了消息可以正常处理。做好这套后备是非常重要的,因为我们无法确保中间件的可用性在100%。
  3. 补偿的实现是不带任何业务逻辑的,我们再梳理一下补偿这个事情。如果A服务是消息的提供者,B-listener是消息监听器,听到消息后会调用B-server中具体的方法handleXXMessage(XXMessage message)来执行业务逻辑,在MQ停止工作的时候,有一个Job(可配置补偿时间以及每次拉取的量)来定期调用A服务提供的专有方法getXXMessages(LocalDateTime from, LocalDateTime to, int batchSize)来拉取消息,然后还是(可以并发)调用B-server的那个handleXXMessage来处理消息。这个补偿的Job可以重用的可配置的,无需每次为每一个消息都手写一套,唯一需要多做的事情是A服务需要提供一个拉取消息的接口。那你可能会说,我A服务这里还需要维护一套基于数据库的消息队列吗,这个不是自己搞一套基于被动拉的消息队列了吗?其实这里的消息往往只是一个转化工作,A一定在数据库中有落地过去一段时间发生过变动的数据,只要把这些数据转化为Message对象提供出去即可。B-server的handleXXMessage由于是幂等的,所以无所谓消息是否重复处理,这里只是在应急情况下进行无脑的过去一段时间的数据的依次处理。
  4. 所有消息的处理端最好对相同的消息处理实现幂等,即使有一些MQ产品支持消息处理且只处理一次,靠自己做好幂等能让事情变得更简单。
  5. 有一些场景下有延迟消息或延迟消息队列的需求,诸如RabbitMQ、RocketMQ都有不同的实现方式。
  6. MQ消息一般而言有两种,一种是(最好)只能被一个消费者进行消费并且只消费一次的,另一种是所有订阅者都可以来处理,不限制人数。不用的MQ中间件对于这两种形式都有不同的实现,有的时候使用消息类型来做,有的使用不同的交换机来做,有的是使用group的划分来做(不同的group可以重复消息相同的消息)。一般来说都是支持这两种实现的。在使用具体产品的时候务必研究相关的文档,做好实验确保这两种消息是以正确的方式在处理,以免发生妖怪问题。
  7. 需要做好消息监控,最最重要的是监控消息是否有堆积,有的话需要及时增强下游处理能力(加机器,加线程),当然做的更好点可以以热点拓扑图绘制所有消息的流向流速一眼就可以看到目前哪些消息有压力。你可能会想既然消息都在MQ体系中不会丢失,消息有堆积处理慢一点其实也没什么问题。是的,消息可以有适当的堆积,但是不能大量堆积,如果MQ系统出现存储问题,大量堆积的消息有丢失也是比较麻烦的,而且有一些业务系统对于消息的处理是看时间的,过晚到达的消息是会认为业务违例进行忽略的。
  8. 图上画了两个MQ集群,一套对内一套对外。原因是对内的MQ集群我们在权限上控制可以相对弱点,对外的集群必须明确每一个Topic,而且Topic需要由固定的人来维护不能在集群上随意增删Topic造成混乱。对内对外的消息实现硬隔离对于性能也有好处,建议在生产环境把对内对外的MQ集群进行隔离划分。

定时任务

(编辑:新余站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

热点阅读