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