gRPC adalah kerangka kerja open source untuk panggilan prosedur jarak jauh. Di Yandex.Market, gRPC digunakan sebagai alternatif yang lebih nyaman untuk REST. Sergey Fedoseenkov, yang menjalankan layanan pengembangan alat untuk mitra Pasar, berbagi pengalamannya menggunakan gRPC sebagai protokol untuk membangun integrasi antara layanan Java dan C ++. Dari laporan tersebut Anda akan belajar cara menghindari masalah umum jika Anda mulai menggunakan gRPC setelah REST, cara mengembalikan kesalahan, menerapkan penelusuran, permintaan debug, dan menguji panggilan klien.
Pada akhirnya ada catatan tidak resmi dari laporan tersebut.
- Pertama, saya ingin memperkenalkan Anda beberapa fakta tentang Yandex.Market, mereka akan berguna sebagai bagian dari laporan. Fakta pertama: kami menulis layanan dalam berbagai bahasa. Ini membebankan persyaratan pelanggan untuk layanan.
Dan jika kita memiliki layanan di Jawa, alangkah baiknya jika klien untuk itu, misalnya, juga plus atau sedikit.

Semua layanan yang kami miliki adalah independen, tidak ada rilis besar yang direncanakan dari seluruh Pasar. Layanan Microsoft akan dirilis secara independen, dan kompatibilitas ke belakang penting bagi kami di sini, sehingga protokol mendukungnya.
Fakta ketiga: kami memiliki integrasi sinkron dan asinkron. Dalam laporan itu, saya terutama akan berbicara tentang sinkron.
Apa yang kami gunakan? Sekarang, tentu saja, dasar dari integrasi kami adalah layanan REST atau seperti REST yang menukar XML / JSON melalui HTTP 1.1. Ada juga XML-RPC - kami terutama menggunakannya ketika mengintegrasikan dengan kode Python, yaitu, Python memiliki server XML-RPC bawaan. Cukup nyaman untuk menggunakannya di sana, dan kami mendukungnya.
Kami pernah memiliki CORBA. Untungnya, kami meninggalkannya. Sekarang kebanyakan REST dan XML / JSON melalui HTTP.

Integrasi sinkron memiliki masalah dengan protokol yang ada. Kami menghadapi masalah seperti itu dan mencoba memperlakukannya dengan gRPC. Apa masalah ini? Seperti yang saya katakan, saya ingin memiliki pelanggan dalam berbagai bahasa. Dianjurkan agar mereka tidak harus ditulis sendiri. Dan, secara umum, akan keren jika klien bisa sinkron dan asinkron - tergantung pada tujuan pengguna layanan.
Saya juga ingin protokol yang kami gunakan untuk mendukung kompatibilitas dengan baik: ini sangat penting dengan rilis independen paralel. Semua rilis kami kompatibel ke belakang, kami tidak memecah umpan balik. Jika Anda memecahkannya, ini adalah bug, dan Anda hanya perlu memperbaikinya sesegera mungkin.
Pendekatan koheren untuk penanganan kesalahan juga diperlukan: semua orang yang membuat layanan REST tahu bahwa Anda tidak bisa hanya menggunakan status HTTP. Mereka biasanya tidak mengizinkan deskripsi rinci masalah, Anda harus memasukkan beberapa status mereka, detail mereka. Dalam layanan REST, setiap orang memperkenalkan implementasi kesalahan ini sendiri, setiap kali Anda harus bekerja secara berbeda dengan ini. Ini tidak selalu nyaman.
Saya juga ingin memiliki manajemen batas waktu di sisi klien. Sekali lagi, mereka yang bekerja dengan HTTP memahami bahwa jika kita menetapkan batas waktu di sisi klien dan berakhir, klien akan berhenti menunggu permintaan selesai, tetapi server tidak akan tahu apa-apa tentang hal itu dan akan terus menjalankannya. Apalagi di tengah ada berbagai proxy yang mengatur timeout global. Dan klien mungkin tidak tahu apa-apa tentang mereka dan mengkonfigurasinya tidak selalu sepele.
Dan akhirnya, masalah dokumentasi. Tidak selalu jelas di mana mendapatkan dokumentasi untuk sumber daya REST atau untuk beberapa metode, parameter apa yang mereka terima, badan mana yang dapat ditransfer, dan bagaimana mengkomunikasikan dokumentasi ini dengan konsumen layanan. Jelas bahwa ada kesombongan, tetapi dengan itu juga, tidak semuanya sepele.
gRPC Teori
Saya ingin berbicara tentang bagian teoretis dari gRPC - apa itu, apa idenya. Dan kemudian kita akan melanjutkan untuk berlatih.

Secara umum, gRPC adalah spesifikasi abstrak. Ini menjelaskan RPC abstrak (panggilan prosedur jarak jauh), yaitu panggilan prosedur jarak jauh yang memiliki properti tertentu. Sekarang kita akan daftar mereka. Properti pertama adalah dukungan untuk panggilan tunggal dan streaming. Artinya, semua layanan yang mengimplementasikan spesifikasi ini mendukung kedua opsi. Item berikutnya adalah ketersediaan metadata, yaitu, sehingga bersama dengan payload Anda bisa melewati beberapa jenis metadata - syaratnya, header. Dan - dukungan untuk membatalkan permintaan dan batas waktu di luar kotak.
Ini juga mengasumsikan bahwa deskripsi pesan dan layanan itu sendiri dilakukan melalui Bahasa Definisi Antarmuka atau IDL tertentu. Spesifikasi ini juga menjelaskan protokol kawat melalui HTTP / 2, yaitu, gRPC mengasumsikan bahwa protokol itu hanya berfungsi melalui HTTP / 2.

Ada implementasi gRPC khas yang digunakan dalam kebanyakan kasus. Kami juga menggunakannya, dan sekarang kami akan melihatnya. Format proto digunakan sebagai IDL. Plugin gRPC untuk compiler proto memungkinkan Anda untuk mendapatkan sumber layanan yang dihasilkan dari deskripsi proto. Dan ada pustaka runtime dalam berbagai bahasa - Java, C ++, Python. Secara umum, hampir semua bahasa populer didukung, pustaka runtime ada untuk mereka. Dan ketika pesan dipertukarkan di antara layanan, pesan proto digunakan, pesan bergaya sesuai dengan skema protobuf.

Saya ingin terjun sedikit ke beberapa fitur tertentu. Inilah mereka. Pengetikan yang kuat, yaitu, pesan proto, adalah pesan yang diketik dengan kuat. Mereka yang pernah bekerja dengan protobuf tahu bahwa di sana Anda dapat menggambarkan bidang dalam pesan Anda dengan tipe. Ada jenis primitif dan string, byte array. Mereka bisa skalar, bisa vektor. Dan, pada kenyataannya, pesan dapat, sebagai suatu bidang, mengandung pesan lain, yang cukup nyaman, secara umum, model apa pun dapat direpresentasikan.

Tentang kompatibilitas ke belakang. Saya ingin mencatat bahwa proto IDL adalah format di mana kompatibilitas mundur diletakkan di luar kotak, yaitu, itu disusun dengan jaminan kompatibilitas mundur, dan Google merilis versi proto3, yang, dibandingkan dengan proto2, yang meningkatkan kompatibilitas mundur lebih jauh lagi. Di sana, ditambah, ada segala macam spesifikasi, bagaimana dan apa yang dapat diubah sehingga kompatibilitas mundur dipertahankan dalam beberapa kasus non-sepele.
Ada kemungkinan nilai default, Anda dapat menambahkan bidang baru dan konsumen tidak perlu mengubah apa pun, pada kenyataannya. Semua bidang dalam proto3 bersifat opsional dan, misalnya, dapat dihapus, dan mengakses bidang jarak jauh tidak menyebabkan kesalahan pada klien.

Fitur gRPC lain adalah bahwa klien dan server dihasilkan menggunakan kompilator proto dan plugin gRPC berdasarkan pada deskripsi proto. Ada kemungkinan pada saat kode sedang ditulis untuk memilih klien mana yang akan digunakan. Yaitu, pilih klien asinkron atau sinkron, tergantung pada jenis kode apa yang Anda tulis. Sebagai contoh, klien asinkron sangat cocok untuk kode reaktif. Dan kesempatan ini untuk bahasa apa pun. Yaitu, setelah Anda menulis proto-deskripsi, setelah itu Anda dapat menghasilkan klien untuk bahasa apa pun, dan Anda tidak perlu mengembangkannya secara terpisah. Anda dapat mendistribusikan antarmuka untuk layanan Anda hanya sebagai proto-deskripsi. Konsumen mana pun dapat menghasilkan klien untuk dirinya sendiri.

Tentang pembatalan permintaan dan tenggat waktu, saya ingin mencatat bahwa permintaan dapat dibatalkan di server dan di klien. Jika kami memahami semuanya, kami tidak perlu memenuhi permintaan lebih lanjut, maka kami dapat membatalkannya. Dimungkinkan untuk mengatur batas waktu berdasarkan permintaan. Di gRPC, sebagian besar pustaka runtime menggunakan tenggat waktu sebagai konsep batas waktu. Tetapi sebenarnya itu sama. Artinya, ini adalah waktu ketika permintaan harus selesai.
Dan hal yang paling menarik adalah server dapat mencari tahu tentang pembatalan permintaan dan tentang berakhirnya batas waktu dan berhenti mengeksekusi permintaan di sisinya. Ini sangat keren, menurut saya tidak ada banyak tempat lain.
Tentang dokumentasi, saya ingin mencatat bahwa karena format proto digunakan dalam IDL untuk gRPC, ini adalah kode biasa. Di sana Anda dapat menulis komentar, termasuk yang sangat rinci. Dan Anda perlu memahami bahwa untuk berintegrasi dengan layanan Anda, pengguna Anda perlu memiliki format proto ini di rumah mereka, dan itu akan sampai pada mereka bersama dengan komentar, mereka tidak akan berbohong di tempat lain. Sangat nyaman. Dan Anda dapat memperluas uraian ini, yaitu, fitur yang sangat nyaman sehingga dokumentasi berada di sebelah kode, sangat mirip dengan yang terletak di sebelah metode dalam bentuk javadoc atau komentar lainnya.
gRPC panggilan unary. Berlatih
Mari kita lanjutkan, lihat sedikit latihan. Dan contoh paling dasar menggunakan gRPC adalah apa yang disebut panggilan unary, atau panggilan tunggal. Ini adalah skema klasik - kami mengirim permintaan ke server dan mendapatkan satu respons dari server. Sepertinya ini berfungsi di HTTP.

Pertimbangkan contoh layanan gema yang kami lakukan. Server akan ditulis dalam plus, klien di Jawa. Sirkuit balancing klasik digunakan di sini. Yaitu, klien mengatasi penyeimbang, dan kemudian penyeimbang sudah memilih backend spesifik untuk memproses permintaan.
Saya ingin memperhatikan - karena gRPC bekerja melalui HTTP / 2, satu koneksi TCP digunakan. Dan selanjutnya, berbagai aliran melewatinya. Di sini Anda dapat melihat bahwa koneksi antara klien dan penyeimbang didirikan sekali dan tetap bertahan, dan kemudian penyeimbang menyeimbangkan beban pada backend yang berbeda untuk setiap panggilan. Jika Anda melihat, itu terjadi seperti ini dan seperti itu jika pesan didistribusikan.

Berikut adalah contoh kode untuk file proto kami. Anda dapat melihat bahwa kami pertama-tama mendeskripsikan pesan, yaitu, kami memiliki EchoRequest dan EchoResponse. Hanya ada satu bidang string yang menyimpan pesan.
Langkah kedua kami jelaskan prosedur kami. Prosedur input menerima EchoRequest, mengembalikan EchoResponse sebagai hasilnya, semuanya sangat sepele. Ini adalah deskripsi layanan dan pesan gRPC yang akan dikejar.

Mari kita lihat bagaimana ini terjadi dalam kasus plus, misalnya. Itu dirakit dalam tiga tahap. Pada tahap pertama, tugas kami adalah menghasilkan sumber pesan. Di sini kami melakukan ini dengan tim ini. Kami memanggil proto compiler, meneruskan proto-file ke input, menunjukkan tempat untuk meletakkan file output.
Tim kedua. Kami juga menghasilkan layanan dengan cara yang sama. Satu-satunya perbedaan dengan perintah sebelumnya adalah bahwa kita melewati plugin, dan berdasarkan deskripsi, yang dalam format proto, itu menghasilkan layanan.
Langkah ketiga - kami mengumpulkan semua ini dalam satu binar sehingga server kami dapat diluncurkan.
Bendera tambahan diteruskan ke tautan, itu disebut grpc ++ _ reflection. Saya ingin mencatat - server gRPC memiliki fitur seperti itu, refleksi server. Ini memungkinkan Anda menjelajahi jenis layanan, panggilan RPC, dan pesan yang dimiliki layanan ini. Secara default, ini dimatikan, dan Anda dapat mengakses layanan hanya jika Anda memiliki format proto. Tetapi, misalnya, untuk debugging, ini sangat nyaman, tanpa format proto yang ada, cukup nyalakan server dengan fitur refleksi dan segera terima informasi.

Sekarang mari kita lihat implementasinya. Implementasinya juga minimalis. Artinya, tugas utama kami adalah mengimplementasikan layanan-echo yang dihasilkan. Ini memiliki satu metode getEcho. Itu hanya menghasilkan pesan dan mengirimkannya kembali. Status OK - status sukses.
Selanjutnya, kita buat ServerBuilder, daftarkan layanan kami di dalamnya, yang kami bangun sedikit lebih tinggi.

Sekarang kita baru mulai dan menunggu permintaan masuk.

Sekarang mari kita lihat klien di Jawa. Kami mengumpulkan gradle. Tugas kita adalah menghubungkan plugin protobuf terlebih dahulu.
Ada satu set dependensi dasar yang perlu kami seret untuk layanan kami, mereka diperlukan pada tahap kompilasi.
Saya juga ingin mencatat bahwa ada perpustakaan runtime. Untuk Java, ia menggunakan netty sebagai server dan klien, mendukung HTTP / 2, cukup nyaman dan berkinerja tinggi.
Selanjutnya kita mengkonfigurasi proto compiler. Compiler itu sendiri tidak perlu diinstal secara lokal untuk Java, itu bisa diambil dari artefak.
Hal yang sama dengan plugin. Secara lokal untuk Jawa, tidak perlu. Anda dapat menyeret artefak. Dan penting untuk mengonfigurasinya sehingga untuk semua pengocokan juga disebut, sehingga stubs dihasilkan.

Mari kita beralih ke kode Java. Di sini kita adalah orang pertama yang membuat rintisan layanan kami. Itu adalah tugas kami bagi Jawa untuk menyediakan Saluran. Ada ChannelBuilder di perpustakaan runtime yang dengannya kita dapat membangun saluran ini. Di sini kita secara manual mengaktifkan teks biasa untuk kesederhanaan, tetapi HTTP2 dan gRPC mengenkripsi semuanya secara default dan menggunakan TLS.
Kami memiliki rintisan klien kami, klien sinkron dihasilkan di sini. Dengan cara yang sama, Anda dapat menghasilkan klien asinkron, ada opsi lain.
Selanjutnya, kami membuat permintaan protobuff kami, yaitu, kami membuat pesan protobuff.

Itu saja, kirimkan, pada klien kami, kami memanggil getEcho dan mencetak hasilnya. Semuanya sederhana. Seperti yang Anda lihat, diperlukan sedikit kode, dan integrasi dibuat.
streaming gRPC. Berlatih
Sekarang mari kita lihat hal yang lebih maju, ini streaming. Saya akan memberi tahu Anda cara kerjanya, dan nanti saya akan memberi tahu Anda cara menggunakannya.

Streaming client-server terlihat hampir sama secara arsitektur. Artinya, kami memiliki koneksi yang terus-menerus antara klien dan penyeimbang. Kemudian perbedaan dimulai. Inti dari streaming adalah bahwa klien dilampirkan ke beberapa backend akhir, dan koneksi disimpan melalui. Begitulah, terus seperti ini. Dan begitulah. Di sini saya ingin mencatat secara terpisah bahwa penggunaan penyeimbang tidak tipikal untuk streaming, yaitu, Anda perlu memahami bahwa permintaan streaming bisa berumur panjang. Artinya, Anda dapat membukanya dan bertukar pesan untuk waktu yang lama. Dan pesan-pesan ini akan melalui penyeimbang, tetapi, pada kenyataannya, selalu menuju ke backend yang sama. Dan tidak jelas mengapa itu dibutuhkan sama sekali.
Praktik yang umum adalah ketika layanan, misalnya, murni mengalir, atau terutama streaming, maka penemuan layanan digunakan. GRPC memiliki titik ekstensi tempat penemuan layanan dapat disematkan.

Apa yang kita butuhkan untuk mengimplementasikan layanan streaming? Kami memiliki format proto yang sama. Kami menambahkan RPC lain, dan di sini Anda dapat melihat bahwa kami telah menambahkan dua kata kunci sebelum permintaan dan sebelum tanggapan. Karenanya, kami mendeklarasikan stream EchoRequest dan EchoResponse.

Semakin menarik dimulai. Kompilasi kami tidak berubah dengan cara apa pun agar layanan streaming dapat dilakukan. Tugas kami selanjutnya adalah mengganti metode baru kami di layanan Echo kami, yang akan berfungsi dengan stream. Dalam hal server, ini semua agak lebih mudah. Artinya, kita dapat terus membaca dari aliran dan dapat menjawab sesuatu. Kami dapat merespons secara tidak sinkron. Artinya, mereka independen, streaming untuk menulis dan streaming untuk membaca, dan di sini semuanya sederhana untuk skenario sederhana.

Ini bacaannya sekarang, ini rekamannya.

Pada klien Java, banyak hal yang sedikit lebih rumit. Di sana Anda tidak dapat menggunakan API sinkron apa pun, yaitu, itu hanya tidak berfungsi dengan stream. Dan di sana API asinkron digunakan. Artinya, tugas kita adalah mengimplementasikan templat Observer. Ada antarmuka StreamObserver di sana. Ini berisi tiga metode: onNext, onCompleted, dan onError. Di sini, untuk kesederhanaan, saya hanya menerapkan onNext. Itu berkedut hanya ketika jawabannya datang kepada kami dari server.

Di sini saya hanya memasukkan antrian untuk olahpesan di antara utas.

Apa bedanya? Alih-alih memblokir Stub, kami hanya membuat newStub. Ini adalah implementasi asinkron yang dapat bekerja sama dengan Observer. Bahkan, Anda dapat melakukan panggilan unary pada Observer, hanya saja tidak begitu nyaman. Kami, setidaknya, menggunakannya secara tidak aktif.
Selanjutnya kita membangun Pengamat kita.
Dan kami melakukan panggilan RPC kami. Kami meneruskan ResponseObserver ke input, dan pada outputnya mengeluarkan RequestObserver kepada kami. Selanjutnya, kita dapat melakukan panggilan di RequestObserver, sehingga mengirimkan pesan ke server. Dan ResponseObserver kami akan bergerak dan memproses pesan.
Berikut ini sebuah contoh. Kami baru saja menelepon. Hubungi onNext, kirimkan Permintaan di sana.
Lebih jauh dari antrian, kami menunggu server untuk merespons dan mencetak.

Saya ingin menarik perhatian pada kenyataan bahwa tugas kami di sini, sebagai orang yang bertanggung jawab atas implementasi streaming, adalah menangani dengan benar penutupan dari RequestObserver ini. Artinya, jika terjadi kesalahan, kita harus memanggil metode onError di atasnya, dan dalam kasus penyelesaian yang berhasil, ketika kita percaya bahwa aliran dapat ditutup, kita harus memanggil metode onCompleted.

Kami melanjutkan. Apa saja aplikasi streaming? Ini adalah hal yang lebih maju, bukan fakta bahwa itu langsung bermanfaat bagi semua orang, tetapi kadang-kadang digunakan. Artinya, yang pertama mengunduh dan mengunggah sejumlah besar data. Server atau klien dapat menghasilkan data dalam beberapa bagian. Bagian-bagian ini mungkin sudah entah bagaimana dikelompokkan pada klien atau di server. Artinya, Anda sudah bisa melakukan optimasi tambahan di sini.
Juga, skema streaming sangat cocok untuk server push. Anda perlu memahami bahwa saya menganggap opsi paling ekstrem ketika kami memiliki streaming dua arah. Dan mungkin mengalir dalam satu arah. Misalnya, dari klien ke server atau dari server ke klien. Dalam hal server ke klien, kami dapat terhubung ke beberapa server, dan itu akan mengirimkan pushies kepada kami, dan untuk ini kami tidak perlu melakukan polling secara teratur.
Keuntungan streaming berikutnya adalah mengikat ke satu mesin. Seperti yang saya katakan, satu koneksi ujung ke ujung akan dibuat untuk semua pesan di dalam aliran, dan koneksi ini akan diikat ke satu mesin, dan itu pasti tidak akan beralih di mana pun. Oleh karena itu, adalah mungkin, pertama, untuk menyederhanakan sesuatu, semacam sinkronisasi interserver, dan plus, Anda dapat melakukan hal-hal transaksional.
Dan streaming dua arah, hanya contoh yang saya tunjukkan, adalah kemampuan untuk membangun beberapa protokol saya sendiri. Hal yang cukup menarik. Kami memiliki antrian internal di Yandex yang hanya menggunakan streaming dua arah. Dan jika tiba-tiba seseorang memiliki tugas seperti itu, maka peluang yang cukup bagus untuk menggunakannya.
, . . . , - , , . . gRPC .
, gRPC.

, . - . gRPC . , , , , , . , runtime- . , . , OK, runtime- .
, Java . . google.rpc.Status 3 : , . , . , . — , , .
error details, , . : , , , stack traces, . , .
— , HTTP , ? . BadRequest . , , error details, .
. , , BadRequest - ( ), - error detail. , , , - . , .

. . , , , . - - , - - , , . . , , Zipkin. , HTTP , — metadata. .
, . , - , , , .
runtime-, - , . Java ClientInterceptor ServerInterceptor. , , . , , , , , - . , - API - . , , , , - . , gRPC, , - . , , - , , , .

- . -. Java . , , - . - , .

. gRPC — . HTTP/2 . - , ? : , . . , gRPC grpc_cli, curl. , . , -, . , gRPC , .
, evans. , CLI: , , , . , . - , , , , , .
- UI — , Postman, — BloomRPC. Postman . Postman, , , . , BloomRPC , .
- , . , , grpc_cli. . , . , , . , . , - - . — .

, , gRPC. . - , - , . Swagger. , HTTP/1 . OpenAPI , . . , HTTP/2, Swagger — .
WSDL — , . . Swagger, , . . -.
, , , , JAX-RS, Java . .
Twirp. ? Go, . . , , Go , gRPC Twirp. ? , gRPC — , , , IDL . proto- , gRPC-. protoc, , .
Twirp . proto- , HTTP/1.1 , JSON. , Twirp Go. , , Java Jetty. , .

? gRPC — REST . , , , , HTTP/2 balancer. service discovery, . gRPC , . .
gRPC — , . CLI, UI. , .
, gRPC. inter-process-. , sidecar pattern. , . , . , -. - , , -. . , , , , - .
, . gRPC . , . , unary-. , .
:
—
C gRPC — , . , , , .
—
Awesome gRPC — GitHub . , , , . . — , .
Anda dapat menemukan banyak sumber daya lain di Internet, pada beberapa slide. Tapi saya paling suka ini. Kode yang sedikit dimodifikasi dari presentasi ada di sini . Terima kasih
Pencatatan Laporan Informal