ไปป์ไลน์แบบสตรีมและไปป์ที่ตั้งชื่อเพื่อเพิ่มอัตรารับส่งข้อมูล
ใช้ FIFO และการแทนที่กระบวนการเพื่อส่งข้อมูลแบบสตรีมระหว่างแต่ละขั้นโดยไม่ต้องใช้ไฟล์ตัวกลาง
ไปป์ไลน์แบบสตรีมและไปป์ที่ตั้งชื่อเพื่อเพิ่มอัตรารับส่งข้อมูล เป็นบทเรียน DevOps Bootcamp ฟรีบน CoddyKit นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน DevOps Bootcamp และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส DevOps Bootcamp มีบทเรียนทั้งหมด 4 บทเรียน
เหตุใดไฟล์ระหว่างกลางจึงลดอัตราการประมวลผล
เมื่อคุณเชื่อมคำสั่งอย่าง sort file.txt > tmp.txt && uniq tmp.txt > result.txt คุณต้องจ่ายต้นทุนแฝง ได้แก่ การเขียนลงดิสก์ การอ่านจากดิสก์ และไปป์ไลน์จะหยุดรอจนกว่าขั้นตอนแรกจะเสร็จสมบูรณ์ก่อนที่ขั้นตอนถัดไปจะเริ่ม
ไปป์ไลน์แบบสตรีมช่วยตัดต้นทุนนั้นออก ข้อมูลจะไหลจากผู้ผลิตไปยังผู้รับโดยตรงผ่านหน่วยความจำ ทีละขั้นตอน และทำงานพร้อมกัน นี่คือแนวคิดหลักเบื้องหลังไปป์ของ Unix และไปป์ที่มีชื่อ (FIFO) ก็ช่วยต่อยอดแนวคิดนี้ให้ไกลยิ่งขึ้น
- ไปป์นิรนาม (
|): เชื่อมคำสั่งสองคำสั่งที่อยู่ติดกันในบรรทัดเชลล์เดียวกัน - ไปป์ที่มีชื่อ (FIFO): ไฟล์พิเศษในระบบไฟล์ที่ทำให้โพรเซสที่ไม่เกี่ยวข้องกันสามารถสตรีมข้อมูลหากันได้
- การแทนที่โพรเซส: ทำให้คำสั่งหนึ่งมองเอาต์พุตของอีกคำสั่งเสมือนเป็นไฟล์
บทเรียนนี้จะแสดงวิธีใช้ทั้งสามรูปแบบเพื่อเพิ่มอัตราการประมวลผลให้สูงสุดในเวิร์กโฟลว์ Bash จริง
โครงสร้างของไปป์ไลน์แบบสตรีม
ไปป์นิรนามเชื่อม stdout ของโพรเซสหนึ่งเข้ากับ stdin ของโพรเซสถัดไป เคอร์เนลจะให้โพรเซสทั้งสองทำงานพร้อมกันในบัฟเฟอร์ในหน่วยความจำที่มีขนาดคงที่ (โดยทั่วไป 64 KB บน Linux)
แนวคิดสำคัญคือ ไปป์ไลน์จะเร็วเท่ากับขั้นตอนที่ช้าที่สุด หากผู้ผลิตเร็วกว่า ก็จะหยุดรอเมื่อบัฟเฟอร์เต็ม หากผู้รับเร็วกว่า ก็จะหยุดรอเมื่อบัฟเฟอร์ว่างเปล่า แรงดันย้อนกลับนี้คือการควบคุมการไหลอัตโนมัติที่ไม่ต้องเสียค่าใช้จ่าย
ตัวอย่างด้านล่างนับที่อยู่ IP ที่ไม่ซ้ำกันในบันทึกการเข้าถึงขนาดใหญ่โดยไม่เขียนไฟล์ชั่วคราวเลย แต่ละขั้นตอนทำงานพร้อมกัน:
#!/usr/bin/env bash
# Stream a 2 GB access log — all stages run in parallel
grep '"GET' /var/log/nginx/access.log \
| awk '{print $1}' \
| sort \
| uniq -c \
| sort -rn \
| head -20สร้างไปป์ที่มีชื่อด้วย mkfifo
ไปป์ที่มีชื่อ (FIFO — เข้าก่อนออกก่อน) สร้างด้วย mkfifo โดยจะปรากฏในระบบไฟล์เหมือนไฟล์ทั่วไป แต่ข้อมูลที่เขียนลงไปจะไม่ถูกเก็บบนดิสก์ แต่จะไหลตรงไปยังโพรเซสที่อ่านข้อมูล
พฤติกรรมสำคัญที่ควรจำ:
- การเขียนไปยัง FIFO จะ หยุดรอ จนกว่าจะมีตัวอ่านเปิดมัน และในทางกลับกัน
- รายการ FIFO จะคงอยู่ในระบบไฟล์ คุณต้องลบด้วย
rmเมื่อใช้งานเสร็จ - อนุญาตให้มีผู้เขียนหลายรายได้ แต่ไม่รับประกันลำดับระหว่างผู้เขียนเหล่านั้น
ด้านล่างนี้ ผู้ผลิตจะบีบอัดข้อมูลลงใน FIFO ขณะที่ผู้รับอัปโหลดข้อมูลไปยัง S3 พร้อมกัน โดยไม่ต้องใช้ไฟล์ชั่วคราว
#!/usr/bin/env bash
mkfifo /tmp/stream_pipe
# Producer: compress in background
gzip -c /var/log/syslog > /tmp/stream_pipe &
# Consumer: read from FIFO (runs in foreground)
wc -l < /tmp/stream_pipe
wait
rm /tmp/stream_pipeการแทนที่โพรเซส: ใช้คำสั่งเสมือนเป็นไฟล์
การแทนที่โพรเซส ใช้ไวยากรณ์ <(command) หรือ >(command) Bash จะสร้าง FIFO (หรือไฟล์ตัวบอกไฟล์ /dev/fd/N) เบื้องหลัง แล้วส่งเส้นทางให้คำสั่งภายนอก
วิธีนี้มีประโยชน์เมื่อเครื่องมือคาดว่าจะได้รับอาร์กิวเมนต์ ชื่อไฟล์ แทน stdin หากไม่ใช้การแทนที่โพรเซส คุณจะต้องใช้ไฟล์ชั่วคราว แต่เมื่อใช้วิธีนี้ คุณจะสตรีมข้อมูลได้โดยตรง
<(cmd)— คำสั่งภายนอกจะอ่านเอาต์พุตจาก cmd>(cmd)— คำสั่งภายนอกจะเขียนไปยังอินพุตของ cmd
#!/usr/bin/env bash
# diff two sorted streams without creating temp files
diff <(sort /etc/passwd) <(sort /etc/group)
# Compare live command output against a baseline
diff <(ls /usr/bin | sort) <(cat ~/bin_baseline.txt | sort)Tee: แยกสตรีมไปยังผู้รับหลายราย
tee อ่าน stdin แล้วเขียนไปยัง ทั้ง stdout และไฟล์หนึ่งไฟล์หรือหลายไฟล์ เมื่อใช้ร่วมกับการแทนที่โพรเซส คุณสามารถแยกสตรีมเดียวไปยังไปป์ไลน์ประมวลผลหลายชุดพร้อมกันได้ โดยไม่ต้องแตะดิสก์เลย
รูปแบบนี้มีประโยชน์เมื่อคุณต้องการ เช่น บันทึกข้อมูลดิบและประมวลผลข้อมูลนั้นไปพร้อมกัน
#!/usr/bin/env bash
# Generate 100000 random numbers, then simultaneously:
# 1. compute the sum
# 2. find the maximum
# 3. count lines (saved to a variable)
seq 1 100000 \
| tee >(awk '{s+=$1} END{print "Sum:", s}') \
>(awk 'BEGIN{m=0} $1>m{m=$1} END{print "Max:", m}') \
| wc -l | xargs echo "Count:"รูปแบบแยกออก: ผู้ผลิตหนึ่งราย ผู้รับหลายราย
เมื่อแหล่งข้อมูลเดียวต้องส่งข้อมูลให้ผู้รับอิสระหลายราย ให้ใช้ tee ร่วมกับการแทนที่โพรเซส >() หลายรายการ ผู้รับแต่ละรายจะได้รับสตรีมทั้งหมดและทำงานพร้อมกัน
วิธีนี้ช่วยหลีกเลี่ยงการอ่านไฟล์ต้นทางหลายครั้ง สำหรับไฟล์ขนาด 10 GB ความแตกต่างนี้มหาศาล — อ่านจากดิสก์ครั้งเดียวแทนที่จะอ่าน N ครั้ง
#!/usr/bin/env bash
# Read a large CSV once; simultaneously:
# - count rows
# - extract column 2 to a file
# - pass column 3 to a stats script
cat large_data.csv \
| tee \
>(wc -l > /tmp/row_count.txt) \
>(cut -d',' -f2 > /tmp/col2.txt) \
>(cut -d',' -f3 | awk '{sum+=$1} END{print sum}' > /tmp/col3_sum.txt) \
> /dev/null
echo "Rows:" $(cat /tmp/row_count.txt)
echo "Col3 sum:" $(cat /tmp/col3_sum.txt)รูปแบบรวมเข้า: ผู้ผลิตหลายราย ผู้รับหนึ่งราย
สิ่งที่ตรงข้ามกับการแยกออกคือ การรวมเข้า: แหล่งข้อมูลอิสระหลายแหล่งสตรีมเข้าสู่ผู้รับรายเดียว FIFO ที่มีชื่อทำให้ทำเช่นนี้ได้อย่างตรงไปตรงมา
กรณีใช้งานทั่วไปคือการรวมสตรีมบันทึกจากเซิร์ฟเวอร์หลายเครื่องแบบเรียลไทม์ หรือการรวมผลลัพธ์บางส่วนจากผู้ปฏิบัติงานแบบขนาน
โปรดทราบว่าเมื่อมีผู้เขียนหลายราย ผู้รับจะเห็นเอาต์พุตที่สอดแทรกกัน ซึ่งไม่เป็นปัญหาสำหรับข้อมูลที่จัดเป็นบรรทัดและแต่ละบรรทัดมีเนื้อหาครบถ้วนในตัวเอง แต่หากลำดับมีความสำคัญ คุณต้องจัดการลำดับด้วยตนเอง
#!/usr/bin/env bash
mkfifo /tmp/fanin_pipe
# Three producers write concurrently into the same FIFO
for host in web1 web2 web3; do
ssh "$host" 'tail -n 500 /var/log/app.log' > /tmp/fanin_pipe &
done
# Single consumer reads all merged output
grep 'ERROR' /tmp/fanin_pipe | sort | uniq -c | sort -rn
wait
rm /tmp/fanin_pipeใช้ mkfifo สำหรับการบีบอัดแบบขนาน
การใช้งาน FIFO ที่เป็นประโยชน์อย่างยิ่งอย่างหนึ่งคือ การบีบอัดแบบขนาน เครื่องมืออย่าง pigz (gzip แบบขนาน) หรือ pbzip2 อ่านข้อมูลจากสตรีม คุณจึงส่งข้อมูลดิบเข้าไปโดยตรงได้โดยไม่ต้องสร้างไฟล์ที่ยังไม่บีบอัดไว้ก่อน
รูปแบบด้านล่างจะจัดเก็บไดเรกทอรี บีบอัดด้วยแกน CPU ทั้งหมด และสตรีมผลลัพธ์ไปยังโฮสต์ระยะไกลพร้อมกัน:
#!/usr/bin/env bash
# Tar + parallel compress + stream to remote — no temp files
# Requires: pigz (parallel gzip)
tar cf - /data/large_dir \
| pigz -p 4 \
| ssh backup-host 'cat > /backups/large_dir.tar.gz'
# Verify the remote file exists
ssh backup-host 'ls -lh /backups/large_dir.tar.gz'ควบคุมขนาดบัฟเฟอร์และการหยุดรอ
ไปป์มีบัฟเฟอร์ของเคอร์เนล (โดยทั่วไป 64 KB) เมื่อบัฟเฟอร์เต็ม ผู้เขียนจะ หยุดรอ และเมื่อบัฟเฟอร์ว่าง ผู้รับจะหยุดรอ โดยทั่วไปนี่คือสิ่งที่คุณต้องการ แต่บางกรณีการหยุดรออาจทำให้เกิดการติดตาย
ความเสี่ยงที่จะติดตาย: หากโพรเซส A เขียนไปยัง FIFO1 และอ่านจาก FIFO2 ขณะที่โพรเซส B เขียนไปยัง FIFO2 และอ่านจาก FIFO1 ทั้งสองโพรเซสอาจหยุดรอให้อีกฝ่ายอ่านข้อมูลก่อน
วิธีแก้:
- ให้ฝั่งใดฝั่งหนึ่งทำงานแบบ เบื้องหลัง (
&) เพื่อไม่ให้เชลล์หยุดรอ - ใช้
mbufferหรือpvเพื่อเพิ่มบัฟเฟอร์ในหน่วยความจำที่ใหญ่ขึ้นระหว่างขั้นตอน - ใช้
pv -q -B 128mเพื่อแทรกบัฟเฟอร์ขนาด 128 MB ช่วยให้ความผันผวนของอัตราการประมวลผลราบรื่นขึ้น
#!/usr/bin/env bash
# pv adds a 64 MB buffer and shows throughput
# Useful when producer and consumer have bursty speeds
dd if=/dev/urandom bs=1M count=200 \
| pv -B 64m \
| gzip \
| wc -cตัวอย่างเชิงปฏิบัติ: ตัวรวมบันทึกแบบเรียลไทม์
นี่คือตัวอย่างรูปแบบที่สมบูรณ์และใช้งานได้จริง: ติดตามไฟล์บันทึกหลายไฟล์ รวมสตรีมผ่านไปป์ที่มีชื่อ กรองหาข้อผิดพลาด และเขียนสรุปแบบสด ทั้งหมดนี้ทำได้โดยไม่ใช้ไฟล์ระหว่างกลาง และทุกขั้นตอนทำงานพร้อมกัน
ไปป์ไลน์ลักษณะนี้มักทำงานเป็นสคริปต์ตรวจสอบเบื้องหลังบนเซิร์ฟเวอร์ที่ใช้งานจริง
#!/usr/bin/env bash
FIFO=/tmp/log_aggregator
mkfifo "$FIFO"
cleanup() { rm -f "$FIFO"; }
trap cleanup EXIT INT TERM
# Fan-in: tail multiple logs into the FIFO
tail -F /var/log/syslog /var/log/auth.log > "$FIFO" &
TAIL_PID=$!
# Consumer: filter and timestamp errors in real time
grep --line-buffered -i 'error\|fail\|crit' "$FIFO" \
| while IFS= read -r line; do
printf '[%s] %s\n' "$(date '+%H:%M:%S')" "$line"
done
kill "$TAIL_PID" 2>/dev/nullเปรียบเทียบประสิทธิภาพของไปป์ไลน์กับไฟล์ชั่วคราว
คุณสามารถวัดความแตกต่างของอัตราการประมวลผลจริงระหว่างแนวทางแบบสตรีมกับแนวทางใช้ไฟล์ชั่วคราวได้ด้วย time แนวทางไปป์ไลน์จะทำงานได้ดีกว่าเมื่อใช้ชุดข้อมูลขนาดใหญ่ เนื่องจาก:
- ขั้นตอนต่าง ๆ ทำงานพร้อมกัน — CPU และการรับส่งข้อมูลทำงานทับซ้อนกัน
- ไม่มีการรับส่งข้อมูลบนดิสก์สำหรับข้อมูลระหว่างกลาง — มีเพียงเอาต์พุตสุดท้ายที่ถูกเขียนลงดิสก์
- การใช้หน่วยความจำคงที่โดยไม่ขึ้นกับขนาดอินพุต (สตรีม ไม่ใช่บัฟเฟอร์)
การทดสอบประสิทธิภาพอย่างง่ายเพื่อเปรียบเทียบทั้งสองแนวทาง:
#!/usr/bin/env bash
# Approach 1: Temp file (sequential)
time bash -c '
seq 1 5000000 > /tmp/nums.txt
sort -n /tmp/nums.txt > /tmp/sorted.txt
uniq /tmp/sorted.txt | wc -l
rm /tmp/nums.txt /tmp/sorted.txt
'
echo '---'
# Approach 2: Streaming pipeline (concurrent)
time bash -c 'seq 1 5000000 | sort -n | uniq | wc -l'แบบทดสอบความเข้าใจ: พฤติกรรมการหยุดรอของไปป์ที่มีชื่อ
พิจารณาสคริปต์ต่อไปนี้:
mkfifo /tmp/mypipe
echo 'hello' > /tmp/mypipe
echo 'done'เมื่อเรียกใช้สคริปต์นี้โดยไม่มีโพรเซสเบื้องหลังหรือตัวอ่าน จะเกิดอะไรขึ้น
ทบทวน: กระบวนการส่งข้อมูลต่อเนื่องและไปป์ที่มีชื่อ
ในบทเรียนนี้ คุณได้สำรวจวิธีเคลื่อนย้ายข้อมูลระหว่างโพรเซสอย่างมีประสิทธิภาพโดยไม่ใช้ไฟล์ชั่วคราวระหว่างทาง:
- ไปป์แบบไม่ระบุชื่อ (
|) เชื่อมคำสั่งที่อยู่ติดกันและเรียกใช้ทุกขั้นตอนพร้อมกัน โดยจัดการแรงดันย้อนกลับโดยอัตโนมัติ - ไปป์ที่มีชื่อ (
mkfifo) สร้างรายการ FIFO ในระบบไฟล์ ซึ่งทำให้โพรเซสที่ไม่เกี่ยวข้องกันหรือโพรเซสเบื้องหลังสามารถส่งข้อมูลต่อเนื่องหากันได้ — การเขียนจะหยุดรอจนกว่าจะมีตัวอ่านอยู่ - การแทนที่โพรเซส (
<(cmd),>(cmd)) ทำให้คำสั่งที่คาดว่าจะได้รับชื่อไฟล์สามารถรับหรือสร้างกระแสข้อมูลได้อย่างโปร่งใส tee+>()แยกกระแสข้อมูลหนึ่งกระแสไปยังตัวรับหลายตัวที่ทำงานพร้อมกัน โดยไม่ต้องอ่านแหล่งข้อมูลซ้ำ- การรวมกระแสเข้า รวมผู้สร้างข้อมูลหลายรายเข้าเป็นผู้รับรายเดียวผ่าน FIFO ที่ใช้ร่วมกัน
- แรงดันย้อนกลับและการหยุดรอ เป็นคุณสมบัติ ไม่ใช่ข้อผิดพลาด — แต่ควรเรียกใช้อย่างน้อยหนึ่งฝั่งของคู่ FIFO เป็นโพรเซสเบื้องหลังเสมอ เพื่อหลีกเลี่ยงภาวะติดตาย
- ใช้
pvหรือmbufferเพื่อเพิ่มบัฟเฟอร์ให้ใหญ่ขึ้นและติดตามอัตราการส่งข้อมูลเมื่อขั้นตอนต่าง ๆ ทำงานเป็นช่วง ๆ
เทคนิคเหล่านี้เป็นรากฐานของวิศวกรรมข้อมูล Bash ที่มีอัตราการประมวลผลสูง: ประมวลผลข้อมูลระดับ GB โดยใช้หน่วยความจำคงที่และใช้การประมวลผลแบบขนานของ CPU/IO ได้สูงสุด
คำถามที่พบบ่อย
บทเรียน “ไปป์ไลน์แบบสตรีมและไปป์ที่ตั้งชื่อเพื่อเพิ่มอัตรารับส่งข้อมูล” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “ไปป์ไลน์แบบสตรีมและไปป์ที่ตั้งชื่อเพื่อเพิ่มอัตรารับส่งข้อมูล” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส DevOps Bootcamp ให้อัปเกรดเป็น CoddyKit PRO คอร์ส DevOps Bootcamp มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “ไปป์ไลน์แบบสตรีมและไปป์ที่ตั้งชื่อเพื่อเพิ่มอัตรารับส่งข้อมูล”
ใช้ FIFO และการแทนที่กระบวนการเพื่อส่งข้อมูลแบบสตรีมระหว่างแต่ละขั้นโดยไม่ต้องใช้ไฟล์ตัวกลาง คุณปฏิบัติ DevOps Bootcamp ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน DevOps Bootcamp หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน DevOps Bootcamp บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน
บทเรียน “ไปป์ไลน์แบบสตรีมและไปป์ที่ตั้งชื่อเพื่อเพิ่มอัตรารับส่งข้อมูล” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน DevOps Bootcamp นี้ได้ไหม
ได้ บทเรียน DevOps Bootcamp ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- การวิเคราะห์ประสิทธิภาพสคริปต์และการหลีกเลี่ยง subshell ที่ไม่จำเป็น
- การทำงานแบบขนานด้วย xargs -P และงานเบื้องหลัง
- การจัดการเวิร์กโหลดด้วย GNU parallel
- ไปป์ไลน์แบบสตรีมและไปป์ที่ตั้งชื่อเพื่อเพิ่มอัตรารับส่งข้อมูล