问题和解决方法

在对接支付宝时,发生了这样的问题,使用http链接时,支付宝notify是没有问题的,但换成https后便通知不到了

最可能的问题便是ssl证书出错了,注意支付宝不支持自签名证书(https://doc.open.alipay.com/doc2/detail?treeId=66&articleId=104422&docType=1)

1
2
去这个网站检测下你的CSR吧
https://cryptoreport.websecurity.symantec.com/checker/views/csrCheck.jsp

事情经过

某天,同事们偷偷给网站加了安全套接字,把http换成了https,于是我负责的支付模块就出错了。

一开始,我就简单改了config,提交给支付宝的notify_url改成了https。本以为就没问题了,但在我又付出了¥0.01的巨款后,发现支付宝还是没有通知过来。。然后就懵逼了

Smaller icon

懵逼了大概7.8秒后,我就去百度了一下、、、我居然去百度了一下。有人说原因是:

支付宝对ssl证书也有检查,而那种免费的ssl证书通不过检查。后来没办法就只好改成http notify了

好吧。。。这种解决方法,这尼玛根本就不是个解决方案、

注:俺们公司用的CA是GeoTrust,不是他们说的免费证书。顺带感叹下做CA真赚钱

于是又去问支付宝客服,给了他交易号,让他帮我查了下日志,就是这样的
Smaller icon

http状态码是0耶。看来支付宝确实没有去访问我们的服务器嘛。看来确实在ssl证书检查这块了,但还不清楚是什么问题,还是问问Google吧~(推荐翻墙工具:http://www.ishadowsocks.net)

Google帮我搜到了支付宝的这个https://doc.open.alipay.com/doc2/detail?treeId=66&articleId=104422&docType=1页面(别问我为什么之前没看到。。支付宝文档太多了,我有点眼花),其中对异步通知是这么说的:

需确认页面是http还是https,如果是https,那么需要安装ssl证书,证书要求有如下:
要求“正规的证书机构签发,不支持自签名”,如果不理解请咨询证书供应商。
域名证书检测地址参考:https://cryptoreport.websecurity.symantec.com/checker/

然后就拿着csr去检测了。。靠,果然有鬼(毕竟不是我写的)~~然后改好重新部署

再付出巨款做测试,终于等来支付宝敲门啦

事故总结

不能说是事故,,因为网站还在测试阶段。。否则,我就该引咎辞职了(宝宝心里苦啊、、)

  1. 仔细看文档
  2. 不要用百度

生成key

1
2
3
openssl genrsa -des3 -out prvtkey.pem 2048
或者无密码保护的
openssl genrsa -out prvtkey.pem 2048

生成.csr(用这个向数字证书颁发机构(即CA)申请一个数字证书)

1
openssl req -new -key prvtkey.pem -out cert.csr

然后填写信息,注意Common Name (eg, your websites domain name),要填写网站域名,如:pay.abc.com

生成的.csr文件是否规范可以在这检测:
https://cryptoreport.websecurity.symantec.com/checker/views/csrCheck.jsp

然后向CA申请数字证书,再用CA给的cacert.pem替换原来的prvtkey.pem

自建CA

http://www.cnblogs.com/AloneSword/p/3809002.html

setTimeout clearTimeout

1
2
3
var timer=setTimeout
clearTimeout(timer)

数组深拷贝

1
2
3
4
5
6
var a=[1,2,3];
b=a.slice(0,[end])
slice 方法返回一个 Array 对象
slice 方法一直复制到 end 所指定的元素
b=a.concat();

选择器

继承

  • 子元素从父元素继承属性,IE/Windows 直到 IE6 还存在相关的问题,在表格内的字体样式会被忽略。
  • 针对子元素创建规则,则可摆脱继承

元素选择器

  • 文档的元素就是最基本的选择器 比如 p、h1、em、a,甚至可以是 html 本身

选择器分组

1
h2, p {color:gray;}

将 h2 和 p 选择器放在规则左边,然后用逗号分隔,就定义了一个规则。其右边的样式(color:gray;)将应用到这两个选择器所引用的元素。逗号告诉浏览器,规则中包含两个不同的选择器

  • 通配符选择器 *,与任何元素匹配

类选择器

  • class

结合元素选择器

1
p.important {color:red;} //匹配 class 属性包含 important 的所有 p 元素

class 值中可能包含一个词列表,各个词之间用空格分隔

1
2
3
4
5
< p class="important warning">
This paragraph is a very important warning.
< /p>
.important.warning {background:silver;}//class 中同时包含 important 和 warning

在 IE7 之前的版本中,不同平台的 Internet Explorer 都不能正确地处理多类选择器

ID选择器

  • ID 选择器前面有一个 # 号
  • 不能使用 ID 词列表
1
#intro {font-weight:bold;}

属性选择器

1
2
3
*[title] {color:red;}// 把包含标题(title)的所有元素变为红色
a[href] {color:red;} // 对有 href 属性的锚(a 元素)应用样式
a[href][title] {color:red;} //将同时有 href 和 title 属性的 HTML 超链接的文本设置为红色

根据具体属性值选择

1
2
3
4
//将指向 Web 服务器上某个指定文档的超链接变成红色
a[href="http://www.w3school.com.cn/about_us.asp"] {color: red;}
//可以把多个属性-值选择器链接在一起来选择一个文档
a[href="http://www.w3school.com.cn/"][title="W3School"] {color: red;}

后代选择器

  • 依赖于上下文关系来应用或者避免某项规则
1
2
3
4
5
6
7
8
9
10
11
12
13
//列表中的 strong 元素变为斜体字
li strong {
font-style: italic;
font-weight: normal;
}
<p><strong>我是粗体字,不是斜体字,因为我不在列表当中,所以这个规则对我不起作用</strong></p>
<ol>
<li><strong>我是斜体字。这是因为 strong 元素位于 li 元素内。</strong></li>
<li>我是正常的字体。</li>
</ol>

xml相关

xml转json: npm包 xml2js

1
2
3
4
5
6
7
//explicitArray:false 返回json的value不为数组,explicitRoot:false,去掉头部
var xml='<xml><a>1</a><b>2</b></xml>';
blue.promisify(new xml2js.Parser({explicitArray:false,explicitRoot:false}).parseString)(xml).then(function(result){
console.dir(result);
},function(error){
//{ a: '1', b: '2' }
})

json转xml:npm包 xmlbuilder

1
2
3
4
5
6
7
8
9
10
11
//‘xml’ root名称,headless: true 去掉 xml 头部,pretty: true 格式化
var json = {
a: 1,
b: 2
};
console.error(xmlbuilder.create('xml',{headless: true}).ele(json).end({ pretty: true}))
<xml>
<a>1</a>
<b>2</b>
</xml>

bluebird

promise.promisify 将api promise 化

prcess

To set an environment variable in Windows:

1
SET NODE_ENV=development

on OS X or Linux

1
export NODE_ENV=development

高阶函数:把函数参数作为参数,或作为返回值

偏函数: 将传入参数作为判断或者其他逻辑条件

注意点

异常处理

异步I/O有两个处理阶段,中间有事件循环调度,异步方法在提交请求后就返回了,try/catch只能捕获当次事件循环内的异常,不能捕获callback的异常

1
2
3
4
5
6
7
8
9
try {
req.body = JSON.parse(buf, options.reviver);
//callback()不能放在这,若callback异常,会导致它多次执行
} catch (err) {
err.body = buf;
err.status = 400;
return callback(err);
}
callback();

阻塞代码

这么写会持续占用CPU资源,破坏事件循环的调度,因为Node是单线程,会导致其余请求得不到响应

1
2
3
4
5
6
// TODO
var start = new Date();
while (new Date() - start < 1000) {
// TODO
}
// 需要阻塞的代码

异步编程解决方案

事件

Node中事件的发布通常是伴随事件循环而异步触发的

  • 事件与侦听器是多对多的,设置过多的侦听器会导致过多的CPU占用
  • 要给EventEmitter对象添加error事件和处理,若触发error事件不处理,会引起线程退出\
  • 使用once()添加的侦听器只会执行一次,在执行后就会将相关的事件移除

使用事件的途径

  • 实例化events
1
2
3
4
5
var events =require(‘events’);
var emitter = new events.EventEmitter();
emitter.on('do',function(value){console.log(value)});
emitter.emit('do','doing');
  • 继承events模块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var util = require('util')
var events = require('events');
function Stream() {
events.EventEmitter.call(this);
}
util.inherits(Stream, events.EventEmitter);
Stream.prototype.test = function() {
var self = this;
try {
throw 111;
} catch (error) {
self.emit('error')
}
}
var stream = new Stream();
stream.on('error', function() {
console.error('asdasd')
});
stream.test();
利用Once()解决雪崩问题
  • 在高访问量、大并发量的情况下缓存突然失效, 大量的请求同时涌入数据库中,数据库无法承受,影响网站速度
  • 在缓存中无数据时,访问量大的话,同条SQL可能会被执行多次,此时可以将所有请求放入事件队列,利用once()来绑定,SQL在执行时,新到的相同调用只需在队列中等待数据,一旦查询结束,得到的结果可以被这些调用共同使用
1
2
3
4
5
6
7
8
9
10
11
12
var proxy = new events.EventEmitter();
var status = "ready";
var select = function(callback) {
proxy.once("selected", callback);
if (status === "ready") {
status = "pending";
db.select("SQL", function(results) {
proxy.emit("selected", results);
status = "ready";
});
}
};

可能会因为侦听器过多引发警号,调用setMaxListeners(0)移除或设置大的阈值

EventProxy 处理多事件对一侦听器,适用于实例化事件
  • all() 当每个事件都被触发了才会执行,只执行一次
1
2
3
var proxy = new EventProxy();
proxy.all("template", "data", "resources", function(template, data, resources) { // TODO
});
  • tail() 首次也是需要每个事件都被触发,之后只要某个事件触发,就会用最新的数据执行
  • after() 事件执行多少次后执行侦听器的单一事件组合订阅
1
2
3
var proxy = new EventProxy();
proxy.after("data", 10, function(datas) { // TODO
});
  • not()
  • any()

EventProxy对异常处理的优化

  • fail()
1
2
3
4
5
6
7
8
9
10
11
12
ep.fail(callback);
//等价于
ep.fail(function(err) {
callback(err);
});
//又等价于
ep.bind('error', function(err) {
// 卸载所有处理函数
ep.unbind();
// 异常回调
callback(err);
});
  • done()
1
2
3
4
5
6
7
8
9
ep.done('tpl');
//等价于
function(err, content) {
if (err) {
// 发生异常,交给error事件处理函数处理
return ep.emit('error', err);
}
ep.emit('tpl', content);
}

done()接受函数参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ep.done(function(content) {
// TODO
// 无需考虑异常
ep.emit('tpl', content);
});
//等价于
function(err, content) {
if (err) {
// 发生异常 给error事件的处理函数处理
return ep.emit('error', err);
}
(function(content) {
// TODO
// 无需考虑异常
ep.emit('tpl', content);
}(content));
}

代码对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
exports.getContent = function(callback) {
var ep = new EventProxy();
ep.all('tpl', function(tpl, data) {
// 成功回调
callback(null, {
template: tpl,
data: data
});
});
// 帧听error事件
ep.bind('error', function(err) {
// 卸载 所有处理函数
ep.unbind();
// 异常回调
callback(err);
});
fs.readFile('template.tpl', 'utf-8', function(err, content) {
if (err) {
// 发生异常,给error事件的处理函数处理
return ep.emit('error', err);
}
ep.emit('tpl', content);
});
};
exports.getContent = function(callback) {
var ep = new EventProxy();
ep.all('tpl', function(tpl, data) {
// 成功回调
callback(null, {
template: tpl,
data: data
});
});
//绑定错误处理函数
ep.fail(callback);
fs.readFile('template.tpl', 'utf-8', ep.done('tpl'));
};

Promise/Deferred(应用参见:es6 Promise)

  • 先执行异步调用,后传递处理方法
  • then()方法只接受function对象,继续返回promise()对象,可选progress事件传入
  • Promise是高级接口,依靠低级接口事件来实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//then
var Promise = function() {
EventEmitter.call(this);
};
util.inherits(Promise, EventEmitter);
Promise.prototype.then = function(fulfilledHandler, errorHandler, progressHandler) {
if (typeof fulfilledHandler === 'function') {
// 用once()方法 保证成功回调只执行一次
this.once('success', fulfilledHandler);
}
if (typeof errorHandler === 'function') {
// 用once()方法 保证成功回调只执行一次
this.once('error', errorHandler);
}
if (typeof progressHandler === 'function') {
this.on('progress', progressHandler);
}
return this;
};
//实现这些功能的对象被称为Deferred,即延迟对象
var Deferred = function() {
this.state = 'unfulfilled';
this.promise = new Promise();
};
Deferred.prototype.resolve = function(obj) {
this.state = 'fulfilled';
this.promise.emit('success', obj);
};
Deferred.prototype.reject = function(err) {
this.state = 'failed';
this.promise.emit('error', err);
};
Deferred.prototype.progress = function(data) {
this.promise.emit('progress', data);
};

对res改造成promise

Deferred用于内部,维护异步模型的状态,Promise作用于外部,通过then()暴露给外部以添加自定义逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var promisify = function(res) {
var deferred = new Deferred();
var result = '';
res.on('data', function(chunk) {
result += chunk;
deferred.progress(chunk);
});
res.on('end', function() {
deferred.resolve(result);
});
res.on('error', function(err) {
deferred.reject(err);
});
return deferred.promise;//更改内部状态的行为由定义者处理
};
//then调用的是promise
promisify(res).then(function() {
// Done
}, function(err) {
// Error
}, function(chunk) {
// progress
console.log('BODY: ' + chunk);
});

Promise多异步协作,all()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Deferred.prototype.all = function(promises) {
var count = promises.length;
var that = this;
var results = [];
promises.forEach(function(promise, i) {
promise.then(function(data) {
count--;
results[i] = data;
if (count === 0) {
that.resolve(results);
}
}, function(err) {
that.reject(err);
});
});
return this.promise;
};
//all()返回resolve()结果集

Promise链式调用

  • 前一个的调用的结果,作为下一个调用的开始,后一个then的回调函数会等待前一个promise的状态变化而运行

将API Promise化

// smooth(fs.readFile);
var smooth = function(method) {
    return function() {
        var deferred = new Deferred();
        var args = Array.prototype.slice.call(arguments, 1); //跳过第一个参数
        args.push(deferred.callback());
        method.apply(null, args);
        return deferred.promise;
    };
};

1
bluebrid的 promisify可以将api promise化
var Promise = require('bluebird') fs.readFileAsync = Promise.promisify(fs.readFie, fs) var Promise = require('bluebird') Promise.promisifyAll(fs)
1
2
3
#### async
`串行执行`:series()
async.series([ function(callback) { fs.readFile('file1.txt', 'utf-8', callback); }, function(callback) { fs.readFile('file2.txt', 'utf-8', callback); } ], function(err, results) { // results => [file1.txt, file2.txt] });
1
2
3
传入的callback()不是由使用者指定,callback()执行时将结果保存然后执行下一个调用,最终的回调函数执行时,保存的结果以数组传入,一旦异常结束所有调用,将异常传递给最终函数的第一个参数
`并行执行`:parallel()
async.parallel([function(callback) { fs.readFile('file1.txt', 'utf-8', callback); }, function(callback) { fs.readFile('file2.txt', 'utf-8', callback); } ], function(err, results) { // results => [file1.txt, file2.txt] }); //等价于 var counter = 2; var results = []; var done = function(index, value) { results[index] = value; counter--; if (counter === 0) { callback(null, results); } }; // 只传递第一个异常 var hasErr = false; var fail = function(err) { if (!hasErr) { hasErr = true; callback(err); } }; fs.readFile('file1.txt', 'utf-8', function(err, content) { if (err) { return fail(err); } done(0, content); }); fs.readFile('file2.txt', 'utf-8', function(err, data) { if (err) { return fail(err); } done(1, data); });
1
2
3
一旦异步调用异常,,就将异常作为第一个参数传给最终回调函数,结果为数组
`异步调用 依赖`:当前一个的结果是后一个调用的输入 waterfall()
async.waterfall([function(callback) { fs.readFile('file1.txt', 'utf-8', function(err, content) { callback(err, content); }); }, function(arg1, callback) { // arg1 => file2.txt fs.readFile(arg1, 'utf-8', function(err, content) { callback(err, content); }); }, function(arg1, callback) { // arg1 => file3.txt fs.readFile(arg1, 'utf-8', function(err, content) { callback(err, content); }); } ], function(err, result) { // result => file4.txt });
1
2
`自动依赖处理` auto()
{ readConfig: function() {}, //读取配置 connectMongoDB: function() {},//连接mongo connectRedis: function() {},//连接redis complieAsserts: function() {},//编译静态 uploadAsserts: function() {},//上传静态到cdn startup: function() {}//启动 } var deps = { readConfig: function(callback) { // read config file callback(); }, connectMongoDB: ['readConfig', function(callback) { // connect to mongodb callback(); }], connectRedis: ['readConfig', function(callback) { // connect to redis callback(); 图灵社区会员 Eric Liu(guangqiang.dev @gmail.com) 专享 尊重版权 }], complieAsserts: function(callback) { // complie asserts callback(); }, uploadAsserts: ['complieAsserts', function(callback) { // upload to assert 2 callback(); }], startup: ['connectMongoDB', 'connectRedis', 'uploadAsserts', function(callback) { // startup }] }; //auto根据依赖关系自动分析 async.auto(deps);
1
2
3
#### Step
`串行`
Step( function readFile1() { fs.readFile('file1.txt', 'utf-8', this); }, function readFile2(err, content) { fs.readFile('file2.txt', 'utf-8', this); }, function done(err, content) { console.log(content); } );
1
2
3
4
thisStep内部的一个next()方法,将调用结果作为下一个任务的参数并调用
`并行` parallel()
Step( function readFile1() { fs.readFile('file1.txt', 'utf-8', this.parallel()); fs.readFile('file2.txt', 'utf-8', this.parallel()); }, function done(err, content1, content2) { // content1 => file1 // content2 => file2 console.log(arguments); });

如果异步方法传回结果为多个参数,step只取前两个。parallel()原理是每次执行时将内部计数器加1,

简介

  • 把它理解成一个状态机,封装了多个内部状态,执行Generator函数会返回一个遍历器对象,可以依次遍历Generator函数内部的每一个状态。
  • 定义时function与函数名之间有一个星号
  • 内部使用yield(产出)语句,定义不同的内部状态
  • 调用遍历器对象的next方法,使指针移向下一个状态

调用Generator函数后,该函数并不执行,而是返回一个指向内部状态的指针对象,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield语句(或return语句)为止。Generator函数是分段执行的,yield语句是暂停执行的标记,而next方法可以恢复执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true },执行完成done变成true
hw.next()
// { value: undefined, done: true }

yield语句

  • 遍历器对象的next遇到yield语句,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值
  • 下一次调用next方法时,再继续往下执行,直到遇到下一个yield语句
  • 如果没有再遇到新的yield语句,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值,若无则返回undefined
  • yield语句如果用在一个表达式之中,必须放在圆括号里面,作函数参数或赋值表达式的右边除外

yield与return区别在于每次遇到yield,函数暂停执行,下一次再从该位置继续向后执行,而return不具备位置记忆的功能。一个函数里面,只能执行一次(或者说一个)return语句,但是可以执行多次(或者说多个)yield语句,所以Generator函数可以返回一系列的值,即生成器之意

yield句本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield语句的返回值。第一次使用next方法时,不能带有参数,V8引擎直接忽略第一次使用next方法时的参数,只有从第二次使用next方法开始,参数才是有效的。从语义上讲,第一个next方法用来启动遍历器对象,所以不用带有参数。

1
2
3
4
5
6
7
8
9
10
11
12
function* f() {
for(var i=0; true; i++) {
var reset = yield i;
if(reset) { i = -1; }
}
}
var g = f();
g.next() // { value: 0, done: false }
g.next() // { value: 1, done: false }
g.next(true) // { value: 0, done: false }

通过next方法的参数,向Generator函数内部输入值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function* dataConsumer() {
console.log('Started');
console.log(`1. ${yield}`);
console.log(`2. ${yield}`);
return 'result';
}
let genObj = dataConsumer();
genObj.next();
// Started
genObj.next('a')
// 1. a
genObj.next('b')
// 2. b

与Iterator接口的关系

  • 执行Generator函数后返回Iterator对象

for…of循环、扩展运算符(…)、解构赋值和Array.from方法内部调用的,都是使用遍历器接口,即可以自动遍历Generator函数

1
2
3
4
5
6
7
8
9
10
11
扩展运算符(...)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。
let z = { a: 3, b: 4 };
let n = { ...z };
n // { a: 3, b: 4 }
扩展运算符可以用于合并两个对象。
let ab = { ...a, ...b };
// 等同于
let ab = Object.assign({}, a, b);

Generator.prototype.return()

  • 遍历器对象的return方法,可以返回给定的值,并且终结遍历Generator函数
1
2
3
4
5
6
7
8
9
10
11
function* gen() {
yield 1;
yield 2;
yield 3;
}
var g = gen();
g.next() // { value: 1, done: false }
g.return("foo") // { value: "foo", done: true },return不提供参数,则返回值的vaule属性为undefined
g.next() // { value: undefined, done: true }

yield*语句

  • 用来在一个Generator函数里面执行另一个Generator函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
function* foo() {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
yield* foo();
yield 'y';
}
// 等同于
function* bar() {
yield 'x';
yield 'a';
yield 'b';
yield 'y';
}
// 等同于
function* bar() {
yield 'x';
for (let v of foo()) {
yield v;
}
yield 'y';
}
for (let v of bar()){
console.log(v);
}
// "x"
// "a"
// "b"
// "y"

yield*语句等同于在Generator函数内部,部署一个for...of循环 ,如果yield*后面跟着一个数组,由于数组原生支持遍历器,因此就会遍历数组成员,还有字符串之类有Iterator接口的数据结构

1
2
3
4
5
function* gen(){
yield* ["a", "b", "c"];
}
gen().next() // { value:"a", done:false }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 下面是二叉树的构造函数,
// 三个参数分别是左树、当前节点和右树
function Tree(left, label, right) {
this.left = left;
this.label = label;
this.right = right;
}
// 下面是中序(inorder)遍历函数。
// 由于返回的是一个遍历器,所以要用generator函数。
// 函数体内采用递归算法,所以左树和右树要用yield*遍历
function* inorder(t) {
if (t) {
yield* inorder(t.left);
 yield t.label;
yield* inorder(t.right);
}
}
// 下面生成二叉树
function make(array) {
// 判断是否为叶节点
if (array.length == 1) return new Tree(null, array[0], null);
return new Tree(make(array[0]), array[1], make(array[2]));
}
let tree = make([[['a'], 'b', ['c']], 'd', [['e'], 'f', ['g']]]);
// 遍历二叉树
var result = [];
for (let node of inorder(tree)) {
result.push(node);
}
result
// ['a', 'b', 'c', 'd', 'e', 'f', 'g']

Generator的一些应用

异步转同步

可以把异步操作写在yield语句里面,等到调用next方法时再往后执行

1
2
3
4
5
6
7
8
9
10
11
12
function* main() {
var result = yield request("http://some.url");
var resp = JSON.parse(result);
console.log(resp.value);
}
function request(url) {
makeAjaxCall(url, function(response) {
it.next(response); //因为yield语句构成的表达式,本身是没有值的,必须加上response参数
});
}
var it = main();
it.next();

流程控制

多个任务需要并列执行时,可以采用数组的写法

1
2
3
4
5
6
7
function* parallelDownloads() {
let [text1,text2] = yield [
taskA(),
taskB()
];
console.log(text1, text2);
}

部署iterator接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function* iterEntries(obj) {
let keys = Object.keys(obj);
for (let i=0; i < keys.length; i++) {
let key = keys[i];
yield [key, obj[key]];
}
}
let myObj = { foo: 3, bar: 7 };
for (let [key, value] of iterEntries(myObj)) {
console.log(key, value);
}
// foo 3
// bar 7

让I/O与CPU计算并行

Node 在*nix平台,通过线程池实现(主线程和I/O线程),在windows下使用IOCP(调用异步方法,等待I/O完成后通知,执行回调,内部也依靠线程池,但由系统内核管理),通过libuv层来兼容。

Node本身是多线程的,但其中的JavaScript是单线程、因为v8的限制,但计算之类的都是在此线程,多线程只是I/O(磁盘,网络等),I/O有另外的线程池

事件循环

  • Node自身的执行模型,在libuv中

  • 在Node启动时,创建一个类似while(true)的循环,每循环一次成一个Tick,每次Tick查看是否有事件要处理,若有就处理事件和它的相关回调函数。在windows中基于IOCP,在*nix中基于多线程

  • 在Tick过程中通过观察者判断是否有事件要处理

  • 异步过程中最重要的就是请求对象,所有状态、传入参数、当前方法和回调函数都封装在此,javascript将对象组装好,送入I/O线程池后就结束了,I/O操作在线程池中等待请求对象被执行

  • Tick在执行过程中检查线程池中是否有执行完的请求,并加入I/O观察者队列中,然后再从观察者取到可用的请求对象当做事件处理,取出对象中的回调函数执行,若有业务层callback再给js执行

Smaller icon

事件驱动的高性能服务器

基于事件驱动的非阻塞I/O模型
通过主循环加载事件触发的方式来运行程序处理请求,无需为每一个请求创建额外的对应线程:操作系统因为线程少,所以在上下文切换时代价很小,有助系统稳定处理大量请求(但不适合密集运算),但用户代码不能并行执行,I/O可以

  • 单线程保证运行安全,避免重入

Smaller icon

另外一些异步api

定时器

  • setTimeout()单次定时执行 setInterval()多次定时执行

原理和异步I/O类似,将创建的定时器放到定时器观察者内部的红黑树,tick执行时,从红黑树中迭代取出定时器对象,检查是否超过定时时间,超过就形成事件并且立刻执行回调函数。

问题:定时不精确,如果某个循环占用时间过多,当再轮到定时器执行时就已经超时了

process.nextTick()

  • 若想立即异步执行一个任务,用这个更高效
1
2
3
4
5
6
7
8
9
10
11
12
13
setTimeout(function () { // TODO }, 0);// 比较浪费性能
process.nextTick=function(callback){
if(process._exiting) return;
if(tickDepth >=process.maxTickDepth)
maxTickWarn();
var tock={callback:callback};
if(process.domain) tock.domain=process.domain;
nextTickQueue.push(tock);
if(nextTickQueue.length){
process._needTickCallback();
}
}

调用process.nextTick(),只会将回调函数放入队列中,在下个Tick取出

setImmediate()

  • 类process.nextTick() 将回调函数延时执行,建议使用这个(v0.9.1以后)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
process.nextTick(function(){
console.log('nextTick延时执行1')
});
process.nextTick(function(){
console.log('nextTick延时执行2');
});
setImmdiate(function(){
console.log('setImmdiate延时执行1');
process.nextTick(function(){
console.log('强势插入');
})
});
setImmediate(function () {
console.log('setImmediate延时执行2');
});
console.log('正常执行');
//正常执行
//nextTick 延时执行1
//nextTick 延时执行2
//setImmediate 延时执行1
//强势插入
//setImmediate 延时执行2

process.nextTick()属于idle观察者,setImmediate()属于check观察者,在每轮循环中,idle观察者先于I/O观察者先与check

process.nextTick()的回调函数保存在数组中,在每轮循环中会将数组中的回调函数全部执行完。setImmediate()保存在链表中,在每轮循环中执行链表的一个回调函数

异步并发控制

并发量过大,下层服务器会吃不消

bagpipe
  1. 通过队列控制并发量
  2. 在当前活跃的异步调用量小于限定值,从队列中取出执行
  3. 活跃调用达到限定值后,调用暂存在队列中
  4. 每个调用结束时,从队列中取出新的异步调用执行

模块实现

依照CommonJS的模块规范,使用require()引入模块的api,使用exports导出模块的方法、变量;其中module对象代表模块本身,exports是它的属性

优点: 每个模块有独立的空间、不必考虑变量污染

Node引入模块通过路径分析、文件定位、编译执行,会对引入的模块缓存其编译和执行后的对象,二次加载时缓存优先核心模块之后文件模块。加载自定义模块(第三方包)最慢,要向上逐级递归查找node_modules目录

因为允许require()中不含文件拓展名,Node会按照.js、.node、.json的次序补足,依次尝试,并且需要调用fs模块同步阻塞判断文件是否存在,会因为Node单线程引起性能问题,对于.node、.json文件带上拓展名能快些

分析目录和包

require()通过分析文件拓展名后仍然没有查到文件、但取到一个目录,此时Node会将目录当做包来处理。

  1. 在当前目录下查找package.json(CommonJS规定的包描述文件)
  2. 通过JSON.parse()解析,取出main属性指定的文件名,若无拓展名就遍历拓展名

如果mian属性指定文件名错误、没有package.json文件,Node会把index.当做默认文件名,依次查找、遍历拓展名。

如果分析目录后没有定位成功,就进入下一个模块路径查找,模块路径都都遍历完了还没找到就抛错。

模块编译

.js 通过fs同步读取文件后编译执行 ,Node对获取的js文件内容进行了头尾包装

1
2
3
(function(exports,require,module,__filename,__dirname){//头尾包装
var math=require('math');
});

如此每个模块都有作作用域隔离,包装后的代码通过vm原生模块runInThisContext()执行,返回一个function对象,再将当前模块对象的exports属性、require()、module、文件的完整路径和文件目录作为参数传给function()。最后调用方拿到了exports属性

exports和module.exports的区别 exports对象是以形参形式传入的,直接赋值不会改变作用域外的值。所以需要对module.exports对象赋值,对exports属性赋值则无碍

1
2
3
4
5
6
7
var change = function(a) {
a = 100;
console.log(a); // => 100
};
var a = 10;
change(a);
console.log(a); // => 10

.node 这是用c/c++编写的拓展文件,通过dlopen()加载再编译 在windows、*nix有不同的实现,通过libuv兼容层封装,实际不用编译,它是c/c++编译生成的,只要加载、执行,exports对象直接和.node联系,执行效率高

.json 通过fs读取,使用JSON.parse()解析,再赋值给exports ,对于JSON文件直接用require()比fs高效

将编译后的模块,以文件路径为索引缓存在Module.cache对象上

核心模块

  • 将核心模块中纯用c/c++写的称为建内模块,因为通常不被用户调用
  • 部分模块通过c/c++编写核心部分,js实现包装、导出来提高性能

Node使用v8的js2c.py将所有内置js转成c++里的数组,生成node_natives.sh头文件,在Node启动时加入内存,然后再根据require()加载、执行;也要经历头尾包装,缓存在NativeModule._cache对象上

NPM

package.json 包描述文件 dependencies npm根据这个自动加载依赖的包,scripts 用于包管理器来安装、编译、测试和卸载包(钩子命令,preinstall之类的) devDependencies 开发时的依赖包,bin 命令行工具

全局安装 -g 将包变成全局可用的执行命令,不是require()到

其他方式安装包 npm install 指定package.json的位置,npm install [pg] –registry=http://registry.url 或者直接采用镜像源 npm config set registry http://registry.url

局域NPM

事务定义

一组原子性SQL查询,独立的工作单元

ACID(atomicity-原子性,consistency-一致性,isolation-隔离性,durability-持久性)

  • 事务不可分割
  • 从一个一致性状态转到另一个一致性状态
  • 事务修改提交之前对其他事务不可见
  • 一旦提交,永远保存

根据业务是否需要事务,选择合适的储存引擎,提升性能
若存储引擎不支持事务也可以使用locktable提供一定保护

隔离级别

  • 脏读(dirty read):事务读取了未提交的数据
  • 不可重复读(nonrepeatable read):事务T1读取一行记录,紧接着事务T2修改了T1刚才读取的那一行记录,然后T1又再次读取这行记录,发现与刚才读取的结果不同
  • 幻像读(phantom read):当某个事务在读某个范围内的记录时,另一个事务又在该范围内加入了新的记录,当之前的事务再读取该范围的记录时,会产生幻行。InnoDB和XtraDB存储引擎通过MVCC解决了幻像读的问题

READ UNCOMMITTED(未提交读): 事务的修改即使未提交,对其他事务也可见,性能略好,但有很多问题,不建议使用

READ COMMITTED(提交读): 事务所做的修改在提交前对其他事务不可见,大多数数据库默认隔离级别

REPEATABLE READ(可重复读): 同一事务多次读取的记录结果是一致的,Mysql默认级别

SERIALIZABLE(序列化) : 强制事务串行执行,会对读取的每一行数据加锁,会导致大量的超时和锁争用,用在非常需要确保数据的一致性并且可以接受没有并发的情况下

事务的隔离级别 脏读可能性 不可重复读可能性 幻像读可能性 备注
READ UNCOMMITTED未提交读 YES YES YES
READ COMMITTED提交读 No YES YES
REPEATABLE READ可重复读 No No YES Mysql默认级别
SERIALIZABLE序列化 No No No 会在读取的每一行数据上都加锁

死锁

多个事务在同一资源上互相占用,并请求锁定对方占用的资源,从而导致恶性循环

  • 多个事务以不同的顺序锁定资源
  • 多个事务同时锁定同一资源

解决方式:1. 检测到死锁时,立刻返回错误 2. 当查询的时间达到锁等待超时的设定后放弃锁请求

InnoDB处理方式: 将持有最少行级排他锁的事务进行回滚

发生死锁后,只有部分或者完全回滚其中一个事务,才能打破死锁。发生死锁无法避免,程序设计时需要考虑如何处理死锁,通常重新执行因死锁回滚的事务即可

事务日志

提高事务的效率

  1. 存储引擎在修改表的数据时只需要修改其内存拷贝,再把该修改行为记录到持久在硬盘上的事务日志中,而不用将修改的数据本身持久到磁盘。
  2. 事务日志采用的是追加的方式,写日志的操作是在磁盘上的一小块顺序I/O,不像随机I/O需要在多次移动磁头。

事务日志持久后,内存被修改的数据在后台慢慢刷回到磁盘。称为预写式日志(Write-Ahead Logging),修改数据需要写两次磁盘.

若数据的修改已经记录到日志并持久化,即使内存的修改没及时写到磁盘,也可以在存储引擎重启时自动恢复

MySQL事务

官方提供的事务存储引擎:InnoDB和NDB Cluster
第三方:XtraDB和PBXT

自动提交

mysql默认会启用自动提交,每个查询都会被当作一个事务执行提交操作,在当前连接中可以设置AUTOCOMMIT 1/ON或0/OFF。

导致大量数据改变的操作ALTER TABLE,还有LOCK TABLES等也会导致自动提交

事务中混合使用存储引擎

  • 在非事务型的表上,回滚操作无效,且不会报错,只会有警告信息

为每张表选择合适的存储引擎

隐式、显式锁定

  • InnoDB使用两段锁定协议,事务执行时,可随时执行锁定,只要到COMMIT或ROLLBACK时才会同时释放所有锁,InnoDB会根据隔离级别自动加锁(隐式)
  • SELECT … LOCK IN SHARE MODE和 SELECT … FOR UPDATE 显式锁定,但不建议使用

LOCK TABLES、UNLOCK TABLES服务器层实现,不能代替事务,且影响性能

MVCC多版本并发控制

  • 行级锁变种 能尽量避免加锁 开销低

此处介绍InnoDB对MVVC的实现

实现原理: 在每行记录后保存两个隐藏的列,一个保存行创建时间,一个保存过期(删除)时间,此处的时间指的是系统版本号,它会在创建事务时递增,

具体操作

  • SElECT
    • 只查找版本号小号或等于当前事务的版本号,确保读取的行,或是在事务开始之前,或是事务自己插入的
    • 行的删除版本要么未定义,要么大于当前事务版本号,确保事务读到行在事务开始之前未被删除
  • INSERT
    • 插入的每一行保存当前系统版本号作为行版本号
  • DELETE
    • 插入的每一行保存当前系统版本号作为行版本号
  • UPDATE
    • 为插入的新记录保存当前系统版本号作为行版本号,同时保存当前系统版本号到原来的行作为行删除标识