转自:https://mp.weixin.qq.com/s/rpiYZkxiLKa77OFw8XaBwA
不堪重负的数据库
张大胖公司的数据库已经不堪重负了。
这个系统最早是两个实习生写的, 按照最初的设计,只是内部用户玩的, 大家可以把一些闲置不用的东西放在上面做交换, 仅此而已,后来为了在互联网的大潮中赚点钱,又包裹上了一层Web的外衣, 让外界也可以访问。
大家没有想到互联网威力如此巨大, 用户量会如此之多, 他们系统使用的Mysql数据库很快就撑不住了。
作为技术负责人的张大胖早已经向老大申请了一笔费用, 专门买了一个高性能的服务器来应对, 但是汹涌而来的用户很快就把高性能给吃得连渣都不剩。
张大胖忧心忡忡: “老大,怎么办? ”
老大也是技术出身,反问道: “你分析过为什么数据库压力这么大吗? ”
“无非就是读写量太大了,尤其是有一些非常复杂的查询, 比如最近24小时最热门的物品之类,需要写很复杂的SQL, 运行起来实在太慢了。”
“我记得咱们俩聊过读写分离啊, 怎么不试一试?”
“老大啊, 你不知道,这实在是不好弄啊, 为了实现读写分离, 得把数据库拆分成master库和slave库, 还比较简单, 但是我们的系统代码也得改啊, 写数据的时候用master 库, 读数据的时候用slave库, 你知道我们这是上个世纪开发的系统,典型的遗留代码, 改动起来太麻烦了。”
老大说:“那也得改啊, 你要知道现在这个系统可是咱们公司最大的收入来源了。 你们要是不想改,就退下来,我只好去找李小疯去做了”
张大胖向来瞧不起马屁精李小疯,技术不咋地,升的到挺快,一起进公司的, 现在已经比自己高一级了。
张大胖赶紧说:“ 别别, 还是我来”
张大胖带着几个弟兄和遗留代码奋战了几个月, 工作量不亚于一次重写。 张大胖深深地体会到,别看现有代码很烂, 但是经过无数人的修补,勉强能工作。 现在自己从头写一遍,出的问题更多,很多小细节考虑不到,被测出了无数Bug。
不过好处也是巨大的,这次重写,理清了业务, 实现了读写分离,还把缓存也用上了, 最后熬了两天两夜,新系统终于上线了。
张大胖想着好日子就要开始了,崭新的代码, 崭新的系统,应该可以撑一段时间。
2复杂的查询
可是新系统上线了一周后,问题又出现了,这次的问题主要集中在一些复杂的SQL查询上,这些SQL查询最要命的得有几十行! 严重地拖累了数据库 !
张大胖找来DBA 小梁过来做优化,小梁看了半天说: “没辙, 你们的业务太复杂了, 你看看有这么多表在做Join,怎么可能快呢?”
张大胖说:“这没办法啊,数据库就是这么设计的啊, 你懂的,无论如何也得满足第一范式吧。 要不这样,你给我们创建一个视图(View) 吧, 把这个复杂的查询给封装起来, 这样我们使用起来就简单了”
“那也是换汤不换药啊, 实际的查询还在, 没有本质的改变, 照样还是慢。”
“唉,这可怎么办, 我们有20多个复杂查询,怎么才能提高速度呢?”
小梁说: “你看看这个超级复杂的查询, 不就是为了获得过去24小时的热门产品吗,要是有个表单独存放就好了 hot_products(id, name, desc, total_sold) , 这样以来一条简单的SQL就搞定”
小梁的话启发了张大胖: 实际上,一套单一的数据库表 对于报表、搜索、事务等不同的行为是不适当的 !
现在复杂的数据查询和简单的数据修改利用的就是同一套领域模型和数据库表, 现在的数据库表主要是为了新增、修改数据而设计的, 对于复杂的查询并不友好。 我们能不能单独的建一套数据库,专门应对查询呢?
有了这个专门的查询库, 用户在界面上发起查询的时候处理起来非常简单, 一条SQL就搞定,甚至都不用通过业务领域层,换句话说数据库模型和展示层是对应的! 再也不用像原来那样从原始数据库表中得到数据,转化成领域对象, 然后再转化成展示层对象, 实在是太麻烦了 !
但是这个专门的查询库该如何更新呢? 更重要的是能不能忍受数据的延迟呢?
3CQRS
张大胖把自己的想法和苦恼给老大讲了下。
老大拍了拍他的肩膀: “看来你小子开窍了啊, 想得挺深入的, 从业务上看数据的延迟可以忍受,比如过去24小时的热门产品,一点点过时的数据对用户不会产生重大的影响。只要你能达到最终一致就可以了。”
“那我们该怎么更新这个专门的查询库呢?”
“我最近在看一个叫做CQRS的东西” 老大说 “ 你遇到的这个问题可以用同样的思路来解决下”
“什么是CQRS ? ”
"Command Query Responsibility Segregation,就是命令(增删改)和查询的责任分离, 你看看这个图"
“这和我刚才的图差不多啊” 张大胖说
“所以说思路是一致的嘛, 在CQRS中, 强调的是读(Query)和写(Command) 的分离 , 它背后的理念是用户读到的数据通常是过时的,比如过去24小时最火的产品, 既然如此, 为什么还要从数据库中读取一遍,转化为领域模型,DTO, VO, 最后在UI层展示呢? 何不直接一点,干脆为‘读’专门建立一个直接的数据源呢? 这新的数据源不一定是关系数据库,可以是Cache ,可以直接存储为xml/json数据, 只要界面查询起来方便即可。 ”
“是,最早我也是这么想的,那这个Event是怎么回事?”
“Event 就是事件喽,例如有人下了一个订单, 导致某个产品已经卖出, 这个时候就可以发布一个产品已经卖出(ProductSold)的事件 , 其中包含产品的ID, 价格,卖出时间等属性, 这样的事件被处理以后,可以变成任意的Read Model,例如过去24小时最火的产品 。”
“奥,原来是这么玩的啊, 通过事件机制把同步变成异步 ” 张大胖说 “ 还有一个问题,如果我们用CQRS, 难道我们的应用需要把所有的Command 和Query完全分开吗, 查询都通过新的数据源? 可是很多查询很简单,直接使用关系数据库就够了啊。 ”
“不,不要把摊子铺得太大, 引入一种新的技术也是需要付出代价的,我们把同步操作变成了异步的操作, 得有良好的事件处理机制才可以。 所以先用这种思路把你的当前问题,也就是复杂查询的问题解决掉吧!” 老大最后拍了板。
(完)