Node.JS
基于Fibers开发Node.js的ExpressJS Restful服务
目前大部分Web应用包括Ruby on Rails, Java Spring, Django都是使用顺序编程风格。顺序编程是非常简单和可读的,大部分开发者都是以顺序方式思考,喜欢将一个应用逻辑划分为顺序的时序步骤。顺序编程通常会导致堵塞I/O,因为线程是遵循先来后到的多任务方式,而不是一种协作式的多任务方式,而非堵塞I/O能够带来更好地扩展性和性能。
但是非堵塞I/O为特点的事件编程确实没有顺序编程代码易于理解,如何能够以易于理解的顺序编程风格编写异步非堵塞I/O的代码,Fibers提供了这种平衡。
Fibers是一个可以在Linux等抢占式线程调度下执行协作多任务的Node.js的线程库包,它是一个流程或应用级别的概念,并不对应着OS的线程,它提供类似执行流的线程,当OS 线程是抢占式调用时,程序员可以使用fibers 实现合作多任务, Fibers概念类似协程coroutines ,执行能够被程序进行暂停或继续。
下面以在一个ExpressJS restful服务案例看看Fibers如何实现了非堵塞异步编程,又能如同顺序编程一样易于理解和阅读,这个服务提供三个方法:
- /google :– 发出一个google http调用,从google返回是一响应给客户端
- /user/:fb_id :– 返回一个指定的facebook id的用户资料JSON数据
- /user/:fb_id/events :– 根据指定的facebook id返回用户和用户的事件。
代码如下:
var express = require('express');
var fibersMiddleWare = require('./fib-middleware');
var request = require("./fib-request");
var fib_redis = require("./fib-redis");
var redis_client = fib_redis.init(require("redis-url").connect());
/*------------------------------------Models----------------------------------*/
var User = {};
User.get = function(id){
var user_json = redis_client.get("user:" + id);
return JSON.parse(user_json);
};
var Event = {};
Event.getUserEvents = function(user_id){
var user_json = redis_client.mget("user:" + id + ":events");
return JSON.parse(user_json);
};
/*----------------------------------------------------------------------------*/
var app = express();
app.use(fibersMiddleWare.runInFiber);
app.get('/google', function(req, res){
var google_response_body = request.get('http://google.com')
res.send(google_response_body);
});
app.get('/users/:fb_id',function(req, res){
var user = User.get(req.params.fb_id)
res.setHeader('Content-Type', 'application/json');
res.send(200,JSON.stringify(user));
});
app.get('/users/:fb_id/events',function(req, res){
var user = User.get(req.params.fb_id)
var events = Event.getUserEvents(user.id);
var response = {'user' : user, 'events' : events};
res.setHeader('Content-Type', 'application/json');
res.send(200,JSON.stringify(response));
});
var server = app.listen(3000, function() {
console.log('Listening on port %d', server.address().port);
});
上面代码使用了中间件fib-middleware', 'fib-request' and 'fib-redis'。有以下特点:
- Fibers 是通过app.use(fibersMiddleWare.runInFiber)作为中间件设置的
- Controller 和 Model 方法是同步和顺序代码
- Http 获得Request 和数据存储 (redis_client)操作是异步
- 除了顺序代码,在 Server.js中并没有什么特殊的
- 这段代码模拟了非堵塞IO代码也可以是同步和顺序的。
让我们看看server.js依赖的fib-middleware.js
var Fiber = require("fibers");
function fiberMiddleWare(req,resp,next){
Fiber(function(){
next();
}).run();
}
exports.runInFiber = fiberMiddleWare
ExpressJS 中间件是一个拦截器过滤模式实现,能够用于实现在请求递交到控制器之前或之后的任何动作操作。
fib-middleware.js 是一个简单机制,在fiber上下文中处理所有的http请求,它类似Fiber(task1).run()
fib-request.js 和 fib-redis.js是提供request和redis的Fibers支持。如果原库包有就不需要了。
fib-request.js代码:
var request = require('request');
var Fiber = require("fibers");
function get(url){
var error,response,body;
var fiber = Fiber.current;
request.get(url,
function(err, resp, b){
error = err;
response = resp;
body = b;
fiber.run()
});
Fiber.yield();
return body;
}
exports.get = get;
fib-redis.js代码如下:
ar Fiber = require('fibers');
var conn;
function get(key){
var error, value;
var fiber = Fiber.current;
conn.get(key, function(err,val){
error = err;
value = val;
fiber.run();
});
Fiber.yield();
return value;
}
function init(connection){
conn = connection;
return { 'get':get};
}
exports.init = init;
fib-request.js 和 fib-redis.js 代码类似sleep案例,线程可暂停然后继续这样让功能实现异步。
上述ExpressJS 应用案例展示了使用Fibers通过顺序代码的方式编写非堵塞I/O。当然Fibers 不只是支持NodeJS,还可以支持其他语言如Ruby, Python等。
大部分应用代码可以分成两个主要部分:
- 业务功能部分:– 所有业务功能逻辑是开发者主要精力编写的。
- 框架库包部分:– 我们会使用一些库包mvc framework, database驱动等等,这可能是由第三方开发,通常跨团队使用。开发人员很少去修改这部分。
具体可见 Bob大叔的 干净架构
Server.js 属于业务应用部分,ExpressJS, Fibers.js, redis.js, fib-middleware.js, fib-request 和 fib-redis.js 属于框架或库包部分,能够重用在很多项目中。而创建Node.js和Fibers的结合部分,也就是fiber包装器wrapper也是相当简单的。
上述代码的性能如下:
$ siege -r 10 -c 100 http://localhost:3000/users/11265765672
Transactions: 1000 hits
Availability: 100.00 %
Elapsed time: 9.09 secs
Data transferred: 0.20 MB
Response time: 0.01 secs
Transaction rate: 110.01 trans/sec
Throughput: 0.02 MB/sec
Concurrency: 0.92
Successful transactions: 1000
Failed transactions: 0
Longest transaction: 0.06
Shortest transaction: 0.00
Reactor模式 - 从回调嵌套到Promise再到Fibers
在Node.js中使用Javascript Generators