记一次redis并发问题

源码分析,redis 2019-05-04

记一次redis并发处理问题

一、场景分析

所在的公司是物联网公司,涉及到向设备发送指令,现在的问题是在和天猫语音对接的一个产品线上出现了一个bug

启用组合指令模式,例如一个情景模式,1、开卧室灯、2开走廊灯、3开客厅灯

  • 上面的三个灯对于我们的产品来说就是一个开关面板,面板上面是一个三路开关,也就是每一个灯对应一个开关,如下图:

image

当天猫精灵一条控制指令发送过来,我在将指令发送给设备,流程图如下

image

二、流程分析

上面可以看到整个流程,包括天猫语音触发应用云到下发控制指令的过程,
那几乎可以把重点锁定在下发参数上面

1.下发二进制指令

  • 现在有三路面板 对应的是 1 1 1 二进制位,公司最多是8路开关
  • 则如上面的流程所示,当控制客厅的时候 下发参数 0 0 0 0 0 1 0 0

2.缓存机制

上面也看到了,在下发控制指令的时候会将 其他开关位的状态也带上。

  • 那么实际在控制的时候,会去查下其他开关位的状态
  • 那么这个缓存状态是由设备来更新的

到这里来看,几乎也不会出现啥问题,接下来我们就要直面真正遇到的问题

三、并发问题分析

上面的例子都是单次请求,因为同一个面板的不同开关位虽然会依赖设备更新缓存,但是单次控制占时还是没有问题的,也就是用户触发天猫控制灯的时候都是一个一个控制的

但是现在引入了场景,也就是天猫精灵上面有场景模式,例如设置场景回家了,那么就是三路开关上所有的灯都要打开所有的灯,!而且天猫精灵是并发处理的,也就是会把三个请求同时发送到应用云上,然后应用云在下发控制指令。

问题分析

那么问题来了,三次请求之间都是有依赖关系的

  • 列如开客厅灯会查询走廊灯的状态,还有卧室灯的状态,都会去查询缓存,而三条控制指令都是同时到达,那么设备还没有更新缓存
  • 比如卧室灯(00000100) 走廊灯(00000101)已经开了,但是客厅灯最后进来,发现其他灯的缓存都是关的,那么会将0带下去((00000010)),然后导致前两次的灯开了又关。

四、优化处理方案

1.不依赖设备更新缓存,采用临时缓存

也就是不用去查询设备的缓存,直接采用临时缓存 ,时间1-2s,当并发的三个请求同时到来时,直接存储一个临时值,告诉其他请求针对要控制的面板有其他开关在控制

服务端采用的sowole进程模型
//采用hash结构,每一个案件对应一个key值,
//每一个按键的请求到来时更新hashkey时间为1s
$redis->hset("concurrent_control_hash_off".$devSn,$keynum,1);
$redis->expire("concurrent_control_hash_off".$devSn,1);
//直接从临时缓存里获取,如果没有在去设备缓存里获取
$res = $redis->hGetAll("concurrent_control_hash_off".$devSn);
  • 上诉的方法的方法在高并发下还是有问题!

2.保证单次请求的redis操作原子性

也就是当并发请求的时候每条redis指令没有顺序

$redis->multi();
$redis->hset("concurrent_control_hash_on".$devSn,$keynum,1);
$redis->expire("concurrent_control_hash_on".$devSn,1);
$redis->hGetAll("concurrent_control_hash_on".$devSn);
$res = $redis->exec();
$concurrentArr = $res[2];
  • @multi()
  • @exec()
    保持原子操作,效果到现在为止要好了些

总结

即使上面根据流程做了多次修改,已经对接流程的修改,总之没有绝对的情况,都需要进行优化,并发测试


本文由 小东@xiaodo 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。