درک RESP: پروتکل پشت Redis

در پست قبلی، خلاصه‌ای از شبیه‌ساز Redis که با Node.js ساخته بودم را به اشتراک گذاشتم. حالا می‌خواهم بخش حیاتی این پروژه را توضیح دهم: RESP یا همان Redis Serialization Protocol.

Redis آرایه‌های مرتب جاوااسکریپتی دریافت نمی‌کند. بلکه بایت‌های خام را از طریق یک سوکت TCP دریافت می‌کند. برای اینکه این بایت‌ها قابل استفاده باشند، به یک ساختار نیاز دارند. سرور باید بداند یک دستور از کجا شروع و کجا تمام می‌شود. همچنین باید بداند چند آرگومان وجود دارد و طول هر کدام چقدر است.

RESP این ساختار را فراهم می‌کند. این زبانی است که کلاینت‌ها و سرورها برای گفتگو با یکدیگر از آن استفاده می‌کنند.

برای مثال، دستور GET name در پروتکل RESP به این شکل است: *2\r\n$3\r\nGET\r\n$4\r\nname\r\n

تجزیه و تحلیل آن به این صورت است:

  • *2 به معنای آرایه‌ای با ۲ المان است.
  • $3 به این معناست که بخش بعدی ۳ بایت طول دارد: GET.
  • $4 به این معناست که بخش بعدی ۴ بایت طول دارد: name.

پارسر این بایت‌ها را به ["GET", "name"] تبدیل می‌کند. حالا موتور می‌تواند دستور را اجرا کند.

چرا فقط رشته‌ها را با فاصله (space) جدا نکنیم؟ جدا کردن ساده با مقادیری که حاوی فاصله هستند، مانند SET message "hello world"، شکست می‌خورد. همچنین در مواجهه با داده‌های باینری نیز کارایی ندارد. RESP این مشکل را با گنجاندن طول هر مقدار حل می‌کند. سرور دقیقاً به همان تعداد بایتی که مشخص شده، می‌خواند. این ویژگی باعث می‌شود پروتکل از نظر باینری ایمن (binary-safe) و قابل اعتماد باشد.

سخت‌ترین درس، یادگیری این نکته بود که TCP مبتنی بر جریان (stream-based) است، نه مبتنی بر پیام (message-based). سرور همیشه یک دستور کامل را در هر بار دریافت نمی‌کند. ممکن است یک دستور در دو بخش مجزا برسد، یا چندین دستور در یک بخش واحد دریافت شوند.

این یعنی پارسر نمی‌تواند فرض کند که هر رویداد داده‌ای معادل یک دستور است. پارسر باید از یک بافر (buffer) داخلی استفاده کند.

پارسر RESP من به این صورت عمل می‌کند:

  1. دریافت بایت‌ها از سوکت.
  2. اضافه کردن بایت‌ها به یک بافر.
  3. تلاش برای تجزیه (parse) یک دستور کامل.
  4. اگر دستور ناقص بود، منتظر داده‌های بیشتر بماند.
  5. اگر دستور کامل بود، آن را برگرداند.
  6. اگر داده اضافی باقی ماند، آن را برای تجزیه بعدی در بافر نگه دارد.

یک پایگاه داده از لایه پروتکل شروع می‌شود. قبل از اینکه Redis بتواند داده‌ها را ذخیره کند، باید دستور را درک کند. قبل از اینکه مقداری را برگرداند، باید پاسخ را کدگذاری (encode) کند.

پیاده‌سازی RESP یک سرور TCP ساده را به یک سیستم واقعاً سازگار با Redis تبدیل کرد. این کار دیدگاه من را نسبت به سیستم‌های بک‌اند (backend) تغییر داد. یک پروتکل چیزی فراتر از یک فرمت است؛ پروتکل قراردادی بین دو سیستم است.

در پست بعدی، به لایه ذخیره‌سازی در حافظه (in-memory storage) خواهم پرداخت.

مخزن: https://github.com/Abhinov007/redis_clone محیط آزمایشی زنده: https://abhinov007.github.io/Redis_Clone/ منبع: https://dev.to/abhinov007/understanding-resp-the-protocol-behind-redis-50p4