在Ruby on Rails和NodeJS开发者之间曾经引起宗教类的口水战:顺序编程风格 Vs 基于事件编程。目前大部分Web应用包括Ruby on Rails, Java Spring, Django都是使用顺序编程风格。顺序编程是非常简单和可读的,大部分开发者都是以顺序方式思考,喜欢将一个应用逻辑划分为顺序的时序步骤。顺序编程通常会导致堵塞I/O,因为线程是遵循先来后到的多任务方式,而不是一种协作式的多任务方式,而非堵塞I/O能够带来更好地扩展性和性能。
这是一篇有关Node.js的非堵塞reactive编程案例,文章以一个简单的根据id查询的RESTful案例为例,从堵塞IO谈到回调函数的使用,然后谈论如何在代码可扩展性和可读性之间取得平衡,引入Promise与Fibers编程。大意翻译如下:
下面代码是根据facebook的id查询用户事件数据的实现,顺序编程代码风格是如下(JS伪代码):
function getUserEvents(request,response){
var facebook_id = request.param('facebook_id');
try{
var user = db.users.findOne({fb_id:facebook_id});
var events = db.events.find({user_id:user.id});
response.write(events);
}catch(err){
response.status(500).send(err);
}
}
|
顺序编程通常会导致堵塞I/O,因为线程是遵循先来后到的多任务方式,而不是一种协作式的多任务方式。
下面我们逐步引入异步来提升这段代码。
基于回调的解决方案
为了解决堵塞的I/O问题,代码可以被划分为三个部分:
1. 在进行网络或IO调用前的过程处理。
2.网络或IO调用。
3.从网络或IO调用中得到返回数据的处理。
上面这三步都是分离执行的,然后它们在NodeJS的事件循环中被触发执行。
function getUserEvents(request,response){
var returnEvents = function(err,events){
if (err) respone.status(500).send(err);;
response.write(events);
});
var givenUserFindAndReturnEvents = function(err,user){
if (err) respone.status(500).send(err);;
db.events.find({user_id:user.id},returnEvents);
};
var findUserAndReturnEvents = function(){
var facebook_id = request.param('facebook_id');
db.users.findOne({fb_id:facebook_id}, givenUserFindAndReturnEvents);
}
findUserAndReturnEvents();
}
|
注意到请求和响应并没有被传递到子函数中,但是子函数可以访问request 和response,因为子函数是一个javascript 闭包. (当调用findUserAndReturnEvents函数时,相当于findUserAndReturnEvents -> givenUserFindAndReturnEvents ->returnEvents 这样一个流式调用)
这三个子函数都是异步执行的, givenUserFindAndReturnEvents 和 returnEvents是在findUserAndReturnEvents回调函数中触发执行的。
这三个子函数执行方式可以使用嵌套的lambda函数风格或findUserAndReturnEvents. givenUserFindAndReturnEvents.returnEvents 这种in-line方式。
这段代码有几个特点:
1.代码分离成网络调用前与网络调用后两个阶段
2.子函数调用者为了完成子函数任务必须传递一个回调函数。
3.顺序逻辑被表达成异步了
4.异步代码更加可扩展伸缩,但是也许增加响应延迟。
5.但是回调会引起可读性的问题:回调地狱 callback hell.
6.跟踪执行流程是困难的,因此称为意大利面条代码spaghetti-code.
7.非堵塞的API可能会限制妨碍你组织你的代码,有侵入性。
8.函数是有层次的。
基于Promise的解决方案
为了解决回调地狱,我们使用代码结构库如q promise. Promise库提供一些标准的代码风格和结构,使得代码更加易读。
var loadEventsForUser = function(err,user){
return db.events.find({user_id:user.id});
};
var findUser = function(){
var facebook_id = request.param('facebook_id');
return db.users.findOne({fb_id:facebook_id});
}
function getUserEvents(request,response){
var success = function(events){
response.write(events);
};
var error = function(err){
response.status(500).send(err);
};
findUser()
.then(loadEventsForUser)
.then(success)
.fail(error);
}
|
关键是最后一行的写法。
代码已经被划分为小的独立的函数,它们被以链条方式链接在一起,使用的是.then 和 .fail 函数. 另外一个重要特性是处理exception, 上面代码能够观察到错误然后调用相应的回调函数,错误处理被单独隔离了。
重要特点:
1.函数是扁平的,比如findUser能独立于loadEventsForUser函数调用。
2.将顺序编程代码切分成独立的可重用的函数并不总是很容易。
3.函数能够使用在其他流程被其他组件复用。
4.相比回调函数有更好的可读性。
5.比顺序编程有着更好地出错处理。
Fibers解决方案
要理解Fibers,需要对抢占式多任务和协作式多任务有一个了解:
抢占式多任务:
在计算中,抢占是暂时中断正在进行的计算任务,而不是与其合作,在其中断以后将继续恢复该任务的执行。这种改变称为上下文切换。
Linux 的调度程序Scheduler (特权任务)会采取取消进程任务,而不是与它的合作。抢占式多任务处理的缺点是操作系统可能会在一个不适当的时间进行上下文切换。
协作式多任务:
早期的多任务处理系统使用的自愿割让时间给另外另一个应用程序。这种方法最终由许多计算机操作系统支持,今天被称为合作多任务处理。
合作多任务依赖于线程一旦在停止是否放弃控制权。合作多任务处理的缺点是如果编写拙劣的代码会堵塞整个系统。实时嵌入式的系统往往采取合作多任务处理范式以获得真正的实时高性能。
Fibers是一个轻量的线程 (也称为绿色线程) ,它是一个进程或应用级别的概念,并不对应着OS的线程,它提供类似执行流的线程,当OS 线程是抢占式调用时,程序员可以使用fibers 实现合作多任务, Fibers概念类似协程coroutines ,执行能够被程序进行暂停或继续。
Fiber代码如下:
var Fiber = require('fibers');
var log_sequence_counter = 1;
function sleep(task, milliseconds) {
var fiber = Fiber.current;
setTimeout(function() {
console.log(log_sequence_counter++ + task + ' callback');
fiber.run();
}, milliseconds);
console.log(log_sequence_counter++ + task + ' thread/fiber suspended');
Fiber.yield();
console.log(log_sequence_counter++ + task + ' thread/fiber resumed');
}
var task1 = function() {
console.log(log_sequence_counter++ + ' task 1 waiting for sleep to end ');
sleep(" task 1",1000);
console.log(log_sequence_counter++ + ' task 1 got back from sleep');
}
var task2 = function() {
console.log(log_sequence_counter++ + ' task 2 waiting for sleep to end ');
sleep(" task 2", 1000);
console.log(log_sequence_counter++ + ' task 2 got back from sleep');
}
Fiber(task1).run();
Fiber(task2).run();
console.log( log_sequence_counter++ + ' main execution flow');
|
有如下特点:
1. Fibers 是使用Fiber() 函数创建,任务功能是使用run方法执行。
2. 一个Fiber可以暂停或继续. 这种方式可以在进行非堵塞IO调用时反复使用。
3. 当一个fiber线程Fiber.current调用时,返回是当前正在执行的fiber.
4. Fiber.yield是暂停当前线程执行,这样允许其他fiber线程能够协作地执行。
5. task1 和 task2 函数并没有回调函数或promises,它是顺序编程代码
6. task1 和 task2 函数并不知道fibers,这样开发者能够作为普通顺序代码读写改这些代码函数。
7.Fibers提供协作多任务能力。
上述代码输出结果:
$ node fibersExample.js
1 task 1 waiting for sleep to end
2 task 1 thread/fiber suspended
3 task 2 waiting for sleep to end
4 task 2 thread/fiber suspended
5 main execution flow
6 task 1 callback
7 task 1 thread/fiber resumed
8 task 1 got back from sleep
9 task 2 callback
10 task 2 thread/fiber resumed
11 task 2 got back from sleep
|
上述输出结果编号显示执行顺序,即使Node.js是一个单线程,它也能够以非堵塞方式执行多任务。
http://www.jdon.com/46372
分享到:
相关推荐
在计算机编程中,特别是JavaScript这种单线程环境中,异步编程是解决性能瓶颈和避免阻塞的主要手段。Reactor模式是一种设计模式,用于处理大量并发事件,它通过集中式的事件调度和错误处理,确保了程序的高效运行和...
在实际项目中,高性能异步爬虫不仅限于Python,其他编程语言如JavaScript(Node.js的`async/await`)、Java(Reactor或Quasar框架)等也有相应的异步解决方案。理解并掌握这些技术,对于提升数据获取能力具有重要...
这种方式在高并发场景下表现出色,如Node.js和Reactor模式。 5. **Epoll模型**:Linux特有的高效I/O事件通知机制,适用于高并发的网络服务器。它减少了系统调用的次数,降低了上下文切换的开销。 6. **反应器和...
15. **异步事件驱动模型**:如Reactor模式,使用事件循环(Event Loop)处理大量并发事件,如Node.js的实现。 通过对上述知识点的深入理解和实践,开发者可以构建出能够高效处理高并发场景的系统。在分析...
4. **异步事件驱动**:结合事件驱动编程模型,如Reactor模式,多线程可以更好地处理大量的并发连接,如Node.js的事件循环。 四、多线程的挑战与解决方案 1. **线程安全**:确保多线程环境下数据的一致性和完整性是...
因此,我们需要采用异步非阻塞模型,如事件驱动编程(Event-Driven Programming)或反应式编程,例如Node.js和Reactor模式。此外,多路复用技术如IO多路复用(select、poll、epoll)也能有效提高处理能力。 2. **...
- **Java Servlets**:Netty、Vert.x 和原生Servlets在性能方面表现出色,特别是在与Ruby on Rails (RoR)、Node.js等其他框架的对比中。 - **基于NIO的Servlet**:自J2SE 1.4起,Java引入了NIO(非阻塞I/O),为开发...
2. **并发模型**:服务器通常采用不同的并发模型,如基于线程的模型(每个请求分配一个线程)、基于事件的模型(如Reactor模式)或基于异步I/O的模型(如Node.js的事件驱动)。了解这些模型的优缺点对选择合适的并发...
这可能需要用到多线程或多进程技术,或者基于事件驱动的编程模型,如Reactor模式。还需要考虑锁机制,如读写锁,来保证数据的一致性。 3. **数据一致性**:在抢答过程中,确保数据的一致性至关重要。可以采用数据库...
万人同时在线意味着需要处理大量的并发连接,这通常采用多线程或多进程技术,或者使用异步IO模型如Reactor或Proactor模式。同时,为了保证数据传输的稳定性和实时性,可能需要用到TCP/IP协议栈以及心跳包、断线重连...
4. **非阻塞I/O和异步处理**:使用非阻塞I/O模型(如Node.js的Event Loop或Java的NIO)和异步编程,可以让服务器在等待IO操作完成时处理其他任务,提高资源利用率。 5. **微服务架构**:将大型应用拆分为一系列小型...
9. **响应式编程**:ktor与Reactor、RxJava等响应式库兼容,可以实现响应式编程,适应异步编程模型。 ktor的使用方法通常包括以下步骤: 1. **添加依赖**:在构建配置中添加ktor相关的依赖,如ktor-server-core、...
**Reactor.nim** 库是 Nim 语言中的一个事件驱动 I/O 库,类似于 Node.js 的 Event Loop 或 Python 的 asyncio 库。它提供了一种处理并发和非阻塞 I/O 的方式,特别适合于高性能网络服务和文件系统操作。ReactorFuse...
3. **异步编程**:采用非阻塞I/O或事件驱动模型(如Epoll、Kqueue等)进行异步网络编程,可以显著提高并发处理能力。 4. **缓存策略**:理解并运用合适的缓存策略,如HTTP缓存、本地缓存,可以减少网络请求,降低...
多线程、多进程技术可以并行处理多个玩家请求,异步IO模型如Reactor和Proactor模式则能提高服务器的响应速度。 4. **数据存储**:游戏数据的持久化存储通常使用关系型数据库(如MySQL)或非关系型数据库(如MongoDB...
3. **架构模式**: 服务器可以采用单线程、多线程或多进程模型,也可以使用异步非阻塞I/O(如Node.js)或事件驱动架构(如Reactor模式)以提高并发性能。 4. **性能优化**: 服务器性能优化涉及负载均衡、缓存策略、...
- **异步非阻塞服务器**(如Reactor模式):使用I/O复用技术,单线程或少量线程即可处理大量并发连接。 - **预读取服务器**(Proactor模式):利用异步I/O,提前发起I/O操作,处理其他任务,I/O完成后回调。 - **...
与Spring WebFlux类似,Vert.x通过异步、非阻塞的方式来提升系统的并发能力。\n\n**ServiceComb API与Edge Service**\n\nServiceComb API是ServiceComb的核心部分,它定义了服务之间的交互方式。Edge Service,即...
JavaScript 允许开发者创建交互式网页,并且是构建 Web 应用程序的重要工具,尤其是在搭配 Node.js 进行服务器端编程时。 根据提供的压缩包文件名 "mytunes-master",我们可以推测这个文件夹可能是项目的主分支或者...
Node.js中的事件循环示例 在开始之前 作为安装节点的环境。 您可以跳过Express部分。 例子 事件循环 事件循环是视频游戏逐事件模拟的非常简单的结构。 while ( true ) { var event = nextEvent ( ) ; ...