การประกาศ
เพื่อนร่วมงาน ในช่วงกลางฤดูร้อน ฉันวางแผนที่จะเผยแพร่บทความอีกชุดเกี่ยวกับการออกแบบระบบคิว: “การทดลอง VTrade” - ความพยายามในการเขียนกรอบการทำงานสำหรับระบบการซื้อขาย ซีรีส์นี้จะกล่าวถึงทฤษฎีและการปฏิบัติในการสร้างการแลกเปลี่ยน การประมูล และการจัดเก็บ ในตอนท้ายของบทความ ฉันขอเชิญคุณลงคะแนนในหัวข้อที่คุณสนใจมากที่สุด
นี่เป็นบทความสุดท้ายในชุดเกี่ยวกับแอปพลิเคชันแบบโต้ตอบแบบกระจายใน Erlang/Elixir ใน
วันนี้เราจะหยิบยกประเด็นการพัฒนาฐานโค้ดและโครงการโดยทั่วไป
องค์กรบริการ
ในชีวิตจริง เมื่อพัฒนาบริการ คุณมักจะต้องรวมรูปแบบการโต้ตอบหลายรูปแบบไว้ในคอนโทรลเลอร์ตัวเดียว ตัวอย่างเช่น บริการผู้ใช้ซึ่งแก้ปัญหาในการจัดการโปรไฟล์ผู้ใช้โครงการ จะต้องตอบสนองต่อคำขอ req-resp และรายงานการอัปเดตโปรไฟล์ผ่าน pub-sub กรณีนี้ค่อนข้างง่าย: เบื้องหลังการส่งข้อความจะมีตัวควบคุมหนึ่งตัวที่ใช้ตรรกะของบริการและเผยแพร่การอัปเดต
สถานการณ์จะซับซ้อนมากขึ้นเมื่อเราจำเป็นต้องใช้บริการแบบกระจายที่ทนทานต่อข้อผิดพลาด ลองจินตนาการว่าข้อกำหนดสำหรับผู้ใช้มีการเปลี่ยนแปลง:
- ตอนนี้บริการควรประมวลผลคำขอบน 5 โหนดคลัสเตอร์
- สามารถทำงานประมวลผลเบื้องหลังได้
- และยังสามารถจัดการรายการสมัครสมาชิกสำหรับการอัพเดตโปรไฟล์แบบไดนามิกได้
หมายเหตุ: เราไม่คำนึงถึงปัญหาของการจัดเก็บและการจำลองข้อมูลที่สอดคล้องกัน สมมติว่าปัญหาเหล่านี้ได้รับการแก้ไขแล้วก่อนหน้านี้ และระบบมีชั้นพื้นที่จัดเก็บข้อมูลที่เชื่อถือได้และปรับขนาดได้อยู่แล้ว และผู้จัดการมีกลไกในการโต้ตอบกับชั้นดังกล่าว
คำอธิบายอย่างเป็นทางการของบริการผู้ใช้มีความซับซ้อนมากขึ้น จากมุมมองของโปรแกรมเมอร์ การเปลี่ยนแปลงมีเพียงเล็กน้อยเนื่องจากการใช้ข้อความ เพื่อให้เป็นไปตามข้อกำหนดแรก เราจำเป็นต้องกำหนดค่าการปรับสมดุลที่จุดแลกเปลี่ยนความต้องการ
ข้อกำหนดในการประมวลผลงานเบื้องหลังเกิดขึ้นบ่อยครั้ง สำหรับผู้ใช้ อาจเป็นการตรวจสอบเอกสารของผู้ใช้ ประมวลผลมัลติมีเดียที่ดาวน์โหลด หรือการซิงโครไนซ์ข้อมูลกับโซเชียลมีเดีย เครือข่าย งานเหล่านี้จำเป็นต้องมีการกระจายภายในคลัสเตอร์และติดตามความคืบหน้าของการดำเนินการ ดังนั้นเราจึงมีทางเลือกในการแก้ปัญหาสองทาง: ใช้เทมเพลตการกระจายงานจากบทความก่อนหน้า หรือหากไม่เหมาะสม ให้เขียนตัวกำหนดเวลางานแบบกำหนดเองที่จะจัดการกลุ่มตัวประมวลผลในแบบที่เราต้องการ
จุดที่ 3 ต้องใช้ส่วนขยายเทมเพลต pub-sub และสำหรับการนำไปใช้ หลังจากสร้างจุดแลกเปลี่ยน pub-sub แล้ว เราจำเป็นต้องเปิดตัวคอนโทรลเลอร์ของจุดนี้เพิ่มเติมภายในบริการของเรา ดังนั้นจึงเหมือนกับว่าเรากำลังย้ายตรรกะสำหรับการประมวลผลการสมัครสมาชิกและการยกเลิกการสมัครจากเลเยอร์การส่งข้อความไปสู่การใช้งานของผู้ใช้
เป็นผลให้การสลายตัวของปัญหาแสดงให้เห็นว่าเพื่อให้เป็นไปตามข้อกำหนด เราจำเป็นต้องเปิดตัวบริการ 5 อินสแตนซ์บนโหนดที่แตกต่างกัน และสร้างเอนทิตีเพิ่มเติม - ตัวควบคุม pub-sub ที่รับผิดชอบในการสมัครสมาชิก
หากต้องการเรียกใช้ตัวจัดการ 5 ตัว คุณไม่จำเป็นต้องเปลี่ยนรหัสบริการ การดำเนินการเพิ่มเติมเพียงอย่างเดียวคือการตั้งค่ากฎการปรับสมดุลที่จุดแลกเปลี่ยน ซึ่งเราจะพูดถึงในภายหลัง
นอกจากนี้ยังมีความซับซ้อนเพิ่มเติม: ตัวควบคุม pub-sub และตัวกำหนดเวลางานแบบกำหนดเองจะต้องทำงานในสำเนาเดียว ขอย้ำอีกครั้งว่าบริการส่งข้อความซึ่งเป็นบริการพื้นฐานจะต้องมีกลไกในการเลือกผู้นำ
การเลือกผู้นำ
ในระบบแบบกระจาย การเลือกผู้นำเป็นขั้นตอนในการแต่งตั้งกระบวนการเดียวที่รับผิดชอบในการจัดกำหนดการการประมวลผลแบบกระจายของโหลดบางส่วน
ในระบบที่ไม่เสี่ยงต่อการรวมศูนย์ จะใช้อัลกอริธึมที่เป็นสากลและเป็นเอกฉันท์ เช่น paxos หรือแพ
เนื่องจากการส่งข้อความเป็นนายหน้าและเป็นองค์ประกอบหลัก จึงทราบเกี่ยวกับผู้ควบคุมบริการทั้งหมด - ผู้นำผู้สมัคร การส่งข้อความสามารถแต่งตั้งผู้นำได้โดยไม่ต้องลงคะแนน
หลังจากเริ่มต้นและเชื่อมต่อกับจุดแลกเปลี่ยนแล้ว บริการทั้งหมดจะได้รับข้อความระบบ #'$leader'{exchange = ?EXCHANGE, pid = LeaderPid, servers = Servers}
. ถ้า LeaderPid
เกิดขึ้นพร้อมกับ pid
กระบวนการปัจจุบันก็ได้รับแต่งตั้งให้เป็นผู้นำและรายชื่อ Servers
รวมโหนดทั้งหมดและพารามิเตอร์
ในขณะนี้ โหนดใหม่ปรากฏขึ้นและโหนดคลัสเตอร์ที่ทำงานถูกตัดการเชื่อมต่อ ตัวควบคุมบริการทั้งหมดจะได้รับ #'$slave_up'{exchange = ?EXCHANGE, pid = SlavePid, options = SlaveOpts}
и #'$slave_down'{exchange = ?EXCHANGE, pid = SlavePid, options = SlaveOpts}
ตามลำดับ
ด้วยวิธีนี้ ส่วนประกอบทั้งหมดจะรับรู้ถึงการเปลี่ยนแปลงทั้งหมด และรับประกันว่าคลัสเตอร์จะมีผู้นำเพียงคนเดียวในเวลาใดก็ตาม
คนกลาง
หากต้องการใช้กระบวนการประมวลผลแบบกระจายที่ซับซ้อน รวมถึงปัญหาในการเพิ่มประสิทธิภาพสถาปัตยกรรมที่มีอยู่ จะสะดวกในการใช้ตัวกลาง
เพื่อไม่ให้เปลี่ยนรหัสบริการและแก้ปัญหา เช่น ปัญหาการประมวลผลเพิ่มเติม การกำหนดเส้นทางหรือบันทึกข้อความ คุณสามารถเปิดใช้งานตัวจัดการพร็อกซีก่อนบริการ ซึ่งจะดำเนินการงานเพิ่มเติมทั้งหมด
ตัวอย่างคลาสสิกของการเพิ่มประสิทธิภาพ pub-sub คือแอปพลิเคชันแบบกระจายที่มีแกนธุรกิจที่สร้างเหตุการณ์การอัปเดต เช่น การเปลี่ยนแปลงราคาในตลาด และเลเยอร์การเข้าถึง - เซิร์ฟเวอร์ N ที่ให้ websocket API สำหรับเว็บไคลเอ็นต์
หากคุณตัดสินใจโดยตรง การบริการลูกค้าจะเป็นดังนี้:
- ลูกค้าสร้างการเชื่อมต่อกับแพลตฟอร์ม ที่ด้านข้างของเซิร์ฟเวอร์ที่ยุติการรับส่งข้อมูล จะมีการเปิดตัวกระบวนการเพื่อให้บริการการเชื่อมต่อนี้
- ในบริบทของกระบวนการบริการ การอนุญาตและการสมัครสมาชิกการอัปเดตจะเกิดขึ้น กระบวนการเรียกวิธีการสมัครสมาชิกสำหรับหัวข้อ
- เมื่อเหตุการณ์ถูกสร้างขึ้นในเคอร์เนล เหตุการณ์จะถูกส่งไปยังกระบวนการที่ให้บริการการเชื่อมต่อ
สมมติว่าเรามีสมาชิก 50000 รายในหัวข้อ "ข่าว" สมาชิกจะถูกกระจายอย่างเท่าเทียมกันใน 5 เซิร์ฟเวอร์ ด้วยเหตุนี้ การอัปเดตแต่ละครั้งที่มาถึงจุดแลกเปลี่ยน จะถูกจำลองแบบ 50000 ครั้ง: 10000 ครั้งในแต่ละเซิร์ฟเวอร์ ตามจำนวนสมาชิกในการอัปเดต ไม่ใช่แผนการที่มีประสิทธิภาพมากนักใช่ไหม?
เพื่อปรับปรุงสถานการณ์ ขอแนะนำพร็อกซีที่มีชื่อเดียวกับจุดแลกเปลี่ยน ผู้รับจดทะเบียนชื่อสากลจะต้องสามารถส่งคืนกระบวนการที่ใกล้เคียงที่สุดตามชื่อได้ ซึ่งเป็นสิ่งสำคัญ
มาเปิดใช้พร็อกซีนี้บนเซิร์ฟเวอร์เลเยอร์การเข้าถึง และกระบวนการทั้งหมดของเราที่ให้บริการ websocket api จะสมัครสมาชิก ไม่ใช่จุดแลกเปลี่ยน pub-sub ดั้งเดิมในเคอร์เนล พร็อกซีสมัครสมาชิกแกนหลักเฉพาะในกรณีของการสมัครสมาชิกที่ไม่ซ้ำกันและทำซ้ำข้อความขาเข้าไปยังสมาชิกทั้งหมด
ด้วยเหตุนี้ ข้อความ 5 ข้อความจะถูกส่งระหว่างเคอร์เนลและเซิร์ฟเวอร์การเข้าถึง แทนที่จะเป็น 50000 ข้อความ
การกำหนดเส้นทางและความสมดุล
ความต้องการ-Resp
ในการใช้งานการรับส่งข้อความในปัจจุบัน มี 7 กลยุทธ์ในการกระจายคำขอ:
default
. คำขอจะถูกส่งไปยังผู้ควบคุมทั้งหมดround-robin
. คำขอจะถูกแจกแจงและกระจายตามรอบระหว่างตัวควบคุมconsensus
. ผู้ควบคุมที่ให้บริการจะแบ่งออกเป็นผู้นำและทาส คำขอจะถูกส่งไปยังผู้นำเท่านั้นconsensus & round-robin
. กลุ่มมีผู้นำ แต่มีการกระจายคำขอไปยังสมาชิกทุกคนsticky
. ฟังก์ชันแฮชได้รับการคำนวณและกำหนดให้กับตัวจัดการเฉพาะ คำขอครั้งต่อไปที่มีลายเซ็นนี้จะถูกส่งไปยังตัวจัดการคนเดียวกันsticky-fun
. เมื่อเริ่มต้นจุดแลกเปลี่ยน ฟังก์ชันการคำนวณแฮชสำหรับsticky
สมดุลfun
. เช่นเดียวกับ Sticky-Fun มีเพียงคุณเท่านั้นที่สามารถเปลี่ยนเส้นทาง ปฏิเสธ หรือประมวลผลล่วงหน้าเพิ่มเติมได้
กลยุทธ์การกระจายจะถูกตั้งค่าเมื่อมีการเตรียมใช้งานจุดแลกเปลี่ยน
นอกจากการปรับสมดุลแล้ว การส่งข้อความยังช่วยให้คุณแท็กเอนทิตีได้อีกด้วย มาดูประเภทของแท็กในระบบกัน:
- แท็กการเชื่อมต่อ ช่วยให้คุณเข้าใจว่าเหตุการณ์เกิดขึ้นจากความเชื่อมโยงใด ใช้เมื่อกระบวนการควบคุมเชื่อมต่อกับจุดแลกเปลี่ยนเดียวกัน แต่มีคีย์เส้นทางต่างกัน
- ป้ายบริการ ช่วยให้คุณสามารถรวมตัวจัดการเป็นกลุ่มสำหรับบริการเดียว และขยายความสามารถในการกำหนดเส้นทางและการปรับสมดุล สำหรับรูปแบบ req-resp การกำหนดเส้นทางเป็นแบบเส้นตรง เราส่งคำขอไปยังจุดแลกเปลี่ยน จากนั้นจะส่งต่อไปยังบริการ แต่ถ้าเราจำเป็นต้องแบ่งตัวจัดการออกเป็นกลุ่มเชิงตรรกะ การแยกจะเสร็จสิ้นโดยใช้แท็ก เมื่อระบุแท็ก คำขอจะถูกส่งไปยังกลุ่มคอนโทรลเลอร์เฉพาะ
- ขอแท็ก. ช่วยให้คุณแยกแยะระหว่างคำตอบได้ เนื่องจากระบบของเราเป็นแบบอะซิงโครนัส เพื่อประมวลผลการตอบสนองของบริการ เราจึงต้องระบุ RequestTag เมื่อส่งคำขอได้ จากนั้นเราจะสามารถเข้าใจคำตอบที่คำขอมาถึงเรา
ผับย่อย
สำหรับ pub-sub ทุกอย่างจะง่ายกว่าเล็กน้อย เรามีจุดแลกเปลี่ยนในการเผยแพร่ข้อความ จุดแลกเปลี่ยนจะกระจายข้อความระหว่างสมาชิกที่สมัครรับรหัสเส้นทางที่พวกเขาต้องการ (เราสามารถพูดได้ว่าสิ่งนี้คล้ายคลึงกับหัวข้อ)
ความสามารถในการปรับขนาดและความทนทานต่อข้อผิดพลาด
ความสามารถในการปรับขนาดของระบบโดยรวมขึ้นอยู่กับระดับความสามารถในการปรับขนาดของเลเยอร์และส่วนประกอบของระบบ:
- บริการได้รับการปรับขนาดโดยการเพิ่มโหนดเพิ่มเติมให้กับคลัสเตอร์พร้อมตัวจัดการสำหรับบริการนี้ ในระหว่างการดำเนินการทดลอง คุณสามารถเลือกนโยบายการปรับสมดุลที่เหมาะสมที่สุดได้
- โดยทั่วไปแล้ว บริการรับส่งข้อความภายในคลัสเตอร์ที่แยกจากกันจะถูกปรับขนาดโดยการย้ายจุดแลกเปลี่ยนที่มีการโหลดโดยเฉพาะไปยังโหนดคลัสเตอร์ที่แยกจากกัน หรือโดยการเพิ่มกระบวนการพร็อกซีไปยังพื้นที่ที่โหลดโดยเฉพาะของคลัสเตอร์
- ความสามารถในการปรับขนาดของระบบทั้งหมดเป็นลักษณะเฉพาะนั้นขึ้นอยู่กับความยืดหยุ่นของสถาปัตยกรรมและความสามารถในการรวมแต่ละคลัสเตอร์ให้เป็นเอนทิตีเชิงตรรกะทั่วไป
ความสำเร็จของโครงการมักขึ้นอยู่กับความเรียบง่ายและความเร็วในการปรับขนาด การส่งข้อความในเวอร์ชันปัจจุบันจะเติบโตไปพร้อมกับแอปพลิเคชัน แม้ว่าเราจะขาดคลัสเตอร์ของเครื่อง 50-60 เครื่อง เราก็สามารถใช้การรวมศูนย์ได้ ขออภัย หัวข้อของสหพันธ์อยู่นอกเหนือขอบเขตของบทความนี้
การจอง
เมื่อวิเคราะห์การปรับสมดุลโหลด เราได้หารือเกี่ยวกับความซ้ำซ้อนของตัวควบคุมบริการแล้ว อย่างไรก็ตาม จะต้องจองข้อความไว้ด้วย ในกรณีที่โหนดหรือเครื่องขัดข้อง การส่งข้อความควรได้รับการกู้คืนโดยอัตโนมัติและในเวลาที่สั้นที่สุด
ในโปรเจ็กต์ของฉัน ฉันใช้โหนดเพิ่มเติมที่รับภาระในกรณีที่เกิดการล้ม Erlang มีการใช้งานโหมดกระจายมาตรฐานสำหรับแอปพลิเคชัน OTP โหมดกระจายจะทำการกู้คืนในกรณีที่เกิดความล้มเหลวโดยการเปิดแอปพลิเคชันที่ล้มเหลวบนโหนดอื่นที่เปิดใช้งานก่อนหน้านี้ กระบวนการนี้โปร่งใส หลังจากเกิดความล้มเหลว แอปพลิเคชันจะย้ายไปยังโหนดที่เกิดข้อผิดพลาดโดยอัตโนมัติ คุณสามารถอ่านเพิ่มเติมเกี่ยวกับฟังก์ชันนี้ได้
การปฏิบัติ
อย่างน้อยเรามาลองเปรียบเทียบประสิทธิภาพของ rabbitmq กับข้อความที่เรากำหนดเองกันคร่าวๆ กัน
ฉันพบ
ในย่อหน้าที่ 6.14.1.2.1.2.2 เอกสารต้นฉบับแสดงผลลัพธ์ของ RPC CAST:
เราจะไม่ทำการตั้งค่าเพิ่มเติมใดๆ กับเคอร์เนล OS หรือ erlang VM ล่วงหน้า เงื่อนไขในการทดสอบ:
- erl เลือก: +A1 +sbtu
- การทดสอบภายในโหนด erlang เดียวจะดำเนินการบนแล็ปท็อปที่มี i7 รุ่นเก่าในเวอร์ชันมือถือ
- การทดสอบคลัสเตอร์ดำเนินการบนเซิร์ฟเวอร์ที่มีเครือข่าย 10G
- รหัสทำงานในคอนเทนเนอร์นักเทียบท่า เครือข่ายในโหมด NAT
รหัสทดสอบ:
req_resp_bench(_) ->
W = perftest:comprehensive(10000,
fun() ->
messaging:request(?EXCHANGE, default, ping, self()),
receive
#'$msg'{message = pong} -> ok
after 5000 ->
throw(timeout)
end
end
),
true = lists:any(fun(E) -> E >= 30000 end, W),
ok.
สถานการณ์ที่ 1: การทดสอบดำเนินการบนแล็ปท็อปที่ใช้ i7 เวอร์ชันมือถือรุ่นเก่า การทดสอบ การส่งข้อความ และการบริการจะดำเนินการบนโหนดเดียวในคอนเทนเนอร์ Docker เดียว:
Sequential 10000 cycles in ~0 seconds (26987 cycles/s)
Sequential 20000 cycles in ~1 seconds (26915 cycles/s)
Sequential 100000 cycles in ~4 seconds (26957 cycles/s)
Parallel 2 100000 cycles in ~2 seconds (44240 cycles/s)
Parallel 4 100000 cycles in ~2 seconds (53459 cycles/s)
Parallel 10 100000 cycles in ~2 seconds (52283 cycles/s)
Parallel 100 100000 cycles in ~3 seconds (49317 cycles/s)
สถานการณ์ 2: 3 โหนดที่ทำงานบนเครื่องที่แตกต่างกันภายใต้นักเทียบท่า (NAT)
Sequential 10000 cycles in ~1 seconds (8684 cycles/s)
Sequential 20000 cycles in ~2 seconds (8424 cycles/s)
Sequential 100000 cycles in ~12 seconds (8655 cycles/s)
Parallel 2 100000 cycles in ~7 seconds (15160 cycles/s)
Parallel 4 100000 cycles in ~5 seconds (19133 cycles/s)
Parallel 10 100000 cycles in ~4 seconds (24399 cycles/s)
Parallel 100 100000 cycles in ~3 seconds (34517 cycles/s)
ในทุกกรณี การใช้งาน CPU จะต้องไม่เกิน 250%
ผลของการ
ฉันหวังว่าวงจรนี้จะไม่ดูเหมือนเป็นการทิ้งความคิด และประสบการณ์ของฉันจะเป็นประโยชน์อย่างแท้จริงต่อทั้งนักวิจัยของระบบแบบกระจายและผู้ปฏิบัติงานที่อยู่ในช่วงเริ่มต้นของการสร้างสถาปัตยกรรมแบบกระจายสำหรับระบบธุรกิจของพวกเขา และกำลังมองหา Erlang/Elixir ด้วยความสนใจ แต่มีข้อสงสัยว่าคุ้มมั้ย...
Фото
เฉพาะผู้ใช้ที่ลงทะเบียนเท่านั้นที่สามารถเข้าร่วมในการสำรวจได้
ฉันควรกล่าวถึงหัวข้อใดโดยละเอียดมากขึ้นโดยเป็นส่วนหนึ่งของชุดการทดลอง VTrade
-
ทฤษฎี: ตลาด คำสั่งซื้อ และเวลา: DAY, GTD, GTC, IOC, FOK, MOO, MOC, LOO, LOC
-
หนังสือสั่งการ. ทฤษฎีและการปฏิบัติในการจัดทำหนังสือแบบแบ่งกลุ่ม
-
การแสดงภาพการซื้อขาย: เห็บ แท่ง ความละเอียด วิธีจัดเก็บและวิธีติดกาว
-
แบ็คออฟฟิศ การวางแผนและพัฒนา การติดตามพนักงานและการสอบสวนเหตุการณ์
-
เอพีไอ มาดูกันว่าอินเทอร์เฟซใดที่จำเป็นและจะใช้งานอย่างไร
-
การจัดเก็บข้อมูล: PostgreSQL, Timescale, Tarantool ในระบบการซื้อขาย
-
ปฏิกิริยาในระบบการซื้อขาย
-
อื่น. ฉันจะเขียนในความคิดเห็น
ผู้ใช้ 6 คนโหวต ผู้ใช้ 4 รายงดออกเสียง
ที่มา: will.com