split
Linuxにストリーミングバージョンがありますか?
SSHを介して大量のデータをバックアップしようとしていますが、SSHのシングルスレッド暗号化は転送を制限します。このマシンはハードウェアAESをサポートしていないため、ChaCha暗号化を使用しますが、CPUはまだネットワークに追いつくことができません。
だから私はデータストリームを2つの部分に分け、各ストリームを別々のSSH接続を介して送信し、ターゲットからストリームを一緒にマージすることでこの問題を解決できると思いました。これにより、暗号化負荷を複数のCPUコアで共有できます。これはすでに存在しなければならないという十分に一般的なアイデアのように見えますが、見つかりません。
編集する:一部の数字では、古いコンピュータのデータ、つまりギガビット有線ネットワークを介して数百ギガバイトのデータをバックアップしています。回転するラストドライブの単一ファイルアクセスよりも高速ですので、パーティションからイメージをコピーしています。したがって、技術的にはランダムアクセスデータですが、それを処理するには大きすぎます。圧縮してみましたがあまり役に立ちませんでした。データの圧縮率が低い。
だから私が探しているのは、split
バイナリmerge
データストリームを複数のストリームに分割する(おそらく固定チャンクに分割される)ことです。
答え1
このような:
parallel --block -1 --pipepart --recend '' -a bigfile 'ssh dst cat \>{#}'
完了すると、cat
ファイルが一緒に表示されます。
ssh dst cat $(seq $(parallel --number-of-threads)) '>' bigfile.bak
2倍大きいファイルのためのスペースが必要です。
答え2
実際には、GNUはparallel
検索可能ファイルをチャンクに直接読み込み、各ファイルを同時操作に提供できるため、この問題を解決するために必要なほとんどの定型句操作を実行できます。しかし、特に回転するハードドライブで作業するときは、parallel
ファイル全体を管理するのではなく、固定サイズのブロックで管理することが望ましいと思います。--block
受信機はジョブごとに処理されます。dd seek=
+copyコマンドで特定のチャンクを瞬時に移動させます。まっすぐターゲットファイルに。
すべてのタスクを実行する完全な機能を備えたスクリプトは次のとおりです。
#!/bin/sh --
# For improved performance, and even in presence of pubkey authentication,
# it is advisable to use ssh master mode, so that each job spawn by `parallel`
# does not undergo the entire authentication handshake. Otherwise a
# several-hundred-GBs file, even split in chunks of 100MB each, would
# force the whole operation to spend several minutes as a total just on ssh
# authentication handshakes.
trap 'for c in c?; do ssh -S "$c" -O exit .; done' EXIT
for c in $(seq 1 $(parallel --number-of-cores)); do
ssh -fnNMS "c$c" -o ControlPersist=yes "$@" || exit
done
# let's say 10 megabytes each chunk
csize="$((10*1024*1024))"
parallel --pipepart -a /dev/sr0 --recend '' --block "$csize" '
ssh -S c{%} localhost "{
dd bs='"$csize"' seek="$(({#} - 1))" count=1 \
iflag=fullblock conv=sparse,notrunc # && \
# head -c '"$csize"'
} 1<>test 2>/dev/null"
'
# Note this assumes GNU `dd` on the destination side, which supports
# the `iflag=fullblock` operation mode. Lacking that, one would use `dd`
# only as a seeking-capable command, hence employing a `count=0` option in
# place of the `count=1`, operating over the _same_ file-descriptor later used
# by a `head -c` command as in the commented-out line.
# Note also that `conv=sparse` option is not POSIX, though it is supported by
# both GNU and BSD `dd`. In case your `dd` does not support that option then
# just leave it out and `dd` will simply operate somewhat slower (YMMV).
一部のシェルは埋め込みコメントをサポートしていないため、コメントアウトされた行(単にコメントアウトされた行ではない)を実際のスクリプトから削除するのが最善です。
あなたはになります送信者次の機械:
$ sh script /dev/sda user@receiver-machine
アイデアは、ヘッドに負担をかけずにHDD速度を飽和させるのに十分な大きさのブロックサイズを使用し、同時にparallel
カーネル自体のブロックデバイスキャッシュを使用するのに十分な連続したディスクセクタの安定した読み取りを維持することです(/ dev/sdaX を直接読むと仮定します。私たちが置いておくとparallel
得られないのです。みんなserveral-hundred-GBs ファイル。同時操作が互いに離れている検索および読み取り操作を要求し、ディスクがヘッドを前後に動かしながらカーネルのブロックデバイスキャッシュを継続的に無効にする(したがって交換可能)。もちろん、私はあなたの古いコンピュータに何百GBのRAMがないと仮定します。
FWIWはこの状況でも一般的なコマンドを使用できsplit
、完全に安全であることに注意してください。パフォーマンスも悪いでしょう。いくつかのクイックテストを実行しましたが、split
結果はGNUを使用するソリューションだけでなく、時には比較のために作成する代替の「定型句」純粋なシェルスクリプトよりも実際に遅かったですparallel
。
とにかく、記録のためsplit
にこのような状況では、自然なコンパニオンツールは、でpaste
実行されるループ書き込みと一致する可能性がある「ループ」読み取りを本質的に実行するツールですsplit -n r/X
。
以下は、4分割ギアボックスの実際の例です。
#!/bin/bash --
sfile="${1:?}"; shift
# Side note: uncomment the following lines if you don't have ssh-keys to authenticate
# to the sender machine and thus you need to enter passwords. Note that this requires
# master mode enabled and allowed by the ssh client, and connection multiplexing enabled
# and allowed by the ssh server. Both are typically enabled by default in OpenSSH.
#for cnum in {1..4}; do
# ssh -fnNMS "c$cnum" -o ControlPersist=yes "$@" || exit
#done
#trap 'for cnum in {1..4}; do ssh -S "c$cnum" -O exit -; done' EXIT
LC_CTYPE=C paste -d \\n \
<(ssh -S c1 "$@" "LC_ALL=C split -n r/1/4 $sfile") \
<(ssh -S c2 "$@" "LC_ALL=C split -n r/2/4 $sfile") \
<(ssh -S c3 "$@" "LC_ALL=C split -n r/3/4 $sfile") \
<(ssh -S c4 "$@" "LC_ALL=C split -n r/4/4 $sfile")
スクリプトでできること受話器次の機械:
$ bash script /dev/sda user@sender-machine > file
その後、受信システムで生成されたサイズを変更する必要がある可能性が高くなりますfile
(興味がある場合は、なぜこれが必要なのかについての詳細な説明を提供できます)。次のように簡単に調整できます。
$ truncate -s <real-size> file
それはすべてです。
1つの(ほとんど理論的な)注意事項は注意する価値があるかもしれません。split -n r/..
本質的に、分割は改行文字によって行われるからです。入力データに改行文字がまったくない場合、分割はまったく行われず、全体のデータ量が単一パスを介して送信されます。例では、接続4です。
ファタイ
答え3
手動「分割」
(ローカル)ソースファイルと(リモート)ターゲットファイルを検索できる場合は、簡単な解決策は、オフセットとサイズを手動で計算し、それぞれファイルを転送する2つ以上のパイプラインを実行することです。はい(ソース):
dd if=/source/file bs=10M count=1000 skip=0 iflag=fullblock \
| ssh user@server 'dd of=/destination/file bs=10M seek=0 conv=notrunc'
これにより、最初の1000MiB(1000
x 10M
)のデータが送信されます。その後、2番目の1000MiBが使用され、送信さskip=1000
れます。seek=1000
そして.skip=2000
等seek=2000
。複数のコマンドを同時に実行できます。
実際には、コマンドの最初の部分を送信する必要はなくskip
(seek
両方0
のデフォルト値であるため)、コマンドの最後の部分を送信する必要もありませんcount
(両方ともdd
EOFで終了するため)。
だから走りたいなら窒素 ssh
両方とも処理して全体をカバーし、を選択して電卓を取得して、/source/file
それぞれbs
の共通値count
とそれぞれのskip
値を計算します。seek
窒素ほぼ同じ部品です。最後の部分は少し小さくても大きくてもかまいませんが、指定しないでくださいcount
。
メモ:
bs=10M
あなたに適していない可能性があります。必要に応じて変更して再計算してください。conv=notrunc
最後のブロックを除くすべてのブロックにとって重要です。使用すると、すぐに
seek=
希少性を拡張できます。/destination/file
これは断片化を引き起こす可能性があります。これを防ぐために、事前にfallocate
使用してください/destination/file
。一部の(古い、非* nix)ファイルシステムは実際にコンテンツを書かず、ファイル拡張をサポートしていないため、ゼロを書くのに時間がかかることがありますseek=
。fallocate
iflag=fullblock
持ち運びが簡単ではありません。通常旗が重要です。次から読むとき定期的な/source/file
あなたおそらくそうする必要はありません。しかし、利用可能な場合は使用してください。要点は、地域住民がそこに到着してすぐに読むのを止めることができcount
ない場合、部分的な読書も重要であるということです。iflag=fullblock
dd
count
dd skip=… … | head -c … | ssh …
別の解決策はnoと同じですcount=
。dd
検索できないストリームから読み取る場合、skip
noはiflag=fullblock
おそらく必要なコンテンツより少ないコンテンツをスキップします。しかし、通常のファイルから読み取るのがskip
ポインタを動かすだけであれば安全です。head -c
持ち運びが簡単ではありませんが。GNUはおよびを
dd
サポートすることで計算を大幅に簡素化できます。たとえば、次のコマンドは最初の200GiB、次の300GiB、残りをそれぞれ送信します。iflag=skip_bytes,count_bytes
oflag=seek_bytes
/source/file
dd if=/source/file bs=10M iflag=count_bytes count=200G | ssh user@server 'dd of=/destination/file bs=10M conv=notrunc' dd if=/source/file bs=10M iflag=skip_bytes,count_bytes skip=200G count=300G | ssh user@server 'dd of=/destination/file bs=10M conv=notrunc oflag=seek_bytes seek=200G' dd if=/source/file bs=10M iflag=skip_bytes skip=500G | ssh user@server 'dd of=/destination/file bs=10M oflag=seek_bytes seek=500G'
もちろん、問題を解決するには、これらのコマンドを並列に実行する必要があります(たとえば、別の端末で)。
iflag=fullblock
いいえ。ツールはブロックではなくバイトを計算するためです。小さくても少なくとも/destination/file
に大きくなることに注意して下さい。500G
/source/file
ダメージ
/source/file
この方法は順次読み書きすることはなく、/destination/file
あるオフセットから別のオフセットにジャンプし続けます。メカニカルドライブが関係している場合、この方法は正しく行われないか、ドライブに負担がかかる可能性があります。
代替方法
この方法を使用すると、検索可能または検索できないソースからのデータを検索可能または検索できない宛先に転送できます。データのサイズを事前に知る必要はありません。順次読み書きします。
送信者で実行されたときにすべてを設定し、受信者で正しいコードを実行するスクリプトを作成することができます。私は解決策を比較的単純に保つことにしました。したがって、送信者で実行するコードスニペットと受信者で実行するコードスニペット、および実行する必要がある手動パイピングがあります。
送信システムにはbash
、ifne
およびサポートがmbuffer
必要です。受信コンピュータには同じツールセットが必要です。一部の要件は軽減することができます。以下の注意事項を参照してください。head
-c
sender
実行可能にするために、トランスポートシステムに次のコードを保存します。
#!/bin/bash
declare -A fd
mkfifo "$@" || exit 1
for f do
exec {fd["$f"]}>"$f"
done
while :; do
for f do
head -c 5M | ifne -n false >&"${fd["$f"]}" || break 2
done
done
rm "$@"
receiver
実行可能になるように受信コンピュータに次のコードを保存します。
#!/bin/bash
declare -A fd
mkfifo "$@" || exit 1
for f do
exec {fd["$f"]}<"$f"
done
while :; do
for f do
<&"${fd["$f"]}" head -c 5M | ifne -n false || break 2
done
done
rm "$@"
スクリプトは非常に似ています。
sender
必要な数の引数を使用して転送システムで実行します。
</source/file ./sender /path/to/fifoS1 /path/to/fifoS2 /path/to/fifoS3
fifoS1
これでfifos、、fifoS2
が生成されますfifoS3
。 FIFO の使用量が少ないか、使用量が多いのはあなた次第です。 fifoへのパスは相対的にすることができます。sender
FIFOからデータを読み取るのを待ちます。
受信機で同じ数のパラメータを使用してコマンドを実行しますreceiver
。
>/destination/file ./receiver /location/of/fifoR1 /location/of/fifoR2 /location/of/fifoR3
fifoR1
これでfifos、、fifoR2
が生成されますfifoR3
。 fifoへのパスは相対的にすることができます。receiver
データがFIFOに書き込まれるのを待ちます。
送信コンピュータからmbuffer
リモート経由でssh
各ローカルfifoを対応するリモートfifoに接続しますmbuffer
。
</path/to/fifoS1 mbuffer -m 10M | ssh user@server 'mbuffer -q -m 10M >/location/of/fifoR1'
</path/to/fifoS2 mbuffer -m 10M | ssh user@server 'mbuffer -q -m 10M >/location/of/fifoR2'
# and so on
これらのコマンドを並列に実行します。すべてのローカルfifoをリモート相手に接続すると、データフローが開始されます。不明な場合:提供されたN番目のパラメータreceiver
は提供されたN番目のパラメータに対応しますsender
。
メモ:
sender
パイプから読み取ることができ、receiver
パイプに書き込むことができます。検索可能なソースを検索可能な宛先に転送するときは、
dd skip=… …
パイプsender
とパイプを接続して中断されreceiver
た転送を再開できますdd seek=… …
。この回答の前のセクションで説明したGNUの制限dd
と機能を念頭に置いてください。dd
-c
for は forhead
in scripts と同じです。このサイズのチャンクはラウンドロビン方式で FIFO に転送されます。変更する場合は、両方のスクリプトで同じ値を使用してください。-b
split
-c 5M
批判的。また、それに応じて-m
sを調整しますmbuffer
。mbuffer
sのサイズと対応するバッファはパフォーマンスにとって重要です。- 送信者のバッファが小さすぎると、送信者の速度が遅くなり、その
sender
一方がゆっくり暗号化されるのを待ちssh
、他のssh
バッファは転送を続行するためにアイドル状態で待機しますsender
。mbuffer
送信側では、それぞれ1つ以上のブロックサイズを使用する必要があると思います。例では、(-m 10M
)を2回使用しました。 - 受信側のバッファが小さすぎて次のパイプに移動する前にそのうちの1つを使い果たした場合、書き込んでいるバッファがいっぱいになるため、
receiver
他のssh
バッファが停止することがあります。各受信機に少なくとも1つのブロックサイズを使用する必要があるとmbuffer
思います。mbuffer
例では、(-m 10M
)を2回使用しました。
- 送信者のバッファが小さすぎると、送信者の速度が遅くなり、その
bash
fifoの数が事前にわからない場合は、ファイル記述子を処理する必要があります。sh
固定数のfifoを使用すると、スクリプトを純粋に移植するのが比較的簡単です。head
dd
サポートに置き換えることができますiflag=fullblock
。mbuffer
+パイプは終了後に自動的に終了する必要がssh
あります。これらのパイプを手動でシャットダウンする必要はありません。sender
receiver
ifne
EOFを検出するために使用されます。ifne
からすべてのファイルを削除できますが、sender
操作が完了したら手動でスクリプトを終了してfifoを削除する必要があります。ジョブがいつ完了するかを知るための良い方法は、ジョブの前に次のことを行うこと./sender
です(cat; echo "done" >&2)
。(</source/file cat; echo "done" >&2) | ./sender …
それを見るとき
done
、それぞれが空でmbuffer
自由になるまで待ちなさい。その後、+をsender
使用して終了できます。CtrlC同様に、
ifne
各プロジェクトを削除しreceiver
て完了したことを確認したら、手動で終了できます。別のコマンドでパイピングする場合、通常は完了したかどうかreceiver
はわかりません。わかると思ってもCtrl+は他のコマンドにもC送信されるので、おそらく最善のアイデアではないでしょう。SIGINT
持っている方がはるかに簡単です
ifne
。 Debian はパッケージifne
にあります。moreutils
答え4
Rustを学ぶには問題を解決しなければならなかったので、問題を解決するためのプログラムを直接書くことにしました。それにすることができますhttps://github.com/JanKanis/streamsplit。例:
streamsplit -i ./mybigfile split 2 'ssh remoteserver streamsplit merge -o ./destinationfile'