小伙伴们,你们好呀,我是老寇,跟我一起学习对接MQTT

安装EMQX

采用docker-compose一键式启动!!!

还没有安装docker朋友,参考文章下面两篇文章

# Ubuntu20.04安装Docker

# Centos7安装Docker 23.0.6

使用 emqx 5.4.1,按照老夫的教程来,请不要改版本号!!!
使用 emqx 5.4.1,按照老夫的教程来,请不要改版本号!!!
使用 emqx 5.4.1,按照老夫的教程来,请不要改版本号!!!
services:
emqx:
image: emqx/emqx:5.4.1
container_name: emqx
# 保持容器在没有守护程序的情况下运行
tty: true
restart: always
privileged: true
ports:
- "1883:1883"
- "8083:8083"
- "8883:8883"
- "18083:18083"
environment:
- TZ=Asia/Shanghai
volumes:
# 挂载数据存储
- ./emqx/data:/opt/emqx/data
# 挂载日志文件
- ./emqx/log:/opt/emqx/log
networks:
- laokou_network
networks:
laokou_network:
driver: bridge

访问 http://127.0.0.1:18083 设置密码

EMQX MQTT【摘抄自官方文档】

EMQX官方文档

MQTT 是物联网 (IoT) 的 OASIS 标准消息传递协议。它被设计为一种极轻量的发布/订阅消息传输协议,非常适合以较小的代码占用空间和极低的网络带宽连接远程设备。MQTT 目前广泛应用于汽车、制造、电信、石油和天然气等众多行业。

EMQX 完全兼容 MQTT 5.0 和 3.x,本节将介绍 MQTT 相关功能的基本配置项,包括基本 MQTT 设置、订阅设置、会话设置、强制关闭设置和强制垃圾回收设置等

客户端对接

本文章采用三种客户端对接

维度 Paho Hivemq-MQTT-Client Vert.x MQTT Client
协议支持 MQTT 3.1.1(5.0 实验性) MQTT 5.0 完整支持 MQTT 5.0(较新版本)
性能 中(同步模式) 高(异步非阻塞) 极高(响应式架构)
依赖复杂度 中(仅 Netty) 高(需 Vert.x 生态)
社区资源 丰富 较少 中等
适用场景 传统 IoT、跨语言项目 企业级 MQTT 5.0、高吞吐 响应式系统、高并发微服务

Paho【不推荐,连接不稳定】

Paho代码地址

引入依赖
<dependencies>
<dependency>
<groupId>org.eclipse.paho</groupId>
<artifactId>org.eclipse.paho.mqttv5.client</artifactId>
<version>1.2.5</version>
</dependency>
<dependency>
<groupId>org.eclipse.paho</groupId>
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
<version>1.2.5</version>
</dependency>
</dependencies>
项目集成

PahoProperties

/**
* @author laokou
*/
@Data
public class PahoProperties { private boolean auth = true; private String username = "emqx"; private String password = "laokou123"; private String host = "127.0.0.1"; private int port = 1883; private String clientId; private int subscribeQos = 1; private int publishQos = 0; private int willQos = 1; private int connectionTimeout = 60; private boolean manualAcks = false; // @formatter:off
/**
* 控制是否创建新会话(true=新建,false=复用历史会话). clearStart=true => Broker 会在连接断开后立即清除所有会话信息.
* clearStart=false => Broker 会在连接断开后保存会话信息,并在重新连接后复用会话信息.
* <a href="https://github.com/hivemq/hivemq-mqtt-client/issues/627">...</a>
*/
// @formatter:on
private boolean clearStart = false; private int receiveMaximum = 10000; private int maximumPacketSize = 10000; // @formatter:off
/**
* 默认会话保留一天.
* 最大值,4294967295L,会话过期时间【永不过期,单位秒】.
* 定义客户端断开后会话保留的时间(仅在 Clean Session = false 时生效).
*/
private long sessionExpiryInterval = 86400L;
// @formatter:on /**
* 心跳包每隔60秒发一次.
*/
private int keepAliveInterval = 60; private boolean automaticReconnect = true; private Set<String> topics = new HashSet<>(0); }

PahoMqttClientMessageCallbackV5

/**
* @author laokou
*/
@Slf4j
@RequiredArgsConstructor
public class PahoMqttClientMessageCallbackV5 implements MqttCallback { private final List<MessageHandler> messageHandlers; @Override
public void disconnected(MqttDisconnectResponse disconnectResponse) {
log.error("【Paho-V5】 => MQTT关闭连接");
} @Override
public void mqttErrorOccurred(MqttException ex) {
log.error("【Paho-V5】 => MQTT报错,错误信息:{}", ex.getMessage());
} @Override
public void messageArrived(String topic, MqttMessage message) {
for (MessageHandler messageHandler : messageHandlers) {
if (messageHandler.isSubscribe(topic)) {
log.info("【Paho-V5】 => MQTT接收到消息,Topic:{}", topic);
messageHandler.handle(new org.laokou.sample.mqtt.handler.MqttMessage(message.getPayload(), topic));
}
}
} @Override
public void deliveryComplete(IMqttToken token) {
log.info("【Paho-V5】 => MQTT消息发送成功,消息ID:{}", token.getMessageId());
} @Override
public void connectComplete(boolean reconnect, String uri) {
if (reconnect) {
log.info("【Paho-V5】 => MQTT重连成功,URI:{}", uri);
}
else {
log.info("【Paho-V5】 => MQTT建立连接,URI:{}", uri);
}
} @Override
public void authPacketArrived(int reasonCode, MqttProperties properties) {
log.info("【Paho-V5】 => 接收到身份验证数据包:{}", reasonCode);
} }

PahoV5MqttClientTest

/**
* @author laokou
*/
@SpringBootTest
@RequiredArgsConstructor
@TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)
class PahoV5MqttClientTest { private final List<MessageHandler> messageHandlers; @Test
void testMqttClient() throws InterruptedException {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(16); PahoProperties pahoProperties = new PahoProperties();
pahoProperties.setClientId("test-client-3");
pahoProperties.setTopics(Set.of("/test-topic-3/#"));
PahoMqttClientV5 pahoMqttClientV5 = new PahoMqttClientV5(pahoProperties, messageHandlers, scheduledExecutorService);
pahoMqttClientV5.open();
Thread.sleep(1000);
pahoMqttClientV5.publish("/test-topic-3/789", "Hello World789".getBytes());
} }

PahoMqttClientMessageCallbackV3

/**
* @author laokou
*/
@Slf4j
@RequiredArgsConstructor
public class PahoMqttClientMessageCallbackV3 implements MqttCallback { private final List<MessageHandler> messageHandlers; @Override
public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
log.info("【Paho-V3】 => MQTT消息发送成功,消息ID:{}", iMqttDeliveryToken.getMessageId());
} @Override
public void connectionLost(Throwable throwable) {
log.error("【Paho-V3】 => MQTT关闭连接");
} @Override
public void messageArrived(String topic, MqttMessage message) throws Exception {
for (MessageHandler messageHandler : messageHandlers) {
if (messageHandler.isSubscribe(topic)) {
log.info("【Paho-V3】 => MQTT接收到消息,Topic:{}", topic);
messageHandler.handle(new org.laokou.sample.mqtt.handler.MqttMessage(message.getPayload(), topic));
}
}
}
}

PahoV3MqttClientTest

/**
* @author laokou
*/
@SpringBootTest
@RequiredArgsConstructor
@TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)
class PahoV3MqttClientTest { private final List<MessageHandler> messageHandlers; @Test
void testMqttClient() throws InterruptedException {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(16); PahoProperties pahoProperties2 = new PahoProperties();
pahoProperties2.setClientId("test-client-4");
pahoProperties2.setTopics(Set.of("/test-topic-4/#"));
PahoMqttClientV3 pahoMqttClientV3 = new PahoMqttClientV3(pahoProperties2, messageHandlers, scheduledExecutorService);
pahoMqttClientV3.open();
Thread.sleep(1000);
pahoMqttClientV3.publish("/test-topic-4/000", "Hello World000".getBytes());
} }

Hivemq-MQTT-Client【不推荐】

注意:订阅一段时间收不到数据,标准mqtt5.0协议,不兼容emqx broker mqtt5.0

Hivemq代码地址

引入依赖
<dependencies>
<dependency>
<groupId>com.hivemq</groupId>
<artifactId>hivemq-mqtt-client-reactor</artifactId>
<version>1.3.5</version>
</dependency>
<dependency>
<groupId>com.hivemq</groupId>
<artifactId>hivemq-mqtt-client-epoll</artifactId>
<version>1.3.5</version>
<type>pom</type>
</dependency>
<dependencies>
项目集成

HivemqProperties

/**
* @author laokou
*/
@Data
public class HivemqProperties { private boolean auth = true; private String username = "emqx"; private String password = "laokou123"; private String host = "127.0.0.1"; private int port = 1883; private String clientId; private int subscribeQos = 1; private int publishQos = 0; private int willQos = 1; // @formatter:off
/**
* 控制是否创建新会话(true=新建,false=复用历史会话). clearStart=true => Broker 会在连接断开后立即清除所有会话信息.
* clearStart=false => Broker 会在连接断开后保存会话信息,并在重新连接后复用会话信息.
* <a href="https://github.com/hivemq/hivemq-mqtt-client/issues/627">...</a>
*/
// @formatter:on
private boolean clearStart = false; private int receiveMaximum = 10000; private int sendMaximum = 10000; private int maximumPacketSize = 10000; private int sendMaximumPacketSize = 10000; private int topicAliasMaximum = 1024; private int sendTopicAliasMaximum = 2048; private long messageExpiryInterval = 86400L; private boolean requestProblemInformation = true; private boolean requestResponseInformation = true; // @formatter:off
/**
* 默认会话保留一天.
* 最大值,4294967295L,会话过期时间【永不过期,单位秒】.
* 定义客户端断开后会话保留的时间(仅在 Clean Session = false 时生效).
*/
private long sessionExpiryInterval = 86400L;
// @formatter:on /**
* 心跳包每隔60秒发一次.
*/
private int keepAliveInterval = 60; private boolean automaticReconnect = true; private long automaticReconnectMaxDelay = 5; private long automaticReconnectInitialDelay = 1; private Set<String> topics = new HashSet<>(0); private int nettyThreads = 32; private boolean retain = false; private boolean noLocal = false; }

HivemqClientV5

/**
* @author laokou
*/
@Slf4j
public class HivemqClientV5 { /**
* 响应主题.
*/
private final String RESPONSE_TOPIC = "response/topic"; /**
* 服务下线数据.
*/
private final byte[] WILL_PAYLOAD = "offline".getBytes(UTF_8); /**
* 相关数据.
*/
private final byte[] CORRELATION_DATA = "correlationData".getBytes(UTF_8); private final HivemqProperties hivemqProperties; private final List<MessageHandler> messageHandlers; private volatile Mqtt5RxClient client; private final Object lock = new Object(); private volatile Disposable connectDisposable; private volatile Disposable subscribeDisposable; private volatile Disposable unSubscribeDisposable; private volatile Disposable publishDisposable; private volatile Disposable disconnectDisposable; private volatile Disposable consumeDisposable; public HivemqClientV5(HivemqProperties hivemqProperties, List<MessageHandler> messageHandlers) {
this.hivemqProperties = hivemqProperties;
this.messageHandlers = messageHandlers;
} public void open() {
if (Objects.isNull(client)) {
synchronized (lock) {
if (Objects.isNull(client)) {
client = getMqtt5ClientBuilder().buildRx();
}
}
}
connect();
consume();
} public void close() {
if (!Objects.isNull(client)) {
disconnectDisposable = client.disconnectWith()
.sessionExpiryInterval(hivemqProperties.getSessionExpiryInterval())
.applyDisconnect()
.subscribeOn(Schedulers.io())
.retryWhen(errors -> errors.scan(1, (retryCount, error) -> retryCount > 5 ? -1 : retryCount + 1)
.takeWhile(retryCount -> retryCount != -1)
.flatMap(retryCount -> Flowable.timer((long) Math.pow(2, retryCount) * 100, TimeUnit.MILLISECONDS)))
.subscribe(() -> log.info("【Hivemq-V5】 => MQTT断开连接成功,客户端ID:{}", hivemqProperties.getClientId()),
e -> log.error("【Hivemq-V5】 => MQTT断开连接失败,错误信息:{}", e.getMessage(), e));
}
} public void subscribe() {
String[] topics = getTopics();
subscribe(topics, getQosArray(topics));
} public String[] getTopics() {
return hivemqProperties.getTopics().toArray(String[]::new);
} public int[] getQosArray(String[] topics) {
return Stream.of(topics).mapToInt(item -> hivemqProperties.getSubscribeQos()).toArray();
} public void subscribe(String[] topics, int[] qosArray) {
checkTopicAndQos(topics, qosArray);
if (!Objects.isNull(client)) {
List<Mqtt5Subscription> subscriptions = new ArrayList<>(topics.length);
for (int i = 0; i < topics.length; i++) {
subscriptions.add(Mqtt5Subscription.builder()
.topicFilter(topics[i])
.qos(getMqttQos(qosArray[i]))
.retainAsPublished(hivemqProperties.isRetain())
.noLocal(hivemqProperties.isNoLocal())
.build());
}
subscribeDisposable = client.subscribeWith()
.addSubscriptions(subscriptions)
.applySubscribe()
.subscribeOn(Schedulers.io())
.retryWhen(errors -> errors.scan(1, (retryCount, error) -> retryCount > 5 ? -1 : retryCount + 1)
.takeWhile(retryCount -> retryCount != -1)
.flatMap(retryCount -> Flowable.timer((long) Math.pow(2, retryCount) * 100, TimeUnit.MILLISECONDS)))
.subscribe(ack -> log.info("【Hivemq-V5】 => MQTT订阅成功,主题: {}", String.join("、", topics)), e -> log
.error("【Hivemq-V5】 => MQTT订阅失败,主题:{},错误信息:{}", String.join("、", topics), e.getMessage(), e));
}
} public void unSubscribe() {
String[] topics = hivemqProperties.getTopics().toArray(String[]::new);
unSubscribe(topics);
} public void unSubscribe(String[] topics) {
checkTopic(topics);
if (!Objects.isNull(client)) {
List<MqttTopicFilter> matchedTopics = new ArrayList<>(topics.length);
for (String topic : topics) {
matchedTopics.add(MqttTopicFilter.of(topic));
}
unSubscribeDisposable = client.unsubscribeWith()
.addTopicFilters(matchedTopics)
.applyUnsubscribe()
.subscribeOn(Schedulers.io())
.retryWhen(errors -> errors.scan(1, (retryCount, error) -> retryCount > 5 ? -1 : retryCount + 1)
.takeWhile(retryCount -> retryCount != -1)
.flatMap(retryCount -> Flowable.timer((long) Math.pow(2, retryCount) * 100, TimeUnit.MILLISECONDS)))
.subscribe(ack -> log.info("【Hivemq-V5】 => MQTT取消订阅成功,主题:{}", String.join("、", topics)), e -> log
.error("【Hivemq-V5】 => MQTT取消订阅失败,主题:{},错误信息:{}", String.join("、", topics), e.getMessage(), e));
}
} public void publish(String topic, byte[] payload, int qos) {
if (!Objects.isNull(client)) {
publishDisposable = client
.publish(Flowable.just(Mqtt5Publish.builder()
.topic(topic)
.qos(getMqttQos(qos))
.payload(payload)
.noMessageExpiry()
.retain(hivemqProperties.isRetain())
.messageExpiryInterval(hivemqProperties.getMessageExpiryInterval())
.correlationData(CORRELATION_DATA)
.payloadFormatIndicator(Mqtt5PayloadFormatIndicator.UTF_8)
.contentType("text/plain")
.responseTopic(RESPONSE_TOPIC)
.build()))
.subscribeOn(Schedulers.io())
.retryWhen(errors -> errors.scan(1, (retryCount, error) -> retryCount > 5 ? -1 : retryCount + 1)
.takeWhile(retryCount -> retryCount != -1)
.flatMap(retryCount -> Flowable.timer((long) Math.pow(2, retryCount) * 100, TimeUnit.MILLISECONDS)))
.subscribe(ack -> log.info("【Hivemq-V5】 => MQTT消息发布成功,topic:{}", topic),
e -> log.error("【Hivemq-V5】 => MQTT消息发布失败,topic:{},错误信息:{}", topic, e.getMessage(), e));
}
} public void publish(String topic, byte[] payload) {
publish(topic, payload, hivemqProperties.getPublishQos());
} public void dispose(Disposable disposable) {
if (!Objects.isNull(disposable) && !disposable.isDisposed()) {
// 显式取消订阅
disposable.dispose();
}
} public void dispose() {
dispose(connectDisposable);
dispose(subscribeDisposable);
dispose(unSubscribeDisposable);
dispose(publishDisposable);
dispose(consumeDisposable);
dispose(disconnectDisposable);
} public void reSubscribe() {
log.info("【Hivemq-V5】 => MQTT重新订阅开始");
dispose(subscribeDisposable);
subscribe();
log.info("【Hivemq-V5】 => MQTT重新订阅结束");
} private MqttQos getMqttQos(int qos) {
return MqttQos.fromCode(qos);
} private void connect() {
connectDisposable = client.connectWith()
.keepAlive(hivemqProperties.getKeepAliveInterval())
.cleanStart(hivemqProperties.isClearStart())
.sessionExpiryInterval(hivemqProperties.getSessionExpiryInterval())
.willPublish()
.topic("will/topic")
.payload(WILL_PAYLOAD)
.qos(getMqttQos(hivemqProperties.getWillQos()))
.retain(true)
.messageExpiryInterval(100)
.delayInterval(10)
.payloadFormatIndicator(Mqtt5PayloadFormatIndicator.UTF_8)
.contentType("text/plain")
.responseTopic(RESPONSE_TOPIC)
.correlationData(CORRELATION_DATA)
.applyWillPublish()
.restrictions()
.receiveMaximum(hivemqProperties.getReceiveMaximum())
.sendMaximum(hivemqProperties.getSendMaximum())
.maximumPacketSize(hivemqProperties.getMaximumPacketSize())
.sendMaximumPacketSize(hivemqProperties.getSendMaximumPacketSize())
.topicAliasMaximum(hivemqProperties.getTopicAliasMaximum())
.sendTopicAliasMaximum(hivemqProperties.getSendTopicAliasMaximum())
.requestProblemInformation(hivemqProperties.isRequestProblemInformation())
.requestResponseInformation(hivemqProperties.isRequestResponseInformation())
.applyRestrictions()
.applyConnect()
.toFlowable()
.firstElement()
.subscribeOn(Schedulers.io())
.retryWhen(errors -> errors.scan(1, (retryCount, error) -> retryCount > 5 ? -1 : retryCount + 1)
.takeWhile(retryCount -> retryCount != -1)
.flatMap(retryCount -> Flowable.timer((long) Math.pow(2, retryCount) * 100, TimeUnit.MILLISECONDS)))
.subscribe(
ack -> log.info("【Hivemq-V5】 => MQTT连接成功,主机:{},端口:{},客户端ID:{}", hivemqProperties.getHost(),
hivemqProperties.getPort(), hivemqProperties.getClientId()),
e -> log.error("【Hivemq-V5】 => MQTT连接失败,错误信息:{}", e.getMessage(), e));
} private void consume() {
if (!Objects.isNull(client)) {
consumeDisposable = client.publishes(MqttGlobalPublishFilter.ALL)
.onBackpressureBuffer(8192)
.observeOn(Schedulers.computation(), false, 8192)
.doOnSubscribe(subscribe -> {
log.info("【Hivemq-V5】 => MQTT开始订阅消息,请稍候。。。。。。");
reSubscribe();
})
.subscribeOn(Schedulers.io())
.retryWhen(errors -> errors.scan(1, (retryCount, error) -> retryCount > 5 ? -1 : retryCount + 1)
.takeWhile(retryCount -> retryCount != -1)
.flatMap(retryCount -> Flowable.timer((long) Math.pow(2, retryCount) * 100, TimeUnit.MILLISECONDS)))
.subscribe(publish -> {
for (MessageHandler messageHandler : messageHandlers) {
if (messageHandler.isSubscribe(publish.getTopic().toString())) {
log.info("【Hivemq-V5】 => MQTT接收到消息,Topic:{}", publish.getTopic());
messageHandler
.handle(new MqttMessage(publish.getPayloadAsBytes(), publish.getTopic().toString()));
}
}
}, e -> log.error("【Hivemq-V5】 => MQTT消息处理失败,错误信息:{}", e.getMessage(), e),
() -> log.info("【Hivemq-V5】 => MQTT订阅消息结束,请稍候。。。。。。"));
}
} private Mqtt5ClientBuilder getMqtt5ClientBuilder() {
Mqtt5ClientBuilder builder = Mqtt5Client.builder().addConnectedListener(listener -> {
Optional<? extends MqttClientConnectionConfig> config = Optional
.of(listener.getClientConfig().getConnectionConfig())
.get();
config.ifPresent(mqttClientConnectionConfig -> log.info("【Hivemq-V5】 => MQTT连接保持时间:{}ms",
mqttClientConnectionConfig.getKeepAlive()));
log.info("【Hivemq-V5】 => MQTT已连接,客户端ID:{}", hivemqProperties.getClientId());
})
.addDisconnectedListener(
listener -> log.error("【Hivemq-V5】 => MQTT已断开连接,客户端ID:{}", hivemqProperties.getClientId()))
.identifier(hivemqProperties.getClientId())
.serverHost(hivemqProperties.getHost())
.serverPort(hivemqProperties.getPort())
.executorConfig(MqttClientExecutorConfig.builder()
.nettyExecutor(ThreadUtils.newVirtualTaskExecutor())
.nettyThreads(hivemqProperties.getNettyThreads())
.applicationScheduler(Schedulers.from(ThreadUtils.newVirtualTaskExecutor()))
.build());
// 开启重连
if (hivemqProperties.isAutomaticReconnect()) {
builder.automaticReconnect()
.initialDelay(hivemqProperties.getAutomaticReconnectInitialDelay(), TimeUnit.SECONDS)
.maxDelay(hivemqProperties.getAutomaticReconnectMaxDelay(), TimeUnit.SECONDS)
.applyAutomaticReconnect();
}
if (hivemqProperties.isAuth()) {
builder.simpleAuth()
.username(hivemqProperties.getUsername())
.password(hivemqProperties.getPassword().getBytes())
.applySimpleAuth();
}
return builder;
} private void checkTopicAndQos(String[] topics, int[] qosArray) {
if (topics == null || qosArray == null) {
throw new IllegalArgumentException("【" + "Hivemq-V5" + "】 => Topics and QoS arrays cannot be null");
}
if (topics.length != qosArray.length) {
throw new IllegalArgumentException("【" + "Hivemq-V5" + "】 => Topics and QoS arrays must have the same length");
}
if (topics.length == 0) {
throw new IllegalArgumentException("【" + "Hivemq-V5" + "】 => Topics array cannot be empty");
}
} private void checkTopic(String[] topics) {
if (topics.length == 0) {
throw new IllegalArgumentException("【" + "Hivemq-V5" + "】 => Topics array cannot be empty");
}
} }

HivemqV5MqttClientTest

/**
* @author laokou
*/
@SpringBootTest
@RequiredArgsConstructor
@TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)
class HivemqV5MqttClientTest { private final List<MessageHandler> messageHandlers; @Test
void testMqttClient() throws InterruptedException {
HivemqProperties hivemqProperties = new HivemqProperties();
hivemqProperties.setClientId("test-client-1");
hivemqProperties.setTopics(Set.of("/test-topic-1/#"));
HivemqClientV5 hivemqClientV5 = new HivemqClientV5(hivemqProperties, messageHandlers);
hivemqClientV5.open();
hivemqClientV5.publish("/test-topic-1/123", "Hello World123".getBytes());
} }

HivemqClientV3

/**
* @author laokou
*/
@Slf4j
public class HivemqClientV3 { /**
* 服务下线数据.
*/
private final byte[] WILL_PAYLOAD = "offline".getBytes(UTF_8); private final HivemqProperties hivemqProperties; private final List<MessageHandler> messageHandlers; private volatile Mqtt3RxClient client; private final Object lock = new Object(); private volatile Disposable connectDisposable; private volatile Disposable subscribeDisposable; private volatile Disposable unSubscribeDisposable; private volatile Disposable publishDisposable; private volatile Disposable disconnectDisposable; private volatile Disposable consumeDisposable; public HivemqClientV3(HivemqProperties hivemqProperties, List<MessageHandler> messageHandlers) {
this.hivemqProperties = hivemqProperties;
this.messageHandlers = messageHandlers;
} public void open() {
if (Objects.isNull(client)) {
synchronized (lock) {
if (Objects.isNull(client)) {
client = getMqtt3ClientBuilder().buildRx();
}
}
}
connect();
consume();
} public void close() {
if (!Objects.isNull(client)) {
disconnectDisposable = client.disconnect()
.subscribeOn(Schedulers.io())
.retryWhen(errors -> errors.scan(1, (retryCount, error) -> retryCount > 5 ? -1 : retryCount + 1)
.takeWhile(retryCount -> retryCount != -1)
.flatMap(retryCount -> Flowable.timer((long) Math.pow(2, retryCount) * 100, TimeUnit.MILLISECONDS)))
.subscribe(() -> log.info("【Hivemq-V3】 => MQTT断开连接成功,客户端ID:{}", hivemqProperties.getClientId()),
e -> log.error("【Hivemq-V3】 => MQTT断开连接失败,错误信息:{}", e.getMessage(), e));
}
} public void subscribe() {
String[] topics = getTopics();
subscribe(topics, getQosArray(topics));
} public String[] getTopics() {
return hivemqProperties.getTopics().toArray(String[]::new);
} public int[] getQosArray(String[] topics) {
return Stream.of(topics).mapToInt(item -> hivemqProperties.getSubscribeQos()).toArray();
} public void subscribe(String[] topics, int[] qosArray) {
checkTopicAndQos(topics, qosArray);
if (!Objects.isNull(client)) {
List<Mqtt3Subscription> subscriptions = new ArrayList<>(topics.length);
for (int i = 0; i < topics.length; i++) {
subscriptions.add(Mqtt3Subscription.builder()
.topicFilter(topics[i])
.qos(getMqttQos(qosArray[i]))
.build());
}
subscribeDisposable = client.subscribeWith()
.addSubscriptions(subscriptions)
.applySubscribe()
.subscribeOn(Schedulers.io())
.retryWhen(errors -> errors.scan(1, (retryCount, error) -> retryCount > 5 ? -1 : retryCount + 1)
.takeWhile(retryCount -> retryCount != -1)
.flatMap(retryCount -> Flowable.timer((long) Math.pow(2, retryCount) * 100, TimeUnit.MILLISECONDS)))
.subscribe(ack -> log.info("【Hivemq-V3】 => MQTT订阅成功,主题: {}", String.join("、", topics)), e -> log
.error("【Hivemq-V3】 => MQTT订阅失败,主题:{},错误信息:{}", String.join("、", topics), e.getMessage(), e));
}
} public void unSubscribe() {
String[] topics = hivemqProperties.getTopics().toArray(String[]::new);
unSubscribe(topics);
} public void unSubscribe(String[] topics) {
checkTopic(topics);
if (!Objects.isNull(client)) {
List<MqttTopicFilter> matchedTopics = new ArrayList<>(topics.length);
for (String topic : topics) {
matchedTopics.add(MqttTopicFilter.of(topic));
}
unSubscribeDisposable = client.unsubscribeWith()
.addTopicFilters(matchedTopics)
.applyUnsubscribe()
.subscribeOn(Schedulers.io())
.retryWhen(errors -> errors.scan(1, (retryCount, error) -> retryCount > 5 ? -1 : retryCount + 1)
.takeWhile(retryCount -> retryCount != -1)
.flatMap(retryCount -> Flowable.timer((long) Math.pow(2, retryCount) * 100, TimeUnit.MILLISECONDS)))
.subscribe(() -> log.info("【Hivemq-V3】 => MQTT取消订阅成功,主题:{}", String.join("、", topics)), e -> log
.error("【Hivemq-V3】 => MQTT取消订阅失败,主题:{},错误信息:{}", String.join("、", topics), e.getMessage(), e));
}
} public void publish(String topic, byte[] payload, int qos) {
if (!Objects.isNull(client)) {
publishDisposable = client
.publish(Flowable.just(Mqtt3Publish.builder()
.topic(topic)
.qos(getMqttQos(qos))
.payload(payload)
.retain(hivemqProperties.isRetain())
.build()))
.subscribeOn(Schedulers.io())
.retryWhen(errors -> errors.scan(1, (retryCount, error) -> retryCount > 5 ? -1 : retryCount + 1)
.takeWhile(retryCount -> retryCount != -1)
.flatMap(retryCount -> Flowable.timer((long) Math.pow(2, retryCount) * 100, TimeUnit.MILLISECONDS)))
.subscribe(ack -> log.info("【Hivemq-V3】 => MQTT消息发布成功,topic:{}", topic),
e -> log.error("【Hivemq-V3】 => MQTT消息发布失败,topic:{},错误信息:{}", topic, e.getMessage(), e));
}
} public void publish(String topic, byte[] payload) {
publish(topic, payload, hivemqProperties.getPublishQos());
} public void dispose(Disposable disposable) {
if (!Objects.isNull(disposable) && !disposable.isDisposed()) {
// 显式取消订阅
disposable.dispose();
}
} public void dispose() {
dispose(connectDisposable);
dispose(subscribeDisposable);
dispose(unSubscribeDisposable);
dispose(publishDisposable);
dispose(consumeDisposable);
dispose(disconnectDisposable);
} public void reSubscribe() {
log.info("【Hivemq-V3】 => MQTT重新订阅开始");
dispose(subscribeDisposable);
subscribe();
log.info("【Hivemq-V3】 => MQTT重新订阅结束");
} private MqttQos getMqttQos(int qos) {
return MqttQos.fromCode(qos);
} private void connect() {
connectDisposable = client.connectWith()
.keepAlive(hivemqProperties.getKeepAliveInterval())
.willPublish()
.topic("will/topic")
.payload(WILL_PAYLOAD)
.qos(getMqttQos(hivemqProperties.getWillQos()))
.retain(true)
.applyWillPublish()
.restrictions()
.sendMaximum(hivemqProperties.getSendMaximum())
.sendMaximumPacketSize(hivemqProperties.getSendMaximumPacketSize())
.applyRestrictions()
.applyConnect()
.toFlowable()
.firstElement()
.subscribeOn(Schedulers.io())
.retryWhen(errors -> errors.scan(1, (retryCount, error) -> retryCount > 5 ? -1 : retryCount + 1)
.takeWhile(retryCount -> retryCount != -1)
.flatMap(retryCount -> Flowable.timer((long) Math.pow(2, retryCount) * 100, TimeUnit.MILLISECONDS)))
.subscribe(
ack -> log.info("【Hivemq-V3】 => MQTT连接成功,主机:{},端口:{},客户端ID:{}", hivemqProperties.getHost(),
hivemqProperties.getPort(), hivemqProperties.getClientId()),
e -> log.error("【Hivemq-V3】 => MQTT连接失败,错误信息:{}", e.getMessage(), e));
} private void consume() {
if (!Objects.isNull(client)) {
consumeDisposable = client.publishes(MqttGlobalPublishFilter.ALL)
.onBackpressureBuffer(8192)
.observeOn(Schedulers.computation(), false, 8192)
.doOnSubscribe(subscribe -> {
log.info("【Hivemq-V3】 => MQTT开始订阅消息,请稍候。。。。。。");
reSubscribe();
})
.subscribeOn(Schedulers.io())
.retryWhen(errors -> errors.scan(1, (retryCount, error) -> retryCount > 5 ? -1 : retryCount + 1)
.takeWhile(retryCount -> retryCount != -1)
.flatMap(retryCount -> Flowable.timer((long) Math.pow(2, retryCount) * 100, TimeUnit.MILLISECONDS)))
.subscribe(publish -> {
for (MessageHandler messageHandler : messageHandlers) {
if (messageHandler.isSubscribe(publish.getTopic().toString())) {
log.info("【Hivemq-V3】 => MQTT接收到消息,Topic:{}", publish.getTopic());
messageHandler
.handle(new MqttMessage(publish.getPayloadAsBytes(), publish.getTopic().toString()));
}
}
}, e -> log.error("【Hivemq-V3】 => MQTT消息处理失败,错误信息:{}", e.getMessage(), e),
() -> log.info("【Hivemq-V3】 => MQTT订阅消息结束,请稍候。。。。。。"));
}
} private Mqtt3ClientBuilder getMqtt3ClientBuilder() {
Mqtt3ClientBuilder builder = Mqtt3Client.builder().addConnectedListener(listener -> {
Optional<? extends MqttClientConnectionConfig> config = Optional
.of(listener.getClientConfig().getConnectionConfig())
.get();
config.ifPresent(mqttClientConnectionConfig -> log.info("【Hivemq-V5】 => MQTT连接保持时间:{}ms",
mqttClientConnectionConfig.getKeepAlive()));
log.info("【Hivemq-V3】 => MQTT已连接,客户端ID:{}", hivemqProperties.getClientId());
})
.addDisconnectedListener(
listener -> log.error("【Hivemq-V3】 => MQTT已断开连接,客户端ID:{}", hivemqProperties.getClientId()))
.identifier(hivemqProperties.getClientId())
.serverHost(hivemqProperties.getHost())
.serverPort(hivemqProperties.getPort())
.executorConfig(MqttClientExecutorConfig.builder()
.nettyExecutor(ThreadUtils.newVirtualTaskExecutor())
.nettyThreads(hivemqProperties.getNettyThreads())
.applicationScheduler(Schedulers.from(ThreadUtils.newVirtualTaskExecutor()))
.build());
// 开启重连
if (hivemqProperties.isAutomaticReconnect()) {
builder.automaticReconnect()
.initialDelay(hivemqProperties.getAutomaticReconnectInitialDelay(), TimeUnit.SECONDS)
.maxDelay(hivemqProperties.getAutomaticReconnectMaxDelay(), TimeUnit.SECONDS)
.applyAutomaticReconnect();
}
if (hivemqProperties.isAuth()) {
builder.simpleAuth()
.username(hivemqProperties.getUsername())
.password(hivemqProperties.getPassword().getBytes())
.applySimpleAuth();
}
return builder;
} private void checkTopicAndQos(String[] topics, int[] qosArray) {
if (topics == null || qosArray == null) {
throw new IllegalArgumentException("【" + "Hivemq-V3" + "】 => Topics and QoS arrays cannot be null");
}
if (topics.length != qosArray.length) {
throw new IllegalArgumentException("【" + "Hivemq-V3" + "】 => Topics and QoS arrays must have the same length");
}
if (topics.length == 0) {
throw new IllegalArgumentException("【" + "Hivemq-V3" + "】 => Topics array cannot be empty");
}
} private void checkTopic(String[] topics) {
if (topics.length == 0) {
throw new IllegalArgumentException("【" + "Hivemq-V3" + "】 => Topics array cannot be empty");
}
} }

HivemqV3MqttClientTest

/**
* @author laokou
*/
@SpringBootTest
@RequiredArgsConstructor
@TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)
class HivemqV3MqttClientTest { private final List<MessageHandler> messageHandlers; @Test
void testMqttClient() throws InterruptedException {
HivemqProperties hivemqProperties2 = new HivemqProperties();
hivemqProperties2.setClientId("test-client-2");
hivemqProperties2.setTopics(Set.of("/test-topic-2/#"));
HivemqClientV3 hivemqClientV3 = new HivemqClientV3(hivemqProperties2, messageHandlers);
hivemqClientV3.open();
hivemqClientV3.publish("/test-topic-2/456", "Hello World456".getBytes());
} }

Vert.x MQTT Client【推荐,只兼容mqtt3.1.1】

# Vert.x MQTT文档

引入依赖
<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-mqtt</artifactId>
<version>4.5.14</version>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>3.7.5</version>
</dependency>
</dependencies>
项目集成

MqttClientProperties

/**
* @author laokou
*/
@Data
public class MqttClientProperties { private boolean auth = true; private String username = "emqx"; private String password = "laokou123"; private String host = "127.0.0.1"; private int port = 1883; private String clientId = UUIDGenerator.generateUUID(); // @formatter:off
/**
* 控制是否创建新会话(true=新建,false=复用历史会话). clearStart=true => Broker 会在连接断开后立即清除所有会话信息.
* clearStart=false => Broker 会在连接断开后保存会话信息,并在重新连接后复用会话信息.
*/
// @formatter:on
private boolean clearSession = false; private int receiveBufferSize = Integer.MAX_VALUE; private int maxMessageSize = -1; /**
* 心跳包每隔60秒发一次.
*/
private int keepAliveInterval = 60; private boolean autoKeepAlive = true; private long reconnectInterval = 1000; private int reconnectAttempts = Integer.MAX_VALUE; private Map<String, Integer> topics = new HashMap<>(0); private int willQos = 1; private boolean willRetain = false; private int ackTimeout = -1; private boolean autoAck = true; /**
* 服务下线主题.
*/
private String willTopic = "/will"; /**
* 服务下线数据.
*/
private String willPayload = "offline"; }

VertxConfig

/**
* @author laokou
*/
@Configuration
public class VertxConfig { @Bean
public Vertx vertx() {
VertxOptions vertxOptions = new VertxOptions();
vertxOptions.setMaxEventLoopExecuteTime(60);
vertxOptions.setMaxWorkerExecuteTime(60);
vertxOptions.setMaxEventLoopExecuteTimeUnit(TimeUnit.SECONDS);
vertxOptions.setMaxWorkerExecuteTimeUnit(TimeUnit.SECONDS);
vertxOptions.setPreferNativeTransport(true);
return Vertx.vertx(vertxOptions);
} }

VertxMqttClient

注意:vertx-mqtt不支持客户端自动断线重连,网络不通畅或连接关闭,需要自己手动调用连接!!!实现这个重连的功能

/**
* @author laokou
*/
@Slf4j
public class VertxMqttClient { private final Sinks.Many<MqttPublishMessage> messageSink = Sinks.many()
.multicast()
.onBackpressureBuffer(Integer.MAX_VALUE, false); private final MqttClient mqttClient; private final Vertx vertx; private final MqttClientProperties mqttClientProperties; private final List<MessageHandler> messageHandlers; private final List<Disposable> disposables; private final AtomicBoolean isConnected = new AtomicBoolean(false); private final AtomicBoolean isLoaded = new AtomicBoolean(false); private final AtomicBoolean isReconnected = new AtomicBoolean(true); public VertxMqttClient(final Vertx vertx, final MqttClientProperties mqttClientProperties,
final List<MessageHandler> messageHandlers) {
this.vertx = vertx;
this.mqttClientProperties = mqttClientProperties;
this.mqttClient = MqttClient.create(vertx, getOptions());
this.messageHandlers = messageHandlers;
this.disposables = Collections.synchronizedList(new ArrayList<>());
} public void open() {
mqttClient.closeHandler(v -> {
isConnected.set(false);
log.error("【Vertx-MQTT】 => MQTT连接断开,客户端ID:{}", mqttClientProperties.getClientId());
reconnect();
})
.publishHandler(messageSink::tryEmitNext)
// 仅接收QoS1和QoS2的消息
.publishCompletionHandler(id -> {
// log.info("【Vertx-MQTT】 => 接收MQTT的PUBACK或PUBCOMP数据包,数据包ID:{}", id);
})
.subscribeCompletionHandler(ack -> {
// log.info("【Vertx-MQTT】 => 接收MQTT的SUBACK数据包,数据包ID:{}", ack.messageId());
})
.unsubscribeCompletionHandler(id -> {
// log.info("【Vertx-MQTT】 => 接收MQTT的UNSUBACK数据包,数据包ID:{}", id);
})
.pingResponseHandler(s -> {
// log.info("【Vertx-MQTT】 => 接收MQTT的PINGRESP数据包");
})
.connect(mqttClientProperties.getPort(), mqttClientProperties.getHost(), connectResult -> {
if (connectResult.succeeded()) {
isConnected.set(true);
log.info("【Vertx-MQTT】 => MQTT连接成功,主机:{},端口:{},客户端ID:{}", mqttClientProperties.getHost(),
mqttClientProperties.getPort(), mqttClientProperties.getClientId());
resubscribe();
}
else {
isConnected.set(false);
Throwable ex = connectResult.cause();
log.error("【Vertx-MQTT】 => MQTT连接失败,原因:{},客户端ID:{}", ex.getMessage(),
mqttClientProperties.getClientId(), ex);
reconnect();
}
});
} public void close() {
disconnect();
} /**
* Sends the PUBLISH message to the remote MQTT server.
* @param topic topic on which the message is published
* @param payload message payload
* @param qos QoS level
* @param isDup if the message is a duplicate
* @param isRetain if the message needs to be retained
*/
public void publish(String topic, int qos, String payload, boolean isDup, boolean isRetain) {
mqttClient.publish(topic, Buffer.buffer(payload), convertQos(qos), isDup, isRetain);
} private void reconnect() {
if (isReconnected.get()) {
log.info("【Vertx-MQTT】 => MQTT尝试重连");
vertx.setTimer(mqttClientProperties.getReconnectInterval(),
handler -> ThreadUtils.newVirtualTaskExecutor().execute(this::open));
}
} private void subscribe() {
Map<String, Integer> topics = mqttClientProperties.getTopics();
checkTopicAndQos(topics);
mqttClient.subscribe(topics, subscribeResult -> {
if (subscribeResult.succeeded()) {
log.info("【Vertx-MQTT】 => MQTT订阅成功,主题: {}", String.join("、", topics.keySet()));
}
else {
Throwable ex = subscribeResult.cause();
log.error("【Vertx-MQTT】 => MQTT订阅失败,主题:{},错误信息:{}", String.join("、", topics.keySet()), ex.getMessage(),
ex);
}
});
} private void resubscribe() {
if (isConnected.get() || mqttClient.isConnected()) {
ThreadUtils.newVirtualTaskExecutor().execute(this::subscribe);
}
if (isLoaded.compareAndSet(false, true)) {
ThreadUtils.newVirtualTaskExecutor().execute(this::consume);
}
} private void consume() {
Disposable disposable = messageSink.asFlux().doOnNext(mqttPublishMessage -> {
String topic = mqttPublishMessage.topicName();
log.info("【Vertx-MQTT】 => MQTT接收到消息,Topic:{}", topic);
for (MessageHandler messageHandler : messageHandlers) {
if (messageHandler.isSubscribe(topic)) {
messageHandler.handle(new MqttMessage(mqttPublishMessage.payload(), topic));
}
}
}).subscribeOn(Schedulers.boundedElastic()).subscribe();
disposables.add(disposable);
} private void disposable() {
for (Disposable disposable : disposables) {
if (ObjectUtils.isNotNull(disposable) && !disposable.isDisposed()) {
disposable.dispose();
}
}
} private void disconnect() {
isReconnected.set(false);
mqttClient.disconnect(disconnectResult -> {
if (disconnectResult.succeeded()) {
disposable();
log.info("【Vertx-MQTT】 => MQTT断开连接成功");
disposables.clear();
}
else {
Throwable ex = disconnectResult.cause();
log.error("【Vertx-MQTT】 => MQTT断开连接失败,错误信息:{}", ex.getMessage(), ex);
}
});
} private void unsubscribe(List<String> topics) {
checkTopic(topics);
mqttClient.unsubscribe(topics, unsubscribeResult -> {
if (unsubscribeResult.succeeded()) {
log.info("【Vertx-MQTT】 => MQTT取消订阅成功,主题:{}", String.join("、", topics));
}
else {
Throwable ex = unsubscribeResult.cause();
log.error("【Vertx-MQTT】 => MQTT取消订阅失败,主题:{},错误信息:{}", String.join("、", topics), ex.getMessage(), ex);
}
});
} private MqttClientOptions getOptions() {
MqttClientOptions options = new MqttClientOptions();
options.setClientId(mqttClientProperties.getClientId());
options.setCleanSession(mqttClientProperties.isClearSession());
options.setAutoKeepAlive(mqttClientProperties.isAutoKeepAlive());
options.setKeepAliveInterval(mqttClientProperties.getKeepAliveInterval());
options.setReconnectAttempts(mqttClientProperties.getReconnectAttempts());
options.setReconnectInterval(mqttClientProperties.getReconnectInterval());
options.setWillQoS(mqttClientProperties.getWillQos());
options.setWillTopic(mqttClientProperties.getWillTopic());
options.setAutoAck(mqttClientProperties.isAutoAck());
options.setAckTimeout(mqttClientProperties.getAckTimeout());
options.setWillRetain(mqttClientProperties.isWillRetain());
options.setWillMessageBytes(Buffer.buffer(mqttClientProperties.getWillPayload()));
options.setReceiveBufferSize(mqttClientProperties.getReceiveBufferSize());
options.setMaxMessageSize(mqttClientProperties.getMaxMessageSize());
if (mqttClientProperties.isAuth()) {
options.setPassword(mqttClientProperties.getPassword());
options.setUsername(mqttClientProperties.getUsername());
}
return options;
} private void checkTopicAndQos(Map<String, Integer> topics) {
topics.forEach((topic, qos) -> {
if (StringUtils.isEmpty(topic) || ObjectUtils.isNull(qos)) {
throw new IllegalArgumentException("【Vertx-MQTT】 => Topic and QoS cannot be null");
}
});
} private void checkTopic(List<String> topics) {
if (CollectionUtils.isEmpty(topics)) {
throw new IllegalArgumentException("【Vertx-MQTT】 => Topics list cannot be empty");
}
} private MqttQoS convertQos(int qos) {
return MqttQoS.valueOf(qos);
} }

VertxMqttClientTest

/**
* @author laokou
*/
@SpringBootTest
@RequiredArgsConstructor
@ContextConfiguration(classes = { DefaultMessageHandler.class, VertxConfig.class })
@TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL)
class VertxMqttClientTest { private final List<MessageHandler> messageHandlers; private final Vertx vertx; @Test
void testMqttClient() throws InterruptedException {
MqttClientProperties properties = new MqttClientProperties();
properties.setHost("127.0.0.1");
properties.setPort(1883);
properties.setUsername("emqx");
properties.setPassword("laokou123");
properties.setClientId("test-client-1");
properties.setTopics(Map.of("/test-topic-1/#", 1));
VertxMqttClient vertxMqttClient = new VertxMqttClient(vertx, properties, messageHandlers);
Assertions.assertDoesNotThrow(vertxMqttClient::open);
Thread.sleep(500);
Assertions.assertDoesNotThrow(() -> vertxMqttClient.publish("/test-topic-1/test", 1, "test", false, false));
Thread.sleep(500);
Assertions.assertDoesNotThrow(vertxMqttClient::close);
Thread.sleep(500);
} }

详细代码请点击

非常推荐使用vertx-mqtt,项目平稳运行好用!!!

但是,需要时注意的是,项目部署到Linux系统,需要最少分配 -Xmx2100m -Xms2100m 内存,不然连接会关闭!

我是老寇,我们下次再见啦~

物联网之对接MQTT最佳实践的更多相关文章

  1. 海量大数据大屏分析展示一步到位:DataWorks数据服务对接DataV最佳实践

    1. 概述 数据服务(https://ds-cn-shanghai.data.aliyun.com)  是DataWorks产品家族的一员,提供了快速将数据表生成API的能力,通过可视化的向导,一分钟 ...

  2. 海量大数据大屏分析展示一步到位:DataWorks数据服务+MaxCompute Lightning对接DataV最佳实践

    1. 概述 数据服务(https://ds-cn-shanghai.data.aliyun.com) 是DataWorks产品家族的一员,提供了快速将数据表生成API的能力,通过可视化的向导,一分钟“ ...

  3. Ubuntu14.04+RabbitMQ3.6.3+Golang的最佳实践

    目录 [TOC] 1.RabbitMQ介绍 1.1.什么是RabbitMQ?   RabbitMQ 是由 LShift 提供的一个 Advanced Message Queuing Protocol ...

  4. 基于AngularJS的前端云组件最佳实践

    AngularJS是google设计和开发的一套前端开发框架,他能帮助开发人员更便捷地进行前端开发.AngularJS是为了克服HTML在构建应用上的不足而设计的,它非常全面且简单易学习,因此Angu ...

  5. 从Uber微服务看最佳实践如何炼成?

    导读:Uber成长非常迅速,工程师团队快速扩充,据说Uber有2000名工程师,8000个代码仓库,部署了1000多个微服务.微服务架构是Uber应对技术团队快速增长,功能快速上线很出色的解决方案.本 ...

  6. 《HBase在滴滴出行的应用场景和最佳实践》

    HBase在滴滴出行的应用场景和最佳实践   背景 对接业务类型 HBase是建立在Hadoop生态之上的Database,源生对离线任务支持友好,又因为LSM树是一个优秀的高吞吐数据库结构,所以同时 ...

  7. QingStor 对象存储架构设计及最佳实践

    对象存储概念及特性 在介绍 QingStor️对象存储内部的的架构和设计原理之前,我们首先来了解一下对象存储的概念,也就是从外部视角看,对象存储有什么特性,我们应该如何使用. 对象存储本质上是一款存储 ...

  8. 微信小程序自动化测试最佳实践(附 Python 源码)

    本文为霍格沃兹测试学院测试大咖公开课<微信小程序自动化测试>图文整理精华版. 随着微信小程序的功能和生态日益完善,很多公司的产品业务形态逐渐从 App 延升到微信小程序.微信公众号等.小程 ...

  9. Istio最佳实践系列:如何实现方法级调用跟踪?

    赵化冰,腾讯云高级工程师,Istio Member,ServiceMesher 管理委员,Istio 项目贡献者,热衷于开源.网络和云计算.目前主要从事服务网格的开源和研发工作. 引言 TCM(Ten ...

  10. Netty基础招式——ChannelHandler的最佳实践

    本文是Netty系列第7篇 上一篇文章我们深入学习了Netty逻辑架构中的核心组件EventLoop和EventLoopGroup,掌握了Netty的线程模型,并且介绍了Netty4线程模型中的无锁串 ...

随机推荐

  1. RowCellMenuCustomizations 实现 Command 绑定

    给GridControl的行数据单元格添加菜单项,并通过Command绑定的方式实现菜单点击动作触发: <dxg:GridControl x:Name="mainGrid" ...

  2. autMan奥特曼机器人-内置微信如何定时给公众号发消息

    autMan版本要求2.1.3以上 一.打开左侧栏的本地开发,然后从实时日志获取公众号的ID或名称 ![2024-10-23T01:45:34.png][1] ![2024-10-23T01:44:5 ...

  3. Linux - 基础环境检查

    检查操作系统:建议根据实际产品需要进行安装 检查主机名:集群中统一前缀并区分服务器功能,小写命名 检查内存:建议至少128G 检查CPU:建议至少2个支持超线程技术的10核芯片 检查磁盘:同一功能的服 ...

  4. Kali Linux(202104)重置root账户密码

    1.前言 如果忘记了Kali Linux系统的登录密码,最关键的需求就是重置root用户的登录密码, 之后使用root账户可以修改其他账户的密码. 因此, 本文就介绍一下在不知道root用户登录密码的 ...

  5. VMware虚拟机上安装Kali Linux详细教程

    1.Kali Linux简介 Kali Linux是一个基于Debian的开源Linux发行版,集成了精心挑选的渗透测试和安全审计的工具,供渗透测试和安全设计人员使用,面向各种信息安全任务:如渗透测试 ...

  6. 深度研究JDK的各种技术细节

    打算建立一个JDK网站,将目前JDK的各种重要特性都深入分析一下.希望JDK中各种技术实现细节都可以在这个网站上查到.相关的模块以及阅读顺序如下图所示. 没有将一些内容规划进来,Java语言基本语法太 ...

  7. 你好,新朋友:生成式AI的第一次对话

    像学习使用智能手机一样开启AI对话 一.从"第一次用智能手机"理解AI交互 还记得初次使用智能手机时的笨拙吗?滑动解锁.输入文字.下载应用-每一步都需要探索.生成式AI的对话也遵循 ...

  8. 【FAQ】HarmonyOS SDK 闭源开放能力 —Map Kit(5)

    1.问题描述: 提供两套标准方案,可根据体验需求选择: 1.地图Picker(地点详情) 用户体验:①展示地图 ②标记地点 ③用户选择已安装地图应用 接入文档:https://developer.hu ...

  9. TSP问题的不可近似性

    \(\S\) 结论 TSP问题:n阶带权无向完全图中,找权值最小的哈密顿回路(无向图中遍历所有顶点的回路) 优化问题,记最优解为OPT 对于一般的n顶点TSP问题(非Metric),任意 多项式时间内 ...

  10. [tldr]windows使用scoop安装make工具辅助程序编译

    make是一个好用的GNU工具,用来辅助我们进行自动化的程序编译,只需要一个Makefile文件,即可实现一行指令自动编译 scoop是windows的一个包管理工具 安装 scoop bucket ...