Пользовательские направления в многосерверной среде? (Spring WebSocket и RabbitMQ)

документация для Spring WebSockets гласит:

4.4.13. Пользовательские направления

Приложение может отправлять сообщения, предназначенные для определенного пользователя, а поддержка STOMP в Spring для этой цели распознает места назначения с префиксом «/ user /». Например, клиент может подписаться на адрес назначения «/ user / queue / position-updates». Этот пункт назначения будет обрабатываться UserDestinationMessageHandler и преобразован в пункт назначения, уникальный для пользовательского сеанса, например "/ очередь / позиция-обновления-пользователь123". Это обеспечивает удобство подписки на одно и то же место назначения, в то же время гарантируя отсутствие конфликтов с другими пользователями, подписывающимися на то же место назначения, так что каждый пользователь может получать уникальные обновления позиции запасов.

Это должно работать в многосерверной среде с RabbitMQ в качестве брокера?

Насколько я могу судить, имя очереди для пользователя создается путем добавления simpSessionId. При использовании рекомендованной клиентской библиотеки stomp.js это приводит к тому, что первый пользователь получает имя очереди "/queue/position-updates-user0" , следующий получит "/queue/position-updates-user1" и так далее. Это, в свою очередь, означает, что первые пользователи, подключившиеся к разным серверам, подпишутся на одну и ту же очередь ("/queue/position-updates-user0").

Единственная ссылка на это, которую я могу найти в документации, такова:

В сценарии с несколькими серверами приложений пользовательский пункт назначения может оставаться нерешенным, поскольку пользователь подключен к другому серверу. В таких случаях вы можете настроить место назначения для широковещательной рассылки неразрешенных сообщений, чтобы другие серверы могли попробовать. Это можно сделать с помощью свойства userDestinationBroadcast объекта MessageBrokerRegistry в конфигурации Java и атрибута user-destination-broadcast элемента брокера сообщений в XML.

Но это только позволяет общаться с пользователем с другого сервера, чем тот, на котором установлен веб-сокет.

Я чувствую, что что-то упускаю? Есть ли способ настроить Spring для безопасного использования MessagingTemplate.convertAndSendToUser(principal.getName(), destination, payload) в многосерверной среде?


person Andy    schedule 05.03.2018    source источник


Ответы (1)


Если они нуждаются в аутентификации (я предполагаю, что их учетные данные хранятся в базе данных), вы всегда можете использовать их уникальный идентификатор пользователя базы данных для подписки.

Что я делаю, так это то, что когда пользователь входит в систему, он автоматически подписывается на две темы: account|system тему для общесистемных трансляций и account|<userId> тему для определенных трансляций.

Вы можете попробовать что-то вроде notification|<userid> для каждого человека, на который подписаться, а затем отправить сообщение в эту тему, и они его получат.

Поскольку идентификаторы пользователей уникальны для каждого пользователя, у вас не должно возникнуть проблем в кластерной среде, пока каждая среда обращается к одной и той же информации базы данных.

Вот мой метод отправки:

  public static boolean send(Object msg, String topic) {
    try {
      String destination = topic;
      String payload = toJson(msg); //jsonfiy the message 
      Message<byte[]> message = MessageBuilder.withPayload(payload.getBytes("UTF-8")).build();
      template.send(destination, message);
      return true;
    } catch (Exception ex) {
      logger.error(CommService.class.getName(), ex);
      return false;
    }
  }

Мои пункты назначения предварительно отформатированы, поэтому, если я хочу отправить сообщение пользователю с идентификатором, равным одному, пункты назначения выглядят примерно как /topic/account|1.

Ive создал контроллер для пинг-понга, который тестирует веб-сокеты для пользователей, которые подключаются, чтобы узнать, позволяет ли их среда использовать веб-сокеты. Я не знаю, поможет ли это вам, но это работает в моей кластерной среде.

/**
   * Play ping pong between the client and server to see if web sockets work
   * @param input the ping pong input
   * @return the return data to check for connectivity
   * @throws Exception exception
   */
  @MessageMapping("/ping")
  @SendToUser(value="/queue/pong", broadcast=false) // send only to the session that sent the request
  public PingPong ping(PingPong input) throws Exception {
    int receivedBytes = input.getData().length;
    int pullBytes = input.getPull();

    PingPong response = input;
    if (pullBytes == 0) {
      response.setData(new byte[0]);
    } else if (pullBytes != receivedBytes)  {
      // create random byte array
      byte[] data =  randomService.nextBytes(pullBytes);
      response.setData(data);
    }
    return response;
  }
person locus2k    schedule 05.03.2018
comment
Спасибо за ответ. Это могло бы быть решением. Итак, в этом случае я предполагаю, что вы не можете использовать SimpMessagingTemplate.convertAndSendToUser (Principal.getName (), destination, payload), и Spring разрешит вам места назначения? - person Andy; 05.03.2018
comment
Вы должны иметь возможность использовать SimpleMessagingTemplate и отлично работает в моей кластерной среде, но я настраиваю свои пункты назначения перед отправкой, поэтому моя строка назначения выглядит примерно как /topic/account|1, если я хочу отправить сообщение пользователю с идентификатором 1, но я не использую convertAndSendTouser метод. Я просто использую простой метод send. Смотрите мою правку моего метода отправки. - person locus2k; 05.03.2018
comment
Ваше решение - хороший обходной путь, но мне любопытно, есть ли способ справиться с этим без необходимости обходить встроенный механизм назначения пользователя. Я обновил свой вопрос. - person Andy; 06.03.2018
comment
Я добавил фрагмент кода, который использует контроллер для отправки сообщений клиенту и обратно для проверки наличия веб-сокетов. - person locus2k; 06.03.2018