Sails.jsメモリの暴騰とソース分析
11360 ワード
Sails.jsはnodeの優れたMVCフレームですが、Sailsを使って流量が増えると、nodeプロセスが突然メモリが暴騰したり、高占有率を維持したりします.ソースコードを調べてみたら、この問題はセッション/GCと関係があります.
PS:メモリリークによるものであれば、コードを慎重にチェックし、変数を正常に回収することができます.
くりを一つあげる
新しいsailsアプリ:
Sailsは何をしましたか
ソース
sailsのソースコードの構造はかなりはっきりしています. 、 が登録されています.
セッション
ソースコードを読み終えて、具体的に
Sailsの
しかし、プロジェクトの多くは
√
√
Redis Store
√
√
Mysql Store
×
√
問題が来ました.もし
ごみの回収
しかし、v 8のゴミ回収トリガには閾値があり、各サブエリアにはデフォルトサイズが設定されています.直接にhep.ccで見られます.新生区:多くの対象がここに割り当てられています.新生区は小さい地域です.ゴミの回収はこの地域でとても頻繁で、他の地域と独立しています. 高齢者用のポインタエリア:ここには他のオブジェクトを指す可能性のあるほとんどのオブジェクトが含まれています.多くの新生区でしばらく生存した後の対象はここに移されます. 旧データエリア:ここには元のデータだけを含むオブジェクトが格納されている(これらのオブジェクトは他のオブジェクトを指すポインタがない).文字列、封箱の数字と、封されていない箱の二重精度の数字配列は、新生エリアでしばらく生存した後にここに移動されます. オブジェクトエリア:ここには他のエリアの大きさを超えるオブジェクトが格納されています.各オブジェクトには、自分のmmapから発生したメモリがあります.ゴミ回収器は大きなオブジェクトを移動しません. コードエリア:コードオブジェクト、つまりJIT以降のコマンドを含むオブジェクトは、ここに割り当てられます.これは実行権限を持つ唯一のメモリエリアです.ただし、コードオブジェクトが大きすぎて対象エリアに置かれている場合には、その大きいオブジェクトに対応するメモリも実行可能です. Cellエリア、属性Cellエリア、Mapエリア:これらの領域はCell、属性Cell、Mapを保存しています.各領域は同じサイズの要素を保存しているので、メモリ構造は簡単です. 新生代の高速gcに対しては、旧世代はMark-SweepとMark-Comppactを使用していますので、老生世代のメモリ回収はリアルタイムではなく、継続的なアクセス圧力の下で、老生世代の占有率は持続的に増加しています.また、ゴミメモリはすぐに回収されていません.
具体的なゴミ回収はここか中国語版に参加できます.
PS:メモリリークによるものであれば、コードを慎重にチェックし、変数を正常に回収することができます.
くりを一つあげる
新しいsailsアプリ:
# new sails app memory
> sails new memeory
> cd memory
config/bootstrap.js
を修正してメモリのスナップショットを増加し、xlsを書き込みます.var fs = require('fs');
// (see note below)
setInterval(function takeSnapshot() {
var mem = process.memoryUsage();
fs.appendFile('./memorysnapshot.xls', mem.rss / 1024 / 1024 + '\t'
+ mem.heapUsed / 1024 / 1024 + '\t' + mem.heapTotal / 1024 / 1024 + '
', 'utf8');
}, 1000); // Snapshot every second
pm 2を使ってsailsを起動します.> pm2 start app.js
> pm2 monit
圧力測定ツールを使用して、10 W要求、100同時# ab
> ab -n 100000 -c 100 http://127.0.0.1:1337/
メモリ占有率Concurrency Level: 100
Time taken for tests: 276.154 seconds
Complete requests: 100000
Failed requests: 0
Total transferred: 1094761464 bytes
HTML transferred: 1044700000 bytes
Requests per second: 362.12 [#/sec] (mean)
Time per request: 276.154 [ms] (mean)
Time per request: 2.762 [ms] (mean, across all concurrent requests)
Transfer rate: 3871.40 [Kbytes/sec] received
PM2 monitoring (To go further check out https://app.keymetrics.io)
app [ ] 0 %%%
[0] [fork_mode] [|||||||| ] 893.184 MB
セッションを閉じる# session
{
"hooks": {
...
"session": false,
...
}
}
#
Requests per second: 381.06 [#/sec] (mean)
# ,
PM2 monitoring (To go further check out https://app.keymetrics.io)
app [ ] 0 %%%
[0] [fork_mode] [|||||||||||||| ] 162.609 MB
その結果、不必要なサービスの閉鎖は、ホームページへのアクセスにどれほどの性能向上をもたらしていませんでしたが、メモリの占有率が非常に低くなりました.次にソースを調べてみます.Sailsは何をしていますか?Sailsは何をしましたか
ソース
sailsのソースコードの構造はかなりはっきりしています.
[email protected]
├── bin/ # sails command
├── errors/ #
└─┬ lib/
├─┬ app/
│ ├── configuration/ # ,
│ ├── private/ # , bind Sails
│ ├── ... # other module, all bind to Sails
│ ├── Sail.js # main entry
│ └── index.js
├─┬ hook/ # sails
│ ├── blueprints/
│ ├── controllers/
│ ├── cors/
│ ├── csrf/
│ ├── grunt/
│ ├─┬ http/
│ │ ├── middleware/ # express middleware
│ │ ├── public/ # favicon.ico
│ │ ├── start.js / # .listen(port)
│ │ ├── initialize.js # load express
│ │ └── ...
│ ├── i18n/
│ ├── logger/
│ ├── moduleloader/
│ ├── orm/
│ ├── policies/
│ ├── pubsub/
│ ├── request/
│ ├── responses/
│ ├── services/
│ ├── session/ # session
│ ├── userconfig/
│ ├── userhook/
│ ├── views/
│ └── index.js
└─┬ hook/ # router
├── bind.js # bind handler to router
├── req.js # sails.request object
├── res.js # Ensure that response object has a minimum set of reasonable defaults Used primarily as a test fixture.
├── ... # default handler config
└── index.js
起動app.js
から始まります....
sails = require('sails')
第1の文require
は、新しいSails() (sails/lib/Sails.js)
オブジェクトを作成する.Sails
が初期化されたとき、バラバラはモジュール/関数の山を束ね、events.EventEmitter
を継承し、ロード中にemit/on
を使用してローディング後の動作を実行した..lift
その後lift
が起動する(他の起動パラメータも最終的にlift
に呼び出される)....
sails.lift(rc('sails')); # rc .sailsrc
sails/lib/lift.js
は、Sailsに対してローディング開始を実行する....
async.series([
function(cb) {
sails.load(configOverride, cb);
},
sails.initialize
], function sailsReady(err, async_data){
... # sails
})
...
.load
方法はsails/lib/app/load.js
に位置し、最後にSailsを起動するまで順次読み込む....
async.auto({
config: [Configuration.load], # config
hooks: ['config', loadHooks], # hooks
registry: ['hooks', # hook middleware sails.middleware
function populateRegistry(cb) {
...
}
],
router: ['registry', sails.router.load] # express router
}, ready__(cb));
...
loadHooks
loadHooks
は、sails/lib/hooks/
の下にロードすべきすべてのモジュールをロードする....
async.series({
moduleloader: ...,
userconfig: ...,
userhooks: ...,
// other hooks
sails/lib/hooks/moduleloader/
は、他の各モジュールをロードする位置、方法を定義する.configure: function() {
sails.config.appPath = sails.config.appPath ? path.resolve(sails.config.appPath) : process.cwd()
// path of config/controllers/policies/...
...
},
// function of how to load other hooks
loadUserConfig/loadUserHooks/loadBlueprints
userhooks
の各hook
のローディングには時間制限があります.var timeoutInterval = (sails.config[hooks[id].configKey || id] && sails.config[hooks[id].configKey || id]._hookTimeout) || sails.config.hookTimeout || 20000;
他のモジュールをロードするときはasync.each
を使用するので、実際のローディングhooks
は順序があります.async.each(_.without(_.keys(hooks), 'userconfig', 'moduleloader', 'userhooks')...)
// hooks sails/lib/app/configuration/default-hooks.js
module.exports = {
'moduleloader': true,
'logger': true,
'request': true,
'orm': true,
...
}
注意silly
(プロジェクトuserhooks
ファイルの下にあるモジュールをロードするための)のロード順序は第二であるが、他のモジュールはロードされていない.この場合、api/hooks/
を設定する場合、属性名はsails[${name}]
の他のモジュール名と同じではないことに注意する.sails
は、各hooks/http/
のミドルウェアをプロジェクト構成config/http.js
に従ってロードし、デフォルトローディング:www: ..., // use 'serve-static' to cache .tmp/public
session: ..., // use express-session
favicon: ..., // favicon.ico
startRequestTimer: ..., // just set req._startTime = new Date()
cookieParser: ...,
compress: ..., // use `compression`
bodyParser: ..., // Default use `skipper`
handleBodyParserError: ...,
// Allow simulation of PUT and DELETE HTTP methods for user agents
methodOverride: (function() {...})(),
// By default, the express router middleware is installed towards the end.
router: app.router,
poweredBy: ...,
// 404 and 500 middleware should be after `router`, `www`, and `favicon`
404: function handleUnmatchedRequest(req, res, next) {...},
500: function handleError(err, req, res, next) {...}
express
:// sails/lib/hooks/http/initialize.js
...
sails.on('ready', startServer);
...
// sails/lib/hooks/http/start.js
// startSever express
...
var liftTimeout = sails.config.liftTimeout || 4000; //
sails.hooks.http.server.listen(sails.config.port...)
...
ready
すべての.initialize
が実行された後、実行を開始する.load
:// sails/lib/app/private/bootstrap.js
...
//
var timeoutMs = sails.config.bootstrapTimeout || 2000;
// run
...
// sails/lib/app/private/initialize.js
// afterBootstrap
...
// startServer
sails.emit('ready');
...
ロゴレベルをsails.config.bootstrap
に設定すると、起動時にsilly
のローディング情報が表示されます.# load hooks
verbose: logger hook loaded successfully.
verbose: request hook loaded successfully.
verbose: Loading the app's models and adapters...
verbose: Loading app models...
verbose: Loading app adapters...
verbose: responses hook loaded successfully.
verbose: controllers hook loaded successfully.
verbose: Loading policy modules from app...
verbose: Finished loading policy middleware logic.
verbose: policies hook loaded successfully.
verbose: services hook loaded successfully.
verbose: cors hook loaded successfully.
verbose: session hook loaded successfully.
verbose: http hook loaded successfully.
verbose: Starting ORM...
verbose: orm hook loaded successfully.
verbose: Built-in hooks are ready.
# register
verbose: Instantiating registry...
# router
verbose: Loading router...
silly: Binding route :: all /* (REQUEST HOOK: addMixins)
# ready
verbose: All hooks were loaded successfully.
#
以上がSails.jsの起動プロセスであり、最終的なhooks/router
の要求はhttp
によって処理される.セッション
ソースコードを読み終えて、具体的に
express
の部分を見にきて、session
とsails/lib/hooks/session/index.js
に位置します.Sailsの
sails/lib/hooks/http/middleware/defaults.js
は、デフォルトsession
のexpress-session
を使用していることがわかる.function MemoryStore() {
Store.call(this)
this.sessions = Object.create(null)
}
メモリがちゃんとしています.爆発しますか?しかし、プロジェクトの多くは
MemoryStore
をsessionとして記憶しており、store
を使用することはない.mysql/redis
memory
はexpress-session
を書き換え、条件によってexpress-session
とred.end (http.ServerResponse)
セッションがあるかどうかを判断し、.touch
の3つのsessionの中間部品は異なる実装がある:.save
memory/mysql/redis
メモリストア√
√
Redis Store
√
√
Mysql Store
×
√
問題が来ました.もし
.touch
が列に並んで閉塞したら、大量の.save
はメモリの中に存在します.流量が持続的に到来すると、store.save
プロセスによって占有されたメモリはガタガタと上にこすります.ごみの回収
req/res
とnode
は保持しているメモリだけが占有され、ゴミ回収処理された後、この部分のメモリは反落します.しかし、v 8のゴミ回収トリガには閾値があり、各サブエリアにはデフォルトサイズが設定されています.直接にhep.ccで見られます.
Heap::Heap()
: ...
// semispace_size_ should be a power of 2 and old_generation_size_ should
// be a multiple of Page::kPageSize.
reserved_semispace_size_(8 * (kPointerSize / 4) * MB),
max_semi_space_size_(8 * (kPointerSize / 4) * MB),
initial_semispace_size_(Page::kPageSize),
target_semispace_size_(Page::kPageSize),
max_old_generation_size_(700ul * (kPointerSize / 4) * MB),
initial_old_generation_size_(max_old_generation_size_ /
kInitalOldGenerationLimitFactor),
old_generation_size_configured_(false),
max_executable_size_(256ul * (kPointerSize / 4) * MB),
...
v 8のGCは「全休止」(stop-the-world)です.これらのいくつかの異なるヒープエリアに対して、異なるゴミ回収アルゴリズムを使います.具体的なゴミ回収はここか中国語版に参加できます.