一、场景描述
系统A: 被监控的业务系统。
系统B: 负责实时数据统计,系统A将数据上报到系统B,计算后将数据入库,但是在入库前需要对数据进行去重操作
![](https://img.haomeiwen.com/i7928684/456f9f0d1b162c0b.png)
DB的存储结构大致如下(省略无用字段):
id | module_name | field_type | data_value | update_time |
---|---|---|---|---|
3 | alipay | dst_ip | xx.xx.xx.xx | 2018-10-07 12:12 |
4 | amazon | dst_ip | xx.xx.xx.xx | 2018-10-09 16:24 |
其中module_name、field_type、data_value上加了唯一索引。
原来的处理逻辑是:系统B处理完一条上报的数据,然后通过replace into
插入一条记录到DB中,然后通过redis的zadd将该记录加入到zset中。当下一次上报数据时直接通过zrank判断是否该记录,如果存在,设置一个过期时间然后返回。伪代码:
判断数据是否存在:
Long rank = jedis.zrank("xxx",value);
if(null != rank){
jedis.expire("xxx",7 * 24 * 60 * 60);//7天
}
如果数据不存在,就添加到redis:
jedis.zadd("xxx",1,value);//score=1,没有利用起来
jedis.expire("xxx",7 * 24 * 60 * 60);//7天
二、需求描述
现在由于系统A上报的数据越来越大,导致系统B的DB数据越来越庞大,严重影响到查询和插入效率(暂时忽略分表).而且在业务上系统B对某个时间段内(比如一周)没有上报的数据没有太多统计意义,所以决定对DB中"过期"的数据进行清除,这样就可以排除掉大部分的无用数据。
三、技术方案的选择
方案一: 能否通过定时任务扫描update_time已过期的数据来实现定时删除?
答案: 否。因为redis这层已经做了数据去重,当发现有重复数据时,只会重新设置一个过期时间然后返回,并不会操作DB.如果直接操作DB,会误删掉很多敏感数据。
方案二: 能否通过redis的过期机制来实现?
答案:不确定,有风险。但是有性能问题,实现起来相对麻烦。要使用上redis的过期机制,就不能使用zset
这种数据结构,因为一个zset有大量value,即使里面只有一个value在不停上报,其它都是无效数据也不会使该key过期。如果选用set
这种数据结构,就会造成非常多的key,这样有两个弊端:
1.内存压力明显加大
2.redis是单线程架构,大量key的过期事件会造成redis性能下降。
而且在还需要开启redis服务端的keyspace
机制来满足对过期key的订阅(redis 3.0的cluster 模式好像对此功能支持不太好,没有测试)
方案三:能否在zadd时将时间戳追加到value中,当有重复数据时,更新时间戳(即value中的时间戳是最后上报数据的时间)。然后通过定时任务遍历所有value,对过期的数据进行删除。
答案: 否。虽然key比较少(2000个key左右),但是未来肯定越来越多。而且zset中的value非常大(百万级别),要想通过遍历的方式一个个去对比时间,效率无疑是非常低下的。
方案四:利用时间戳做zset的score能否实现?
答案: 能。即将当前时间转换成时间戳,然后设置成score.当有重复数据上报时只需更新score。然后用一个定时任务拉取指定数量key的数据(可以通过srandmember来随机拉取),因为设置了score,所以zset中的value是有序的(按时间排序),直接通过zrank
获取一批数据(可以根据数据量修改大小,默认30000)(注意: zrangebyscores
在大数据量的时候非常耗时,所以这里采用了zrank),我们只需要将这批数据挨个判断是否过期即可,如果其中一个value没有过期,就不需要往下对比,大大提升了执行效率。
四、总结
这里通过四个方案的对比发现只有方案四是比较理想的。但是还算不上非常健壮,因为我们太依赖redis,需要依赖redis的持久化机制。因为redis一旦不可用,系统就失去了"防重"功能,很容易压垮DB,线程数秒升。如果只做防重而不需要考虑过期数据的删除,BloomFilter是一种比较好的选择,而且redis对它已经比较好的支持。还可以在redis不可用时使用Bitset做一层防重兜底,防止数据穿透DB.
网友评论