引入mqtt
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-integrationartifactId>
dependency>
<dependency>
<groupId>org.springframework.integrationgroupId>
<artifactId>spring-integration-streamartifactId>
dependency>
<dependency>
<groupId>org.springframework.integrationgroupId>
<artifactId>spring-integration-mqttartifactId>
dependency>
配置文件
spring:
mqtt:
username: admin #账号
password: password #密码
url: tcp://127.0.0.1:1883 #地址
client:
id : paho3245 #客户端id 随便给,不能重复
default:
topic: topic
completionTimeout: 3000
代码
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.annotation.IntegrationComponentScan;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory;
import org.springframework.integration.mqtt.core.MqttPahoClientFactory;
import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
/**
* @author lingzhiying
* @title: MqttSenderConfig.java
* @projectName
* @description:
* @date 2019年8月13日
*/
@Configuration
@IntegrationComponentScan
public class MqttSenderConfig {
@Value("${spring.mqtt.username}")
private String username;
@Value("${spring.mqtt.password}")
private String password;
@Value("${spring.mqtt.url}")
private String hostUrl;
@Value("${spring.mqtt.client.id}")
private String clientId;
@Value("${spring.mqtt.default.topic}")
private String defaultTopic;
@Bean
public MqttConnectOptions getMqttConnectOptions(){
MqttConnectOptions mqttConnectOptions=new MqttConnectOptions();
mqttConnectOptions.setUserName(username);
mqttConnectOptions.setPassword(password.toCharArray());
mqttConnectOptions.setServerURIs(new String[]{hostUrl});
mqttConnectOptions.setMaxInflight(50); //最大并发数
mqttConnectOptions.setKeepAliveInterval(60); //心跳检查
return mqttConnectOptions;
}
@Bean
public MqttPahoClientFactory mqttClientFactory() {
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
factory.setConnectionOptions(getMqttConnectOptions());
return factory;
}
@Bean
@ServiceActivator(inputChannel = "mqttOutboundChannel")
public MessageHandler mqttOutbound() {
MqttPahoMessageHandler messageHandler = new MqttPahoMessageHandler(clientId, mqttClientFactory());
messageHandler.setAsync(true);
messageHandler.setDefaultTopic(defaultTopic);
return messageHandler;
}
@Bean
public MessageChannel mqttOutboundChannel() {
return new DirectChannel();
}
}
--------------------分割线---------------------
import org.springframework.integration.annotation.MessagingGateway;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.stereotype.Component;
import org.springframework.integration.mqtt.support.MqttHeaders;
/**
* @author lingzhiying
* @title: MqttGateway.java
* @projectName spacepm
* @description:
* @date 2019年8月13日
*/
@Component
@MessagingGateway(defaultRequestChannel = "mqttOutboundChannel")
public interface MqttGateway {
void sendToMqtt(String data,@Header(MqttHeaders.TOPIC) String topic);
}
自行注入调用,上述方案并发不高的情况下没有问题,但是一旦并发数量过高超出设置的阈值,会报错32202异常
原理https://www.cnblogs.com/ynx01/p/10912426.html 在这,
于是进行改良
参考 https://blog.csdn.net/aispeech_cc/article/details/88737322
由单一客户端 改成 多客户端
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.Lifecycle;
import org.springframework.integration.handler.AbstractMessageHandler;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.stereotype.Component;
import cn.meiot.config.MqttSenderConfig;
public class MultiMqttMessageHandler extends AbstractMessageHandler implements Lifecycle{
private final AtomicBoolean running = new AtomicBoolean();
private volatile Map<Integer, MessageHandler> mqttHandlerMap;
private Integer handlerCount = 5; //客户端数量
@Autowired
private MqttSenderConfig senderConfig;
@Override
public void start() {
if (!this.running.getAndSet(true)) {
doStart();
}
}
private void doStart(){
mqttHandlerMap = new ConcurrentHashMap<>();
for(int i=0;i<handlerCount;i++){
mqttHandlerMap.put(i, senderConfig.createMqttOutbound());
}
}
@Override
public void stop() {
if (this.running.getAndSet(false)) {
doStop();
}
}
private void doStop(){
for(Map.Entry<Integer, MessageHandler> e : mqttHandlerMap.entrySet()){
MessageHandler handler = e.getValue();
((MyMqttPahoMessageHandler)handler).doStop();
}
}
@Override
public boolean isRunning() {
return this.running.get();
}
@Override
protected void handleMessageInternal(Message<?> message) throws Exception {
Random random = new Random();
MyMqttPahoMessageHandler messageHandler = (MyMqttPahoMessageHandler)mqttHandlerMap.get(random.nextInt(handlerCount));
messageHandler.handleMessageInternal(message);
}
}
--------------------分割线---------------------
import org.springframework.integration.mqtt.core.MqttPahoClientFactory;
import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler;
import org.springframework.messaging.Message;
public class MyMqttPahoMessageHandler extends MqttPahoMessageHandler{
public MyMqttPahoMessageHandler(String clientId, MqttPahoClientFactory clientFactory) {
super(clientId, clientFactory);
// TODO Auto-generated constructor stub
}
@Override
public void doStop() {
super.doStop();
}
@Override
public void handleMessageInternal(Message<?> message) throws Exception {
super.handleMessageInternal(message);
}
@Override
public void onInit() {
try {
super.onInit();
} catch (Exception e) {
e.printStackTrace();
}
}
}
--------------------分割线---------------------
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.annotation.IntegrationComponentScan;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory;
import org.springframework.integration.mqtt.core.MqttPahoClientFactory;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import cn.meiot.handler.MultiMqttMessageHandler;
import cn.meiot.handler.MyMqttPahoMessageHandler;
/**
* @author lingzhiying
* @title: MqttSenderConfig.java
* @projectName spacepm
* @description:
* @date 2019年8月13日
*/
@Configuration
@IntegrationComponentScan
public class MqttSenderConfig {
@Value("${spring.mqtt.username}")
private String username;
@Value("${spring.mqtt.password}")
private String password;
@Value("${spring.mqtt.url}")
private String hostUrl;
@Value("${spring.mqtt.client.id}")
private String clientId;
@Value("${spring.mqtt.default.topic}")
private String defaultTopic;
public static final Integer sleepTime = 50;
@Bean
public MqttConnectOptions getMqttConnectOptions(){
MqttConnectOptions mqttConnectOptions=new MqttConnectOptions();
mqttConnectOptions.setUserName(username);
mqttConnectOptions.setPassword(password.toCharArray());
mqttConnectOptions.setServerURIs(new String[]{hostUrl});
mqttConnectOptions.setMaxInflight(50);
return mqttConnectOptions;
}
@Bean
public MqttPahoClientFactory mqttClientFactory() {
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
factory.setConnectionOptions(getMqttConnectOptions());
return factory;
//return factory;
}
//@Bean
//@ServiceActivator(inputChannel = "mqttOutboundChannel")
public MessageHandler createMqttOutbound(){
String tempId = MqttAsyncClient.generateClientId();
MyMqttPahoMessageHandler messageHandler = new MyMqttPahoMessageHandler(clientId + "sender" + tempId, mqttClientFactory());
messageHandler.setAsync(true);
messageHandler.setDefaultTopic(defaultTopic);
messageHandler.setDefaultQos(0); //值得注意的是这里,设置0和1是有差别的
messageHandler.onInit();
return messageHandler;
}
@Bean
@ServiceActivator(inputChannel = "mqttOutboundChannel")
public MessageHandler mqttOutbound() {
return new MultiMqttMessageHandler();
}
@Bean
public MessageChannel mqttOutboundChannel() {
return new DirectChannel();
}
}
Qos 设置为0 :是不需要回调的,只管发不管成不成功,
设置为1:需要回调,需要mqtt服务端配置.这样能保证消息发成功,存在问题,回调过慢的时候,并且并发过高,仍可能出现错误
mqttConnectOptions.setMaxInflight(50); 可以设置该值
mqtt是一个单线程的运行的,后续会未发出的会累积在后面,累积超过该值的时候回崩掉
并发数量同时超过客户端的综合上述问题仍会出现
<dependency>
<groupId>com.google.guavagroupId>
<artifactId>guavaartifactId>
<version>18.0version>
dependency>
import java.util.concurrent.TimeUnit;
import com.google.common.util.concurrent.RateLimiter;
public class MqttServiceImpl{
//1秒钟 500个令牌
private static RateLimiter rateLimiter = RateLimiter.create(500);
@Autowired
private MqttGateway mqttGateway;
//获取上下文
@Autowired
private ApplicationContext applicationContext;
public void sendToMqtt(String data,String serialNumber){
try {
//1秒内有响应 则发送
if(rateLimiter.tryAcquire(1, TimeUnit.SECONDS)) {
mqttGateway.sendToMqtt(data, serialNumber);
}
}//此处可以用全局异常处理
catch (MessageHandlingException e){
//当并发数量过高异常时,清空mqtt客户端池,
MultiMqttMessageHandler multiMqttMessageHandler = (MultiMqttMessageHandler)applicationContext.getBean(MultiMqttMessageHandler.class);
//start会重新new一个客户端池
multiMqttMessageHandler.start();
log.error("并发过高清空mqtt");
}
}
}
做出双重保障 一限流 ,二 抛弃无效队列,重置mqtt
目前高并发基本能顶住。各位有更好的办法 欢迎指点