Задать вопрос

Архитектура highload websocket проекта?

Добрый всем вечер, проектируем высоконагруженный сервис на языке go. Нужно держать очень много одновременных соединений.

Пока логика такая:
1. Клиент подключается к сокетам с передачей уникального номера канала, по которому будут приходить сообщения
2. Сервис ищет объект канала в map’е , если не находит - ищет в базе данных(пока смотрим в сторону redis) запись с номером этого канала и затем кладёт в map, если ничего не находит - отключает клиента.
3. Дальше в горутине запускает слушатель pub/sub и подписывается на идентификатор канала. При поступлении сообщений отправляет по сокетам пользователю.

Канал - что-то вроде комнаты для нескольких подключений.

Вопрос в архитектуре. Подходит ли вариант хранения каналов в массиве, или есть какие-то другие идеи?

Пример подобного сервиса - PUSHER

UPD ::
На счёт хранения каналов пока идей нет, но создавать слушателя в каждой рутине будет затратно, потому создаём глобального слушателя на все каналы, и разбираем сообщение на канал/данные.
То есть в pub пишем {channel: kwb9162, message: hello} и парсим на стороне сервера.
  • Вопрос задан
  • 2282 просмотра
Подписаться 11 Средний 10 комментариев
Решения вопроса 1
WinPooh32
@WinPooh32
Stack Overflow answer searching expert
Из-за особенностей работы сборщика мусора в Go у вас скорее всего возникнут проблемы, если ключи в map'е содержат указатели (включая структуры их содержащие).

Вот накидал способ хранения сокетов немного в другом виде без указателей в ключах:
const n = 4 // длина идентификатора комнаты в байтах
type (
	noop struct{}
	WebSocket struct{} // сокет

	Sockets []WebSocket
	RoomKey [n]byte
	
	Index uint32

	Room map[Index] struct{}
	Rooms map[RoomKey] Room
)

var sockets = make(Sockets, 0, 2000000)
var rooms = make(Rooms, 1000)
var lastSocketRoomKey RoomKey

func insert(ws WebSocket, rk RoomKey){
	room, _ := rooms[rk]
	// тут создание комнаты, если ее нет
	// ...
	
	sockets = append(sockets, ws)
	
	last := len(sockets) - 1
	room[Index(last)] = noop{}
  
        // сохраним ключ комнаты последнего сокета
        // чтобы можно было исправить индекс при удалении
        copy(lastSocketRoomKey[:], rk[:])
}

func remove(i Index, rk RoomKey){
	// удаляем сокет,
	// переместив последний в массиве на место удаляемого

	last := len(sockets) - 1
	sockets[i] = sockets[last]
	sockets = sockets[:last]

	// удаляем сокет из комнаты
	room, _ := rooms[rk]
	delete(room, Index(i))

	// чиним индекс перемещенного сокета
	roomOfLast := rooms[lastSocketRoomKey]
	delete(roomOfLast, Index(last))
	roomOfLast[Index(i)] = noop{}
}


Когда вам нужно разослать сообщения, в моем примере доступ к сокету будет происходить примерно так:
for _, index := range rooms[roomkey]{
     // sockets[index] -- сокет в комнате с roomkey
     
    // т.к. из-за особенности структуры искать сокет в комнате получается невыгодно долго, 
    // придется удалять закрытые сокеты "ленивым" способом во время рассылки, 
    // либо заполнять массив индексов и после уже удалять эти сокеты из общего хранилища
    // функцией remove(index, roomkey)
}


Очевидно, что в данном примере доступ к sockets и rooms не должен происходить из разных горутин.
Ответ написан
Комментировать
Пригласить эксперта
Ответы на вопрос 3
@deliro
Искать канал в обычном массиве - это O(n). Хотя бы дихотомией, а лучше map юзать
Ответ написан
alfss
@alfss
https://career.habr.com/alfss
Я оставлю это тут, вдруг вам концепт пригодится https://github.com/alfssobsd/demowebsocket-server
Ответ написан
Комментировать
kentuck1213
@kentuck1213
У каждого пользователя свой канал, тут подайдет key=value хранилище:
{
   user: 1,
   channel: "asdsad12e123"
}
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы