最近在做一个需要扫表的业务根据需要隐去部分细节:
有一个表或者很多表,数据量是>亿级,需要做清理工作,比如删除过期的数据,不完整的数据,无效的数据。
怎么做呢?
解决方案:
1 回收写入
将写入操作做收缩,每次写入操作都对数据做校验,每次数据查询都做有效性检测。
比如入库的时候,无效数据不入库,保证后续入库数据的有效性和正确性—此为开源;
数据出库时做有效性检测,不仅能保证输出还变相的对历史数据做了清理工作,此为节流;
这样的问题是:
入库操作复杂了,可以看到入库操作更为耗时和复杂,而出库的校验也增加了数据查询的效率,还有一点就是如果数据在库中已经失效就说明后续不可能再被访问到,无法被清理,那么久永远的留在了库中,造成资源的浪费;
2 洗数据
定时脚本洗数据,对所有的数据进行一次清理,一遍洗完就是下一遍清洗,周而复始。
问题:
单独洗数据需要大量额外的资源,其次洗数据的频率、时间和过程中对业务的影响无法估计。
综合以上方案(后续补充其他可行的方案),个人支持第二种。
但是第二种需要优化:
1 脚本洗数据是否需要频率限制?
最好不需要,不管半个月还是1一个月洗一次对于业务方来说是不可接受的,你无法保证这洗数据这段时间不会影响到用户,而且清理工作应该是循环往复的而不是定时性的“走场子”;
2 怎么洗?
遍历,对于未知的问题的清理和排查只能是遍历;
3 时间怎么确定,什么时候开始洗?
对于用户来说,只要不是特殊情况,凌晨永远是用户量最少的时候,机器负载最小的时候,除非你是百度贴吧,晚上没人时机器被其他业务占用导致晚上负载比白天还高。
所以综上可以得到一条较好的解决方案:
每天凌晨(0-5点),洗数据,分段洗,从高到低开始洗。
分段洗是数据量决定的;
在如此庞大(亿级其实是谦虚的说法,真实数据量大得多得多)的数据量上
量的分段:一次完全的清洗是一个漫长的过程,每天洗一部分是最好的方式。
时间的分段:凌晨QPS小,机器负载小,空跑不是跑,不如多给点活;
从高到低:
从当前最顶层的数据开始向下洗数据,好处是:
最近进入的数据永远是最“热”的,使用频率最高的,保证当前的服务良好,反之越底层、越久远的数据往往是越“不需要”的数据;
好了,注意点说的查不到了,简单的实现:
crontab:
0 0 * * * php test.php > /dev/null 2>&1 #每天0点开始
while(true)
{
// 5点后退出
if(time("H")>5){
break;
}
// 获取上次的游标接着跑
if(!getCursor( ) ){
// 游标为空,取当前表中最大id
cursor = getMaxId();
} else {
cursor = getCursor();
}
// 获取数据,一次100条
$data = getData(100);
foreach($data as $_data){
if(!checkdata( $_data )){
// 需要清理的数据入Redis list
$redis->rRush( CRON_DELETE ,$_data);
}
}
//并保存当前的游标,下次接着跑
cursor = end($data);
updateCursor(cursor);
unset($data);
}
// 清理工作
$data = $redis->lPop(CRON_DELETE);
delete($data);