记一次redis并发处理问题
一、场景分析
所在的公司是物联网公司,涉及到向设备发送指令,现在的问题是在和天猫语音对接的一个产品线上出现了一个bug
启用组合指令模式,例如一个情景模式,1、开卧室灯、2开走廊灯、3开客厅灯
- 上面的三个灯对于我们的产品来说就是一个
开关面板
,面板上面是一个三路开关
,也就是每一个灯对应一个开关,如下图:
当天猫精灵一条控制指令发送过来,我在将指令发送给设备,流程图如下
二、流程分析
上面可以看到整个流程,包括天猫语音触发应用云到下发控制指令的过程,
那几乎可以把重点锁定在下发参数上面
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()
保持原子操作,效果到现在为止要好了些
总结
即使上面根据流程做了多次修改,已经对接流程的修改,总之没有绝对的情况,都需要进行优化,并发测试
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。