大量並發的應急處理方案與實踐2——使用緩衝
《大量並發的應急處理方案與實踐》提供的方法,只能做應急時的一種參考,不推薦在設計之初時使用,設計之初我們應該採用更合理的架構,以避免文中所提及的問題發生,關於架構請參考我的另一篇文章《開餐館與做軟體——如何提高大型網站效能》。
資源可以分成兩種,一種為禁止並發訪問的資源,另一種為允許並發訪問的資源。禁止並發訪問的資源如高速公路收費站,每一個收費口一次只處理一個通過請求,當有多個車輛請求通過時,解決的辦法有兩種,一種是增設收費口,另一種就是讓車輛排隊等候,即不馬上處理請求,讓請求等待空閑資源,我把這種解決辦法稱作後向非同步處理,即將需要處理的請求,延後一段時間處理,這種方法適用於使用者不需要看到即使效果的情況。關於“後向非同步處理” 的例子請參考《大量並發的應急處理方案與實踐1——非同步處理》。允許並發訪問的資源如商場,任何顧客都可以隨時購買需要的商品,早先的大部分商場商品前都有一個櫃檯,顧客通過售貨員購買需要的商品,當顧客增多後,售貨員就會應接不暇,讓顧客排隊是一個辦法,但是沒有哪個商場會強制顧客排隊購買商品,這種“後向非同步處理”不適合解決這樣的問題,於是一種更好的解決方案產生了——“超市”,取出部分商品直接擺放在售貨台上,讓顧客自己去取,然後只需要安排一個服務人員隨時觀察,發現商品不全再到庫房中取來相應商品進行補充。
即將如下流程:
顧客 -> 售貨員1 -> 商品1 -> 付費
顧客 -> 售貨員2 -> 商品2 -> 付費
顧客 -> 售貨員3 -> 商品3 -> 付費
改造為
庫房 -> 服務員 —> 售貨台(緩衝)(前向非同步處理)
顧客 -> 商品(1、2、3)-> 排隊付費(後向非同步處理)
這是一個典型例子,顧客對於付費的請求不需要即時響應,我們可以採用後向非同步處理的方法。但是對於查看商品的請求越早滿足越好,因此我們採用另一種前向非同步處理的辦法,將商品(資料)提前放到售貨台(緩衝)裡,這樣當顧客(請求)需要的時候可以直接取走。下面我們來看一個你可能會遇到的例子:
有一天你的老闆突然接到電話,客戶抱怨說當登入人數增加的時候,頁面開啟的非常慢,這時老闆找到你,你竭盡全力向老闆解釋,老闆用無奈的眼神望著你,對你說:"使用者很急,全靠你了。"
你垂頭喪氣的走回到座位上,開始一點點排查原因,這時你發現原來這些頁面在開啟時,伴隨大量的資料庫操作,
這就是頁面開啟速度慢的原因。遇到這樣的問題,比較簡單快速的改造方法就是使用緩衝。
即是將如下結構,
使用者-> 頁面與邏輯 -> 資料庫
改造為:
資料庫 -> 監控程式 -> 緩衝 (前向非同步處理)
使用者-> 頁面與邏輯 -> 緩衝
舉例來說你發現需要加速的頁面 test.jsp, 其通過TestDB.java 類擷取資料。
public class TestDB{
...
// 讀取資料庫 1
String sql1 = "..."; //查詢語句
List tmplist1 = queryBySQL(sql1); //queryBySQL 方法,執行sql 語句,執行結果放到一個List 中返回
...
// 讀取資料庫 2
String sql2 = "..."; //查詢語句
List tmplist2 = queryBySQL(sql2); //queryBySQL 方法,執行sql 語句,執行結果放到一個List 中返回
...
// 讀取資料庫 3
String sql3 = "..."; //查詢語句
List tmplist3 = queryBySQL(sql3); //queryBySQL 方法,執行sql 語句,執行結果放到一個List 中返回
...
}
首先,我們建立一個與頁面相對應的緩衝模組(例如Buffer)。為需要快取資料的頁面建立一個與之對應的類TestBuffer.java 用來存放資料。必須保證TestBuffer.java 中的資料與資料庫中相應資料的一一對應關係,因此我們將TestBuffer.java 設計為一個單例。
#Buffer
-- TestBuffer.java
public class TestBuffer{
private volatile static TestBuffer singleton=null;
private TestBuffer(){}
public static TestBuffer getInstance()
{
if(singleton==null){
synchronized(TestBuffer.class)
{
singleton=new TestBuffer();
}
}
return singleton;
}
}
我們分析需要加速頁面的資料庫操作模組,找出所有資料庫操作點。例如我們分析TestDB.java ,發現如下資料庫
操作點:讀取資料庫 1、讀取資料庫 2、讀取資料庫 3。這三個操作點分別返回三個List 對象,這三個List 對象就是我們要緩衝的資料。我們將這三個對象加入到testBuffer.java 中。
public class TestBuffer{
private volatile static TestBuffer singleton=null;
private TestBuffer(){}
private static List tmplist1 = null
private static List tmplist2 = null
private static List tmplist3 = null
public static TestBuffer getInstance()
{
if(singleton==null){
synchronized(TestBuffer.class)
{
singleton=new TestBuffer();
}
}
return singleton;
}
public List getTmplist1(){
return tmplist1;
}
public List setTmplist1(List tmplist1){
this.tmplist1 = tmplist1;
}
public List getTmplist2(){
return tmplist2;
}
public List setTmplist2(List tmplist2){
this.tmplist2 = tmplist2;
}
public List getTmplist3(){
return tmplist3;
}
public List setTmplist3(List tmplist3){
this.tmplist3 = tmplist3;
}
}
重新改造原先的資料庫訪問模組TestDB.java ,更改如下:
public class TestDB{
...
//String sql1 = "..."; //查詢語句
//String sql2 = "..."; //查詢語句
//String sql3 = "..."; //查詢語句
//List tmplist1 = queryBySQL(sql1); //queryBySQL 方法,執行sql 語句,執行結果放到一個List 中返回
//List tmplist2 = queryBySQL(sql2); //queryBySQL 方法,執行sql 語句,執行結果放到一個List 中返回
//List tmplist3 = queryBySQL(sql3); //queryBySQL 方法,執行sql 語句,執行結果放到一個List 中返回
List tmplist1 = TestBuffer.getTmplist1();
List tmplist2 = TestBuffer.getTmplist2();
List tmplist3 = TestBuffer.getTmplist3();
...
}
然後我們添加從資料庫到緩衝的前向非同步處理模組,即我們實現一個提前啟動並執行監控程式,將資料庫中的資料放到緩衝中,並保持緩衝與資料庫中的資料同步,我們可以使用線程實現。
public class testThread implements Runnable{
private static long interval = 3000; //迴圈間隔
@Override
public void run(){
while(true){
...
String sql1 = "..."; //查詢語句
String sql2 = "..."; //查詢語句
String sql3 = "..."; //查詢語句
List tmplist1 = queryBySQL(sql1);
TestBuffer.setTmplist1(tmplist1);
List tmplist2 = queryBySQL(sql2);
TestBuffer.setTmplist2(tmplist2);
List tmplist3 = queryBySQL(sql3);
TestBuffer.setTmplist3(tmplist3);
...
try {
Thread.sleep(interval);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
以上提供的方法只做您遇到類似問題時的一種參考,是術,方法千變萬化而道理不離其宗,合理使用緩衝和非同步處理是解決這一類問題的核心。