一、概述
在很多系统中,往往需要将各种操作写入数据库(比如客户端发起的操作)。
最简单的做法是,封装一个公共的写日志的api,各个操作中调用该api完成自己操作日志的入库。但因为入数据库效率比较低,如果每个操作自己入库,则会影响响应速度。而且当操作并发度很高时,往往同时有多个线程在写数据库,也会对系统有影响。
考虑的解决方案是,这个api并不实际完成入库,而是将每个操作日志信息写到一个公共的缓存中,然后应用系统起了一个独立的线程(一直运行)在后台进行入库。如果当前缓存中有记录,就写库,没有记录,就堵塞住。
这里关键的是要有一个缓存日志的数据结构,我们这里使用java current包中的并发数据结构LinkedBlockingDeque类。
二、具体代码
importjava.util.ArrayList;importjava.util.List;importjava.util.concurrent.LinkedBlockingDeque;public class LogManager implementsRunnable {//定义缓存数据结构,支持并发操作
private static LinkedBlockingDeque list = new LinkedBlockingDeque();static{//创建并启动线程,该线程实现日志的入库
new Thread(newLogManager()).start();
}public static voidaddActionLog(ActionLog log) {
list.add(log);
}
@Overridepublic voidrun() {
List items = new ArrayList();while (true) {//从缓存中取出1条日志,但不从缓存中删除。用于检查队列中是否有数据
ActionLog item =list.peek();if (item == null && items.size() > 0) {//item为null说明缓存中没有日志了,这时如果临时队列items中有记录,就可以批量入库了
work(items);
}//从缓存中取出并删除最前面的日志,如果无数据,在堵塞
ActionLog result =deleteItem();//将取出的记录放到临时队列中
items.add(result);
}
}privateActionLog deleteItem() {
ActionLog result= null;try{//取出并删除最前面的一条数据,如果缓存中无数据,则堵塞住
result =list.takeFirst();
}catch(InterruptedException e) {
e.printStackTrace();
}returnresult;
}private void work(Listitems) {//TODO 完成入库。这里代码没有提供//入库后删除临时队列中记录
items.clear();
}
}
各种工作线程调用 addActionLog 方法将要入库的日志放到缓存中。由LogManager通过自己的线程控制入库。
三、小结
上述代码虽然简单,但很好的解决了前面提出的问题。不过这个解决方案存在一个问题。
如果说系统产生日志的速度超过了单个线程入库的速度,上述代码就有问题。就会造成该线程不停地入库。
这时就需要考虑增加额外的处理速度,如增加入库线程。
所以上述代码,只适合并发日志量不是特别大的情况下的场景。