spring boot 整合MQTT高并发处理方案

引入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
目前高并发基本能顶住。各位有更好的办法 欢迎指点

你可能感兴趣的:(spring,boot)