**前情:**公司的一个产品涉及到硬件交互,选用了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。
-
自定义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(); } }
-
自定义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); }
-
配置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); }
-
调用sendToMqtt()方法可自动选择其中一个MyMqttPahoMessageHandler来处理发送mqtt消息。大功告成~