Sails 修改 http response 的实现

Sails 修改 http response 的实现

需求

需要在某些接口返回的 json 返回值内容,添加一些字段,比如之前接口返回值是:

{
"code": 0,
"data": {}
}

现在要添加一个字段,这样子:

{
"code": 0,
"data": {}
“newValue” :‘’
}

简单的办法是直接修改接口返回值的地方,但是需要每个接口都要修改一下,不通用也麻烦。

sails 提供了 policy 机制, 其实也就是 express 的 middleware ,提供切片式的操作,可以在这里修改上下文,一般用来做 log 工作。因为 js 闭包的特性,在 policy 里,是可以访问 req(request) 里的属性的,但是无法访问 res(response)里的属性。

既然不能直接访问属性,那就换个思路,在 policy 里替换 res.json 方法,加上我们自定义的内容。
这个 policy 的代码如下:

/**
* 查询新券数量
* @param req
* @param res
* @param next
*/

module.exports = function getNewTicketNums(req, res, next) {
var ud = req.param('ud');
var user_id = req.param('user_id');
user_id = user_id || ud;
if (!user_id) {
console.log('getNewTicketNums 没有 user_id');
return next()
}
// 保存之前的 json function
var end = res.json;
res.json = function () {
// 把当前 function 参数保存起来
var arg = arguments;
try {
console.log('modify response value', arg);
TreasureHuntService.getNewTicketNums(user_id, function (err, nums) {
// 修改了参数
arg[1].hasTicketNews = nums || 0;
// 继续执行之前定义的方法.
end.apply(res, arg);
});
} catch (err) {
end.apply(res, arg);
}
};
next();
};

主要是依赖 apply 来实现函数替换。
关于 apply 的作用可以看文档 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/apply

OK,写好这个 policy 之后,我们 sails 的 policies 文件里配置哪个结果需要这个修改功能就可以啦。

"getUserAsset": ["getNewTicketNums"],
"demandAssets": ["getNewTicketNums", "requestAuthentication"]

参考:
http://blog.csdn.net/puncha/article/details/9137001
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/apply

Node 性能优化

Node 性能优化

前言

没有 profile 谈优化都是耍流氓,性能优化的大前提是 profile ,有数据才能找出程序慢在哪里了。
本篇文章主要介绍 Node 后端的性能优化,前端的同学可以看看 Chrome 的 devtools https://github.com/CN-Chrome-DevTools/CN-Chrome-DevTools

一、Web 应用优化

性能的瓶颈往往在 IO

IO 层优化

磁盘 IO 为什么慢

计算机里的常见 IO 有 :

  • CPU 一二级缓存
  • 内存
  • 硬盘
  • 网络

硬盘的 IO 开销是非常昂贵的,硬盘 IO 花费的 CPU 时钟周期是内存的 41000000/250 = 164000 倍。

所有在一般应用中,优化要首先考虑数磁盘 IO , 通常也就是数据层的优化,说到数据库优化,很多人第一时间会想到加索引,但是什么加了索引查询会变快呢?索引要怎么加才合适呢?

为什么索引快

关于索引的原理可以看看这篇文章,索引原理。索引快主要的原因是:

  • 索引占用空间更小,可以有效减少磁盘 IO 次数。
  • 索引可以使用方便快速查询的数据结构,如b+树
索引怎么加

回到我们的主题,没有 profile 谈优化都是耍流氓
以 mongo 为例,mongo 是带有慢查询功能的。
MongoDB 查询优化分析 这篇文章介绍了如何开启和使用 mongo 的慢查询功能。
开启慢查询收集功能后,使用 db.system.profile.find().pretty() 语句可以查询到哪些语句的查询比较慢。以下面这个查询语句为例:

query new_koala.llbrandomredpackage query: { user_id: "56ddb33e23db696f89fdae2a", status: { $ne: 1 } }

查询条件是 user_id、status 两个,所以给这两个字段加上索引可以提高查询速度。
当然,如果 mongo 没有是先开启慢查询,扫描一下 mongo.log 也是个办法。

grep '[0-9][0-9][0-9]ms' /var/log/mongodb/mongodb.log

这样就可以找出所有查询耗时大于100 ms 的记录。然后再对症下药即可。

缓存大法好,有选择地用。

上文有说到,内存 IO 比磁盘 IO 快非常多,所以使用内存缓存数据是有效的优化方法。常用的工具如 redis、memcached 等。
缓存效果显著,所以很多时候一谈到优化,很多人就会想到加缓存,但是使用缓存是有代价的,你需要维护缓存的更新和失效,这是个繁琐的事情,用上了缓存后你会经常碰到缓存没有及时更新带来的问题。
重要的事情说多几遍:
缓存有副作用
缓存有副作用
缓存有副作用

并不是所有数据都需要缓存,访问频率高,生成代价比较高的才考虑是否缓存,也就是说影响你性能瓶颈的考虑去缓存。

而且缓存还有 缓存雪崩缓存穿透 等问题要解决。见 缓存穿透与缓存雪崩

静态文件缓存

静态文件如图片、js 文件等具有不变性,是非常适合做缓存的。
常见的静态文件缓存服务有 nginx、vanish 等。

代码层面优化。

合并查询

在代码这一块,常做的事情是将多次的查询合并为一次,消灭 for 循环,实际上还是减少数据库查询。例如

for user_id in userIds 
var account = user_account.findOne(user_id)

这类代码实际上可以改写成:

var user_account_map = {}   // 注意这个对象将会消耗大量内存。
user_account.find(user_id in user_ids).forEach(account){
user_account_map[account.user_id] = account
}
for user_id in userIds
var account = user_account_map[user_id]

这样就把 N 次的查询合并为一次。
实际上还是为了减少 IO。

关于过早优化

性能优化的工作做多了以后,往往会陷入一个什么都想着去优化的状态,这样就可能陷入过早优化的深坑中。
这里引用一下其他人的观点
https://www.zhihu.com/question/24282796

二、内存泄露排查

Node 是基于 V8 这个 js 引擎的,这里我们了解下 V8 里的内存相关的知识。

V8 的 GC 垃圾回收机制

V8 的内存分代

在 V8 中,主要将内存分为新生代和老生代两代。新生代的对象为存活时间比较短的对象,老生代中的对象为存活时间较长的或常驻内存的对象。

+—+—+—+—————————-+
| 新生代 | 。。。。。。老生代 |
+—+—+—+—————————-+

默认情况下,新生代的内存最大值在 64 位系统和 32 位系统上分别为 32 MB 和 16 MB。V8 对内存的最大值在 64 位系统和 32 位系统上分别为 1464 MB 和 732 MB。

为什么这样分两代呢?是为了最优的 GC 算法。新生代的 GC 算法 Scavenge 速度快,但是不合适大数据量;老生代针使用 Mark-Sweep(标记清除) & Mark-Compact(标记整理) 算法,合适大数据量,但是速度较慢。分别对新旧两代使用更适合他们的算法来优化 GC 速度。

详情参见《深入浅出 nodejs》5.1 V8 的垃圾回收机制与内存限制

V8 的 GC log

在启动程序的时候添加 –trace_gc 参数,V8 在进行垃圾回收的时候,会将垃圾回收的信息打印出来:

➜  $ node --trace_gc aa.js
...
[94036] 68 ms: Scavenge 8.4 (42.5) -> 8.2 (43.5) MB, 2.4 ms [allocation failure].
[94036] 74 ms: Scavenge 8.9 (43.5) -> 8.9 (46.5) MB, 5.1 ms [allocation failure].
[94036] Increasing marking speed to 3 due to high promotion rate
[94036] 85 ms: Scavenge 16.1 (46.5) -> 15.7 (47.5) MB, 3.8 ms (+ 5.0 ms in 106 steps since last GC) [allocation failure].
[94036] 95 ms: Scavenge 16.7 (47.5) -> 16.6 (54.5) MB, 7.2 ms (+ 1.3 ms in 14 steps since last GC) [allocation failure].
[94036] 111 ms: Mark-sweep 23.6 (54.5) -> 23.2 (54.5) MB, 6.2 ms (+ 15.3 ms in 222 steps since start of marking, biggest step 0.3 ms) [GC interrupt] [GC in old space requested].
...

V8 提供了很多程序启动选项:

启动项 含义
–max-stack-size 设置栈大小
–v8-options 打印 V8 相关命令
–trace-bailout 查找不能被优化的函数,重写
–trace-deopt 查找不能优化的函数

使用 memwatch 模块来检测内存泄露

npm模块 memwatch 是一个非常好的内存泄漏检查工具,让我们先将这个模块安装到我们的app中去,执行以下命令:

npm install --save memwatch

然后,在我们的代码中,添加:

var memwatch = require('memwatch');

然后监听 leak 事件

memwatch.on('leak', function(info) {
console.error('Memory leak detected: ', info);
});

这样当我们执行我们的测试代码,我们会看到下面的信息:

{
start: Fri Jan 02 2015 10:38:49 GMT+0000 (GMT),
end: Fri Jan 02 2015 10:38:50 GMT+0000 (GMT),
growth: 7620560,
reason: 'heap growth over 5 consecutive GCs (1s) - -2147483648 bytes/hr'
}
mem

memwatch 发现了内存泄漏!memwatch 判定内存泄漏事件发生的规则如下:

当你的堆内存在5个连续的垃圾回收周期内保持持续增长,那么一个内存泄漏事件被派发

了解更加详细的内容,查看 memwatch

使用 heapdump dump 出 Node 应用内存快照

检测到了内存泄露的时候,我们需要查看当时内存的状态,heapdump 可以抓下当时内存的快照。

memwatch.on('leak', function(info) {
console.error(info);
var file = '/tmp/myapp-' + process.pid + '-' + Date.now() + '.heapsnapshot';
heapdump.writeSnapshot(file, function(err){
if (err) console.error(err);
else console.error('Wrote snapshot: ' + file);
});
});

运行我们的代码,磁盘上会产生一些 .heapsnapshot 的文件到/tmp目录下。

使用 Chrome 的开发者工具分析内存消耗

heapdump 提供的内存快照是可以用 Chrome 的开发者工具来查看的。把 .heapsnapshot 文件导入到 Chrome Developer Tools

怎么使用内存分析工具呢?
Chrome开发者工具之JavaScript内存分析
这篇文件详细介绍了如何使用开发者工具来分析内存的使用情况。可以参考,这里就不细说了。

摘取个例子,使用对比视图。
对比视图 demo
这个例子展示了通过对比前后的内存变化来找出内存泄露的原因,看起来还是很简单方便的。

但是,理想很美好,现实很残酷。下面展示下日常开发中 dump 下的数据。
使用对比视图:


可以看出 array 是内存增长的主要元凶,但也只能得到这个线索,那具体是那些 array 消耗了内存呢?
点开 array 查看详细信息:


一大堆的匿名数组,无法准确查到具体那些 array 消耗了内存。
主要原因是后端使用了 sails 这个 web 框架,框架里的代码量比较多,干扰项太多,无法准确地判断是哪些 function 出现了问题。

内存泄露原因

通常,造成内存泄露的原因有如下几个。

  • 慎用内存当缓存,非用的话控制好缓存的大小和过期时间,防止出现永远无法释放的问题
  • 队列消费不及时,数组、回调,生产者的速度比消费者速度快,堆积了大量生产者导致无法释放作用域或变量
  • 作用域未释放,无法立即回收的内存有全局变量和闭包,尽量使用变量赋值为 null|undefined 来触发回收

这部分的详细解释请参考《深入浅出 nodejs》5.4 内存泄露。

三、优化应用 CPU 瓶颈

上面介绍了 IO 优化,内存优化,使用 Node 做后端的话还会经常碰到 CPU 瓶颈。总所周知,Node 是单线程的,所以对 CPU 密集的运算不是太胜任,所以应该避免使用 Node 来进行 CPU 密集的运算。
那么如果出现了 CPU 类的问题要怎么处理呢?

V8log:

加入 –prof 参数可以在应用结束是收集 log,执行命令之后,会在该目录下产生一个 *-v8.log 的日志文件,我们可以安装一个日志分析工具 tick

tick 工具分析 log

可以分析每个 function 的处理时间。

➜  $ sudo npm install tick -g
➜ $ node-tick-processor *-v8.log
[Top down (heavy) profile]:
Note: callees occupying less than 0.1% are not shown.

inclusive self name
ticks total ticks total
426 36.7% 0 0.0% Function: ~<anonymous> node.js:27:10
426 36.7% 0 0.0% LazyCompile: ~startup node.js:30:19
410 35.3% 0 0.0% LazyCompile: ~Module.runMain module.js:499:26
409 35.2% 0 0.0% LazyCompile: Module._load module.js:273:24
407 35.1% 0 0.0% LazyCompile: ~Module.load module.js:345:33
406 35.0% 0 0.0% LazyCompile: ~Module._extensions..js module.js:476:37
405 34.9% 0 0.0% LazyCompile: ~Module._compile module.js:378:37
...

前端的同学可以直接在 chrome 里收集 cpu profile 用于分析。

使用第三方平台

alinode,基于 Node 运行时的应用性能管理解决方案,笔者没有体验过,不预评价。

总结

文章主要介绍的还是后端开发中如何做性能优化的几种方式:

  • 添加索引
  • 接口缓存
  • 静态文件缓存
  • 合并查询
    这几种方法的目的其实都是为了减少 IO。看来 IO 过高是 Node 应用反应慢的主要原因。

此外,文章也介绍了如何排查处理内存泄露和 CPU 过高的问题。这两类问题是也是影响 Node 性能的一大原因。

参考:

《深入浅出 nodejs》朴灵著
MySQL索引原理及慢查询优化
MongoDB 查询优化分析
如何用redis/memcache做Mysql缓存层?
缓存穿透与缓存雪崩
http://www.barretlee.com/blog/2015/10/07/debug-nodejs-in-command-line/
http://www.w3ctech.com/topic/842
https://addyosmani.com/blog/taming-the-unicorn-easing-javascript-memory-profiling-in-devtools/
http://m.oschina.net/blog/270248
http://www.cnblogs.com/constantince/p/4607497.html
http://www.open-open.com/lib/view/open1421734578984.html#_label13

基于 waterline 的简单事务处理

基于 waterline 的简单事务处理

安装

$ npm install waterline-transact

配置

需要配合 Sails.js 使用

在 sails 的./api/model 添加一个 Model
Transaction.js

module.exports = {

  attributes: {
//事务名
name: {type: 'string'},
//保存事务处理的一些消息,例如事务处理失败原因。
msg: {type: 'string'},
//事务状态 start,pending,finish,canceling,rollback...
state: {type: 'string'},
start_time: {type: 'date'},
end_time: {type: 'date'},
//事务操作的文档
/**
* {
* coll 操作的表
* act 动作 insert or update
* docId 操作的对象的id
* data 操作对象的数据
* pre update操作时,对象在update之前的状态
* time 时间戳
* }
*/

changes: {type: 'array'}
}
};

这个 Model 用于保存事务的操作记录。

在 bootsstrap.js 里将 sails 对象传入

var Transact = require('waterline-transact');
Transact.init(sails);

使用

这个库在封装了 waterline 的 create 和 update 方法,需要事务操作时,使用本库提供的 txCreate 和 txUpdate 方法来替代。这样就会自动记录本次操作,方便回滚。

var Transact = require('waterline-transact');
Transact.transactionManager("正常事务", function (transactionId, taskCallback) {
/**
* @User Model
* @transactionId 事务ID
* @user_data 本次create操作保存的数据
*
*/

Transact.txCreate(User, transactionId, user_data, function (err, cUser) {
taskCallback(err, cUser);
});

}, function (err, result) {
//do some thing
})

测试

需要在 sails 的./api/model 添加一个 Model
User.js

module.exports = {
attributes: {
phone : { type: 'string', unique: true},
created_from: { type: 'string'},

}
};

将 test 目录下的 WaterLineTransct.test.js 放入 sails 的测试目录中,启动 sails 的测试即可。

Dependencies

License

The MIT License

Sails 关闭自动路由 Automatic Routes 功能。

Sails 关闭自动路由 Automatic Routes 功能。

Sails 中的路由两种:Custom Routes 和 Automatic Routes,自定义路由和自动路由。详见文档:
Sails Routes

自定义路由就是我们在 routes.js 中为指定的 url 分配处理的 Action 如:

'post /purchase/pay':{
controller:'PurchaseController',
action: 'pay'
}

自动路由则是,我们在 sails 中添加了 PurchaseController 并添加了 pay 方法后, 如:

module.exports = {
pay: function () {
doSomeThing()
}
}

可以直接使用 /purchase/pay 访问,Post 和 Get 方式均可

现在有个问题就是,我们希望只有 Post 请求才被处理,但是 Sails 的路由机制是,先在自定义路由 routes.js 中匹配,没有结果则在自动路由中匹配
所以我们使用 get /purchase/pay 方式请求的话,虽然该请求被自定义路由的 Post 过滤掉,但是请求还是是会被自动路由处理的。这算是 Sails 的缺陷(这应该算是个 BUG 吧,哈哈。),还不够智能。

要实现我们的目的,我们需要关闭自动路由功能。 sails 提供了此项配置,在 PurchaseController 添加以下配置:

module.exports = purchase = {

_config: {
actions: false, //关闭自动路由
shortcuts: false,
rest: false
},

buy: function () {
doSomeThing()
}

}

这样就行啦,关于该配置的详细信息见文档:sails-config-blueprint
还可以顺便关闭 Rest 接口功能。

Stackoverflow 上有人问了类似的问题,也可以参考下:
http://stackoverflow.com/questions/26921889/disabling-default-sails-js-routes

使用 Webstorm 的 N 个理由

使用 Webstorm 的 N 个理由

导航

文件跳转+搜索

方法跳转+引用

  • 跳转到变量定义的地方 ctrl+b
  • 查询引用变量的位置(雾, 不太准确, 勉强够用), 不太准确的原因是 js 是动态类型的语言, 动态类型判断引用关系会比较麻烦.
    古语有云:”动态类型一时爽,代码重构火葬场”,

  • 上/下一个光标位置 win + [ ]

  • 一个编辑位置 win+shitf+<–

文件结构 快速跳转方法

文件结构视图, 查看当前文件的所有元素.


好用的地方是支持搜索, 可以快速跳转到你要的方法.

书签

经常编辑的功能 就加个书签吧

编辑

重构

rename 神器

variable 将表达式抽出为变量(消灭重复表达式)

inline variable 的反操作, 适合这个变量只使用了一次的时候

parameter

自定义模板

live template


将常用的方法抽出为模板, 再也不用 Ctrl+CV 了.

format + validate

  • 自带 format 工具,也支持 eslint 等工具, 实时检查代码格式或 error
  • 找出未被使用的变量
  • 使用 F2 跳到错误位置

方便你快速编辑的

  • 选择块代码 alt+up 用鼠标小心翼翼地选择一小段代码真的很累的.
  • 复制行 win+d 写一个列表的时候常用
  • 删除行 + win 能少按几下是几下
  • vim 插件 vim 党

版本控制

change list

变更列表, 方便区分不同任务的变更, 避免提交暂时不需要提交的东西. 也方便 code review


建议配合 Task 功能使用.

change list 还有个不易发现的神级功能, Shelve Changes 搁置提交列表
如果你改动一堆文件, 然后你想切换分支, 但是 git 冲突不让你切换, 在之前你的选择只有两个

  • commit 当前的修改
  • 放弃
    Shelve Changes 提供第三个选择, 把这堆修改搁置, 这样 git 就不会冲突了.

对比视图

code review 神器
配合 change list 提交代码之前 review 一下本次修改的代码.

解决冲突

非常实用的功能, 简单的冲突还能手动解决, 复杂的冲突只能依赖这个工具了.


左侧的代码是你的, 右边的是 server 的, 中间是合并的结果.
首先接受不冲突的更新, 然后手动选择你需要的修改, << 表示接受, X表示放弃.

git 历史

在行号后面点击右键, 选择 Annotate 可以查看代码的最后修改人.
在当前文件点击右键, 选择 Git –> show history 则可以查看该文件的提交历史.

选中一个提交, 点击右键选择 Compare (或者按下 Ctrl + D ), 可以打开对比视图, 查看本地提交修改了哪些东西.

本地历史

一般用不上, 但是关键时刻能救你一命的功能.
详细记录你每一次修改, 如果你 git 上误操作, 代码被吃了, 在这里还能找回来.

RUN 程序运行

RUN

快速定位出错位置


方便搜索 log 不要再用 ####### 来定位 log 了. 你有 Ctrl+ F

Debug

Debug 的清晰度会比 log 高很多, 可以方便地查看上下文, 可以更快的找出问题.

Debug 整个工程的话, 速度非常慢, 不建议使用. 推荐用来 Debug 单文件执行.

TEST

配置 Mocha 运行环境 (注意: Webstorm 11 对 Mocha 的支持出现了问题)


清晰的视图,快速定位 log. 抛弃那种 ============ 的 log 方式吧.

集成

集成第三方的项目管理工具到 Task 功能

Trello

Jira

开发工具的选择

开发工具和编程语言一样, 没有最好的, 只有最合适的.
合适的工具只是为了提升开发效率而已.

跑题

  1. 推荐开发环境升级 sails 和 sails-mongo 到最新版.

npm install sails@latest
npm install sails@latest -g
npm install sails-mongo@latest
npm install sails-hook-autoreload

  1. 推荐使用马克飞象编辑器

新的写博客的方式

新的写博客的方式

之前博客都是写在博客园里的:我的博客。在博客园里写博客主要是懒,不想折腾。平时我自己使用马克飞象来编辑文章,排版轻松,方便同步到印象笔记,但是马克飞象里的文章放到博客园之后排版就混乱了,博客园对 markdown 的支持太差劲。而且文章也不好管理,笔记里一份,博客里一份,一下子没想到好的解决方案,就这样一直拖着了。

今年开始使用阿里云,还是挺方便的,突然想自己搭建博客,比较可定制性比较高,起码可以搞定 markdown 的问题。说做就做,搞定麻烦的备案后,这个博客就搭建好了。使用流行的解决方案 WordPress ,简单快速。

WordPress 有很多支持 markdown 的插件,试了几个,还是不满意,主要是对 markdown 格式的解释不一致,最终出来的文章还是跟我在马克飞象写的格式不一致。

除了格式不一致的问题,还有文章管理的问题,我现在更喜欢在 Evernote 里写东西,我需要把写好的东西一个一个搬到博客里,呃,好累。搜索一下,找到一个同步插件,可以把 Evernote 里特定标签的笔记同步到 WordPress 中,居然还是支持马克飞象的,哈哈。

现在写博客的流程是这样的。

  1. 在马克飞象编辑文章
  2. 自动同步文章到 Evernote
  3. 自动同步 Evernote 到 WordPress

所有笔记都在马克飞象管理,然后笔记就自动同步到 Evernote 和 WordPress 啦,非常轻松,而且修改久文章也会自动更新到两个地方。