どのように暗号化犬を解読します

6914 ワード

仕事の中で1つのデータベースのデータをCSVフォーマットにエクスポートする机能をして、开発环境の下ですべてとても正常で、それから満足してテスト环境に上がってテストを行って、结果はテストの学友がフィードバックして大量のデータをエクスポートする时Chromeは直接ネットの间违いを报告して、私はとても悩んで、自分で発行する时も大きいデータの量を使ってテストして、どうして问题がなくて、1つのIt works on my computerは口から出ます厳格な原則に基づいてやはり自分でテスト環境を走ってテストして、データ量が100万本ぐらいに達するとほとんど毎回エクスポートして間違いを報告することを発見しました.最初はネットワークがタイムアウトしたかどうかを考えていましたが、このダウンロードプロセスは実はずっとデータ転送があり、タイムアウト設定をトリガーすべきではありません.そうしないと、他のダウンロードファイルのプロセスもタイムアウトしなければならないので、別の方法でこの問題を調べる必要があります.手がかりを探してStack Overflowで検索します.主な言い方は以下の2つです.
ChromeのPredict network actions to improve page load performanceオプションウイルス対策ソフトは互換性がありません
2つの解決策は失敗し,一見正解/顔を覆うように見えないため,この導出インタフェースの応答ヘッダを偏門法で設定した.
Content-Disposition: inline;
インタフェースから転送されたcsvデータをそのままWebページとしてブラウザに表示しても、Chrome DevToolsを利用して詳細なエラーメッセージを見ることができます.時間のかかるダウンロードプロセスをシミュレートするために、Chrome DevToolsのNetworkパネルのThrottleオプションを開き、Slow 3 Gに設定することで、環境外ネットワークのネットワーク速度をシミュレートすることができます.4回テストし、7分程度で3回エラーを報告し、1回10分程度で停止した.Chromeのダウンロード結果は1つの失敗-ネットワークエラーしか表示されなかったが、コンソールを開くと手がかりが見つかり、毎回のエラーは同じだった.
net::ERR_INCOMPLETED_CHUNK_ENCODING 200
このエラーメッセージを借りてStack Overflowを検索すると、問題点が発見されたようです.
This is typically caused by a server not sending us the terminal 0-length chunk. We sit around waiting for more data with the request hung until the server closes the socket. At that point, we have no way to know whether we’ve received the entire file or not. This seems to be working as intended. The server needs to be fixed.
リンクBug Chromeを添付すると、サーバがエンドを表す最後の0-length chunkを送信していないことを意味します.応答に相当するデータは不完全で、これはエラーメッセージのINCOMPLETEDと一致します.原因を分析する手がかりができました.業務コードを見てみましょう.//resはExpressのResponseオブジェクトconst stream=Modelを指します.find({}).stream();
stream.on(“data”, data => {//do some stuff res.write(data); });
stream.on(“error”, err => {//process and log the error });
stream.on(“end”, () => { res.end(); }); ここでは関連概念を紹介する必要があります.まずstreamはmongoseの読み取り可能なストリームであり、resはExpressのResponseオブジェクトであり、streamは読み取り可能なストリームであり、resは書き込み可能なストリームです.通常、データの読み取り速度は書き込み速度よりも速いことが知られているため、読み取りと書き込み速度のバランスを維持するために、読み取り可能なストリームから読み出したデータはバッファに蓄積され、書き込み可能なストリームが空いている間に書き込み動作を継続します.expressストリーミングresponseオブジェクトはhttpプロトコルレベルでTransfer-Encodingのヘッダを使用します.
Transfer-Encoding: Chunked
この第1部について、httpプロトコルには以下の規定があります.
The terminating chunk is a regular chunk, with the exception that its length is zero.
最終的なブロック転送が完了した後、クライアントストリームデータの転送が完了したことを通知するゼロ長のブロックを転送する必要がある.以上のコードの問題は、読み書き速度のバランスを考慮することなく、データが読み出されるか否かのみを考慮したものであり、実際には可読ストリームから読み出されたデータが大量に蓄積する可能性があり、このとき可読ストリームstreamがendイベントをトリガすると、必ずしもresストリームにデータが完全に書き込まれているとは限らず、TCP Socketはバッファのデータをネットワークを介して送信した後、直ちにバッファのデータを削除することはない、実際にデータを削除するには、エンドツーエンドのACKメッセージが到着するのを待つ必要があります.ネットワークの状況が悪い場合、エンドツーエンドのACKメッセージは到着するのに長い時間がかかる可能性があります.このとき、バッファはすぐにいっぱいになり、再書き込みができません.テスト環境で外部ネットワークを使用する場合、ネットワーク状況が悪く、expressがTCP Socket書き込みバッファに書き込むデータはブロックされます.このときres.end()メソッドを呼び出すと,実際に書き込まれたデータが不完全になり,最後のゼロ長ブロックも書き込まれず,クライアントは自然にデータ受信が不完全であると判定し,エラーを報告したが,これも開発時にイントラネット環境でこのような状況が発生しなかった理由である.Backpressureの簡単な紹介ここでは、Backpressureという概念について説明します.
通常、データ処理時に一般的な問題に直面します.バックプレッシャーとは、データ転送中にキャッシュ後に大量のデータが蓄積されていることを意味します.データが最後に達するたびに複雑な演算に遭遇したり、何が原因で予想より遅くなったりして蓄積されると、ソースからのデータは膨大になり、プラグのように詰まってしまいます.
このような状況を処理するために合理的なメカニズムが必要ですjsはすでにこのクラスの問題の解決策を提供している.
書き込み可能なストリームのwriteメソッドを呼び出すと、このメソッドはバッファのデータの蓄積状況に基づいて戻り結果を決定し、書き込みを継続できればtrueを返し、一時的に書き込みを継続できない場合falseを返します(書き込みキューが忙しいか、読み込まれたデータブロックが大きすぎるため).このメソッドがfalseを返す場合、writeメソッドの呼び出しを続行するのではなく、drainイベントの発行を待ってから続行する必要があります.ストリームのpipeメソッドを用いて,読み書きバランスの制御をNodeに渡す.jsは自分で完成します.
しかし、実際にpipe法が用いられるのも第一案であり、Nodeにすぎない.jsはこれらの処理を手伝ってくれました.ReadableStreamのソースコードにはこのようなsrcがあります.on(‘data’, ondata); function ondata(chunk) { debug(‘ondata’); const ret = dest.write(chunk); debug(‘dest.write’, ret); if (ret === false) {//If the user unpiped during dest.write() , it is possible//to get stuck in a permanently paused state if that write//also returned false.//=> Check whether dest is still a piping destination. if (!cleanedUp) { if (state.pipes.length === 1 && state.pipes[0] === dest) { debug(‘false write response, pause’, 0); state.awaitDrainWriters = dest; state.multiAwaitDrain = false; } else if (state.pipes.length > 1 && state.pipes.includes(dest)) { debug(‘false write response, pause’, state.awaitDrainWriters.size); state.awaitDrainWriters.add(dest); } src.pause(); } if (!ondrain) {//When the dest drains, it reduces the awaitDrain counter//on the source. This would be more elegant with a .once()//handler in flow(), but adding and removing repeatedly is//too slow. ondrain = pipeOnDrain(src, dest); dest.on(‘drain’, ondrain); } } } srcストリーム、すなわちパイプのソースがdataイベントを開始すると、ondataコールバックを実行しdestを呼び出す.writeメソッドは、ターゲットストリームであるパイプの他端にデータを書き込み、戻り値がfalse(cleanedupはfalseがまだイベントリスナーがsrcを傍受しているイベントを示す)であると判断し、このとき書き込まれたターゲットストリームが書き込みを継続できないことを示すため、データの書き込みプロセスを一時停止するとともに、drainイベントを待つ必要がある書き込み可能なストリームオブジェクトを保存し、その書き込み可能なストリームにdrainイベントリスナーを追加し、その具体的な実装は、function pipeOnDrain(src,dest){return function pipeOnDrainFunctionResult(){const state=src._readableState;
// `ondrain` will call directly,
// `this` maybe not a reference to dest,
// so we use the real dest here.
if (state.awaitDrainWriters === dest) {
  debug('pipeOnDrain', 1);
  state.awaitDrainWriters = null;
} else if (state.multiAwaitDrain) {
  debug('pipeOnDrain', state.awaitDrainWriters.size);
  state.awaitDrainWriters.delete(dest);
}

if ((!state.awaitDrainWriters || state.awaitDrainWriters.size === 0) &&
  EE.listenerCount(src, 'data')) {
  state.flowing = true;
  flow(src);
}

}; } drainイベントをリスニングした後、ソース、すなわち読み取り可能なストリームのフロー状態を復元し、データの読み取りを継続します.ここで、1つの読み取り可能なストリームが複数の書き込み可能なストリームに書き込まれている場合、すべての書き込み可能なストリームがdrainイベントを監視してからフローを復元する必要があることに注意してください.実際の話し方と私自身のこの仕事の内容を結びつけて、以上の2つの解決策について、私自身の見方を話します.まず第2に、通常の作業でストリームを使用してCSVをエクスポートする方法は、通常、MongoDBの読み取り可能なストリームを生成し、dataイベントのコールバックでデータ構造の変換と処理を行い、resストリームに書き込む.pipeメソッドを使用すると、Transform変換ストリームを自分で書く必要があり、const{Transform}=require(‘stream’);
class MyTransform extends Transform { … }
//export.js const stream = Model.find({}).stream(); const transform = new MyTransform();
stream.pipe(transform).pipe(res);
//エラーが発生した場合、パイプを切断します.on(‘error’, err => { … });
transform.on(‘error’, err => { … }); この解決策は、最初に調べたデータに基づいて他のテーブルやライブラリに行って他のデータを調べる必要がある場合が多いため、データの処理時に非同期要求を開始する必要があり、Transformストリームで非同期操作を行う必要がある場合は、異なるTransformストリームのサブクラスを自分で書く必要があり、非常に煩わしいため、通常、他のデータを非同期で調べる必要がない場合に使用される.では、他のデータベースやテーブルを非同期で照会する必要がある場合はどうすればいいのでしょうか.通常、第1の解決方法で、比較的柔軟で、具体的な書き方は以下の通りです.const stream=Model.find({}).stream();
stream.on(‘data’, data => { asyncOperation(data).then(result => { const finalData = doSomething(result); let writable = res.write(finalData); if (!writable) { stream.pause(); } }); });
//バッファが空いている場合、ストリームオブジェクトはdrainイベントを開始し、res.on(‘drain’,()=>{stream.resume();})を書き込み続けることができることを表します.