利用java concurrent 包实现日志写数据库的并发处理

一、概述

在很多系统中,往往需要将各种操作写入数据库(比如客户端发起的操作)。

最简单的做法是,封装一个公共的写日志的api,各个操作中调用该api完成自己操作日志的入库。但因为入数据库效率比较低,如果每个操作自己入库,则会影响响应速度。而且当操作并发度很高时,往往同时有多个线程在写数据库,也会对系统有影响。

考虑的解决方案是,这个api并不实际完成入库,而是将每个操作日志信息写到一个公共的缓存中,然后应用系统起了一个独立的线程(一直运行)在后台进行入库。如果当前缓存中有记录,就写库,没有记录,就堵塞住。

这里关键的是要有一个缓存日志的数据结构,我们这里使用java current包中的并发数据结构LinkedBlockingDeque类。

 

二、具体代码

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.LinkedBlockingDeque;

public class LogManager implements Runnable {
    //定义缓存数据结构,支持并发操作
    private static LinkedBlockingDeque<ActionLog> list = new LinkedBlockingDeque<ActionLog>();
    static{
        //创建并启动线程,该线程实现日志的入库
        new Thread(new LogManager()).start();
    }
    public static void addActionLog(ActionLog log) {
        list.add(log);
    }

    @Override
    public void run() {
        List<ActionLog> items = new ArrayList<ActionLog>();
        while (true) {
            //从缓存中取出1条日志,但不从缓存中删除。用于检查队列中是否有数据
            ActionLog item = list.peek();
            if (item == null && items.size() > 0) {
                //item为null说明缓存中没有日志了,这时如果临时队列items中有记录,就可以批量入库了
                work(items);
            }
            //从缓存中取出并删除最前面的日志,如果无数据,在堵塞
            ActionLog result = deleteItem();
            //将取出的记录放到临时队列中
            items.add(result);
        }
    }

    private ActionLog deleteItem() {
        ActionLog result = null;
        try {
            //取出并删除最前面的一条数据,如果缓存中无数据,则堵塞住
            result = list.takeFirst();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return result;
    }

    private void work(List<ActionLog> items) {
        //TODO 完成入库。这里代码没有提供
        //入库后删除临时队列中记录
        items.clear();
    }

}

各种工作线程调用 addActionLog 方法将要入库的日志放到缓存中。由LogManager通过自己的线程控制入库。

三、小结

上述代码虽然简单,但很好的解决了前面提出的问题。不过这个解决方案存在一个问题。

如果说系统产生日志的速度超过了单个线程入库的速度,上述代码就有问题。就会造成该线程不停地入库。

这时就需要考虑增加额外的处理速度,如增加入库线程。

所以上述代码,只适合并发日志量不是特别大的情况下的场景。

posted @ 2016-01-23 16:12  51kata  阅读(2471)  评论(0编辑  收藏  举报