node.js is based on single-threaded non-blocking asynchronous I/O. Asynchronous I/O means that when encountering an I/O operation, the thread does not block but performs the following operations. , then after the I/O operation is completed, how does the thread know that the operation is completed?
When the operation completes the time-consuming I/O operation, the thread of the I/O operation will be notified in the form of an event. The thread will process the event at a specific time and proceed to the next step. Operation, in order to complete asynchronous I/O, the thread must have an event loop mechanism, constantly insisting on whether there are unfinished events, and completing the processing of these events in sequence.
For blocking I/O, when a thread encounters a time-consuming I/O operation, it will stop executing and wait for the operation to be completed. At this time, the thread cannot accept other operation requests. In order to provide throughput, Multiple threads must be created, each thread to respond to a customer's request, but at the same time, only one thread can run on a CPU core. If multiple threads want to execute, they must switch between different threads.
Therefore, node.js reduces the cost of thread creation and thread switching in multi-threads. The cost of thread switching is very high. It needs to allocate memory for it and include it in the schedule. At the same time, when thread switching Sometimes it is necessary to perform memory paging and other operations. These operations can be reduced by using a single thread. However, this programming method also has shortcomings and is not in line with people's design thinking.
node.js is based on the event mode to implement asynchronous I/O. When it is started, it will continuously traverse whether there are any completed events, and then execute them. After the execution is completed, another event will be Notify the thread in the form that this operation has been completed, and this event will be added to the unfinished event list. The thread will traverse this event at some point in the next time and then execute it. In this mechanism, a large event needs to be The tasks are divided into small events, and node.js is also suitable for handling some high I/O and low logic scenarios.
The following example demonstrates asynchronous file reading:
##
var fs = require('fs'); fs.readFile('file.txt', 'utf-8', function(err, data) { if (err) { <span style="white-space:pre"> </span>console.error(err); } else { <span style="white-space:pre"> </span>console.log(data); } }); [javascript] view plain copy console.log("end");
fs. readFile reads the file asynchronously, and then the process will continue without waiting for it to finish reading the file. When the file is read, an event will be released, and the execution thread will execute the corresponding event when it traverses the event. Operation, here is to execute the corresponding callback function. In the example, the string end will be printed out before the file content.
node.js event API
events.EventEmitter: EventEmitter emits events in node.js with The event listening function provides encapsulation. Each event consists of a string identifying the event name and the corresponding operation.
Event monitoring:
var events = require("events"); var emitter = new events.EventEmitter(); <span style="font-family: Arial, Helvetica, sans-serif;">emitter.on("eventName", function(){</span> console.log("eventName事件发生") })
Event publishing:
emitter.emit("eventName");
EventEmitter.once("eventName", listener) : Register a listener for the event that only executes once. When the event occurs for the first time and the listener is triggered, the The listener will be dismissed, and if the event occurs later, the listener will not be executed.
EventEmitter.removeListener(event, listener) :Remove the event listener
EventEmitter.removeAllListeners(event): Remove all event listeners
EventEmitter.setMaxListeners(n): node.js default maximum number of listeners for a single event is 10. If A warning will be given if it exceeds 10. This is done to prevent memory overflow. We can change this limit to another number. If it is set to 0, it means no limit.
EventEmitter.listeners(event) : Returns a list of listeners for an event
Collaboration between multiple events In slightly larger applications, the separation between data and web servers is inevitable, such as Sina Weibo, Facebook, Twitter, etc. The advantage of this is that the data sources are unified, and various rich client programs can be developed for the same data sources.
api.getUser("username", function (profile) { // Got the profile }); api.getTimeline("username", function (timeline) { // Got the timeline }); api.getSkin("username", function (skin) { // Got the skin });
api.getUser("username", function (profile) { api.getTimeline("username", function (timeline) { api.getSkin("username", function (skin) { // TODO }); }); });
为解决这类问题,我曾写作一个模块来实现多事件协作,以下为上面代码的改进版:
var proxy = new EventProxy(); proxy.all("profile", "timeline", "skin", function (profile, timeline, skin) { // TODO }); api.getUser("username", function (profile) { proxy.emit("profile", profile); }); api.getTimeline("username", function (timeline) { proxy.emit("timeline", timeline); }); api.getSkin("username", function (skin) { proxy.emit("skin", skin); });
EventProxy也是一个简单的事件侦听者模式的实现,由于底层实现跟Node.js的EventEmitter不同,无法合并进Node.js中。但是却提供了比EventEmitter更强大的功能,且API保持与EventEmitter一致,与Node.js的思路保持契合,并可以适用在前端中。
这里的all方法是指侦听完profile、timeline、skin三个方法后,执行回调函数,并将侦听接收到的数据传入。
利用事件队列解决雪崩问题
所谓雪崩问题,是在缓存失效的情景下,大并发高访问量同时涌入数据库中查询,数据库无法同时承受如此大的查询请求,进而往前影响到网站整体响应缓慢。
那么在Node.js中如何应付这种情景呢。
var select = function (callback) { db.select("SQL", function (results) { callback(results); }); };
以上是一句数据库查询的调用,如果站点刚好启动,这时候缓存中是不存在数据的,而如果访问量巨大,同一句SQL会被发送到数据库中反复查询,影响到服务的整体性能。一个改进是添加一个状态锁。
var status = "ready"; var select = function (callback) { if (status === "ready") { status = "pending"; db.select("SQL", function (results) { callback(results); status = "ready"; }); } };
但是这种情景,连续的多次调用select发,只有第一次调用是生效的,后续的select是没有数据服务的。所以这个时候引入事件队列吧:
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"; }); } };
这里利用了EventProxy对象的once
方法,将所有请求的回调都压入事件队列中,并利用其执行一次就会将监视器移除的特点,保证每一个回调只会被执行一次。对于相同的SQL语句,保证在同一个查询开始到结束的时间中永远只有一次,在这查询期间到来的调用,只需在队列中等待数据就绪即可,节省了重复的数据库调用开销。由于Node.js单线程执行的原因,此处无需担心状态问题。这种方式其实也可以应用到其他远程调用的场景中,即使外部没有缓存策略,也能有效节省重复开销。此处也可以用EventEmitter替代EventProxy,不过可能存在侦听器过多,引发警告,需要调用setMaxListeners(0)
移除掉警告,或者设更大的警告阀值。
The above is the detailed content of Detailed explanation of event listening and event publishing usage examples in Node.js. For more information, please follow other related articles on the PHP Chinese website!