小伙伴们,你们好呀,我是老寇,跟我一起学习对接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. [JOI 2020 Final] 火事 题解

    给一篇题解.(下面这张图是从 luogu 上粘贴的,因为不太会画图) 其中纵坐标为 \(t\),横坐标为 \(a_i\). 发现同颜色块只有平行四边形和直角梯形(等腰直角三角形)两种情况. 可以将直角 ...

  2. 1 前端知识学习-初始Web和Web标准

    0️⃣ 初始Web和Web标准 Web ​ Web(World Wide Web) 即全球广域网.也成为万维网.我们常说的Web端就是网页端. 网页 ​ 网页是构成网站的基本元素.网页主要由文字.图像 ...

  3. Typecho实现版权声明的三种方式

    在安装完Typecho之后,第一件事应该就是想着如何去折腾了.对于个人博客而言,不希望自己辛辛苦苦写的文章,被别人转载或无脑采集,还不留原地址,所以就需要在文章的末尾地方放上一个版权声明,来提醒下转载 ...

  4. Hadoop - [01] 概述

    Hadoop官网:https://hadoop.apache.org/ Hadoop下载:https://archive.apache.org/dist/hadoop/common/ 一.Hadoop ...

  5. 基于ThreeJs的大屏3D地图(二)——气泡图、渐变柱体与热力图

    前提 上一篇文章中我们完成了地图区块模型的渲染,在此基础之上本篇来讲解气泡图.3D柱形图以及3D热力图的实现方式. 首先,为了更好的关注点分离及与地图渲染模块的解耦,我们可以把所有类型的可视化元素抽象 ...

  6. SuperSocket 服务端 和 SuperSocket.ClientEngine 客户端及普通客户端

    internal class Program { //static void Main(string[] args) //{ // byte[] arr = new byte[1024]; // 1. ...

  7. 基于webman实现的服务层框架-webman-biz-framework

    简介 webman的基础上扩展的一个服务层框架,基于分层体系结构的代码模式. 如果觉得有用,可以帮我在webman-biz-framework点个小星星哟,也希望大家交流 分层体系结构的代码模式 什么 ...

  8. Linux 安装 MySQL 8.0

    目录 下载 安装数据库 修改mysql配置文件(若没有则新建) 安装并初始化mysql 查看mysql密码 配置启动 登录MySQL 修改密码 配置远程连接 配置防火墙 常见错误 Windows 安装 ...

  9. IvorySQL 升级指南:从 3.x 到 4.0 的平滑过渡

    日前,IvorySQL 4.0 重磅发布,全面支持 PostgreSQL 17,并且增强了对 Oracle 的兼容性.关于 IvorySQL 4.0 的介绍,各位小伙伴可以通过这篇文章回顾:Ivory ...

  10. Linux系统发邮件

    Linux系统发送邮件 管理服务器时我们经常需要写一些监测脚本,然后在出问题的时候通过邮件来通知 SMTP SMTP(Simple Mail Transfer Protocol)简易邮件传输通讯协议 ...