高並發環境下JavaWeb的緩衝到期策略

來源:互聯網
上載者:User

  最近公司的幾個平台經常在高峰期掛掉,經檢查是因為資料庫有太多Slow Query導致的,當初也沒細想為什麼會出現這麼多的Slow Query,而且大部分還是相同的查詢,單獨拿某個Sql查詢消耗時間大都在毫秒層級,為了安全起見,對所有Sql又做了一次最佳化,並且寫了監測指令碼,定期殺掉太慢的查詢,但這樣的話還是會影響到有些使用者的訪問。

     網站採用了Spring+SpringJDBC+Servlet+Memcache的架構,資料庫是一對Master-Slave的Mysql,有大概幾十個介面和4個網站共用這個資料庫,採用了proxool資料庫連接池。因為資料的即時性,所以CDN和Memcache的緩衝到期時間都是在5分鐘左右,好了環境介紹完畢,開始著手解決這個問題。

     為了類比高峰期並發環境,使用Apache的ab命令對網站進行壓力測試,此時測試環境是沒有Cache的,果不其然,log裡出現了資料庫連接已經佔滿的異常資訊,猜測是在大並發環境下,緩衝正好到期,所有的訪問都去請求資料庫導致串連佔滿,經過考慮,有了以下解決方案,不足之處請說明。

 

備忘:以下過程中出現的client為Memcache的執行個體,省略了初始化的過程,所用到的Memcache庫為xmemcache1.3.3

 

1、  增加資料備份,防止緩衝到期後同時請求資料庫

2、  增加同步機制,保證並發環境下只有一個使用者在更新資料

3、  增加資料更新回調介面,當緩衝到期後,調用介面更新資料

4、  驗證資料正確性,防止在更新pojo類時出現的ClassCastException

 

定義資料更新回調介面:

1 public interface MemcachedCallback {
2 Object update(Map<String, Object> args);
3 boolean validate(Object data);
4 }

寫入緩衝時,增加資料備份

 1 public static void setCallBack(String keyName, Object object) {
2 if (client == null)
3 return;
4 try {
5 client.set(keyName, MemcachedMgr.DEFAULT_TIMEOUT, object);
9 client.set(keyName + "_OLD", MemcachedMgr.OLD_DATA_TIMEOUT, object);
10 } catch (Exception e) {
11 log.error("Cache set timeout for key" + keyName + " with value: "
12 + object.toString() + " Error: " + e.getMessage());
13 }
14 }

操作線程池更新緩衝

 1 public class MemcachedPolicy{
2 private static ThreadPoolExecutor pool = new ThreadPoolExecutor(10, 200, 500,
3 TimeUnit.MILLISECONDS,new ArrayBlockingQueue<Runnable>(10),
4 new ThreadPoolExecutor.CallerRunsPolicy());
5 public static synchronized void exec(String key, MemcachedCallback callback, Map<String, Object> args) {
6 //判斷當前是否有更新快取作業
7 if(MemcachedMgr.get(key + "_MPFlag") == null || "0".equals(MemcachedMgr.get(key + "_MPFlag"))) {
8 MemcachedMgr.set(key + "_MPFlag", "1");
9 pool.execute(new PolicyHandler(key, callback, args));
10 }
11 }
12
13 public static synchronized void remove(String memKey) {
14 MemcachedMgr.set(memKey + "_MPFlag", "0");
15 }
16 }
17
18 class PolicyHandler extends Thread{
19 private MemcachedCallback callback;
20 private Map<String, Object> args;
21 private String memKey;
22 public PolicyHandler(String key,MemcachedCallback callback, Map<String, Object> args) {
23 this.memKey = key;
24 this.callback = callback;
25 this.args = args;
26 }
27
28 public void run() {
29 MemcachedMgr.setCallBack(memKey, callback.update(args));//更新資料
30 MemcachedPolicy.remove(memKey);
31 }
32 }

準備工作都已經做好,開始實現具體的策略,MemcacheMgr將只對外提供get方法:

 1 public static Object get(String keyName, Map<String, Object> args, MemcachedCallback callback) {
2 if (client == null){ //如果memcache意外重啟,則讀取資料庫(適用於極端情況)
3 return callback.update(args);
4 }
5 try {
6 Object data = client.get(keyName);
7 boolean hasError = false;
8 if(data != null && !callback.validate(data)) { //如果資料校正失敗,則更新資料
9 data = null;
10 hasError = true;
11 }
12 if(data == null) {
13 if(!hasError) {
14 data = client.get(keyName + "_OLD");//擷取備份緩衝
15 }
16 //將緩衝更新任務加入線程池隊列
17 MemcachedPolicy.exec(keyName, callback, args);
18 if(data == null) { //當memcache重啟,備份資料為空白的情況下
19 int count = 10;//最多5秒逾時
20 while(count > 0) {
21 if((data = client.get(keyName)) != null) {
22 break;
23 }
24 count -- ;
25 Thread.sleep(500);
26 }
27 }
28 }
29 return data;
30 } catch (Exception e) {
31 return null;
32 }
33 }

 

Memcache的工具類已經重構好了,接下來開始使用吧:

at IndexServlet

1、在Servlet裡有一些從request擷取到的參數,可以直接通過加final關鍵詞的方式讓update裡直接使用,不過對於參數的校正還是應該跟資料操作隔離開的。

 1 Map<String, Object> args = new HashMap<String, Object>();
2 args.put("page", page);
3 args.put("sort", sort);
4
5 Map<String, Object> data = (Map<String, Object>) MemcachedMgr.get("your key", args, new MemcachedCallback() {
6
7 public Object update(Map<String, Object> args) {
8 Map<String, Object> map = new HashMap<String, Object>();
9 //所有的參數都可以從args拿到
10          //TODO 查詢資料庫,並將結果存入map
11 return map;
12 }
13
14 public boolean validate(Object data) {
15 Map<String, Object> map = (Map<String, Object>) data;
16 try {
17 //TODO 通過強制類型轉換來判斷是否有轉換錯誤,或者自訂校正
18 } catch (Exception e) {
19 return false;
20 }
21 return true;
22 }
23 });
24 //TODO request.setAttribute & 轉寄

算是告一段落,開始壓力測試,類比300個並發測試該介面,資料庫只有一個process,而且QPS基本沒什麼變化。

據同事講,緩衝到期請求擊穿資料庫這種情況叫“Dogpile”,google了下dogpile,只發現hibernate裡內建了DogpilePrevention,百度沒有找到相關資料……

採用map存放頁面所需所有資料感覺上還是不太好,暫時沒想到更好的辦法,先這麼著吧,如果有好的解決方案,請大家不吝指教。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.