API Gateway (REST) ​​+ Microservices ที่ขับเคลื่อนด้วยเหตุการณ์


16

ฉันมี microservices มากมายที่มีฟังก์ชั่นที่ฉันเปิดเผยผ่าน REST API ตามรูปแบบเกตเวย์ API เนื่องจาก microservices เหล่านี้เป็นแอปพลิเคชั่น Spring Boot ฉันจึงใช้ Spring AMQP เพื่อให้เกิดการสื่อสารแบบซิงโครนัสสไตล์ RPC ระหว่างไมโครไซต์เหล่านี้ สิ่งต่าง ๆ ราบรื่นไปแล้ว อย่างไรก็ตามยิ่งฉันอ่านเกี่ยวกับสถาปัตยกรรม microservice ที่ขับเคลื่อนด้วยเหตุการณ์มากขึ้นและดูโครงการเช่น Spring Cloud Stream ยิ่งฉันเชื่อมั่นมากขึ้นว่าฉันอาจทำสิ่งที่ผิดกับ RPC วิธีการแบบซิงโครนัส เพื่อตอบสนองการร้องขอนับร้อยหรือพันต่อวินาทีจากแอปพลิเคชันไคลเอนต์)

ฉันเข้าใจจุดหลังสถาปัตยกรรมที่ขับเคลื่อนด้วยเหตุการณ์ สิ่งที่ฉันไม่ค่อยเข้าใจคือวิธีการใช้รูปแบบดังกล่าวจริงเมื่อนั่งอยู่ข้างหลังแบบจำลอง (REST) ​​ที่คาดว่าจะตอบสนองต่อทุกคำขอ ตัวอย่างเช่นถ้าฉันมีเกตเวย์ API เป็น microservice และอีกบริการหนึ่งที่เก็บและจัดการผู้ใช้ฉันจะสร้างแบบจำลองเช่นGET /users/1ในเหตุการณ์ที่ขับเคลื่อนด้วยเหตุการณ์ได้อย่างหมดจดได้อย่างไร

คำตอบ:


9

พูดตามฉัน:

เหตุการณ์ REST และ asynchronous ไม่ใช่ทางเลือก มันเป็นฉากฉากหลังอย่างสมบูรณ์

คุณสามารถมีหนึ่งหรืออื่น ๆ หรือทั้งสองอย่างหรือไม่ เป็นเครื่องมือที่แตกต่างกันโดยสิ้นเชิงสำหรับโดเมนปัญหาที่แตกต่างกันโดยสิ้นเชิง ในความเป็นจริงอเนกประสงค์สื่อสารตอบสนองการร้องขอเป็นอย่างที่มีความสามารถของการเป็นตรงกันเหตุการณ์ที่ขับเคลื่อนด้วยและใจกว้างความผิด


เป็นตัวอย่างเล็ก ๆ น้อย ๆ โปรโตคอล AMQP ส่งข้อความผ่านการเชื่อมต่อ TCP ใน TCP, ทุกแพ็คเก็ตจะต้องได้รับการยอมรับโดยรับ หากผู้ส่งแพ็คเก็ตไม่ได้รับ ACK สำหรับแพ็คเก็ตนั้นมันจะส่งแพ็กเก็ตนั้นไปเรื่อย ๆ จนกว่าจะเป็น ACK'd หรือจนกว่าเลเยอร์ของแอปพลิเคชันจะ "ยอมแพ้" และยกเลิกการเชื่อมต่อ นี่เป็นรูปแบบคำขอการตอบสนองที่ไม่ผิดพลาดอย่างเห็นได้ชัดเพราะ "คำขอส่งแพ็คเก็ต" ทุกคนต้องมี "ตอบรับการตอบรับแพ็กเก็ต" ที่แนบมาด้วยและความล้มเหลวในการตอบสนองผลลัพธ์ในการเชื่อมต่อทั้งหมดล้มเหลว แต่ AMQP ซึ่งเป็นโปรโตคอลที่ได้มาตรฐานและนำมาใช้กันอย่างแพร่หลายสำหรับการรับส่งข้อความข้อความผิดพลาดแบบอะซิงโครนัสมีการสื่อสารผ่าน TCP! สิ่งที่ช่วยให้?

แนวคิดหลักในการเล่นที่นี่เป็นที่ที่ปรับขนาดได้อย่างอิสระคู่ส่งข้อความความผิดพลาดจะถูกกำหนดโดยข้อความสิ่งที่คุณส่งไม่ว่าคุณจะส่งพวกเขา ในคำอื่น ๆcoupling หลวมถูกกำหนดไว้ที่ชั้นสมัคร

ลองดูที่ทั้งสองฝ่ายสื่อสารกันโดยตรงกับ RESTful HTTP หรือทางอ้อมกับนายหน้าข้อความ AMQP สมมติว่าปาร์ตี้ A ประสงค์จะอัปโหลดภาพ JPEG ไปยังปาร์ตี้ B ที่จะลับคมบีบอัดหรือปรับปรุงภาพ ปาร์ตี้ A ไม่ต้องการรูปภาพที่ถูกประมวลผลในทันที แต่จะต้องมีการอ้างอิงเพื่อใช้และเรียกคืนในอนาคต นี่เป็นวิธีหนึ่งที่อาจเป็นไปได้ในส่วนที่เหลือ:

  • ปาร์ตี้ A ส่งPOSTข้อความร้องขอHTTP ไปยังปาร์ตี้ B ด้วยContent-Type: image/jpeg
  • ปาร์ตี้ B ประมวลภาพ (เป็นเวลานานถ้ามันใหญ่) ในขณะที่ปาร์ตี้ A รออยู่อาจทำสิ่งอื่น ๆ
  • ปาร์ตี้ B ส่ง201 Createdข้อความตอบกลับHTTP ไปยังปาร์ตี้ A พร้อมContent-Location: <url>ส่วนหัวที่เชื่อมโยงไปยังอิมเมจที่ประมวลผล
  • ปาร์ตี้ A พิจารณางานที่ทำเนื่องจากตอนนี้มีการอ้างอิงถึงอิมเมจที่ประมวลผลแล้ว
  • ในอนาคตเมื่อปาร์ตี้ A ต้องการภาพที่ดำเนินการแล้วจะได้รับโดยใช้ลิงก์จากContent-Locationส่วนหัวก่อนหน้า

201 Createdรหัสการตอบสนองลูกค้าบอกว่าไม่เพียง แต่ได้รับคำขอของพวกเขาประสบความสำเร็จก็ยังสร้างทรัพยากรใหม่ ในการตอบกลับ 201 Content-Locationส่วนหัวเป็นลิงก์ไปยังทรัพยากรที่สร้างขึ้น ระบุไว้ใน RFC 7231 ส่วน 6.3.2 และ 3.1.4.2

ตอนนี้มาดูกันว่าการโต้ตอบนี้ทำงานอย่างไรกับโปรโตคอล RPC ที่ตั้งอยู่บน AMQP:

  • ปาร์ตี้ A ส่งนายหน้าข้อความ AMQP (เรียกว่า Messenger) ข้อความที่มีรูปภาพและคำแนะนำในการจัดเส้นทางไปยังปาร์ตี้ B สำหรับการประมวลผลจากนั้นตอบกลับไปยังปาร์ตี้ A พร้อมที่อยู่บางประเภทสำหรับรูปภาพ
  • ปาร์ตี้ A กำลังรออาจทำสิ่งอื่น ๆ
  • Messenger ส่งข้อความต้นฉบับของ Party A ไปยัง Party B
  • ปาร์ตี้ B ประมวลผลข้อความ
  • ปาร์ตี้ B ส่งข้อความที่มีที่อยู่สำหรับประมวลภาพและคำแนะนำเพื่อส่งข้อความไปยังปาร์ตี้ A
  • Messenger ส่งปาร์ตี้ A ข้อความจากปาร์ตี้ B ที่มีที่อยู่รูปภาพที่ถูกประมวลผล
  • ปาร์ตี้ A พิจารณางานที่ทำเนื่องจากตอนนี้มีการอ้างอิงถึงอิมเมจที่ประมวลผลแล้ว
  • บางครั้งในอนาคตเมื่อ Party A ต้องการภาพมันจะดึงภาพโดยใช้ที่อยู่ (อาจส่งข้อความถึงบุคคลอื่น ๆ )

คุณเห็นปัญหาที่นี่หรือไม่ ในทั้งสองกรณีพรรคไม่สามารถได้รับที่อยู่ภาพจนกระทั่งหลังจากที่พรรค B ประมวลผลภาพ แต่ปาร์ตี้ A ไม่ต้องการรูปภาพในทันทีและโดยสิทธิ์ทั้งหมดก็ไม่ต้องสนใจน้อยหากการประมวลผลเสร็จสิ้น!

เราสามารถแก้ไขปัญหานี้ได้อย่างง่ายดายในกรณี AMQP โดยให้ฝ่าย B บอกว่า B ยอมรับภาพสำหรับการประมวลผลโดยให้ที่อยู่ A สำหรับภาพที่จะประมวลผลหลังจากเสร็จสิ้น จากนั้นปาร์ตี้ B สามารถส่งข้อความในอนาคตเพื่อระบุว่าการประมวลผลภาพเสร็จสิ้นแล้ว AMQP ส่งข้อความเพื่อช่วยเหลือ!

ยกเว้นสิ่งที่เดาได้: คุณสามารถทำสิ่งเดียวกันกับ RESTได้ ในตัวอย่าง AMQP เราเปลี่ยนข้อความ "นี่คือรูปภาพที่ประมวลผล" เป็น "รูปภาพกำลังประมวลผลคุณสามารถรับได้ในภายหลัง" ในการทำเช่นนั้นใน RESTful HTTP เราจะใช้202 AcceptedรหัสและContent-Locationอีกครั้ง:

  • ปาร์ตี้ A ส่งPOSTข้อความHTTP ไปยังปาร์ตี้ B ด้วยContent-Type: image/jpeg
  • ฝ่าย B ส่งการ202 Acceptedตอบกลับทันทีซึ่งมีเนื้อหา "การดำเนินการแบบอะซิงโครนัส" ซึ่งจะอธิบายว่าการประมวลผลนั้นเสร็จสิ้นแล้วหรือไม่ รวมทั้งยังเป็นContent-Location: <link>ส่วนหัวซึ่งในการ202 Acceptedตอบสนองเป็นลิงค์ไปยังทรัพยากรที่แสดงโดยสิ่งที่ร่างกายตอบสนองคืออะไร ในกรณีนี้นั่นหมายความว่ามันเป็นลิงค์ไปสู่การทำงานแบบอะซิงโครนัสของเรา!
  • ปาร์ตี้ A พิจารณางานที่ทำเนื่องจากตอนนี้มีการอ้างอิงถึงอิมเมจที่ประมวลผลแล้ว
  • บางครั้งในอนาคตเมื่อปาร์ตี้ A ต้องการอิมเมจที่ประมวลผลอันดับแรกจะรับรีซอร์สการดำเนินการ async ที่เชื่อมโยงกับContent-Locationส่วนหัวเพื่อพิจารณาว่าการประมวลผลเสร็จสิ้นหรือไม่ ถ้าเป็นเช่นนั้น Party A จะใช้ลิงก์ในการดำเนินการ async เพื่อรับอิมเมจที่ประมวลผล

ข้อแตกต่างเพียงอย่างเดียวคือในรุ่น AMQP ปาร์ตี้ B จะบอกฝ่าย A เมื่อทำการประมวลผลภาพ แต่ในรุ่น REST Party A จะตรวจสอบว่าการประมวลผลเสร็จสิ้นก่อนที่จะต้องการภาพหรือไม่ วิธีการเหล่านี้สามารถปรับขนาดได้อย่างเท่าเทียมกัน เมื่อระบบมีขนาดใหญ่ขึ้นจำนวนข้อความที่ส่งทั้งใน async AMQP และกลยุทธ์ async REST จะเพิ่มขึ้นตามความซับซ้อนของ asymptotic ข้อแตกต่างเพียงอย่างเดียวคือไคลเอนต์กำลังส่งข้อความพิเศษแทนเซิร์ฟเวอร์

แต่วิธีที่เหลือมีเทคนิคอีกไม่กี่ขึ้นที่แขน: การค้นพบแบบไดนามิกและการเจรจาต่อรองโปรโตคอล พิจารณาว่าการโต้ตอบและการซิงค์ REST ของ async เริ่มต้นอย่างไร ปาร์ตี้ A ส่งคำขอเดียวกันไปยังปาร์ตี้ B โดยมีความแตกต่างเพียงอย่างเดียวคือข้อความแห่งความสำเร็จที่ Party B ตอบกลับมา จะเกิดอะไรขึ้นถ้าพรรค A ต้องการเลือกว่าการประมวลผลภาพแบบซิงโครนัสหรือแบบอะซิงโครนัส? จะเกิดอะไรขึ้นถ้า Party A ไม่รู้ว่า Party B นั้นมีความสามารถในการประมวลผลแบบซิงค์หรือไม่?

HTTP จริง ๆ แล้วมีโปรโตคอลมาตรฐานสำหรับสิ่งนี้อยู่แล้ว! มันเรียกว่าการตั้งค่า HTTP โดยเฉพาะการrespond-asyncตั้งค่าของ RFC 7240 มาตรา 4.1 หากปาร์ตี้ A ต้องการการตอบสนองแบบอะซิงโครนัสก็จะรวมPrefer: respond-asyncส่วนหัวด้วยคำขอ POST เริ่มต้น หากพรรค B ตัดสินใจที่จะให้เกียรติคำขอนี้ก็จะส่งกลับการตอบสนองที่มี202 Accepted Preference-Applied: respond-asyncมิฉะนั้น Party B จะละเว้นPreferส่วนหัวและส่งกลับ201 Createdตามปกติ

สิ่งนี้ทำให้ฝ่าย A สามารถเจรจากับเซิร์ฟเวอร์ปรับตัวเข้ากับการปรับใช้การประมวลผลภาพแบบไดนามิกได้ นอกจากนี้การใช้ลิงก์ที่ชัดเจนหมายถึงปาร์ตี้ A ไม่จำเป็นต้องรู้เกี่ยวกับบุคคลอื่นนอกเหนือจาก B: ไม่มีนายหน้าข้อความ AMQP และไม่มีพรรคลึกลับ C ที่รู้วิธีเปลี่ยนที่อยู่ของรูปภาพให้เป็นข้อมูลภาพไม่ใช่ B-Async ที่สอง ปาร์ตี้หากต้องการคำขอทั้งแบบซิงโครนัสและแบบอะซิงโครนัส ฯลฯ เพียงอธิบายสิ่งที่ต้องการสิ่งที่ต้องการเลือกแล้วตอบสนองต่อรหัสสถานะเนื้อหาการตอบสนองและลิงก์ เพิ่มในCache-Controlส่วนหัวสำหรับคำแนะนำที่ชัดเจนเกี่ยวกับเวลาที่จะเก็บสำเนาของข้อมูลในเครื่องและในขณะนี้เซิร์ฟเวอร์สามารถเจรจากับลูกค้าซึ่งทรัพยากรของลูกค้าอาจเก็บสำเนาของเครื่อง (หรือออฟไลน์!) นี่คือวิธีที่คุณสร้างไมโครไซต์ที่รองรับข้อผิดพลาดแบบหลวม ๆ ใน REST


1

ไม่ว่าคุณจะต้องขับเคลื่อนเหตุการณ์ล้วนหรือไม่ก็ตามขึ้นอยู่กับสถานการณ์เฉพาะของคุณ สมมติว่าคุณต้องการจริงๆแล้วคุณสามารถแก้ปัญหาโดย:

จัดเก็บสำเนาข้อมูลแบบอ่านอย่างเดียวในพื้นที่โดยฟังเหตุการณ์ที่แตกต่างและเก็บข้อมูลไว้ในส่วนของข้อมูล แม้ว่าสิ่งนี้จะช่วยให้คุณอ่านข้อมูลนั้นได้อย่างรวดเร็ว แต่จัดเก็บในรูปแบบที่เหมาะสมกับแอปพลิเคชันนั้น ๆ แต่ก็หมายถึงว่าข้อมูลของคุณจะสอดคล้องกันทั่วทั้งบริการ

ในการสร้างแบบจำลองGET /users/1ด้วยวิธีนี้ผู้ใช้อาจรับฟังUserCreatedและUserUpdatedจัดกิจกรรมและจัดเก็บชุดย่อยที่มีประโยชน์ของข้อมูลผู้ใช้ในบริการ เมื่อคุณต้องการรับข้อมูลผู้ใช้คุณสามารถสอบถามที่เก็บข้อมูลภายในเครื่องของคุณได้

ลองสมมติว่าบริการที่เปิดเผย/users/ปลายทางไม่ได้เผยแพร่เหตุการณ์ใด ๆ ในอินสแตนซ์นี้คุณสามารถบรรลุสิ่งที่คล้ายกันได้โดยเพียงแค่แคชการตอบสนองต่อการร้องขอ HTTP ที่คุณทำดังนั้นจึงไม่จำเป็นต้องทำการร้องขอ HTTP มากกว่า 1 รายการต่อผู้ใช้ภายในระยะเวลาหนึ่ง


ฉันเข้าใจ. แต่สิ่งที่เกี่ยวกับการจัดการข้อผิดพลาด (และการรายงาน) กับลูกค้าในสถานการณ์นี้?
โทนี่อี. สตาร์ค

ฉันหมายถึงฉันจะรายงานกลับไปยังข้อผิดพลาดของลูกค้า REST ที่เกิดขึ้นเมื่อจัดการUserCreatedเหตุการณ์ได้อย่างไร (เช่นชื่อผู้ใช้ซ้ำหรืออีเมลหรือฐานข้อมูลดับ)
Tony E. Stark

ขึ้นอยู่กับว่าคุณกำลังทำอะไรอยู่ หากคุณอยู่ในระบบผู้ใช้คุณสามารถทำการตรวจสอบทั้งหมดของคุณเขียนไปยังที่เก็บข้อมูลที่นั่นแล้วเผยแพร่เหตุการณ์ มิฉะนั้นฉันเห็นว่าเป็นที่ยอมรับอย่างสมบูรณ์ในการดำเนินการตามคำขอ HTTP มาตรฐานไปยัง/users/ปลายทางและอนุญาตให้ระบบนั้นเผยแพร่กิจกรรมหากประสบความสำเร็จและตอบสนองต่อคำขอกับหน่วยงานใหม่
Andy Hunt

0

ด้วยระบบที่มีแหล่งที่มาเหตุการณ์ลักษณะแบบอะซิงโครนัสมักจะเข้ามาเล่นเมื่อบางสิ่งที่แสดงถึงสถานะอาจเป็นฐานข้อมูลหรือมุมมองรวมของข้อมูลบางอย่างเปลี่ยนไป การใช้ตัวอย่างของคุณการเรียกใช้ GET / api / users สามารถส่งคืนการตอบสนองจากบริการที่มีการแสดงรายการผู้ใช้ในระบบล่าสุด ในสถานการณ์อื่นคำขอไปยัง GET / api / ผู้ใช้อาจทำให้บริการใช้กระแสข้อมูลของเหตุการณ์นับตั้งแต่สแนปชอตสุดท้ายของผู้ใช้เพื่อสร้างสแนปชอตอื่นและเพียงส่งคืนผลลัพธ์ ระบบที่ขับเคลื่อนด้วยเหตุการณ์ไม่จำเป็นต้องเป็นแบบอะซิงโครนัสอย่างสิ้นเชิงจากคำขอไปยังการตอบกลับ แต่มีแนวโน้มที่จะอยู่ในระดับที่บริการจำเป็นต้องโต้ตอบกับบริการอื่น ๆ บ่อยครั้งที่ไม่เหมาะสมที่จะส่งคืนคำขอ GET แบบอะซิงโครนัสและคุณสามารถส่งคืนการตอบกลับของบริการได้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.