عنوان:

‫Protocol Buffers فرمتی برای تبادل دیتا


نویسنده: خلیلی
تاریخ: ۱۳۹۸/۰۲/۲۰ ۲۳:۲۵
آدرس: www.dntips.ir
Protocol Buffers  فرمتی جدید برای تبادل دیتا بین سرور و کلاینت میباشد که توسط گوگل طراحی و ساخته شده است و همچنین اکثر زیرساخت‌های گوگل از این فرمت برای تبادل اطلاعات بین سرویس‌ها استفاده میکنند. Protocol Buffer را میتوان به عنوان جایگزینی برای JSON/XML بکار برد و به دلایل زیادی که در ادامه درباره‌ی آن صحبت میکنیم میتواند گزینه‌ی مناسبی برای Microservices‌ها باشد و همچنین سرعت بالا، سادگی در استفاده، پشتیبانی از زبان‌های برنامه نویسی متعدد از ویژگی‌های منحصر به فرد این زبان برای تبادل اطلاعات است.
در ابتدا میخواهم کمی راجع به تبادل دیتا، از گذشته تا به حال صحبت کنم:
مدت‌ها است از csv‌ها برای تبادل اطلاعات استفاده میشود؛ اما مزایا و معایب خاص خود را دارد، از جمله اینکه parse کردن راحتی دارد، راحتی در خواندن و غیره. معایبش هم این‌است که گارانتی برای نوع type ندارد و اینکه المان‌هایی که حاوی کاما هستند با مشکل رو به رو میشوند و غیره...
بعد از آن دیتابیس‌ها وارد کار شدند که همه‌ی ما کم و بیش با آنها آشنا هستیم؛ در آن‌ها دیتا‌ها کاملا با type مشخصی هستند و اینکه در table‌های مجزا ذخیره میشوند. مشکلاتش هم این است که دیتا باید حتما flat باشد و اینکه بین دیتابیس‌های مختلف definition‌های مختلفی وجود دارد.
بعد از آن با JSON آشنایی داریم که مزایای زیادی دارد و مدت هاست که مورد استفاده قرار گرفته و شامل این‌است که دیتا در آن میتواند تو در تو ذخیره شود، آرایه داشته باشد، کاملا در دنیای وب مورد قبول واقع شده، به وسیله‌ی هر زبانی قابل خواندن‌است و اینکه خیلی راحت در شبکه قابل انتقال می‌باشد. معایبش هم این‌است که خیلی راحت میتواند خیلی بزرگ شود و اینکه قابلیت کامنت، متادیتا و داکیومنتیشن هم ندارد.
اما میرسیم به گزینه‌ی آخر که protocol buffers است و ابزاری هست که ممکن است خیلی‌ها با آن آشنا نباشند. قبل از بررسی دقیقش به مزایا و معایبش می‌پردازیم. مزایا آن این‌است که دیتا در آن کاملا typed میباشد. دیتای آن به صورت اتوماتیک compressed می‌شود. اسکیما در آن توسط زبان منحصر به فردش قابل تعریف است و توسط تقریبا همه‌ی زبان‌های برنامه نویسی مشهور قابل استفاده‌است. تغییرات اسکیما در آن کنترل شده‌است. 3 تا 10 بار کم حجم‌تر و 20 تا 100 بار سریعتر از xml است و اینکه از روی آن می‌توان کد آماده برای استفاده تولید کرد که سرعت برنامه نویسی را خیلی بالا می‌برد. از مشکلاتش هم این است که ممکن است در یک سری از زبان‌های برنامه نویسی خاص قابل استفاده نباشد. البته بر روی C#, Nodejs, C, Go, Python ,... به خوبی کار می‌کند. مشکل دیگرش هم این‌است که نمی‌شود فایلش را با ادیتورها باز کرد و قابل خواندن نیست؛ چون serialized و compressed شده‌است.

طریقه‌ی استفاده از پروتکل بافر
در طی مختصری، نحوه‌ی کار کردن با این ابزار را مورد بررسی قرار می‌دهیم. طبق شکل زیر:

ابتدا از طریق فرمت protocol buffer، فایل‌های خود را که قرار است انتقال داده شوند، مینویسیم.

سپس بصورت خودکار برای زبان برنامه نویسی مطبوع خود آن را generate میکنیم.

کد‌های تولید شده بصورت خودکار و کاملا آماده هستند و ضمن اینکه encode/decode شدن بصورت خودکار توسط فریم ورک انجام شده و قابلیت تعامل بین زبان‌های مختلف برنامه نویسی یا سرویس‌های مختلف برقرار است.


نکته:

  •  بعضی از دیتابیس‌ها از فرمت protocol buffers پشتیبانی میکنند.
  • اکثر فریم ورک‌های RPC شامل gRPC از پروتکل بافر برای تبادل دیتا استفاده میکنند.
  • گوگل برای تمام سرویس‌های داخلی خود از آن استفاده میکند.
  • بعضی از پروژه‌های خیلی بزرگ مثل etcd از پروتکل بافر برای تبادل دیتا استفاده میکنند.
  • ما در این مقاله از ورژن 3 پروتکل بافر استفاده میکنیم.


نصب Code generator

برای اینکه بتوانیم از طریق فایل‌هایی که میسازیم کد‌های generate شده را تولید کنیم، احتیاج به کامپایلر مربوطه را داریم.

اگر از MacOSX استفاده میکنید، به راحتی با استفاده از دستور زیر می‌توانید آن را نصب کنید:

brew install protobuf

اگر هم از ویندوز استفاده میکنید، از این طریق میتوانید نسخه‌ی مورد نظر را به راحتی دانلود و مورد استفاده قرار بدهید:

https://github.com/google/protobuf/releases
https://github.com/google/protobuf/releases/download/v3.5.1/protoc-3.5.1-win32.zip

حالا میخواهیم اولین فایل خود را با این فرمت بسازیم.

اول از همه با هم نگاهی به ساختار فایل مربوطه میاندازیم:

همانطور که در تصویر فوق می‌بینید، همه چیز به سادگی مشخص است؛ ورژن 3 که آخرین ورژن پروتکل بافر میباشد، آیتمی به نام MyMessage با پراپرتی‌هایی مشخص شده از Type بخصوص، تعریف شده‌اند، تگ‌ها هم باید به ترتیب وارد شده باشند.

حالا میخواهیم بصورت واقعی protocol buffer خود را طراحی کرده و سپس از روی آن کد‌های مربوطه را generate نماییم؛ به نام sample.proto بصورت زیر:

syntax = "proto3";

package helloworld;

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

در فایل فوق علاوه بر تعریف‌های اولیه، یک سرویس را هم اضافه کرده‌ایم و همچنین متدی را با ورودی و خروجی‌های مشخصی ایجاد کرده‌ایم (امکانات پروتکل بافر خیلی بیشتر از این موارد است؛ از جمله فرمت‌های آرایه و غیره را نیز پشتیبانی میکند، همچنین از روشی برای versioning استفاده میکند که obsolete کردن پراپرتی‌ها و نسخه بندی را بسیار راحت می‌کند و ...). به سادگی قابلیت طراحی و پیاده سازی سرور و کلاینت مربوط به این آیتم ایجاد شده با استفاده از زبان‌های برنامه نویسی مختلف فراهم میباشد. حال کافی‌است که پروتکل بافر خود را با زبان دلخواه خود generate کنیم. در قسمت زیر برای زبان‌های برنامه نویسی Go و #C، کد‌ها را تولید میکنیم.

protoc sample.proto --go_out=plugins=grpc:.  
protoc sample.proto --csharp_out=.

بعد از تولید شدن کد‌ها با استفاده از زبان برنامه نویسی دلخواه خود میتوانید مشاهد کنید سرویس ها، تایپ‌ها و غیره همگی ساخته شده‌اند و کاملا آماده‌ی استفاده هستند.

در مقاله‌ی بعدی به آشنایی با gRPC می‌پردازیم و ضمن اینکه یک سرور با #C و یک کلاینت با زبان برنامه نویسی Go را نوشته که از طریق پروتکل بافر با هم به تبادل اطلاعات می‌پردازند!

نظرات

  • محمد جواد ابراهیمی در ۱۳۹۸/۰۲/۲۴ ۱۳:۳۷
    مقاله جالب و خوبی بود فقط ذکر چند نکته خالی از لطف نیست.
    protobuf در مرورگر پشتیبانی نمی‌شود و برای کار با آن می‌توان از  protobuf.js (پیاده سازی Protocol Buffers برای Javascript) استفاده کرد 
    برای استفاده از protobuf در دات نت نیز 2 کتابخانه موجود است
    1- کتابخانه  Google.Protobuf : که پیاده سازی و استفاده از آن شبیه نسخه اصلی protobuf است. (اطلاعات بیشتر)
    2- کتابخانه  protobuf-net : که پیاده سازی و استفاده از آن شبیه بقیه سریالایزر‌های دات نتی بوده و بیشتر متمایل به سی شارپ است. (اطلاعات بیشتر)
    به نظرم دومی بیشتر باب میل سی شارپی‌ها باشد و نیز ساده تر. با دیدن مثال هر دو کتابخانه میتوانید بهتر متوجه این تفاوت شوید.
    لینک زیر هم به مقایسه این دو کتابخانه پرداخته : 
    • خلیلی در ۱۳۹۸/۰۲/۲۵ ۱۴:۵۸
      ممنون برای نکات مفیدی که اضافه کردید
      البته در مقاله‌ی بعدی به این موضوع اشاره خواهد شد و برای استفاده از آن در browser‌ها بنده grpc-gateway  رو پیشنهاد میکنم
      پروتکل بافر به خودی خود فرمتی هست برای تبادل دیتا، موارد بیشتر به gRPC مربوط میشه مثل HTTP/2 و غیره که در جای خودش مفصل بررسی خواهد شد.
      • محمد جواد ابراهیمی در ۱۳۹۸/۰۲/۲۶ ۴:۱۲
        کتابخانه جالبی بود ولی به نظرم خوب نیست زیرا در واقع یک Reverse Proxy هست که روی سرور بین وبسرویس و کلاینت قرار میگیرد و Json RESTful رو به GRPC تبدیل و ترجمه می‌کند، در این صورت ما از مزایای واقعی GRPC مانند schema-based بودن، Bi-directional streaming و حتی خود Protobuf محروم می‌شویم.
        من برای استفاده از GRPC در مرورگر پروژه خود grpc رو برای کلاینت‌های وبی پیشنهاد میکنم (grpc-web)
        gRPC-Web provides a Javascript library that lets browser clients access a gRPC service  
        • خلیلی در ۱۳۹۸/۰۲/۲۷ ۲۳:۹
          لفظ جمله‌ی "خوب نیست" به نظرم کمی غیر عادلانس راجع به grpc-gateway
          اول از همه اینکه ما به عنوان برنامه نویس اول از همه برای انتخاب ابزار به نظرم باید با خودمان صادق باشیم، من شخصا توی این چندین سال که کار کردم تا به حال احتیاجی به Bi-directional streaming نداشتم! (ضمن اینکه روش هایی هست که اگر در صورت نیاز مجبور باشیم از آن استفاده کنیم هر چند رسمی نیستند)
          دوم اینکه اصلا متوجه نمیشم چطور میفرمایید "از مزایای schema-based بودن، حتی خود protobuf محروم میشویم"، هیچ چیزی قرار نیست تغییر کنه، سرویس‌ها اگر احتیاج به restful Api داشته باشند از این gateway استفاده و بقیه‌ی سرویس‌ها تماما به حالت عادی خودشان کار میکنند، پس اصالا جمله‌ی فوق رو نمیتونم بپذیرم
          ضمن اینکه، کلاینت هایی داشته باشیم که قابلیت کار کردن با protobuf رو نداشته باشند پس حتما تا سال‌های آینده نیاز اساسی به یک سیستم restful هنوز هم دیده میشه، شما باید نگاهتون به یک پروژه‌ی واقعی باشه، نه یک hello world. سیستم ممکنه بخواد api به مشتری هاش بده که خیلی متداوله و خیلی دلایله دیگه پس این نیاز رو باید جدی بگیریم
          و همینطور اینکه با استفاده از gateway به راحتی swagger هم میتونید داشته باشید
          و مساله‌ی دیگه که شخصا برام مهمه سادگیه کاره، عملا فقط کافیه توی protobuf هامون تعریف کنیم که این سرویس رو میخوایم ازش استفاده کنیم و تمام!
          نکته آخر: در کل به نظرم بحث سر انتخاب‌های ابزار‌های جزئی کاملا به بیزینس ربط داره بحث کردن بدین صورت به نظرم فقط وقت میگیره که این ابزار خوبه و این بده، ترجیه میدم این جزیات رو به خواننده بسپارم چون بنا به هر پروژه‌ی خاص ممکنه متفاوت باشه و تمرکزمون رو روی اصل مساله بذاریم.
          • محمد جواد ابراهیمی در ۱۳۹۸/۰۲/۲۸ ۱۰:۲۳
            به این این دلیل گفتم خوب نیست چون برای موضوع جاری (استفاده از Protobuf و GRPC) خوب نیست و گرنه اگر موضوع مقاله "تبدیل Json RESTful به GRPC بود" قطعا این کتابخانه میتوانست بهترین باشد.
            حالا چرا میگم خوب نیست؟ چون طبق تصویر زیر که از داکیومنت ریپازیتوریش برداشته شده و کاملا گویاست، grpc-gateway به عنوان یک Reverse Proxy درخواست‌های کلاینت را به GRPC تبدیل و ترجمه میکند و ارتباط Client با سرور همچنان Json RESTful بوده و پس عملا مزایای GRPC و Protobuf رو از بین میبره. اصلا اگر قراره همچنان ارتباط سرور با کلاینت RESTful باشه دیگه چرا GRPC ؟!

             ضمن اینکه الان کتابخانه third-party برای GRPC در اکثر زبان‌ها (# C++, Python, Ruby, Objective-C, PHP, C  و...) وجود داره پس مشکلی بابت عدم پشتیبانی نخواهیم داشت.
            قطعا که Json RESTful به این زودی‌ها محبوبیت خودشو از دست نخواهد داد و grpc-gateway هم کاربرد خاص خودش رو خواهد داشت ولی عرض من این هست که با پوشاندن لباس Json RESTful به GRPC اون رو فلج نکنیم! مخصوصا زمانی که امکان استفاده از کتابخانه‌های GRPC رو در تمام کلاینت هامون داریم.


            • علیرضا سالاری مقدم در ۱۳۹۸/۰۲/۲۸ ۱۱:۲۰
              هدف این gateway در واقع بیشتر برای معماری مایکروسرویس هاست.
              ارتباط کلاینت‌ها با gateway به صورت Restful و ارتباط gateway با سرویس‌ها (و خود سرویس‌ها با یکدیگر) با grpc.
            • خلیلی در ۱۳۹۸/۰۲/۲۸ ۱۴:۲۳
              چند تا مساله را میگم و دیگه این بحث رو ادامه نمیدم به هیچ وجه (چون مساله‌ی مطرح شده هیچ ربطی به این مقاله‌ی خاص نداره و فقط چون این وبسایت بازدید کننده‌ی زیادی داره مجبورم کامل توضیح بدم که ما اشتباها خط غلط به کسی ندیم و گمراه نکنیم کسی رو ناخواسته)
              1) موضوع این مقاله فقط و فقط protocol buffers بود و هیچ ربطی به هیچ زبان برنامه نویسی نداشت و وارد شدن به این مساله در اینجا اشتباه است، من حتی در این مقاله توضیح ندادم که با استفاده از grpc و protobuf مستقیما نمیتونیم از طریق browser درخواست بدیم، و وارد کردن این مساله و همینطور لایبری‌های مختلف جاوا اسکریپتی و سی شارپی جایگاهش در این نقاله نبود چون نگاه خیلی بالاتر از این بود تا اینکه به سطح بیاییم و بخواهیم راجع به یک کتابخونه خاص و یک زبان خاص و مقایسه بین آنها رو انجام بدیم!
              2) از اینجا میتونید مشاهد کنید که حداقل به صورت رسمی (اینکه توی گوگل سرچ زده بشه و اولین نتیجه به عنوان سند در پاسخ داده بشه مورد قبول نیست، چون قطعا ممکنه به صورت غیر رسمی ابزار هایی باشه که مورد قبول در تولید یک سیستم روی پروداکشن نیست!) زبان‌های swift, rust , ... هنوز به صورت رسمی ساپورت نمیشند! پس اگر نگاهمون به عنوان توسعه دهنده فراتر از یک مثال hello-world باشه باید اینو مد نظر بگیریم.
              3) تکنولوژی باید به ساختار و سیستم ما کمک کنه نه اینکه خودش bottleneck سیستم بشه، تکنولوژی جذابه ولی نکته‌ی مهم اینه که ما اول از همه به این مساله دقت کنیم که باید به عنوان برنامه نویس روی تکنولوژی سوار باشیم نه برعکس (بزرگترین مشکل وقتی پیش میاد که هیجان زده ابزارو تکنولوژی جدید آشنا بشیم و بخواهیم در سیستم استفاده کنیم! هیجان بیش از حد قاتل سسیستمه) با انتخاب اشتباهمون کل سازمان رو دچار مشکل نکنیم، مخصوصل وقتی با یک تکنولوژی آشنا میشیم وفور لایبری‌های مختلف نباید ما رو گمراه کنه!
              4) وقتی دیدتون به عنوان توسعه دهنده از مونولوتیک خارج میشه و به مایکروسرویس نگاه میکنید حتما باید از زاویه‌ی خیلی بالاتری نگاه کنید، جایی که توقع میره همه‌ی سیستم بتونه به همدیگه به بهترین نحو بدون مشکل کار کنه پس انتخاب ابزار خیلی اهمیتش بالاتره از تکنولوژی جدید که شاید تازه هم باهاش آشنا شده باشید، api-gateway  به عنوان یک اصل برای معماری ماکروسرویس پذیرفته شده و حتی خیلی توصیه میشه
              5) نگاه به سیستم‌های طراحی شده‌ی دنیا هم خیلی بهمون کمک میکنه حتی پروژه‌های بزرگی مثله etcd از gateway استفاده کرده اند چرا که سنگ بزرگ نشانه نزدنه و ریسک تغییرات خیلی بزرگ و بنیادین کل نرم افزار رو به چالش میشکه و علاوه بر اینکه هزینه‌ی زیاد learning curve برای کله تیم داره، ریسک شکست رو هم خیلی بالا میبره، شما فرض کنید که برنامه نویس فرانت اند که تا حالا تو عمرش به غیر از restful api چیز دیگه ای ندیده ناگهان با کلی ابزار جدید آشنا باید بشه که حتی ممکنه رسمی و صد در صد قابل اتکا هم نباشه، این خودش شروع fail شدن یک سیستم رو نتیجه میده!
              در مقاله‌ی بعدی سعی میکنم مثال عملی با استفاده از زبان‌های برنامه نویسی مختلف بزنم و توضیحات جزئی‌تری بدم و موارد بیشتر رو فقط و فقط به خواننده که خودش قطعا توسعه دهنده هست و میتونه بالا و پایین رو ببینه میسپارم!
              ممنون
              • محمد جواد ابراهیمی در ۱۳۹۸/۰۲/۲۸ ۱۵:۳۶
                لطفا پیام بنده رو با دقت بخونید. من نگفتم باید کل دنیا رو به GRPC مجهز کنیم! گفتم جایی که میتونیم از خود grpc استفاده کنیم، استفاده از grpc-gateway و تبدیلش به RESTful اشتباه هست چون مزایاش رو از بین میبره. اشاره هم کردم که قطعا grpc-gateway هم کاربرد خاص خودش رو داره و کاربردشم جایی هست که کلاینت ما امکان استفاده از grpc رو نداشته باشه (مانند همون rust و swift یا آشنا نبودن برنامه نویس‌های فرانت که شما هم اشاره کردید) وگرنه دلیل مقاومتتون رو متوجه نمیشم! در باره این گزاره بدیهی که وقتی از grpc استفاده میکنیم و کلاینتمون هم مشکلی نداره باهاش باز اصرار بر دفاع از grpc-gateway دارید! مثال صریح این قضیه هم در همین میکروسرویس‌ها هست و استفاده از grpc-gateway برای ارتباطات بین میکروسرویس‌ها اصلا توجیه پذیر نیست و توصیه نمیشه.
                ضمنا همین پروژه etcd خروجی grpc داره و  Go Client و CLI اش  "صرفا" از grpc برای api call استفاده میکنه و از خروجی grpc-gateway رو برای کلاینت هایی که grpc ساپورت نمیکنن ارائه داده. 
                و در آخر به نظرم گفتگوی کاملی در این باره شده و تصمیم رو به عهده خوانندگان میسپارم و این بحث رو ادامه نمیدم و امیدوارم این گفتگو مخاطبین رو از به بیراهه کشوندنشون جلوگیری کرده باشه و به دوستان کمک کرده باشه که با "دید باز" و "آگاهی کامل"، یک ابزار رو انتخاب کنند. 
                • علیرضا سالاری مقدم در ۱۳۹۸/۰۲/۲۸ ۲۲:۴۷
                  قرار نیست تا مایکروسرویس‌ها از طریق grpc-gateway در ارتباط باشند!
                  مایکروسرویس‌ها با کلاینت مخصوص زبان خودشون با یکدیگر در ارتباطند و grpc-gateway صرفا یک تبدیل کننده Restful به grpc هست تا با بقیه سیستم یکپارچه بشه