Node 在淘宝的应用实
践
这些年,我们一起开发过的 Node.js
By @ 朴灵




    1
关于我
                        连 IE6 都能
                        兼容的男
• CNode 社区                  人
• 前端 at SAP for Mobile Web
• 前端 at 淘宝数据产品部

                2
议程
• 我为什么要做 Node 开发
• 准备工作与作品
• Node.js 带来的新问题与如何逆袭
 •   异步编程

 •   缓存与内存

 •   Buffer

• Node.js 在淘宝产品中的一点实践
               3
长达半天的欢乐


       icons powered by morcha design




前端屌丝的坎坷路
           4
Node 与前端的亲缘




     5
Node 与前端的亲缘




     6
左手 HTML5 右手
        Node.js
• 熟知的 JavaScript 执行原理 / 事件循环
• 熟悉的 API 、事件、单线程、回调
• Ajax/ 异步
• 相比 HTML5 , Node 将开启更多的可能
 性


              7
好奇心 & 满足感
• HTTP 协议栈:深入后端,反哺前端
 •   Status code

 •   Cookie & Session

 •   Request & Response

 •   Web Framework

• 高性能 JavaScript 平台
• 拓宽视野
                          8
Go, go, go!!!
var http example.jsServer running at http://127.0.0.1:1337/res) {
% node = require('http');http.createServer(function (req,
 res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello Worldn');}).listen(1337,
'127.0.0.1');console.log('Server running at
http://127.0.0.1:1337/');




                                  9
作品




 10
全 JavaScript 堆栈的产
         品
                               Connect


WebGhost                     ITier


Redis & MRedis
                 MongoSkin


                              Should

                        11
前端工程师到 Web 工程
     师
            让女神青睐


如何摆脱前端屌丝的身份

           结果重了 10 斤




      12
问题:异步协作
var proxy = new EventProxy();
proxy.all("template", "data", "l10n", render);
•    嵌套还是并行?
$.get("template", function (template) {
    // something
$.get("template", function (template) {
    proxy.trigger("template", template);
}); // something
    $.get("data", function (data) {
$.get("data", function (data) {
        // something
    // something
        $.get("l10n", function (l10n) {
    proxy.trigger("data", data);
});         // something
            render(template, data);
$.get("l10n", function (l10n) {
        });
    // something
    });
    proxy.trigger("l10n", l10n);
});
});
                                  13
问题:异步还是同步
var proxy = new EventProxy();
var status = "ready";
var _getFile = function (callback) {


 • 复杂的异步编程
  proxy.once("template", callback);
  if (status === "ready") {
    fs.readFile("views/index.html", function (err, file) {
      status = "pending";
      proxy.fire("template", err, file);
    });
  }
};
var view = fs.readFileSync("../views/index.html", "utf8");
var _template;
var getTemplate = function (callback) {
  if (_template) {
    callback(null, _template);
  } else {

 同步 + 缓存,妥妥滴
    _getFile(function (err, file) {
      if (!err && !_template) {
        _template = file.toString();
      }
      callback(null, _template);
    });
  }
};
                                                 14
问题:缓存的使用
var map = {};
var get = function (key) {
var LimitableMap = require('limitablemap');
  return map[key];
}; map = new LimitableMap(1000);
var
map.set("key1", "key1");
var set = function (key, value) {
map.get("key1");
  map[key] = value;
};
// 检查缓存
if (!get(key)) {
  // 从数据库或别的地方获取了对象后,放进缓存中
  set(key, value);
}

                         15
问题: Session
• V8 内存堆栈限制
• 分布式中, Session 需要共享
 (Redis)
• 重启应用不丢失 session
• 多点 Redis ,备份容灾
            16
// 正确的方法
var chunks = [];
var size = 0;

       问题: Buffer 对象
res.on('data', function (chunk) {
  chunks.push(chunk);
  size += chunk.length;
});
var data = function () {
res.on('end',
               "";
res.on('data', function (chunk) {
  var data = null;
   //简单且正确的方法 对象
      chunk 是一个 Buffer
//switch(chunks.length) {
var bufferHelper = 隐藏的 toString ()
    case += chunk;// new BufferHelper();
   data 0: data = new Buffer(0);
}) break;
req.on("data", function (chunk) {
    case 1: data = chunks[0];
   bufferHelper.concat(chunk);
.on("end", function () {
     break;
})// 对 data 转码
    default:
.on('end', function () {
}); data = new Buffer(size);
   var html i= bufferHelper.toBuffer().toString();
     for (var = 0, pos = 0, l = chunks.length; i < l; i++) {
       var chunk = chunks[i];
}); chunk.copy(data, pos);
       pos += chunk.length;
     }
     break;
  }                                       17
问题: String 传输的性
       能
• 7k 大小的静态文件,需做替换
• String ➛ Buffer
• 缓存 Buffer , 4 倍性能提升

            18
问题:多核 CPU 的利
var cluster = require('node-cluster');

  •         用
    单线程与多核 CPU
var master = new cluster.Master();
master.register(8080, 'app.js');
  • 单线程因为异常退出?
master.dispatch();    负载均衡
                      多核利用
  • 仿若熟悉的 Web Worker: 提升稳定
var http = require('http');
var cluster = require('node-cluster');
      child_process
var worker = new cluster.Worker();
  •    进程与消息
var server = http.createServer(function (req, res) {
  // server
});

worker.ready(function (socket) {
  server.emit('connection', socket);
});                               19
小结
• 异步编程问题? EventProxy 、 JScex
 等
• 内存限制问题?第三方存储 Redis
• CPU 消耗问题?缓存中间结果
• 单线程 CPU 利用不足问题?多进程
• 单线程稳定性问题? Node-Cluster
              20
实践:运维
         // 异步方法中 try catch 是不靠谱
• 异常     滴
         双机房
         进程数量
         // 异步方法的异常
• 日志     双 Redis MRedis 模块
         CPU
         async(functionMongoSkin {
         双 MongoDB
         内存if (err) {
                         (err, data)

• 监控     数据源集群
         Loadlogger.error(err);
         磁盘 IO // TODO
             return;
• 部署     流量}
           // TODO

• 备份容灾   });



          21
实践:测试
• 测试
    •   单元测试

    •   自动化测试

    •   性能测试         Should.js

•   持续集成                         WebGhost


                22
实践: CommonJS & Node &
         NPM

CommonJS            NPM




           Node

             23
实践:公司范围内共享代码


• 如何保护隐私代码
• 如何重用散乱代码
• 如何告别复制粘贴

         24
实践:公司范围内共享代码
          单向同步
 本地 NPM          官方 NPM




私有模块      项目      公有模块


            25
展望
• 深度发掘前端开发和用户体验
• 无需与开发沟通,节省成本
• 知晓细节,更易改进产品体验
• 感谢伟大的 github
• 感谢伟大的 NPM 促成的生态圈
• 感谢 Node 这件美妙的礼物
          26
Q&A
屌丝のぎゃくしゅ
    う


           27

Node.js在淘宝的应用实践

  • 1.
  • 2.
    关于我 连 IE6 都能 兼容的男 • CNode 社区 人 • 前端 at SAP for Mobile Web • 前端 at 淘宝数据产品部 2
  • 3.
    议程 • 我为什么要做 Node开发 • 准备工作与作品 • Node.js 带来的新问题与如何逆袭 • 异步编程 • 缓存与内存 • Buffer • Node.js 在淘宝产品中的一点实践 3
  • 4.
    长达半天的欢乐 icons powered by morcha design 前端屌丝的坎坷路 4
  • 5.
  • 6.
  • 7.
    左手 HTML5 右手 Node.js • 熟知的 JavaScript 执行原理 / 事件循环 • 熟悉的 API 、事件、单线程、回调 • Ajax/ 异步 • 相比 HTML5 , Node 将开启更多的可能 性 7
  • 8.
    好奇心 & 满足感 •HTTP 协议栈:深入后端,反哺前端 • Status code • Cookie & Session • Request & Response • Web Framework • 高性能 JavaScript 平台 • 拓宽视野 8
  • 9.
    Go, go, go!!! varhttp example.jsServer running at http://127.0.0.1:1337/res) { % node = require('http');http.createServer(function (req, res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello Worldn');}).listen(1337, '127.0.0.1');console.log('Server running at http://127.0.0.1:1337/'); 9
  • 10.
  • 11.
    全 JavaScript 堆栈的产 品 Connect WebGhost ITier Redis & MRedis MongoSkin Should 11
  • 12.
    前端工程师到 Web 工程 师 让女神青睐 如何摆脱前端屌丝的身份 结果重了 10 斤 12
  • 13.
    问题:异步协作 var proxy =new EventProxy(); proxy.all("template", "data", "l10n", render); • 嵌套还是并行? $.get("template", function (template) { // something $.get("template", function (template) { proxy.trigger("template", template); }); // something $.get("data", function (data) { $.get("data", function (data) { // something // something $.get("l10n", function (l10n) { proxy.trigger("data", data); }); // something render(template, data); $.get("l10n", function (l10n) { }); // something }); proxy.trigger("l10n", l10n); }); }); 13
  • 14.
    问题:异步还是同步 var proxy =new EventProxy(); var status = "ready"; var _getFile = function (callback) { • 复杂的异步编程 proxy.once("template", callback); if (status === "ready") { fs.readFile("views/index.html", function (err, file) { status = "pending"; proxy.fire("template", err, file); }); } }; var view = fs.readFileSync("../views/index.html", "utf8"); var _template; var getTemplate = function (callback) { if (_template) { callback(null, _template); } else { 同步 + 缓存,妥妥滴 _getFile(function (err, file) { if (!err && !_template) { _template = file.toString(); } callback(null, _template); }); } }; 14
  • 15.
    问题:缓存的使用 var map ={}; var get = function (key) { var LimitableMap = require('limitablemap'); return map[key]; }; map = new LimitableMap(1000); var map.set("key1", "key1"); var set = function (key, value) { map.get("key1"); map[key] = value; }; // 检查缓存 if (!get(key)) { // 从数据库或别的地方获取了对象后,放进缓存中 set(key, value); } 15
  • 16.
    问题: Session • V8内存堆栈限制 • 分布式中, Session 需要共享 (Redis) • 重启应用不丢失 session • 多点 Redis ,备份容灾 16
  • 17.
    // 正确的方法 var chunks= []; var size = 0; 问题: Buffer 对象 res.on('data', function (chunk) { chunks.push(chunk); size += chunk.length; }); var data = function () { res.on('end', ""; res.on('data', function (chunk) { var data = null; //简单且正确的方法 对象 chunk 是一个 Buffer //switch(chunks.length) { var bufferHelper = 隐藏的 toString () case += chunk;// new BufferHelper(); data 0: data = new Buffer(0); }) break; req.on("data", function (chunk) { case 1: data = chunks[0]; bufferHelper.concat(chunk); .on("end", function () { break; })// 对 data 转码 default: .on('end', function () { }); data = new Buffer(size); var html i= bufferHelper.toBuffer().toString(); for (var = 0, pos = 0, l = chunks.length; i < l; i++) { var chunk = chunks[i]; }); chunk.copy(data, pos); pos += chunk.length; } break; } 17
  • 18.
    问题: String 传输的性 能 • 7k 大小的静态文件,需做替换 • String ➛ Buffer • 缓存 Buffer , 4 倍性能提升 18
  • 19.
    问题:多核 CPU 的利 varcluster = require('node-cluster'); • 用 单线程与多核 CPU var master = new cluster.Master(); master.register(8080, 'app.js'); • 单线程因为异常退出? master.dispatch(); 负载均衡 多核利用 • 仿若熟悉的 Web Worker: 提升稳定 var http = require('http'); var cluster = require('node-cluster'); child_process var worker = new cluster.Worker(); • 进程与消息 var server = http.createServer(function (req, res) { // server }); worker.ready(function (socket) { server.emit('connection', socket); }); 19
  • 20.
    小结 • 异步编程问题? EventProxy、 JScex 等 • 内存限制问题?第三方存储 Redis • CPU 消耗问题?缓存中间结果 • 单线程 CPU 利用不足问题?多进程 • 单线程稳定性问题? Node-Cluster 20
  • 21.
    实践:运维 // 异步方法中 try catch 是不靠谱 • 异常 滴 双机房 进程数量 // 异步方法的异常 • 日志 双 Redis MRedis 模块 CPU async(functionMongoSkin { 双 MongoDB 内存if (err) { (err, data) • 监控 数据源集群 Loadlogger.error(err); 磁盘 IO // TODO return; • 部署 流量} // TODO • 备份容灾 }); 21
  • 22.
    实践:测试 • 测试 • 单元测试 • 自动化测试 • 性能测试 Should.js • 持续集成 WebGhost 22
  • 23.
    实践: CommonJS &Node & NPM CommonJS NPM Node 23
  • 24.
  • 25.
    实践:公司范围内共享代码 单向同步 本地 NPM 官方 NPM 私有模块 项目 公有模块 25
  • 26.
    展望 • 深度发掘前端开发和用户体验 • 无需与开发沟通,节省成本 •知晓细节,更易改进产品体验 • 感谢伟大的 github • 感谢伟大的 NPM 促成的生态圈 • 感谢 Node 这件美妙的礼物 26
  • 27.