小弟以前是做Java开发的(web后端),最近跳槽到一家电商网站,用的是 node.js 。最近在看公司的代码,有一点疑问请大家指点: 举个例子,我们有个功能是店铺上新品之后给关注了这个店铺的人发推送,伪代码是这样的:
async function pushFollowers(shop) {
var start = 0
var size = 100
while (true) {
var followers = await getFollowers(shop.id, {start: start, size: size})
if (followers.length == 0) {
break
}
pushUsers(followers, '通知内容')
start += size
}
}
这里边 pushUsers 也是一个异步函数,就是调用我们接入的一个第三方推送给用户发推送。这里我有点疑问,就这样调用异步函数就可以了吗,如果店铺关注者比较多,那不是要产生很大异步任务,nodejs处理的过来吗?这种情况以前在Java里的话我可能会弄个队列来处理,是不是nodejs自己带了异步队列所以不需要了?这样会有什么问题吗
这个就类似于问nodejs为何可以处理高并发的问题。
来自酷炫的 CNodeMD
如果你这个代码和在你线上的web应用上,其它代码不要想执行了,异步不是线程兄弟,不能这样玩,while(true)会柱塞主进程。另开一个node或者子进程吧。 而且就算是可以这样,每个用户都搞个死循环,要死哦。还是一个脚本跑所有用户吧。
来自酷炫的 CNodeMD
@zswnew 求教
@zy445566 有 await 的不会吧?
@cheetahhh 你自己写个两个异步,在第一个执行的异步写while(ture),你看看第二个异步会不会执行
来自酷炫的 CNodeMD
@zy445566 你是说这样吗:
const sleep = require('sleep-promise')
async function task1() {
let n = 0
while (true) {
await sleep(100)
console.log(`task1 ${n}`)
n += 1
if (n >= 10) {
break
}
}
}
async function task2() {
await sleep(500)
console.log('task2')
}
task1()
task2()
@cheetahhh 我说的while(true)是不break的
这种写法有很大问题,主要有以下几个不合理的地方:
- node在用户级别是单线程的,无限循环会阻塞主线程,虽然有终止条件,但是如果要推送的用户数量巨大,那这个node进程基本就没办法接收其他请求了。
- getFollower是在一个while(true) 里面,也就是说在这个店铺的关注用户量很大的情况下,短时间内会产生很多数据库读请求,有可能对DB造成冲击。
- getFollower的参数有start和size,可以料想这里面使用skip和limit去查库的,在用户量大的情况下,越往后就需要skip过越多文档,效率会变低。
- 如果pushFollowers函数跑到一半进程挂了,怎么办?没办法从断点处继续了,唯一的方式就是重头到尾再跑一遍,这样就有的用户可能会重复收到推送消息。
一些可能的解决方案:
- 既然是异步耗时任务,可以专门建立一个做异步耗时任务的服务,让node去调用它,而不是node亲自去做这些事。具体来说可以通过消息队列,类似pub/sub的方式。另外批量地给消息队列传信息可以提高效率。这个服务应该要能很方便地添加新逻辑。这实际上是web-server和service分开的一种做法。
- 短时间内大量直接的DB操作不可取,可以考虑分批进行。
- 一般的文档都有一个创建时间字段(一般都带索引),可以考虑使用这个字段去查。
- 如何从断开处继续任务就是异步任务服务该考虑的事情了。
@nullcc 非常感谢你的回复,分析的很好。
针对
node在用户级别是单线程的,无限循环会阻塞主线程,虽然有终止条件,但是如果要推送的用户数量巨大,那这个node进程基本就没办法接收其他请求了。
我有点疑问,在每次 await getFollowers
等待数据库响应的时候主线程不是可以处理其他请求吗
@cheetahhh 这还要看具体的await方法里面的运算量,如果是I/O密集型,可能问题还不大,如果是计算密集型,问题就大了。不过总的来说,处理这类问题还是不要用上述方案,问题规模不大的时候还能扛住,之后肯定扛不住。
@cheetahhh 你说的是正确的,这里用了async/await,使用while(true)并没有什么问题(不考虑代码规范的话),并不存在上面几个人所说的堵塞当前进程的问题
回到你说的问题,有个疑问,如果NodeJS处理不过来,为什么你觉得加个队列就能处理得过来?
@CoderIvan 消息队列的作用是把生产者的请求路由到后面的异步发送服务(消费者),如果后端异步服务开多个node,效率要比上面代码中单个node跑效率高的多。而且后端服务也未必要是node。
@CoderIvan 如果只是纯异步操作,while(true)是没什么问题,我说的是这种处理问题的方法,每个程序员水平不同,很难避免会在异步方法里面加入CPU密集型的操作,因此这种活尽量不要放在web-server层面做。
@nullcc 兄弟,觉不觉得,扯远了
学习了~
@cheetahhh 我理解加await之后。可以理解当前请求执行的代码是交出执行权的,可以理解当前这个请求是暂停的。这个时候node是可以去处理其他请求。那么当await后面的io完成之后,当前请求就有机会在后面得到执行权,继续执行当前代码。
主调度线程是单线程的, 具体操作的处理是可以多线程的