你对微服务的认知有误解?

2020-11-27 15:57:51 APP开发网 8727

微服务是一个新兴的软件架构,就是把一个大型的单个应用程序和服务拆分为数十个的支持微服务。一个微服务的策略可以让工作变得更为简便,它可扩展单个组件而不是整个的应用程序堆栈,从而满足服务等级协议。

对于大型应用程序来说,增加更多的用户则意味着提供更大型的弹性计算云(EC2)实例规模,即便只是其中的一些功能扩大了规模亦是如此。其最终结果就是企业用户只需为支持超过微服务的那部分需求的EC2实例支付费用。

微服务有很多优点,其中最大的优点就是,微服务比传统的应用程序能更有效的利用计算资源。这个是因为微服务可以通过扩展组件来处理性能瓶颈的问题,开发人员的工作量就减少了,不再需要部署一个完整的应用程序的全新迭代,而只需要为那些额外的组件部署计算资源。这样一来就可将资源的利用率提高了。第二个好处是可以更快且更容易的进行更新。因为传统的单体应用程序在进行更新的时候需要做详细的QA测试,这样才能保证此次更新不会影响其他的功能或者特性。但是微服务,是分开的且相对独立的,更新应用程序中的单个组件,对其他部分的功能和特性都不会有影响的,虽然依旧需要做测试,但是工作量减少了很多。第三个好处就是,微服务架构对于新兴的云服务有积极作用,比如说

但是,随着微数量急速的增长,同步交互比例也随着service在急速增长,我们也将遇到一些麻烦。比如ops工程师们,他们得在一个又一个的service里奔波,将那些二手信息拼凑在一起,比如谁身边发生了什么,他做了什么,又怎样的结果等。这是避免不了的问题,也有了一些相应的解决办法,比如 Google提供的协议,确保个人服务具有比系统更高的SLA。另一种方法是简单地分解将服务绑定在一起的同步关系。

上述描述的方法其实并不能从根本上解决问题,真正的解决办法是需要用异步机制。比如,在电商网站中,你会看到这样的同步接口,在客户点击购买之后,就会触发一个复杂且一步的处理过程,为什么这么说呢,是因为这个看似简单的过程里包含了购买,支付,物流等过程,这一切的发生都开始于购买键被点击。这样的过程能简单明确的提供给顾客,就是因为将这一程序处理逻辑切分成多个异步的处理才得以实现的。这也就是在实际生活中的运用,在实际情况下,我们其实已经自动拥抱了异步了。我们发现自己会定时轮询数据库表来更改又或者通过计算机即时程序来定时工作来实现一些更新。

这篇文章将会讨论一种完全不同的架构:就是通过事件流来处理上述的问题,这个方法也是之后讨论的一系列的一个基础。

首先,先给大家普及一下三个简单的概念。两个微服务的之间有三种交互方式:

当我们进入正式的例子之前,我们需要先普及三个简单的概念。一个service与另外一个service有三种交互方式:命令(Commands)、事件(Events)以及查询(Queries)。

事件的美妙之处在于外部数据可以被系统中的任何service所重用。

而且从service的角度来说,事件要比命令和查询都要解耦。这个很重要。

服务之间的交互有三种机制:分别是查询,命令,触发器。

查询是一个请求,是一个查找一些东西的请求(request)。重要的是,查询不会使得系统状态发生改变。

命令是一个操作。希望在另一个服务中执行某些操作的一个请求,会改变系统状态的东西。,命令期待有响应。

事件,事件既是一个事实也是一个触发器。 发生了一些事情,表示为通知。

举一个简单易懂的例子,当顾客在电商平台购买了一件东西之后,接下来就会出现两件事情。1.支付;2.系统检查是否还有更多的商品需要被订购。

首先是顾客的支付。在支付的时候,系统需要检查还有有其他的商品被订购。在请求驱动(request-approach)的架构中,这两个行为被表现为一个命令链条。交互就像下面这样:首先要注意的问题是购买更多的这个业务流程是随着订单服务(Order Service)一块被初始化的。这就使得责任需要共同承担,责任跨了两个service。理想情况下,我们希望separation of concerns,也就是关注隔离。这个时候不适用事件驱动,使用请求驱动会更合适。

在返回给用户之前,UI service 发布一个Order Requested事件,然后等待Order Confirmed(或者Rejected)。订单服务(Orders Service)和库存服务(Stock Service)反应这个事件。在这个过程中,UI serviceOrders Service并没有太多的改变,这整个过程是通过事件来进行通讯的。订单服务向库存服务提供需要什么的信息,然后库存服务根据自己的情况来决定是否参与本次交互,这就是事件驱动架构非常重要的属性。

这个Stock service(库存服务)很有趣。Order Service告诉他要做什么。然后Stock Service自己决定是否参与本次交互,这是事件驱动架构非常重要的属性,也就是——接收者驱动流程控制(Receiver drives process control)。这样一来,控制就反转了。这种控制反转给接收者,很好的解耦了服务之间的交互,这就为架构提供了可插拔性,组件之间就更具灵活性。

随着架构变得越来越复杂,这种可插拔性的因素变得更加重要。就用上一个例子来说,如果我们想要添加一个根据供需调整产品的价格的service,用于实时管理定价。在一个命令驱动的世界里,我们就需要引入一个可以由库存服务(Stock Service)和订单服务(Orders Service)调用的类似up date Price这样的方法。但是在事件驱动(event-driven)世界更新价格的话,service只需要订阅共享的stream就是了,当相应的条件符合时,就去执行更新价格的操作。

上面描述的例子涉及到了命令与事件,但还没有提到查询。现在我们将上述的例子在进行一些扩展,将查询这个交互方式融入其中,就是将订单服务(Orders Service)在支付之前检查是否有足够的库存。在请求驱动(request-driven)的架构中,我们可能会向库存服务(Stock Service)发送一个查询请求然后获取到当前的库存数量。这就导致了模型混合,事件流纯粹被用作通知,允许任何的service加入这个流程,但查询却是通过请求驱动的方式直接访问源。

对于服务需要独立发展的较大的生态系统,远程查询要涉及到很多关联,耦合很严重,要把很多服务捆绑在一起。我们可以通过内部化来避免这种涉及多个上下文交叉的查询。而事件流可以被用于在每个service中缓存数据集,这样我们就可以在本地来完成查询。所以,增加这个库存检查,订单服务(Order Service)可以订阅库存服务(Stock Service)的事件流,库存一有更新,订单服务就会收到通知,然后把更新存储到本地的数据库。这样接下来就可以查询本地这个视图(view来检查是否有足够的库存。

纯事件驱动系统没有远程查询的概念 - 事件将状态传播到本地查询的服务

通过事件来传播( “Queryby Event Propagation”)的查询有以下三个好处:

1、更好的解耦:在本地查询,这样就不涉及跨上下文调用了,这种做法涉及到的服务量远远不及那种请求驱动所涉及到的服务数量多。

2、更好的自治:订单服务(Order Service)拥有一份库存数据集的复制,所以订单服务可以任意使用这个本地的数据集。

3、高效的连接:如果我们在每次下订单的时候都要去查询库存,就要求每次都要高效的做连接,通过跨网络对两个service进行连接。随着需求的增加,或者更多的数据源需要关联,这可能会变得越来越艰巨。所以通过事件传播来查询(Query by Event Propagation)将查询与连接本地化后就可以解决这个问题,即进行本地查询。

但是任何方法都会有它的不足,Service从本质上变得有状态了。这样就使得他们需要被跟踪和矫正这些数据集,随着时间的推移,也就是你得保证数据同步。状态的重复也可能使一些问题更难理解,这些问题需要我们谨慎对待的。但是,任何问题都会有他的解决方法,接下来就来介绍一下相关的解决方法。

模式(Patterns)和集群服务(Clustering Services)的混合

该模型看起来与企业消息有一些类似,但是并不是这样的,在实践中,企业消息主要关注的是状态的转换,并且通过网络将数据库有效地捆绑在一起。而事件协作(Event Collaboration)则更偏重的是协作,事件协作是关于服务(service)通过一系列事件进行一些业务目标,而不是简单的是状态转换。因此业务处理(business processing)的一种模式,不是简单的转换状态的机制。

这种模式最显著的优势就是在处理问题的时候既可以处理宏观问题又可以处理微观问题又可以处理宏观问题,也可以同时处理微观与宏观的问题。模式组合使用也很常见,这样做的原因是我们总是希望远程查询能够更加方便灵活,这样就能在数据及增长的时候,不用消耗成本维护本地的数据集,我们只需要轻松部署简单的函数就可以了。并且在现实生活中,我们大多时候是无状态的,比如在使用浏览器的时候,远程查询是一个比较合适的选择。

远程查询设计的技巧就是限制查询接口的范围,理想情况下应该是在有限的上下文中(context)。通常情况下,建立一个具有多个特定,具体视图的架构,而不是单一的共享数据存储。(一个独立(bounded)的上下文,独立上下文,一般是指有那么一组service,它们共享同一个发布流水线或者是同一个领域模型【domain model】)。

为了限制远程查询(remote queries)的边界(scope),我们可以使用一种叫做集群式上下文模式(clustered context pattern。这种情况下,事件就流纯粹是用作上下文之间的通信。但在一个上下文里的具体service们则可以既有事件驱动(event-driven)的处理,同时也有请求驱动(request-driven)的视图(view),具体根据实际情况需要。

在下面的例子中,我们有三个部分,三个之间只通过事件相互沟通。在每一个内部,我们使用了更细粒度的事件驱动流。其中一些包括视图层(查询层)。如图所示:

单一写入者原则(Single Writer Principle

单一的写入者的意思就是一件事情对应对应着一个service。用刚才的例子来解释,就是说Order Service也只属于订单,处理库存这一件事情就只是Stock Service来负责。这样做我们就可以通过单个代码路径来排除一致性,比较快捷的验证和其他写入路径(write path问题。用刚才的例子来解释,就是订单服务(Order Service)将控制着对订单进行的每个状态的更改,但整个事件流跨越了订单(Orders),付款(Payments)和发货(Shipments),每个都由它们各自的服务来管理。

分配事件传播event propagation)的责任很重要,因为他们代表了共同的事实(facts),以及数据在外部(data-on-the-outside,而不仅仅是短暂的事件,或者是那种无须保存短暂的聊天。因此,随着时间的推移,服务(services)需要去负责更新和同步这些共享数据集(shared data sets):比如,修复错误,处理schema的变化等情况。

上图中每个颜色代表Kafka的一个topic,针对下订单、发货和付款。 当用户点击购买时,会引发“Order Requested”,等待“Order Confirmed”事件,然后再回复给用户。 另外三个服务处理与其工作流程部分相关的状态转换。 例如,付款处理完成后,订单服务(Order Service)将订单从已验证(Validated推送到已确认(Confirmed

事件驱动(event-driven)五个关键好处:

1.状态同步更新:事件流对分布式数据集提供了一种有效的机制,数据集可以在一个有界的上下文里被重构(传播更新)和查询。

2.离线/异步流:当用户点击按钮时,很多事情都会发生。 一些同步,一些异步。 对能力的设计,无论是以前的,还是将来的,都是更自由的。提高了性能,提高了自由度。

3. Joins:从不同的服务(service)组合/join/扩展数据集更容易。 join更快速,而且还是本地化的。

4.可追溯性: 当有一个统一化的,中心化的,不可变的,保持性的地方来记录每个互动时,它会及时展现,debug的时候也更容易定位问题,而不是陷入一场关于分布式的谋杀。(这里有点晦涩)

5.解耦:把一个很长的同步执行链的命令给分解,异步化。,分解同步工作流, Brokers topic解耦服务(service),所以更容易插入新的服务(service),具有更强的插拔性。

总结

在上述的方法中,我们采用的驱动方法不是命令而是事件,事件触发业务处理过程,也可以被用来更新本地视图。在有必要的时候,特别是在较小的系统中,可以再次使用远程同步查询,并且将远程同步查询的范围扩大。所有这些方法都只是模式(pattern),带有自身的局限,比如缺乏一定的灵活性;范围还不够广,会存在模式覆盖不到的地方,这就要求在处理问题的时候在具体问题具体分析。例如,单点登录服务,全局查询的service仍然是一个好主意,因为它很少更新。所以解决问题的时候有一个必备的思路,就是事件的基准出发去考虑问题。事件让服务之间不再耦合,并且将控制(flow-control)权转移到接收者,这就有了更好的分离关注(separated concerns和更好的可插拔性。

并且事件驱动还有一个显著的优点就是,无论框架是大型的还是小型的,是复杂的还是简单的,他都一样适用。事件让service们可以自主的决定自己的所有事情,提供自由发展所需的自主权。

本文还介绍了事件和查询混合的场景。说到查询,在纯事件驱动方法中,查询完全基于本地的数据集,而没有远程查询。本地数据集则是通过事件触发来更新状态。然而,很多时候,基于请求驱动的查询方式在很多时候也是比较方便的,因为本地数据集的方式,状态的同步更新确实是一件更加需要成本的事情。

然后介绍了单一写入者原则。单一写入者让我们数据更新有了统一的入口,有助于我们通过单个代码路径(尽管不一定是单个进程)来排除一致性,验证和其他写入路径(writepath问题。

最后讨论了集群上下文模型。每个领域模型组成一个独立的区域,然后再由多个区域共同组成一个领域模型集群,模型之间又通过Kafka来交互。每个领域模型里又可以包含几种模式的混合,比如EventsViewsUI,这些里边可以既有事件驱动模式,又有请求驱动模式。

综上所言,现在微服务"交互方式"观念应该转变了。RESTRPC是属于同一种类型的,都属于请求驱动(request-driven)模式,这种模式很多时候是同步的,一条链上挂了很多的服务调用,势必在链条变长后,性能堪忧。本文为大家介绍了一个构建微服务的新的工具——事件驱动(event-driven)的模式,它解耦、异步,带来了更好的扩展性和性能。


电话咨询
邮件咨询
在线地图
QQ客服