当前位置: 首页 > Web前端 > JavaScript

Node异常数据响应排查(pm2ClusterMode,异步)

时间:2023-03-27 16:02:37 JavaScript

昨天收到铁人反馈,在node.js写的一个js文件里的方法。这个js文件声明了一个var全局变量(global),然后上面提到的方法就是先判断全局变量是否有值,有值则直接返回,有则通过接口获取值没有价值;然后在页面进行特定操作后,会将该全局变量的值清空为null,然后再去上面获取接口的值,本地就正常了。服务器上的错误给的信息一定是不够的。刚才问了几条消息,是不是报错了?因为出错会导致Node应用重启,进而导致状态失败。你能提供一个复现代码而不报错吗?这个一般要看项目的大小,或者不能提供问题定位的当前进度。本地是正常的,服务器上的执行环境错误是怎么回事?常见问题?环境稳定机制?特斯?这里想看看有没有什么猫腻的操作,比如serverless,不能保存状态。Node然后询问本地和服务器启动了哪些服务?这里我想确认一下是不是Cluster,因为Cluster状态是不共享的,需要特殊的解决办法。其实我已经知道这个人不是专业的Node开发者,之前的信息也可能有毒。这时突然给我发了一张日志截图,直接破案了。集群模式数据共享问题,从本地节点服务没有这个问题,服务器应该是pm2startindex.js-i4之类的。0|wwwxxxxx1|wwwxxxxx3|wwwxxxxx0|wwwxxxxx接下来是最小的复制演示来解决和解决问题。重现Cluster数据共享的问题。其实他在给我看是Cluster的时候就已经定位到问题了。这是一个非常明显的数据共享问题。看一下我们的复现例子,可以发现单个实例的输出是正确的,正是因为请求落到不同的机器(实例)上导致了不同的响应。如果(!global.a){global.a=1}console.log(global.a,Date.now())functionrandomTask(){console.log(++global.a,Date.now())如果(global.a<5){setTimeout(randomTask,Math.random()*1000)}}randomTask();无状态化您的应用程序确保您的应用程序是无状态的,这意味着没有本地数据存储在进程中,例如会话/websocket连接、会话内存及相关。使用Redis、Mongo或其他数据库在进程之间共享状态。另一个关于如何编写高效、生产就绪的无状态应用程序的资源是十二要素应用程序宣言。修复没有启动Cluster集群模式,因为本地是非Cluster集群模式,所以表现正常。那么第一个解决方案就是不在生产环境开启集群模式,但是一般来说这种方案是不可取的,因为生产环境的要求比较高,集群模式才是最优方案。添加单实例数据服务|降级到单实例模式和redis类似,只不过是创建了一个新的单实例nodeJs脚本。获取数据和更新数据是对这个脚本服务的请求。因为没有使用集群模式,所以不存在共享问题。同时也避免了之前方案的问题,因为数据服务不对外开放,只针对内网服务,所以请求级别不会太大。redisPublished&subscribe通过redis实现发布订阅功能。PublishedAllWorkers更新数据时更新数据。订阅在收到更新时更新自己的数据。代码如下所示。至于为什么会有多个redis实例?这是因为一个redis实例只能是发布者或者订阅者,所以我们需要有两个实例,一个发布更新数据,一个监听其他worker的更新。//ioredisconstRedis=require("ioredis")letredisClient3=newRedis()letredisClient4=newRedis()setInterval(()=>{constmessage={foo:Math.random(),pid:process.pid};constchannel=`my-channel-${1+Math.round(Math.random())}`;redisClient3.publish(channel,JSON.stringify(message));console.log("Published%sto%s",消息,频道);},5000);redisClient4.subscribe("my-channel-1","my-channel-2",(err,count)=>{if(err){console.error("订阅失败:%s",err.message);}else{console.log(`订阅成功!此客户端当前订阅了${count}个频道。`);}});redisClient4.on("message",(channel,message)=>{console.log(`从${channel}`收到${message});})因为fs不能在集群实例间通信,所以需要找一个通用的访问,那么本地Diskettes也是一个可行的选择。但是可能和fs有冲突,所以不推荐使用。试了一下,好像不会报错,内容也不会乱,但是可能取出来的内容是空的。constfs=require('fs');conststr=`process.pid:${process.pid}`.repeat(999)+'\n';console.log(`process.pid:${process.pid}`)consttest=()=>{for(vari=0;i<10;i++){console.log(`process.pid:${process.pid}${i}`)fs.writeFile('message.txt',`${i}${str}`,(err)=>{if(err)console.log(err)});}setTimeout(test,Math.random()*100);//设置超时(测试);}测试();集群模块不适合我们,因为pm2启动了所有的Worker。如果(cluster.isMaster){constworker=cluster.fork();worker.send('Hello');}elseif(cluster.isWorker){process.on('message',(msg)=>{process.send(msg);});}