扩展您对MQTT客户端和负载平衡的了解。
介绍
MQTT是一种机器对机器(M2M),物联网连接协议。它被设计为一个非常轻量级的发布和订阅消息传输。对于需要较小代码占用空间和/或网络带宽非常宝贵的远程位置的连接非常有用。
每个MQTT客户端都订阅某些主题,并在发布者开始推送有关这些主题的消息时接收消息。
如何扩展?
水平扩展的目的是在同一应用程序的多个实例之间分配负载。如果这些实例中的MQTT客户端订阅了相同的主题,则将向每个实例传递相同的MQTT消息,这不是所理想的行为。
竞争消费者
Spring Cloud Stream定义了“Consumer Groups”的概念,如下所示:
“虽然发布 - 订阅模型可以轻松地通过共享主题连接应用程序,但通过创建给定应用程序的多个实例来扩展的能力同样重要。当这样做时, 应用程序的不同实例被置于竞争的消费者关系中,其中只有一个实例可以处理给定的消息。“
基于此定义,Spring Cloud Stream允许跨多个客户端分配主题的负载,如下图所示。示例
在此示例中,MQTT客户端将消息发布到RabbitMQ中的一个主题,并且多个消费者将共享该主题的消息。
安装RabbitMQ和MQTT插件
首先,我们将使用Docker镜像运行RabbitMQ的实例。然后,我们将安装MQTT插件。
>docker run -d --hostname vs29 --name vs29 -p 8081:15672 -p 5672:5672 -p 1883:1883 rabbitmq:3-management
现在,让我们检查一下该容器的启动日志:
>docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
fbd443154bf6 rabbitmq:3-management "docker-entrypoint.s..." 6 seconds ago Up 2 seconds 4369/tcp, 0.0.0.0:1883->1883/tcp, 5671/tcp, 15671/tcp, 0.0.0.0:5672->5672/tcp, 25672/tcp, 0.0.0.0:8081->15672/tcp vs29>docker logs fbd443154bf6 -f
....
....
...
2019-02-03 07:34:16.709 [info] <0.198.0>
node : rabbit@vs29
home dir : /var/lib/rabbitmq
config file(s) : /etc/rabbitmq/rabbitmq.conf
cookie hash : O+z+vUDvSh3J1vK/lV08Xw==
log(s) :
database dir : /var/lib/rabbitmq/mnesia/rabbit@vs29
日志的最后几行表示现在正在读取服务器。现在,让我们安装MQTT插件:
Navigate to the container first:>dockerexec-u 0 -it fbd443154bf6 /bin/bash
Enable the plugin now:
root@vs29:/#rabbitmq-plugins enable rabbitmq_mqtt
在RabbitMQ中添加新用户
让我们使用管理UI在RabbitMQ中添加一个新用户。打开URL http:// RabbitMQhost:8081 /然后导航到“Admin”选项卡(RabbitMQ中的默认凭据是guest / guest)。
要创建新用户:
在“用户名”字段中添加用户名。然后,让我们添加用户'client1'。在“密码”字段中设置密码,让我们将密码设置为“client1”
点击“添加用户”
默认用户无权访问任何虚拟主机。单击“client1”以编辑此用户的权限。在新页面中,单击“设置权限”以授予用户访问所有虚拟主机的权限。
要验证一切正常,请使用MQTT客户端将数据推送到新服务器。在本教程中,我们将使用Mosquitto服务器提供的命令'mosquitto_pub'和'mosquitto_sub'。首先,让我们订阅服务器中的所有主题。其次,我们将一些数据推送到服务器。
..>mosquitto_pub -h xxx.xxx.xxx.xxx -t /my/topic -m "Hello World" -u client1 -P client1 -p 1883
..>mosquitto_sub -h xxx.xxx.xxx.xxx -t "#" -u client1 -P client1
Hello World
如果一切顺利,你应该收到'Hello World'
创建消息接收器服务
本教程的目标是在同一应用程序的多个实例之间分配负载。因此,让我们使用Spring Boot和Spring Cloud Stream创建一个使用消息的简单服务。
创建一个新的Spring Boot项目。您可以使用IDE或Spring Initializer
调整您的mvn文件以包含以下内容:
org.springframework.bootspring-boot-starter-parent2.1.2.RELEASE1.8Greenwich.RELEASEorg.springframework.bootspring-boot-starter-amqporg.springframework.cloudspring-cloud-streamorg.springframework.cloudspring-cloud-stream-binder-rabbitorg.springframework.bootspring-boot-starter-testtestorg.springframework.cloudspring-cloud-stream-test-supporttestorg.springframework.cloudspring-cloud-dependencies${spring-cloud.version}pomimportorg.springframework.bootspring-boot-maven-pluginspring-milestonesSpring Milestoneshttps://repo.spring.io/milestone
现在,让我们添加我们的流监听器:
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.Input;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.messaging.SubscribableChannel;
@EnableBinding(MessageSink.InputChannel.class)
public class MessageSink {
@StreamListener(InputChannel.SINK)
public void handle(String message) {
System.out.println("new message:" + message + ", from worker :" + Thread.currentThread().getName());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public interface InputChannel {
String SINK = "message-sink";
@Input(SINK)
SubscribableChannel sink();
}
}
下一步是定义和配置我们的Channel(这是本教程中最重要的部分)。配置将添加到application.yml文件中:
spring:
cloud:
stream:
bindings:
message-sink :
destination: amq.topic
binder: rabbit
group: messages-consumer-group
consumer :
concurrency: 1
rabbit:
bindings:
message-sink:
consumer:
durableSubscription: true
declareExchange: true
exchangeDurable: true
exchangeType: topic
queueNameGroupOnly: true
rabbitmq:
host: xxx.xxx.xxx.xxx
password: client1
username: client1
我们来讨论application.yml中的重要配置:
destination:amq.topic是MQTT插件使用的默认Exchange,因此我们需要订阅它。
group:根据Spring Cloud Documents,“订阅给定目标的所有组都会收到已发布数据的副本,但每个组中只有一个成员从该目标收到给定的消息”
consumer.concurrency:可用于处理此使用者中收到的消息的最大线程数。我们将此数字修改为任何正值,并且仍然应用“分组消费者”的概念。
queueNameGroupOnly:根据Spring Cloud Documents,'当为true时,从名称等于的队列中使用group。否则,队列名称为destination.group。例如,当使用Spring Cloud Stream从现有RabbitMQ队列中使用时,这很有用。实际上,这是一个非常重要的配置。省略此属性将在启动服务时导致许多错误,因为Spring将生成以'amq'开头的队列名称,这是RabbitMQ不允许的。您将在此主题中获得更多详细信息
验证负载分配
让我们启动服务的两个实例,并使用MQTT客户端推送一些数据。首先,打开命令Shell窗口,导航到项目源,然后使用该命令构建项目
>mvn clean compile package
其次,打开两个命令Shell窗口,导航到项目文件夹,然后使用该命令启动服务
>java -jar target\balanced_mqtt_client-0.0.1-SNAPSHOT.jar
现在,我们将从MQTT客户端推送一些数据:
>mosquitto_pub -h xxx.xxx.xxx.xxx -t /my/topic -m"message 1"-u client1 -P client1 -p 1883>mosquitto_pub -h xxx.xxx.xxx.xxx -t /my/topic -m"message 2"-u client1 -P client1 -p 1883>mosquitto_pub -h xxx.xxx.xxx.xxx -t /my/topic -m"message 3"-u client1 -P client1 -p 1883>mosquitto_pub -h xxx.xxx.xxx.xxx -t /my/topic -m"message 4"-u client1 -P client1 -p 1883>mosquitto_pub -h xxx.xxx.xxx.xxx -t /my/topic -m"message 5"-u client1 -P client1 -p 1883
在消费者方面,将看到以下消息:
消费者1:
2019-02-07 10:33:55.848 INFO 14284 --- [ main] o.s.i.a.i.AmqpInboundChannelAdapter : started inbound.messages-consumer-group
2019-02-07 10:33:55.858 INFO 14284 --- [ main] r.n.cloud.rabbitmq.mqtt.MqttApplication : Started MqttApplication in 8.824 seconds (JVM running for 9.318)
new message:message 1, from worker :messages-consumer-group-1
new message:message 3, from worker :messages-consumer-group-1
new message:message 5, from worker :messages-consumer-group-1
消费者2:
O 13832 --- [ main] o.s.i.a.i.AmqpInboundChannelAdapter : started inbound.messages-consumer-group
O 13832 --- [ main] r.n.cloud.rabbitmq.mqtt.MqttApplication : Started MqttApplication in 7.8 seconds (JVM running for 8.495)
worker :messages-consumer-group-1
worker :messages-consumer-group-1
我们可以看到,消息是在两个消费者之间分配的。
结论
本教程将介绍如何使用RabbitMQ服务器和“分组使用者”功能实现负载均衡的MQTT使用者。本文示例代码下载地址