事件驱动型应用—理论篇

微服务为什么会存在

微服务架构是将单体服务拆分成一系列小服务,这些小服务都拥有独立的进程,通过轻量级通信方式进行通信,而作为独立的业务服务,则可采用一些自动化部署机制独立部署,每个服务可以使用不同的开发语言和数据存储技术,实现去中心化的服务管理。

微服务架构的核心诉求是支撑业务敏捷开发和部署,因此微服务架构的本质是如何优雅地支持微服务的“拆分”和“组合”,如何进行合理的架构拆分与设计,如何最大限度地减少微服务之间沟通的成本,这是微服务架构的关键所在。

微服务不只是个技术问题,更多的是关于组织和团队的问题,系统架构和组织之间存在映射关系,如果无法搭建高效的系统架构,则无法提升组织的效率,反之也是这样。

微服务架构的解决方案有很多种,对于海外团队的目标,就是要从耦合,可靠性,伸缩性,性能,几个维度共同衡量,去选择一种适合我们的解决方案。

我们不会这么做-分布式单体陷阱

微服务已经发展了很多年,很多团队按照各种各样的拆分策略进行了相关的尝试,大多时候,这种模式是我们最容易想到的,也有太多的团队,选择了这种模式。这也是我们首先放弃的一种方案。

这张图里,每个长方形,都描绘的是一种简单的服务,有自己的存储,有自己的处理逻辑,同时有自己对外提供的接口,他们之间也会产生相互的依赖,在产生的依赖中,他们用同步的方式,通过rpc 或者rest 的方式,相互调用。完成自己的功能。

我并不是觉得这种方式完全不好,只是这里会产生一个新的问题。单体应用按照业务边界拆分为多个微服务时,服务间选择用REST或者RPC等远程调用方式简单替代原有的进程内方法调用,会出现这种图的现象。

抛开调用方式的差异来看采用微服务前后的系统架构,会发现:两者几乎是完全一样的!!而微服务版本在某些情况下可能表现的更糟糕:因为调用方式更脆弱,网络进一步增加了不可靠性。而我们将网络调用当成 “胶水” 来使用,试图把分散的业务逻辑模块(已经拆分为微服务)按照单体时代的同样方式简单粘在一起,这当然比单体在同一个进程内直接方法调用更加的不可靠。

这个就是分布式单体。

这个本质的问题就是,我们试图去解决单体的一些问题,做了很多努力,拆了好多的服务,也产生了好多团队,但是发现本质问题貌似没有解决,耦合性,稳定性,貌似都不是很好。

我们不会选择这样的方案,因此,我们产生的第一个思考是,单体和微服务是一种集成和拆分的关系,说到底就是粒度的问题,那么服务到底要如何明确粒度呢。

避免分布式单体-如何定义我们服务的粒度

如果我们简单按照单体服务的模块进行拆分,很容易让我们陷入进分布式单体的陷阱,这里其实是需要去仔细思考,我要如何去定义我们的服务,怎么可以更科学的,去把我们服务的粒度给找出来。我们在这里总结了几个服务拆分和集成的几个经验。当然了。经验不是准则,只是给我们做判断的一种视角,海外团队也是带着这种视角去审视我们的服务现在OK不OK,能不能合在一起,要不要拆分一下。

微服务的拆分

服务的拆分根本的原则是要找准问题域,识别我们业务中的存在哪些问题,在战术层面呢,会有五种常见的策略给予我们帮助。

  • 服务的功能性:是根据服务的功能性去进行拆分,举个例子,发送邮件,发送短信,发送快递,从功能性的角度来说,他们是不一样的,我们可以把她们拆成三个服务。

  • 变化频率:根据服务的变化频率及波动性,比如发邮件和充值收银台,收银台是我们的需求经常会出现的地方,而邮件服务,基本是不会变的。如果出现了这种情况,我们可以考虑把她们拆分成不同的服务。

  • 可伸缩性和吞吐量 :根据服务的可伸缩性和吞吐量去权衡,如果两个服务,一个服务的运行对内存或者网络io的要求非常高,出现了这种情况,他们拆分开,也是一种不错的选择。

  • 故障差异:根据服务的故障差异,如果两个服务,其中一个,外部依赖过于多,比如网络低于1M/s,那她一定就会发生毁灭性的遭难,变得极其不稳定,出现这种情况,我们把她们拆分开,就是个很好的选择。

  • 数据安全性:数据安全性,这个应该是一个很常见的策略了,举个例子,我们的支付的服务,里面会用到了很多重要的三方密钥 ,支付环节中的三方密钥,牵一发而动全身。这类服务需要拆分出来。

从拆分的视角看,上面这五种,都是一些很常用的策略,当然在我们不同的场景里,可能会产出不一样的策略的,就像邦纳 说的那样,架构的设计,最难的地方在于,这不是你可以谷歌出来的东西,因为你遇到的状况,你需要解决的问题,总会是不一样的。因此常用策略对我们很重要,我们不断用常用的策略去审视我们自己的问题,最终去解决我们自己的问题。

OK,微服务是一个名词,而不是一个描述性语言,不是一个把单体服务拆分越小越好的准则。而是围绕有界上下文构建事务的思考。因此我们需要看看,有了拆分的思路,那么服务集成的思路会有哪些呢?

微服务的集成

  • 事务:服务的事务。

大家可以想一下,基本上是我们的产品或者我们的问题域告诉我们什么应该在一起,什么不应该在一起,但是在我们分布式的架构中,最复杂的问题,就是服务和服务之间产生的各种问题,尤其是遇到和事务相关的问题时,举个例子,我要注册成功一个可付费用户,但是需要安全性相关的信息校验通过,才可以注册成功,他们业务中如果是强依赖,从事务的角度考虑,将一个分布式事务简化到了一个本地事务,降低通讯对整个流程的影响。把她们合在一起,为什么不呢???当然我们不对这个例子做过多的扩展,就假设这个安全性的校验是一个非常简单的规则。

  • 数据依赖:两个服务,会依赖同一份数据。

这个可以直接举例子,服务A需要服务B的一份数据,我们会怎么做?A直接读B的表?那B如果改了,A也不得不改动了,这不好,如果考虑数据隔离,rpc 访问?这就是性能的问题的根源,稳定性也会降低。当然了,我们可能有太多的拆分的思路,强迫我们把他们分开,扩展性呀,稳定性呀,但是如果仅仅就是问题域的角度,是我们把她们分开的,那出现这种问题,他们合在一起,也不能说是一种坏的选择。

  • 工作流和编排:如下图

我们的服务有着非常好的有界上下文,隔离性,低耦合,如果我们一个请求过来了,他的会经过服务A-B-C-D 再返回去,会经过很长的一个路径,这里的问题是啥,如果链路会好长, 挂了一个,效率也会低好多,可以考虑把这些合在一起。

这里其实我想讲的是我们用上面这些东西去思考去重新看待我们现在的服务,分解也好,集成也好,这些呢,是一种辅助权衡的视角,每一条单独拿出来,当作准则,一定是玩不转的,服务最好的粒度,一定是来源于我们不断的迭代,不断的拆分集成拆分集成,还是那句话,架构的设计,最难的地方在于,这不是你可以谷歌出来明确答案的东西。

OK,当我们有了解决我们服务粒度的方法后,那如何去评价我们的设计呢?

多维度权衡微服务架构

这里我们采用了Neal Ford 21年提出的一个最新的评价思路,微服务本质上可以归结为三个维度去进行评价,并且可以构建联合维度,去一起描述这个评价体系。通讯,一致性,编排。

第一个:通讯:同步还是异步,

这个很重要,因为他的选择,对于时间,性能,可靠性,可用性都有着很大的影响;

异步,我告诉你个事情,然后我继续做我的事情,快,可以发送个队列,然后队列去获取buffer,让对方去处理;但是你需要处理的就是错误的兼容。告知成功,可处理成功是两个事情。

同步呢,阻塞住,告诉你,这里有个很大的问题,当服务A同步调用服务B的时候,这一种动态创建了一种量子纠缠,因为如果他们在调用,你就不能让他们以不同的规模去运行,A以十倍的规模去运行,去调用服务B,那A一定收到大量的timeout 或者失败,你的可靠性一定收到严峻的挑战,然后当你的调用结束时,他们有可以独立的去运行了。当然耗时的问题也是存在的

第二个:一致性:原子性还是最终一致性

这里的原子性指的是我做N件事情,要么都成功,要么都失败,你要告诉我结果,但是最终一致性,是你无法立刻告诉结果,这两个应该是我们很熟悉的问题

第三个:编排:是精心设计的还是精心安排的;

这里主要的差异存在两个设计上的方式,

存在中心的编排:我要做N件事情,调用N个服务,会存在一个中心的编排服务,为我把这N件事情给处理掉,同时帮我兼容服务调用包括错误处理等。他就在努力的为我把这些事情给处理好。

另外一种是无中心化的编排;我做的N件事情,由服务内部自己去完成,服务A做完了,需要B 去做了,B做完了,需要C去做了,这种无中心的协作。

这三个维度是可以组成一个三维空间的。两两组和,其实可以构建一个8中组合的方式。大家下面可以去考虑下,这8种的组合方式会产生什么样的应用。

这里我们简单分析两种:

  • 第一种:中心化编排,同步,原子一致性

  • 第二种:异步,去中心化协调,并且保持最终一致性。

在这种模式中,他很多指标都有着非常好的凸显,质量非常的高。

因此海外,也是基于这种维度组合选取的我们架构风格-事件驱动的架构风格。

我们的选择-事件驱动

要介绍事件驱动,我们先说下实体陷阱,现在大多针对于系统的构建思路,在设计之初,过多的去关注名词,来创建初始组件,这个时候我们会容易坠入“实体陷阱”。

比如 我们做会员订阅,会有三个顶级模型,合同,权益,会员,如果我们仅从会员订阅的prd中名词的方向去思考,会让我们忽视掉合同这个顶级概念。

采用需求中的名词标识的每个实体,并基于该实体制作了一个组件。这不是架构;这是框架到数据库的关系映射。换句话说,如果系统仅需要简单的数据库CRUD操作,那么我们可以直接从数据库创建用户界面。

这里的问题是 错误地将数据库关系,标识为应用程序中的工作流,这时就会出现实体陷阱反模式,这种对应关系在现实世界中很少出现。相反,此反模式通常表明对,应用程序的实际工作流程缺乏思考。使用实体陷阱创建的组件,也往往过于粗粒度,很难为开发团队提供更多的指导。

而事件优先的角度去设计我们的服务,在设计之初把关注点放在系统的行为上,找到系统的命令以及事件,而非先去考虑系统的结构,系统的实体。

OK,那么什么是命令,什么是事件呢?

命令

命令:一个人或其它实体希望被执行的动作

  • 这个动作是尚未发生的。它可能在未来某个时刻发生(如果请求被接受和执行)

  • 存在被拒绝的可能:不愿意执行(参数校验失败,权限不足),不能执行(接收者故障或者资源无法访问)

  • 是一个动词

我们举一个生活的例子:一位顾客访问了线上购物网站,然后选择了一些要购买的商品。为了完成购买,顾客在网站的支付表单中输入一些信息,然后点击“完成购买”按钮。

  • 命令成功:购买数据成功提交到数据库,而客户收到一个响应,告诉他们订单正在处理中。

  • 命令失败:出现这种情况的原因可能有很多。如账单信息不准确、收货地址不完整、购物车中的商品缺货,或者是后端程序出现了一些技术问题。

当然,在到达结账界面的过程中,也有很多命令已经成功结束。即使是点击链接查看商品详情的操作,都可以看作是一个命令。

产生命令的目标是为了接下来的命令执行:

  • 将命令发送给期望的执行者

  • 收到命令的执行者将根据命令的要求进行执行:在执行的过程中内部可能有多个动作,有些动作可能需要和其他模块通讯而触发命令,这些动作执行完毕可能会造成领域状态的改变从而继续触发新的事件

事件

命令表示一个人或代理希望执行的动作。而事件则表示已经发生的动作,或者有状态已经发生改变。

  • 事件可以被理解为是对已经发生的事实的客观陈述

  • 这意味着事件通常是不可变的:事件的信息(代表着客观事实)不能被篡改,事件的产生不能逆转

  • 命名:事件通常以动词的完成时态命名,如 用户登陆已成功。

举个例子:登录网站,是个命令,登录已成功或者登陆已失败就是对应的事件了。

你可以把命令看着茧,而把事件视为蝴蝶。最重要的是,动作是以命令开始的,一旦得到执行,它就成为了事件。

产生事件的目标是为了接下来的事件传播:

  • 将已经发生的事件通知给对此感兴趣的观察者

  • 收到事件的观察者将根据事件的内容进行判断和决策:可能会有接下来的动作,有些动作可能需要和其他模块通讯而触发命令,这些动作执行完毕可能会造成领域状态的改变从而继续触发新的事件

命令和事件的区别

  • 命令可以被拒绝,事件无法拒绝,但是可以被忽略

  • 命令在当下,事件会延后

  • 命令的意图在告知希望发生的事情,事件的意图在告知已经发生的事情

明确完这两个概念,我们来看下,事件驱动的服务会是什么样子

基于事件的服务

在这张图里,菱形代表我们的命令,当服务接受到命令,会产生事件,这个事件会发布到事件流中,同时不同的服务会订阅他们关注的事件。

这里我们举个例子:假设一个场景,在那种不是一定要登陆app中,存在一个活动,在活动截止前,用户登陆成功,就给用户发送100代金券,如果用户登陆失败了,那么就要给该设备所在地区的能力值减1;

怎么做呢?

用户发送登陆命令,接收命令的服务,判断用户是否登陆成功,然后发送登陆成功和登陆失败的两个命令到事件流中,代金券服务,活动服务,能力相关服务,去订阅登陆相关的事件。完成自己的逻辑。

当一个命令得到执行,后续的服务,完全解耦。

抽象起来,一个典型的微服务在业务处理流程中的通讯行为可以概括为以下三点:

  1. 输入:以一个命令请求或者一个事件通知为输入,这是业务处理流程的起点

  1. 内部动作:微服务的内部逻辑,业务规则,通常为0-N个

  1. 通告变更:以事件形式对外发布事件,通告上述操作产生的业务状态的变更。可选,通常为0-1个

在这个行为模式中,3通常都是在流程的最后:只有当各种内部动作完成,业务逻辑实现结束,状态变更完成,“木已成舟”,才能以事件的方式对外发布:“操作已完成,状态已变更,望周知”。

从业务逻辑处理的角度来看,内部操作的动作是业务逻辑的 “实现” 部分:这些操作组成了完整的业务逻辑——如果这些操作失败,则业务处理将会直接影响(失败或者部分失败)。而发布事件则是业务逻辑完成之后的后续 “通知” 部分:当业务逻辑处理完毕,状态变更完成后,以事件的方式驱动后续的进一步处理。注意是驱动,而不是直接操纵。

从时间线的角度来看整个业务处理流程如下图所示:

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值