Часть 3. Добавление поддержки Pub/Sub
Источник:
https://github.com/kzeiter/build-redis-in-go

В этой части нашей серии клонов Redis мы добавим поддержку функции Pub/Sub (публикация/подписка). Pub/Sub позволяет клиентам подписываться на каналы и получать сообщения, публикуемые на этих каналах. Эта функция обычно используется в приложениях реального времени, таких как чаты и обновления в реальном времени.

Мы будем реализовывать две новые команды для Pub/Sub:

SUBSCRIBE channel
PUBLISH   channel message

Команда SUBSCRIBE позволяет клиентам подписываться на канал, а команда PUBLISH позволяет клиентам публиковать сообщения в канале. Когда сообщение публикуется в канале, все подписанные клиенты получат это сообщение.

Внедрение публикации/подписки

Чтобы реализовать Pub/Sub, нам нужно будет изменить нашу структуру store, чтобы отслеживать подписчиков для каждого канала. Мы будем использовать карту, где ключи — это имена каналов, а значения — срезы client структур. Структура client будет представлять клиента, который подписан на канал, и будет содержать ссылку на объект net.Conn клиента, который мы будем использовать для отправки сообщений клиенту.

type store struct {
   data map[string]string
   list map[string][]string
   sets map[string]map[string]bool
   subs map[string][]client
}

func (s *store) subscribe(channel string, conn net.Conn) string {
   client := client{conn: conn}
   s.subs[channel] = append(s.subs[channel], client)
   
   return "SUBSCRIBED"
}

func (s *store) publish(channel string, message string) {
   subs, ok := s.subs[channel]
   if ok {
      for _, subscriber := range subs {
         fmt.Fprintf(subscriber.conn, "+%s\n", message)
      }
   }
}

Нам также нужно определить структуру client:

type client struct {
    conn net.Conn
}

Когда клиент подписывается на канал, мы создадим новую структуру client и добавим ее в список подписчиков канала. Когда сообщение публикуется на канале, мы проходим по списку подписчиков этого канала и отправляем сообщение каждому из них.

Вот обновленный handleCommand с новой функциональностью Pub/Sub:

func (s *store) handleCommand(command string, args []string) string {
    switch command {
    case "SET":
        // Using Join to save the rest of the received data
        return s.set(args[0], strings.Join(args[1:], " "))
    case "GET":
        return s.get(args[0])
    case "DEL":
        s.del(args[0])
        return "DELETED"
    case "INCR":
        return fmt.Sprintf("%v", s.incr(args[0]))
    case "INCRBY":
        return fmt.Sprintf("%v", s.incrBy(args[0], args[1]))
    case "DECR":
        return fmt.Sprintf("%v", s.decr(args[0]))
    case "DECRBY":
        return fmt.Sprintf("%v", s.decrBy(args[0], args[1]))
    case "LPUSH":
        return fmt.Sprintf("%v", s.lPush(args[0], args[1]))
    case "RPUSH":
        return fmt.Sprintf("%v", s.rPush(args[0], args[1]))
    case "LPOP":
        return s.lPop(args[0])
    case "RPOP":
        return s.rPop(args[0])
    case "LLEN":
        return fmt.Sprintf("%v", s.lLen(args[0]))
    case "LINDEX":
        index, _ := strconv.Atoi(args[1])
        return s.lIndex(args[0], index)
    case "SADD":
        return fmt.Sprintf("%v", s.sadd(args[0], args[1]))
    case "SREM":
        return fmt.Sprintf("%v", s.srem(args[0], args[1]))
    case "SMEMBERS":
        members := s.smembers(args[0])
        result := ""
      
        for _, member := range members {
           result += fmt.Sprintf("%v ", member)
        }
      
        return strings.TrimSpace(result)
    case "SISMEMBER":
        return fmt.Sprintf("%v", s.sismember(args[0], args[1]))    
    case "SUBSCRIBE":
        return s.subscribe(args[0], conn)
    case "PUBLISH":
        s.publish(args[0], strings.Join(args[1:], " "))
        return "PUBLISHED"
    default:
        return "ERROR"
    }
}

Мы добавили два новых случая в оператор switch в методе handleCommand для обработки команд SUBSCRIBE и PUBLISH. Мы также добавили методы subscribe и publish для обработки подписки на каналы и публикации сообщений в каналах соответственно.

В методе subscribe мы создаем новую структуру client с объектом net.Conn клиента и добавляем ее в список подписчиков для указанного канала.

В методе publish мы сначала проверяем, есть ли подписчики на указанный канал. Если они есть, мы перебираем список подписчиков и отправляем сообщение каждому из них, используя клиентский объект net.Conn.

Заключение

В этой части нашей серии клонов Redis мы добавили поддержку функций Pub/Sub. Мы реализовали команды SUBSCRIBE и PUBLISH и обновили нашу структуру store, включив в нее карту subs, в которой хранится список подписчиков для каждого канала.

Pub/Sub — это мощный шаблон обмена сообщениями, который позволяет издателям отправлять сообщения нескольким подписчикам, ничего о них не зная. Это делает его идеальным для создания приложений реального времени, таких как чаты или онлайн-игры.

В следующей части нашей серии клонов Redis мы добавим поддержку сохраняемости, реализуя простую дисковую систему хранения. Следите за обновлениями!

Источник:
https://github.com/kzeiter/build-redis-in-go

Часть 1. Создание базового хранилища ключей и значений:
https://khalidzeiter.medium.com/building-a-simple-redis-clone-in-golang-part-1 -7f816f2f61bd

Часть 2. Добавление поддержки чисел, списков и наборов:
https://khalidzeiter.medium.com/building-a-simple-redis-clone-in-golang-part -2-9130c0c3ef22

Часть 4. Добавление сохраняемости с помощью дискового хранилища:
https://khalidzeiter.medium.com/building-a-simple-redis-clone-in-golang-part-4-bf4ff349dad3

Заключение и следующие шаги:
https://khalidzeiter.medium.com/building-a-simple-redis-clone-in-golang-conclusion-and-next-steps-128b0503a55b