English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
このプロジェクトに不慣れであれば、まず以前に書いた一連の記事を読むことをお勧めします。これらを読みたいと思わない場合は心配しないでください。ここでもその内容が含まれます。
では、始めましょう。
昨年、Nexus.jsの実装を始めました。これはWebkitに基づいています/JavaScriptの多スレッドサービスエンジンであるJavaScriptの核。ある時期、この作業を放棄しましたが、制御できない理由がいくつかありました。主に:長時間働くことができませんでした。
したがって、Nexusのアーキテクチャやそれがどのように動作するかについて議論することから始めましょう。
イベントループ
イベントループはありません
(無锁)タスクオブジェクトを持つスレッドプールがあります
setTimeoutやsetImmediateの呼び出しやPromiseの作成のたびに、タスクがタスクキューに並びます。
計画タスクのたびに、最初に利用可能なスレッドがタスクを選択し実行します。
CPUコア上でPromiseを処理します。Promise.all()の呼び出しは並行でPromiseを解決します。
ES6
サポートしていますasync/await、そして推奨されています
サポートしていますfor await(...)
サポートしています解构
サポートしていますasync try/catch/finally
モジュール
CommonJSをサポートしていません。(require(...)およびmodule.exports)
すべてのモジュールはES6のimport/export文法
サポートしています動的インポートをimport('file-または-packge').then(...)
サポートしていますimport.meta、例えば:import.meta.filenameおよびimport.meta.dirnameなど
追加機能:直接URLからインポートをサポートしています、例えば:
import { h } from 'https://unpkg.com/preact/dist/preact.esm.js';
EventEmitter
NexusはPromiseに基づくEventEmitterクラスを実装しています
イベントハンドラはすべてのスレッド上でソートされ、並行処理を実行します。
EventEmitter.emit(...)の返り値はPromiseであり、イベントハンドラで返された値が構成される配列として解決できます。
例えば:
class EmitterTest extends Nexus.EventEmitter { constructor() { super(); for(let i = 0; i < 4; i++) this.on('test', value => { console.log(`fired test ${i}!`); console.inspect(value); }); for(let i = 0; i < 4; i++) this.on('returns-a-value', v => `${v + i}`); } } const test = new EmitterTest(); async function start() { await test.emit('test', { payload: 'test 1}); console.log('first test done!'); await test.emit('test', { payload: 'test 2}); console.log('second test done!'); const values = await test.emit('returns-a-value', 10); console.log('third test done, returned values are:'); console.inspect(values); } start().catch(console.error);
I/O
すべての入力/出力は3つの原語(Device、Filter、Stream)を通じて完了されます。
すべての入力/出力原語はEventEmitterクラスを実装しています
Deviceを使用するには、Deviceの上にReadableStreamやWritableStreamを作成する必要があります
データを操作するには、FiltersをReadableStreamやWritableStreamに追加します。
最後にsource.pipe(...destinationStreams)を使用して、source.resume()を待ちデータを処理します。
すべての入力/出力操作はArrayBufferオブジェクトを使用して完了されます。
Filterを使用してprocess(buffer)メソッドでデータを処理しました。
例えば:2個の独立した出力ファイルがUTFに変換されます-8をUTFに変換します6。
const startTime = Date.now(); try { const device = new Nexus.IO.FilePushDevice('enwik8');}} const stream = new Nexus.IO.ReadableStream(device); stream.pushFilter(new Nexus.IO.EncodingConversionFilter("UTF-8", "UTF-16LE")); const wstreams = [0,1,2,3] .map(i => new Nexus.IO.WritableStream(new Nexus.IO.FileSinkDevice('enwik16-' + i))); console.log('パイプ...'); stream.pipe(...wstreams); console.log('ストリーミング...'); await stream.resume(); await stream.close(); await Promise.all(wstreams.map(stream => stream.close())); console.log(`終了まで ${(Date.now() * startTime) / 1000}秒!`); } catch (e) { console.error('エラーが発生しました: ', e); } } start().catch(console.error);
TCP/UDP
Nexus.jsはAcceptorクラスを提供しており、IPアドレスのバインドを担当します。/ポートとリスニング接続
接続リクエストを受け取ると、connectionイベントがトリガーされ、Socketデバイスが提供されます。
各Socketインスタンスは全双方向のI/Oデバイス。
ReadableStreamとWritableStreamを使用してSocketを操作できます。
最も基本的な例:(クライアントに「Hello World」を送信)
const acceptor = new Nexus.Net.TCP.Acceptor(); let count = 0; acceptor.on('connection', (socket, endpoint) => { const connId = count;++ console.log(`接続番号 #${connId} から ${endpoint.address}:${endpoint.port}`); const rstream = new Nexus.IO.ReadableStream(socket); const wstream = new Nexus.IO.WritableStream(socket); const buffer = new Uint8Array(13); const message = 'Hello World!\n'; for(let i = 0; i < 13; i++) buffer[i] = message.charCodeAt(i); rstream.pushFilter(new Nexus.IO.UTF8StringFilter()); rstream.on('data', buffer => console.log(`got message: ${buffer}`)); rstream.resume().catch(e => console.log(`client #${connId} at ${endpoint.address}:${endpoint.port} disconnected!`)); console.log(`sending greeting to #${connId}!`); wstream.write(buffer); }); acceptor.bind('127.0.0.1', 10000); acceptor.listen(); console.log('server ready');
Http
NexusはNexus.Net.HTTP.Serverクラスを提供しており、このクラスは基本的にTCPAcceptorを継承しています。
いくつかの基本的なインターフェース
サーバー側が受入接続の基本的なHttpヘッダーの解析を完了したときに、/確認時には、接続と同じ情報でconnectionイベントをトリガーします。
各接続インスタンスにはrequestとresponseオブジェクトがあります。これらは入力/出力デバイス。
ReadableStreamとWritableStreamを構築してrequestを操作できます。/response。
パイプでResponseオブジェクトに接続すると、入力のストリームはブロックエンコードモードで使用されます。それ以外の場合、response.write()を使用して通常の文字列を書き込むことができます。
複雑な例:(基本的なHttpサーバーとブロックエンコーディング、詳細は省略)
.... /** * Creates an input stream from a path. * @param path * @returns {Promise<ReadableStream>} */ async function createInputStream(path) { if (path.startsWith('/)) // If it starts with '/', omit it. path = path.substr(1); if (path.startsWith('.')) // If it starts with '.', reject it. throw new NotFoundError(path); if (path === '/' || !path) // If it's empty, set to index.html. path = 'index.html'; /** * `import.meta.dirname` and `import.meta.filename` replace the old CommonJS `__dirname` and `__filename`. */ const filePath = Nexus.FileSystem.join(import.meta.dirname, 'server_root', path); try { // Stat the target path. const {type} = await Nexus.FileSystem.stat(filePath); if (type === Nexus.FileSystem.FileType.Directory) // If it's a directory, return its 'index.html' return createInputStream(Nexus.FileSystem.join(filePath, 'index.html')); else if (type === Nexus.FileSystem.FileType.Unknown || type === Nexus.FileSystem.FileType.NotFound) // 見つからない場合は、NotFoundをスローします。 throw new NotFoundError(path); } catch(e) { if (e.code) throw e; throw new NotFoundError(path); } try { // まず、デバイスを作成します。 const fileDevice = new Nexus.IO.FilePushDevice(filePath); // それでは、ソースデバイスを使用して作成された新しいReadableStreamを返します。 return new Nexus.IO.ReadableStream(fileDevice); } catch(e) { throw new InternalServerError(e.message); } } /** * 接続カウンタ。 */ let connections = 0; /** * 新しいHTTPサーバーを作成します。 * @type {Nexus.Net.HTTP.Server} */ const server = new Nexus.Net.HTTP.Server(); // サーバーエラーは、サーバーが接続を監視している間にエラーが発生したことを意味します。 // このようなエラーはほとんど無視できますが、それでも表示します。 server.on('error', e => { console.error(FgRed + 明るい + 'Server Error: ' + e.message + '\n' + e.stack, Reset); }); /** * 接続を監視します。 */ server.on('connection', async (connection, peer) => { // 接続IDは0から始まり、新しい接続ごとにインクリメントします。 const connId = connections++ // この接続の開始時刻を記録します。 const startTime = Date.now(); // 構造化はサポートされていますが、なぜそれを使用しないのでしょうか?63 const { request, response } = connection; // URLの部分を解析します。 const { path } = parseURL(request.url); // ここでは、接続中に発生するエラーを保存します。 const errors = []; // inStreamはReadableStreamファイルソースであり、outStreamはWritableStreamにラップされたリスポンス(デバイス)です。 let inStream, outStream; try { // リクエストをログ出力します。 console.log(`> #${FgCyan + connId + リセット} ${Bright + peer.address}:${peer.port + リセット} ${ FgGreen + request.method + 「${FgYellow}${path}${Reset}"`, Reset); // 「Server」ヘッダーを設定します。 response.set('Server', `nexus.js`)/0.1.1 // 入力ストリームを作成します。 inStream = await createInputStream(path); // 出力ストリームを作成します。 outStream = new Nexus.IO.WritableStream(response); // すべての `error` イベントをハックし、エラーを `errors` 配列に追加します。 inStream.on('error', e => { errors.push(e); }); request.on('error', e => { errors.push(e); }); response.on('error', e => { errors.push(e); }); outStream.on('error', e => { errors.push(e); }); // 内容类型とリクエストステータスを設定します。 response .set('Content-Type', mimeType(path)) .status(200); // Hook input to output(s). const disconnect = inStream.pipe(outStream); try { // Resume our file stream, this causes the stream to switch to HTTP chunked encoding. // This will return a promise that will only resolve after the last byte (HTTP chunk) is written. await inStream.resume(); } catch (e) { // Capture any errors that happen during the streaming. errors.push(e); } // Disconnect all the callbacks created by `.pipe()`. return disconnect(); } catch(e) { // If an error occurred, push it to the array. errors.push(e); // Set the content type, status, and write a basic message. response .set('Content-Type', 'text/plain') .status(e.code || 500) .send(e.message || 'An error has occurred.'); } finally { // Close the streams manually. This is important because we may run out of file handles otherwise. if (inStream) await inStream.close(); if (outStream) await outStream.close(); // Close the connection, has no real effect with keep-alive connections. await connection.close(); // レスポンスのステータスを取得。 let status = response.status(); // ターミナルに出力する色を決定。 const statusColors = { '200': 明るい + FgGreen, // 緑色は 200 (OK), '404': 明るい + FgYellow, // 黄色は 404 (Not Found) '500': 明るい + FgRed // 赤色は 500 (内部サーバーエラー) }; let statusColor = statusColors[status]; if (statusColor) status = statusColor + status + リセット; // 接続(および完了までの時間)をコンソールにログ。 console.log(`< #${FgCyan + connId + リセット} ${Bright + peer.address}:${peer.port + リセット} ${ FgGreen + request.method + リセット} "${FgYellow}${path}${リセット}" ${status} ${(Date.now() * startTime)}ms` + (errors.length ? " " + FgRed + 明るい + errors.map(error => error.message).join(', ') + リセット : リセット)); } }); /** * リスンするIPとポート。 */ const ip = '0.0.0.0', port = 3000; /** * 「reuse」フラグを設定するかどうか。 (オプション、デフォルト=false) */ const portReuse = true; /** * 許可される最大の同時接続数。デフォルトは 128 私のシステム上で。 (オプション、システム固有) * @type {number} */ const maxConcurrentConnections = 1000; /** * 選択されたアドレスとポートをバインドします。 */ server.bind(ip, port, portReuse); /** * リクエストをリスニングし始めます。 */ server.listen(maxConcurrentConnections); /** * ストリーミングを楽しんでください! */ console.log(FgGreen + `Nexus.js HTTPサーバーが${ip}:${port}でリスニングしています` + リセット);
ベンチマーク
ここまでに実現したすべてをカバーしたと思います。それでは、現在の性能について話しましょう。
ここに現在のHttpサーバーのベンチマークがあります、以下の数:100個の並行接続と合計10000個のリクエスト:
これはApacheBench、バージョン 2.3 <$Revision: 1796539 $> 著作権 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ The Apache Software Foundationにライセンスされています, http://www.apache.org/ localhostをベンチマーク中 (少し待ってください)...完了 サーバーのソフトウェア: nexus.js/0.1.1 サーバーのホスト名: localhost サーバーのポート: 3000 ドキュメントのパス: / ドキュメントの長さ: 8673 バイト 並行度: 100 テストに要した時間: 9.991 秒 完全なリクエスト: 10000 失敗したリクエスト: 0 転送された合計: 87880000バイト 転送されたHTML: 86730000バイト 秒ごとのリクエスト数: 1000.94 [#/sec] (平均) リクエストごとの時間: 99.906 [ms] (平均) リクエストごとの時間: 0.999 [ms] (mean, across all concurrent requests) Transfer rate: 8590.14 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.1 0 1 Processing: 6 99 36.6 84 464 Waiting: 5 99 36.4 84 463 Total: 6 100 36.6 84 464 一定の時間以内にサービスされたリクエストの割合(ms) 50% 84 66% 97 75% 105 80% 112 90% 134 95% 188 98% 233 99% 238 100% 464 (最長のリクエスト)
毎秒1000のリクエストが実行されました。7上、上でこのベンチマークソフトウェアを含む、5GメモリのIDE、およびサーバー自体。
voodooattack@voodooattack:~$ cat /proc/cpuinfo processor : 0 vendor_id : GenuineIntel cpu family : 6 model : 60 model name : Intel(R) Core(TM) i7-4770 CPU @ 3.40GHz stepping : 3 microcode : 0x22 cpu MHz : 3392.093 cache size : 8192 KB physical id : 0 siblings : 8 core id : 0 cpu cores : 4 apicid : 0 initial apicid : 0 fpu : yes fpu_exception : yes cpuid level : 13 wp : yes flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf pni pclmulqdq dtes64 モニタ ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm cpuid_fault tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid xsaveopt dtherm ida arat pln pts バグ : bogomips : 6784.18 clflushサイズ : 64 キャッシュアライメント : 64 アドレスサイズ : 39 バイト 物理的, 48 バイト 虚擬 電力管理:
私は試してみました1000個の並行リクエストがありましたが、APacheBenchは多くのソケットがオープンされたためタイムアウトしました。httperfを試してみました、以下が結果です:
voodooattack@voodooattack:~$ httperf --port=3000 --num-conns=10000 --rate=1000 httperf --client=0/1 --server=localhost --port=3000 --uri=/ --rate=1000 --send-buffer=4096 --recv-buffer=16384 --num-conns=10000 --num-calls=1 httperf: 警告: ファイルオープン制限 > FD_SETSIZE; ファイルオープン数の最大値をFD_SETSIZEに制限 最大接続バースト長: 262 合計: 接続 9779 リクエスト 9779 返信 9779 テスト-期間 10.029 s 接続率: 975.1 conn/s (1.0 ミリ秒/conn, <=1022 同時接続 接続時間 [ms]: min 0.5 avg 337.9 max 7191.8 中央値 79.5 stddev 848.1 接続時間 [ms]: connect 207.3 コネクション 長さ [リプライ]/conn]: 1.000 リクエスト レート: 975.1 req/s (1.0 ミリ秒/req) リクエスト サイズ [B]: 62.0 リプライ レート [リプライ]/s]: min 903.5 avg 974.6 max 1045.7 stddev 100.5 (2 サンプル) リプライ タイム [ms]: レスポンス 129.5 転送 1.1 リプライ サイズ [B]: ヘッダー 89.0 コンテンツ 8660.0 フッター 2.0 (total 8751.0) リプライ ステータス: 1xx=0 2xx=9779 3xx=0 4xx=0 5xx=0 CPU time [s]: user 0.35 system 9.67 (user 3.5% system 96.4% total 99.9%) Net I/O: 8389.9 KB/s (68.7*10^6 bps) エラーズ: total 221 クライアント-timi 0 socket-timi 0 connrefused 0 connreset 0 エラーズ: fd-unavail 221 addrunavail 0 ftab-full 0 other 0
あなたが見たように、それもまだ動作しています。ただし、ストレスのために、一部の接続がタイムアウトすることがあります。私はこの問題の原因を研究しています。
これで Nexus.js に関する全ての学習内容が終わりました。質問があれば、以下にコメントしてください。ナイアラジン教材のサポートに感謝します。
声明:この記事の内容はインターネットから取得され、著作権者に帰属します。インターネットユーザーによって自発的に貢献し、自己でアップロードされました。本サイトは権利を所有しておらず、編集も行われていません。著作権侵害を疑う内容がある場合は、以下のメールアドレスまでご連絡ください:notice#oldtoolbag.com(メール送信時、#を@に変更して報告してください。関連する証拠を提供し、確認がついた場合は、本サイトは即座に侵害を疑われる内容を削除します。)