从经典架构项目中解析事件驱动架构

接上篇,咱们采用了领域驱动的开发方式,使用了充血模型,享受了他的好处,可是也不得不面对他带来的弊端。这个弊端在分布式的微服务架构下面又被放大。前端

事务一致性程序员

事务一致性的问题在Monolithic下面不是大问题,在微服务下面倒是很致命,咱们回顾一下所谓的ACID原则数据库

Atomicity – 原子性,改变数据状态要么是一块儿完成,要么一块儿失败json

Consistency – 一致性,数据的状态是完整一致的后端

Isolation – 隔离线,即便有并发事务,互相之间也不影响架构

Durability – 持久性, 一旦事务提交,不可撤销并发

在单体服务和关系型数据库的时候,咱们很容易经过数据库的特性去完成ACID。可是一旦你按照DDD拆分聚合根-微服务架构,他们的数据库就已经分离开了,你就要独立面对分布式事务,要在本身的代码里面知足ACID。框架

对于分布式事务,你们通常会想到之前的JTA标准,2PC两段式提交。我记得当年在Dubbo群里面,基本每周都会有人询问Dubbo啥时候支撑分布式事务。实际上根据分布式系统中CAP原则,当P(分区容忍)发生的时候,强行追求C(一致性),会致使(A)可用性、吞吐量降低,此时咱们通常用最终一致性来保证咱们系统的AP能力。固然不是说放弃C,而是在通常状况下CAP都能保证,在发生分区的状况下,咱们能够经过最终一致性来保证数据一致。异步

例:分布式

在电商业务的下订单冻结库存场景。须要根据库存状况肯定订单是否成交。

假设你已经采用了分布式系统,这里订单模块和库存模块是两个服务,分别拥有本身的存储(关系型数据库),

在一个数据库的时候,一个事务就能搞定两张表的修改,可是微服务中,就无法这么作了。

在DDD理念中,一次事务只能改变一个聚合内部的状态,若是多个聚合之间须要状态一致,那么就要经过最终一致性。订单和库存明显是分属于两个不一样的限界上下文的聚合,这里须要实现最终一致性,就须要使用事件驱动的架构。

事件驱动实现最终一致性

事件驱动架构在领域对象之间经过异步的消息来同步状态,有些消息也能够同时发布给多个服务,在消息引发了一个服务的同步后可能会引发另外消息,事件会扩散开。严格意义上的事件驱动是没有同步调用的。

例子:

在订单服务新增订单后,订单的状态是“已开启”,而后发布一个Order Created事件到消息队列上

库存服务在接收到Order Created 事件后,将库存表格中的某sku减掉可销售库存,增长订单占用库存,而后再发送一个Inventory Locked事件给消息队列

订单服务接收到Inventory Locked事件,将订单的状态改成“已确认”

有人问,若是库存不足,锁定不成功怎么办? 简单,库存服务发送一个Lock Fail事件, 订单服务接收后,把订单置为“已取消”。

好消息,咱们能够不用锁!事件驱动有个很大的优点就是取消了并发,全部请求都是排队进来,这对咱们实施充血模型有很大帮助,咱们能够不须要本身来管理内存中的锁了。取消锁,队列处理效率很高,事件驱动能够用在高并发场景下,好比抢购。

是的,用户体验有改变,用了这个事件驱动,用户的体验有可能会有改变,好比原来同步架构的时候没有库存,就立刻告诉你条件不知足没法下单,不会生成订单;可是改了事件机制,订单是当即生成的,极可能过了一会系统通知你订单被取消掉。 就像抢购“小米手机”同样,几十万人在排队,排了好久告诉你没货了,明天再来吧。若是但愿用户当即获得结果,能够在前端想办法,在BFF(Backend For Frontend)使用CountDownLatch这样的锁把后端的异步转成前端同步,固然这样BFF消耗比较大。

没办法,产品经理不接受,产品经理说用户的体验必须是没有库存就不会生成订单,这个方案会不断的生成取消的订单,他不能接受,怎么办?那就在订单列表查询的时候,略过这些cancel状态的订单吧,也许须要一个额外的视图来作。我并非一个理想主义者,解决当前的问题是我首先要考虑的,咱们设计微服务的目的是本想是解决业务并发量。而如今面临的倒是用户体验的问题,因此架构设计也是须要妥协的:( 可是至少分析完了,我知道我妥协在什么地方,为何妥协,将来还有可能改变。

针对上面的技术我特地整理了一下,有不少技术不是靠几句话能讲清楚,因此干脆找朋友录制了一些视频,不少问题其实答案很简单,可是背后的思考和逻辑不简单,要作到知其然还要知其因此然。若是想学习Java工程化、高性能及分布式、深刻浅出。微服务、Spring,MyBatis,Netty源码分析的朋友能够加个人Java进阶群:582505643,群里有阿里大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给你们

多个领域多表Join查询

我我的认为聚合根这样的模式对修改状态是特别合适,可是对搜索数据的确是不方便,好比筛选出一批符合条件的订单这样的需求,自己聚合根对象不能承担批量的查询任务,由于这不是他的职责。那就必须依赖“领域服务(Domain Service)”这种设施。

当一个方法不便放在实体或者值对象上,使用领域服务即是最佳的解决方法,请确保领域服务是无状态的。

咱们的查询任务每每很复杂,好比查询商品列表,要求按照上个月的销售额进行排序; 要按照商品的退货率排序等等。可是在微服务和DDD以后,咱们的存储模型已经被拆离开,上述的查询都是要涉及订单、用户、商品多个领域的数据。如何搞? 此时咱们要引入一个视图的概念。好比下面的,查询用户名下订单的操做,直接调用两个服务本身在内存中join效率无疑是很低的,再加上一些filter条件、分页,无法作了。因而咱们将事件广播出去,由一个单独的视图服务来接收这些事件,并造成一个物化视图(materialized view),这些数据已经join过,处理过,放在一个单独的查询库中,等待查询,这是一个典型的以空间换时间的处理方式。

通过分析,除了简单的根据主键Find或者没有太多关联的List查询,咱们大部分的查询任务能够放到单独的查询库中,这个查询库能够是关系数据库的ReadOnly库,也能够是NoSQL的数据库,实际上咱们在项目中使用了ElasticSearch做为专门的查询视图,效果很不错

限界上下文(Bounded Context)和数据耦合

除了多领域join的问题,咱们在业务中还会常常碰到一些场景,好比电商中的商品信息是基础信息,属于单独的BC,而其余BC,无论是营销服务、价格服务、购物车服务、订单服务都是须要引用这个商品信息的。可是须要的商品信息只是所有的一小部分而已,营销服务须要商品的id和名称、上下架状态;订单服务须要商品id、名称、目录、价格等等。这比起商品中心定义一个商品(商品id、名称、规格、规格值、详情等等)只是一个很小的子集。这说明不一样的限界上下文的一样的术语,可是所指的概念不同。 这样的问题映射到咱们的实现中,每次在订单、营销模块中直接查询商品模块,确定是不合适,由于

商品中心须要适配每一个服务须要的数据,提供不一样的接口

并发量必然很大

服务之间的耦合严重,一旦宕机、升级影响的范围很大。

特别是最后一条,严重限制了咱们得到微服务提供的优点“松耦合、每一个服务本身能够频繁升级不影响其余模块”。这就须要咱们经过事件驱动方法,适当冗余一些数据到不一样的BC去,把这种耦合拆解开。这种耦合有时候是经过Value Object嵌入到实体中的方式,在生成实体的时候就冗余,好比订单在生成的时候就冗余了商品的信息;有时候是经过额外的Value Object列表方式,营销中心冗余一部分相关的商品列表数据,并随时关注监听商品的上下级状态,同步替换掉本限界上下文的商品列表。

下图一个下单场景分析,在电商系统中,咱们能够认为会员和商品是全部业务的基础数据,他们的变动应该是经过广播的方式发布到各个领域,每一个领域保留本身须要的信息。

保证最终一致性

最终一致性成功依赖不少条件

依赖消息传递的可靠性,可能A系统变动了状态,消息发到B系统的时候丢失了,致使AB的状态不一致

依赖服务的可靠性,若是A系统变动了本身的状态,可是还没来得及发送消息就挂了。也会致使状态不一致

我记得JavaEE规范中的JMS中有针对这两种问题的处理要求,一个是JMS经过各类确认消息(Client Acknowledge等)来保证消息的投递可靠性,另外是JMS的消息投递操做能够加入到数据库的事务中-即没有发送消息,会引发数据库的回滚(没有查资料,不是很准确的描述,请专家指正)。不过如今符合JMS规范的MQ没几个,特别是保一致性须要下降性能,如今标榜高吞吐量的MQ都把问题抛给了咱们本身的应用解决。因此这里介绍几个常见的方法,来提高最终一致性的效果。

使用本地事务

仍是以上面的订单扣取信用的例子

订单服务开启本地事务,首先新增订单;

而后将Order Created事件插入一张专门Event表,事务提交;

有一个单独的定时任务线程,按期扫描Event表,扫出来须要发送的就丢到MQ,同时把Event设置为“已发送”。

方案的优点是使用了本地数据库的事务,若是Event没有插入成功,那么订单也不会被建立;线程扫描后把event置为已发送,也确保了消息不会被漏发(咱们的目标是宁肯重发,也不要漏发,由于Event处理会被设计为幂等)。

针对上面的技术我特地整理了一下,有不少技术不是靠几句话能讲清楚,因此干脆找朋友录制了一些视频,不少问题其实答案很简单,可是背后的思考和逻辑不简单,要作到知其然还要知其因此然。若是想学习Java工程化、高性能及分布式、深刻浅出。微服务、Spring,MyBatis,Netty源码分析的朋友能够加个人Java进阶群:582505643,群里有阿里大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给你们

缺点是须要单独处理Event发布在业务逻辑中,繁琐容易忘记;Event发送有些滞后;定时扫描性能消耗大,并且会产生数据库高水位隐患;

咱们稍做改进,使用数据库特有的MySQL Binlog跟踪(阿里的Canal)或者Oracle的GoldenGate技术能够得到数据库的Event表的变动通知,这样就能够避免经过定时任务来扫描了

不过用了这些数据库日志的工具,会和具体的数据库实现(甚至是特定的版本)绑定,决策的时候请慎重。

使用Event Sourcing 事件溯源

事件溯源对咱们来讲是一个特别的思路,他并不持久化Entity对象,而是只把初始状态和每次变动的Event记录下来,并在内存中根据Event还原Entity对象的最新状态,具体实现很相似数据库的Redolog的实现,只是他把这种机制放到了应用层来。

虽然事件溯源有不少宣称的优点,引入这种技术要特别当心,首先他不必定适合大部分的业务场景,一旦变动不少的状况下,效率的确是个大问题;另一些查询的问题也是困扰。

我认可我是标题党, 为何要写这篇充满争议的文章?目前架构师这个职位特别火热,程序员的目标都是成为一个使人尊敬的架构师。可是咱们真的理解架构师应该作些什么?不少人把架构师和框架师等同起来,认为研究框架多的才是架构师

下面说的状况请勿对号入座。

盲目的追新:

技术人员的喜爱每每是什么技术流行就追什么技术。如今的技术发展快,先后端不断涌现各类框架,咱们巴不得把这些框架都用在本身的项目里才行,要否则怎么好意思和别人打招呼啊。

我亲身经历,有个技术人员必定要把原来单元测试框架的xml初始数据改成json,他的原话是”json看的更舒服”,可是改完后,咱们的单元测试反而难落地了,缘由是原来的单元测试框架有个工具是能够将表中的数据自动生成xml的,而改为json后,咱们必须手写json数据了。 他的喜爱不包括给你们更好用的工具。

按技术站队,以结果反推:

不少人把手段当成了目的,成为了框架的信徒。用了Java开发,你的设计就必定是面向对象的?用了Spring boot就是微服务了吗?这些荒唐的事情却在技术圈不断发生,技术人员甚至会按照语言、框架造成不一样的圈子,各类技术圈互相鄙视,互相踩,真相此时没法越辩越明,反而把技术方向带歪了。

技术要和实际场景结合

架构师也要深刻了解掌握技术,可是更多的是了解技术的优劣和使用场景,而不是简单的生搬硬套。以如今流行的微服务架构来讲,Netflix使用RESTful接口做为通信,咱们是否是要把公司的用了n年的基于TCP的RPC换成RESTful接口,由于根据Netflix的实践,RESTful能够更好的解耦、更强的伸缩性等优势,还能支持多种语言开发,互通性好。可是咱们须要对RESTful完全的理解清楚:

RESTful接口不简单是是http+json,Richardson成熟度模型中哪一个层级更合适咱们的内网API通信,HATEOAS是否须要?

RESTful的核心是资源,如何在微服务中抽象资源概念,如何将基于过程的RPC调用平滑的迁移到RESTful上?

多语言开发是快,可是后续维护如何找到稳定的Go、Scala、xxx语言程序员来源?

以上的问题应该是架构师在考虑引入新技术的时候的重点,其中对技术优劣和架构思路是核心

相关文章
相关标签/搜索