รู้เบื้องต้นเกี่ยวกับ Python Microservices ด้วย Nameko
เผยแพร่แล้ว: 2022-03-11บทนำ
รูปแบบสถาปัตยกรรมไมโครเซอร์วิส เป็นรูปแบบสถาปัตยกรรมที่ได้รับความนิยมเพิ่มขึ้น เนื่องจากมีความยืดหยุ่นและความยืดหยุ่น เมื่อรวมกับเทคโนโลยีอย่าง Kubernetes แล้ว การเริ่มต้นแอปพลิเคชันโดยใช้สถาปัตยกรรม Microservices ก็ง่ายขึ้นอย่างที่ไม่เคยมีมาก่อน
ตามบทความคลาสสิกจากบล็อกของ Martin Fowler รูปแบบสถาปัตยกรรม Microservices สามารถสรุปได้ดังนี้:
กล่าวโดยสรุป รูปแบบสถาปัตยกรรมไมโครเซอร์วิสเป็นแนวทางในการพัฒนาแอปพลิเคชันเดียวให้เป็นชุดบริการขนาดเล็ก โดยแต่ละรายการจะทำงานในกระบวนการของตนเองและสื่อสารกับกลไกที่มีน้ำหนักเบา ซึ่งมักจะเป็น API ทรัพยากร HTTP บริการเหล่านี้สร้างขึ้นจากความสามารถทางธุรกิจและสามารถปรับใช้ได้อย่างอิสระโดยเครื่องจักรการปรับใช้อัตโนมัติเต็มรูปแบบ
กล่าวอีกนัยหนึ่ง แอปพลิเคชันที่ตามสถาปัตยกรรมไมโครเซอร์วิสประกอบด้วยบริการอิสระและไดนามิกหลายบริการที่สื่อสารระหว่างกันโดยใช้โปรโตคอลการสื่อสาร เป็นเรื่องปกติที่จะใช้ HTTP (และ REST) แต่อย่างที่เราเห็น เราสามารถใช้โปรโตคอลการสื่อสารประเภทอื่นๆ เช่น RPC (Remote Procedure Call) ผ่าน AMQP (Advanced Message Queuing Protocol)
รูปแบบไมโครเซอร์วิสสามารถคิดได้ว่าเป็นกรณีเฉพาะของ SOA (สถาปัตยกรรมเชิงบริการ) อย่างไรก็ตาม ใน SOA เป็นเรื่องปกติที่จะใช้ ESB (รถบัสบริการสำหรับองค์กร) เพื่อจัดการการสื่อสารระหว่างบริการต่างๆ ESB มักจะมีความซับซ้อนสูงและมีฟังก์ชันสำหรับการกำหนดเส้นทางข้อความที่ซับซ้อนและการประยุกต์ใช้กฎเกณฑ์ทางธุรกิจ ในไมโครเซอร์วิส เป็นเรื่องปกติที่จะใช้แนวทางอื่น: "จุดปลายอัจฉริยะและท่อใบ้" หมายความว่าบริการควรประกอบด้วยตรรกะทางธุรกิจและความซับซ้อนทั้งหมด (การทำงานร่วมกันสูง) แต่การเชื่อมต่อระหว่างบริการควรจะง่ายเพียง เป็นไปได้ (การแยกส่วนสูง) หมายความว่าบริการไม่จำเป็นต้องรู้ว่าบริการอื่นใดจะสื่อสารกับบริการดังกล่าว นี่เป็นการแยกข้อกังวลที่ใช้ในระดับสถาปัตยกรรม
อีกแง่มุมหนึ่งของไมโครเซอร์วิสคือไม่มีการบังคับใช้ว่าเทคโนโลยีใดควรใช้ในแต่ละบริการ คุณควรจะสามารถเขียนบริการด้วยชุดซอฟต์แวร์ใดๆ ที่สามารถสื่อสารกับบริการอื่นๆ ได้ แต่ละบริการมีการจัดการวงจรชีวิตของตนเองเช่นกัน ทั้งหมดนี้หมายความว่าในบริษัท มีความเป็นไปได้ที่จะมีทีมทำงานแยกจากกัน ด้วยเทคโนโลยีที่แตกต่างกันและแม้แต่วิธีการจัดการ แต่ละทีมจะคำนึงถึงความสามารถทางธุรกิจ ช่วยสร้างองค์กรที่คล่องตัวมากขึ้น
Python Microservices
เมื่อคำนึงถึงแนวคิดเหล่านี้แล้ว ในบทความนี้เราจะเน้นที่การสร้างการพิสูจน์แนวคิดของแอปพลิเคชัน Microservices โดยใช้ Python เราจะใช้ Nameko ซึ่งเป็นเฟรมเวิร์กไมโครเซอร์วิสของ Python มี RPC ผ่าน AMQP ในตัว ช่วยให้คุณสื่อสารระหว่างบริการต่างๆ ของคุณได้อย่างง่ายดาย นอกจากนี้ยังมีอินเทอร์เฟซที่เรียบง่ายสำหรับการสืบค้น HTTP ซึ่งเราจะใช้ในบทช่วยสอนนี้ อย่างไรก็ตาม สำหรับการเขียน Microservices ที่แสดงจุดปลาย HTTP ขอแนะนำให้คุณใช้เฟรมเวิร์กอื่น เช่น Flask ในการเรียกเมธอด Nameko ผ่าน RPC โดยใช้ Flask คุณสามารถใช้ flask_nameko ซึ่งเป็นเสื้อคลุมที่สร้างขึ้นสำหรับการทำงานร่วมกันของ Flask กับ Nameko เท่านั้น
การตั้งค่าสภาพแวดล้อมพื้นฐาน
เริ่มต้นด้วยการเรียกใช้ตัวอย่างที่ง่ายที่สุด ดึงมาจากเว็บไซต์ Nameko และขยายเพื่อจุดประสงค์ของเรา ขั้นแรก คุณจะต้องติดตั้ง Docker เราจะใช้ Python 3 ในตัวอย่าง ดังนั้นตรวจสอบให้แน่ใจว่าคุณได้ติดตั้งมันไว้ด้วย จากนั้นสร้าง python virtualenv และรัน $ pip install nameko
ในการรัน Nameko เราจำเป็นต้องมีนายหน้าข้อความ RabbitMQ จะรับผิดชอบในการสื่อสารระหว่างบริการ Nameko ของเรา ไม่ต้องกังวลเพราะคุณไม่จำเป็นต้องติดตั้งการพึ่งพาเครื่องของคุณอีก ด้วย Docker เราสามารถดาวน์โหลดอิมเมจที่กำหนดไว้ล่วงหน้า รันมัน และเมื่อเราทำเสร็จแล้ว ให้หยุดคอนเทนเนอร์ ไม่มี daemons, apt-get
หรือ dnf install
เริ่มคอนเทนเนอร์ RabbitMQ โดยเรียกใช้ $ docker run -p 5672:5672 --hostname nameko-rabbitmq rabbitmq:3
(คุณอาจต้องใช้ sudo เพื่อทำเช่นนั้น) สิ่งนี้จะเริ่มต้นคอนเทนเนอร์ Docker โดยใช้ RabbitMQ เวอร์ชันล่าสุด 3 และเปิดเผยผ่านพอร์ตเริ่มต้น 5672
สวัสดีชาวโลกด้วยไมโครเซอร์วิส
ไปข้างหน้าและสร้างไฟล์ชื่อ hello.py
ด้วยเนื้อหาต่อไปนี้:
from nameko.rpc import rpc class GreetingService: name = "greeting_service" @rpc def hello(self, name): return "Hello, {}!".format(name)
บริการ Nameko เป็นคลาส คลาสเหล่านี้เปิดเผยจุดเข้าใช้งานซึ่งถูกนำไปใช้เป็นส่วนขยาย ส่วนขยายในตัวรวมถึงความสามารถในการสร้างจุดเริ่มต้นที่แสดงวิธีการ RPC, ตัวฟังเหตุการณ์, ปลายทาง HTTP หรือตัวจับเวลา นอกจากนี้ยังมีส่วนขยายชุมชนที่สามารถใช้เพื่อโต้ตอบกับฐานข้อมูล PostgreSQL, Redis ฯลฯ ... เป็นไปได้ที่จะเขียนส่วนขยายของคุณเอง
ไปข้างหน้าและเรียกใช้ตัวอย่างของเรา หากคุณให้ RabbitMQ ทำงานบนพอร์ตเริ่มต้น เพียงแค่เรียกใช้ $ nameko run hello
จะพบ RabbitMQ และเชื่อมต่อโดยอัตโนมัติ จากนั้น เพื่อทดสอบบริการของเรา ให้รัน $ nameko shell
ในเทอร์มินัลอื่น สิ่งนี้จะสร้างเชลล์แบบโต้ตอบซึ่งจะเชื่อมต่อกับอินสแตนซ์ RabbitMQ เดียวกันนั้น สิ่งที่ยอดเยี่ยมคือ การใช้ RPC ผ่าน AMQP ทำให้ Nameko ดำเนินการค้นหาบริการโดยอัตโนมัติ เมื่อเรียกใช้เมธอด RPC nameko จะพยายามค้นหาบริการที่ทำงานอยู่ที่เกี่ยวข้อง
เมื่อรันเชลล์ Nameko คุณจะได้รับอ็อบเจ็กต์พิเศษที่เรียกว่า n
เพิ่มในเนมสเปซ ออบเจ็กต์นี้อนุญาตให้ส่งเหตุการณ์และทำการเรียก RPC หากต้องการโทร RPC ไปที่บริการของเรา ให้เรียกใช้:
> >> n.rpc.greetingservice.hello(name='world') 'Hello, world!'
โทรพร้อมกัน
คลาสบริการเหล่านี้สร้างอินสแตนซ์ในขณะที่โทรออกและทำลายหลังจากการโทรเสร็จสิ้น ดังนั้นควรเป็นสถานะไร้สัญชาติโดยเนื้อแท้ หมายความว่าคุณไม่ควรพยายามรักษาสถานะใดๆ ในวัตถุหรือคลาสระหว่างการเรียก นี่หมายความว่าบริการจะต้องไร้สัญชาติ ด้วยสมมติฐานที่ว่าบริการทั้งหมดเป็นแบบไร้สัญชาติ Nameko สามารถใช้ประโยชน์จากการทำงานพร้อมกันได้โดยใช้ Greenthreads ของเหตุการณ์ บริการที่สร้างอินสแตนซ์นี้เรียกว่า "ผู้ปฏิบัติงาน" และสามารถกำหนดจำนวนผู้ปฏิบัติงานที่ทำงานพร้อมกันได้สูงสุดที่กำหนดค่าไว้
ในการตรวจสอบความถูกต้องของการทำงานพร้อมกันของ Nameko ให้แก้ไขซอร์สโค้ดโดยเพิ่มโหมดสลีปในการเรียกโพรซีเดอร์ก่อนที่จะส่งคืนการตอบกลับ:
from time import sleep from nameko.rpc import rpc class GreetingService: name = "greeting_service" @rpc def hello(self, name): sleep(5) return "Hello, {}!".format(name)
เรากำลังใช้ sleep
สลีปจากโมดูล time
ซึ่งไม่ได้เปิดใช้งานอะซิงโครนัส อย่างไรก็ตาม เมื่อเรียกใช้บริการของเราโดยใช้ nameko run
มันจะทำการแพตช์ทริกเกอร์โดยอัตโนมัติจากการบล็อกการโทร เช่น sleep(5)
ขณะนี้คาดว่าเวลาตอบสนองจากการเรียกโพรซีเดอร์ควรใช้เวลาประมาณ 5 วินาที อย่างไรก็ตาม พฤติกรรมจากตัวอย่างต่อไปนี้จะเป็นอย่างไร เมื่อเราเรียกใช้จากเชลล์ nameko
res = [] for i in range(5): hello_res = n.rpc.greeting_service.hello.call_async(name=str(i)) res.append(hello_res) for hello_res in res: print(hello_res.result())
Nameko จัดเตรียมเมธอด call_async
ที่ไม่ปิดกั้นสำหรับจุดเข้าใช้งาน RPC แต่ละจุด โดยจะส่งคืนอ็อบเจ็กต์การตอบกลับพร็อกซีที่สามารถสอบถามผลลัพธ์ได้ เมธอดของ result
เมื่อเรียกใช้บนพร็อกซีการตอบกลับ จะถูกบล็อกจนกว่าจะมีการตอบกลับ
ตามที่คาดไว้ ตัวอย่างนี้รันในเวลาเพียงประมาณห้าวินาที ผู้ปฏิบัติงานแต่ละคนจะถูกบล็อกระหว่างรอการเรียก sleep
เครื่องให้เสร็จสิ้น แต่การดำเนินการนี้ไม่ได้หยุดให้พนักงานคนอื่นเริ่มทำงาน แทนที่การเรียกส sleep
ปนี้ด้วยการบล็อกการเรียกฐานข้อมูล I/O ที่เป็นประโยชน์ และคุณได้รับบริการพร้อมกันที่รวดเร็วมาก
ตามที่อธิบายไว้ก่อนหน้านี้ Nameko จะสร้างผู้ปฏิบัติงานเมื่อมีการเรียกเมธอด จำนวนผู้ปฏิบัติงานสูงสุดสามารถกำหนดค่าได้ โดยค่าเริ่มต้น ตัวเลขนั้นถูกตั้งค่าเป็น 10 คุณสามารถทดสอบการเปลี่ยน range(5)
ในตัวอย่างด้านบนเป็น ตัวอย่างเช่น range(20) วิธีนี้จะเรียกวิธีการ hello
20 ครั้ง ซึ่งขณะนี้ควรใช้เวลาสิบวินาทีในการเรียกใช้:
> >> res = [] > >> for i in range(20): ... hello_res = n.rpc.greeting_service.hello.call_async(name=str(i)) ... res.append(hello_res) > >> for hellores in res: ... print(hello_res.result()) Hello, 0! Hello, 1! Hello, 2! Hello, 3! Hello, 4! Hello, 5! Hello, 6! Hello, 7! Hello, 8! Hello, 9! Hello, 10! Hello, 11! Hello, 12! Hello, 13! Hello, 14! Hello, 15! Hello, 16! Hello, 17! Hello, 18! Hello, 19!
สมมติว่าคุณได้รับผู้ใช้พร้อมกันมากเกินไป (มากกว่า 10 คน) ที่เรียกวิธีการ hello
นั้น ผู้ใช้บางคนจะรอการตอบกลับนานกว่าที่คาดไว้ห้าวินาที ทางออกหนึ่งคือการเพิ่มจำนวนงานโดยแทนที่การตั้งค่าเริ่มต้นโดยใช้ไฟล์ปรับแต่ง ตัวอย่างเช่น อย่างไรก็ตาม หากเซิร์ฟเวอร์ของคุณมีขีดจำกัดกับผู้ปฏิบัติงานทั้งสิบคนแล้ว เนื่องจากวิธีการเรียกอาศัยการสืบค้นฐานข้อมูลจำนวนมาก การเพิ่มจำนวนผู้ปฏิบัติงานอาจทำให้เวลาตอบสนองเพิ่มขึ้นอีก
ปรับขนาดบริการของเรา
ทางออกที่ดีกว่าคือการใช้ความสามารถของ Nameko Microservices จนถึงขณะนี้ เราใช้เซิร์ฟเวอร์เพียงเครื่องเดียว (คอมพิวเตอร์ของคุณ) ใช้งาน RabbitMQ หนึ่งอินสแตนซ์ และบริการหนึ่งอินสแตนซ์ ในสภาพแวดล้อมที่ใช้งานจริง คุณจะต้องเพิ่มจำนวนโหนดที่เรียกใช้บริการซึ่งได้รับการเรียกมากเกินไปโดยพลการ คุณยังสามารถสร้างคลัสเตอร์ RabbitMQ ได้หากต้องการให้นายหน้าข้อความของคุณมีความน่าเชื่อถือมากขึ้น
ในการจำลองการปรับขนาดบริการ เราสามารถเปิดเทอร์มินัลอื่นและเรียกใช้บริการเหมือนเมื่อก่อน โดยใช้ $ nameko run hello
สิ่งนี้จะเริ่มต้นอินสแตนซ์บริการอื่นที่มีศักยภาพในการรันผู้ปฏิบัติงานเพิ่มอีกสิบคน ตอนนี้ ให้ลองเรียกใช้ข้อมูลโค้ดนั้นอีกครั้งด้วย range(20)
ตอนนี้ควรใช้เวลาห้าวินาทีอีกครั้งในการเรียกใช้ เมื่อมีการเรียกใช้อินสแตนซ์บริการมากกว่าหนึ่งรายการ Nameko จะทำการปัดเศษคำขอ RPC ระหว่างอินสแตนซ์ที่มีอยู่
Nameko สร้างขึ้นเพื่อจัดการกับเมธอดเหล่านั้นที่เรียกในคลัสเตอร์อย่างแข็งแกร่ง หากต้องการทดสอบ ให้ลองเรียกใช้ snipped และก่อนที่จะเสร็จสิ้น ให้ไปที่เทอร์มินัลที่ใช้บริการ Nameko แล้วกด Ctrl+C
สองครั้ง สิ่งนี้จะปิดโฮสต์โดยไม่ต้องรอให้คนงานทำงานให้เสร็จ Nameko จะจัดสรรการโทรใหม่ไปยังอินสแตนซ์บริการอื่นที่มีอยู่
ในทางปฏิบัติ คุณจะใช้ Docker เพื่อบรรจุบริการของคุณ ดังที่เราจะทำในภายหลัง และเครื่องมือประสาน เช่น Kubernetes เพื่อจัดการโหนดของคุณที่ใช้บริการและการพึ่งพาอื่นๆ เช่น นายหน้าข้อความ หากทำอย่างถูกต้อง ด้วย Kubernetes คุณจะแปลงโฉมแอปพลิเคชันของคุณอย่างมีประสิทธิภาพในระบบแบบกระจายที่มีประสิทธิภาพ ปราศจากจุดพีคที่คาดไม่ถึง นอกจากนี้ Kubernetes ยังอนุญาตให้ปรับใช้เป็นศูนย์เวลาหยุดทำงาน ดังนั้น การปรับใช้บริการเวอร์ชันใหม่จะไม่ส่งผลต่อความพร้อมใช้งานของระบบของคุณ

การสร้างบริการโดยคำนึงถึงความเข้ากันได้แบบย้อนหลังเป็นสิ่งสำคัญ เนื่องจากในสภาพแวดล้อมที่ใช้งานจริง บริการเดียวกันหลายเวอร์ชันสามารถทำงานพร้อมกันได้ โดยเฉพาะอย่างยิ่งในระหว่างการปรับใช้ หากคุณใช้ Kubernetes ในระหว่างการปรับใช้ มันจะฆ่าคอนเทนเนอร์เวอร์ชันเก่าทั้งหมดเมื่อมีการเรียกใช้คอนเทนเนอร์ใหม่เพียงพอเท่านั้น
สำหรับ Nameko การมีบริการเดียวกันหลายเวอร์ชันที่ทำงานพร้อมกันไม่ใช่ปัญหา เนื่องจากจะกระจายการโทรแบบวนซ้ำ การโทรจึงอาจผ่านเวอร์ชันเก่าหรือใหม่ ในการทดสอบนั้น ให้เก็บเทอร์มินัลไว้หนึ่งเครื่องกับบริการของเราที่รันเวอร์ชันเก่า และแก้ไขโมดูลบริการให้มีลักษณะดังนี้:
from time import sleep from nameko.rpc import rpc class GreetingService: name = "greeting_service" @rpc def hello(self, name): sleep(5) return "Hello, {}! (version 2)".format(name)
หากคุณเรียกใช้บริการนั้นจากเทอร์มินัลอื่น คุณจะได้รับทั้งสองเวอร์ชันทำงานพร้อมกัน ตอนนี้ เรียกใช้ข้อมูลโค้ดการทดสอบของเราอีกครั้ง แล้วคุณจะเห็นทั้งสองเวอร์ชันแสดงขึ้น:
> >> res = [] > >> for i in range(5): ... hello_res = n.rpc.greeting_service.hello.call_async(name=str(i)) ... res.append(hello_res) > >> for hellores in res: ... print(hello_res.result()) Hello, 0! Hello, 1! (version 2) Hello, 2! Hello, 3! (version 2) Hello, 4!
การทำงานกับหลายอินสแตนซ์
ตอนนี้เรารู้วิธีทำงานกับ Nameko อย่างมีประสิทธิภาพแล้ว และวิธีปรับขนาดทำงานอย่างไร ต่อไปเราจะก้าวไปอีกขั้นและใช้เครื่องมือเพิ่มเติมจากระบบนิเวศของ Docker: docker-compose สิ่งนี้จะได้ผลหากคุณกำลังปรับใช้กับเซิร์ฟเวอร์เดียว ซึ่งไม่เหมาะอย่างแน่นอน เนื่องจากคุณจะไม่ใช้ประโยชน์จากข้อดีมากมายของสถาปัตยกรรมไมโครเซอร์วิส อีกครั้ง หากคุณต้องการมีโครงสร้างพื้นฐานที่เหมาะสมมากขึ้น คุณอาจใช้เครื่องมือประสาน เช่น Kubernetes เพื่อจัดการระบบคอนเทนเนอร์แบบกระจาย ดังนั้นไปข้างหน้าและติดตั้ง docker-compose
อีกครั้งที่เราต้องทำคือปรับใช้อินสแตนซ์ RabbitMQ และ Nameko จะดูแลส่วนที่เหลือ เนื่องจากบริการทั้งหมดสามารถเข้าถึงอินสแตนซ์ RabbitMQ นั้นได้ ซอร์สโค้ดแบบเต็มสำหรับตัวอย่างนี้มีอยู่ในที่เก็บ GitHub นี้
มาสร้างแอปพลิเคชั่นการเดินทางอย่างง่ายเพื่อทดสอบความสามารถของ Nameko แอปพลิเคชันนั้นอนุญาตให้ลงทะเบียนสนามบินและการเดินทาง แต่ละสนามบินจะถูกเก็บไว้เป็นชื่อสนามบิน และการเดินทางจะจัดเก็บรหัสสำหรับสนามบินต้นทางและปลายทาง สถาปัตยกรรมของระบบของเรามีลักษณะดังนี้:
ตามหลักการแล้วไมโครเซอร์วิสแต่ละรายการจะมีอินสแตนซ์ฐานข้อมูลของตัวเอง อย่างไรก็ตาม เพื่อความง่าย ฉันได้สร้างฐานข้อมูล Redis เดียวสำหรับไมโครเซอร์วิสทั้ง Trips และ Airports เพื่อแชร์ ไมโครเซอร์วิสของเกตเวย์จะได้รับคำขอ HTTP ผ่าน API ที่เหมือน REST อย่างง่าย และใช้ RPC เพื่อสื่อสารกับสนามบินและการเดินทาง
มาเริ่มกันที่ไมโครเซอร์วิสเกตเวย์กัน โครงสร้างตรงไปตรงมาและทุกคนที่มาจากเฟรมเวิร์กอย่าง Flask น่าจะคุ้นเคยกันดี โดยทั่วไปเราจะกำหนดจุดปลายสองจุด โดยแต่ละจุดจะอนุญาตทั้งวิธี GET และ POST:
import json from nameko.rpc import RpcProxy from nameko.web.handlers import http class GatewayService: name = 'gateway' airports_rpc = RpcProxy('airports_service') trips_rpc = RpcProxy('trips_service') @http('GET', '/airport/<string:airport_id>') def get_airport(self, request, airport_id): airport = self.airports_rpc.get(airport_id) return json.dumps({'airport': airport}) @http('POST', '/airport') def post_airport(self, request): data = json.loads(request.get_data(as_text=True)) airport_id = self.airports_rpc.create(data['airport']) return airport_id @http('GET', '/trip/<string:trip_id>') def get_trip(self, request, trip_id): trip = self.trips_rpc.get(trip_id) return json.dumps({'trip': trip}) @http('POST', '/trip') def post_trip(self, request): data = json.loads(request.get_data(as_text=True)) trip_id = self.trips_rpc.create(data['airport_from'], data['airport_to']) return trip_id
มาดูบริการของสนามบินกันดีกว่า ตามที่คาดไว้ จะแสดงวิธี RPC สองวิธี วิธีการ get
จะทำการสืบค้นฐานข้อมูล Redis และส่งคืนสนามบินสำหรับรหัสที่ระบุ วิธีการ create
จะสร้างรหัสสุ่ม เก็บข้อมูลสนามบิน และส่งคืนรหัส:
import uuid from nameko.rpc import rpc from nameko_redis import Redis class AirportsService: name = "airports_service" redis = Redis('development') @rpc def get(self, airport_id): airport = self.redis.get(airport_id) return airport @rpc def create(self, airport): airport_id = uuid.uuid4().hex self.redis.set(airport_id, airport) return airport_id
สังเกตว่าเราใช้นามสกุล nameko_redis
อย่างไร ดูรายการส่วนขยายชุมชน ส่วนขยายถูกนำไปใช้ในลักษณะที่ใช้การฉีดการพึ่งพา Nameko ดูแลการเริ่มต้นวัตถุส่วนขยายจริงที่ผู้ปฏิบัติงานแต่ละคนจะใช้
ระหว่างสนามบินกับไมโครเซอร์วิสของทริปนั้นไม่แตกต่างกันมากนัก นี่คือลักษณะของไมโครเซอร์วิสของ Trips:
import uuid from nameko.rpc import rpc from nameko_redis import Redis class AirportsService: name = "trips_service" redis = Redis('development') @rpc def get(self, trip_id): trip = self.redis.get(trip_id) return trip @rpc def create(self, airport_from_id, airport_to_id): trip_id = uuid.uuid4().hex self.redis.set(trip_id, { "from": airport_from_id, "to": airport_to_id }) return trip_id
Dockerfile
สำหรับแต่ละไมโครเซอร์วิสนั้นตรงไปตรงมามาก การพึ่งพาอาศัยเพียงอย่างเดียวคือ nameko
และในกรณีของบริการสนามบินและการเดินทาง จำเป็นต้องติดตั้ง nameko-redis
ด้วย การขึ้นต่อกันเหล่านี้มีอยู่ใน requirements.txt
.txt ในแต่ละบริการ บริการ Dockerfile สำหรับ Airports มีลักษณะดังนี้:
FROM python:3 RUN apt-get update && apt-get -y install netcat && apt-get clean WORKDIR /app COPY requirements.txt ./ RUN pip install --no-cache-dir -r requirements.txt COPY config.yml ./ COPY run.sh ./ COPY airports.py ./ RUN chmod +x ./run.sh CMD ["./run.sh"]
ข้อแตกต่างระหว่างสิ่งนั้นกับ Dockerfile สำหรับบริการอื่น ๆ คือไฟล์ต้นทาง (ในกรณีนี้คือ airports.py
) ซึ่งควรเปลี่ยนตามนั้น
สคริปต์ run.sh
ดูแลการรอจนกว่า RabbitMQ และในกรณีของบริการสนามบินและการเดินทาง ฐานข้อมูล Redis ก็พร้อม ตัวอย่างต่อไปนี้แสดงเนื้อหาของ run.sh
สำหรับสนามบิน อีกครั้งสำหรับบริการอื่น ๆ เพียงแค่เปลี่ยนจากสนาม gateway
aiports
trips
ตามลำดับ:
#!/bin/bash until nc -z ${RABBIT_HOST} ${RABBIT_PORT}; do echo "$(date) - waiting for rabbitmq..." sleep 1 done until nc -z ${REDIS_HOST} ${REDIS_PORT}; do echo "$(date) - waiting for redis..." sleep 1 done nameko run --config config.yml airports
บริการของเราพร้อมที่จะดำเนินการแล้ว:
$ docker-compose up
มาทดสอบระบบกัน เรียกใช้คำสั่ง:
$ curl -i -d "{\"airport\": \"first_airport\"}" localhost:8000/airport HTTP/1.1 200 OK Content-Type: text/plain; charset=utf-8 Content-Length: 32 Date: Sun, 27 May 2018 05:05:53 GMT f2bddf0e506145f6ba0c28c247c54629
บรรทัดสุดท้ายคือรหัสที่สร้างขึ้นสำหรับสนามบินของเรา หากต้องการทดสอบว่าใช้งานได้หรือไม่ ให้เรียกใช้:
$curl localhost:8000/airport/f2bddf0e506145f6ba0c28c247c54629 {"airport": "first_airport"} Great, now let's add another airport: $ curl -i -d "{\"airport\": \"second_airport\"}" localhost:8000/airport HTTP/1.1 200 OK Content-Type: text/plain; charset=utf-8 Content-Length: 32 Date: Sun, 27 May 2018 05:06:00 GMT 565000adcc774cfda8ca3a806baec6b5
ตอนนี้เรามีสนามบินสองแห่ง ก็พอสำหรับการเดินทาง มาสร้างการเดินทางกันเถอะ:
$ curl -i -d "{\"airport_from\": \"f2bddf0e506145f6ba0c28c247c54629\", \"airport_to\": \"565000adcc774cfda8ca3a806baec6b5\"}" localhost:8000/trip HTTP/1.1 200 OK Content-Type: text/plain; charset=utf-8 Content-Length: 32 Date: Sun, 27 May 2018 05:09:10 GMT 34ca60df07bc42e88501178c0b6b95e4
เหมือนเมื่อก่อน บรรทัดสุดท้ายนั้นแสดงถึงรหัสการเดินทาง ตรวจสอบว่าใส่ถูกต้องหรือไม่:
$ curl localhost:8000/trip/34ca60df07bc42e88501178c0b6b95e4 {"trip": "{'from': 'f2bddf0e506145f6ba0c28c247c54629', 'to': '565000adcc774cfda8ca3a806baec6b5'}"}
สรุป
เราได้เห็นแล้วว่า Nameko ทำงานอย่างไรโดยการสร้างอินสแตนซ์ที่ทำงานอยู่ในเครื่องของ RabbitMQ เชื่อมต่อกับมันและทำการทดสอบหลายอย่าง จากนั้น เราใช้ความรู้ที่ได้รับเพื่อสร้างระบบอย่างง่ายโดยใช้สถาปัตยกรรมไมโครเซอร์วิส
แม้จะเรียบง่ายมาก แต่ระบบของเราก็ใกล้เคียงกับหน้าตาของการทำให้ใช้งานได้จริง คุณควรใช้เฟรมเวิร์กอื่นเพื่อจัดการคำขอ HTTP เช่น Falcon หรือ Flask ทั้งสองตัวเลือกนี้เป็นตัวเลือกที่ยอดเยี่ยมและสามารถใช้เพื่อสร้างไมโครเซอร์วิสบน HTTP อื่นๆ ได้อย่างง่ายดาย เช่น ในกรณีที่คุณต้องการหยุดบริการเกตเวย์ของคุณ เป็นต้น Flask มีข้อได้เปรียบในการมีปลั๊กอินเพื่อโต้ตอบกับ Nameko อยู่แล้ว แต่คุณสามารถใช้ nameko-proxy ได้โดยตรงจากเฟรมเวิร์กใดก็ได้
Nameko นั้นง่ายต่อการทดสอบเช่นกัน เราไม่ได้พูดถึงการทดสอบที่นี่เพื่อความเรียบง่าย แต่โปรดดูเอกสารการทดสอบของ Nameko
ด้วยชิ้นส่วนที่เคลื่อนไหวทั้งหมดภายในสถาปัตยกรรมไมโครเซอร์วิส คุณต้องการให้แน่ใจว่าคุณมีระบบการบันทึกที่แข็งแกร่ง หากต้องการสร้าง ให้ดูการบันทึก Python: บทช่วยสอนเชิงลึกโดยเพื่อน Toptaler และ Python Developer: Son Nguyen Kim