上一篇:富文本编辑文章redis自动保存bug修复(2020-04-23 21:10:54)

物联网数据采集实时展示出现闪烁的排坑经历

2020年05月10日 01:07

昨天和今天两天,都在折腾这个问题,今晚总算是解决了。趁现在记忆还清晰,赶紧复盘一下。


项目分3个部分:

1. python采集传感器数据,lpush到采集队列

2. php从采集队列pop数据, 进行处理,同时提供后台系统接口

3. 后台用的vue框架,实时展现采集过来的设备数据


问题是:

后台有一项设备在线离线的状态数据,在连接上传感器之后,数据总是在在线和离线之间切换,也就是本文题目所说的数据闪烁的bug。


问题涉及在于项目的2、3部分,所以第1部分本人就在此忽略了。


下面就开始复盘排坑经历。


一.页面数据展示有问题,首先想到的就是确保接口是否有问题。


接口的设备在线状态数据,是从redis里设备数据种获取的。

而设备数据其中的状态,是在一个swoole定时任务里写入的。

这个swoole定时任务,就是遍历所有设备,来确定设备的真实状态,具体逻辑如下:


有一个redis hash key,存储设备最新采集的数据,每个设备最多只有一条数据(即可能也没有)

1. 用for循环遍历设备,当前时间,如果比采集数据里的采集时间大于一个设定值(此值后台可设定),就很有可能代表设备离线,否则就必定是在线。

2. 如果最新采集数据里没有当前设备的数据,则必定是离线

3. 如果设备离线,就将设备的最新采集数据删除。

4. 抓出设备旧数据里的在线状态,跟现在确定的状态对比,如果不一致,就更新redis里设备数据,同时向客户端(此处指后台)推送消息


1步骤里原本逻辑是当 处理时间 >数据采集时间 + 设定值时,就一定代表离线。这种逻辑在测试时,并不准确。就是当python采集服务暂停了一会儿的时候,设备最新采集数据跟当前隔有了一段时间,根据原本逻辑就判定其为离线状态,但实际上设备也可以是在线状态。


解决这种情况下的设备状态判断,翟码农用的fsockopen方法,来连接传感器设备,从而判定其状态。


代码大致如下:

$con = fsockopen(IP,端口);
if( false === $con ){
$status = 0;
}else{
$status = 1
}

此方法缺点就是当设备离线时,fsockopen方法用时有点长。


翟码农在这个判断设备在线状态的swoole定时任务里,打上了设备状态的日志,为了便于观察,暂只观察一个设备的数据,for循环里加个判断就好了。

foreach($device_arr as $item){
$device_id = $item['id'];
if( 123456 !== $device_id){
continue;
}
...
}


按理说,如果是接口数据的问题,应该日志里就会出现状态不断切换的现象。可事实上,并不是如此。


二、状态数据闪烁,难道是定时任务出现并发设置情况导致?

既然接口找不到问题,那就只好努力想想原因了。


如果是定时任务出现并发,日志记录就应该有重复。而且逻辑是相同的,即得到的状态结果也应该是相同的。如此说来,就也不应该会出现数据闪烁的情况。


三、会不会是php项目里,在哪个地方定时做了设备数据的初始化,从而将写好的设备状态给覆盖了?

翟码农找了很久,并没有。


到了这里,本人却仍然没有绝对的排除不是接口的问题,而是仍然做了一个猜想,就命名其为第四种原因思考吧。


四、难道之前写的日志记录,时间间隔太大?真的是接口数据一直在变从而导致闪烁的?

为了进一步验证猜想,所以本人直接用while死循环去调用后台接口,打印出接口结果里的状态数据。

function post($url, $data) {
//初使化init方法
$ch = curl_init();

//指定URL
curl_setopt($ch, CURLOPT_URL, $url);

//设定请求后返回结果
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

//声明使用POST方式来进行发送
curl_setopt($ch, CURLOPT_POST, 1);

//发送什么数据呢
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);

//忽略证书
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

//忽略header头信息
curl_setopt($ch, CURLOPT_HEADER, 0);

//设置超时时间
curl_setopt($ch, CURLOPT_TIMEOUT, 10);

//发送请求
$output = curl_exec($ch);

//关闭curl
curl_close($ch);

//返回数据
return $output;
}

while(true){
$result = curl_init($url, $post_data)
file_put_contents('test.log', 'status:'.$result['status'].PHP_EOL, FILE_APPEND);
}

此中注意接口header里的ContentType,如果是application/x-www-form-urlencode方式,$post_data数据要拼接成字符串,如果是application/json格式,就用json_encode对$post_data处理下。


辛苦的试完此方法后,十几秒钟几万条的数据里,仍然看不到状态频繁变换的情况。所以,此猜想被证明失败。


到了这里,一天半的时间过去了,后来才想,或许真的就是前端代码的问题,所以就去看前端代码。


挑了几个设备数据的请求看了看,感觉状态对应有点乱:接口明明是1(表示在线),页面却显示离线,又或者是接口返回0,页面却显示在线。


看花了眼,索性直接在接口里,将状态数据全部写死,这会儿才发现问题果真是出在前端:页面里设备列表的在线状态数据并没有什么变化(想象中应该全部要么在线要么离线)


所以啊,此步骤是解决问题的关键所在。如果早就将接口数据写死了,就一目了然的知道是前端的问题了。可惜我辛苦加了一天半的班。


是后端接口问题?还是前端问题?感觉没有头绪时,就将接口数据写死,辅助自己快速定位问题所在的地方。

在vue页面里,找到了一段包含socket关键词的代码,里面有关于tableData的重新赋值,tableData就是列表数据。


追踪溯源,找到了websocket的路径(此时我还理解成是前端定时去后端捞取数据)。


路径里有端口号,在服务器里查看接口占用情况:

netstat -anop | grep 端口 


果然看到swoole相关的字眼,swoole正是可以用来实现服务通信的。


虽然知道了大概是在swoole相关的代码里,可是具体到底在哪里呢?

vue前端这边,可以console.log打印出socket调用后的数据结果,然后根据数据里的具体单词或字眼去php项目源码里进行查找

就这样子,最终找到了。


流程并不是像我开始所想的那样(vue这边定时去后端读取数据),而是php这边往vue推送的。


这部分的流程是这么玩的:

1.前面swoole定时任务遍历设备检查在线状态时,如果状态不一致,就发送客户端推送消息。此时并没有真的发送,只是将消息push到redis队列中去。

2.swoole另一个定时任务,就是去消费该redis队列,将消息发送出去,这里就对应到Vue处理socket请求这边了。其中消息里就包含设备的最新是否在线的状态数据。


而导致vue页面数据闪烁的原因是:

swoole多进程处理采集队列里的数据时,也会往客户端推送消息,此处主要推送的是设备详情数据,类如空调的温湿度、工作模式,各个零件是否故障等等,bug是此处设备里有自带的是否在线的status数据,跟随着详情数据一同发送给客户端了。


所以最终局面就是:swoole定时任务这边给客户端推送了设备当下最新的是否在线的状态数据,而swoole多进程处理采集数据这边,却又将设备原本存储的是否在线的状态数据推送到客户端了,两者一混合,从而就导致了设备是否在线的状态数据来回变换的问题。


解决方法很简单,是否在线状态这个数据,就专一的由swoole定时任务处理就好,在处理采集数据这边,在发送到客户端的消息里,将设备的状态数据排除掉。


同时也验证了生活里,如果我们要想将事情简单化,那就最好将这件事情只交给某个人处理,而不是多人来处理。当然那种为了节省成本而将超负荷的任务交付给一个人的情况,就不适应这句话了。


本文为翟码农个人博客里有关开发经验总结的原创文章,转载请注明出处:http://www.zhai14.com/blog/solve-the-data-refresh-bug-in-IOT-project.html



  • 2020年05月09日 23:02文章创建
  • 2020年05月10日 01:07文章发布
上一篇:富文本编辑文章redis自动保存bug修复(2020-04-23 21:10:54)
我要评论
«-必填,限2-20个字符,中文/字母/字母数字组合
«-评论后,邮箱会收到激活链接,未激活邮箱的留言,将无法显示
评论列表
暂无评论,期待你的评论哦!
回到顶部