Commit 7ec541e5bbb07c3a08fd78ce5b3af6ec056d1ec2

Authored by puhui999
1 parent 6c971631

perf(iot):【场景联动】WebSocket 重连锁从 Redisson 分布式锁改为 ReentrantLock 单机锁

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 }