Розуміння RESP: протокол, що стоїть за Redis

У своєму попередньому дописі я надав огляд свого клона Redis, побудованого на Node.js. Тепер я хочу пояснити критично важливу частину цього проєкту: RESP, або Redis Serialization Protocol.

Redis не отримує зручних масивів JavaScript. Він отримує сирі байти через TCP-сокет. Щоб вони були придатними для роботи, ці байти повинні мати структуру. Сервер має знати, де команда починається і закінчується. Він має знати, скільки існує аргументів і якої довжини кожен із них.

RESP забезпечує цю структуру. Це мова, якою спілкуються клієнти та сервери.

Наприклад, команда GET name у форматі RESP виглядає так: *2\r\n$3\r\nGET\r\n$4\r\nname\r\n

Ось розшифровка:

  • *2 означає масив із 2 елементів.
  • $3 означає, що наступна частина має довжину 3 байти: GET.
  • $4 означає, що наступна частина має довжину 4 байти: name.

Парсер перетворює ці байти на ["GET", "name"]. Тепер рушій може виконати команду.

Чому б просто не розділити рядки за пробілами? Просте розділення не спрацює зі значеннями, що містять пробіли, як-от SET message "hello world". Воно також не працює з бінарними даними. RESP вирішує це, вказуючи довжину кожного значення. Сервер зчитує саме ту кількість байтів, яка вказана. Це робить протокол надійним і бінарно-безпечним (binary-safe).

Найважчим уроком було усвідомлення того, що TCP базується на потоках (stream-based), а не на повідомленнях (message-based). Сервер не завжди отримує одну повну команду за раз. Команда може надійти двома окремими частинами. Або ж кілька команд можуть надійти одним пакетом.

Це означає, що парсер не може припускати, що одна подія отримання даних дорівнює одній команді. Парсер повинен використовувати внутрішній буфер.

Мій RESP-парсер працює так:

  1. Отримати байти із сокета.
  2. Додати байти до буфера.
  3. Спробувати розпарсити повну команду.
  4. Якщо команда неповна, чекати на нові дані.
  5. Якщо команда повна, повернути її.
  6. Якщо залишаються зайві дані, залишити їх у буфері для наступного парсингу.

База даних починається з рівня протоколу. Перш ніж Redis зможе зберегти дані, він має зрозуміти команду. Перш ніж повернути значення, він має закодувати відповідь.

Реалізація RESP перетворила простий TCP-сервер на справжню систему, сумісну з Redis. Це змінило мій погляд на бекенд-системи. Протокол — це більше, ніж просто формат. Це контракт між двома системами.

У наступному дописі я розгляну рівень зберігання даних у пам'яті (in-memory storage layer).

Репозиторій: https://github.com/Abhinov007/redis_clone Жива пісочниця: https://abhinov007.github.io/Redis_Clone/ Джерело: https://dev.to/abhinov007/understanding-resp-the-protocol-behind-redis-50p4