mqtt客户端程序_IBM MessageSight和MQTT客户端应用程序的高可用性

mqtt客户端程序

本文将向您展示如何编写使用IBM®MessageSight的高可用性(HA)配置的MQTT应用程序。 本文将涉及:

  • IBM MessageSight
  • MQTT V3.1协议和Paho开源项目
  • IBM MessageSight中的高可用性概念

您将学习如何制作一个简单的MQTT应用程序,并通过以下方式对其进行加固,以通过IBM MessageSight设备实现弹性和HA消息传递:

  • 将连接扩展到HA对IBM MessageSight设备
  • 处理掉线的连接和设备中断
  • 跨设备故障转移维护正确的状态
  • 处理客户中断

IBM MessageSight

IBM MessageSight是功能全面的消息传递设备,使您的企业可以访问物联网(IoT)。 它提供可靠,可伸缩,安全且对开发人员友好的消息传递。 有关IBM MessageSight的更多信息,请参阅本文底部的“ 相关主题”部分。

MQTT V3.1协议和Paho开源消息传递项目

MQTT是围绕发布/订阅模型设计的轻量级消息传递协议。 当占用空间,资源和/或网络带宽非常宝贵,并且其低功耗和高效信息分配特别适合移动应用程序时,它特别有用。 有关MQTT协议的更多信息,请参见本文底部的“ 相关主题”部分。

Paho Eclipse项目为各种平台(包括C,Java™和Python)提供了MQTT客户端的开源实现。 本文包含基于Paho R1.0.0.3的示例代码。 通过在您喜欢的IDE的构建路径中包含适当的Paho Java库来构建代码示例。 这些库未随文章一起提供,因为Paho正在开发中,因此建议您下载最新的可用Paho JAR文件,以从项目的最新改进中受益。

IBM MessageSight中的HA概念

HA使您的IT基础架构能够适应各种中断,从而最大程度地延长用户的可用性时间。 中断可能是计划内的(例如软件或硬件升级),也可能是计划外的(软件或硬件故障)。 IBM MessageSight通过组合一对设备来提供HA,如下所示:

  • 第一个设备充当主要设备并执行核心消息传递工作,而第二个设备充当备用设备( A )。
  • 如果主设备发生故障( B ),则备用设备将接管并成为新的主设备( C )。
  • 解决第一台设备的故障后,它会重新加入HA对并成为新的备用设备,从而恢复HA功能( D )。
两个IBM MessageSight设备的HA配置中的故障转移
两个IBM MessageSight设备的HA配置中的故障转移

编写用于IBM MessageSight HA配置的MQTT应用程序

考虑单个独立的IBM MessageSight设备,其消息传递端点在端口16102和IP地址node1.example.com上公开,以及一个简单的基于Paho的Java MQTT客户端,该客户端连接并发布MQTT消息,如下图和代码示例所示。 :

MQTT发布者与单个IBM MessageSight设备的连接
MQTT发布者与单个IBM MessageSight设备的连接
MQTT客户端代码演示了与独立IBM MessageSight设备的连接
package com.ibm.sample;
import org.eclipse.paho.client.mqttv3.*;

public class DemoPublisher {
  MqttClient c;

  public void runDemo() {	  
    try {
      System.out.println("Starting demo.");
      c = new MqttClient("tcp://node1.example.com:16102", "DWDemo", null);
      c.connect();
      c.publish("DWDemoTopic", ("A simple message").getBytes(), 2, false);
      System.out.println("Message published - demo complete.");
    } catch (MqttException e) {
      // Error handling goes here...
      System.out.println("Demo error");
      e.printStackTrace();
    }
  }  	
}

在此Java示例中,具有clientId DWDemo的MQTT客户端在端口1102上的node1.example.com上连接到IBM MessageSight。 端口16102对应于作为IBM MessageSight的示例消息中心Hub DemoHub一部分提供的MQTT端点,并为您提供了测试该样本的依据,如下所示。 连接后,MQTT客户端将单个消息发布到主题DWDemoTopic

IBM MessageSight UI显示了在端口16102上启用的演示端点
IBM MessageSight UI显示了在端口16102上启用的演示端点

步骤1.使客户端能够连接到HA对IBM MessageSight设备

如下所示,一对HA HA的IBM MessageSight设备具有两个可能的主机供客户端连接-每个HA设备(node1.example.com和node2.example.com)一个。 因为高可用性对具有通用配置-包括通用消息传递中心和端点,所以端口号保持不变。 那么,如何修改MQTT客户端以连接到该HA对? 通过使用Paho,它使您可以定义要连接的多个URI,通过尝试连接到端点列表直到成功,从而自动搜寻主要设备,如以下代码所示:

MQTT发布者到HA对IBM MessageSight设备的客户端连接
MQTT发布者到HA对IBM MessageSight设备的客户端连接
MQTT客户端代码演示了与HA HA对IBM MessageSight设备的连接
package com.ibm.sample;
import org.eclipse.paho.client.mqttv3.*;

public class DemoPublisher {
  MqttClient c;
  MqttConnectOptions conOptions;

  public void runDemo() {	  
    try {
      System.out.println("Starting demo.");
      String haURIs[] = new String[]{"tcp://node1.example.com:16102",
                                     "tcp://node2.example.com:16102"};
      c = new MqttClient("tcp://node1.example.com:16102", "DWDemo", null);
      conOptions = new MqttConnectOptions();
      conOptions.setServerURIs(haURIs);
      c.connect(conOptions);
      c.publish("DWDemoTopic", ("A simple message").getBytes(), 2, false);
      System.out.println("Message published - demo complete.");
    } catch (MqttException e) {
      // Error handling goes here...
      System.out.println("Demo error");
      e.printStackTrace();
    }
  }  	
}

在中,通过实例化MqttConnectOptions对象并调用其setServerURIs(String[] array)方法来提供HA详细信息。 Paho将在c.connect(conOptions)调用中自动处理此数组,直到建立成功的连接。 尽管通过setServerURIs提供了HA端点,但是仍然使用node1.example.com作为目标URI来实例化MQTT客户端。 如果使用默认的基于文件的客户端持久性,此API会保持兼容性并提供密钥,这一点将在下面进行说明。

步骤2.处理掉线的连接

该客户端现在可以执行到HA对IBM MessageSight设备的初始连接,但是如果c.connect(...)调用设备中断会发生什么情况? 如何修改客户端以自动重新连接到新的主设备? 为了处理这种情况,您可以向客户端添加逻辑,以使用MQTT和Paho的内置MqttCallback功能异步地检测和处理断开的连接,并实现其connectionLost(Throwable cause)方法。 如果主MessageSight设备发生故障,则备用设备准备继续发送消息时会有延迟。 显示了如何处理此中断,以及如何扩展上面的示例代码以顺序发送多个消息。

HA MQTT客户端代码,改进了对丢弃的连接的处理,突出显示了异步添加
package com.ibm.sample;
import org.eclipse.paho.client.mqttv3.*;

public class DemoPublisher {
  MqttClient c;
  MqttConnectOptions conOptions;
  int messageId = 1;
  DemoCallback callback;

  public void runDemo() {	  
    try {
      System.out.println("Starting demo.");
      String haURIs[] = new String[]{"tcp://node1.example.com:16102",
                                     "tcp://node2.example.com:16102"};
      c = new MqttClient("tcp://node1.example.com:16102", "DWDemo", null);
      callback = new DemoCallback();
      c.setCallback(callback);
      conOptions = new MqttConnectOptions();
      conOptions.setServerURIs(haURIs);
      connect();
    } catch (MqttException e) {
      // Error handling goes here...
    }
    // Now let's start our HA messaging
    while (true) {
      publishAMessage(); 
    }
  }  

  private void publishAMessage() {
    boolean publishCallCompletedErrorFree = false;
    while (!publishCallCompletedErrorFree) {
      try {
        c.publish("DWDemoTopic", ("Message "+messageId).getBytes(), 2, false);
        publishCallCompletedErrorFree = true;
        System.out.println("Message "+messageId+" published.");
        messageId++;
      } catch (MqttException e) {
        /*
         * If we hit this, we do not set publishCallCompletedErrorFree to true, nor do 
         * we increment the message count. We therefore retry the publish. This helps 
         * handle MessageSight outages, as this allows the asynchronous reconnect to 
         * occur. You may wish to add improved error handling to cover other conditions.
         */
      } finally {
        pause();
      }
    }
  }

  private void connect() {
    // Let's try a cycle of reconnects. We rely on Paho's built-in HA code to hunt out
    // the primary appliance for us.	
    boolean tryConnecting = true;
    while (tryConnecting) {
      try {
        c.connect(conOptions);
      } catch (Exception e1) {
        System.out.println("Connection attempt failed with '"+e1.getCause()+
             "'. Retrying.");
        /* We'll do nothing as we'll shortly try connecting again. You may wish to track
         * the number of attempts to guard against long-term or permanent issues,
         * for example, misconfigured URIs.
         */
      }
      if (c.isConnected()) {
        System.out.println("Connected.");
        tryConnecting = false;
      } else {
        pause();
      }
    }
  }
  
  private void pause() {
    try {
      Thread.sleep(1000);
    } catch (InterruptedException e) {
      // Error handling goes here...
    }
  }
  
  private class DemoCallback implements MqttCallback {

    public void connectionLost(Throwable cause) {
      System.out.println("Connection lost - attempting reconnect.");
      connect();
    }

    @Override
    public void deliveryComplete(IMqttDeliveryToken arg0) {
      // Not needed in this simple demo
    }

    @Override
    public void messageArrived(String arg0, MqttMessage arg1) throws Exception {
      // Not needed in this simple demo		
    }
  }
}

在清单中,添加了pause()方法以:

  • 故障转移事件期间的节流连接尝试 -减少了设备故障转移期间客户端上的负载,如果有成百上千的客户端尝试重新连接,则有助于减轻新主设备上的压力。
  • 油门出版物 -如果您希望自己运行示例代码,可以使演示输出更清晰。

步骤3.跨设备故障转移维护状态

现在,如果设备发生故障转移,您的客户端将能够自动重新连接。 但是,服务消息仍然有可能丢失,因为客户端和服务器都保持瞬时状态以跟踪消息传递的状态,这对于异步消息传递或更高质量的服务尤其重要。 在当前代码中, connect()调用隐式破坏了与较早连接关联的所有先前状态,因此您可能会丢失故障转移事件之前的消息。 因此,下一步是保留连接尝试之间的状态,使您可以准确地恢复发生故障的设备停止处的消息传递。 您可以通过使用MqttConnectOptions对象上的MqttConnectOptions cleanSession=false设置来添加以下代码:

维持连接状态
public void runDemo() {	  
    try {
      System.out.println("Starting demo.");
      String haURIs[] = new String[]{"tcp://node1.example.com:16102",
                                     "tcp://node2.example.com:16102"};
      c = new MqttClient("tcp://node1.example.com:16102", "DWDemo", null);
      callback = new DemoCallback();
      c.setCallback(callback);
      conOptions = new MqttConnectOptions();
      conOptions.setServerURIs(haURIs);
      conOptions.setCleanSession(false);
      connect();
    } catch (MqttException e) {
      // Error handling goes here...
    }    
...

步骤4.向客户端应用程序添加持久性

要构建真正的HA解决方案,必须解决所有可能的故障点。 到目前为止,您已经创建了处理设备中断的代码,因此现在您需要创建代码以从客户端本身的中断中恢复。 在上面的清单中,您使用c = new MqttClient("tcp://node1.example.com:16102", "DWDemo", null);实例化了MQTT客户端c = new MqttClient("tcp://node1.example.com:16102", "DWDemo", null); 。 该代码对应于具有内存内持久性的MQTT客户端,因此,如果客户端进程失败或重新启动,则任何客户端临时状态都将丢失。 因此,您应该通过使用更强大的持久性机制来提高弹性,例如Paho的默认基于文件的持久性实现如下所示:

最终的MQTT客户端列表,包括基于文件的持久性
package com.ibm.sample;
import org.eclipse.paho.client.mqttv3.*;

public class DemoPublisher {
  MqttClient c;
  MqttConnectOptions conOptions;
  int messageId = 1;
  DemoCallback callback;

  public void runDemo() { 
    try {
      System.out.println("Starting demo.");
      String haURIs[] = new String[]{"tcp://node1.example.com:16102",
                                     "tcp://node2.example.com:16102"};
      c = new MqttClient("tcp://node1.example.com:16102", "DWDemo");
      callback = new DemoCallback();
      c.setCallback(callback);
      conOptions = new MqttConnectOptions();
      conOptions.setServerURIs(haURIs);
      conOptions.setCleanSession(false);
      connect();
    } catch (MqttException e) {
      // Error handling goes here...
    }
    // Now let's start our HA messaging
    while (true) {
      publishAMessage(); 
    }
  }  
  
  private void publishAMessage() {
    boolean publishCallCompletedErrorFree = false;
    while (!publishCallCompletedErrorFree) {
      try {
        c.publish("DWDemoTopic", ("Message "+messageId).getBytes(), 2, false);
        publishCallCompletedErrorFree = true;
        System.out.println("Message "+messageId+" published.");
        messageId++;
      } catch (MqttException e) {
        /*
         * If we hit this, we do not set publishCallCompletedErrorFree to true, nor do 
         * we increment the message count. We therefore retry the publish. This helps 
         * handle MessageSight outages, as this allows the asynchronous reconnect to 
         * occur. You may wish to add improved error handling to cover other conditions.
         */
      } finally {
        pause();
      }
    }
  }

  private void connect() {
    // Let's try a cycle of reconnects. We rely on Paho's built-in HA code to hunt out
    // the primary appliance for us.
    boolean tryConnecting = true;
    while (tryConnecting) {
      try {
        c.connect(conOptions);
      } catch (Exception e1) {
        System.out.println("Connection attempt failed with '"+e1.getCause()+
             "'. Retrying.");
        /* We'll do nothing as we'll shortly try connecting again. You may wish to track
         * the number of attempts to guard against long-term or permanent issues,
         * for example, misconfigured URIs.
         */
      }
      if (c.isConnected()) {
        System.out.println("Connected.");
        tryConnecting = false;
      } else {
        pause();
      }
    }
  }
  
  private void pause() {
    try {
      Thread.sleep(1000);
    } catch (InterruptedException e) {
      // Error handling goes here...
    }
  }
  
  private class DemoCallback implements MqttCallback {

    public void connectionLost(Throwable cause) {
      System.out.println("Connection lost - attempting reconnect.");
      connect();
    }

    @Override
    public void deliveryComplete(IMqttDeliveryToken arg0) {
      // Not needed in this simple demo
    }

    @Override
    public void messageArrived(String arg0, MqttMessage arg1) throws Exception {
      // Not needed in this simple demo		
    }
  }
}

恭喜-您现在拥有一个HA客户端,它可以应对设备和客户端的故障。 在总结您取得的成就之前,值得重点介绍在开发和实现MQTT客户端应用程序时的三种最佳实践。 到目前为止,您已经在本文中涉及了这些元素,但是强调它们以帮助您避免自己的应用程序开发和部署中的问题很有用。

最佳实践1.避免cleanSession和客户端持久性之间的不匹配

如果您将MQTT客户端与cleanSession=false但是持久性机制较弱,那么重新启动客户端(例如,选择代码修订版)将导致客户端瞬态丢失,但不会导致服务器端瞬态丢失。 这种数据丢失可能导致客户端与设备之间失去一致性,从而可能导致意外行为。 因此,建议您:

  • 请在客户端代码中使用更强的持久性机制,如中所示,请记住,或
  • 恢复为cleanSession=true直到准备好使用更强的持久层对应用程序进行HA强化,或
  • 遵循来确保状态的一致性。

最佳实践2.避免在MQTT客户端版本之间污染消息传递数据

在MQTT应用程序的开发周期的早期,您可能希望客户在重要区域(例如消息有效负载)进行修订。 您应该意识到,使用cleanSession=false ,具有强大持久层的客户端以及QoS> 0的消息可能会导致从早期代码修订版中获取消息有效负载。 根据您的应用程序,这些可能无法正确处理,从而导致应用程序意外故障。 为防止这些故障,请考虑以下内容。

最佳实践3.确保和恢复客户端与服务器之间的一致性

在开发环境中,您可能需要确保客户端和服务器具有干净的同步瞬态,以确保可预测的消息传递。 您可以通过以下两种方式之一清除数据并恢复一致性:

  1. 在重新连接客户端之前,可以通过管理方式删除数据:
    • 删除客户端数据:请参阅客户端持久性文档以删除此数据。 如果使用内存持久性,请重新启动客户端进程。
    • 删除服务器端数据:您可以在IBM MessageSight上进行管理操作-请参阅使用Web UI的示例。
  2. 或者,使用以下连接/断开连接/连接顺序,以编程方式同时删除客户端和服务器端数据:
    • 临时连接具有相同客户端ID和cleanSession=true标志的客户端。
    • 断开。
    • 重新设置了cleanSession=false标志的客户端。
使用WebUI在IBM MessageSight上删除断开连接的MQTT客户端的状态
通过IBM MessageSight的WebUI删除MQTT客户端的状态

结论

本文向您展示了如何使用Paho开源库为IBM MessageSight编写HA MQTT V3.1客户端,并且您现在知道了有助于避免常见问题的最佳实践。


翻译自: https://www.ibm.com/developerworks/websphere/library/techarticles/1406_bakowski/1406_bakowski.html

mqtt客户端程序

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值