設計思路
- 每次點擊不直接更新資料庫,避免高併發下連線風暴。
- 計數請求寫入記憶體緩存 (
LinkedList),極低延遲。
- 獨立執行緒/定時任務批次寫入資料庫(每 60 秒或累積 N 筆)。
核心類別
CountBean.java (資料載體)
package com.tot.count;
public class CountBean {
private String countType; private int countId;
// getter/setter 略
}
CountCache.java (記憶體緩存)
package com.tot.count;
import java.util.LinkedList;
public class CountCache {
public static LinkedList list = new LinkedList<>();
public static void add(CountBean cb) { if (cb!=null) list.add(cb); }
}
CountControl.java (定時批次寫入)
package com.tot.count;
import java.sql.*;
public class CountControl {
private static long lastExecuteTime = 0;
private static long executeSep = 60000; // 60秒
public synchronized void executeUpdate() {
Connection conn = null; PreparedStatement ps = null;
try {
conn = DBUtils.getConnection(); // 需自行實作連線池
conn.setAutoCommit(false);
ps = conn.prepareStatement("UPDATE t_news SET hits=hits+1 WHERE id=?");
while (!CountCache.list.isEmpty()) {
CountBean cb = CountCache.list.removeFirst();
ps.setInt(1, cb.getCountId());
ps.addBatch(); // 批次處理
}
ps.executeBatch();
conn.commit();
} catch (Exception e) { e.printStackTrace(); }
finally { /* 關閉資源 */ }
}
public void run() {
if (System.currentTimeMillis() - lastExecuteTime > executeSep) {
lastExecuteTime = System.currentTimeMillis();
executeUpdate();
}
}
}
JSP 調用範例
<%
CountBean cb = new CountBean();
cb.setCountId(Integer.parseInt(request.getParameter("cid")));
CountCache.add(cb);
new CountControl().run(); // 每次請求檢查是否到批次時間
%>
優化與生產化建議
- 批次大小觸發:結合時間與筆數雙重條件(如 60 秒或 1000 筆)。
- ScheduledExecutorService:替代 JSP 呼叫
run(),由容器啟動定時任務。
- 持久化緩存:防止重啟丟失,可定期序列化至磁碟或使用 Redis。
- 分庫分表:計數表獨立庫,減少主業務鎖爭用。
- 原子操作:資料庫層面
hits=hits+1 已是原子,無需應用層鎖。