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如何实现了非堵塞异步编程,又能如同顺序编程一样易于理解和阅读,这个服务提供三个方法:

  1. /google :– 发出一个google http调用,从google返回是一响应给客户端
  2. /user/:fb_id :– 返回一个指定的facebook id的用户资料JSON数据
  3. /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等。

大部分应用代码可以分成两个主要部分:

  1. 业务功能部分:– 所有业务功能逻辑是开发者主要精力编写的。
  2. 框架库包部分:– 我们会使用一些库包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

异步编程

事件驱动