gRPC
gRPC zamonaviy, open source va yuqori unumdor RPC freymvorki bo'lib, istalgan muhitda ishlay oladi. U pluggable load balancing, tracing, health checking va authentication qo'llab-q
gRPC zamonaviy, open source va yuqori unumdor RPC freymvorki bo'lib, istalgan muhitda ishlay oladi. U pluggable load balancing, tracing, health checking va authentication qo'llab-quvvatlashi bilan servislarni data markazlar ichida va ular orasida samarali ulaydi.
Ko'plab RPC tizimlari kabi, gRPC ham masofadan chaqiriladigan funksiyalar (metodlar) nuqtai nazaridan servisni aniqlash konsepsiyasiga asoslanadi. Har bir metod uchun parametrlar va qaytish turlarini aniqlaysiz. Servislar, parametrlar va qaytish turlari Google'ning open source, tildan mustaqil protocol buffers mexanizmi orqali .proto fayllarda belgilanadi.
gRPC transportyori bilan Nest .proto fayllardan foydalanib client va serverlarni dinamik bog'laydi, bu masofaviy prosedura chaqiruvlarini (RPC) oson implement qilishga, tuzilgan ma'lumotlarni avtomatik serializatsiya va deserializatsiya qilishga yordam beradi.
O'rnatish
gRPC asosidagi microservice'larni qurishni boshlash uchun avval kerakli paketlarni o'rnating:
1$ npm i --save @grpc/grpc-js @grpc/proto-loaderUmumiy ko'rinish
Boshqa Nest microservice transport qatlam implementatsiyalari kabi, gRPC transportyer mexanizmini createMicroservice() metodiga uzatiladigan options obyektining transport propertysi orqali tanlaysiz. Quyidagi misolda hero servisini sozlaymiz. options propertysi servis haqida metadata beradi; uning propertylari quyida tasvirlangan.
1const app = await NestFactory.createMicroservice<MicroserviceOptions>(AppModule, {
2 transport: Transport.GRPC,
3 options: {
4 package: 'hero',
5 protoPath: join(__dirname, 'hero/hero.proto'),
6 },
7});join() funksiyasi path paketidan import qilinadi; Transport enum'i esa @nestjs/microservices paketidan import qilinadi.
nest-cli.json faylida assets propertysini qo'shamiz, u non-TypeScript fayllarni distribut qilishga imkon beradi va watchAssets - barcha non-TypeScript assetlarni kuzatishni yoqadi. Bizning holatda .proto fayllar dist papkaga avtomatik ko'chirilishini xohlaymiz.
1{
2 "compilerOptions": {
3 "assets": ["**/*.proto"],
4 "watchAssets": true
5 }
6}Opsiyalar
gRPC transportyer options obyekti quyida tasvirlangan propertylarni ochadi.
package | Protobuf package nomi (.proto faylidagi package sozlamasiga mos). Required |
protoPath |
.proto faylga absolute (yoki root dirga nisbatan) path. Required
|
url | Ulanish url'i. ip address/dns name:port formatidagi string (masalan, Docker serveri uchun '0.0.0.0:50051'), transportyor ulanishni o'rnatadigan address/portni belgilaydi. Optional. Default: 'localhost:5000' |
protoLoader | .proto fayllarni yuklash uchun utilitaga tegishli NPM paket nomi. Optional. Default: '@grpc/proto-loader' |
loader |
@grpc/proto-loader options'lari. Ular .proto fayllar xatti-harakatini batafsil boshqarish imkonini beradi. Optional. Batafsil ma'lumot uchun
here
|
credentials | Server credentials. Optional. Read more here |
gRPC servis namunasi
HeroesService nomli gRPC servisimizni aniqlaymiz. Yuqoridagi options obyektida protoPath propertysi .proto ta'rif fayli hero.proto ga pathni belgilaydi. hero.proto fayli protocol buffers yordamida tuzilgan. U quyidagicha ko'rinadi:
1// hero/hero.proto
2syntax = "proto3";
3
4package hero;
5
6service HeroesService {
7 rpc FindOne (HeroById) returns (Hero) {}
8}
9
10message HeroById {
11 int32 id = 1;
12}
13
14message Hero {
15 int32 id = 1;
16 string name = 2;
17}Bizning HeroesService FindOne() metodini ochadi. Bu metod HeroById turidagi input argumentni kutadi va Hero xabarini qaytaradi (protocol buffers message elementlari yordamida ham parametr turlari, ham qaytish turlari aniqlanadi).
Endi servisni implement qilishimiz kerak. Ushbu ta'rifni bajaradigan handlerni aniqlash uchun controllerda @GrpcMethod() dekoratoridan foydalanamiz, quyida ko'rsatilganidek. Bu dekorator metodni gRPC servis metodi sifatida e'lon qilish uchun kerakli metadatalarni taqdim etadi.
Oldingi microservices boblarida kiritilgan @MessagePattern() dekoratori (read more) gRPC asosidagi microservice'larda ishlatilmaydi. @GrpcMethod() dekoratori gRPC microservice'lar uchun amalda uning o'rnini bosadi.
1@Controller()
2export class HeroesController {
3 @GrpcMethod('HeroesService', 'FindOne')
4 findOne(data: HeroById, metadata: Metadata, call: ServerUnaryCall<any, any>): Hero {
5 const items = [
6 { id: 1, name: 'John' },
7 { id: 2, name: 'Doe' },
8 ];
9 return items.find(({ id }) => id === data.id);
10 }
11}@GrpcMethod() dekoratori @nestjs/microservices paketidan import qilinadi, Metadata va ServerUnaryCall esa grpc paketidan import qilinadi.
Yuqorida ko'rsatilgan dekorator ikki argument oladi. Birinchisi servis nomi (masalan, 'HeroesService'), bu hero.proto faylidagi HeroesService servis ta'rifiga mos. Ikkinchisi ('FindOne' stringi) HeroesService ichida aniqlangan FindOne() rpc metodiga mos keladi.
findOne() handler metodi uchta argument oladi: chaqiruvchidan kelgan data, gRPC so'rov metadatasini saqlaydigan metadata va GrpcCall obyektining sendMetadata kabi propertylariga kirish uchun call.
@GrpcMethod() dekoratorining har ikki argumenti ixtiyoriy. Ikkinchi argument ('FindOne') berilmasa, Nest handler nomini Upper Camel Casega aylantirib, .proto faylidagi rpc metodga avtomatik bog'laydi (masalan, findOne handleri FindOne rpc chaqiruviga moslashtiriladi). Bu quyida ko'rsatilgan.
1@Controller()
2export class HeroesController {
3 @GrpcMethod('HeroesService')
4 findOne(data: HeroById, metadata: Metadata, call: ServerUnaryCall<any, any>): Hero {
5 const items = [
6 { id: 1, name: 'John' },
7 { id: 2, name: 'Doe' },
8 ];
9 return items.find(({ id }) => id === data.id);
10 }
11}Shuningdek, birinchi @GrpcMethod() argumentini ham tashlab ketishingiz mumkin. Bu holatda Nest handler aniqlangan class nomi asosida .proto faylidagi servis ta'rifi bilan avtomatik bog'laydi. Masalan, quyidagi kodda HeroesService klassi o'z handler metodlarini hero.proto faylidagi HeroesService servis ta'rifi bilan 'HeroesService' nomlar mosligi bo'yicha bog'laydi.
1@Controller()
2export class HeroesService {
3 @GrpcMethod()
4 findOne(data: HeroById, metadata: Metadata, call: ServerUnaryCall<any, any>): Hero {
5 const items = [
6 { id: 1, name: 'John' },
7 { id: 2, name: 'Doe' },
8 ];
9 return items.find(({ id }) => id === data.id);
10 }
11}Mijoz
Nest ilovalari .proto fayllarda aniqlangan servislarni iste'mol qiluvchi gRPC clientlari bo'lishi mumkin. Masofaviy servisga ClientGrpc obyekt orqali kirasiz. ClientGrpc obyektini bir nechta usul bilan olishingiz mumkin.
Afzal usul - ClientsModuleni import qilish. register() metodidan foydalanib .proto faylda aniqlangan servislar paketini injection token bilan bog'laysiz va servisini sozlaysiz. name propertysi injection token hisoblanadi. gRPC servislar uchun transport: Transport.GRPC dan foydalaning. options propertysi yuqorida ta'riflangan propertylarga ega obyekt.
1imports: [
2 ClientsModule.register([
3 {
4 name: 'HERO_PACKAGE',
5 transport: Transport.GRPC,
6 options: {
7 package: 'hero',
8 protoPath: join(__dirname, 'hero/hero.proto'),
9 },
10 },
11 ]),
12];register() metodi obyektlar massivini qabul qiladi. Bir nechta paketlarni ro'yxatdan o'tkazish uchun ro'yxatga vergul bilan ajratilgan bir nechta registration obyektlarini bering.
Ro'yxatdan o'tkazilgach, @Inject() yordamida sozlangan ClientGrpc obyektini inject qilamiz. So'ng ClientGrpc obyektining getService() metodidan foydalanib servis instansiyasini olamiz, quyida ko'rsatilganidek.
1@Injectable()
2export class AppService implements OnModuleInit {
3 private heroesService: HeroesService;
4
5 constructor(@Inject('HERO_PACKAGE') private client: ClientGrpc) {}
6
7 onModuleInit() {
8 this.heroesService = this.client.getService<HeroesService>('HeroesService');
9 }
10
11 getHero(): Observable<string> {
12 return this.heroesService.findOne({ id: 1 });
13 }
14}gRPC Client proto loader konfiguratsiyasida keepCase options true qilinmagan bo'lsa (options.loader.keepcase microservice transporter konfiguratsiyasida), nomida underscore _ bo'lgan maydonlarni yubormaydi.
E'tibor bering, boshqa microservice transport usullarida ishlatiladigan texnikadan kichik farq bor. ClientProxy klassi o'rniga ClientGrpc klassidan foydalanamiz; u getService() metodini taqdim etadi. getService() generic metodi servis nomini argument sifatida oladi va uning instansiyasini (agar mavjud bo'lsa) qaytaradi.
Muqobil ravishda, @Client() dekoratori yordamida ClientGrpc obyektini quyidagicha instansiyalash mumkin:
1@Injectable()
2export class AppService implements OnModuleInit {
3 @Client({
4 transport: Transport.GRPC,
5 options: {
6 package: 'hero',
7 protoPath: join(__dirname, 'hero/hero.proto'),
8 },
9 })
10 client: ClientGrpc;
11
12 private heroesService: HeroesService;
13
14 onModuleInit() {
15 this.heroesService = this.client.getService<HeroesService>('HeroesService');
16 }
17
18 getHero(): Observable<string> {
19 return this.heroesService.findOne({ id: 1 });
20 }
21}Nihoyat, murakkabroq ssenariylar uchun here da ta'riflanganidek ClientProxyFactory klassi yordamida dinamik sozlangan clientni inject qilishimiz mumkin.
Har ikki holatda ham .proto faylda aniqlangan metodlar bilan bir xil metodlar to'plamiga ega HeroesService proxy obyektiga ega bo'lamiz. Endi bu proxy obyektga murojaat qilsak (ya'ni heroesService), gRPC tizimi so'rovlarni avtomatik serializatsiya qiladi, ularni masofaviy tizimga uzatadi, javobni qaytaradi va javobni deserializatsiya qiladi. gRPC bizni ushbu tarmoq muloqot tafsilotlaridan himoya qilgani sababli, heroesService mahalliy provider kabi ko'rinadi va ishlaydi.
Eslatma: barcha servis metodlari lower camel cased (tilning tabiiy konventsiyasiga rioya qilish uchun). Shuning uchun, masalan, .proto faylimizdagi HeroesService ta'rifi FindOne() funksiyasini o'z ichiga olsa ham, heroesService instansiyasi findOne() metodini taqdim etadi.
1interface HeroesService {
2 findOne(data: { id: number }): Observable<any>;
3}Message handler Observable ham qaytarishi mumkin, bu holda stream tugaguncha natija qiymatlar emit qilinadi.
1@Get()
2call(): Observable<any> {
3 return this.heroesService.findOne({ id: 1 });
4}gRPC metadata (so'rov bilan birga) yuborish uchun ikkinchi argumentni quyidagicha uzatishingiz mumkin:
1call(): Observable<any> {
2 const metadata = new Metadata();
3 metadata.add('Set-Cookie', 'yummy_cookie=choco');
4
5 return this.heroesService.findOne({ id: 1 }, metadata);
6}Metadata klassi grpc paketidan import qilinadi.
Iltimos, bu avvalroq aniqlagan HeroesService interfeysini yangilashni talab qilishini unutmang.
Misol
Ishlaydigan misol here mavjud.
gRPC Reflection
gRPC Server Reflection Specification gRPC clientlariga server ochib bergan API tafsilotlarini so'rash imkonini beradigan standartdir, bu REST API uchun OpenAPI hujjatini ochishga o'xshaydi. Bu grpc-ui yoki postman kabi developer debugging tool'lari bilan ishlashni ancha osonlashtirishi mumkin.
Serverga gRPC reflection qo'llab-quvvatlashini qo'shish uchun avval kerakli implementatsiya paketini o'rnating:
1$ npm i --save @grpc/reflectionSo'ng uni gRPC server options'ida onLoadPackageDefinition hook yordamida quyidagicha ulash mumkin:
1import { ReflectionService } from '@grpc/reflection';
2
3const app = await NestFactory.createMicroservice<MicroserviceOptions>(AppModule, {
4 options: {
5 onLoadPackageDefinition: (pkg, server) => {
6 new ReflectionService(pkg).addToServer(server);
7 },
8 },
9});Endi server reflection specification bo'yicha API tafsilotlarini so'ragan xabarlarga javob beradi.
gRPC streamingi
gRPC o'z-o'zidan streams deb ataladigan uzoq muddatli live ulanishlarni qo'llab-quvvatlaydi. Streamlar Chatting, Observations yoki Chunk-data uzatish kabi holatlarda foydali. Batafsil ma'lumot uchun rasmiy hujjatdagi here ga qarang.
Nest GRPC stream handlerlarini ikki xil yo'l bilan qo'llab-quvvatlaydi:
- RxJS
Subject+Observablehandler: javoblarni Controller metodi ichida yozish yokiSubject/Observableiste'molchisiga uzatish uchun foydali - Pure GRPC call stream handler: Node standart
Duplexstream handleri uchun qolgan dispatchni bajaradigan executor'ga uzatish uchun foydali
Streaming namunasi
HelloService nomli yangi sample gRPC servisini aniqlaymiz. hello.proto fayli protocol buffers yordamida tuzilgan. U quyidagicha ko'rinadi:
1// hello/hello.proto
2syntax = "proto3";
3
4package hello;
5
6service HelloService {
7 rpc BidiHello(stream HelloRequest) returns (stream HelloResponse);
8 rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse);
9}
10
11message HelloRequest {
12 string greeting = 1;
13}
14
15message HelloResponse {
16 string reply = 1;
17}LotsOfGreetings metodini yuqoridagi misollarda bo'lgani kabi @GrpcMethod dekoratori bilan shunchaki implement qilish mumkin, chunki qaytadigan stream bir nechta qiymat emit qilishi mumkin.
Ushbu .proto fayli asosida HelloService interfeysini aniqlaymiz:
1interface HelloService {
2 bidiHello(upstream: Observable<HelloRequest>): Observable<HelloResponse>;
3 lotsOfGreetings(
4 upstream: Observable<HelloRequest>,
5 ): Observable<HelloResponse>;
6}
7
8interface HelloRequest {
9 greeting: string;
10}
11
12interface HelloResponse {
13 reply: string;
14}Proto interfeysini ts-proto paketi bilan avtomatik generatsiya qilish mumkin, batafsil here.
Subject strategiyasi
@GrpcStreamMethod() dekoratori funksiya parametrini RxJS Observable sifatida beradi. Shunday qilib, bir nechta xabarlarni qabul qilib, qayta ishlashimiz mumkin.
1@GrpcStreamMethod()
2bidiHello(messages: Observable<any>, metadata: Metadata, call: ServerDuplexStream<any, any>): Observable<any> {
3 const subject = new Subject();
4
5 const onNext = message => {
6 console.log(message);
7 subject.next({
8 reply: 'Hello, world!'
9 });
10 };
11 const onComplete = () => subject.complete();
12 messages.subscribe({
13 next: onNext,
14 complete: onComplete,
15 });
16
17 return subject.asObservable();
18}@GrpcStreamMethod() dekoratori bilan full-duplex o'zaro aloqani qo'llab-quvvatlash uchun controller metodi RxJS Observable qaytarishi kerak.
Metadata va ServerUnaryCall klasslari/interfeyslari grpc paketidan import qilinadi.
Servis ta'rifiga ko'ra (.proto faylda), BidiHello metodi servisga stream so'rovlarini yuborishi kerak. Clientdan streamga bir nechta asinxron xabarlarni yuborish uchun RxJS ReplaySubject klassidan foydalanamiz.
1const helloService = this.client.getService<HelloService>('HelloService');
2const helloRequest$ = new ReplaySubject<HelloRequest>();
3
4helloRequest$.next({ greeting: 'Hello (1)!' });
5helloRequest$.next({ greeting: 'Hello (2)!' });
6helloRequest$.complete();
7
8return helloService.bidiHello(helloRequest$);Yuqoridagi misolda, streamga ikki xabar yozdik (next() chaqiruvlari) va servisga ma'lumot yuborishni yakunlaganimizni (complete() chaqiruvini) bildirdik.
Call stream handleri
Metod qaytish qiymati stream qilib belgilansa, @GrpcStreamCall() dekoratori funksiya parametrini grpc.ServerDuplexStream sifatida beradi, u .on('data', callback), .write(message) yoki .cancel() kabi standart metodlarni qo'llab-quvvatlaydi. Mavjud metodlar bo'yicha to'liq hujjatni heredan topishingiz mumkin.
Muqobil ravishda, metod qaytish qiymati stream bo'lmasa, @GrpcStreamCall() dekoratori mos ravishda grpc.ServerReadableStream (hereni o'qing) va callback degan ikkita parametrni beradi.
Keling, to'liq duplex o'zaro aloqani qo'llab-quvvatlashi kerak bo'lgan BidiHelloni implement qilishdan boshlaymiz.
1@GrpcStreamCall()
2bidiHello(requestStream: any) {
3 requestStream.on('data', message => {
4 console.log(message);
5 requestStream.write({
6 reply: 'Hello, world!'
7 });
8 });
9}Bu dekorator qaytish parametri berilishini talab qilmaydi. Stream har qanday boshqa standart stream turi kabi boshqarilishi kutiladi.
Yuqoridagi misolda javob streamiga obyektlar yozish uchun write() metodidan foydalandik. .on() metodiga ikkinchi parametr sifatida uzatilgan callback funksiya servis yangi data chunk olganida har safar chaqiriladi.
LotsOfGreetings metodini implement qilamiz.
1@GrpcStreamCall()
2lotsOfGreetings(requestStream: any, callback: (err: unknown, value: HelloResponse) => void) {
3 requestStream.on('data', message => {
4 console.log(message);
5 });
6 requestStream.on('end', () => callback(null, { reply: 'Hello, world!' }));
7}Bu yerda requestStreamni qayta ishlash tugagach javob yuborish uchun callback funksiyasidan foydalandik.
Sog'liq tekshiruvlari
Kubernetes kabi orkestratorda gRPC ilova ishlayotganida, uning ishlab turganini va sog'lom holatda ekanini bilish kerak bo'lishi mumkin. gRPC Health Check specification gRPC clientlariga sog'liq holatini ochib berish imkonini beradigan standart bo'lib, orkestrator tegishli ravishda harakat qilishi mumkin.
gRPC health check qo'llab-quvvatlashini qo'shish uchun avval grpc-node paketini o'rnating:
1$ npm i --save grpc-health-checkSo'ng uni gRPC servisga onLoadPackageDefinition hook orqali ulash mumkin. E'tibor bering, protoPath health check va hero paketini ham o'z ichiga olishi kerak.
1import { HealthImplementation, protoPath as healthCheckProtoPath } from 'grpc-health-check';
2
3const app = await NestFactory.createMicroservice<MicroserviceOptions>(AppModule, {
4 options: {
5 protoPath: [
6 healthCheckProtoPath,
7 protoPath: join(__dirname, 'hero/hero.proto'),
8 ],
9 onLoadPackageDefinition: (pkg, server) => {
10 const healthImpl = new HealthImplementation({
11 '': 'UNKNOWN',
12 });
13
14 healthImpl.addToServer(server);
15 healthImpl.setStatus('', 'SERVING');
16 },
17 },
18});gRPC health probe konteynerli muhitda gRPC health checklarini sinash uchun foydali CLI.
gRPC metadatasi
Metadata - bu muayyan RPC chaqiruv haqida key-value juftliklari ro'yxati ko'rinishidagi ma'lumot bo'lib, keylar string, qiymatlar esa odatda string bo'ladi, ammo binary data ham bo'lishi mumkin. Metadata gRPC uchun opaque - u clientga chaqiruv bilan bog'liq ma'lumotni serverga taqdim etish va aksincha imkonini beradi. Metadata authentication tokenlar, request identifikatorlari, monitoring uchun teglar, shuningdek data to'plamidagi yozuvlar soni kabi ma'lumotlarni o'z ichiga olishi mumkin.
@GrpcMethod() handlerida metadatani o'qish uchun ikkinchi argumentdan (metadata) foydalaning; uning tipi Metadata (grpc paketidan import qilinadi).
Handlerdan metadatani qaytarish uchun ServerUnaryCall#sendMetadata() metodidan foydalaning (uchinchi handler argumenti).
1@Controller()
2export class HeroesService {
3 @GrpcMethod()
4 findOne(data: HeroById, metadata: Metadata, call: ServerUnaryCall<any, any>): Hero {
5 const serverMetadata = new Metadata();
6 const items = [
7 { id: 1, name: 'John' },
8 { id: 2, name: 'Doe' },
9 ];
10
11 serverMetadata.add('Set-Cookie', 'yummy_cookie=choco');
12 call.sendMetadata(serverMetadata);
13
14 return items.find(({ id }) => id === data.id);
15 }
16}Xuddi shuningdek, @GrpcStreamMethod() handlerlarida (subject strategy) metadatani o'qish uchun ikkinchi argumentdan (metadata) foydalaning; u Metadata tipida (grpc paketidan import qilinadi).
Handlerdan metadatani qaytarish uchun ServerDuplexStream#sendMetadata() metodidan foydalaning (uchinchi handler argumenti).
call stream handlers ichidan (@GrpcStreamCall() dekoratori bilan belgilangan handlerlar) metadatani o'qish uchun requestStream referensidagi metadata eventini tinglang, quyidagicha:
1requestStream.on('metadata', (metadata: Metadata) => {
2 const meta = metadata.get('X-Meta');
3});