请教《深入浅出Nodejs》中的一个关于解决雪崩的问题

请教《深入浅出Nodejs》中的一个关于解决雪崩的问题

原文链接如下: http://www.infoq.com/cn/articles/tyq-nodejs-event,作者为淘宝的朴灵,原名田永强

其中提到了通过将请求的回调函数压入事件队列中来解决雪崩的办法,核心代码如下:

var proxy = new EventProxy();
var status = "ready";
var select = function (callback) {
    proxy.once("selected", callback);
    if (status === "ready") {
        status = "pending";
        db.select("SQL", function (results) {
            proxy.emit("selected", results);
            status = "ready";
        });
    }
};

由于没找到完整代码,我便创建了一个http server来验证,验证结果确实是db查询的次数大大减少,补全后的核心代码如下:

var _db,
    status = 'ready',
    event = new emitter();

event.setMaxListeners(0); mongodb.open(function(err, db) { _db = db; });

http.createServer(function(request, response) { event.once(‘got’, function(result) { response.writeHead(200); response.end(result.name); });

if (status == 'ready') {
    status = "pendding";
    _db.collection('users', function(err, collection) {
        collection.findOne({name: 'leo'}, function(err, doc) {
            doc && event.emit('got', doc);
            status = "ready";
        });
    });
}

}).listen(9090);

我现在的问题就是我的代码这样写对吗?请高人指点一二。


9 回复

您的代码已经接近正确了,但还有一些地方需要调整。首先,让我们理解一下这段代码的核心思想:通过引入事件队列机制来避免短时间内重复执行数据库查询,从而防止雪崩效应。

原理分析

  • EventProxyemitter 都是用来管理事件的工具。
  • status 变量用来跟踪当前状态,确保同一时间只进行一次数据库查询。
  • 当请求到来时,如果当前状态是 ready,则设置状态为 pending 并执行数据库查询。查询完成后,重置状态为 ready

代码优化

  1. 状态变量拼写错误status 在某处被拼写为 pendding,应该是 pending
  2. 事件监听器的管理:可以使用 Node.js 自带的 events 模块来简化代码。

完整代码示例

const http = require('http');
const mongodb = require('mongodb');
const EventEmitter = require('events');

const eventEmitter = new EventEmitter();

mongodb.connect('mongodb://localhost:27017/mydatabase', (err, db) => {
    if (err) throw err;
    console.log('Database connected!');
    const _db = db.db('mydatabase');
    eventEmitter.setMaxListeners(0);

    http.createServer((request, response) => {
        // 设置状态为 ready,以便后续请求可以触发数据库查询
        let status = 'ready';

        eventEmitter.once('got', (result) => {
            response.writeHead(200);
            response.end(JSON.stringify(result));
        });

        if (status === 'ready') {
            status = 'pending';
            _db.collection('users').findOne({ name: 'leo' }, (err, doc) => {
                if (doc) {
                    eventEmitter.emit('got', doc);
                }
                status = 'ready';
            });
        }
    }).listen(9090, () => {
        console.log('Server running at http://127.0.0.1:9090/');
    });
});

关键点解释

  1. 状态管理:通过 status 变量来控制是否正在执行数据库查询,避免并发请求导致的雪崩。
  2. 事件监听:使用 EventEmitter 来管理事件,确保每次查询结果只处理一次。
  3. 数据库连接:确保在 HTTP 服务器启动前完成 MongoDB 的连接操作。

这样修改后,您的代码应该能够有效地避免雪崩问题,并且更加健壮和易于维护。希望这能帮助到您!


我觉得加入不加这个status 判断状态的话,假如有10个并发,一个一个的接着去连接数据库,回调函数压入事件队列中,等操作系统处理完回传给回调函数。假如数据库读写响应出现瓶颈的时候,回传数据给node就慢, 当上一个请求还没处理完的时候,这个请求又开始处理,这样的话就会造成不必须的系统消耗,降低系统的吞吐量。

你的意思是说加入不加入这个状态锁都不会影响性能吗?但是实际上聪ab测试的结果来看的话,rps(request per second)差了三倍。

还想问一下这个问题,需要为每个请求打开并且关闭一次数据库吗?你可以看到我现在的代码是打开数据库连接后就没有关闭的,这样有关系吗?

要加状态锁。

node是单线程异步IO的,没有必要打开在关闭数据库,之所以要打开和关闭数据库,因为像php就是同步操作io的,一个请求跑在一个线程上,然后操作数据库,假如这时候不关闭的话,线程越多,数据库所占的内存也会增多,最后导致服务器crash掉, 而 node是单线程的,始终只有单个数据库进程在处理,如果还频繁的打开和关闭数据库,会造成不必要的浪费。

这个问题太关键了,不胜感激!

可以告诉我你的邮箱地址吗?

Qq 461153861

你提供的代码逻辑基本正确,但有一些小问题需要修正。主要问题在于变量名不一致和状态管理上的小错误。以下是修正后的代码示例:

var http = require('http');
var mongodb = require('mongodb');
var EventEmitter = require('events').EventEmitter;

var _db;
var status = 'ready';
var event = new EventEmitter();

event.setMaxListeners(0);

mongodb.MongoClient.connect('mongodb://localhost:27017/test', function(err, db) {
    if (err) throw err;
    _db = db;
});

http.createServer(function(request, response) {
    event.once('got', function(result) {
        response.writeHead(200);
        response.end(result.name);
    });

    if (status === 'ready') {
        status = 'pending';
        _db.collection('users', function(err, collection) {
            if (err) throw err;
            collection.findOne({ name: 'leo' }, function(err, doc) {
                if (doc) event.emit('got', doc);
                status = 'ready';
                response.end(); // 确保响应结束
            });
        });
    } else {
        response.writeHead(200);
        response.end('Please wait...');
    }
}).listen(9090);

解释

  1. EventEmitter 使用:使用 EventEmitter 来管理事件监听和触发。
  2. 状态管理:使用 status 变量来控制是否正在进行数据库查询,避免并发查询。
  3. 错误处理:增加了错误处理,确保数据库连接和查询的错误可以被捕获并处理。
  4. 响应处理:如果当前状态为 pending,则返回 “Please wait…” 响应。

这样可以确保在多个并发请求时,数据库查询只执行一次,从而避免了雪崩效应。

回到顶部