1、事前
你相信吗?曾经有一段日子,我几乎没接到过合格的产品需求。前端
开局几句话,技术全靠猜。程序员
老是觉得简单的需求
曾经,我从产品那里接到过这么一个需求:数据库
对系统的用户进行分级,不一样级别的用户有不一样的福利。缓存
依然如常,无图无文档,只是这么一句话。我知道,需求一句话,分析五日功嘛。为了项目能持续发展,我只好本身分析本身搞了。安全
从业务上看,目前的用户对象尚无等级一说,咱们先为用户对象加上个级别属性。又由于不一样的用户等级,可享受到不一样的福利。好比:达到 3 级的用户,能够享受购物 9.5 折优惠,物流费用全免,客服快速回复等。微信
因此,我作出设计以下:多线程
首先,我把每一个等级用户该享受的福利放到一个列表里。这个用来供前端展现用户当前可享受到的福利。架构
而后,在每一项福利中,我去设定一个可享受此福利的最低级别。只有用户的级别超过这个最低级别的时候,才能够享受到此项福利。好比,支付优惠 9.5 折,我只须要在支付服务中打包个支付权利 9.5 折这种东西,而后设定个最低级别便可。微服务
这事儿看着是如此简单,因此,实现方案也没什么特殊的。当用户每次升级的时候,我只须要更新用户级别便可。性能
这个时候,需求比较初级,要求也不高。在知足升级条件后,须要用户主动点击升级。同时,再填写一些相关信息,申请一些专属的福利就能够了。
好,设计,开发,上线一条龙走起来!
需求变成坑
过了一阵子,咱们的运营们敢于探索,勤于开拓,去搞了一堆资源互换回来。当我据说此事时,内心已经预感不妙了。
果真,没两天,咱们的产品高高兴兴地通知我,因为兄弟团队愿意和咱们的项目进行合做,所以用户的福利将获得极大的丰富,那些更加丰富的福利全都由兄弟团队提供。
因此,请我简单的搞一下,对接上这些合做方,进一步提高咱们系统的粘性。
如常,依然没有任何文档,我依然只能本身分析。
如今,根据我丰富的被折腾经验,我知道开始有坑了。当我对接合做方接口的时候,他们都须要我传入一些特定的用户标识过去,可让双方共享用户。
需求开始复杂了,不过庆幸的是,我改改代码就能够了,还好还好,我松了口气……
好,设计,开发,上线一条龙走起!
惋惜,咱们的业务就像一群群的蜜蜂同样,你永远不知道他们会给你带来什么样的花朵。
没过太久,产品告诉我,几个兄弟团队想和咱们一块儿搞一次超级大活动。我以为天黑了……
没文档没有产品原型,依然就是微信中的来来每每。
我知道此时,我得往深里想一想了。需求是能够肆意妄为的,而我能阻止业务需求的肆意妄为吗?不能,因此,我要考虑一整套弹性的方案,能应对这些变幻无穷,又漫天飞舞的需求。
2、初见
隐患的伊始
来看看这个见鬼的大活动吧。
首先,按照设计,若是合做方们想要和咱们一块儿大联欢,那么咱们就要把用户升级的信息告诉他们。这样,合做方们才能进行验证,并提供用户级别对应的福利。因此,当咱们的用户升级的时候,我须要每次都把这件事同步给咱们的合做方。
又由于咱们是和多个兄弟团队合做,好比,和物流团队合做,和支付团队合做。在这种状况下,不一样合做方的互动逻辑是分布在不一样的服务中的。
此时,我有两种方案可供选择:
1.在用户服务里,用户升级时,当即主动的经过接口去调用分布在不一样的服务上的相关逻辑,把用户升级这件事同步到合做方那里。可是,这个方案有个很大的问题——由于咱们须要调用其余服务的接口,这就形成服务和服务之间耦合起来了。未来有点小改动,可能都须要咱们改代码。
2.在微服务里,实际上是很推崇使用消息队列的。当用户升级时,我只需发送消息到消息队列中,而后让相关的服务去订阅这个消息便可。这个方案,使用消息队列能够解耦服务之间的关系。
由于微服务自己的目的就是解耦和灵活,而且第二个方案和咱们架构是适配的,所以我选择了第二个方案。
在第二个方案中,正由于消息能够把服务之间进行解耦,因此,当用户升级的时候,我只须要操做用户服务数据库中的用户表进行升级,并把升级这事儿包裹成消息扔到消息队列中便可。
我甚至能够把更新用户表和发送升级消息到队列包装成一个事务。
好,设计,开发,上线一条龙走起!
这就是能应对后续不断变化的技术方案吗?事实证实,并不能,由于,这套方案即将会被变化的需求给完全击垮。
问题的大爆发
斗转星移,时空变幻。需求如滚滚的流水般涌来,而咱们的技术方案如同一套不管如何加强也不够健壮的大坝。
通过几度需求的变换,此时用户升级已经变成了知足条件后自动升级;咱们合做的兄弟团队也日益增多;咱们的服务也越拆越多……在这些汩汩涌出的变化中,问题已经如同潜伏在水底的鳄鱼,即将爬上岸来猎取几个程序员来祭天了。
问题的迹象一开始出如今用户升级的数据上。那时,咱们接连被运营们提的问题所困扰。
有些运营人员发现,某些用户升级过快了,用户的升级速度已经远远超出了当初设计时预估的速度了。
而这种过快的升级不只使得运营人员没法及时构思和设计后续的运营活动,还使得咱们的运营成本快速的上涨,并所以给公司经营带来了必定的损失。
固然,如同以往同样,业务是历来不会出错的,出错的永远是技术。这不,出问题的缘由都给咱们安排的明明白白了:
极可能是程序出了 bug,由于出了某些技术性的故障,致使用户升级的时候没有一级级的升上去,出现了跳跃性的升级…………
在追踪问题的时候,咱们猛然发现了这个技术方案的一个缺陷:因为根本没有预料到用户升级的重要性,咱们的不少用户升级相关的日志并未开启,而且没有存储任何用户升级的历史记录。
这瞬间成了一笔糊涂帐,我无 fuck 可说。
雪上加霜的是,又有用户们投诉,他们老是在某些时候会出现一些卡顿。咱们再一查,发现是用户升级致使的数据库问题。
最先的设计是用户升级直接更新数据库表,可是大意了:
- 当用户数量出现大涨的时候。
- 新用户初期升级难度小,因此升级很频繁。
忽略了这两个因素,这就形成了咱们的数据库有点承受不住这种频繁的更新。
并且,在查这些问题的时候,之前有些用户投诉的问题也随之被挖了出来。好比,用户升级后有些福利却没有给他们,悲催的是这些痕迹也没有被完整的留下来……
糊涂帐加糊涂帐成了笔烂帐。
啊,我要被祭天了吗?
跺脚后智商从新占领高地
如今来看看咱们要面临的问题吧。
首先出场的是用户升级无法追根溯源的问题。由于咱们每次用户升级,须要通知相关的服务,而后还得保证每一个相关的服务处理成功了,到此时,用户升级才算真正的成功。因此,为了能还技术们一个清白,能别搞得成为烂帐,就必须把用户的每次升级给记录下来,而且还得把每一个相关服务对升级事件的处理也记录在案。
下一个要解决的小兄弟是数据库更新的问题。这个数据库更新该怎么办?缓存后同步?那缓存自己的更新出现了问题怎么办?验证呗!怎么验证?每次升级时候去和历史记录核对一遍吗?
这时候,个人脑壳里开始进入了混沌状态。不知道该怎么办了。
有点着急啊,怎么办呢?只好去看看网上有没有什么方案能够提供一些思路。
最终,这就促成了我对事件溯源(Event Sourcing)模式的初见。
当我看到事件溯源的时候,我脚一跺,我感受个人智商回来了。
事件溯源拯救快被祭天的我
首先,我们看看事件溯源是什么样的。
以我们如今搞得用户升级为例,说一下事件溯源模式:
用户升级时,咱们只须要把用户升级这件事经过 Event Store 这个中间件传给支付服务、物流服务等这些相关的服务。而后,支付服务、物流服务之类的处理完用户升级通知给他们的事件后,会也建立一个事件对象,放到 Event Store 里。
这里的 Event Store 其实主要是用来作两件事:
- 传递事件
- 存储事件历史
那么,事件溯源是怎么来搞定我面临的这些问题的呢?
首先,若是咱们要追根溯源,就须要把用户升级和用户升级后相关服务作得处理都要存起来,造成一个完整的业务链条。有了这个链条,才能被称为追根溯源。
事件溯源模式正好告诉你们,有事儿就要存起来!
其次,当咱们用户升级的时候把事件存储下来以后,咱们还须要实时去更新级别吗?
咱们来分析一下:用户升级的真正目的是什么?从业务角度来讲,其实就是经过提供各类福利去提高用户的活跃度。那么,这件事须要实时吗?彷佛没必要须,由于用户几乎不太可能升级后立刻去使用对应的福利。
好,若是能够不实时,那么用户升级这件事儿就能避免实时更新数据库了。
若是咱们在开始把历史事件存储下来了以后,其实能够在凌晨的时候去定时根据用户级别发生的事件,去把用户的级别升级到正确的级别。
因此能够看到了,事件溯源在这事儿上把个人两个问题全解决了。
这就是我和事件溯源模式的初见。而在从此的技术生涯中,它将会常常陪伴着我。
3、认识
真正认识下事件溯源模式吧
事件溯源总结下来其实只有以下二个核心特色:
1.把触发业务数据变化的缘由包装成了事件对象——若是把这件事儿抽象的看待一下,就是咱们能够把业务中任何须要注意的状况发生变化时,均可以包装成事件。
2.这些包装成事件的业务数据会按照事件发生的顺序,被持久化存储到专门的地方——须要专门说一下这个事件按照顺序存放的问题,在事件溯源模式中,按照事件发生的顺序持久化存储是很是重要的一件事。若是一个模式中的事件没有严格按照事件顺序进行持久化存储,其实很难说这个模式会是一个合格的事件溯源模式。
因此事件溯源模式就作了两件事:
- 定义什么样的业务逻辑能够被定义为事件;
- 把定义好的事件在发生后给按顺序记录下来。
事件溯源常伴吾身
认识到了事件溯源的核心特色后,我在后面的开发生涯里反复的使用了这个模式去帮我解决不一样业务的特定场景的问题。好比订单的状态更新,再好比秒杀活动的性能问题。
在不断地使用事件溯源过程当中,我总结出了须要使用事件溯源的一些场景。当遇到相似的场景时,我老是会第一时间尝试用事件溯源模式来解决问题。
这些场景是:
-
想知道关键数据被更改时,意图、缘由或者目的时;
-
更新数据确实性能出现了问题,一时之间也没办法经过硬件升级或者大规模集群去解决这个问题;
-
还原某些现场,或者想经过一些数据重复的还原线上环境是很是重要的事情;
而事实证实,在这些场景中使用事件溯源也确实不负我望,而且还带来了不少额外的好处:
1.因为事件能够按照顺序存储,因此能够搞成追加方式去持久化,而这种追加操做来持久化事件的方式能够放到前台,对用户体验或者性能要求很高的地方。这样不会引起前台卡顿。同时呢,可让事件能跟水流同样,被引入到后台任务中慢慢处理。
2.事件自己是一种场景记录,因此,利用这些记录的时候,能够根据自身状况,在任何合适的时间,合适的环境,去根据事件去实施或者复现某些业务状态。
3.事件的存储自己能够被当成一种审计日志,只要记录的信息够全,事件溯源自己就会自然的变成可靠安全的审计数据。
4.事件溯源自己能够和各类事件驱动的系统相融合,很是适合扩展和对接各种靠事件驱动的应用和系统。
5.事件溯源不会给已经很是复杂的业务对象增长复杂度。好比,一个订单对象,根据订单对象设计订单表的时候,可能还得搞个备注字段用来存储一些更新时的说明;可能还得搞个最近更新时间记录下最近更新发生在何时;甚至可能因为自己业务状态的复杂,还得特地拆解成几个不一样的状态字段……
总之,随着我对事件溯源认识的逐渐加深,我以为自身已经开始有了微服务专家的气质。
4、不满
固然,太阳底下没啥新鲜事儿。任何新东西的引入总会带来一些不足,同时呢,随着使用事件溯源模式的次数增多,我也愈发认识到了这个模式的不足。
1.要存储的事件数据太多了,致使查询得引入另外一个查询职责分离模式(CQRS),才能解决大部分的查询问题。
2.使用事件溯源的时候因为事件发生的顺序存储很是重要,因此,使用多线程,多进程,集群的时候,就必需要严格保证事件顺序存储的正确性,通常来讲,得给事件对象搞个时间戳不说,可能还得引入全局惟一标识符产生器去产生事件 ID。
3.因为事件自己是个业务对象了,因此,你知道了,它自身必定会进化的。因此,还得考虑老版本新版本的共存问题,这种通常至少得给事件结构弄个版本字段去标识事件对象的版本。
4.事件存下来了,并且大部分时候都是附加形式的顺序存储。这就致使查询事件的时候没办法,只能按照事件标识符和事件的时间之类的作查询,而这样的话,其实就是查询出来了一个事件流。若是要场景重现和分析业务对象状态的时候,就非得把这个事件流给整个从新处理一遍。
5.事件溯源这事儿其实就是人为的松绑了业务的一致性要求。可是,业务须要的一致性问题依然仍是须要另外的处理。好比,咱们搞了电商网站,同时呢,又经过事件溯源模式去落地了库存商品数量更新的业务,又恰巧把库存的存货减小的各类缘由给设计成了不一样的事件,那么,当库存由于非客户下单减小发生时,又刚好客户在下单,这时候,就须要单独的处理他们之间的冲突,去保证状态的一致性。
6.事件这东西自己可能由于业务缘由须要各类传递,而在这期间,无论使用什么方式去传播事件,没人会给你保证事件不会重复传播。这时候,就得考虑处理事件的幂等性。这也是事件溯源带来的麻烦。
5、结尾
事件溯源模式虽然解决了个人不少问题,可是同时又由于引入这个模式,我又增长了很大的工做量。真是金无足赤啊。
也许这世上根本不存在什么溯源模式,有的只是防止背锅的无奈罢了。
你好,我是四猿外,一家上市公司的技术总监,管理的技术团队一百余人。
我从一名非计算机专业的毕业生,转行到程序员,一路打拼,一路成长。
我会把本身的成长故事写成文章,把枯燥的技术文章写成故事。
欢迎关注个人公众号或者加我微信交流。