ضغط الاستجابات في GRPC لـ ASP.NET CORE 3.0

تم إعداد ترجمة المقال استعدادًا لبدء الدورة التدريبية "C # ASP.NET Core Developer" .








في هذه الحلقة من سلسلتي حول gRPC و ASP.NET Core ، سننظر في كيفية توصيل وظيفة ضغط الاستجابة لخدمات gRPC.



ملاحظة : في هذه المقالة ، أغطي بعض تفاصيل الضغط التي تعلمتها من خلال التعرف على إعدادات وطرق الاتصال. من المحتمل أن تكون هناك طرق أكثر دقة وفعالية لتحقيق نفس النتائج.



هذه المقالة جزء من سلسلة حول gRPC و ASP.NET Core .



متى يجب تمكين الضغط في GRPC؟



إجابة مختصرة: هذا يعتمد على حمولاتك.

إجابة طويلة:

تستخدم gRPC مخزن بروتوكول مؤقت كأداة لتسلسل رسائل الطلب والرد المرسلة عبر الشبكة. ينشئ المخزن المؤقت للبروتوكول تنسيقًا تسلسليًا ثنائيًا مصممًا للحمولات الصغيرة الفعالة بشكل افتراضي. مقارنة بحمولات JSON العادية ، يوفر protobuf حجم رسالة أكثر تواضعًا. JSON مطول جدًا ومقروء. ونتيجة لذلك ، فإنه يتضمن أسماء الخصائص في البيانات المرسلة عبر الشبكة ، مما يزيد من عدد وحدات البايت التي يجب إرسالها.



يستخدم المخزن المؤقت للبروتوكول الأعداد الصحيحة كمعرفات للبيانات المنقولة عبر الشبكة. يستخدم مفهوم متغير 128 الأساسي ، والذي يسمح للحقول ذات القيم من 0 إلى 127 أن تتطلب بايت واحد فقط للنقل. في كثير من الحالات ، من الممكن قصر رسائلك على الحقول الموجودة في هذا النطاق. تتطلب الأعداد الصحيحة الكبيرة أكثر من بايت واحد.



لذلك ، تذكر أن حمولة protobuf صغيرة بالفعل ، حيث يهدف التنسيق إلى تقليل البايتات المرسلة عبر الشبكة إلى أصغر حجم ممكن. ومع ذلك ، لا يزال هناك احتمال لمزيد من الضغط بدون فقدان باستخدام تنسيق مثل GZip. يجب اختبار هذه الإمكانات على حمولاتك ، حيث سترى تقليل الحجم فقط إذا كانت حمولتك تحتوي على بيانات نصية متكررة كافية للاستفادة من الضغط. ربما بالنسبة لرسائل الاستجابة الصغيرة ، فإن محاولة ضغطها قد ينتج عنها وحدات بايت أكثر من استخدام رسالة غير مضغوطة ؛ من الواضح أنه ليس جيدًا.



وتجدر الإشارة أيضًا إلى الضغط العلوي للمعالج ، والذي يمكن أن يفوق المكسب الذي تحصل عليه من تقليل الحجم. يجب عليك تتبع وحدة المعالجة المركزية والذاكرة العامة للطلبات بعد تغيير مستوى الضغط للحصول على صورة كاملة لخدماتك.



لا يستخدم ASP.NET Core Server Integration الضغط افتراضيًا ، ولكن يمكننا تمكينه للخادم بأكمله أو لخدمات معينة. يبدو هذا كإعداد افتراضي معقول حيث يمكنك تتبع ردودك لأساليب مختلفة بمرور الوقت وتقييم فوائد ضغطها.



كيف يمكنني تمكين ضغط الاستجابة في GRPC؟



لقد وجدت حتى الآن طريقتين رئيسيتين لتوصيل ضغط استجابة gRPC. يمكنك تكوين هذا على مستوى الخادم بحيث تطبق جميع خدمات gRPC الضغط على الاستجابات ، أو على مستوى الخدمة الفردية.



التكوين على مستوى الخادم



services.AddGrpc(o =>
{
   o.ResponseCompressionLevel = CompressionLevel.Optimal;
   o.ResponseCompressionAlgorithm = "gzip";
});


Startup.cs GitHub



عند تسجيل خدمة gRPC في حاوية حقن التبعية باستخدام طريقة في AddGrpcالداخل ConfigureServices، لدينا الفرصة للتهيئة GrpcServiceOptions. في هذا المستوى ، تؤثر المعلمات على جميع خدمات gRPC التي ينفذها الخادم.



باستخدام طريقة التمديد الزائد AddGrpc، يمكننا توفير Action<GrpcServiceOptions>. في مقتطف الشفرة أعلاه ، اخترنا خوارزمية ضغط "gzip". يمكننا أيضًا CompressionLevelأن نحدد من خلال معالجة الوقت الذي نضحي به لضغط البيانات للحصول على حجم أصغر. إذا لم يتم تحديد المعلمة ، فسيتم استخدام الإعداد الافتراضي الحالي CompressionLevel.Fastest. في المقتطف السابق ، سمحنا بمزيد من الوقت للضغط لتقليل عدد البايت إلى أصغر حجم ممكن.



تكوين مستوى الخدمة



services.AddGrpc()
   .AddServiceOptions<WeatherService>(o =>
       {
           o.ResponseCompressionLevel = CompressionLevel.Optimal;
           o.ResponseCompressionAlgorithm = "gzip";
       });


Startup.cs GitHub



AddGrpcتعود المكالمة IGrpcServerBuilder. يمكننا استدعاء طريقة تمديد تسمى منشئ AddServiceOptionsلتقديم معلمات لكل خدمة على حدة. هذه الطريقة عامة وتقبل نوع خدمة gRPC التي يجب أن تنطبق عليها المعلمات.



في المثال السابق ، اخترنا توفير معلمات للمكالمات التي تتم معالجتها بواسطة التنفيذ WeatherService. في هذا المستوى ، تتوفر نفس الخيارات التي ناقشناها لتكوين مستوى الخادم. في هذا السيناريو ، لن تتلقى خدمات gRPC الأخرى على هذا الخادم خيارات الضغط التي قمنا بتعيينها لتلك الخدمة المعينة.



طلبات عملاء GRPC



الآن بعد تمكين ضغط الاستجابة ، نحتاج إلى التأكد من أن طلباتنا تشير إلى أن عميلنا يقبل المحتوى المضغوط. في الواقع ، يتم تمكين هذا افتراضيًا عند استخدامه GrpcChannelمع طريقة تم إنشاؤها ForAddress، لذلك لا نحتاج إلى القيام بأي شيء في كود العميل الخاص بنا.



var channel = GrpcChannel.ForAddress("https://localhost:5005");


Program.cs GitHub



ترسل القنوات التي تم إنشاؤها بهذه الطريقة بالفعل رأس "grpc-Accept-encoding" يتضمن نوع ضغط gzip. يقرأ الخادم هذا العنوان ويحدد أن العميل يسمح بإرجاع الاستجابات المضغوطة.



تتمثل إحدى طرق تصور تأثير الضغط في تمكين التسجيل لتطبيقنا في وقت التصميم. يمكن القيام بذلك عن طريق تعديل الملف على appsettings.Development.jsonالنحو التالي:



{
 "Logging": {
   "LogLevel": {
       "Default": "Debug",
       "System": "Information",
       "Grpc": "Trace",
       "Microsoft": "Trace"
   }
 }
}


appsettings.Development.json GitHub



عند بدء تشغيل خادمنا ، نحصل على سجلات وحدة تحكم أكثر تفصيلاً.



info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
     Executing endpoint 'gRPC - /WeatherForecast.WeatherForecasts/GetWeather'
dbug: Grpc.AspNetCore.Server.ServerCallHandler[1]
     Reading message.
dbug: Microsoft.AspNetCore.Server.Kestrel[25]
     Connection id "0HLQB6EMBPUIA", Request id "0HLQB6EMBPUIA:00000001": started reading request body.
dbug: Microsoft.AspNetCore.Server.Kestrel[26]
     Connection id "0HLQB6EMBPUIA", Request id "0HLQB6EMBPUIA:00000001": done reading request body.
trce: Grpc.AspNetCore.Server.ServerCallHandler[3]
     Deserializing 0 byte message to 'Google.Protobuf.WellKnownTypes.Empty'.
trce: Grpc.AspNetCore.Server.ServerCallHandler[4]
     Received message.
dbug: Grpc.AspNetCore.Server.ServerCallHandler[6]
     Sending message.
trce: Grpc.AspNetCore.Server.ServerCallHandler[9]
     Serialized 'WeatherForecast.WeatherReply' to 2851 byte message.
trce: Microsoft.AspNetCore.Server.Kestrel[37]
     Connection id "0HLQB6EMBPUIA" sending HEADERS frame for stream ID 1 with length 104 and flags END_HEADERS
trce: Grpc.AspNetCore.Server.ServerCallHandler[10]
     Compressing message with 'gzip' encoding.
trce: Grpc.AspNetCore.Server.ServerCallHandler[7]
     Message sent.
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
     Executed endpoint 'gRPC - /WeatherForecast.WeatherForecasts/GetWeather'
trce: Microsoft.AspNetCore.Server.Kestrel[37]
     Connection id "0HLQB6EMBPUIA" sending DATA frame for stream ID 1 with length 978 and flags NONE
trce: Microsoft.AspNetCore.Server.Kestrel[37]
     Connection id "0HLQB6EMBPUIA" sending HEADERS frame for stream ID 1 with length 15 and flags END_STREAM, END_HEADERS
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
     Request finished in 2158.9035ms 200 application/grpc


Log.txt GitHub



في السطر السادس عشر من هذا السجل ، نرى أن WeatherReply (في الواقع ، مجموعة مكونة من 100 عنصر WeatherData في هذا المثال) تم إجراء تسلسل لها إلى protobuf ويبلغ حجمها 2851 بايت.



لاحقًا ، في السطر 20 ، نرى أنه تم ضغط الرسالة باستخدام ترميز gzip ، وفي السطر 26 ، يمكننا رؤية حجم إطار البيانات لهذه المكالمة ، وهو 978 بايت. في هذه الحالة ، تم ضغط البيانات جيدًا (بنسبة 66٪) لأن عناصر WeatherData المتكررة تحتوي على نص والعديد من القيم الموجودة في الرسالة تتكرر.



في هذا المثال ، كان لضغط gzip تأثير جيد على حجم البيانات.



تعطيل ضغط الاستجابة في تنفيذ أسلوب الخدمة



يمكن التحكم في ضغط الاستجابة في كل طريقة. لقد وجدت حاليًا طريقة لإيقاف تشغيله. عند تمكين الضغط لخدمة أو خادم ، يمكننا إلغاء الاشتراك في الضغط كجزء من تنفيذ أسلوب الخدمة.



دعنا نلقي نظرة على سجل الخادم عند استدعاء طريقة الخدمة التي تنقل رسائل WeatherData من الخادم. إذا كنت ترغب في معرفة المزيد حول البث إلى الخادم ، فيمكنك قراءة مقالتي السابقة تدفق البيانات إلى خادم باستخدام gRPC و .NET Core .



info: WeatherForecast.Grpc.Server.Services.WeatherService[0]
     Sending WeatherData response
dbug: Grpc.AspNetCore.Server.ServerCallHandler[6]
     Sending message.
trce: Grpc.AspNetCore.Server.ServerCallHandler[9]
     Serialized 'WeatherForecast.WeatherData' to 30 byte message.
trce: Grpc.AspNetCore.Server.ServerCallHandler[10]
     Compressing message with 'gzip' encoding.
trce: Microsoft.AspNetCore.Server.Kestrel[37]
     Connection id "0HLQBMRRH10JQ" sending DATA frame for stream ID 1 with length 50 and flags NONE
trce: Grpc.AspNetCore.Server.ServerCallHandler[7]
     Message sent.


Log.txt GitHub



في السطر السادس ، نرى أن حجم رسالة WeatherData الفردية يبلغ 30 بايت. يتم ضغط السطر 8 ، ويوضح السطر 10 أن البيانات الآن يبلغ طولها 50 بايت - أكثر من الرسالة الأصلية. في هذه الحالة ، لا فائدة لنا من ضغط gzip ، نرى زيادة في الحجم الإجمالي للرسالة المرسلة عبر الشبكة.



يمكننا إيقاف الضغط لرسالة معينة عن طريق ضبط طريقة WriteOptionsالاتصال في الخدمة.



public override async Task GetWeatherStream(Empty _, IServerStreamWriter<WeatherData> responseStream, ServerCallContext context)
{
   context.WriteOptions = new WriteOptions(WriteFlags.NoCompress);

   //  ,    
}


WeatherService.cs GitHub



فإننا يمكن أن يحدد WriteOptionsفي ServerCallContextالجزء العلوي من طريقة خدمتنا. نحن نمرر في حالة جديدة WriteOptionsتم WriteFlagsضبطها على NoCompress. يتم استخدام هذه المعلمات للإدخال التالي.



عند تدفق الردود ، يمكن أيضًا تعيين هذه القيمة على IServerStreamWriter.



public override async Task GetWeatherStream(Empty _, IServerStreamWriter<WeatherData> responseStream, ServerCallContext context)
{   
   responseStream.WriteOptions = new WriteOptions(WriteFlags.NoCompress);

   //     
}


WeatherService.cs GitHub



عندما نستخدم هذه المعلمة ، تُظهر السجلات أنه لا يتم تطبيق ضغط على استدعاءات طريقة الخدمة هذه.



info: WeatherForecast.Grpc.Server.Services.WeatherService[0]
     Sending WeatherData response
dbug: Grpc.AspNetCore.Server.ServerCallHandler[6]
     Sending message.
trce: Grpc.AspNetCore.Server.ServerCallHandler[9]
     Serialized 'WeatherForecast.WeatherData' to 30 byte message.
trce: Microsoft.AspNetCore.Server.Kestrel[37]
     Connection id "0HLQBMTL1HLM8" sending DATA frame for stream ID 1 with length 35 and flags NONE
trce: Grpc.AspNetCore.Server.ServerCallHandler[7]
     Message sent.


Log.txt GitHub



الآن يبلغ طول رسالة 30 بايت 35 بايت في إطار البيانات. هناك حمل صغير يزيد حجمه عن 5 بايت ولا داعي للقلق بشأنه هنا.



تعطيل ضغط الاستجابة من عميل GRPC



بشكل افتراضي ، تتضمن قناة gRPC معلمات تحدد الترميزات التي تقبلها. يمكن تكوين هذه عند إنشاء قناة إذا كنت تريد تعطيل ضغط الردود من العميل الخاص بك. بشكل عام ، سأتجنب هذا وأترك ​​الخادم يقرر ما يجب فعله ، لأنه يعرف بشكل أفضل ما يمكن وما لا يمكن ضغطه. ومع ذلك ، قد تحتاج في بعض الأحيان إلى مراقبة هذا من العميل.



الطريقة الوحيدة التي وجدتها في بحثي عن API حتى الآن هي إعداد قناة عن طريق تمرير مثيل GrpcChannelOptions. إحدى خصائص هذه الفئة هي CompressionProviders- IList<ICompressionProvider>. بشكل افتراضي ، عندما تكون هذه القيمة فارغة ، يضيف تطبيق العميل تلقائيًا موفر ضغط Gzip. هذا يعني أن الخادم يمكنه استخدام gzip لضغط رسائل الاستجابة ، كما رأينا.



private static async Task Main()
{
   using var channel = GrpcChannel.ForAddress("https://localhost:5005", new GrpcChannelOptions { CompressionProviders = new List<ICompressionProvider>() });
   var client = new WeatherForecastsClient(channel);
   var reply = await client.GetWeatherAsync(new Empty());
   foreach (var forecast in reply.WeatherData)
  {
       Console.WriteLine($"{forecast.DateTimeStamp.ToDateTime():s} | {forecast.Summary} | {forecast.TemperatureC} C");
   }
   Console.WriteLine("Press a key to exit");
   Console.ReadKey();
}


Program.cs GitHub

في مثال كود العميل هذا ، نقوم بإعداد GrpcChannelنسخة جديدة ودفعها GrpcChannelOptions. نحن نخصص CompressionProvidersقائمة فارغة للممتلكات . نظرًا لأننا الآن لا نحدد مقدمي الخدمة في قناتنا عند إنشاء المكالمات وإرسالها عبر تلك القناة ، فلن يتم تضمين أي ترميزات ضغط في رأس ترميز grpc-Accept-encoding. يرى الخادم هذا ولا يقوم بتنسيق gzip للاستجابة.



ملخص



في هذا المنشور ، استكشفنا إمكانية ضغط رسائل الاستجابة من خادم gRPC. وجدنا أنه في بعض الحالات (وليس كلها) ، يمكن أن يؤدي ذلك إلى حمولة أقل. لقد رأينا أنه ، بشكل افتراضي ، تتضمن مكالمات العميل قيمة gzip "grpc-Accept-encoding" في الرؤوس. إذا تم تكوين الخادم لتطبيق الضغط ، فسيفعل ذلك فقط إذا كان نوع الضغط المدعوم يطابق رأس الطلب.



يمكننا التهيئة GrpcChannelOptionsعند إنشاء قناة للعميل لتعطيل ضغط gzip. على الخادم ، يمكننا تكوين الخادم بأكمله مرة واحدة أو خدمة منفصلة لضغط الاستجابات. يمكننا أيضًا تجاوز هذا وتعطيله على مستوى كل طريقة خدمة.



لمعرفة المزيد حول gRPC ، يمكنك قراءة جميع المقالات التي تعد جزءًا منسلسلة gRPC و ASP.NET Core .






كل شيء عن الدورة






اقرأ أكثر






All Articles