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

This commit is contained in:
puhui999
2026-01-25 18:24:43 +08:00
parent 6c971631b0
commit 7ec541e5bb
3 changed files with 32 additions and 80 deletions

View File

@@ -76,7 +76,6 @@
<jimubi.version>2.3.0</jimubi.version> <jimubi.version>2.3.0</jimubi.version>
<weixin-java.version>4.7.9-20251224.161447</weixin-java.version> <weixin-java.version>4.7.9-20251224.161447</weixin-java.version>
<alipay-sdk-java.version>4.40.607.ALL</alipay-sdk-java.version> <alipay-sdk-java.version>4.40.607.ALL</alipay-sdk-java.version>
<!-- OkHttp -->
<okhttp.version>4.12.0</okhttp.version> <okhttp.version>4.12.0</okhttp.version>
</properties> </properties>

View File

@@ -1,67 +0,0 @@
package cn.iocoder.yudao.module.iot.dal.redis.rule;
import jakarta.annotation.Resource;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Repository;
import java.util.concurrent.TimeUnit;
import static cn.iocoder.yudao.module.iot.dal.redis.RedisKeyConstants.WEBSOCKET_CONNECT_LOCK;
/**
* IoT WebSocket 连接锁 Redis DAO
* <p>
* 用于保证 WebSocket 重连操作的线程安全,避免多线程同时重连导致的资源竞争
*
* @author HUIHUI
*/
@Repository
public class IotWebSocketLockRedisDAO {
/**
* 锁等待超时时间(毫秒)
*/
private static final long LOCK_WAIT_TIME_MS = 5000;
/**
* 锁持有超时时间(毫秒)
*/
private static final long LOCK_LEASE_TIME_MS = 10000;
@Resource
private RedissonClient redissonClient;
/**
* 在分布式锁保护下执行操作
*
* @param serverUrl WebSocket 服务器地址
* @param runnable 需要执行的操作
* @throws Exception 如果获取锁超时或执行操作时发生异常
*/
public void lock(String serverUrl, Runnable runnable) throws Exception {
String lockKey = formatKey(serverUrl);
RLock lock = redissonClient.getLock(lockKey);
try {
// 尝试获取分布式锁
boolean acquired = lock.tryLock(LOCK_WAIT_TIME_MS, LOCK_LEASE_TIME_MS, TimeUnit.MILLISECONDS);
if (!acquired) {
throw new RuntimeException("获取 WebSocket 连接锁超时,服务器: " + serverUrl);
}
// 执行操作
runnable.run();
} finally {
// 释放锁
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
private static String formatKey(String serverUrl) {
return String.format(WEBSOCKET_CONNECT_LOCK, serverUrl);
}
}

View File

@@ -3,13 +3,15 @@ package cn.iocoder.yudao.module.iot.service.rule.data.action;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.dal.dataobject.rule.config.IotDataSinkWebSocketConfig; import cn.iocoder.yudao.module.iot.dal.dataobject.rule.config.IotDataSinkWebSocketConfig;
import cn.iocoder.yudao.module.iot.dal.redis.rule.IotWebSocketLockRedisDAO;
import cn.iocoder.yudao.module.iot.enums.rule.IotDataSinkTypeEnum; import cn.iocoder.yudao.module.iot.enums.rule.IotDataSinkTypeEnum;
import cn.iocoder.yudao.module.iot.service.rule.data.action.websocket.IotWebSocketClient; import cn.iocoder.yudao.module.iot.service.rule.data.action.websocket.IotWebSocketClient;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
/** /**
* WebSocket 的 {@link IotDataRuleAction} 实现类 * WebSocket 的 {@link IotDataRuleAction} 实现类
* <p> * <p>
@@ -24,8 +26,17 @@ import org.springframework.stereotype.Component;
public class IotWebSocketDataRuleAction extends public class IotWebSocketDataRuleAction extends
IotDataRuleCacheableAction<IotDataSinkWebSocketConfig, IotWebSocketClient> { IotDataRuleCacheableAction<IotDataSinkWebSocketConfig, IotWebSocketClient> {
@Resource /**
private IotWebSocketLockRedisDAO webSocketLockRedisDAO; * 锁等待超时时间(毫秒)
*/
private static final long LOCK_WAIT_TIME_MS = 5000;
/**
* 重连锁key 为 WebSocket 服务器地址
* <p>
* WebSocket 连接是与特定服务器实例绑定的,使用单机锁即可保证重连的线程安全
*/
private final ConcurrentHashMap<String, ReentrantLock> reconnectLocks = new ConcurrentHashMap<>();
@Override @Override
public Integer getType() { public Integer getType() {
@@ -87,23 +98,32 @@ public class IotWebSocketDataRuleAction extends
} }
/** /**
* 使用分布式锁进行重连 * 使用锁进行重连,保证同一服务器地址的重连操作线程安全
* *
* @param webSocketClient WebSocket 客户端 * @param webSocketClient WebSocket 客户端
* @param config 配置信息 * @param config 配置信息
*/ */
private void reconnectWithLock(IotWebSocketClient webSocketClient, IotDataSinkWebSocketConfig config) throws Exception { private void reconnectWithLock(IotWebSocketClient webSocketClient, IotDataSinkWebSocketConfig config) throws Exception {
webSocketLockRedisDAO.lock(config.getServerUrl(), () -> { ReentrantLock lock = reconnectLocks.computeIfAbsent(config.getServerUrl(), k -> new ReentrantLock());
boolean acquired = false;
try {
acquired = lock.tryLock(LOCK_WAIT_TIME_MS, TimeUnit.MILLISECONDS);
if (!acquired) {
throw new RuntimeException("获取 WebSocket 重连锁超时,服务器: " + config.getServerUrl());
}
// 双重检查:获取锁后再次检查连接状态,避免重复连接 // 双重检查:获取锁后再次检查连接状态,避免重复连接
if (!webSocketClient.isConnected()) { if (!webSocketClient.isConnected()) {
log.warn("[reconnectWithLock][WebSocket 连接已断开,尝试重新连接,服务器: {}]", config.getServerUrl()); log.warn("[reconnectWithLock][WebSocket 连接已断开,尝试重新连接,服务器: {}]", config.getServerUrl());
try {
webSocketClient.connect(); webSocketClient.connect();
} catch (Exception e) { }
throw new RuntimeException("WebSocket 重连失败,服务器: " + config.getServerUrl(), e); } catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("获取 WebSocket 重连锁被中断,服务器: " + config.getServerUrl(), e);
} finally {
if (acquired && lock.isHeldByCurrentThread()) {
lock.unlock();
} }
} }
});
} }
} }