日志队列Task设计及实现,观察者模式防止线程超时占满线程池

本文介绍了一种适用于单节点系统的日志异步批量入库设计方案。通过自定义队列和线程池,实现了日志数据与业务逻辑的解耦,有效减轻了数据库的压力,并确保了系统的稳定运行。

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

目录

 

设计流程图

背景

设计方案

主要代码

日志对象放入队列主要代码

Task主要代码

日志队列线程池主要代码

被观察对象

实际任务执行观察者

监控任务观察者

执行任务线程包装类

监控任务线程包装类


设计流程图

 

 

背景

由于本系统为单节点系统,从节约单机性能的角度考虑,不增加redis队列或者使用mq等消息组件对日志与业务系统解耦。所以设计此方案,目的是实现日志的异步批量入库,减少直接同步入库对数据库以及压力,并且在异步时避免了使用业务功能的公共线程池。一般情况下,日志数据需要与业务数据分库,日志功能也应该做到与业务功能解耦,可以使用mq,kafka等方式,实现日志统一收集,写入到统一的日志中心(存入MongDB或者ES等数据库),便于做日志分析。

设计方案

  1. 声明日志对象队列:volatile Queue dataQueue;
  2. 业务系统产生的日志同步写入队列
  3. 调度任务定时获取队列中的日志数据,批量写入到数据
    1. 调度任务
    2. 日志写入线程池
    3. 日志监控线程池
  4. 不使用公共线程池,而使用单独的线程池,防止对主业务造成影响
  5. 简单实现Hystrix的观察者模式:实际执行任务的线程执行结束,关系监控该任务的监控线程;当实际执行任务的线程执行超过设置的超时时间,监控线程启动,关闭实际执行任务的线程。

主要代码

日志对象放入队列主要代码

@Transactional(propagation = Propagation.NOT_SUPPORTED, noRollbackFor = Exception.class)
public void insertIbBizexceptionlogAsync(IbBizexceptionlog ibBizexceptionlog) {
    try {
        if (!LogInsertTask.getDataQueueInstance().offer(ibBizexceptionlog)) {
            // 放入失败删除头部再尝试放入一次
            // 这是由于队列长度只有500000的固定长度,只保留最新的,当然这里不考虑多线程的情况,尝试放入如果失败两次则不再尝试。
            LogInsertTask.getDataQueueInstance().remove();
            LogInsertTask.getDataQueueInstance().offer(ibBizexceptionlog);
        }
    } catch (Exception e) {
        log.error("业务异常对象放入日志队列异常:", e);
    }
}

 

Task主要代码

import com.xxx.LogThreadPoll;
import jdk.nashorn.internal.ir.debug.ObjectSizeCalculator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
import java.util.*;
import java.util.concurrent.*;

/**
 * 日志写入数据库job.
**/
@Component
@Slf4j
public class LogInsertTask {

    /**
    * 日志对象队列.
    */
    private static volatile Queue dataQueue;

    /**
     * 获取日志对象队列.
     * @return 日志对象队列
    */
    public static Queue getDataQueueInstance() {
        if (dataQueue == null) {
            dataQueue = new LinkedBlockingQueue<>(500000);
        }
        return dataQueue;
    }

    public void insertLog() {
    if (getDataQueueInstance().peek() != null) {
        log.info("日志批量处理Job执行开始");
        long startTime = System.currentTimeMillis();
        int size = getDataQueueInstance().size();
        log.info("日志队列条数:{}, 日志队列大小:{}b", size,
            ObjectSizeCalculator.getObjectSize(getDataQueueInstance()));

        Object o = null;
        for (int i = 0; i < size; i++) {
            // getDataQueueInstance().poll() 检索并删除此队列的头,如果此队列为空,则返回null。
            if (null != (o = getDataQueueInstance().poll())) {
                // 业务处理,将o 通过instanceof 分离成不同的日志对象,如异常、mq接收、mq推送等
                // 整理成集合批量插入
            }
        }

        // 使用单独的线程池
        LogThreadPoll.excute(() -> {
            // 如果代码中出现sleep.wait等,抛出InterruptedException 需要手动标记线程中断或者return
            // 手动标记线程中断:Thread.currentThread().interrupt();
            // 业务处理,执行数据库批量插入操作
        }, 9, TimeUnit.MINUTES, "xx日志" + startTime);

        log.info("日志批量处理Job主线程执行结束,耗时:{}", System.currentTimeMillis() - startTime);
    }
}

@Scheduled(cron = "${log.insert.task}")
public void timer() {
    try {
        insertLog();
    } catch (Exception e) {
        log.error("日志批量处理Job执行结束,异常", e);
    }
}
}

日志队列线程池主要代码

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.*;
/**
* 日志队列落地线程池.
**/
@Slf4j
public class LogThreadPoll {
    /**
     * 任务执行线程工厂.
    */
    private static ThreadFactory namedThreadFactory = 
        new ThreadFactoryBuilder().setNameFormat("log-thread-insert-%d").build();

    /**
     * 任务监控线程工厂.
    */
    private static ThreadFactory scheduledNamedThreadFactory = 
        new ThreadFactoryBuilder().setNameFormat("log-thread-scheduled-%d").build();

    /**
     * 任务执行线程池.
    */
    private static ExecutorService threadPoolExecutor;

    /**
     * 任务监控线程池
    */
    private static ScheduledExecutorService scheduledExecutor;

    /**
     * 创建任务执行线程池.
     * @return 任务执行线程池
    */
    private static ExecutorService getThreadPoolExecutor() {
        if (null == threadPoolExecutor || threadPoolExecutor.isShutdown()) {
            synchronized (LogThreadPoll.class){
                if (threadPoolExecutor==null || threadPoolExecutor.isShutdown()) {
                    threadPoolExecutor= new ThreadPoolExecutor(
                        3,
                        3,
                        0L,
                        TimeUnit.SECONDS,
                        new LinkedBlockingQueue<Runnable>(3),
                        namedThreadFactory,
                        new ThreadPoolExecutor.DiscardPolicy());
                }
            }
        }
        return threadPoolExecutor;
    }

    /**
     * 创建创建任务监控线程池.
     * @return 任务监控线程池
    */
    private static ScheduledExecutorService getScheduledExecutor() {
        if (null == scheduledExecutor || scheduledExecutor.isShutdown()) {
            synchronized (LogThreadPoll.class){
                if (scheduledExecutor==null || scheduledExecutor.isShutdown()) {
                    scheduledExecutor = 
                        Executors.newScheduledThreadPool(3, scheduledNamedThreadFactory);
                }
            }
        }
        return scheduledExecutor;
    }

    /**
     * 新线程执行任务.
     * @param originCommand 原始任务
     * @param timeLimit 线程超时时间限制
     * @param unit 时间单位
     * @param runnableCode 线程追踪标识
     * @return 任务Future
    */
    public static Future<?> excute(
        Runnable originCommand, int timeLimit, TimeUnit unit, String runnableCode) {
        if (null == originCommand) {
            return null;
        }
        // 通知管理器
        CommandStatus commandStatus = new CommandStatus();

        // 任务监控Runnable 可以增加其他逻辑
        ScheduledLogRunnable scheduledLogRunnable = new ScheduledLogRunnable();
        scheduledLogRunnable.setCommandStatus(commandStatus);
        ScheduledFuture<?> scheduledFuture =
            getScheduledExecutor().scheduleAtFixedRate(
                scheduledLogRunnable, timeLimit, 1, unit);

        // 被通知者-任务监控
        ScheduledFutureObserver futureObserverScheduledFuture = 
            new ScheduledFutureObserver(scheduledFuture, runnableCode);

        // 通知管理器放入:被通知者-任务监控
        commandStatus.addObserver(futureObserverScheduledFuture);

        // 任务执行Runnable
        LogRunnable logRunnable = new LogRunnable();
        logRunnable.setCommandStatus(commandStatus);
        logRunnable.setRunnableCode(runnableCode);
        logRunnable.setRunnable(originCommand);
        Future<?> future = getThreadPoolExecutor().submit(logRunnable);

        // 被通知者-任务执行
        FutureObserver futureObserverCommand = new FutureObserver(future, runnableCode);

        // 通知管理器放入:被通知者-任务执
        commandStatus.addObserver(futureObserverCommand);

        return future;
    }



    /**
     * 关闭任务执行线程池.
     * 一般不会调用
    */
    public static void shutdown() {
        if (null != threadPoolExecutor && !threadPoolExecutor.isShutdown()) {
            threadPoolExecutor.shutdownNow();
        }
    }

    /**
     * 关闭任务监控线程池.
     * 一般不会调用
    */
    public static void shutDownScheduledExecutor() {
        if (null != scheduledExecutor && !scheduledExecutor.isShutdown()) {
            scheduledExecutor.shutdownNow();
        }
    }
}

被观察对象

继承Observable

import java.util.Observable;
/**
 * 任务执行状态.
**/
public class CommandStatus extends Observable {
    /**
     * 0 执行中 1 完成 -1 超时 2 异常
    */
    private int threadStatus;

    public int getThreadStatus() {
        return threadStatus;
    }

    public void setThreadStatus(int threadStatus) {
        if (this.threadStatus != threadStatus) {
            this.threadStatus = threadStatus;
            setChanged();
            notifyObservers(threadStatus);
        }
    }
}

实际任务执行观察者

实现Observer

import lombok.extern.slf4j.Slf4j;
import java.util.Observable;
import java.util.Observer;
import java.util.concurrent.Future;
/**
 * 执行任务线程被通知对象.
**/
@Slf4j
public class FutureObserver implements Observer {
    private Future<?> future;
    private String runnableCode;

    public FutureObserver(Future<?> future, String runnableCode) {
        this.future = future;
        this.runnableCode = runnableCode;
    }

    @Override
    public void update(Observable o, Object arg) {
        try {
            if (o instanceof CommandStatus) {
                if (null != arg && 1 == (Integer) arg) {
                    log.info("日志执行任务({})插入成功", runnableCode);
                } else if (null != arg && -1 == (Integer) arg) {
                    log.info("日志执行任务({})插入超时", runnableCode);
                }

                if (null != future && !future.isDone()) {
                    if (future.cancel(true)) {
                        log.info("日志执行任务({})中断成功", runnableCode);
                    } else {
                        log.info("日志执行任务({})中断失败", runnableCode);
                    }
                }
            }
        } catch (Exception e) {
            log.error("执行任务({}),cancel异常", runnableCode, e);
        }
    }
}

监控任务观察者

实现Observer

import lombok.extern.slf4j.Slf4j;
import java.util.Observable;
import java.util.Observer;
import java.util.concurrent.*;
/**
 * 监控任务线程被通知对象.
**/
@Slf4j
public class ScheduledFutureObserver implements Observer {
    private ScheduledFuture<?> scheduledFuture;
    private String runnableCode;

    public ScheduledFutureObserver(
        ScheduledFuture<?> scheduledFuture, String     runnableCode) {
        this.scheduledFuture = scheduledFuture;
        this.runnableCode = runnableCode;
    }

    @Override
    public void update(Observable o, Object arg) {
        try {
            if (o instanceof CommandStatus) {
                // 无论哪种状态 都断开线程
                if (null != scheduledFuture) {
                    scheduledFuture.cancel(true);
                }
            }
        } catch (Exception e) {
            log.error("监控任务({}),cancel异常", runnableCode, e);
        }
    }
}

执行任务线程包装类

实现Runnable 接口

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
/**
 * 执行任务线程包装类.
**/
@Slf4j
@Data
public class LogRunnable implements Runnable {
    private CommandStatus commandStatus;
    private String runnableCode;
    private Runnable runnable;

    @Override
    public void run() {
        try {
            runnable.run();
            // 改变状态触发通知
            commandStatus.setThreadStatus(1);
        } catch (Exception e) {
            log.error("日志执行任务({})异常", runnableCode, e);
            // 改变状态触发通知
            commandStatus.setThreadStatus(2);
        }
    }
}

监控任务线程包装类

实现Runnable 接口

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
/**
* 监控任务线程包装类.
**/
@Slf4j
@Data
public class ScheduledLogRunnable implements Runnable {
    private CommandStatus commandStatus;

    @Override
    public void run() {
        commandStatus.setThreadStatus(-1);
    }
}

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值