spring-integration优化MQTT(32202)异常

**前情:**公司的一个产品涉及到硬件交互,选用了mqtt协议,于是乎经过一番技术选型。我用emq做mqtt broker,又撸了一个mqtt消息处理服务(A)。

这个A服务用了spring-integration,spring框架集成spring-integration自然是so easy。又花了点时间用策略设计模式写好了A服务的业务框架,接下来就是堆业务逻辑了。

**问题:**自己做压力测试的时候,发现经常会报ERROR,MQTT(32202): 正在发布过多的消息,研究了一下发现在paho源码里有如下代码:

if (message instanceof MqttPublish) {
			synchronized (queueLock) {
				if (actualInFlight >= this.maxInflight) {
					//@TRACE 613= sending {0} msgs at max inflight window
					log.fine(CLASS_NAME, methodName, "613", new Object[]{new Integer(actualInFlight)});

					throw new MqttException(MqttException.REASON_CODE_MAX_INFLIGHT);
				}

注意maxInflight这个值,默认是10,也就是说,在使用paho发送mqtt消息时,同时只能发10个,你要是多起几个thread循环往mqtt client里怼消息,很容易就超过10个了,这个时候就会报错丢消息。

解决方案:

  • 增大maxInflight(最低需要paho1.2.0版本)
  • 对paho源码修改
  • 配置多个mqtt client

我选用第三种办法。由于我选用了spring-integration,没有单独使用paho,单独使用paho可以直接做成多client模式。下面介绍如何使用spring-integration配置成多mqtt client,**思路是:**由于mqttMessageHandler只会引用一个paho客户端,并且在内部对paho客户端做了封装,所以直接修改MqttPahoMessageHandler复杂度较高,我们可以重新写一个MultiMqttMessageHandler,内部初始化多个MqttPahoMessageHandler,这样通过MessageingGateway发送消息时,直接通过MultiMqttMessageHandler来处理mqtt消息,MultiMqttMessageHandler可以通过负载均衡的方式来把消息分派给各个MqttPahoMessageHandler。

  1. 自定义MyMqttPahoMessageHandler类,继承MqttPahoMessageHandler,重写MqttPahoMessageHandler的doStop(),onInit(),handleMessageInternal()方法,注意权限由protected改成public。handleMessageInternal()会由channel通过dispatcher间接调用;重写onInit()用来手动初始化MqttPahoMessageHandler。

    @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();
    		}
    	}
    
  2. 自定义MultiMqttMessageHandler类,继承AbstractMessageHandler,并implements Lifecycle,自定义一个MessageHandler,添加一个Map成员属性,用来维系多个MyMqttPahoMessageHandler;handlerCount变量可配置多个mqtt client。这里只用了radom随机数来做负载均衡,比较简陋,后期可以做迭代优化。

    private final AtomicBoolean running = new AtomicBoolean();
    
    	private volatile Map<Integer, MessageHandler> mqttHandlerMap;
    	@Value("${spring.mqtt.sender.count}")
    	private Integer handlerCount = 3;
    	@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);
    	}
    
  3. 配置MqttSenderConfig类

    @Bean
    	public MqttPahoClientFactory mqttClientFactory() {
    		DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
    		factory.setServerURIs(hostUrl);
    		factory.setUserName(username);
    		factory.setPassword(password);
    		return factory;
    	}
    
    	public MessageHandler createMqttOutbound(){
    		String tempId = MqttAsyncClient.generateClientId();
    		MyMqttPahoMessageHandler messageHandler = new MyMqttPahoMessageHandler(clientId + "sender" + tempId, mqttClientFactory());
    		messageHandler.setAsync(true);
    		messageHandler.setDefaultTopic(defaultTopic);
    		messageHandler.setDefaultQos(1);
    		messageHandler.onInit();
    		return messageHandler;
    	}
    
    	@Bean
    	@ServiceActivator(inputChannel = "mqttOutboundChannel")
    	public MessageHandler mqttOutbound() {
    		return new MultiMqttMessageHandler();
    	}
    
    	@Bean
    	public MessageChannel mqttOutboundChannel() {
    		return new DirectChannel();
    	}
    
    	@MessagingGateway(defaultRequestChannel = "mqttOutboundChannel")
    	public interface MqttGateway {
    		void sendToMqtt(String data, @Header(MqttHeaders.TOPIC) String topic);
    	}
    
  4. 调用sendToMqtt()方法可自动选择其中一个MyMqttPahoMessageHandler来处理发送mqtt消息。大功告成~

  • 4
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
Spring Integration提供了基于MQTT的适配器,可以很方便地集成MQTT协议到Spring应用程序中。下面是基于spring-integration-mqtt在生产环境中使用的步骤: 1. 添加依赖 在项目的pom.xml文件中,添加spring-integration-mqtt依赖: ```xml <dependency> <groupId>org.springframework.integration</groupId> <artifactId>spring-integration-mqtt</artifactId> <version>5.5.0</version> </dependency> ``` 2. 配置MQTT连接参数 在项目的配置文件中,添加MQTT连接参数: ```yaml spring: mqtt: url: tcp://localhost:1883 username: test password: test ``` 3. 配置MQTT消息通道 在Spring Integration中,可以使用`MessageChannel`来定义消息通道,下面是一个基本的MQTT消息通道配置: ```xml <int-mqtt:message-driven-channel-adapter id="mqttInboundAdapter" client-id="test" url="${spring.mqtt.url}" topics="test/topic" qos="1" channel="mqttInputChannel"> <int:poller fixed-delay="1000"/> </int-mqtt:message-driven-channel-adapter> <int:channel id="mqttInputChannel"/> ``` 4. 处理MQTT消息 在处理MQTT消息的地方,可以使用`@ServiceActivator`注解和`MessageHandler`接口来处理消息: ```java @Service public class MqttMessageHandler implements MessageHandler { @Override @ServiceActivator(inputChannel = "mqttInputChannel") public void handleMessage(Message<?> message) { String topic = message.getHeaders().get(MqttHeaders.TOPIC, String.class); String payload = message.getPayload().toString(); System.out.println("Received message - Topic: " + topic + ", Payload: " + payload); } } ``` 5. 发布MQTT消息 在需要发送MQTT消息的地方,可以使用`MessageChannel`来发送消息: ```java @Autowired private MessageChannel mqttOutputChannel; public void sendMessage() { mqttOutputChannel.send(MessageBuilder.withPayload("hello").setHeader(MqttHeaders.TOPIC, "test/topic").build()); } ``` 以上就是基于spring-integration-mqtt在生产环境中使用的步骤。需要注意的是,本文只是介绍了基本的MQTT使用方法,实际应用中还需要考虑消息质量、消息持久化等问题。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值