实习过程中碰到的冷热数据分离方案与阿里的AnalyticDB

368 阅读13分钟

前言

最近我的需求都差不多做完了,我没事的时候会看看文档和代码,发现了一个关于数据量过大,对数据进行冷热数据分离方案,打算总结总结分享给大家。 还有就是我最近接了一个考生分页查询的接口,联了六张表,leader叫我查ADB,我一脸懵逼,第一次听这个东西,后来才了解到这是一个阿里提供的一个云数据库的服务,后面让我一一道来。

f75346c234d22f239330418f5743c44e.jpeg

冷热数据分离

1.背景 考试服务经过四年的沉淀,答题数据超过6000万(不算重复答题),安排考试用户超过800万,由于数据量过多,系统关联慢查询突增,需要进行冷热数据隔离。

2.什么是冷热数据分离 冷热分离就是在处理数据的时候将数据分成冷库和热库,冷库存放的是已经走到最终状态的数据,同时也是不常使用的数据;热库存放的未走到最终状态的数据,还需要在进行变更的、经常使用的数据。

3.什么情况下要使用冷热数据分离 数据走到终态后只有读没有写的需求,比如考试完结状态。
用户能接受新旧数据分开查询,比如有些考试系统默认只让查询1个年内的考试,如果要查询1年前的考试,还需要访问其他的页面.

4.冷热分离的实现思路

4.1冷热数据都用mysql。

4.2如何判断一个数据是冷数据还是热数据? 一般而言,在判断一个数据到底是冷数据还是热数据时,主要采用主表里一个字段或多个字段的组合作为区分标识。 这个字段可以是时间维度,比如订单的下单时间,可以把3个月前的订单数据当作冷数据,3个月内的订单数据当作热数据。当然,这个字段也可以是状态维度,比如根据订单状态字段来区分,将已完结的订单当作冷数据,未完结的订单当作热数据。 还可以采用组合字段的方式来区分,比如把下单时间小于3个月且状态为已完结的订单标识为冷数据,其他的当作热数据。而在实际工作中,最终使用哪种字段来判断,还是需要根据实际业务来决定的。

4.3如何触发冷热数据分离? 通过定时扫描数据库的方式来触发。通过类似于xxl-job的分布式调度平台配置一个定时任务。这个定时任务每隔一段时间就扫描一次热数据库里面的订单表,找出符合冷数据标准的订单数据,进行冷热分离。 比如业务需求是已经关闭超过1个月的订单视为冷数据,这种场景下,订单变更的那一瞬间,即使订单已经关闭了,也不能将其视为冷数据,而必须再等待1个月。这样的情况非常适合使用定时扫描。

4.4如何分离冷热数据?

① 判断数据是冷是热。
②将要分离的数据插入冷数据库中。
③从热数据库中删除分离的数据。
这个逻辑看起来简单,而实际做方案时,以下3点都要考虑在内。

问题一:如何保证数据的一致性?

任何一个程序都要考虑在运行过程中突然出错中断时,应该如何处理。比如业务逻辑如下: (1) 找出符合冷数据的订单。
(2) 将这些订单添加到冷数据库。
(3)将这些订单从热数据库中删除。 举个例子: 例1:假设执行到步骤(2)的时候失败了,那么,要确保这些订单数据最终还是会被移到冷数据库。 例2:假设执行到步骤(3)的时候失败了,那么,要确保这些订单数据最终还是会从热数据库中删除。

这称为“最终一致性”,即最终数据和业务实际情况是一致的。这里的解决方案为,保证每一步都可以重试且操作都有幂等性,具体逻辑分为4步。

①在热数据库中给需要迁移的数据加标识:ColdFlag=WaittingForMove(实际处理中标识字段的值用数字就可以,这里是为了方便理解),从而将冷热数据标识的计算结果进行持久化,后面可以使用。
②找出所有待迁移的数据(ColdFlag=WaittingForMove)。这一步是为了确保前面有些线程因为部分原因运行失败,出现有些待迁移的数据没有迁移的情况时,可以通过这个标识找到这些遗留在热数据库中的订单数据。也就是上述例1中的情况。
③在冷数据库中保存一份数据,但在保存逻辑中需要加个判断来保证幂等性,通俗来说就是假如保存的数据在冷数据库已经存在了,也要确保这个逻辑可以继续进行。这样可以防止上述例2中的情况,因为可能会出现有一些订单其实已经保存到冷数据库中了,但是在将它们从热数据库删除时的逻辑出错了,它们仍然保留在热数据库中,等下次冷热分离的时候,又要将这些订单重复插入冷数据库中。这里面就要通过幂等性来确保冷数据库中没有重复数据。
④再从热数再从中删除对应的数据。

问题二:数据量很大,一次性处理不完?

采用定时扫描的逻辑就需要考虑数据量这个问题了。回到业务场景中,假设每天做一次冷热分离,根据前面的估算,每天有10万的订单数据和几十万的订单历史记录数据要迁移,但是程序不可能一次性插入几十万条记录,这时就要考虑批量处理了。这个实现逻辑也很简单,在迁移数据的地方加个批量处理逻辑就可以了。为方便理解,来看一个示例。 假设每次可以迁移1000条数据。

(1)在热数据库中给需要的数据添加标识:ColdFlag=WaittingForMove。这个过程使用Update语句就可以完成,每次更新大概10万条记录。
(2)找出前1000条待迁移的数据(ColdFlag=WaittingForMove)。
(3)在冷数据库中保存一份数据。
(4)从热数据库中删除对应的数据。
(5)循环执行(2)~(4)。

问题三:并发性?

在定时迁移冷热数据的场景里(比如每天),假设每天处理的数据量大到连单线程批量处理都应对不了,该怎么办?这时可以使用多个线程进行并发处理。回到场景中,假设已经有3000万的数据,第一次运行冷热分离的逻辑时,这些数据如果通过单线程来迁移,一个晚上可能无法完成,会影响第二天的客服工作,所以要考虑并发,采用多个线程来迁移。

(1)如何启动多线程?本项目采用的是定时器触发逻辑,性价比最高的方式是设置多个定时器,并让每个定时器之间的间隔短一些,然后每次定时启动一个线程后开始迁移数据。还有一个比较合适的方式是自建一个线程池,然后定时触发后面的操作:先计算待迁移的数据数量,再计算要同时启动的线程数,如果大于线程池的数量就取线程池的线程数,假设这个要启动的线程数量为N,最后循环N次启动线程池的线程来迁移数据。考虑了如何启动多线程的问题,接下来就是考虑锁了。

(2)某线程宣布正在操作某个数据,其他线程不能操作它(锁)因为是多线程并发迁移数据,所以要确保每个线程迁移的数据都是独立分开的,不能出现多个线程迁移同一条记录的情况。其实这就是锁的一个场景。

1)获取锁的原子性:当一个线程发现某个待处理的数据没有加锁时就给它加锁,这两步操作必须是原子性的,即要么一起成功,要么一起失败。实现这个逻辑时是要防止以下这种情况:“我是当前正在运行的线程,我发现一条订单没有锁,结果在要给它加锁的瞬间,它已经被别人加锁了。”可采用的解决方案是在表中加上LockThread字段,用来判断加锁的线程,每个线程只能处理被自己加锁成功的数据。然后使用一条Update…Where…语句,Where条件用来描述待迁移的未加锁或锁超时的数据,Update操作是使LockThread=当前线程ID,它利用MySQL的更新锁机制来实现原子性。

LockThread可以直接放在业务表中,也可以放在一个扩展表中。放在业务表中会对原来的表结构有一些侵入,放在扩展表中会增加一张表。最终,项目组选择将其放在业务表中,因为这种情况下编写的Update语句相对更简单,能缩短工期。

2)获取锁必须与处理开始保证一致性:当前线程开始处理这条数据时,需要再次检查操作的数据是否由当前线程锁定成功,实际操作为再次查询一下LockThread=当前线程ID的数据,再处理查询出来的数据。为什么要多此一举?因为当前面的Update…Where…语句执行完以后,程序并不知道哪些数据被Update语句更新了,也就是说被当前线程加锁了,所以还需要通过另一条SQL语句来查出这些被当前线程加锁成功的数据。这样就确保了当前线程处理的数据确实是被当前线程成功锁定的数据。

3)释放锁必须与处理完成保证一致性:当前线程处理完数据后,必须保证锁被释放。线程正常处理完后,数据不在热数据库,而是直接到了冷数据库,后续的线程不会再去迁移它,所以也就没有锁有没有及时释放的顾虑了。

5.查询冷热数据

5.1历史数据处理。
一般而言,只要与持久化层有关的架构方案都需要考虑历史数据的迁移问题,即如何让旧架构的历史数据适用于新的架构。因为前面的分离逻辑在考虑失败重试的场景时刚好覆盖了这个问题,所以其解决方案很简单,只需要批量给所有符合冷数据条件的历史数据加上标识ColdFlag=WaittingForMove,程序就会自动迁移了。还可以采用binglog方式同步。

5.2使用全量数据怎么办 目前业务中如根据使用考试中的错题,不需要区分是是何时的错题,将全量考试以及对应的错题抽取到adb中,然后查询adb。

阿里AnalyticDB

阿里的 AnalyticDB 是一种分布式实时分析型数据库,专为大规模数据分析和实时查询设计。与传统的关系型数据库相比,AnalyticDB 在处理大规模数据和复杂查询方面具有显著的优势,总之应该就是性能好,所以对于一些复杂查询可以选择使用adb。

阿里AnalyticDB 能够实现高性能的主要原因在于其采用了多种先进的技术和架构设计。以下是几个关键因素:

MPP 架构: Massively Parallel Processing (MPP):MPP 架构允许多个计算节点并行处理查询任务,显著提高了查询性能。 水平扩展: 可以通过增加节点来线性提升性能和存储容量,确保系统在数据量增长时仍能保持高性能。

列式存储: 列式存储:与传统的行式存储相比,列式存储在处理分析查询时更高效。列式存储可以减少 I/O 开销,提高查询速度。 压缩和编码:列式存储支持高效的压缩和编码技术,进一步节省存储空间,减少 I/O 操作。

分布式存储: 分布式存储引擎:数据分布在多个节点上,每个节点只处理部分数据,减少了单个节点的负载。 数据分片和分区:通过数据分片和分区技术,将数据分散到不同的节点上,提高查询性能和可扩展性。

高性能索引: 多种索引技术:支持 B-Tree、Bitmap 等多种索引技术,加速数据检索和查询。 自适应索引:根据查询模式自动选择最优的索引策略,提高查询效率。

内存计算: 内存缓存:常用数据和查询结果可以缓存在内存中,减少磁盘 I/O 操作,提高查询速度。 列式内存布局:在内存中也采用列式布局,进一步优化查询性能。

优化的查询引擎: 查询优化器:内置的查询优化器能够自动选择最优的查询计划,减少不必要的计算和 I/O 操作。 并行执行:查询任务可以并行执行,充分利用多核 CPU 的计算能力。

硬件优化: 高性能硬件:使用高性能的 SSD 存储和高速网络,减少 I/O 延迟和网络传输时间。 GPU 加速:在某些场景下,可以利用 GPU 进行加速计算,进一步提升性能。

自动负载均衡: 动态负载均衡:系统会自动检测各节点的负载情况,并进行动态调整,确保整体性能最优。 故障恢复:支持自动故障恢复,确保系统的高可用性和稳定性。

现在又来了一个考试消息通知的需求,因为数据量大了,要对原来的微服务拆分,所以要看他们之前的shi山代码,还要优化一些功能,很复杂,总之已老实。

冲冲冲!!!孩子们

f126faa61750f49c04d7aef21c53b644.jpeg