Розуміння 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-парсер працює так:
- Отримати байти із сокета.
- Додати байти до буфера.
- Спробувати розпарсити повну команду.
- Якщо команда неповна, чекати на нові дані.
- Якщо команда повна, повернути її.
- Якщо залишаються зайві дані, залишити їх у буфері для наступного парсингу.
База даних починається з рівня протоколу. Перш ніж 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