工作中,随着数据库表数据量的增大,我们会发现,对表数据的读写操作会变得越来越慢,有时候查询一条数据会耗费几十秒或几分钟才查出结果,甚至多点击几次查询还会出现宕机。
这个时候,我们可能首先会想到通过对表结构、业务代码、索引、SQL语句等方面进行优化,以此来提高读写操作响应速度。然而,对于表数据量相对较大的情况,我们发现优化效果有限,并未达到预期效果。
此时,我们可以考虑是否可以通冷热分离来提升读写、查询效率。
为了更好的理解冷热分离的概念,我们先了解下什么是热数据,什么是冷数据。
热数据
热数据指的是需要即时对用户进行分发的数据,即从数据源抓取之后经过数据处理,需要即时存储到可快速分发的存储介质供API或直接面向用户的系统使用。
热数据需要重点保障服务质量和稳定性,为了保证数据的时效性,在数据处理上也是优先级高的数据。
冷数据
冷数据指的是不需要即时发给用户的数据。
这些数不会原样分发给用户,它们需要经过长期的积累,使我们可以从中得到基于此更高层次的分析。
冷热分离
冷热分离指的是在处理数据时将数据库分为冷库和热库,冷库指用于存放走到了终态的数据(冷数据)的数据库,热库用于存放还需要修改的数据(热数据)的数据库。
从冷热分离的定义我们可以知道当业务需求涉及到冷热数据,表数据量增长速度快或数据量较大时, 我们就该考虑是否使用冷热分离解决方案了。比如:
1)数据走到终态后,只有读没有写的需求。
2)用户能够接受新旧数据分开实现业务,比如查询新旧数据的时候分开操作。
4.1 如何判断一个数据到底是冷数据还是热数据?
一个数据是冷数据还是热数据,需要根据实际的业务需求来制定判定条件。满足热数据条件的归为热数据,满足冷数据条件的归为冷数据。
判定条件可以是表里的1个字段或多个字段组合的方式组成。
时间、状态等都是比较适合用作判定条件的字段。
比如,我们管理一个订单系统,针对订单主表,我们可以使用下面两种方式作为冷热数据的界定:
关于判断冷热数据的逻辑,这里还有 2 个注意要点必须说明:
1)如果一个数据被标识为冷数据,业务代码不会再对它进行写操作;
2)不会同时存在读冷/热数据的需求。
4.2 如何触发冷热数据分离?
一般来说,冷热数据分离的触发实现有3种方式:业务层代码实现、binlog实现、定时扫描数据库实现。
(一)业务层代码实现
在代码中实现,当有对数据进行写操作时,触发冷热分离。
建议使用场景:业务代码比较简单,并且不按照时间区分冷热数据时使用。
(二)binlog实现
监听数据库变更日志binlog的方式来触发。
建议使用场景:业务代码比较复杂,不敢随意变更,并且不按照时间区分冷热数据时使用。
实际使用binlog方式实现过程中,我们可能会遇到以下问题:
问题描述:
定时任务删除数据时,也会产生binlog,如何区别数据的删除是定时任务产生的,还是正常的业务?
解决方案:
我们可以通过设置binlog的session级别,也就是在此session中操作的语句,并不会产生binlog。
set session sql_log_bin=0;//optset session sql_log_bin=1;
我们在定时任务执行时,先关闭binlog,然后,执行删除语句,然后,重新恢复binlog。这些删除的数据,就不会通过canal同步到冷库中了。
(三)定时扫描数据库实现
通过定时扫描数据库的方式来触发。
建议使用场景:在按照时间区分冷热数据时使用。
4.3 如何实现冷热数据分离?
冷热数据分离示意图如下:
上述分离逻辑看起来比较简单,不过在实际方案实施的时候,我们需要考虑以下情况:
(1)一致性:同时修改多个数据库,如何保证数据的一致性?
情形描述:一致性要求指的是,当分离过程中如何保证任何一步出错后数据还是一致的。
解决方法:保证每一步都可以重试,并且操作都有幂等性。
具体逻辑分4步:
(2)数据量:假设数据量大,一次性处理不完,该怎么办?是否需要使用批量处理?
情形描述:定时扫描的逻辑需要考虑数据量问题。
解决方法:在搬数据的地方增加批量逻辑。
如:假设我们每次可以搬50条数据
a. 在热数据库中给要搬的数据加个标识:ColdFlag=WaittingForMove;
b. 找出前 50 条待搬的数据(ColdFlag=WaittingForMove);
c. 在冷数据库中保存一份数据;
d. 从热数据库中删除对应的数据;
e. 循环执行 b。
(3)并发性:假设数据量大到要分到多个地方并行处理,该怎么办?
情形描述:数据量过大,以致单线程无法处理时,我们使用多线程需要考虑哪些问题。
step1:如何启动多线程?
方式1:采用的是定时器触发逻辑,设置多个定时器,并让每个定时器之间的间隔短一些,然后每次定时启动一个线程就开始搬运数据。
方式2:自建一个线程池,然后定时触发后面的操作:先计算待搬动的热数据的数量,再计算要同时启动的线程数,如果大于线程池的数量就取线程池的线程数,假设这个数量为 N,最后循环 N 次启动线程池的线程搬运冷热数据。
step2:某线程宣布某个数据正在操作,其他线程不要动(锁)
step3:如果出现某线程正常处理完后,数据不在热库,直接跑到了冷库,这是正常的,无需处理。
step4:某线程失败退出了,结果锁没释放怎么办(锁超时)?
分两种情况处理:
考虑到前面逻辑比较复杂,这里我们特地画了一个分离的流程图,如下图所示:
4.4 如何使用冷热数据?
如何使用冷热数据,需要根据实际业务需求来定。
比如我们要查询未完结订单,这个时候使用的就是热库中的数据。
需要注意的是:
在判断数据是冷数据还是热数据时,必须确保用户不允许同时有读冷热数据的需求。
5.2 冷热分离解决方案的不足
冷热分离确实可以在某种程度上解决写读写数据慢的问题,但是仍然存在诸多不足。具体表现有:
1)用户查询冷数据速度依旧很慢。
2)由于冷数据多到一定程度,业务就无法再修改冷数据,因为数据量太大系统承受不住。