Commit 7ec541e5bbb07c3a08fd78ce5b3af6ec056d1ec2
1 parent
6c971631
perf(iot):【场景联动】WebSocket 重连锁从 Redisson 分布式锁改为 ReentrantLock 单机锁
Showing
3 changed files
with
32 additions
and
80 deletions
yudao-dependencies/pom.xml
| ... | ... | @@ -76,7 +76,6 @@ |
| 76 | 76 | <jimubi.version>2.3.0</jimubi.version> |
| 77 | 77 | <weixin-java.version>4.7.9-20251224.161447</weixin-java.version> |
| 78 | 78 | <alipay-sdk-java.version>4.40.607.ALL</alipay-sdk-java.version> |
| 79 | - <!-- OkHttp --> | |
| 80 | 79 | <okhttp.version>4.12.0</okhttp.version> |
| 81 | 80 | </properties> |
| 82 | 81 | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/redis/rule/IotWebSocketLockRedisDAO.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.dal.redis.rule; | |
| 2 | - | |
| 3 | -import jakarta.annotation.Resource; | |
| 4 | -import org.redisson.api.RLock; | |
| 5 | -import org.redisson.api.RedissonClient; | |
| 6 | -import org.springframework.stereotype.Repository; | |
| 7 | - | |
| 8 | -import java.util.concurrent.TimeUnit; | |
| 9 | - | |
| 10 | -import static cn.iocoder.yudao.module.iot.dal.redis.RedisKeyConstants.WEBSOCKET_CONNECT_LOCK; | |
| 11 | - | |
| 12 | -/** | |
| 13 | - * IoT WebSocket 连接锁 Redis DAO | |
| 14 | - * <p> | |
| 15 | - * 用于保证 WebSocket 重连操作的线程安全,避免多线程同时重连导致的资源竞争 | |
| 16 | - * | |
| 17 | - * @author HUIHUI | |
| 18 | - */ | |
| 19 | -@Repository | |
| 20 | -public class IotWebSocketLockRedisDAO { | |
| 21 | - | |
| 22 | - /** | |
| 23 | - * 锁等待超时时间(毫秒) | |
| 24 | - */ | |
| 25 | - private static final long LOCK_WAIT_TIME_MS = 5000; | |
| 26 | - | |
| 27 | - /** | |
| 28 | - * 锁持有超时时间(毫秒) | |
| 29 | - */ | |
| 30 | - private static final long LOCK_LEASE_TIME_MS = 10000; | |
| 31 | - | |
| 32 | - @Resource | |
| 33 | - private RedissonClient redissonClient; | |
| 34 | - | |
| 35 | - /** | |
| 36 | - * 在分布式锁保护下执行操作 | |
| 37 | - * | |
| 38 | - * @param serverUrl WebSocket 服务器地址 | |
| 39 | - * @param runnable 需要执行的操作 | |
| 40 | - * @throws Exception 如果获取锁超时或执行操作时发生异常 | |
| 41 | - */ | |
| 42 | - public void lock(String serverUrl, Runnable runnable) throws Exception { | |
| 43 | - String lockKey = formatKey(serverUrl); | |
| 44 | - RLock lock = redissonClient.getLock(lockKey); | |
| 45 | - | |
| 46 | - try { | |
| 47 | - // 尝试获取分布式锁 | |
| 48 | - boolean acquired = lock.tryLock(LOCK_WAIT_TIME_MS, LOCK_LEASE_TIME_MS, TimeUnit.MILLISECONDS); | |
| 49 | - if (!acquired) { | |
| 50 | - throw new RuntimeException("获取 WebSocket 连接锁超时,服务器: " + serverUrl); | |
| 51 | - } | |
| 52 | - | |
| 53 | - // 执行操作 | |
| 54 | - runnable.run(); | |
| 55 | - } finally { | |
| 56 | - // 释放锁 | |
| 57 | - if (lock.isHeldByCurrentThread()) { | |
| 58 | - lock.unlock(); | |
| 59 | - } | |
| 60 | - } | |
| 61 | - } | |
| 62 | - | |
| 63 | - private static String formatKey(String serverUrl) { | |
| 64 | - return String.format(WEBSOCKET_CONNECT_LOCK, serverUrl); | |
| 65 | - } | |
| 66 | - | |
| 67 | -} |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotWebSocketDataRuleAction.java
| ... | ... | @@ -3,13 +3,15 @@ package cn.iocoder.yudao.module.iot.service.rule.data.action; |
| 3 | 3 | import cn.hutool.core.util.StrUtil; |
| 4 | 4 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 5 | 5 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.config.IotDataSinkWebSocketConfig; |
| 6 | -import cn.iocoder.yudao.module.iot.dal.redis.rule.IotWebSocketLockRedisDAO; | |
| 7 | 6 | import cn.iocoder.yudao.module.iot.enums.rule.IotDataSinkTypeEnum; |
| 8 | 7 | import cn.iocoder.yudao.module.iot.service.rule.data.action.websocket.IotWebSocketClient; |
| 9 | -import jakarta.annotation.Resource; | |
| 10 | 8 | import lombok.extern.slf4j.Slf4j; |
| 11 | 9 | import org.springframework.stereotype.Component; |
| 12 | 10 | |
| 11 | +import java.util.concurrent.ConcurrentHashMap; | |
| 12 | +import java.util.concurrent.TimeUnit; | |
| 13 | +import java.util.concurrent.locks.ReentrantLock; | |
| 14 | + | |
| 13 | 15 | /** |
| 14 | 16 | * WebSocket 的 {@link IotDataRuleAction} 实现类 |
| 15 | 17 | * <p> |
| ... | ... | @@ -24,8 +26,17 @@ import org.springframework.stereotype.Component; |
| 24 | 26 | public class IotWebSocketDataRuleAction extends |
| 25 | 27 | IotDataRuleCacheableAction<IotDataSinkWebSocketConfig, IotWebSocketClient> { |
| 26 | 28 | |
| 27 | - @Resource | |
| 28 | - private IotWebSocketLockRedisDAO webSocketLockRedisDAO; | |
| 29 | + /** | |
| 30 | + * 锁等待超时时间(毫秒) | |
| 31 | + */ | |
| 32 | + private static final long LOCK_WAIT_TIME_MS = 5000; | |
| 33 | + | |
| 34 | + /** | |
| 35 | + * 重连锁,key 为 WebSocket 服务器地址 | |
| 36 | + * <p> | |
| 37 | + * WebSocket 连接是与特定服务器实例绑定的,使用单机锁即可保证重连的线程安全 | |
| 38 | + */ | |
| 39 | + private final ConcurrentHashMap<String, ReentrantLock> reconnectLocks = new ConcurrentHashMap<>(); | |
| 29 | 40 | |
| 30 | 41 | @Override |
| 31 | 42 | public Integer getType() { |
| ... | ... | @@ -87,23 +98,32 @@ public class IotWebSocketDataRuleAction extends |
| 87 | 98 | } |
| 88 | 99 | |
| 89 | 100 | /** |
| 90 | - * 使用分布式锁进行重连 | |
| 101 | + * 使用锁进行重连,保证同一服务器地址的重连操作线程安全 | |
| 91 | 102 | * |
| 92 | 103 | * @param webSocketClient WebSocket 客户端 |
| 93 | 104 | * @param config 配置信息 |
| 94 | 105 | */ |
| 95 | 106 | private void reconnectWithLock(IotWebSocketClient webSocketClient, IotDataSinkWebSocketConfig config) throws Exception { |
| 96 | - webSocketLockRedisDAO.lock(config.getServerUrl(), () -> { | |
| 107 | + ReentrantLock lock = reconnectLocks.computeIfAbsent(config.getServerUrl(), k -> new ReentrantLock()); | |
| 108 | + boolean acquired = false; | |
| 109 | + try { | |
| 110 | + acquired = lock.tryLock(LOCK_WAIT_TIME_MS, TimeUnit.MILLISECONDS); | |
| 111 | + if (!acquired) { | |
| 112 | + throw new RuntimeException("获取 WebSocket 重连锁超时,服务器: " + config.getServerUrl()); | |
| 113 | + } | |
| 97 | 114 | // 双重检查:获取锁后再次检查连接状态,避免重复连接 |
| 98 | 115 | if (!webSocketClient.isConnected()) { |
| 99 | 116 | log.warn("[reconnectWithLock][WebSocket 连接已断开,尝试重新连接,服务器: {}]", config.getServerUrl()); |
| 100 | - try { | |
| 101 | - webSocketClient.connect(); | |
| 102 | - } catch (Exception e) { | |
| 103 | - throw new RuntimeException("WebSocket 重连失败,服务器: " + config.getServerUrl(), e); | |
| 104 | - } | |
| 117 | + webSocketClient.connect(); | |
| 105 | 118 | } |
| 106 | - }); | |
| 119 | + } catch (InterruptedException e) { | |
| 120 | + Thread.currentThread().interrupt(); | |
| 121 | + throw new RuntimeException("获取 WebSocket 重连锁被中断,服务器: " + config.getServerUrl(), e); | |
| 122 | + } finally { | |
| 123 | + if (acquired && lock.isHeldByCurrentThread()) { | |
| 124 | + lock.unlock(); | |
| 125 | + } | |
| 126 | + } | |
| 107 | 127 | } |
| 108 | 128 | |
| 109 | 129 | } | ... | ... |