1313import com .fun90 .airopscat .repository .UserRepository ;
1414import jakarta .enterprise .context .ApplicationScoped ;
1515import jakarta .inject .Inject ;
16+ import jakarta .persistence .LockTimeoutException ;
17+ import jakarta .persistence .PessimisticLockException ;
1618import jakarta .transaction .Transactional ;
1719import lombok .extern .slf4j .Slf4j ;
1820
21+ import java .sql .SQLTransientException ;
1922import java .time .LocalDateTime ;
2023import java .time .OffsetDateTime ;
2124import java .time .ZoneId ;
3235@ Slf4j
3336public class AccountOnlineIpService {
3437 private static final int DEFAULT_CLEANUP_BATCH_SIZE = 1000 ;
38+ private static final int UPSERT_MAX_ATTEMPTS = 3 ;
39+ private static final long UPSERT_RETRY_BACKOFF_MILLIS = 50L ;
3540
3641 private final AccountOnlineIpRepository accountOnlineIpRepository ;
3742 private final AccountRepository accountRepository ;
@@ -139,7 +144,6 @@ public long deleteByAccountNo(String accountNo) {
139144 * @param onlineAccountIps guard agent 上报的账号在线 IP 聚合记录
140145 * @return 本轮成功 upsert 的记录数
141146 */
142- @ Transactional
143147 public int refreshFromGuardAccountIps (String nodeIp , List <GuardOnlineAccountIpReport > onlineAccountIps ) {
144148 if (nodeIp == null || nodeIp .isBlank () || onlineAccountIps == null ) {
145149 return 0 ;
@@ -196,13 +200,10 @@ public int refreshFromGuardAccountIps(String nodeIp, List<GuardOnlineAccountIpRe
196200 continue ;
197201 }
198202 String connectionId = buildGuardAccountIpConnectionId (accountNo , clientIp , normalizedNodeIp , nodeTag );
199- try {
200- accountOnlineIpRepository . upsertOnlineStatus ( accountNo , clientIp , connectionId , normalizedNodeIp ,
201- node == null ? null : node . getId (), nodeTag , now , now , now , now , offlineThreshold );
203+ if ( upsertOnlineStatusWithRetry ( accountNo , clientIp , connectionId , normalizedNodeIp ,
204+ node == null ? null : node . getId (), nodeTag , now , now , now , now , offlineThreshold ,
205+ "refreshFromGuardAccountIps" )) {
202206 count ++;
203- } catch (Exception e ) {
204- log .error ("refreshFromGuardAccountIps upsert 失败: accountNo={}, clientIp={}, connectionId={}, nodeIp={}" ,
205- accountNo , clientIp , connectionId , normalizedNodeIp , e );
206207 }
207208 }
208209 }
@@ -232,13 +233,10 @@ private int refreshFromGuardConnectionRefs(List<GuardOnlineConnectionRefReport>
232233 continue ;
233234 }
234235 LocalDateTime sessionStartTime = resolveConnectionStartTime (connection .getStart (), now );
235- try {
236- accountOnlineIpRepository . upsertOnlineStatus ( accountNo , clientIp , connectionId , nodeIp ,
237- node == null ? null : node . getId (), nodeTag , now , sessionStartTime , now , now , offlineThreshold );
236+ if ( upsertOnlineStatusWithRetry ( accountNo , clientIp , connectionId , nodeIp ,
237+ node == null ? null : node . getId (), nodeTag , now , sessionStartTime , now , now , offlineThreshold ,
238+ "refreshFromGuardConnectionRefs" )) {
238239 count ++;
239- } catch (Exception e ) {
240- log .error ("refreshFromGuardConnectionRefs upsert 失败: accountNo={}, clientIp={}, connectionId={}, nodeIp={}" ,
241- accountNo , clientIp , connectionId , nodeIp , e );
242240 }
243241 }
244242 return count ;
@@ -256,6 +254,72 @@ public List<AccountOnlineIpDto> getOnlineRecordsByAccountNos(List<String> accoun
256254 return convertToDtoList (records );
257255 }
258256
257+ private boolean upsertOnlineStatusWithRetry (String accountNo ,
258+ String clientIp ,
259+ String connectionId ,
260+ String nodeIp ,
261+ Long nodeId ,
262+ String nodeTag ,
263+ LocalDateTime lastOnlineTime ,
264+ LocalDateTime sessionStartTime ,
265+ LocalDateTime createTime ,
266+ LocalDateTime updateTime ,
267+ LocalDateTime offlineThresholdTime ,
268+ String source ) {
269+ for (int attempt = 1 ; attempt <= UPSERT_MAX_ATTEMPTS ; attempt ++) {
270+ try {
271+ accountOnlineIpRepository .upsertOnlineStatus (accountNo , clientIp , connectionId , nodeIp ,
272+ nodeId , nodeTag , lastOnlineTime , sessionStartTime , createTime , updateTime , offlineThresholdTime );
273+ return true ;
274+ } catch (Exception e ) {
275+ if (isRetryableUpsertFailure (e ) && attempt < UPSERT_MAX_ATTEMPTS ) {
276+ log .warn ("{} upsert 遇到并发锁冲突,准备重试: accountNo={}, clientIp={}, connectionId={}, nodeIp={}, attempt={}/{}, error={}" ,
277+ source , accountNo , clientIp , connectionId , nodeIp , attempt , UPSERT_MAX_ATTEMPTS , e .getMessage ());
278+ sleepBeforeRetry (attempt );
279+ continue ;
280+ }
281+ log .error ("{} upsert 失败: accountNo={}, clientIp={}, connectionId={}, nodeIp={}, attempt={}/{}" ,
282+ source , accountNo , clientIp , connectionId , nodeIp , attempt , UPSERT_MAX_ATTEMPTS , e );
283+ return false ;
284+ }
285+ }
286+ return false ;
287+ }
288+
289+ private boolean isRetryableUpsertFailure (Throwable throwable ) {
290+ Throwable current = throwable ;
291+ while (current != null ) {
292+ if (current instanceof PessimisticLockException
293+ || current instanceof LockTimeoutException
294+ || current instanceof SQLTransientException ) {
295+ return true ;
296+ }
297+ String simpleName = current .getClass ().getSimpleName ();
298+ if (simpleName .contains ("LockAcquisition" ) || simpleName .contains ("TransactionRollback" )) {
299+ return true ;
300+ }
301+ String message = current .getMessage ();
302+ if (message != null ) {
303+ String lowerMessage = message .toLowerCase ();
304+ if (lowerMessage .contains ("deadlock found" )
305+ || lowerMessage .contains ("try restarting transaction" )
306+ || lowerMessage .contains ("lock wait timeout" )) {
307+ return true ;
308+ }
309+ }
310+ current = current .getCause ();
311+ }
312+ return false ;
313+ }
314+
315+ private void sleepBeforeRetry (int attempt ) {
316+ try {
317+ Thread .sleep (UPSERT_RETRY_BACKOFF_MILLIS * attempt );
318+ } catch (InterruptedException e ) {
319+ Thread .currentThread ().interrupt ();
320+ }
321+ }
322+
259323 /**
260324 * 清理超过指定小时数的历史在线记录
261325 */
0 commit comments