ConcurrentLinkedQueue实现异步日志写入

为减少用户操作时资源占用并提升体验,日志写入通过ConcurrentLinkedQueue异步处理。该队列是线程安全的FIFO容器,利用CAS保证元素一致性。采用模板模式设计,基类包含异步队列和抽象处理方法,子类实现日志写入逻辑。通过@PostConstruct注解,在服务初始化时启动异步任务。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        在用户操作量较大的情况下,如果实时写入日志会导致资源被严重占用,用户长时间获取不到返回结果,影响用户体验,所以日志操作通常是在用户操作完后放入异步队列,等用户操作少的时间段再将日志写入数据库。

        此处实现异步日志写入用到了并发包下的ConcurrentLinkedQueue,一个线程安全的队列实现,遵循FIFO原则进行排序,采用CAS操作,来保证元素的一致性。

        首先采用模板模式,定义一个异步队列服务的模板类BaseAsyncQueueService,其中包含一个队列log_queue,和异步操作的抽象方法asyncProcess()。

/**
 * 基础异步队列服务,用于异步保存日志信息
 */
public abstract class BaseAsyncQueueService<T> {

  /**
   * 处理,消费
   * @param t
   * @return
   */
  public abstract boolean asyncProcess(T t);

  /**
   * 队列
   */
  private ConcurrentLinkedQueue<Object> log_queue;

  public BaseAsyncQueueService() {
    log_queue = new ConcurrentLinkedQueue();
  }

  public synchronized boolean isEmpty() {
    return log_queue.isEmpty();
  }

  public synchronized int getSize() {
    return log_queue.size();
  }

  public synchronized T poll() {
    return (T) log_queue.poll();
  }

  public 
package com.example.demo.service.impl; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.date.DateUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.UUID; import cn.hutool.core.thread.ThreadFactoryBuilder; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.example.demo.dao.entity.WExecuteHost; import com.example.demo.dao.entity.WPersonalHost; import com.example.demo.dao.mapper.WExecuteHostMapper; import com.example.demo.service.WExecuteHostService; import com.example.demo.service.WPersonalHostService; import lombok.Data; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.stereotype.Service; import javax.annotation.PreDestroy; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @Service public class WExecuteHostServiceImpl extends ServiceImpl<WExecuteHostMapper, WExecuteHost> implements WExecuteHostService { @Autowired private WPersonalHostService wPersonalHostService; @Data private static class UserSession { private Process cmdProcess; private volatile boolean isProcessRunning; private String sessionId; private String logFilePath; private final Queue<String> logBuffer = new ConcurrentLinkedQueue<>(); private static final int BATCH_SIZE = 50; // 批量写入阈值 } private final Map<String, UserSession> userSessions = new ConcurrentHashMap<>(); private final SimpMessagingTemplate messagingTemplate; // 异步日志写入线程池 private static final ExecutorService LOG_WRITER_POOL = Executors.newCachedThreadPool( new ThreadFactoryBuilder().setNamePrefix("log-writer-").build()); // 日志刷新调度器 private static final ScheduledExecutorService LOG_FLUSH_SCHEDULER = Executors.newSingleThreadScheduledExecutor(); public WExecuteHostServiceImpl(SimpMessagingTemplate messagingTemplate) { this.messagingTemplate = messagingTemplate; startLogFlushService(); } // 初始化日志刷新服务 private void startLogFlushService() { LOG_FLUSH_SCHEDULER.scheduleAtFixedRate(() -> { userSessions.values().forEach(session -> { if (!session.getLogBuffer().isEmpty()) { List<String> batch = CollUtil.newArrayList(session.getLogBuffer()); session.getLogBuffer().clear(); asyncWriteLog(session.getLogFilePath(), String.join("\n", batch)); } }); }, 0, 1, TimeUnit.SECONDS); } @Override public String pipList() { // 示例实现:返回模拟的管道列表(实际需根据业务需求实现) return "[]"; } @Override public void executeCommand(String command, String executeHost, String userId) { // 1. 权限校验 if (!validateUserHost(userId, executeHost)) { sendError("无权访问该主机", userId); return; } // 2. 初始化日志系统 UserSession session = userSessions.computeIfAbsent(userId, key -> { if (userSessions.containsKey(userId)) { sendError("已有命令执行中,请等待完成", userId); return null; } return createNewSession(userId, executeHost); }); if (session == null) return; // 3. 命令处理 if ("ABORT".equalsIgnoreCase(command)) { handleAbort(userId); return; } // 写入命令到日志 session.getLogBuffer().offer( "\n———————— 执行时间 " + DateUtil.now() + " ————————\n[<<Input<<] " + command); // 发送命令到进程 IoUtil.write(session.getCmdProcess().getOutputStream(), "GBK", false, command + "\n"); } private boolean validateUserHost(String userId, String executeHost) { LambdaQueryWrapper<WPersonalHost> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(WPersonalHost::getP13, userId) .eq(WPersonalHost::getExecuteHost, executeHost) .eq(WPersonalHost::getState, "在线"); return wPersonalHostService.getOne(queryWrapper) != null; } private UserSession createNewSession(String userId, String executeHost) { try { UserSession session = new UserSession(); session.setSessionId(UUID.randomUUID().toString()); session.setLogFilePath(initLogFile(userId, executeHost)); // 启动CMD进程(带唯一标题) ProcessBuilder pb = new ProcessBuilder("cmd.exe", "/k", "title " + generateUniqueTitle(userId)); session.setCmdProcess(pb.redirectErrorStream(true).start()); // 启动输出监听线程 startOutputThread(session, userId); return session; } catch (IOException e) { sendError("进程启动失败: " + e.getMessage(), userId); return null; } } private String initLogFile(String userId, String executeHost) { // 1. 构建基础路径(使用File.separator) String baseDir = FileUtil.normalize( System.getProperty("user.dir") + File.separator + "command-log"); // 2. 构建安全路径(统一使用File.separator) String safePath = FileUtil.normalize( baseDir + File.separator + userId + File.separator + executeHost + File.separator + "项目名称"); // 3. 安全校验(现在路径分隔符一致) if (!safePath.startsWith(baseDir)) { throw new SecurityException("非法日志路径: " + safePath); } // 4. 创建目录(自动处理路径分隔符) FileUtil.mkdir(safePath); // 5. 生成日志文件 String logFileName = DateUtil.today() + ".log"; return FileUtil.touch(safePath + File.separator + logFileName).getAbsolutePath(); } private void startOutputThread(UserSession session, String userId) { new Thread(() -> { try (BufferedReader reader = new BufferedReader( new InputStreamReader(session.getCmdProcess().getInputStream(), "GBK"))) { String line; while ((line = reader.readLine()) != null) { // WebSocket推送 messagingTemplate.convertAndSend("/topic/commandOutput/" + userId, line); // 日志缓冲 session.getLogBuffer().offer(line); if (session.getLogBuffer().size() >= UserSession.BATCH_SIZE) { asyncWriteLog(session.getLogFilePath(), String.join("\n", session.getLogBuffer())); session.getLogBuffer().clear(); } } } catch (Exception e) { sendError("输出流异常: " + e.getMessage(), userId); } finally { cleanupSession(userId); } }).start(); } private void asyncWriteLog(String logFilePath, String content) { CompletableFuture.runAsync(() -> { try { FileUtil.appendString(content + System.lineSeparator(), logFilePath, "GBK"); } catch (Exception e) { System.err.println("日志写入失败: " + e.getMessage()); } }, LOG_WRITER_POOL); } private void cleanupSession(String userId) { UserSession session = userSessions.remove(userId); if (session != null) { try { if (session.getCmdProcess() != null) { session.getCmdProcess().destroyForcibly(); } // 强制将剩余日志写入文件(新增代码) if (!session.getLogBuffer().isEmpty()) { asyncWriteLog(session.getLogFilePath(), String.join("\n", session.getLogBuffer())); session.getLogBuffer().clear(); } } catch (Exception ignored) {} } } @PreDestroy public void cleanup() { LOG_FLUSH_SCHEDULER.shutdown(); LOG_WRITER_POOL.shutdown(); userSessions.forEach((userId, session) -> { try { if (session.getCmdProcess() != null) { session.getCmdProcess().destroyForcibly(); } } catch (Exception ignored) { } }); userSessions.clear(); } public void handleAbort(String userId) { UserSession session = userSessions.get(userId); if (session != null && session.isProcessRunning()) { session.getCmdProcess().destroyForcibly(); sendError("⏹ 用户命令已终止", userId); } else { sendError("⏹ 无执行中命令", userId); } } private void sendError(String message, String userId) { try { messagingTemplate.convertAndSend("/topic/commandOutput/" + userId, "❌ " + message); } catch (Exception ignored) { } } private String generateUniqueTitle(String userId) { return "CMD_SESSION_" + userId + "_" + System.currentTimeMillis(); } }能不能在命令执行结束的时候,追加一行“命令执行结束”返回前端页面
06-07
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值