读写分离水太深,你把握不住,让CQRS来

多年之前,那时我正年轻,作技术如鱼得水,甚至一度但愿本身能当一生的一线程序员。程序员

可是我又有两个小愿望想要达成:一个是想多挣点钱;另外一个就是对项目的技术栈和架构选型能多有点主动权。数据库

多挣点钱是由于当时我刚结婚不久,有本身的家庭规划,因此挣钱的欲望也蛮强。缓存

而想有多点技术主动权的缘由则是当时领导很赏识我,有些东西逐渐的放权让我作,我尝到了甜头,因此,也有了本身的一些小野心。服务器

而正巧就在那时候,领导给我了一个如今看来职业生涯中还挺重要的机会。架构

当时,广告联盟正是发展的如火如荼的时候,公司也想参与进去分杯羹,因而决定从零开始搞一套广告平台。并发

而我正好也有些相似的开发经验,且作事还算靠谱,因而,领导便想着让我去当这套系统的技术负责人。高并发

若是我能把系统作好,对我来讲绝对是个证实本身的机会,对之后达成个人两个小愿望有好处。对我诱惑很大。性能

只是,老天给你开了一扇门,就总要给你关一扇窗。这个机会不只仅是我领导看上了,当时,还有另一个部门的老大也瞄上了。学习

不得已,上了高层会议讨论。讨论来讨论去的结果就是学习当时别的公司的作法,内部竞争。测试

两个部门作各作一套平台,而后各放到线上运营一阵子,谁作得好谁就能获得公司全力投入的机会。

好吧,机会变成了冒险。只是到此时,我也并不能退缩。一旦我退缩会连累赏识个人领导,并且未来在公司的发展也会严重受阻,只能冲了。

为了赢得这场竞争,我和这套系统的产品负责人也沟通了许久。最后定下来了两个必须实现的目标:

1. 这套系统功能必定要尽可能多,尤为是提供给相关业务人员的功能要多。

之因此要这样,是由于如今是内部竞争。而对于内部竞争,使用咱们这套系统的业务人员话语权其实很是大,他们的满意度极可能是最终评估的胜负手。

同时,咱们也计划为投放在咱们这套系统的广告主们多准备一些体验度很是好的数据追踪和分析功能,这样能最大的增长咱们产品的吸引力。

2. 这套系统的稳定性和可靠性要求很是高,有时候哪怕为此作一些过分设计和实现也是值得的。

这里要解释下稳定性和可靠性在咱们当时那个场景里的含义。稳定性就是要保证性能是稳定的,也就是说咱们的系统响应时间应该尽全力保证在一个很短的时间内响应。

而可靠性则是咱们的系统应该尽全力保证不出错,由于出错极可能就会形成用户流失,致使咱们的产品失败。

定完目标以及产品给完需求后,我就和团队进入了异常艰苦的开发工做。那时候,我真的是付出了我全身心的心血。

其实,我原本是个享受生活赛过埋头苦干的人。虽然此前工做也很忙碌,可是空闲日子也是过得很惬意的。听听歌,看看电影,有时和老婆找家餐厅享用美食,时不时的也会踢一场酣畅淋漓的足球。

但是,自从开始投入了这套广告系统的开发之后,清闲的日子就一去不复返了。

我记得那时候我下班是踉踉跄跄的走,上班又是踉踉跄跄的来。当时最大的心愿就是有张床,躺下去永远别有人叫醒我。

但是即便这样辛苦,我依然遇到了数不清楚的难题,这些横亘在开发路上的硬骨头,致使个人开发目标一再被调整。

其中最麻烦的,就是高并发的性能问题。

当时个人经验尚浅,Java 说实话周边的生态也并不完善。能用来承载访问的也就是缓存和数据库。同时,因为版权等问题,我还只能选择 MySQL 数据库。

为了解决这些性能问题,我还特地把官方的 MySQL 手册打印了出来,每天钻研。

开始的时候,为了抗住预想中的超高并发量,我采用的是当时很流行的读写分离模式。

可是,实际测试下来,老是有各类不满意的地方。其中最麻烦的就是各类复杂查询的性能。

我说过为了得到内部竞争的胜利,这套系统咱们尽量想去往高并发、多功能这两个目标上靠。因此,为了这两个目标,这套系统其实多了不少方便业务人员使用的功能,而且这个功能设想的目标是:

在高并发下,也依然保持稳定和流畅。

其中,最典型的一个业务就是能够实时更新的广告投放排行功能。

这个广告投放排行需求是这样的:

  • 首先,咱们的用户要能在管理后台看到他们本身的投放广告排行,排名是根据消费的金额和点击次数等指标来排次序。
  • 其次,在咱们的后台,也给业务人员也搞了个这么个排名,不一样的是它是个全局的,是咱们全部客户投放的广告的一个总排行。
  • 而后,这个排名要能实时的根据消费金额和点击次数的变化而变化。固然,这个实时能够搞成准实时,只要别延迟太过也能够。

自己呢,作排行榜因为用的指标比较多,就须要写很复杂的 SQL 去数据库中查询。再加上个须要实时变化,那就得不停的去数据库中查询。

而对于这种状况,我不管如何优化老是得不到满意的结果。若是我缓存这个排行呢,因为这个排行须要各类统计加排序,因此从数据库中查询出来后,还须要各类模型转换,若是并发量上来,查询再转换,性能真的掉的飞快。

那时候,个人压力很是大,脑子一直在想着性能问题,手上的 MySQL 手册翻得都快烂的掉了页。就连回到家睡觉时,眼睛闭上脑海里老是想着如何解决这些问题。

最终上线的时间不断地逼近,手上的项目却死死卡在这些性能难题上难以进展,竞争对手却时不时听到内部竞争对手顺利进行到某某程度的消息。

这一切的一切我快扛不住了,心里劝本身放弃的声音也愈来愈大。

我曾经一度认为本身是一个韧性很是强的人,可是如今看来,其实也就是个再普通不过的打工仔而已。

我要逃避了,我想去和产品商量就这样上线吧,我不想管了,是死是活看老天爷吧,赌对方也遇到我这种问题,甚至还不如我。

只是就在我准备拉上产品最终肯定就这样上线的时候,我心里强烈的不甘阻止了我。我想在我放弃以前,不管如何要知道竞争对手怎么样了,对方有什么方案和思路可供我参考的。

我找遍了我全部公司的熟人,去不停的打探竞争对手的消息。可是,结果并很差,由于对方比我作的更绝,他们进行了封闭式的开发,并且警戒性很是高。

最终,我只获得了一个关键词:CQRS。对方用 CQRS 来解决性能问题!!!

我年少读书,那时尚未手机,老是能一心一意的作好读书这件事,读书效率极高。可是现在有了手机,如今我再读书,老是时不时会分心去看看手机里的信息,有时候为了好好把书读进去,还不得不把手机特地丢在远处,防止分心。

而 CQRS 就是这种思路。这个模式与其说是一种架构模式还不如说是一种思想。

CQRS 认为一套系统里的操做,总共就分为读和写两大类。若是一套系统不专门把读和写专门分开优化,那么系统就像我读书带着手机那样,会一心两用,从而由于彼此影响,致使各自的性能没法达到最优。

因此,读写应该专门的分开,并分别优化。

在 CQRS 里,写这种行为被称为命令,而读行为被称为查询。由于想让他们分开,因此 CQRS 模式中文翻译过来就被称为命令查询权责分离模式

我知道这套思路以后,原本并不在乎,由于乍一看,这套东西其实和我采用的数据库的读写分离是同样的,就是把读写给分开。

可是,个人技术直觉告诉我,这些并无那么简单。

在计算机的世界里,一个名词不会平白无故出现,也不会平白无故的开始流行。若是真的和数据库的读写分离同样,那直接叫数据库读写分离就行了。必定有什么不同了。

我没再知足于中文的搜索结果了,我直接去了 Martin Flower 的网站看原始版本去了。而后,我发现了这样一幅架构图。

再结合他的原文我一会儿明白了,是模型,模型的不一样!

原来的数据库读写分离确实把读写的这两个行为分开了,可是它依然有一个重要的事情没有作,那就是职责的分开。

什么叫职责的分开呢?就是读写双方不要搞同一套模型。而数据库读写分离的问题就在这里,它使用了同一个模型。

使用同一个模型在这里形成的问题是,这个模型因为既要考虑读取数据不能太困难,也要考虑写入数据不能太困难。

而这个偏偏就是违背了 CQRS 中的核心思想:读写完全自由

若是咱们使用 CQRS 思想的话,假设写入不须要关心读取的问题,读取数据也不用关心写入的问题,那么双方是否是能够完全放飞自我了?

好比,写入数据因为不须要考虑读取,那我大可使用 Json 格式,使用 XML 格式之类的非标准格式,甚至直接写个日志均可以。而读取数据则根本不须要考虑写入的问题,我甚至能够弄成一个容易搜索的索引格式来。

而 CQRS 在我看来,正是解决卡死个人性能问题的灵丹妙药。

以广告排行这个问题为例,广告排行麻烦就麻烦在,每次加载排行榜须要有很复杂的查询,去数据库中读取数据。

若是能完全地把排行榜的读取排行榜依赖的那些点击、消费指标的更新分开,那我苦恼的排行榜性能问题就能迎刃而解。

我费劲心思后,仿照 CQRS 的原版思想搞了一个这样的设计思路:

这里,数据统计就是广告排名须要的点击、消费等数据。这些数据会被放到一个单独的数据库中,这个数据库只用来写入,不考虑读。

而后,展现广告排行的功能自己又会单独从缓存中把广告排行的模型直接读取出来展现出去,而不用专门再作什么转换了。也不存在什么复杂查询的问题。

可是,咱们的需求是要准实时的让广告排行根据点击、消费等数据自动更新,那么若是写入数据和读取数据模型分开了,该怎么办呢?

多年之前,当我第一次在网上买东西的时候,内心有个疑问:我下了个订单,卖我东西的商家是怎么知道的?莫非要一直盯着?

这个问题到我亲自开发电商系统的时候才知道,当咱们下单的时候,须要发一个通知给对应的商家,告诉商家哪一个客户购买了哪一个商品。

因此,广告排行自动更新的解决方案有了,和电商下单通知商家的道理同样。当有数据写入的时候,咱们把写入的数据复制一份通知给读取数据的模型就能够了。

好,如今整套逻辑完整了。

可是,我并无急于立刻把 CQRS 这套模式去应用到实际的项目当中。由于,我发现我居然不知道 CQRS 这套模式的缺点是什么。

要知道,世界上还不存在完美的解决方案,全都是既有优势又有缺点的。而 CQRS 我居然以为很完美的解决了个人问题,这说明我对这套模式的认知还存在问题。

当时,离约定的上线时间已经愈来愈近了,差很少还剩一周时间。我真的很想闭眼把方案实施下去。

可是,不行,我这我的作事向来喜欢把事情想得通透,把事物认知的十分清楚后再去作。

我决定冒险花两天去实现两个功能点,而后亲自体验一下引入 CQRS 的得与失。

当两天后,我终于发现了问题:引入 CQRS 的模式后,最大的问题在于引入了过分的复杂性

因为须要读和写分开,那么咱们开发的工做量无形中被加大了一倍。又引入 CQRS,这变得更复杂了。

由于咱们发现,不一样的功能,只有使用不一样的读取或者写入模型才能充分用上 CQRS 的优势。

好比,广告排行可能使用了缓存中间件去存取现成的排名。根据关键字搜索各类合适的广告,可能就得考虑开源的搜索引擎中间件。每引入一种都会增长开发成本、服务器成本,以及更多的复杂度。

最终,咱们的广告系统按时上线了。

只不过,并无普遍的采用 CQRS 模式,我只是把最重要的功能点用上了 CQRS,其他的有关性能的问题,我决定暂时放下。

之因此这样,是由于我以为大部分的问题,实际上是咱们过分设计引起的。即便所以我失败了,我也认了。

我并不想为本身亲手打造的系统埋下巨大的隐患,更不想给团队带来无谓的工做量,我不想卷成这样。

上线后,我是如此忐忑,尤为是在上线运营的头两个月。

我不知道本身的妥协是否会诱发巨大的问题,我也不知道本身的所做所为是否是真的是对的。

两个系统的竞争在上线两个月后就有结果了。

这么快的获得结果,偏偏就是由于个人对手普遍的使用了 CQRS 模式。

他从一开始设计的时候,就想着一举成名,他的系统里引入了七八种中间件。把大量的功能拆分红了读写两部分,而这引起了巨大的灾难,过分的复杂性,致使整个系统难以控制。

其中最头痛的就是,因为引入 CQRS,他们必须经过消息的传递去沟通读写两套组件。

可是,当读取组件收到消息后,却发现写入失败了。致使用户看到了对应的数据后,过一段时间,却发现数据和之前看到的对不上了。

好比,点击次数,开始看到的是 1000 次,结果两个小时后,发现变成了 999 次了。

这类问题天天都在出现,而他们由于系统太复杂了,查问题、定位问题、解决问题的时间被大大拉长。最后,客户们纷纷不干了,公司只好把客户转到了我这边的平台上。

竞争结束了,我胜利了,但是我真的没法高兴地起来。由于今天他由于错误的引入新技术失败了,那明天我又未尝不会由于误用新技术新思想而失败呢?今日的他又未尝不是明日的我?

愿天下程序员凡事深思熟悉,谨言慎行!


你好,我是四猿外,一家上市公司的技术总监,管理的技术团队一百余人。

我从一名非计算机专业的毕业生,转行到程序员,一路打拼,一路成长。

我会把本身的成长故事写成文章,把枯燥的技术文章写成故事。

欢迎关注个人公众号,关注后回复【666】可领取我整理的一些珍藏技术资料。

相关文章
相关标签/搜索