ストリーミング分割/マージコマンド?

ストリーミング分割/マージコマンド?

splitLinuxにストリーミングバージョンがありますか?

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(1000x 10M)のデータが送信されます。その後、2番目の1000MiBが使用され、送信さskip=1000れます。seek=1000そして.skip=2000seek=2000。複数のコマンドを同時に実行できます。

実際には、コマンドの最初の部分を送信する必要はなくskipseek両方0のデフォルト値であるため)、コマンドの最後の部分を送信する必要もありませんcount(両方ともddEOFで終了するため)。

だから走りたいなら窒素 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=fullblockddcount

    dd skip=… … | head -c … | ssh …別の解決策はnoと同じですcount=dd検索できないストリームから読み取る場合、skipnoはiflag=fullblockおそらく必要なコンテンツより少ないコンテンツをスキップします。しかし、通常のファイルから読み取るのがskipポインタを動かすだけであれば安全です。head -c持ち運びが簡単ではありませんが。

  • GNUはおよびをddサポートすることで計算を大幅に簡素化できます。たとえば、次のコマンドは最初の200GiB、次の300GiB、残りをそれぞれ送信します。iflag=skip_bytes,count_bytesoflag=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あるオフセットから別のオフセットにジャンプし続けます。メカニカルドライブが関係している場合、この方法は正しく行われないか、ドライブに負担がかかる可能性があります。


代替方法

この方法を使用すると、検索可能または検索できないソースからのデータを検索可能または検索できない宛先に転送できます。データのサイズを事前に知る必要はありません。順次読み書きします。

送信者で実行されたときにすべてを設定し、受信者で正しいコードを実行するスクリプトを作成することができます。私は解決策を比較的単純に保つことにしました。したがって、送信者で実行するコードスニペットと受信者で実行するコードスニペット、および実行する必要がある手動パイピングがあります。

送信システムにはbashifneおよびサポートが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へのパスは相対的にすることができます。senderFIFOからデータを読み取るのを待ちます。

受信機で同じ数のパラメータを使用してコマンドを実行します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

  • -cfor は for headin scripts と同じです。このサイズのチャンクはラウンドロビン方式で FIFO に転送されます。変更する場合は、両方のスクリプトで同じ値を使用してください。-bsplit-c 5M批判的。また、それに応じて-msを調整しますmbuffer

  • mbuffersのサイズと対応するバッファはパフォーマンスにとって重要です。

    • 送信者のバッファが小さすぎると、送信者の速度が遅くなり、そのsender一方がゆっくり暗号化されるのを待ちssh、他のsshバッファは転送を続行するためにアイドル状態で待機しますsendermbuffer送信側では、それぞれ1つ以上のブロックサイズを使用する必要があると思います。例では、(-m 10M)を2回使用しました。
    • 受信側のバッファが小さすぎて次のパイプに移動する前にそのうちの1つを使い果たした場合、書き込んでいるバッファがいっぱいになるため、receiver他のsshバッファが停止することがあります。各受信機に少なくとも1つのブロックサイズを使用する必要があるとmbuffer思います。mbuffer例では、(-m 10M)を2回使用しました。
  • bashfifoの数が事前にわからない場合は、ファイル記述子を処理する必要があります。sh固定数のfifoを使用すると、スクリプトを純粋に移植するのが比較的簡単です。

  • headddサポートに置き換えることができますiflag=fullblock

  • mbuffer+パイプは終了後に自動的に終了する必要がsshあります。これらのパイプを手動でシャットダウンする必要はありません。senderreceiver

  • ifneEOFを検出するために使用されます。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'

関連情報