一、概述
在很多系統中,往往需要將各種操作寫入數據庫(比如客戶端發起的操作)。
最簡單的做法是,封裝一個公共的寫日志的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通過自己的線程控制入庫。
三、小結
上述代碼雖然簡單,但很好的解決了前面提出的問題。不過這個解決方案存在一個問題。
如果說系統產生日志的速度超過了單個線程入庫的速度,上述代碼就有問題。就會造成該線程不停地入庫。
這時就需要考慮增加額外的處理速度,如增加入庫線程。
所以上述代碼,只適合並發日志量不是特別大的情況下的場景。