在keepAliveInterval超过之前,ActiveMQ丢失了发送给分离的使用者的持久消息

时间:2014-11-28 12:51:53

标签: activemq mqtt

我做什么

  • 有一个MQTT客户端 - Android应用程序。它连接到服务器并创建主题。
  • 通过ActiveMQ控制台发送包含所有默认设置和有效负载{“name”:“1st message”}的消息。消息已成功传递。如果打开Active Subscribers,enqueued = 1,dequeued = 1
  • 在Android上打开飞行模式,因此确实没有连接,但ActiveMQ仍会在Active Subscribers中显示它。
  • 发送消息WITH复选框持久交付和有效负载{“名称”:“持久”}如果打开活动订阅者,排队= 2,并且登队= 2
  • 发送消息没有复选框持久交付和有效负载{“名称”:“不持久”}如果打开活动订阅者,排队= 3,并且登队= 2
  • 在Android上关闭飞行模式,出现连接,只传递一条消息 - 不是持久性的。

我尝试了什么

配置

  • ActiveMQ版本:apache-activemq-5.10.0
  • $ java -version

    java版“1.6.0_65” Java(TM)SE运行时环境(版本1.6.0_65-b14-462-11M4609) Java HotSpot(TM)64位服务器VM(版本20.65-b04-462,混合模式)

  • OS:MaсOSX 10.9.5

  • conf / activemq.xml - 默认值,更改1行(两次添加“+ nio”)

<transportConnector name="mqtt+nio" uri="mqtt+nio://0.0.0.0:1883maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>

我的Android代码: (如果还不够,请告诉我)

public boolean connectIfNecessary() throws MqttException, IOException {

    synchronized (synchLock) {

        MqttConnectOptions connectionOptions = new MqttConnectOptions();
        connectionOptions.setCleanSession(false);
        connectionOptions.setUserName(userName);
        connectionOptions.setPassword(password.toCharArray());

        if (mqttClient == null) {

            stash = new MessageStash(applicationRoot + "/" + userName);

            mqttClient = new MqttAsyncClient(
                    brokerUrl,
                    userName,
                    new MqttDefaultFilePersistence(applicationRoot + "/" + userName)
            );
            Log.d(TAG, "Broker URL: " + brokerUrl);
            Log.d(TAG, "Connection clientId: " + userName);
            Log.d(TAG, "Application path: " + applicationRoot);

            mqttClient.setCallback(new MqttCallback() {
                @Override
                public void connectionLost(Throwable cause) {
                    Log.e(TAG, "Connection lost. Cause: " + cause.toString());
                    service.onConnectionLost();
                    notification.updateStatus(Notification.STATUS_DISCONNECTED);
                }

                @Override
                public void messageArrived(String topic, MqttMessage message) throws Exception {
                    ConnectionBinder recipient = recipients.get(getTopicFromInbound(topic));
                    if (recipient != null)
                        recipient.onMessageReceived(message.toString());
                    Log.d(TAG, "Message " + message + " received");
                }

                @Override
                public void deliveryComplete(IMqttDeliveryToken token) {
                    Log.d(TAG, "Message delivery complete");
                }
            });
        }

        if(mqttClient.isConnected()) // connection is already active - we can subscribe to the topic synchronously (see connect method)
            return true;

        if (connecting) // connecting was earlier initiated from a different thread - just let things take their course
            return false;

        connecting = true;

        notification.updateStatus(Notification.STATUS_CONNECTING);
        mqttClient.connect(connectionOptions, null, new IMqttActionListener() {
            @Override
            public void onSuccess(IMqttToken asyncActionToken) {
                connecting = false;
                Log.d(TAG, "connected");

                for (MessageStash.Message message : stash.get()) {
                    try {
                        send(message.topic(), message.body());
                        message.commit();
                    } catch (IOException e) {
                        // we can safely ignore it here because this code is executed after the connection is restored
                        // so there will be no need to stash the message, but even the connection will be lost while
                        // resubmitting messages here there will be no need to worry - the message will remain stashed
                        // because message.commit will not be executed
                    }
                }

                for (Map.Entry<String, ConnectionBinder> binder : recipients.entrySet())
                    subscribe(binder.getKey());

                notification.updateStatus(Notification.STATUS_CONNECTED);
            }

            @Override
            public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
                connecting = false;
                // todo: onConnectionLost only on recoverable exceptions
                Log.e(TAG, "Failed to connect :" + exception.toString());
                service.onConnectionLost();
                notification.updateStatus(Notification.STATUS_DISCONNECTED);
            }
        });
        return false;
    }
}

private void subscribe(String topic) {

    final String inboundTopic = getInboundTopic(topic);

    try {

        mqttClient.subscribe(inboundTopic, QoS_EXACLY_ONCE,
                null,
                new IMqttActionListener() {
                    @Override
                    public void onSuccess(IMqttToken iMqttToken) {
                        Log.d(TAG, "Successfully subscribed to " + inboundTopic);
                    }

                    @Override
                    public void onFailure(IMqttToken iMqttToken, Throwable throwable) {
                        Log.e(TAG, "Subscribe to " + inboundTopic + " failed: " + throwable.toString());
                    }
                });
    } catch (MqttException e) {
        logger.log(String.format("Exception subscribing to %s", inboundTopic), e);
    }
}

public void unregisterSubscriber(String topic) {

    String inboundTopic = getInboundTopic(topic);

    if (mqttClient.isConnected())
        try {
            mqttClient.unsubscribe(inboundTopic);
        } catch (MqttException e) {
            logger.log(String.format("Exception unsubscribing from %s", inboundTopic), e);
        }
    recipients.remove(topic);
}

public void send(String topic, String message) throws IOException {

    String outboundTopic = getOutboundTopic(topic);

    try {
        mqttClient.publish(outboundTopic, message.getBytes(), QoS_EXACLY_ONCE, true);
        Log.d(TAG, "published to " + outboundTopic + " :" + message);
    } catch (MqttException e) {
        switch (e.getReasonCode()) {
            // todo: double check this is the only recoverable failure
            // it seems likely that REASON_CODE_CLIENT_DISCONNECTING should also be here
            // I am not 100% sure, but I've seen a message 'Publish of blah failed ' with this reason code
            case MqttException.REASON_CODE_CLIENT_DISCONNECTING:
            case MqttException.REASON_CODE_CLIENT_NOT_CONNECTED:
                stash.put(topic, message);   // stash it for when the connection comes back online;
                break;
            default:
                logger.log(String.format("Exception publishing to %s", outboundTopic), e);
                break;
        }
    }
}

public static final String TAG = "MQTT Connection";
private static final int QoS_EXACLY_ONCE = 2;

private final Service service;
private final Notification notification;
private final Object synchLock = new Object();
private final String applicationRoot;
private final ILogger logger;

private MqttAsyncClient mqttClient;
private HashMap<String, ConnectionBinder> recipients = new HashMap<String, ConnectionBinder>();
private MessageStash stash;
private boolean connecting;
private String brokerUrl = null;
private String userName;
private String password;

2 个答案:

答案 0 :(得分:2)

问题的原因是ActiveMQ的MQTT连接器没有查看订阅的QoS标志。它包含以下代码

    QoS qoS;
    if (message.propertyExists(QOS_PROPERTY_NAME)) {
        int ordinal = message.getIntProperty(QOS_PROPERTY_NAME);
        qoS = QoS.values()[ordinal];

    } else {
        qoS = message.isPersistent() ? QoS.AT_MOST_ONCE : QoS.AT_LEAST_ONCE;
    }

因此,解决问题就足以为每条消息设置QoS。如果您使用NMS:

对于ActiveMQ 5.10

msg.Properties.SetInt("ActiveMQ.MQTT.QoS", 2); // 2 - EXACTLY_ONCE

对于ActiveMQ 5.9

 msg.Properties.SetInt("QoSPropertyName", 2); // 2 - EXACTLY_ONCE

使用ActiveMQ Web控制台无法做到这一点。

答案 1 :(得分:0)

花费大量时间来寻找该问题的答案。 我认为ActiveMq中存在一个错误。线

qoS = message.isPersistent() ? QoS.AT_MOST_ONCE : QoS.AT_LEAST_ONCE;

应更改为

qoS = message.isPersistent() ? QoS.AT_LEAST_ONCE : QoS.AT_MOST_ONCE ;