欢迎关注,微信公众号/知乎/稀土掘金/小红书都叫 ------【浣熊say】
专注输出国央企招聘、Java开发、物联网和数字孪生相关优质内容,关注公众号有小福利哦~
一个看似很简单的redis需求,我问了农行和腾讯大佬也没想出更优秀的解决方案!
来自领导的需求?
最近小浣熊领导又作妖了,给了我一个乍一听很简单,但是我想了半个小时也没想出更好解决方案的需求。
背景是这样的,我们有一个MQTT集群,其中有一个topic是某类IOT数据,也就是某个设备的"胎压",这个数据需要实时通过websock推送到前端,并且有个参数需要计算两次传递值的差值。
本来这块儿当时是让外包兄弟做的,领导也没把需求定义清楚,外包兄弟很直接,直接给我弄了上一秒的"胎压"和这一秒的"胎压"存在redis里面,算这个差值的时候直接取上一秒的"胎压"和本次的"胎压"一算就完事儿了,代码如下:
scss
private void redisCacheTirePressure(String data) {
try {
// generate cache key
String redisKey = RopewayUtils.GetRopewayCaheKey(MongoConstants.TIRE_PRESSURE_LIST_CACHEKEY);
// put new value
redisTemplate.opsForList().rightPush(redisKey, data);
// get new total size
Long size = redisTemplate.opsForList().size(redisKey);
if (size.intValue() > 2) {
// remove other record(s), just keep two records
redisTemplate.opsForList().trim(redisKey, size - 2, size - 1);
}
} catch (Exception e) {
// e.printStackTrace();
}
}
好家伙,我真想反手给外包兄弟一个赞👍,代码注释还是英文,很有水平。但是,领导最近看了看,发现不对啊,为啥这个"胎压"的差值一直没变化 啊,半个月过去了气都漏完了,胎压差值还是0,肯定是小浣熊又写bug了。
我特么是诚惶诚恐,立马停止摸鱼,开始想起了解决方案。
农行、腾讯大佬怎么说?
想了半天,小浣熊想了个效率不怎么高的方案,就是把半个小时的历史数据全部缓存在redis里面,然后当这一秒的数据到了,我就去redis里面拿半小时前的数据,然后计算这个差值,返回给前端。
但是,这样的设计毕竟很暴力,也很不优雅,因为我们的IOT数据是1s一次的,对于一个IOT场景来说redis需要存储的数据就是30x60=1800条数据。虽然看起来还好,但是未来IOT场景增大的话,这段代码就可能存在性能问题,并且如果需求改了,计算1个小时或者24个小时甚至一个月的差值,那这个方案又当如何应对?所以小浣熊得想办法优化,就是有没有那种只存2条数据的办法?
于是自己想不出方案就找外援吧,这是就想起了自己还在腾讯呆着的研究生室友,先问问他知道怎么做不?首先咱们把需求给他描述清楚咯,入下:
腾讯大佬怎么说
我和我的室友合计了一下,好像也没想出啥好的方案~ 都在感叹自己是菜鸡,当然我更菜一点儿。
农行大佬怎么说
农行大佬的角度比较清奇,不然不能改变方案,那就改变领导,不得不说也是一个好的思路~
小浣熊的菜鸡解决方案
没办法,只有蛮干了,那我们只有在redis当中把前半个小时的历史数据缓存下来,实时删除多余的部分数据,代码如下:
scss
private void redisCacheTirePressure(String data,int cacheTimeMinutes) {
try {
String redisKey = RopewayUtils.GetRopewayCaheKey(MongoConstants.TIRE_PRESSURE_ZSET_CACHEKEY);
long currentTime = System.currentTimeMillis();
//存储当前的数据到redis的zset当中,score为当前时间,zset默认排序是按照升序排列的
redisTemplate.opsForZSet().add(redisKey,data,currentTime);
//从前到后遍历zset,找到和currentTime差值为cacheTimeMinutes的节点
Set<Object> expiredMembers = redisTemplate.opsForZSet().rangeByScore(redisKey, Double.NEGATIVE_INFINITY, currentTime - cacheTimeMinutes * 60 * 1000);
//删除超过cacheTimeMinutes的节点,保证在zset的首尾节点时差为30分钟
for (Object expiredMember : expiredMembers) {
redisTemplate.opsForZSet().remove(redisKey, expiredMember);
}
} catch (Exception e) {
logger.error(e.getMessage());
}
}
计算实时差值数据的时候,只需要取redis中zset的第一个和最后一个元素进行计算就可以咯,需求就这样完成了!
ini
String redisKey = RopewayUtils.GetRopewayCaheKey(MongoConstants.TIRE_PRESSURE_ZSET_CACHEKEY);
Set<String> records = redisTemplate.opsForZSet().range(redisKey,0,-1);
List<String> recordsList = new ArrayList<>(records);
if (recordsList != null && records.size() > 1) {
String prev = recordsList.get(0);
String next = recordsList.get(recordsList.size() - 1);
//其它的操作
}
最后
当然,我这个方案漏洞百出,首先当项目规模扩大之后,就算一个IOT设备那么数据也有30x60=1800条,如果设备数据不断扩张,极有可能干爆redis。并且,假设当领导改成计算一个月的差值的话,那么数据量将会达到60x60x24x30 = 259万条的zset数据,redis爆之无疑。
不知道掘金的各位大佬有没有其它的好方案,帮帮我这个菜鸡想出更好的方案,适应当项目规模扩大的时候或者领导改需求时的场景,感激不尽~