پروتوباف، یک راهنمای عملی (Python . Go)

با گسترش معماری‌های میکروسرویسی، نیاز به انتقال متعدد داده بین سرویس‌های ریز و درست خیلی پررنگ شده است. گاهی یک ریکوئست ساده از از سمت کاربر، منتهی به صد ها رکوئست درون-سیستمی در یک محصول می‌شود. برای آشنایی بیشتر با این چالش و یکی از روزآمد ترین راه حل‌های آن، شما را به خواندن ادامه متن دعوت می‌کنم.

انکودینگ چیست؟

به عمل تبدیل یک ساختمان داده به آرایه بایت انکد encode کردن میگویند. این عمل باید برگشت پذیر باشد. حالا چرا encoding این قدر مهم است؟ چون هر زمان قرار باشد داده‌ای از طریق شبکه جابجا شود یا در جایی ذخیره شود، لازم است به شیوه‌ای انکد شود. ممکن است زبان و پلتفرم مبدا و مقصد / نویسنده و خواننده کاملا متفاوت باشند. لذا encoding های معروف، استاندارد شده هستند و در زبان‌های برنامه نویسی مختلف پیاده‌سازی شده‌اند.

در این بین، برخی encoding ها توسط انسان قابل خوانده شدن هستند. مثل json. در زیر نمونه‌ای از یک ساختمان داده که به شکل جیسون encode شده میبینید.

یک نمونه json. انواع داده‌های ساپورت شده را میبینید.
یک نمونه json. انواع داده‌های ساپورت شده را میبینید.

امروزه اکثر زبان‌های برنامه نویسی و حتی فریم‌ورک‌های سخت افزاری قابلیت encode/decode کرده جیسون را دارا هستند.

پروتوباف proto

انکد شده‌ی protobuf، بطور کامل قابل خوانده‌شدن توسط انسان نیست. اما در عوض این کاستی، سرعت encode/decode و نیز حجم بسیار پایین‌تر را به ارمغان می‌آورد. پیاده‌سازی‌های encoder/decoder پروتو برای بیش از ۱۰ زبان برنامه‌نویسی پرکاربرد، از جمله گو و پایتون، توسط گوگل ارائه می‌شود و کاملا اپن‌سورس هستند.

اکنون به این شکل میتوانید کامپایلر protoc را نصب کنید:

sudo apt install protobuf-compiler
go get google.golang.org/protobuf/cmd/protoc-gen-go \
       google.golang.org/grpc/cmd/protoc-gen-go-grpc

پروتوی پروفایل یوزر و کامپایل آن

حال یک نمونه فایل پروتو را با هم مشاهده می‌کنیم. در اینجا ساختار داده‌ای که می‌خواهیم ارسال/ذخیره، یا بطور کلی encode کنیم، آورده شده است.

یک ساختمان از پیش تعیین شده برای encode کردن اطلاعات پروفایل
یک ساختمان از پیش تعیین شده برای encode کردن اطلاعات پروفایل

اکنون این فایل پروتو را با کامپایلر protoc کامپایل می‌کنیم:

$ protoc --go_out=. --go_opt=paths=source_relative profile.proto

خروجی این عمل یک فایل autogen به زبان گو هست، که امکانات لازم برای encode/decode را فراهم می‌آورد. در فایل ساخته شده، موارد زیادی دیده می‌شود. از جمله آنها، یک struct، شامل فیلد‌های متناظر در Message تعریف شده در فایل پروتو است:

به تگ‌های زرد توجه نکنید. فیلد‌ها مهم هستند. کامنت‌ها در کد ساخته شده منتقل می‌شوند.
به تگ‌های زرد توجه نکنید. فیلد‌ها مهم هستند. کامنت‌ها در کد ساخته شده منتقل می‌شوند.

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

لازم بود تا فایل پروتو را برای پایتون هم کامپایل کنم. چون در نظر داریم یک نمونه پروفایل را با یک کد Go encode کنیم و نتایج binary شده را در یک فایل بنویسیم. سپس با یک اسکریپت پایتون، همان فایل را بخوانیم و decode کنیم. انتظار میرود داده‌ها در هر دو سو یکسان باشد.

اما پیش از این کار؛ یک تفاوت مهم که تا الان بین پروتو و encodingای مثل json (که اول متن بهش اشاره کردم) دیدیم را نام ببرید؟

در پروتو باید از قبل مشخص کنیم که چه فیلدهایی داریم و نوع (type) هر فیلد دقیقاً چیست (هشدار: این جمله دقیق نیست). ولی برای json این‌طور نیست (حتی این جمله هم دقیق نیست).

انکد کردن یک مسیج پروتو با Go

در اینجا یک کد نمونه به زبان گو داریم که در آن یک پروفایل مثالی میسازیم و آنرا encode شده درون یک فایل ذخیره میکنیم.

همیشه ارور ها رو چک کنید (remove رو مخصوصا چک نکردم)
همیشه ارور ها رو چک کنید (remove رو مخصوصا چک نکردم)

دیکد کردن مسیج انکد شده در پایتون

حالا که اطلاعات پروفایل را encodeشده ذخیره کردیم، کافی‌ست با یک اسکریپت پایتون فایل را باز کنیم و بوسیله Message مربوطه، که در اینجا UserProfile است، محتوایات را decode کنیم:

در اینجا فقط لیست permissions پرینت شده برای proof of concept
در اینجا فقط لیست permissions پرینت شده برای proof of concept

بنچمارک عمل decode در دو زبان Go و Python

از اینجا به بعد، در نظر دارم از فرآیندهای decode در این دو زبان، بنچ‌مارک بگیرم. با همین فایل باینری ساده Amir_Ehsandar.pb کار را انجام میدیم. برای گو تابع زیر را نوشتم که decode را انجام دهد. نتایج زیر را کسب کردیم:

هر بار decode کردن در حدود 4.5 میکروثانیه طول کشیده است.
هر بار decode کردن در حدود 4.5 میکروثانیه طول کشیده است.

سرعت خیلی بالایی در دیکد کردن با زبان گو مشاهده شد. حدود ۴.۵ میکروثانیه. حال این کار را در زبان پایتون انجام بدهیم تا بتوانیم نتایج را با هم مقایسه کنیم. در زیر کد مربوطه و نتایج آن برای زبان پایتون آمده است:

برای پایتون چیز سرراستی برای بنچمارک ندیدم. بهمین دلیل با time انجامش دادم.
برای پایتون چیز سرراستی برای بنچمارک ندیدم. بهمین دلیل با time انجامش دادم.

بنابراین طبق مشاهدات، بنظر میرسد هر بار decode کردن این پروتوی خاص ما، در حدود ۳۲ میکروثانیه طول میکشد. این عدد نزدیک ۷ برابر زمان سپری شده در گو است.