Надежный обмен сообщениями с JGroups

Надежный обмен сообщениями с JGroups

1. обзор

JGroups - это Java API для надежного обмена сообщениями. Он имеет простой интерфейс, который обеспечивает:

  • гибкий стек протоколов, включая TCP и UDP

  • фрагментация и повторная сборка больших сообщений

  • надежный одноадресный и многоадресный

  • обнаружение неисправностей

  • управление потоком

Как и многие другие функции.

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

2. Настроить

2.1. Maven Dependency

Нам нужно добавить одну зависимость к нашемуpom.xml:


    org.jgroups
    jgroups
    4.0.10.Final

Последнюю версию библиотеки можно проверить наMaven Central.

2.2. сетей

JGroups будут пытаться использоватьIPV6 по умолчанию. В зависимости от конфигурации нашей системы это может привести к невозможности связи между приложениями.

Чтобы избежать этого, мы установим свойствоjava.net.preferIPv4Stack вtrue при запуске наших приложений здесь:

java -Djava.net.preferIPv4Stack=true com.example.jgroups.JGroupsMessenger

3. JChannels

Наше соединение с сетью JGroups -JChannel.. Канал присоединяется к кластеру и отправляет и принимает сообщения, а также информацию о состоянии сети.

3.1. Создание канала

Мы создаемJChannel с путем к файлу конфигурации. Если мы опустим имя файла, он будет искатьudp.xml в текущем рабочем каталоге.

Мы создадим канал с явно названным файлом конфигурации:

JChannel channel = new JChannel("src/main/resources/udp.xml");

Конфигурация JGroups может быть очень сложной, но для большинства приложений достаточно стандартных конфигураций UDP и TCP. Мы включили файл для UDP в наш код и будем использовать его в этом руководстве.

Для получения дополнительной информации о настройке транспорта см. Руководство JGroupshere.

3.2. Подключение канала

После того, как мы создали наш канал, нам нужно присоединиться к кластеру. A cluster is a group of nodes that exchange messages.с

Для присоединения к кластеру требуется имя кластера:

channel.connect("example");

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

3.3. Название канала

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

channel.name("user1");

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

3.4. Закрытие канала

Очистка канала необходима, если мы хотим, чтобы узлы своевременно получали уведомление о том, что мы вышли.

Мы закрываемJChannel его методом close:

channel.close()

4. Изменения представления кластера

Создав JChannel, мы готовы видеть состояние одноранговых узлов в кластере и обмениваться с ними сообщениями.

JGroups maintains cluster state inside the View class. Каждый канал имеет одинView сети. Когда представление изменяется, оно доставляется через обратный вызовviewAccepted().

В этом руководстве мы расширим класс APIReceiverAdaptor, который реализует все методы интерфейса, необходимые для приложения.

Это рекомендуемый способ реализации обратных вызовов.

Давайте добавимviewAccepted в наше приложение:

public void viewAccepted(View newView) {

    private View lastView;

    if (lastView == null) {
        System.out.println("Received initial view:");
        newView.forEach(System.out::println);
    } else {
        System.out.println("Received new view.");

        List
newMembers = View.newMembers(lastView, newView); System.out.println("New members: "); newMembers.forEach(System.out::println); List
exMembers = View.leftMembers(lastView, newView); System.out.println("Exited members:"); exMembers.forEach(System.out::println); } lastView = newView; }

КаждыйView содержитList изAddress объектов, представляющих каждого члена кластера. JGroups предлагает удобные методы для сравнения одного представления с другим, которое мы используем для обнаружения новых или вышедших членов кластера.

5. Отправка сообщений

Обработка сообщений в JGroups проста. Message содержит массивbyte и объектыAddress, соответствующие отправителю и получателю.

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

5.1. Широковещательные сообщения

СоздаетсяMessage с местом назначения и байтовым массивом; JChannel устанавливает нам отправителя. If the target is null, __ the entire cluster will receive the message.

Мы примем текст из командной строки и отправим его в кластер:

System.out.print("Enter a message: ");
String line = in.readLine().toLowerCase();
Message message = new Message(null, line.getBytes());
channel.send(message);

Если мы запустим несколько экземпляров нашей программы и отправим это сообщение (после того, как мы реализуем методreceive() ниже), все они получат его,including the sender.

5.2. Блокировка наших сообщений

Если мы не хотим видеть наши сообщения, мы можем установить для этого свойство:

channel.setDiscardOwnMessages(true);

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

5.3. Прямые сообщения

Для отправки прямого сообщения требуется действительныйAddress. Если мы обращаемся к узлам по имени, нам нужен способ найтиAddress. К счастью, для этого у нас естьView.

ТекущийView всегда доступен изJChannel:

private Optional
getAddress(String name) { View view = channel.view(); return view.getMembers().stream() .filter(address -> name.equals(address.toString())) .findAny(); }

ИменаAddress доступны через метод классаtoString(), поэтому мы просто ищем вList членов кластера нужное нам имя.

Таким образом, мы можем принять имя с консоли, найти связанный пункт назначения и отправить прямое сообщение:

Address destination = null;
System.out.print("Enter a destination: ");
String destinationName = in.readLine().toLowerCase();
destination = getAddress(destinationName)
  .orElseThrow(() -> new Exception("Destination not found");
Message message = new Message(destination, "Hi there!");
channel.send(message);

6. Получение сообщений

Мы можем отправлять сообщения, а теперь давайте попробуем получить их сейчас.

Давайте переопределим пустой метод приемаReceiverAdaptor’s:

public void receive(Message message) {
    String line = Message received from: "
      + message.getSrc()
      + " to: " + message.getDest()
      + " -> " + message.getObject();
    System.out.println(line);
}

Поскольку мы знаем, что сообщение содержитString, мы можем безопасно передатьgetObject() вSystem.out.

7. Государственная биржа

Когда узел входит в сеть, ему может потребоваться получить информацию о состоянии кластера. JGroups предоставляет механизм передачи состояния для этого.

Когда узел присоединяется к кластеру, он просто вызываетgetState(). Кластер обычно получает состояние от самого старого члена в группе - координатора.

Давайте добавим в наше приложение счетчик широковещательных сообщений. Мы добавим новую переменную-член и увеличим ее внутриreceive():

private Integer messageCount = 0;

public void receive(Message message) {
    String line = "Message received from: "
      + message.getSrc()
      + " to: " + message.getDest()
      + " -> " + message.getObject();
    System.out.println(line);

    if (message.getDest() == null) {
        messageCount++;
        System.out.println("Message count: " + messageCount);
    }
}

Мы проверяем адресатnull, потому что, если мы подсчитываем прямые сообщения, каждый узел будет иметь свой номер.

Затем мы переопределяем еще два метода вReceiverAdaptor:

public void setState(InputStream input) {
    try {
        messageCount = Util.objectFromStream(new DataInputStream(input));
    } catch (Exception e) {
        System.out.println("Error deserialing state!");
    }
    System.out.println(messageCount + " is the current messagecount.");
}

public void getState(OutputStream output) throws Exception {
    Util.objectToStream(messageCount, new DataOutputStream(output));
}

Подобно сообщениям, JGroups передает состояние в виде массиваbytes.

JGroups предоставляет координаторуInputStream для записи состояния иOutputStream для чтения нового узла. API предоставляет удобные классы для сериализации и десериализации данных.

Обратите внимание, что в рабочем коде доступ к информации о состоянии должен быть поточно-ориентированным.

Наконец, мы добавляем вызовgetState() в наш запуск после подключения к кластеру:

channel.connect(clusterName);
channel.getState(null, 0);

getState() принимает пункт назначения, из которого следует запросить состояние, и тайм-аут в миллисекундах. Пункт назначенияnull указывает координатора, а 0 означает, что тайм-аут отсутствует.

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

Затем, если мы добавим третьего клиента или остановим и запустим одного из них, мы увидим, что только что подключенный узел печатает правильное количество сообщений.

8. Заключение

В этом уроке мы использовали JGroups для создания приложения для обмена сообщениями. Мы использовали API для отслеживания того, какие узлы подключены к кластеру и вышли из него, а также для передачи состояния кластера новому узлу при его присоединении.

Примеры кода, как всегда, можно найтиover on GitHub.