一个看似很简单的redis需求,我问了农行和腾讯大佬也没想出

欢迎关注,微信公众号/知乎/稀土掘金/小红书都叫 ——【浣熊say】

专注输出国央企招聘、Java开发、物联网和数字孪生相关优质内容,关注公众号有小福利哦~

一个看似很简单的redis需求,我问了农行和腾讯大佬也没想出更优秀的解决方案!

来自领导的需求?

最近小浣熊领导又作妖了,给了我一个乍一听很简单,但是我想了半个小时也没想出更好解决方案的需求。

背景是这样的,我们有一个MQTT集群,其中有一个topic是某类IOT数据,也就是某个设备的“胎压”,这个数据需要实时通过websock推送到前端,并且有个参数需要计算两次传递值的差值。

本来这块儿当时是让外包兄弟做的,领导也没把需求定义清楚,外包兄弟很直接,直接给我弄了上一秒的“胎压”和这一秒的“胎压”存在redis里面,算这个差值的时候直接取上一秒的“胎压”和本次的“胎压”一算就完事儿了,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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条数据的办法?

于是自己想不出方案就找外援吧,这是就想起了自己还在腾讯呆着的研究生室友,先问问他知道怎么做不?首先咱们把需求给他描述清楚咯,入下:

image.png

腾讯大佬怎么说

image.png
我和我的室友合计了一下,好像也没想出啥好的方案~ 都在感叹自己是菜鸡,当然我更菜一点儿。

农行大佬怎么说

image.png
农行大佬的角度比较清奇,不然不能改变方案,那就改变领导,不得不说也是一个好的思路~

小浣熊的菜鸡解决方案

没办法,只有蛮干了,那我们只有在redis当中把前半个小时的历史数据缓存下来,实时删除多余的部分数据,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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的第一个和最后一个元素进行计算就可以咯,需求就这样完成了!

1
2
3
4
5
6
7
8
9
10
11
12
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爆之无疑。

不知道掘金的各位大佬有没有其它的好方案,帮帮我这个菜鸡想出更好的方案,适应当项目规模扩大的时候或者领导改需求时的场景,感激不尽~

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%