Как устроен Redis и как с ним работать в Go?

Ответ

Redis (REmote DIctionary Server) — это высокопроизводительное in-memory хранилище данных типа «ключ-значение», которое часто используется как база данных, кэш или брокер сообщений.

Ключевые аспекты архитектуры:

  1. Хранение в памяти (In-Memory): Все данные хранятся в оперативной памяти, что обеспечивает сверхнизкие задержки при чтении и записи.
  2. Однопоточная модель (Single-Threaded Event Loop): Redis использует один поток для обработки всех команд. Это исключает гонки данных и необходимость в блокировках, но для утилизации многоядерных процессоров запускают несколько экземпляров Redis. Для операций ввода-вывода используется мультиплексирование (epoll, kqueue), что позволяет эффективно обрабатывать тысячи одновременных подключений.
  3. Персистентность (сохранение на диск): Для сохранности данных при перезапуске Redis предлагает два механизма:
    • RDB (Redis Database): Создание снимков (snapshots) всей базы данных через определённые интервалы. Оптимально для бэкапов.
    • AOF (Append-Only File): Запись каждой операции изменения данных в лог-файл. Обеспечивает лучшую сохранность данных.

Основные типы данных:

Redis — это не просто хранилище строк. Он поддерживает сложные структуры данных:

  • Strings: Строки, числа, битовые поля.
  • Lists: Списки, реализующие двустороннюю очередь (deque).
  • Hashes: Хеш-таблицы (аналог map в Go).
  • Sets: Неупорядоченные множества уникальных строк.
  • Sorted Sets (ZSETs): Упорядоченные множества, где у каждого элемента есть вес (score). Идеально для рейтингов и таблиц лидеров.
  • Streams: Структура для реализации логов и очередей сообщений.
  • HyperLogLogs, Bitmaps, Geospatial indexes.

Пример использования в Go (с библиотекой go-redis):

Библиотека go-redis/redis является современным стандартом для работы с Redis в Go.

package main

import (
    "context"
    "fmt"
    "github.com/go-redis/redis/v8"
)

var ctx = context.Background()

func main() {
    rdb := redis.NewClient(&redis.Options{
        Addr:     "localhost:6379", // Адрес Redis сервера
        Password: "",               // Пароль, если есть
        DB:       0,                // Номер базы данных
    })

    // Устанавливаем значение ключа
    err := rdb.Set(ctx, "user:1", "Alex", 0).Err()
    if err != nil {
        panic(err)
    }

    // Получаем значение ключа
    val, err := rdb.Get(ctx, "user:1").Result()
    if err != nil {
        panic(err)
    }
    fmt.Println("user:1 ->", val)

    // Пример работы с Hash
    rdb.HSet(ctx, "user:2", "name", "Bob", "age", 30)
    name, _ := rdb.HGet(ctx, "user:2", "name").Result()
    age, _ := rdb.HGet(ctx, "user:2", "age").Result()
    fmt.Printf("User 2: Name - %s, Age - %sn", name, age)
}

Основные сценарии использования:

  • Кэширование: Снижение нагрузки на основную базу данных.
  • Хранение сессий: Быстрое сохранение и получение данных о пользовательских сессиях.
  • Очереди задач: Использование Lists или Streams для создания фоновых очередей.
  • Pub/Sub: Реализация системы обмена сообщениями в реальном времени.
  • Счётчики и рейтинги: Атомарные операции (INCR) и упорядоченные множества (Sorted Sets).