表数据量大读写缓慢如何优化(2)【查询分离】

上一篇聊到过,冷热分离解决方案的性价比高,但它并非一个最优的方案,仍然存在诸多不足,好比:查询冷数据慢、业务没法再修改冷数据、冷数据多到必定程度系统依旧扛不住,咱们若是想把这些问题一一解决掉,能够用另一种解决方案——查询分离。(注意:查询分离与读写分离仍是有区别的。面试

业务场景二

某 SaaS 客服系统,系统里有一个工单查询功能,工单表中存放了几千万条数据,且查询工单表数据时须要关联十几个子表,每一个子表的数据也是超亿条。数据库

面对如此庞大的数据量,跟前面的冷热分离同样,每次客户查询数据时几十秒才能返回结果,即使咱们使用了索引、SQL 等数据库优化技巧,效果依然不明显。编程

加上工单表中有些数据是几年前的,可是这些数据涉及诉讼问题,须要继续保持更新,所以没法将这些旧数据封存到别的地方,也就无法经过前面的冷热分离方案来解决。多线程

最终采用了查询分离的解决方案,才得以将这个问题顺利解决:将更新的数据放在一个数据库里,而查询的数据放在另一个系统里。由于数据的更新都是单表更新,不须要关联也没有外键,因此更新速度立马获得提高,数据的查询则经过一个专门处理大数据量的查询引擎来解决,也快速地知足了实际的查询需求。架构

经过这种解决方案处理后,每次查询数据时,500ms 内就可获得返回结果,客户不再抱怨了。并发

经过上面这个例子,你们对查询分离的业务场景已经有了必定认知,但若是想掌握整个业务场景,继续往下看吧。异步

什么是查询分离?

关于查询分离的概念,从简单的字面意思上也好理解,即每次写数据时保存一份数据到另外的存储系统里,用户查询数据时直接从另外的存储系统里获取数据。示意图以下:ide

表数据量大读写缓慢如何优化(2)【查询分离】

何种场景下使用查询分离?

当在实际业务中遇到如下情形,则能够考虑使用查询分离解决方案。工具

  • 数据量大;
  • 全部写数据的请求效率尚可;
  • 查询数据的请求效率很低;
  • 全部的数据任什么时候候均可能被修改;
  • 业务但愿优化查询数据的效率;

你们对查询分离这个概念特别熟悉,可是对于查询分离的使用场景一无所知,这可不行,只有了解了查询分离的真正使用场景,才能在遇到实际问题时采起最正确的解决方案。大数据

查询分离实现思路

在实际工做中,若是业务要求必须使用查询分离的解决方案,咱们就务必掌握查询分离的实现思路。也只有这样,咱们真正遇到问题时才能有条不紊地开展工做。

查询分离解决方案的实现思路以下:

  1. 如何触发查询分离?
  2. 如何实现查询分离?
  3. 查询数据如何存储?
  4. 查询数据如何使用?

针对以上问题,咱们一点一点来讨论。

(一)如何触发查询分离?

这个问题说明的是咱们应该在何时保存一份数据到查询数据中,即何时触发查询分离这个动做。

通常来讲,查询分离的触发逻辑分为3种。

(1)修改业务代码:在写入常规数据后,同步创建查询数据。

表数据量大读写缓慢如何优化(2)【查询分离】

(2)修改业务代码:在写入常规数据后,异步创建查询数据。

表数据量大读写缓慢如何优化(2)【查询分离】

(3)监控数据库日志:若有数据变动,更新查询数据。

表数据量大读写缓慢如何优化(2)【查询分离】

经过观察以上3种触发逻辑示意图,发现了什么吗?3种触发逻辑的优缺点对比表以下:

修改业务代码同步创建查询数据 修改业务代码异步创建查询数据 监控数据库日志
优势 一、保证查询数据的实时性和一致性。二、业务逻辑灵活可控 一、不影响主流程 一、不影响主流程。二、业务代码0侵入
缺点 一、侵入业务代码。二、减缓写操做速度。 一、查询数据更新前,用户可能会查询到过期的数据。 一、查询数据更新前,用户可能会查询到过期的数据。二、架构复杂一些

为方便理解表中的内容,咱们来一块儿聊一下其中的几个概念。

什么叫业务灵活逻辑可控?举个例子:通常来讲,写业务代码的人能从业务逻辑中快速判断出何种状况下更新查询数据,而监控数据库日志的人并不能将所有的数据库变动分支穷举,再把全部的可能性关联到对应的更新查询数据逻辑中,最终致使任何数据的变动都须要从新创建查询数据。

什么叫减缓写操做速度?创建查询数据的一个动做能减缓多少写操做速度?答案:不少。举个例子:当你只是简单更新了订单的一个标识,原本查询数据时间只须要 2ms,而在查询数据时可能会涉及重建(好比使用 ES 查询数据时会涉及索引、分片、主从备份,其中每一个动做又细分为不少子动做,这些内容后面文章会聊到),这时创建查询数据的过程可能就须要 1s 了,从 2ms 变成 1s,你说减缓幅度大不大?

查询数据更新前,用户可能查询到过期数据。 这里咱们结合第 2 种触发逻辑来说,好比某个操做正处于订单更新状态,状态更新时会经过异步更新查询数据,更新完后订单才从“待审核”状态变为“已审核”状态。假设查询数据的更新时间须要 1 秒,这 1 秒中若是用户正在查询订单状态,这时主数据虽然已变为“已审核”状态,但最终查询的结果仍是显示“待审核”状态。

根据前面的对比表,总结每种触发逻辑的适用场景以下:

触发逻辑 适用场景
修改业务代码,同步创建查询数据 业务代码比较简单且对写操做响应速度要求不高
修改业务代码,异步创建查询数据 业务代码比较简单且对写操做响应
监控数据库日志 业务代码比较复杂,或者改动代价太大

这里,结合实战案例说明下:在一个真实业务场景中,虽然咱们对业务的代码比较熟悉,可是业务要求每次修改工单请求时响应速度快,咱们最终就选择了修改业务代码异步创建查询数据这种触发逻辑。

(二)如何实现查询分离?

以上共谈到 3 种触发逻辑,第 1 种是同步创建查询数据的过程比较简单,这里就不展开说明,第 3 种监控数据库日志我会在 13 讲具体讲解,因此这部份内容咱们主要围绕第 2 种讨论。

关于第 2 种触发方案:修改业务代码异步创建查询数据,最基本的实现方式是单独起一个线程创建查询数据,不过这种作法会出现以下状况:

  • 写操做较多且线程太多,最终撑爆JVM。
  • 建查询数据的线程出错了,如何自动重试。
  • 多线程并发时,不少并发场景须要解决。

面对以上三种状况,咱们该如何处理?此时使用MQ管理这些这些线程便可解决。

MQ的具体操做思路为每次主数据写操做请求处理时,都会发一个通知给MQ,MQ收到通知后唤醒一个线程更新查询数据,示意图以下:

表数据量大读写缓慢如何优化(2)【查询分离】

了解了MQ的具体操做思路后,咱们还应该考虑如下5大问题。

问题一:MQ如何选型?

若是公司已使用 MQ,那选型问题也就不存在了,毕竟技术部门不会同时维护 2 套 MQ 中间件,而若是公司还没使用 MQ,这就须要考虑选型问题了。

这里我分享两点选型原则,但愿对你有帮助。

(1)召集技术中心全部能作技术决策的人共同投票选型。

(2)无论咱们选择哪一个 MQ ,最终都能实现想要的功能,只不过是易用不易用、多写少写业务代码的问题,所以咱们从易用性和代码工做量角度考量便可。

问题二:MQ宕机了怎么办?

若是 MQ 宕机了,咱们只须要保证主流程正常进行,且 MQ 恢复后数据正常处理便可,具体方案分为三大步骤。

  • 每次写操做时,在主数据中加个标识:NeedUpdateQueryData=true,这样发到 MQ 的消息就很简单,只是一个简单的信号告知更新数据,并不包含更新的数据 id。

  • MQ 的消费者获取信号后,先批量查询待更新的主数据,而后批量更新查询数据,更新完后查询数据的主数据标识 NeedUpdateQueryData 就更新成 false 了。

  • 固然还存在多个消费者同时搬运动做的状况,这就涉及并发性的问题,所以问题与上一篇聊的冷热分离中的并发性处理逻辑相似,这里就不细聊了(有兴趣的同窗能够去看看)。

问题三:更新查询数据的线程失败了怎么办?

若是更新的线程失败了,NeedUpdateQueryData 的标识就不会更新,后面的消费者会再次将有 NeedUpdateQueryData 标识的数据拿出来处理。但若是一直失败,咱们能够在主数据中多添加一个尝试搬运次数,好比每次尝试搬运时 +1,成功后就清零,以此监控那些尝试搬运次数过多的数据。

问题四:消息的幂等消费

在编程中,一个幂等操做的特色是屡次执行某个操做均与执行一次操做的影响相同。

举个例子,好比主数据的订单 A 更新后,咱们在查询数据中插入了 A,但是此时系统出问题了,系统误觉得查询数据没更新,又把订单 A 插入更新了一次。

所谓幂等,就是无论更新查询数据的逻辑执行几回,结果都是咱们想要的结果。所以,考虑消费端并发性的问题时,咱们须要保证更新查询数据幂等。

问题五:消息的时序性问题

好比某个订单 A 更新了 1 次数据变成 A1,线程甲将 A1 的数据搬到查询数据中。不一下子,后台订单 A 又更新了 1 次数据变成 A2,线程乙也启动工做,将 A2 的数据搬到查询数据中。

所谓的时序性就是若是线程甲启动比乙早,但搬运数据动做比线程乙还晚完成,就有可能出现查询数据最终变成过时的 A1。以下图(动做前面的序号表明实际动做的前后顺序):

表数据量大读写缓慢如何优化(2)【查询分离】

此时解决方案为主数据每次更新时,都更新上次更新时间 last_update_time,而后每一个线程更新查询数据后,检查当前订单 A 的 last_update_time 是否跟线程刚开始得到的时间同样,且 NeedUpdateQueryData 是否等于 false,若是都知足的话,咱们就将 NeedUpdateQueryData 改成 true,而后再作一次搬运。

看到这,你心中可能有个疑问:MQ 在这里的做用只是一个触发信号的工具,若是不用 MQ 好像也没啥问题啊,这你就大错特错了,MQ 的做用还很多呢,不信你往下看。

  • 服务的解耦:这样主业务逻辑就不会依赖更新查询数据这个服务了。
  • 控制更新查询数据服务的并发量:若是咱们直接调用更新查询数据服务,因写操做速度快,更新查询数据速度慢,写操做一旦并发量高,会给更新查询数据服务形成超负荷压力。若是经过消息触发更新查询数据服务,咱们就能够经过控制消息消费者的线程数来控制负载。

(三)查询数据如何存储?

咱们应该使用什么技术存储查询数据呢?目前,市面上主要使用 Elasticsearch 实现大数据量的搜索查询,固然还可能会使用到MongoDB、HBase 这些技术,这就须要咱们对各类技术的特性了如指掌,再进行技术选型。

关于技术选型这个问题,我以为不少时候咱们不能单单只考虑业务功能的需求,还须要考虑组织结构。团队最熟悉哪款中间件,花费的成本最小,优先考虑的就应该是这种。

(四)查询数据如何使用?

因 ES 自带 API,因此使用查询数据时,咱们在查询业务代码中直接调用 ES 的 API 就行。

不过,这个办法会出现一个问题:数据查询更新完前,查询数据不一致怎么办?这里分享 2 种解决思路。

  1. 在查询数据更新到最新前,不容许用户查询。(咱们没用过这种设计,但我确实见过市面上有这样的设计。
  2. 给用户提示:您目前查询到的数据多是 1 秒前的数据,若是发现数据不许确,能够尝试刷新一下,这种提示用户通常比较容易接受。

总体方案

以上,咱们已经把四个问题都讨论完了,咱们再一块儿看看查询分离的总体方案,以下图所示:

表数据量大读写缓慢如何优化(2)【查询分离】

总结一下,本篇关于查询分离的架构主要分为四个部分:如何触发查询分离?如何实现查询分离?查询数据如何存储?查询数据如何使用?

历史数据迁移

新的架构方案上线后,旧的数据如何适用新的架构方案?这是实际业务中须要咱们考虑的问题。

在这个方案里,咱们只须要把全部的历史数据加上这个标识:NeedUpdateQueryData=true,程序就会自动处理了。

查询分离解决方案的不足

查询分离这个解决方案虽然能解决一些问题,但咱们也要清醒地认识到它的不足。

不足一: 使用 Elasticsearch 存储查询数据时,注意事项是什么(此方案并未详细展开)?

不足二: 主数据量愈来愈大后,写操做仍是慢,到时仍是会出问题。

不足三: 主数据和查询数据不一致时,假设业务逻辑须要查询数据保持一致性呢?

接下来的文章将会来聊使用elastic search作查询数据的存储系统时须要注意哪些问题,这个问题无论是面试仍是实际工做中,咱们都会碰到。一个技术使用起来并不难,难的是使用这个技术时你会碰到什么问题,你又是如何解决的?

相关文章
相关标签/搜索