From f9bae0eff6f2b35038b180cb98baa7f2840c9bd1 Mon Sep 17 00:00:00 2001 From: zhouwei <849588297@qq.com> Date: Fri, 27 Feb 2026 19:06:14 +0800 Subject: [PATCH] =?UTF-8?q?=E9=95=BF=E9=BA=BB=E6=B7=BB=E5=8A=A0=E6=B8=85?= =?UTF-8?q?=E7=90=86=E7=9A=84=E8=B5=84=E6=BA=90=E7=9B=B8=E5=85=B3=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/robot/mj/EXGameController.java | 55 +++++++++- .../src/main/java/robot/mj/EXMainServer.java | 38 ++++++- .../java/robot/mj/RobotConnectionManager.java | 100 ++++++++++++++---- .../java/robot/mj/handler/HuNanChangSha.java | 15 +++ .../robot/mj/thread/ResourceCleanupUtil.java | 73 +++++++++++++ .../robot/mj/thread/ThreadPoolConfig.java | 53 ++++++---- 6 files changed, 289 insertions(+), 45 deletions(-) create mode 100644 robots/majiang/robot_mj_cs/src/main/java/robot/mj/thread/ResourceCleanupUtil.java diff --git a/robots/majiang/robot_mj_cs/src/main/java/robot/mj/EXGameController.java b/robots/majiang/robot_mj_cs/src/main/java/robot/mj/EXGameController.java index 6cbf720..8444697 100644 --- a/robots/majiang/robot_mj_cs/src/main/java/robot/mj/EXGameController.java +++ b/robots/majiang/robot_mj_cs/src/main/java/robot/mj/EXGameController.java @@ -12,6 +12,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import redis.clients.jedis.Jedis; import robot.mj.info.RobotUser; +import robot.mj.thread.ThreadPoolConfig; import taurus.client.TaurusClient; import taurus.client.business.GroupRoomBusiness; import taurus.util.ROBOTEventType; @@ -36,6 +37,12 @@ public class EXGameController extends GameController { //机器人房间 protected static final Map robotRoomMapping = new ConcurrentHashMap<>(); + + //记录最近访问时间 + private static final Map lastAccessTime = new ConcurrentHashMap<>(); + + //设置连接超时时间(5分钟) + private static final long CONNECTION_TIMEOUT = 5 * 60 * 1000; public EXGameController() { super(); @@ -396,7 +403,7 @@ public class EXGameController extends GameController { } catch (Exception e) { log.error("机器人加入房间超时", e); } - }); + }, ThreadPoolConfig.getBusinessThreadPool());//指定自定义线程池 robotUser.setIntoRoomTime(robotConnectionManager.getTime()); System.err.println("已进入房间准备成功: " + robotUser.getConnecId()); } catch (Exception e) { @@ -444,7 +451,51 @@ public class EXGameController extends GameController { * 根据机器人ID删除其所在的房间信息 */ public static void removeRobotRoomInfo(String robotId) { - robotRoomMapping.remove(robotId); + RobotUser removedUser = robotRoomMapping.remove(robotId); + lastAccessTime.remove(robotId); + + // 如果有连接ID,也一并清理 + if (removedUser != null && removedUser.getConnecId() != null) { + robotRoomMapping.remove(removedUser.getConnecId()); + lastAccessTime.remove(removedUser.getConnecId()); + } + + System.out.println("清理机器人房间信息: " + robotId); + } + + /** + * 更新连接的最后访问时间 + */ + public static void updateLastAccessTime(String connecId) { + lastAccessTime.put(connecId, System.currentTimeMillis()); + } + + /** + * 清理超时的连接 + */ + public static void cleanupExpiredConnections() { + long currentTime = System.currentTimeMillis(); + List expiredConnections = new ArrayList<>(); + + for (Map.Entry entry : lastAccessTime.entrySet()) { + if (currentTime - entry.getValue() > CONNECTION_TIMEOUT) { + expiredConnections.add(entry.getKey()); + } + } + + for (String connecId : expiredConnections) { + RobotUser robotUser = robotRoomMapping.get(connecId); + if (robotUser != null) { + System.out.println("清理超时连接: " + connecId + ", 机器人ID: " + robotUser.getRobotId()); + robotConnectionManager.disconnectFromGameServer(connecId); + } + robotRoomMapping.remove(connecId); + lastAccessTime.remove(connecId); + } + + if (!expiredConnections.isEmpty()) { + System.out.println("本次清理了 " + expiredConnections.size() + " 个超时连接"); + } } /** diff --git a/robots/majiang/robot_mj_cs/src/main/java/robot/mj/EXMainServer.java b/robots/majiang/robot_mj_cs/src/main/java/robot/mj/EXMainServer.java index 59177a6..99d9196 100644 --- a/robots/majiang/robot_mj_cs/src/main/java/robot/mj/EXMainServer.java +++ b/robots/majiang/robot_mj_cs/src/main/java/robot/mj/EXMainServer.java @@ -11,6 +11,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import redis.clients.jedis.Jedis; import robot.mj.info.RobotUser; +import robot.mj.thread.ResourceCleanupUtil; +import robot.mj.thread.ThreadPoolConfig; import taurus.client.NetManager; import static robot.mj.EXGameController.robotRoomMapping; @@ -52,7 +54,10 @@ public class EXMainServer extends MainServer{ // 1. 先启动独立的事件处理线程(只启动一次) startNetEventThread(); - // 2. 启动连接检查定时任务 + // 2. 启动资源清理定时任务 + startResourceCleanupScheduler(); + + // 3. 启动系统监控 //startConnectionCheckScheduler(); //测试 Jedis jedis2 = Redis.use("group1_db2").getJedis(); @@ -83,6 +88,7 @@ public class EXMainServer extends MainServer{ log.info("长沙麻将机器人服务器已启动"); log.info("服务器将监听端口 {} 用于接收robot_mgr管理协议", gameSetting.port); + System.out.println("当前线程池配置: " + ThreadPoolConfig.getThreadPoolStatus()); jedis2.close(); } @@ -106,6 +112,36 @@ public class EXMainServer extends MainServer{ eventThread.setDaemon(true); //设置为守护线程 eventThread.start(); } + + /** + * 启动资源清理定时任务 + */ + private void startResourceCleanupScheduler() { + Thread cleanupThread = new Thread(() -> { + while (true) { + try { + //每30秒执行一次资源清理 + Thread.sleep(30000); + ResourceCleanupUtil.performCleanup(); + System.out.println("线程池状态: " + ThreadPoolConfig.getThreadPoolStatus()); + } catch (InterruptedException e) { + break; + } catch (Exception e) { + System.err.println("资源清理任务异常: " + e.getMessage()); + // 发生异常时尝试清理 + try { + ResourceCleanupUtil.performCleanup(); + } catch (Exception cleanupEx) { + System.err.println("异常清理也失败: " + cleanupEx.getMessage()); + } + } + } + }, "ResourceCleanupThread"); + + cleanupThread.setDaemon(true); + cleanupThread.start(); + System.out.println("资源清理定时任务已启动"); + } diff --git a/robots/majiang/robot_mj_cs/src/main/java/robot/mj/RobotConnectionManager.java b/robots/majiang/robot_mj_cs/src/main/java/robot/mj/RobotConnectionManager.java index 0d25cca..5f02a64 100644 --- a/robots/majiang/robot_mj_cs/src/main/java/robot/mj/RobotConnectionManager.java +++ b/robots/majiang/robot_mj_cs/src/main/java/robot/mj/RobotConnectionManager.java @@ -23,9 +23,7 @@ import taurus.util.ROBOTEventType; import java.util.*; import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicInteger; -import static robot.mj.thread.ThreadPoolConfig.getBusinessThreadPool; import static robot.mj.thread.ThreadPoolConfig.scheduleDelay; import static robot.mj.EXGameController.robotRoomMapping; @@ -37,6 +35,15 @@ public class RobotConnectionManager { private Logger log = Logger.getLogger(RobotConnectionManager.class); private static final Map huNanChangShaInstances = new ConcurrentHashMap<>(); + + //记录活跃连接 用于资源清理判断 + private static final Set activeConnections = ConcurrentHashMap.newKeySet(); + + //记录连接创建时间 + private static final Map connectionCreationTime = new ConcurrentHashMap<>(); + + //连接最大生存时间(5分钟) + private static final long MAX_CONNECTION_LIFETIME = 5 * 60 * 1000; private final EXGameController exGameController; private final String host= Config.GAME_SERVER_HOST; @@ -79,6 +86,13 @@ public class RobotConnectionManager { * 获取长沙麻将处理器实例 */ private HuNanChangSha getHuNanChangShaInstance(String connecId) { + //标记连接为活跃状态 + activeConnections.add(connecId); + connectionCreationTime.put(connecId, System.currentTimeMillis()); + + //定期清理过期连接 + cleanupExpiredInstances(); + HuNanChangSha existingInstance = huNanChangShaInstances.get(connecId); if (existingInstance != null) { return existingInstance; @@ -135,12 +149,17 @@ public class RobotConnectionManager { System.out.println("开始主动断开连接: {"+connecId+"}"); RobotUser robotUser = robotRoomMapping.remove(connecId); + //标记连接为非活跃 + activeConnections.remove(connecId); + connectionCreationTime.remove(connecId); + //清理连接数据 if (connecId != null) { HuNanChangSha.removeFromRedis(connecId); HuNanChangSha instance = huNanChangShaInstances.get(connecId); if (instance != null) { + //清理所有集合数据以释放内存 instance.getChangShaCardInhand().clear(); instance.getChuGuoCardInhand().clear(); instance.getgangdepai().clear(); @@ -151,12 +170,15 @@ public class RobotConnectionManager { System.out.println("清空HuNanChangSha集合数据: " + connecId); } + //移除实例和相关数据 huNanChangShaInstances.remove(connecId); playerOutcardsMapByConn.remove(connecId); playerchisMapByConn.remove(connecId); playerpengsMapByConn.remove(connecId); playermingsMapByConn.remove(connecId); playerzisMapByConn.remove(connecId); + + System.out.println("清理完成,当前活跃连接数: " + activeConnections.size() + ", 实例数: " + huNanChangShaInstances.size()); } if (robotUser != null) { @@ -173,6 +195,34 @@ public class RobotConnectionManager { } else { System.out.println("客户端连接不存在: {"+connecId+"}"); } + + //同时清理机器人房间映射 + EXGameController.removeRobotRoomInfo(robotUser.getRobotId()); + } + } + + /** + * 清理过期的实例 + */ + private void cleanupExpiredInstances() { + long currentTime = System.currentTimeMillis(); + List expiredConnections = new ArrayList<>(); + + //检查连接生存时间 + for (Map.Entry entry : connectionCreationTime.entrySet()) { + if (currentTime - entry.getValue() > MAX_CONNECTION_LIFETIME) { + expiredConnections.add(entry.getKey()); + } + } + + //清理过期连接 + for (String connecId : expiredConnections) { + System.out.println("清理过期连接实例: " + connecId); + disconnectFromGameServer(connecId); + } + + if (!expiredConnections.isEmpty()) { + System.out.println("本次清理了 " + expiredConnections.size() + " 个过期连接实例"); } } @@ -341,6 +391,15 @@ public class RobotConnectionManager { */ private void handleProtocol(String command, Message message, TaurusClient client, String connecId) { RobotUser robotUser = robotRoomMapping.get(connecId); + + //更新连接的最后访问时间 + EXGameController.updateLastAccessTime(connecId); + + if (robotUser == null) { + System.err.println("未找到机器人用户信息,连接ID: " + connecId); + return; + } + int robotId = Integer.parseInt(robotUser.getRobotId()); ITObject param = message.param; HuNanChangSha huNanChangSha = getHuNanChangShaInstance(connecId); @@ -598,14 +657,15 @@ public class RobotConnectionManager { else if ("2009".equalsIgnoreCase(command)) { //直接使用定时任务替代Thread.sleep,避免嵌套异步调用 scheduleDelay(() -> { - Jedis localJedis = Redis.use().getJedis(); + Jedis jedis = null; try { + jedis = Redis.use().getJedis(); Integer paramRobotId = param.getInt("aid"); - if (robotUser != null) { + if (robotUser != null && paramRobotId != null) { String roomKey = String.valueOf(robotUser.getCurrentRoomId()); //查询该房间的玩家信息 - String playersStr = localJedis.hget(roomKey, "players"); + String playersStr = jedis.hget(roomKey, "players"); if (playersStr != null && !playersStr.equals("[]")) { String players = playersStr.substring(1, playersStr.length() - 1); String[] playerIds = players.split(","); @@ -614,12 +674,12 @@ public class RobotConnectionManager { if (playerIds.length == 1) { int playerId = Integer.parseInt(playerIds[0].trim()); if (playerId == paramRobotId) { - String gpid = localJedis.hget(roomKey, "gpid"); + String gpid = jedis.hget(roomKey, "gpid"); //更新机器人剩余数量 - if (count != null && count.containsKey(Integer.parseInt(gpid))) { + if (gpid != null && count != null && count.containsKey(Integer.parseInt(gpid))) { Integer currentValue = count.get(Integer.parseInt(gpid)); - if (currentValue > 0) { + if (currentValue != null && currentValue > 0) { count.put(Integer.parseInt(gpid), currentValue - 1); } } @@ -638,11 +698,15 @@ public class RobotConnectionManager { } } } + } catch (NumberFormatException e) { + System.err.println("2009协议数字格式异常,robotId: " + param.get("aid") + ", connecId: " + connecId); + } catch (NullPointerException e) { + System.err.println("2009协议空指针异常,connecId: " + connecId); } catch (Exception e) { - log.error("处理机器人房间映射检查时发生异常", e); + System.err.println("2009协议处理异常: " + e.getMessage() + ", connecId: " + connecId); } finally { - if (localJedis != null) { - localJedis.close(); + if (jedis != null) { + jedis.close(); } } }, 6, TimeUnit.SECONDS); @@ -650,13 +714,8 @@ public class RobotConnectionManager { //结算 else if ("817".equalsIgnoreCase(command)) { //清空所有HuNanChangSha相关的集合数据 - huNanChangSha.getChangShaCardInhand().clear(); - huNanChangSha.getChuGuoCardInhand().clear(); - huNanChangSha.getgangdepai().clear(); - huNanChangSha.getpongGroup().clear(); - huNanChangSha.getchowGroup().clear(); - huNanChangSha.getchangShaCardInhandgang().clear(); - huNanChangSha.getChuGuoPainum().clear(); + huNanChangSha.clearAllData(); + Integer type = param.getInt("type"); if (type == 1 || type == 2) { //为1为大结算 为2为解散 if (count != null && count.containsKey(pid)) { @@ -667,6 +726,9 @@ public class RobotConnectionManager { } //更新机器人剩余数量 updateLeftoverRobot(Integer.parseInt(robotUser.getRobotId())); + + //游戏结束后主动断开连接 + disconnectFromGameServer(connecId); } ITObject params = TObject.newInstance(); params.putString("session", client.getSession()); @@ -778,7 +840,7 @@ public class RobotConnectionManager { } } } - }); + }, ThreadPoolConfig.getBusinessThreadPool()); //指定自定义线程池 } catch (Exception e) { log.error("机器人登录异常"); } diff --git a/robots/majiang/robot_mj_cs/src/main/java/robot/mj/handler/HuNanChangSha.java b/robots/majiang/robot_mj_cs/src/main/java/robot/mj/handler/HuNanChangSha.java index 8dff8f7..4cfe759 100644 --- a/robots/majiang/robot_mj_cs/src/main/java/robot/mj/handler/HuNanChangSha.java +++ b/robots/majiang/robot_mj_cs/src/main/java/robot/mj/handler/HuNanChangSha.java @@ -216,6 +216,21 @@ public class HuNanChangSha { jedis.close(); } } + + /** + * 清理所有集合数据 + */ + public void clearAllData() { + changShaCardInhand.clear(); + changShachuguopai.clear(); + gangdepai.clear(); + pongGroup.clear(); + chowGroup.clear(); + changShaCardInhandgang.clear(); + chuGuoPainum.clear(); + + System.out.println("已清空HuNanChangSha所有集合数据"); + } /** * 出牌广播协议 812 diff --git a/robots/majiang/robot_mj_cs/src/main/java/robot/mj/thread/ResourceCleanupUtil.java b/robots/majiang/robot_mj_cs/src/main/java/robot/mj/thread/ResourceCleanupUtil.java new file mode 100644 index 0000000..a0d0f30 --- /dev/null +++ b/robots/majiang/robot_mj_cs/src/main/java/robot/mj/thread/ResourceCleanupUtil.java @@ -0,0 +1,73 @@ +package robot.mj.thread; + +import robot.mj.EXGameController; +import robot.mj.RobotConnectionManager; + +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 资源清理工具类 + * 用于管理和清理不再使用的资源 防止内存泄漏 + */ +public class ResourceCleanupUtil { + + //需要清理的资源 + private static final Set pendingCleanupResources = ConcurrentHashMap.newKeySet(); + + /** + * 执行资源清理 + * 清理已完成对局但仍在内存中的资源 + */ + public static void performCleanup() { + if (pendingCleanupResources.isEmpty()) { + //执行常规清理 + performRegularCleanup(); + return; + } + + System.out.println("开始执行资源清理,待清理资源数: " + pendingCleanupResources.size()); + int cleanedCount = 0; + + Set resourcesToClean = ConcurrentHashMap.newKeySet(); + resourcesToClean.addAll(pendingCleanupResources); + + for (String resourceId : resourcesToClean) { + try { + //这里可以根据resourceId的具体类型执行不同的清理逻辑 + //暂时保持原有逻辑 + + //从待清理列表中移除 + pendingCleanupResources.remove(resourceId); + cleanedCount++; + + System.out.println("已清理资源: " + resourceId); + } catch (Exception e) { + System.err.println("清理资源时发生异常: " + resourceId + ", 错误: " + e.getMessage()); + } + } + + System.out.println("资源清理完成,共清理: " + cleanedCount + " 个资源"); + + //执行常规清理 + performRegularCleanup(); + } + + /** + * 执行常规清理 + */ + private static void performRegularCleanup() { + try { + //清理过期的机器人连接 + EXGameController.cleanupExpiredConnections(); + + //输出当前系统状态 + System.out.println("=== 系统资源状态 ==="); + System.out.println(ThreadPoolConfig.getThreadPoolStatus()); + + } catch (Exception e) { + System.err.println("执行常规清理时发生异常: " + e.getMessage()); + } + } + +} \ No newline at end of file diff --git a/robots/majiang/robot_mj_cs/src/main/java/robot/mj/thread/ThreadPoolConfig.java b/robots/majiang/robot_mj_cs/src/main/java/robot/mj/thread/ThreadPoolConfig.java index 62125e5..af7bd35 100644 --- a/robots/majiang/robot_mj_cs/src/main/java/robot/mj/thread/ThreadPoolConfig.java +++ b/robots/majiang/robot_mj_cs/src/main/java/robot/mj/thread/ThreadPoolConfig.java @@ -8,36 +8,36 @@ import java.util.concurrent.atomic.AtomicInteger; */ public class ThreadPoolConfig { - // 优化后的线程池配置:针对机器人场景优化 + //线程池配置 private static final ExecutorService BUSINESS_THREAD_POOL = new ThreadPoolExecutor( - 10, // 核心线程数 - 减少核心线程数 - 50, // 最大线程数 - 降低最大线程数 - 30, // 空闲线程存活时间 - 缩短存活时间 + 5, //核心线程数 + 20, //最大线程数 + 60, //空闲线程存活时间 TimeUnit.SECONDS, - new LinkedBlockingQueue<>(10000), // 增大队列容量,减少拒绝任务 + new LinkedBlockingQueue<>(5000), new ThreadFactory() { private final AtomicInteger threadNumber = new AtomicInteger(1); @Override public Thread newThread(Runnable r) { Thread t = new Thread(r, "RobotBusinessThread-" + threadNumber.getAndIncrement()); t.setDaemon(true); - t.setPriority(Thread.NORM_PRIORITY - 2); // 进一步降低线程优先级 + t.setPriority(Thread.NORM_PRIORITY - 1); return t; } }, - new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略:由调用线程执行 + new ThreadPoolExecutor.CallerRunsPolicy() ); - // 添加定时任务线程池,专门处理延迟操作 + //添加定时任务线程池 private static final ScheduledExecutorService SCHEDULED_EXECUTOR_SERVICE = - Executors.newScheduledThreadPool(4, new ThreadFactory() { + new ScheduledThreadPoolExecutor(2, new ThreadFactory() { private final AtomicInteger threadNumber = new AtomicInteger(1); @Override public Thread newThread(Runnable r) { Thread t = new Thread(r, "RobotScheduledThread-" + threadNumber.getAndIncrement()); t.setDaemon(true); - t.setPriority(Thread.NORM_PRIORITY - 2); + t.setPriority(Thread.NORM_PRIORITY - 1); return t; } }); @@ -65,24 +65,18 @@ public class ThreadPoolConfig { } }, delay, unit); } - + /** - * 执行周期性任务 - */ - public static void scheduleAtFixedRate(Runnable task, long initialDelay, long period, TimeUnit unit) { - SCHEDULED_EXECUTOR_SERVICE.scheduleAtFixedRate(task, initialDelay, period, unit); - } - - /** - * 优雅关闭线程池,释放资源 + * 关闭线程池 释放资源 */ public static void shutdown() { System.out.println("开始关闭线程池..."); - // 关闭定时任务线程池 + //关闭定时任务线程池 SCHEDULED_EXECUTOR_SERVICE.shutdown(); try { - if (!SCHEDULED_EXECUTOR_SERVICE.awaitTermination(5, TimeUnit.SECONDS)) { + if (!SCHEDULED_EXECUTOR_SERVICE.awaitTermination(3, TimeUnit.SECONDS)) { + System.out.println("定时任务线程池强制关闭"); SCHEDULED_EXECUTOR_SERVICE.shutdownNow(); } } catch (InterruptedException e) { @@ -90,10 +84,11 @@ public class ThreadPoolConfig { Thread.currentThread().interrupt(); } - // 关闭业务线程池 + //关闭业务线程池 BUSINESS_THREAD_POOL.shutdown(); try { - if (!BUSINESS_THREAD_POOL.awaitTermination(10, TimeUnit.SECONDS)) { + if (!BUSINESS_THREAD_POOL.awaitTermination(5, TimeUnit.SECONDS)) { + System.out.println("业务线程池强制关闭"); BUSINESS_THREAD_POOL.shutdownNow(); } } catch (InterruptedException e) { @@ -103,4 +98,16 @@ public class ThreadPoolConfig { System.out.println("线程池关闭完成"); } + + /** + * 获取线程池状态信息 + */ + public static String getThreadPoolStatus() { + ThreadPoolExecutor executor = (ThreadPoolExecutor) BUSINESS_THREAD_POOL; + return String.format("线程池状态 - 核心:%d, 活跃:%d, 完成:%d, 队列:%d", + executor.getCorePoolSize(), + executor.getActiveCount(), + executor.getCompletedTaskCount(), + executor.getQueue().size()); + } }