Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

เชี่ยวชาญ Concurrency: การขยายระบบอย่างมีประสิทธิภาพสำหรับแอปประสิทธิภาพสูง

เชี่ยวชาญ Concurrency

หลายคนจะใช้แอปของคุณพร้อมกัน และคุณต้องการส่งมอบแอปให้เร็วที่สุด ดังนั้นคุณต้องมีวิธีจัดการ concurrency อย่าเพิ่งหวาดกลัว! เว็บเซิร์ฟเวอร์ส่วนใหญ่ทำอย่างนี้เป็นค่าเริ่มต้นแล้ว แต่เมื่อคุณต้องขยายระบบ คุณต้องการใช้ concurrency ให้มีประสิทธิภาพสูงสุด

ประเภท Concurrency ที่แตกต่างกัน

มีหลายวิธีจัดการ concurrency: multi-process, multi-threading และ event-driven แต่ละอย่างมีการใช้งาน ข้อดีและข้อเสีย ในบทความนี้ คุณจะได้เรียนรู้ว่าพวกมันต่างกันอย่างไร และเมื่อไหร่ควรใช้ตัวไหน

Multi-process (Unicorn)

นี่คือวิธีที่ง่ายที่สุดในการจัดการ concurrency Master process fork ตัวเองเป็น worker processes หลายตัว Worker process จัดการคำขอจริง ในขณะที่ master จัดการ workers

แต่ละ worker process มีโค้ดเบสเต็มรูปแบบในหน่วยความจำ ทำให้วิธีนี้ใช้หน่วยความจำมาก และยากที่จะขยายไปสู่โครงสร้างพื้นฐานขนาดใหญ่

สรุป Multi-process
Use caseตัวอย่าง non-Ruby ที่คุณคุ้นเคยคือ Chrome browser มันใช้ multi-process concurrency ให้แต่ละแท็บมี process ของตัวเอง มันอนุญาตให้แท็บเดียวแครชโดยไม่ทำให้แอปพลิเคชันทั้งหมดล้ม ในกรณีของพวกเขา ยังช่วยแยก exploits ให้อยู่ในแท็บเดียว
ข้อดีง่ายที่สุดในการนำไปใช้
ไม่สนใจความยากลำบากเรื่อง thread safety
แต่ละ worker แครชได้โดยไม่ทำลายระบบส่วนที่เหลือ
ข้อเสียแต่ละ process โหลดโค้ดเบสเต็มรูปแบบในหน่วยความจำ ทำให้ใช้หน่วยความจำมาก
ดังนั้น ไม่สามารถขยายไปสู่การเชื่อมต่อพร้อมกันจำนวนมากได้

Multi-threading (Puma)

โมเดล threading นี้อนุญาตให้ process เดียวจัดการคำขอหลายคำขอพร้อมกัน โดยการรัน threads หลายตัวภายใน process เดียว

ต่างจาก multi-process approach ทุก threads รันภายใน process เดียวกัน หมายความว่าพวกมันแชร์ข้อมูลอย่าง global variables ดังนั้น จึงใช้หน่วยความจำเพิ่มเพียงเล็กน้อยต่อ thread

Global Interpreter Lock

นี่นำเราไปสู่ global interpreter lock (GIL) ใน MRI GIL เป็น lock รอบการทำงานของโค้ด Ruby ทั้งหมด แม้ threads ของเราจะดูเหมือนรันแบบ parallel แต่จริงๆ แค่ thread เดียวที่ active ในเวลาเดียวกัน

IO ทำงานนอกเหนือจาก GIL เมื่อคุณรันคิวรี่ฐานข้อมูลรอผลลัพธ์ มันจะไม่ lock Thread อื่นจะได้โอกาสทำงานในתוךนั้น หากคุณทำคณิตศาสตร์มากและ操作 hashes หรือ arrays ใน threads คุณจะใช้เพียง single core หากใช้ MRI ในกรณีส่วนใหญ่คุณยังต้องการ multiple processes เพื่อใช้ประโยชน์จากเครื่องจักรเต็มที่ หรือคุณอาจใช้ Rubinius หรือ jRuby ซึ่งไม่มี GIL

Thread Safety

หากคุณใช้ multiple threads คุณต้องระมัดระวังเขียนโค้ดที่จัดการ shared data ให้ thread safe คุณสามารถทำได้โดยใช้ Mutex เพื่อ lock shared data structures ก่อนจัดการ จะทำให้ threads อื่นไม่ได้พึ่งพาข้อมูลเก่า בזמןที่คุณกำลังเปลี่ยนข้อมูล

สรุป Multi-threaded
Use caseนี่คือตัวเลือก "กลางทาง" ใช้สำหรับเว็บแอปพลิเคชันมาตรฐานหลายตัวที่ควรจัดการโหลดคำขอสั้นๆ มาก (เช่น เว็บแอปพลิเคชันที่แน่น)
ข้อดีใช้หน่วยความจำน้อยกว่า multi-process
ข้อเสียคุณต้องแน่ใจว่าโค้ดของคุณ thread safe
ถ้า thread ก่อให้เกิด crash อาจทำให้ process ล้มได้
GIL lock ทุกการดำเนินการยกเว้น I/O

Event-loop (Thin)

Event-loops ใช้เมื่อคุณต้องทำ concurrent I/O operations มาก โมเดลตัวเองไม่ได้บังคับให้คำขอหลายคำขอรันพร้อมกัน แต่เป็นวิธีที่มีประสิทธิภาพในการจัดการผู้ใช้พร้อมกันจำนวนมาก

ด้านล่างคุณจะเห็น event loop ง่ายๆ ที่เขียนใน Ruby ลูปจะรับ event จาก event_queue และจัดการ ถ้าไม่มี event มันจะ sleep และทำซ้ำดูว่ามี event ใหม่ใน queue หรือไม่

loop do if event_queue.any? handle_event(event_queue.pop) else sleep 0.1 end end

เวอร์ชันประกอบภาพ

ในภาพประกอบนี้ เราไปต่ออีกขั้น Event loop ตอนนี้ทำการเต้นรำที่สวยงามกับ OS, queue และหน่วยความจำบางส่วน

ขั้นตอนทีละขั้น

  1. OS ติดตามความพร้อมของ network และ disk
  2. เมื่อ OS เห็น I/O พร้อม มันส่ง event ไป queue
  3. Queue เป็นรายการ events ที่ event loop รับตัวบนสุด
  4. Event loop จัดการ event
  5. มันใช้หน่วยความจำบางส่วนจัดเก็บ metadata เกี่ยวกับ connections
  6. มันสามารถส่ง event ใหม่เข้า event queue ได้อีก เช่น ข้อความปิด queue ตามเนื้อหาของ event
  7. ถ้ามันอยากทำ I/O operation มันบอก OS ว่าสนใจ I/O operation เฉพาะ OS ติดตาม network และ disk (ดู [1]) และเพิ่ม event อีกครั้งเมื่อ I/O พร้อม
สรุป Event-loop
Use caseเมื่อใช้การเชื่อมต่อพร้อมกันมากกับผู้ใช้ คิดถึงบริการอย่าง Slack, Chrome notifications
ข้อดีเกือบไม่มี memory overhead ต่อ connection
ขยายได้ถึงจำนวน parallel connections มาก
ข้อเสียเป็น mental model ที่ยากต่อการเข้าใจ
Batch sizes ต้องเล็กและคาดเดาได้ เพื่อไม่ให้ queues สะสม

คุณควรใช้ตัวไหน?

เราหวังว่าบทความนี้ให้คุณเข้าใจ concurrency models ต่างๆ ได้ดีขึ้น เรื่องนี้เป็นหนึ่งในเรื่องที่ยากต่อการเข้าใจในฐานะนักพัฒนา แต่การเข้าใจมันจะให้คุณเครื่องมือทดลองและใช้ setup ที่เหมาะกับแอปของคุณ

สรุป

  • สำหรับแอปส่วนใหญ่ threading สมเหตุสมผล ระบบนิเวศ Ruby/Rails กำลังเคลื่อนไหวไปทางนี้ช้าๆ
  • ถ้าคุณรันแอป concurrent มากกับ long-running streams event-loop ให้คุณขยายได้
  • ถ้าคุณไม่มีไซต์ traffic สูง หรือคาดว่า workers จะแตก ให้เลือก multi-process แบบเก่าๆ ที่ดี

และ แท้จริงคุณสามารถรัน event loop ภายใน thread ภายใน multi-process setup ได้ ดังนั้น ใช่ คุณสามารถได้ stroopwafel และกินมันพร้อมกันได้!

ถ้าอยากอ่านเพิ่มเติมเกี่ยวกับ concurrency models เหล่านี้ ดูบทความโดยละเอียดของเราเกี่ยวกับ multi-process, multi-threading และ event loops