微服务存储实现2之查询分离

为什么要用查询分离

  冷热分离虽然可以将热点数据和冷数据分开存储,提升了热点数据的查询效率,但是冷数据的查询效率依然不高,且无法承受复杂的查询和统计。此外,针对于一些业务数据不是特别好区分是热数据还是冷数据,因此用冷热分离就不好实现。

  MySQL的主从架构中,可以将主库设置专门用于写操作,从库则用于读操作,根据不同的请求操作不同的库,彼此不会抢占数据库资源而且,主库用InnoDb存储引擎,从库使用MyISM, MyISM不支持事务, 但是查询性能更好。这个方案适用于数据库高并发的场景,但是主从同步会拖累主库的负载,且当数据量比较大时,效率提升有限。

  为了避免数据库主从架构中数据同步的影响,以及存储数据量大的问题查询库可以考虑使用专门用于处理大数据量的查询引擎来解决。主库依旧是负责更新操作,由于不需要关联且没有外键,既不会占用数据库资源,写性能也有较好的提升。

  综合上面的介绍,当数据不那么方便进行冷热区分,但是存储量比较大,并发数量比较大,查询数据的效率也有一定的要求时,比较需要将这部分大量的数据进行存储,并且还要方便搜索,而查询分离就是解决这些问题的。

概念与使用场景

什么是查询分离?
查询分离即每次写完数据之后保存一份数据到其它的存储系统里面,用户查询数据时直接从中获取。

哪些场景使用查询分离?
1 数据量大:比如单表行数上千万
2 查询数据的响应效率很低:表数据量大,或者关联查询太过复杂,导致查询很慢的情况。
3 所有写数据请求的相应效率尚可
4 所有数据任何时候都可能被修改或者查询,这一点冷热分离是做不到的。

实现思路:

1 如何触发查询分离
第一种:修改业务代码,在写入常规数据后同步更新查询数据,该实现可以保证查询数据的实时性和一致性,但是存在业务代码的入侵,减缓了写操作的响应。
第二种:修改业务代码,在写入常规数据后异步更新查询数据,该方式不影响主流程,但是存在查询数据更新前,用户可能会查到过时数据。但是写操作响应比较快。
第三种:监控数据库日志,如有数据变更,则更新查询数据。这个设计不会影响业务代码,但是在查询数据更新前,用户可能会查到过时数据,此外这种实现架构比较复杂。

2 如何实现查询分离
这里我们针对于第二种方案说一下具体的实现细节。由于是异步实现的,有以下问题需要考虑:

  • 一个写操作就要新建一个线程去更新查询数据,因此当写操作太多时,就要加以控制,以免拖垮JVM
  • 创建查询数据的线程出错时,应该考虑自动重试
  • 需要考虑多并发场景

为了解决写操作太多时的流量太大,以及结合异步的场景,我们使用MQ进行流量晓峰和异步解耦。当每次主程序处理主数据写操作时,就发一个通知给MQ,MQ收到通知后唤醒一个线程来更新查询数据

了解MQ的大致思路之后,还要考虑以下四个问题:



问题1 MQ的选型:
MQ的类型目前主要有RabbitMQ、RocketMQ、Kafka、Active MQ, Redis等。在并发量不高时,无论哪一种MQ都可以实现想要的功能。只不过存在是否易用、业务代码多少的区别。

问题2 MQ宕机了怎么办:
首先,当主数据库的数据更新后发送通知给MQ,但是MQ宕机了,此时MQ没有这条消息,出现消息丢失;其次,MQ收到消息,然后消费者消费消息,但是MQ宕机,MQ不知道消费者是否成功消费,可能存在消息重复投递
MQ宕机,只需要保证主流程正常进行,且MQ恢复后数据正常处理即可,具体方案分为3步。

  1. 每次进行写操作时,在主数据中加标识NeedUpdateQueryData=true,这样发到MQ的消息就只是一个简单的信号告知需要更新数据。MQ的消费者获取信号后,先批量查询待更新的主数据,然后批量更新查询数据,更新完成后主数据标识更新为false
  2. 针对于第一种MQ宕机导致消息丢失,等待MQ恢复后,即使前面的数据没有更新,但是当主库其它数据更新时,就会发送通知信号,那么触发消费者查询主库中NeedUpdateQueryData=true的所有数据,然后更新到查询数据库。当然这个地方需要等待下一次的消息通知,有一定的延时,可以考虑增加定时任务。
  3. 针对于第二种MQ宕机呆滞消息重复消费,等待MQ恢复后,如果前面的消费者更新成功,那么对应的主库数据NeedUpdateQueryData=false。当MQ恢复后,即使又通知消费者消费,但是消费者线程在主库根据标识查询需要更新的数据时不会把之前已经跟新的数据查询到

问题3更新查询数据的线程失败了怎么办?
如果更新失败,NeedUpdateQueryData就不会更新,后面的消费者依然可以处理需要更新的数据。但是如果一直失败,可以再主库中增加尝试迁移次数,失败加1,成功后清理,以便监控那些失败次数过多的数据。

问题4:消息的幂等性
当把主库需要更新的数据同步到查询数据库后,更新主库的标识NeedUpdateQueryData为false时失败了,那么下一次消息可能再次同步到查询数据库.这样就会出现幂等消费.针对这种情况可以考虑把同步数据与更新标识放在一个事务中.


 

3 查询数据如何存储
目前可用于大数据量搜索查询的技术有Elasticsearch, MangoDB, HBase这些技术.HBase的设计初衷是用来作海量存储的,复杂查询销量不是很高.Elasticsearch对查询的扩展性比较好,后面以ES为例,对查询数据库进行进一步说明.

4 查询数据如何使用

ES自带API,在查询业务中直接调用ES的API即可.

5 历史数据如何迁移

只需要将历史数据加上标识NeedUpdateQueryData=true,那么程序就会自动处理.

ES相关介绍:
Elasticsearch是分布式开源搜索和分析引擎中比较实用的工具.在使用需要注意关注如下几点:
1 es不是准实时的 当更新数据至es且返回成功提示时, 会发现通过es查询返回的数据仍然不是最新的, 这是因为修改的记录存放到Memory Buffer时,是不能被搜多到,需要等到Refresh(默认1s), 将memory Buffer中的数据写入segment,
并存放在File System Cache中,这时的数据可以被搜索了.
2 es宕机恢复之后, 数据丢失. 上面提高每隔1秒(根据配置)Memory Buffer中的数据会被写入segment,此时这部分数据可被搜索到,但是还没有持久化,一旦系统宕机,就会出现数据丢失.当然可以修改后相关配置,将刷新的策略进行改变,但是需要考虑到性能的影响.
3 分页越深, 查询效率越低: es的读操作主要分为两个阶段, Query Phase, Fetch Phase.
Query Phase: 协调节点把请求分发到所有分片, 每个分片在本地查询后建一个结果集队列, 将命令中的document Id,以及所搜分数存档在队列中返回给协调节点.协调节点建立全局队列,随后将所有的结果集并进行全局排序.
Fetch Phase: 协调节点现根据结果集里的Document Id向所有分片获取完成的Document,然后所有分片返回完整的Document给协调节点,最后协调节点将结果返回给客户端.
在es查询过程中,如果search方法带有from 和size参数,那么集群需要给协调节点返回分片数*(from + size)条数据, 然后在单机上进行排序, 最后给客户端返回size大小的数据.为了控制性能,可以使用Elas-ticsearch中的max_result_window进行配置,这个数据默认为10000,当from+size>max result window时,Elasticsearch将返回错误。

 

posted @   小兵要进步  阅读(40)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?

侧边栏
点击右上角即可分享
微信分享提示