Commit 4be18af236ac036741c6adba23fa58d06f17a73c
1 parent
04009322
【代码优化】IoT: 基于 guava 对 producer 做 cache
Showing
1 changed file
with
50 additions
and
19 deletions
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/execute/IotRocketMQDataBridgeExecute.java
| @@ -3,6 +3,9 @@ package cn.iocoder.yudao.module.iot.service.rule.execute; | @@ -3,6 +3,9 @@ package cn.iocoder.yudao.module.iot.service.rule.execute; | ||
| 3 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataBridgeDO; | 3 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataBridgeDO; |
| 4 | import cn.iocoder.yudao.module.iot.enums.rule.IotDataBridgTypeEnum; | 4 | import cn.iocoder.yudao.module.iot.enums.rule.IotDataBridgTypeEnum; |
| 5 | import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage; | 5 | import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage; |
| 6 | +import com.google.common.cache.CacheBuilder; | ||
| 7 | +import com.google.common.cache.CacheLoader; | ||
| 8 | +import com.google.common.cache.LoadingCache; | ||
| 6 | import lombok.extern.slf4j.Slf4j; | 9 | import lombok.extern.slf4j.Slf4j; |
| 7 | import org.apache.rocketmq.client.producer.DefaultMQProducer; | 10 | import org.apache.rocketmq.client.producer.DefaultMQProducer; |
| 8 | import org.apache.rocketmq.client.producer.SendResult; | 11 | import org.apache.rocketmq.client.producer.SendResult; |
| @@ -11,7 +14,9 @@ import org.apache.rocketmq.common.message.Message; | @@ -11,7 +14,9 @@ import org.apache.rocketmq.common.message.Message; | ||
| 11 | import org.apache.rocketmq.remoting.common.RemotingHelper; | 14 | import org.apache.rocketmq.remoting.common.RemotingHelper; |
| 12 | import org.springframework.stereotype.Component; | 15 | import org.springframework.stereotype.Component; |
| 13 | 16 | ||
| 17 | +import java.time.Duration; | ||
| 14 | import java.time.LocalDateTime; | 18 | import java.time.LocalDateTime; |
| 19 | +import java.util.concurrent.Executors; | ||
| 15 | 20 | ||
| 16 | /** | 21 | /** |
| 17 | * RocketMQ 的 {@link IotDataBridgeExecute} 实现类 | 22 | * RocketMQ 的 {@link IotDataBridgeExecute} 实现类 |
| @@ -22,6 +27,36 @@ import java.time.LocalDateTime; | @@ -22,6 +27,36 @@ import java.time.LocalDateTime; | ||
| 22 | @Slf4j | 27 | @Slf4j |
| 23 | public class IotRocketMQDataBridgeExecute implements IotDataBridgeExecute { | 28 | public class IotRocketMQDataBridgeExecute implements IotDataBridgeExecute { |
| 24 | 29 | ||
| 30 | + /** | ||
| 31 | + * 针对 {@link IotDataBridgeDO.RocketMQConfig} 的 DefaultMQProducer 缓存 | ||
| 32 | + */ | ||
| 33 | + private final LoadingCache<IotDataBridgeDO.RocketMQConfig, DefaultMQProducer> PRODUCER_CACHE = CacheBuilder.newBuilder() | ||
| 34 | + // 只阻塞当前数据加载线程,其他线程返回旧值 | ||
| 35 | + .refreshAfterWrite(Duration.ofMinutes(10)) | ||
| 36 | + // 增加移除监听器,自动关闭 producer | ||
| 37 | + .removalListener(notification -> { | ||
| 38 | + DefaultMQProducer producer = (DefaultMQProducer) notification.getValue(); | ||
| 39 | + if (producer != null) { | ||
| 40 | + try { | ||
| 41 | + producer.shutdown(); | ||
| 42 | + log.info("[PRODUCER_CACHE][配置({}) 对应的 producer 已关闭]", notification.getKey()); | ||
| 43 | + } catch (Exception e) { | ||
| 44 | + log.error("[PRODUCER_CACHE][配置({}) 对应的 producer 关闭失败]", notification.getKey(), e); | ||
| 45 | + } | ||
| 46 | + } | ||
| 47 | + }) | ||
| 48 | + // 通过 asyncReloading 实现全异步加载,包括 refreshAfterWrite 被阻塞的加载线程 | ||
| 49 | + .build(CacheLoader.asyncReloading(new CacheLoader<IotDataBridgeDO.RocketMQConfig, DefaultMQProducer>() { | ||
| 50 | + @Override | ||
| 51 | + public DefaultMQProducer load(IotDataBridgeDO.RocketMQConfig config) throws Exception { | ||
| 52 | + DefaultMQProducer producer = new DefaultMQProducer(config.getGroup()); | ||
| 53 | + producer.setNamesrvAddr(config.getNameServer()); | ||
| 54 | + producer.start(); | ||
| 55 | + log.info("[PRODUCER_CACHE][配置({}) 对应的 producer 已创建并启动]", config); | ||
| 56 | + return producer; | ||
| 57 | + } | ||
| 58 | + }, Executors.newCachedThreadPool())); | ||
| 59 | + | ||
| 25 | @Override | 60 | @Override |
| 26 | public void execute(IotDeviceMessage message, IotDataBridgeDO dataBridge) { | 61 | public void execute(IotDeviceMessage message, IotDataBridgeDO dataBridge) { |
| 27 | // 1.1 校验数据桥接的类型 == ROCKETMQ | 62 | // 1.1 校验数据桥接的类型 == ROCKETMQ |
| @@ -33,14 +68,9 @@ public class IotRocketMQDataBridgeExecute implements IotDataBridgeExecute { | @@ -33,14 +68,9 @@ public class IotRocketMQDataBridgeExecute implements IotDataBridgeExecute { | ||
| 33 | } | 68 | } |
| 34 | 69 | ||
| 35 | private void executeRocketMQ(IotDeviceMessage message, IotDataBridgeDO.RocketMQConfig config) { | 70 | private void executeRocketMQ(IotDeviceMessage message, IotDataBridgeDO.RocketMQConfig config) { |
| 36 | - // 1.1 创建生产者实例,指定生产者组名 | ||
| 37 | - DefaultMQProducer producer = new DefaultMQProducer(config.getGroup()); | ||
| 38 | - // TODO @puhui999:可以考虑,基于 guava 做 cache,使用 config 作为 key,然后假个 listener 超时,销毁 producer | ||
| 39 | try { | 71 | try { |
| 40 | - // 1.2 设置 NameServer 地址 | ||
| 41 | - producer.setNamesrvAddr(config.getNameServer()); | ||
| 42 | - // 1.3 启动生产者 | ||
| 43 | - producer.start(); | 72 | + // 1. 获取或创建 Producer |
| 73 | + DefaultMQProducer producer = PRODUCER_CACHE.get(config); | ||
| 44 | 74 | ||
| 45 | // 2.1 创建消息对象,指定Topic、Tag和消息体 | 75 | // 2.1 创建消息对象,指定Topic、Tag和消息体 |
| 46 | Message msg = new Message( | 76 | Message msg = new Message( |
| @@ -58,18 +88,22 @@ public class IotRocketMQDataBridgeExecute implements IotDataBridgeExecute { | @@ -58,18 +88,22 @@ public class IotRocketMQDataBridgeExecute implements IotDataBridgeExecute { | ||
| 58 | } | 88 | } |
| 59 | } catch (Exception e) { | 89 | } catch (Exception e) { |
| 60 | log.error("[executeRocketMQ][message({}) config({}) 发送异常]", message, config, e); | 90 | log.error("[executeRocketMQ][message({}) config({}) 发送异常]", message, config, e); |
| 61 | - } finally { | ||
| 62 | - // 3. 关闭生产者 | ||
| 63 | - producer.shutdown(); | ||
| 64 | } | 91 | } |
| 65 | } | 92 | } |
| 66 | 93 | ||
| 67 | // TODO @芋艿:测试代码,后续清理 | 94 | // TODO @芋艿:测试代码,后续清理 |
| 68 | public static void main(String[] args) { | 95 | public static void main(String[] args) { |
| 69 | - // 1. 创建 IotRocketMQDataBridgeExecute 实例 | 96 | + // 1. 创建一个共享的实例 |
| 70 | IotRocketMQDataBridgeExecute action = new IotRocketMQDataBridgeExecute(); | 97 | IotRocketMQDataBridgeExecute action = new IotRocketMQDataBridgeExecute(); |
| 71 | 98 | ||
| 72 | - // 2. 创建测试消息 | 99 | + // 2. 创建共享的配置 |
| 100 | + IotDataBridgeDO.RocketMQConfig config = new IotDataBridgeDO.RocketMQConfig(); | ||
| 101 | + config.setNameServer("127.0.0.1:9876"); | ||
| 102 | + config.setGroup("test-group"); | ||
| 103 | + config.setTopic("test-topic"); | ||
| 104 | + config.setTags("test-tag"); | ||
| 105 | + | ||
| 106 | + // 3. 创建共享的消息 | ||
| 73 | IotDeviceMessage message = IotDeviceMessage.builder() | 107 | IotDeviceMessage message = IotDeviceMessage.builder() |
| 74 | .requestId("TEST-001") | 108 | .requestId("TEST-001") |
| 75 | .productKey("testProduct") | 109 | .productKey("testProduct") |
| @@ -82,14 +116,11 @@ public class IotRocketMQDataBridgeExecute implements IotDataBridgeExecute { | @@ -82,14 +116,11 @@ public class IotRocketMQDataBridgeExecute implements IotDataBridgeExecute { | ||
| 82 | .tenantId(1L) | 116 | .tenantId(1L) |
| 83 | .build(); | 117 | .build(); |
| 84 | 118 | ||
| 85 | - // 3. 创建 RocketMQ 配置 | ||
| 86 | - IotDataBridgeDO.RocketMQConfig config = new IotDataBridgeDO.RocketMQConfig(); | ||
| 87 | - config.setNameServer("127.0.0.1:9876"); | ||
| 88 | - config.setGroup("test-group"); | ||
| 89 | - config.setTopic("test-topic"); | ||
| 90 | - config.setTags("test-tag"); | 119 | + // 4. 执行两次测试,验证缓存 |
| 120 | + log.info("[main][第一次执行,应该会创建新的 producer]"); | ||
| 121 | + action.executeRocketMQ(message, config); | ||
| 91 | 122 | ||
| 92 | - // 4. 执行测试 | 123 | + log.info("[main][第二次执行,应该会复用缓存的 producer]"); |
| 93 | action.executeRocketMQ(message, config); | 124 | action.executeRocketMQ(message, config); |
| 94 | } | 125 | } |
| 95 | 126 |