split
Linux에 스트리밍 버전이 있나요?
SSH를 통해 대량의 데이터를 백업하려고 하는데 SSH의 단일 스레드 암호화로 인해 전송이 제한됩니다. 이 머신은 하드웨어 AES를 지원하지 않기 때문에 ChaCha 암호화를 사용하지만 CPU가 여전히 네트워크를 따라갈 수 없습니다.
그래서 저는 데이터 스트림을 두 부분으로 나누고 각 스트림을 별도의 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
그게 다야.
한 가지(대부분 이론적인) 주의 사항은 주의할 가치가 있을 수 있습니다. split -n r/..
본질적으로 분할은 개행 문자에 의해 수행되기 때문입니다. 입력 데이터에 개행 문자가 전혀 없으면 분할이 전혀 수행되지 않고 전체 데이터 양이 단일 경로를 통해 전송됩니다. 예에서는 연결 4입니다.
화타이
답변3
수동 "분할"
(로컬) 소스 파일과 (원격) 대상 파일을 검색할 수 있는 경우 간단한 해결책은 오프셋과 크기를 수동으로 계산하고 각각 파일 조각을 전송하는 두 개 이상의 파이프라인을 실행하는 것입니다. 예(소스):
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
)의 데이터가 전송됩니다. 그런 다음 두 번째 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) 파일 시스템은 실제로 내용을 쓰지 않고 파일 확장을 지원하지 않으므로 0을 쓰는 데 시간이 오래 걸릴 수 있습니다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
보내는 쪽에서는 각각 하나 이상의 블록 크기를 사용해야 한다고 생각합니다 . 예시에서는 (-m 10M
)를 두 번 사용했습니다. - 수신 측의 버퍼가 너무 작아서 다음 파이프로 이동하기 전에 그 중 하나를 소진할 경우 , 쓰고 있는 버퍼가 가득 차게 되므로
receiver
다른ssh
버퍼가 정지될 수 있습니다 . 각 수신기마다 최소한 하나의 블록 크기를 사용해야 한다고mbuffer
생각합니다 .mbuffer
예시에서는 (-m 10M
)를 두 번 사용했습니다.
- 보낸 사람의 버퍼가 너무 작으면 보낸 사람의
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
. 데비안에서는 패키지ifne
에 있습니다 .moreutils
답변4
Rust를 배우려면 문제를 해결해야 했기 때문에 문제를 해결하기 위한 프로그램을 직접 작성하기로 했습니다. 그것은에있을 수 있습니다https://github.com/JanKanis/streamsplit. 예:
streamsplit -i ./mybigfile split 2 'ssh remoteserver streamsplit merge -o ./destinationfile'