Pengantar Layanan Mikro Python dengan Nameko

Diterbitkan: 2022-03-11

pengantar

Pola arsitektur layanan mikro adalah gaya arsitektur yang semakin populer, mengingat fleksibilitas dan ketahanannya. Bersama dengan teknologi seperti Kubernetes, semakin mudah untuk mem-bootstrap aplikasi menggunakan arsitektur Microservices yang belum pernah ada sebelumnya.

Menurut artikel klasik dari blog Martin Fowler, gaya arsitektur Microservices dapat diringkas sebagai:

Singkatnya, gaya arsitektur layanan mikro adalah pendekatan untuk mengembangkan aplikasi tunggal sebagai rangkaian layanan kecil, masing-masing berjalan dalam prosesnya sendiri dan berkomunikasi dengan mekanisme ringan, sering kali merupakan API sumber daya HTTP. Layanan ini dibangun berdasarkan kemampuan bisnis dan dapat diterapkan secara independen oleh mesin penerapan yang sepenuhnya otomatis.

Dengan kata lain, aplikasi yang mengikuti arsitektur layanan mikro terdiri dari beberapa layanan independen dan dinamis yang berkomunikasi satu sama lain menggunakan protokol komunikasi. Adalah umum untuk menggunakan HTTP (dan REST), tetapi seperti yang akan kita lihat, kita dapat menggunakan jenis protokol komunikasi lain seperti RPC (Remote Procedure Call) melalui AMQP (Advanced Message Queuing Protocol).

Pola layanan mikro dapat dianggap sebagai kasus spesifik SOA (arsitektur berorientasi layanan). Dalam SOA adalah umum, bagaimanapun, untuk menggunakan ESB (enterprise service bus) untuk mengatur komunikasi antar layanan. ESB biasanya sangat canggih dan mencakup fungsionalitas untuk perutean pesan yang kompleks dan aplikasi aturan bisnis. Dalam layanan mikro, lebih umum untuk menggunakan pendekatan alternatif: "titik akhir pintar dan pipa bodoh", yang berarti bahwa layanan itu sendiri harus berisi semua logika dan kompleksitas bisnis (kohesi tinggi), tetapi hubungan antara layanan harus sesederhana mungkin (decoupling tinggi), artinya suatu layanan tidak perlu mengetahui layanan lain mana yang akan berkomunikasi dengannya. Ini adalah pemisahan keprihatinan yang diterapkan pada tingkat arsitektur.

Aspek lain dari layanan mikro adalah bahwa tidak ada penegakan tentang teknologi mana yang harus digunakan dalam setiap layanan. Anda harus dapat menulis layanan dengan tumpukan perangkat lunak apa pun yang dapat berkomunikasi dengan layanan lain. Setiap layanan memiliki manajemen siklus hidup sendiri juga. Semua itu berarti bahwa dalam sebuah perusahaan, dimungkinkan untuk memiliki tim yang bekerja pada layanan terpisah, dengan teknologi dan bahkan metodologi manajemen yang berbeda. Setiap tim akan memperhatikan kemampuan bisnis, membantu membangun organisasi yang lebih gesit.

Layanan Mikro Python

Dengan mengingat konsep-konsep ini, dalam artikel ini kita akan fokus membangun bukti konsep aplikasi Microservices menggunakan Python. Untuk itu, kami akan menggunakan Nameko, kerangka kerja layanan mikro Python. Ini memiliki RPC over AMQP bawaan, memungkinkan Anda untuk berkomunikasi dengan mudah di antara layanan Anda. Ini juga memiliki antarmuka sederhana untuk kueri HTTP, yang akan kita gunakan dalam tutorial ini. Namun, untuk menulis Layanan Mikro yang mengekspos titik akhir HTTP, Anda disarankan untuk menggunakan kerangka kerja lain, seperti Flask. Untuk memanggil metode Nameko melalui RPC menggunakan Flask, Anda dapat menggunakan flask_nameko, pembungkus yang dibuat hanya untuk mengoperasikan Flask dengan Nameko.

Mengatur Lingkungan Dasar

Mari kita mulai dengan menjalankan contoh yang paling sederhana, diekstrak dari situs Nameko, dan mengembangkannya untuk tujuan kita. Pertama, Anda perlu menginstal Docker. Kami akan menggunakan Python 3 dalam contoh kami, jadi pastikan Anda telah menginstalnya juga. Kemudian, buat python virtualenv dan jalankan $ pip install nameko .

Untuk menjalankan Nameko, kita memerlukan perantara pesan RabbitMQ. Ini akan bertanggung jawab untuk komunikasi antara layanan Nameko kami. Namun, jangan khawatir, karena Anda tidak perlu menginstal satu ketergantungan lagi pada mesin Anda. Dengan Docker, kita cukup mengunduh gambar yang telah dikonfigurasi sebelumnya, menjalankannya, dan setelah selesai, cukup hentikan wadahnya. Tidak ada daemon, apt-get atau dnf install .

Layanan Mikro Python dengan Nameko berbicara dengan broker RabbitMQ

Mulai wadah RabbitMQ dengan menjalankan $ docker run -p 5672:5672 --hostname nameko-rabbitmq rabbitmq:3 (Anda mungkin perlu sudo untuk melakukannya). Ini akan memulai wadah Docker menggunakan RabbitMQ versi 3 terbaru dan mengeksposnya melalui port default 5672.

Halo Dunia dengan Layanan Mikro

Silakan dan buat file bernama hello.py dengan konten berikut:

 from nameko.rpc import rpc class GreetingService: name = "greeting_service" @rpc def hello(self, name): return "Hello, {}!".format(name)

Layanan Nameko adalah kelas. Kelas-kelas ini mengekspos titik masuk, yang diimplementasikan sebagai ekstensi. Ekstensi bawaan mencakup kemampuan untuk membuat titik masuk yang mewakili metode RPC, pendengar acara, titik akhir HTTP, atau pengatur waktu. Ada juga ekstensi komunitas yang dapat digunakan untuk berinteraksi dengan database PostgreSQL, Redis, dll… Anda dapat menulis ekstensi sendiri.

Mari kita lanjutkan dan jalankan contoh kita. Jika Anda menjalankan RabbitMQ pada port default, jalankan $ nameko run hello . Ini akan menemukan RabbitMQ dan terhubung secara otomatis. Kemudian, untuk menguji layanan kami, jalankan $ nameko shell di terminal lain. Ini akan membuat shell interaktif yang akan terhubung ke instance RabbitMQ yang sama. Hebatnya, dengan menggunakan RPC melalui AMQP, Nameko mengimplementasikan penemuan layanan otomatis. Saat memanggil metode RPC, nameko akan mencoba menemukan layanan berjalan yang sesuai.

Dua layanan Nameko berbicara melalui RabbitMQ RPC

Saat menjalankan shell Nameko, Anda akan mendapatkan objek khusus bernama n yang ditambahkan ke namespace. Objek ini memungkinkan untuk mengirim acara dan melakukan panggilan RPC. Untuk melakukan panggilan RPC ke layanan kami, jalankan:

 > >> n.rpc.greetingservice.hello(name='world') 'Hello, world!'

Panggilan Serentak

Kelas layanan ini dipakai pada saat panggilan dibuat dan dihancurkan setelah panggilan selesai. Oleh karena itu, mereka harus secara inheren tanpa kewarganegaraan, artinya Anda tidak boleh mencoba mempertahankan status apa pun di objek atau kelas di antara panggilan. Ini menyiratkan bahwa layanan itu sendiri harus tanpa kewarganegaraan. Dengan asumsi bahwa semua layanan tidak memiliki kewarganegaraan, Nameko mampu memanfaatkan konkurensi dengan menggunakan eventlet greenthreads. Layanan yang dipakai disebut "pekerja", dan mungkin ada jumlah pekerja maksimum yang dikonfigurasi yang berjalan pada waktu yang sama.

Untuk memverifikasi konkurensi Nameko dalam praktik, ubah kode sumber dengan menambahkan sleep ke panggilan prosedur sebelum mengembalikan respons:

 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)

Kami menggunakan sleep dari modul time , yang tidak diaktifkan async. Namun, saat menjalankan layanan kami menggunakan nameko run , itu akan secara otomatis menambal hasil pemicu dari memblokir panggilan seperti sleep(5) .

Sekarang diharapkan waktu respons dari panggilan prosedur akan memakan waktu sekitar 5 detik. Namun, apa yang akan menjadi perilaku dari cuplikan berikut, ketika kita menjalankannya dari shell 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 menyediakan metode call_async non-blocking untuk setiap titik masuk RPC, mengembalikan objek balasan proxy yang kemudian dapat ditanyakan hasilnya. Metode result , saat dipanggil pada proxy balasan, akan diblokir hingga respons dikembalikan.

Seperti yang diharapkan, contoh ini berjalan hanya dalam waktu sekitar lima detik. Setiap pekerja akan diblokir menunggu panggilan sleep selesai, tetapi ini tidak menghentikan pekerja lain untuk memulai. Ganti panggilan sleep ini dengan panggilan basis data I/O pemblokiran yang berguna, misalnya, dan Anda mendapatkan layanan serentak yang sangat cepat.

Seperti yang dijelaskan sebelumnya, Nameko membuat pekerja ketika sebuah metode dipanggil. Jumlah maksimum pekerja dapat dikonfigurasi. Secara default, angka tersebut disetel ke 10. Anda dapat menguji mengubah range(5) dalam cuplikan di atas menjadi, misalnya, range(20). Ini akan memanggil metode hello 20 kali, yang sekarang membutuhkan waktu sepuluh detik untuk dijalankan:

 > >> 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!

Sekarang, anggaplah Anda mendapatkan terlalu banyak (lebih dari 10) pengguna bersamaan yang memanggil metode hello itu. Beberapa pengguna akan menunggu lebih dari lima detik yang diharapkan untuk tanggapan. Salah satu solusinya adalah meningkatkan jumlah pekerjaan dengan mengesampingkan pengaturan default menggunakan, misalnya, file konfigurasi. Namun, jika server Anda sudah mencapai batasnya dengan sepuluh pekerja itu karena metode yang dipanggil bergantung pada beberapa kueri basis data yang berat, meningkatkan jumlah pekerja dapat menyebabkan waktu respons meningkat lebih banyak lagi.

Menskalakan Layanan Kami

Solusi yang lebih baik adalah dengan menggunakan kemampuan Nameko Microservices. Sampai saat ini, kami hanya menggunakan satu server (komputer Anda), menjalankan satu instance RabbitMQ, dan satu instance layanan. Di lingkungan produksi, Anda ingin meningkatkan jumlah node yang menjalankan layanan yang menerima terlalu banyak panggilan secara sewenang-wenang. Anda juga dapat membangun cluster RabbitMQ jika Anda ingin broker pesan Anda lebih dapat diandalkan.

Untuk mensimulasikan penskalaan layanan, kita cukup membuka terminal lain dan menjalankan layanan seperti sebelumnya, menggunakan $ nameko run hello . Ini akan memulai contoh layanan lain dengan potensi untuk menjalankan sepuluh pekerja lagi. Sekarang, coba jalankan cuplikan itu lagi dengan range(20) . Sekarang perlu lima detik lagi untuk berjalan. Ketika ada lebih dari satu instans layanan yang berjalan, Nameko akan melakukan round-robin permintaan RPC di antara instans yang tersedia.

Nameko dibangun untuk menangani panggilan metode tersebut dalam sebuah cluster dengan kuat. Untuk mengujinya, coba jalankan snipped dan sebelum selesai, buka salah satu terminal yang menjalankan layanan Nameko dan tekan Ctrl+C dua kali. Ini akan mematikan host tanpa menunggu pekerja selesai. Nameko akan mengalokasikan kembali panggilan ke instance layanan lain yang tersedia.

Dalam praktiknya, Anda akan menggunakan Docker untuk menyimpan layanan Anda, seperti yang akan kami lakukan nanti, dan alat orkestrasi seperti Kubernetes untuk mengelola node Anda yang menjalankan layanan dan dependensi lainnya, seperti broker pesan. Jika dilakukan dengan benar, dengan Kubernetes, Anda akan secara efektif mengubah aplikasi Anda dalam sistem terdistribusi yang kuat, kebal terhadap puncak yang tidak terduga. Selain itu, Kubernetes memungkinkan penerapan zero-downtime. Oleh karena itu, menerapkan versi baru layanan tidak akan memengaruhi ketersediaan sistem Anda.

Sangat penting untuk membangun layanan dengan mempertimbangkan beberapa kompatibilitas mundur, karena dalam lingkungan produksi dapat terjadi beberapa versi berbeda dari layanan yang sama untuk berjalan pada waktu yang sama, terutama selama penerapan. Jika Anda menggunakan Kubernetes, selama penerapan itu hanya akan mematikan semua penampung versi lama jika ada cukup penampung baru yang berjalan.

Untuk Nameko, menjalankan beberapa versi berbeda dari layanan yang sama pada waktu yang sama bukanlah masalah. Karena mendistribusikan panggilan secara round-robin, panggilan mungkin melalui versi lama atau baru. Untuk mengujinya, pertahankan satu terminal dengan layanan kami yang menjalankan versi lama, dan edit modul layanan agar terlihat seperti:

 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)

Jika Anda menjalankan layanan itu dari terminal lain, Anda akan menjalankan dua versi secara bersamaan. Sekarang, jalankan cuplikan pengujian kami lagi dan Anda akan melihat kedua versi ditampilkan:

 > >> 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!

Bekerja dengan Banyak Instance

Sekarang kita tahu cara efektif bekerja dengan Nameko, dan cara kerja penskalaan. Sekarang mari kita melangkah lebih jauh dan menggunakan lebih banyak alat dari ekosistem Docker: docker-compose. Ini akan bekerja jika Anda menerapkan ke satu server, yang jelas tidak ideal karena Anda tidak akan memanfaatkan banyak keuntungan dari arsitektur Microservices. Sekali lagi, jika Anda ingin memiliki infrastruktur yang lebih sesuai, Anda dapat menggunakan alat orkestrasi seperti Kubernetes untuk mengelola sistem container terdistribusi. Jadi, lanjutkan dan instal docker-compose.

Sekali lagi, yang harus kita lakukan adalah men-deploy instance RabbitMQ dan Nameko akan mengurus sisanya, mengingat semua layanan dapat mengakses instance RabbitMQ tersebut. Kode sumber lengkap untuk contoh ini tersedia di repositori GitHub ini.

Mari buat aplikasi perjalanan sederhana untuk menguji kemampuan Nameko. Aplikasi itu memungkinkan pendaftaran bandara dan perjalanan. Setiap bandara hanya disimpan sebagai nama bandara, dan perjalanan menyimpan id untuk bandara asal dan tujuan. Arsitektur sistem kami terlihat seperti berikut:

Ilustrasi aplikasi perjalanan

Idealnya, setiap layanan mikro akan memiliki instance database sendiri. Namun, untuk kesederhanaan, saya telah membuat satu database Redis untuk layanan mikro Perjalanan dan Bandara untuk dibagikan. Layanan mikro Gateway akan menerima permintaan HTTP melalui API sederhana seperti REST dan menggunakan RPC untuk berkomunikasi dengan Bandara dan Perjalanan.

Mari kita mulai dengan layanan mikro Gateway. Strukturnya mudah dan harus sangat familiar bagi siapa saja yang berasal dari kerangka kerja seperti Flask. Kami pada dasarnya mendefinisikan dua titik akhir, masing-masing memungkinkan metode GET dan 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

Mari kita lihat layanan Bandara sekarang. Seperti yang diharapkan, itu memperlihatkan dua metode RPC. Metode get hanya akan menanyakan database Redis dan mengembalikan bandara untuk id yang diberikan. Metode create akan menghasilkan id acak, menyimpan informasi bandara, dan mengembalikan id:

 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

Perhatikan bagaimana kita menggunakan ekstensi nameko_redis . Lihatlah daftar ekstensi komunitas. Ekstensi diimplementasikan dengan cara yang menggunakan injeksi ketergantungan. Nameko menangani inisiasi objek ekstensi aktual yang akan digunakan setiap pekerja.

Tidak ada banyak perbedaan antara layanan mikro Bandara dan Perjalanan. Berikut adalah tampilan layanan mikro 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 untuk setiap layanan mikro juga sangat mudah. Satu-satunya ketergantungan adalah nameko , dan dalam hal layanan Bandara dan Perjalanan, ada kebutuhan untuk menginstal nameko-redis juga. Dependensi tersebut diberikan dalam requirements.txt di setiap layanan. Layanan Dockerfile untuk Bandara terlihat seperti:

 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"]

Satu-satunya perbedaan antara itu dan Dockerfile untuk layanan lain adalah file sumber (dalam hal ini airports.py ), yang harus diubah sesuai dengan itu.

Skrip run.sh menangani menunggu hingga RabbitMQ dan, dalam hal layanan Bandara dan Perjalanan, database Redis sudah siap. Cuplikan berikut menunjukkan konten run.sh untuk airports. Sekali lagi, untuk layanan lain hanya mengubah dari aiports ke gateway atau trips yang sesuai:

 #!/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

Layanan kami sekarang siap dijalankan:

$ docker-compose up

Mari kita uji sistem kita. Jalankan perintah:

 $ 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

Baris terakhir itu adalah id yang dihasilkan untuk bandara kami. Untuk menguji apakah itu berfungsi, jalankan:

 $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

Sekarang kita punya dua bandara, Itu cukup untuk membentuk sebuah perjalanan. Mari buat perjalanan sekarang:

 $ 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

Seperti sebelumnya, baris terakhir itu mewakili ID perjalanan. Mari kita periksa apakah itu dimasukkan dengan benar:

 $ curl localhost:8000/trip/34ca60df07bc42e88501178c0b6b95e4 {"trip": "{'from': 'f2bddf0e506145f6ba0c28c247c54629', 'to': '565000adcc774cfda8ca3a806baec6b5'}"}

Ringkasan

Kita telah melihat bagaimana Nameko bekerja dengan membuat instance RabbitMQ yang berjalan secara lokal, menghubungkannya dan melakukan beberapa pengujian. Kemudian, kami menerapkan pengetahuan yang diperoleh untuk membuat sistem sederhana menggunakan arsitektur Microservices.

Meskipun sangat sederhana, sistem kami sangat mirip dengan seperti apa penerapan siap produksi. Anda sebaiknya menggunakan kerangka kerja lain untuk menangani permintaan HTTP seperti Falcon atau Flask. Keduanya adalah opsi yang bagus dan dapat dengan mudah digunakan untuk membuat layanan mikro berbasis HTTP lainnya, jika Anda ingin merusak layanan Gateway Anda, misalnya. Flask memiliki keuntungan karena sudah memiliki plugin untuk berinteraksi dengan Nameko, tetapi Anda dapat menggunakan nameko-proxy langsung dari framework apa pun.

Nameko juga sangat mudah untuk diuji. Kami belum membahas pengujian di sini untuk kesederhanaan, tetapi periksa dokumentasi pengujian Nameko.

Dengan semua bagian yang bergerak di dalam arsitektur layanan mikro, Anda ingin memastikan bahwa Anda memiliki sistem pencatatan yang kuat. Untuk membangunnya, lihat Python Logging: Tutorial Mendalam oleh sesama Toptaler dan Pengembang Python: Son Nguyen Kim.