在公司的项目中,有一个日志模块,代码写的很好,效率比较高,而且能支持高并发,频繁写日志的系统,拿来分析分析。
首先贴个代码块;
public class LogHelp extends Thread {
private static Logger logger = Logger.getLogger(LogHelp.class);
/** 设置线程的检查时间间隔 */
private long interval = 1000L;
/**
* 保存 list对象的队列. 每个对象的初始容量MAX_COUNT, 新加入的list对象被添加到队尾,
* currentList指示当前正在操作的list对象
*/
public static ConcurrentLinkedQueue<List<String>> imprQueue = new ConcurrentLinkedQueue<List<String>>();
/** list中最大个数 8192 */
private static int MAX_COUNT = 8192;
/** 当前正在处理的当前的 list对象 */
private static List<String> currentList = new ArrayList<String>(MAX_COUNT);
/** 临时引用 */
private List<String> tempList = new ArrayList<String>();
/** 上次从queue中取出list去保存的时间点 */
private static long lastSaveTimePoint = System.currentTimeMillis();
/**
* 两次保存的最大时间间隔, 若某两次取出的时间间隔超过这个值, 无论数量是否到达, 都取出一个list去保存. 单位: 毫秒.
* 来自配置项impr.queue.interval.max
*/
private static long MAX_SAVE_INTERVAL = 1000 * 10;
/**
* 线程锁参数, true:可执行 false:不可执行
*/
private static boolean canOperate = true;
public void run() {
while (true) {
try {
// 读取配置文件信息,设置线程的检查时间间隔
interval = 1000;
// 读取配置文件信息,设置imprQueue队列中list的最大容量
MAX_COUNT = 8192;
// 读取配置文件信息,设置imprQueue队列两次取出list的最大时间间隔
MAX_SAVE_INTERVAL = 20000;
try {
synchronized (this) {
// 防止陷入无限循环, 等待1秒后,启动下一次入库操作
wait(interval);
}
} catch (InterruptedException e) { // 退出出口,
// 收到InterruptedException异常即退出
System.err
.println(">> InterruptedException occurred to exit, please restart ClickLogThread");
return;
}
// 主方法
saveLog();
} catch (RuntimeException e) {
e.printStackTrace();
}
}
}
/**
* 定时将内存中的广告位监控日志数据, 批量写入数据库
*/
private void saveLog() {
// (1)时间的判断 超时的入库
if (currentList.size() > 0
&& System.currentTimeMillis() - lastSaveTimePoint >= MAX_SAVE_INTERVAL) {
listIntoQueue();
}
// (2)队列入库
tempList = getNextListToSave();
while (tempList != null) {
printLog(tempList);
tempList.clear();
tempList = getNextListToSave();
}
}
/**
* list进队列(有锁的操作)
*/
private static void listIntoQueue() {
if (canOperate) {
canOperate = false;
try {
imprQueue.offer(currentList); // '装填已满的list'被加到队列尾部
currentList = new ArrayList<String>(MAX_COUNT);
lastSaveTimePoint = System.currentTimeMillis();
} catch (Exception e) {
e.printStackTrace();
}
canOperate = true;
}
}
/**
* 取出队列头部的list, 该list元素数量已经达到MAX_COUNT; 如果数量没有到达MAX_COUNT或没有队列, 返回null
*
* @return 队列头部的元素数量已经达到MAX_COUNT的list; 可能为null
*/
private List<String> getNextListToSave() {
if (imprQueue.size() > 0) {
return imprQueue.poll();
} else {
return null;
}
}
/**
* 输出日志
*
* @param list
*/
private void printLog(List<String> list) {
if (list == null || list.size() == 0)
return;
//
else {
for (String s : list) {
logger.info(s);
}
}
}
/** 把一个对象保存到内存list中去 */
public static void saveLogInMemory(String str) {
if (str == null || StringUtils.isEmpty(str)) {
return;
}
// 如果currentList中的元素个数到达MAX_COUNT, 或者queue中没有list而最大时间间隔已到达, 则准备下一个list
if (currentList.size() >= MAX_COUNT) {
listIntoQueue();
}
currentList.add(str);
}
}
log4j配置文件
log4j.rootLogger=INFO,dailyFile
log4j.additivity.org.apache=true
#console
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.Threshold=INFO
log4j.appender.console.ImmediateFlush=true
log4j.appender.console.Target=System.err
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d{yyyyMMdd:HH:mm:ss,SSS} - %c -%-4r %-5p --> %m%n
#dailyFile
log4j.appender.dailyFile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.dailyFile.Threshold=DEBUG
log4j.appender.dailyFile.ImmediateFlush=true
log4j.appender.dailyFile.Append=true
log4j.appender.dailyFile.File=/mnt/dev/opt/rxlog/dspbrain.log
log4j.appender.dailyFile.DatePattern='.'yyyyMMdd_HH-mm
log4j.appender.dailyFile.layout=org.apache.log4j.PatternLayout
log4j.appender.dailyFile.layout.ConversionPattern=%d{yyyyMMdd\:HH\:mm\:ss,SSS} - %c -%-4r %-5p --> %m%n
#log4j.appender.dailyFile.BufferedIO=true
#log4j.appender.dailyFile.BufferSize=8192
在tomcat启动时,开启日志线程
@WebListener
public class StartListener implements ServletContextListener{
public void contextInitialized(ServletContextEvent sce) {
System.out.println("servlet context 初始化");
System.out.println(sce.getServletContext().getServerInfo());
new LogHelp().start();
}
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("servlet context 销毁");
}
}
这个loghelp中相当于新开了一条专门的线程,用来记录日志。
1、首先在tomcat启动的时候,开启日志线程,对外暴露的保存日志的方法是 saveLogInMemory(String str);
2、这个日志的线程是一个while(true);无线循环,然后线程会等待一秒钟,也就是会一秒钟循环执行一次。循环队列的时候,会做两个操作
a.检查currentList是否有值,且是否超过10秒钟,满足条件,入队列
b.检查tempList是否有值,满足条件,弹出队列
所以,队列相当于一个大池子,currentList相当于不断从外面接水往池子里倒水,templist相当于不断从队列这个大池子里舀水向外撒。队列这个大池子有个监控系统,控制向里面倒水,和 向外面舀水。