Commit 7ec541e5bbb07c3a08fd78ce5b3af6ec056d1ec2

Authored by puhui999
1 parent 6c971631

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

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 }
... ...