目录
设计流程图
背景
由于本系统为单节点系统,从节约单机性能的角度考虑,不增加redis队列或者使用mq等消息组件对日志与业务系统解耦。所以设计此方案,目的是实现日志的异步批量入库,减少直接同步入库对数据库以及压力,并且在异步时避免了使用业务功能的公共线程池。一般情况下,日志数据需要与业务数据分库,日志功能也应该做到与业务功能解耦,可以使用mq,kafka等方式,实现日志统一收集,写入到统一的日志中心(存入MongDB或者ES等数据库),便于做日志分析。
设计方案
- 声明日志对象队列:volatile Queue dataQueue;
- 业务系统产生的日志同步写入队列
- 调度任务定时获取队列中的日志数据,批量写入到数据
- 调度任务
- 日志写入线程池
- 日志监控线程池
- 不使用公共线程池,而使用单独的线程池,防止对主业务造成影响
- 简单实现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);
}
}