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,7 +76,6 @@ | ||
| 76 | <jimubi.version>2.3.0</jimubi.version> | 76 | <jimubi.version>2.3.0</jimubi.version> |
| 77 | <weixin-java.version>4.7.9-20251224.161447</weixin-java.version> | 77 | <weixin-java.version>4.7.9-20251224.161447</weixin-java.version> |
| 78 | <alipay-sdk-java.version>4.40.607.ALL</alipay-sdk-java.version> | 78 | <alipay-sdk-java.version>4.40.607.ALL</alipay-sdk-java.version> |
| 79 | - <!-- OkHttp --> | ||
| 80 | <okhttp.version>4.12.0</okhttp.version> | 79 | <okhttp.version>4.12.0</okhttp.version> |
| 81 | </properties> | 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,13 +3,15 @@ package cn.iocoder.yudao.module.iot.service.rule.data.action; | ||
| 3 | import cn.hutool.core.util.StrUtil; | 3 | import cn.hutool.core.util.StrUtil; |
| 4 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | 4 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 5 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.config.IotDataSinkWebSocketConfig; | 5 | import cn.iocoder.yudao.module.iot.dal.dataobject.rule.config.IotDataSinkWebSocketConfig; |
| 6 | -import cn.iocoder.yudao.module.iot.dal.redis.rule.IotWebSocketLockRedisDAO; | ||
| 7 | import cn.iocoder.yudao.module.iot.enums.rule.IotDataSinkTypeEnum; | 6 | import cn.iocoder.yudao.module.iot.enums.rule.IotDataSinkTypeEnum; |
| 8 | import cn.iocoder.yudao.module.iot.service.rule.data.action.websocket.IotWebSocketClient; | 7 | import cn.iocoder.yudao.module.iot.service.rule.data.action.websocket.IotWebSocketClient; |
| 9 | -import jakarta.annotation.Resource; | ||
| 10 | import lombok.extern.slf4j.Slf4j; | 8 | import lombok.extern.slf4j.Slf4j; |
| 11 | import org.springframework.stereotype.Component; | 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 | * WebSocket 的 {@link IotDataRuleAction} 实现类 | 16 | * WebSocket 的 {@link IotDataRuleAction} 实现类 |
| 15 | * <p> | 17 | * <p> |
| @@ -24,8 +26,17 @@ import org.springframework.stereotype.Component; | @@ -24,8 +26,17 @@ import org.springframework.stereotype.Component; | ||
| 24 | public class IotWebSocketDataRuleAction extends | 26 | public class IotWebSocketDataRuleAction extends |
| 25 | IotDataRuleCacheableAction<IotDataSinkWebSocketConfig, IotWebSocketClient> { | 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 | @Override | 41 | @Override |
| 31 | public Integer getType() { | 42 | public Integer getType() { |
| @@ -87,23 +98,32 @@ public class IotWebSocketDataRuleAction extends | @@ -87,23 +98,32 @@ public class IotWebSocketDataRuleAction extends | ||
| 87 | } | 98 | } |
| 88 | 99 | ||
| 89 | /** | 100 | /** |
| 90 | - * 使用分布式锁进行重连 | 101 | + * 使用锁进行重连,保证同一服务器地址的重连操作线程安全 |
| 91 | * | 102 | * |
| 92 | * @param webSocketClient WebSocket 客户端 | 103 | * @param webSocketClient WebSocket 客户端 |
| 93 | * @param config 配置信息 | 104 | * @param config 配置信息 |
| 94 | */ | 105 | */ |
| 95 | private void reconnectWithLock(IotWebSocketClient webSocketClient, IotDataSinkWebSocketConfig config) throws Exception { | 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 | if (!webSocketClient.isConnected()) { | 115 | if (!webSocketClient.isConnected()) { |
| 99 | log.warn("[reconnectWithLock][WebSocket 连接已断开,尝试重新连接,服务器: {}]", config.getServerUrl()); | 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 | } |