壓縮網頁載入時間:Web頁面并行化的幾點考慮
原創【51CTO精選譯文】建立網站時,最常見的需求是將網頁載入時間最小化,例如,一般對主要頁面的載入時間要求控制在0.5秒以內,當然網頁載入時間是與許多因素關聯的,如網絡,緩存,Web服務器,腳本語言/代碼,數據庫訪問等。當然我想要討論在創建Web頁面時對數據庫的訪問,我指的是動態Web頁面,如使用PHP,Java/J2EE,Ruby和Asp(.NET)等創建的動態Web頁面。
一個非常常見的編程風格是使用腳本段,如:
- < html>
- < body>
- Time now is < %= new java.util.Date() %>
- < /body>
- < /html>
使用當前時間的文本代替上面的“< %= new java.util.Date() %>”。
如果我制作了一個動態內容站點,比如使用Wordpress建立一個博客站點,需要生成多個動態內容,如最新的帖子,熱門標簽,帖子評論等,同樣也會生成對數據庫的訪問,主要是查詢數據庫,我想這里不用再多說什么,大家都能理解了。
問題
在生成一些“重量級”的網頁時,如在線報紙和書店,可能會涉及到多個查詢,如你是否已經登錄?我們是否有建議推給你?最新的主題是什么?你以前的興趣是什么?你有朋友在線嗎?你在網站上會產生什么內容?
我最近審查一個網站,每個頁面產生的查詢次數都大于500,我個人認為這是一個非常高的數目了,但這些查詢確實都是需要的,問題是網頁載入時間花了2秒。
經過調整,重寫和使用索引后,頁面載入時間下降到0.6秒,但這還不夠快,問題是所有數據庫訪問都是串行的,它們需要并行起來。Web頁面并行化的需求由此誕生。
MySQL為一個查詢計算只能使用一個線程,但I/O卻是支持多線程的,這就導致在標準的Linux發行版上,對于一個給定的Web頁面,只有一個CPU可以使用。模板引擎是一個接一個地解析腳本段的,按順序執行的,在Java中,一個JSP頁面被重寫為一個普通的Java Servlet類,腳本段成為主要的代碼,HTML代碼成為打印標準的輸出,因此獲得是線性執行代碼。
即便是再先進的框架,其標準的方法還是線性的,例如,使用Spring框架,你有Java對象和控制器負責Web頁面,可以避免在動態Web頁面中使用腳本,只需要那些控制器提供的數據,因此如果使用Spring + Velocity,一個Web頁面看起來就會是:
- < html>
- < body>
- Login time as recorded in DB is: ${user.loginTime}
- < /body>
- < /html>
在預建user對象時調用getLoginTime()方法進行轉換,但這個方法是如何工作的呢?
它會緩慢地初始化以便訪問數據庫得到答案嗎?
在某些init()方法期間控制器會設置值嗎?
控制器會設置值以響應Web頁面的請求參數,并逐一解析它們嗎?
上面的一切都是線性或是串行執行的。
壓縮網頁載入時間:如何并行?
Web頁面并行化不是一件容易的事情,需要理解多線程編程,程序員需要了解競爭環境、死鎖和資源缺乏問題等,不過這些在動態網頁中一般不會成問題,有些編程語言對多線程編程提供了良好的支持,Java就是這樣一門語言。
假設我需要產生一個10個查詢來響應Web頁面的請求,如使用Java,我們可以像下面這樣編寫代碼:
- CountDownLatch doneSignal = new CountDownLatch(10);
- Runnable task1 = new Runnable() {
- public void run()
- {
- user.setLoginTime(this.jdbcTemplate.queryForInt("SELECT ... FROM ..."));
- doneSignal.countDown();
- }
- } ;
- Runnable task2 = new Runnable() {
- public void run()
- {
- headlines = getSimpleJdbcTemplate().query("SELECT * FROM headline WHERE...",
- new ParameterizedRowMapper< Headline>() {
- public Headline mapRow(ResultSet rs, int rowNum)
- {
- Headline headline = new Headline();
- headline.setTitle(rs.getString("title");
- headline.setUrl(rs.getString("url");
- ...
- }
- }
- doneSignal.countDown();
- }
- } ;
- ...
- Runnable task10 = new Runnable() {
- ...
- doneSignal.countDown();
- }
- Executor executor = Executors.newFixedThreadPool(numberOfAvailableProcessors);
- executor.execute(task1);
- ...
- executor.execute(task10);
- doneSignal.await();
- // Now fill in the Model
上面的代碼其實相對較簡單,可讀性也比較好,它的意思是:
◆讓我們創建10個任務,但不執行它們,僅僅是制定命令;
◆每項任務完成后,讓CountDownLatch知道它已經完成(但記住我們還沒有執行它);
◆我們創建或使用一個線程池,使用n個線程,n可以與我們的處理器數量進行關聯;
◆我們要求線程池處理所有的線程,在處理過程中,要么是同時運行它們,要么有些是順序執行,依賴于有多少線程可使用;
◆我們請求CountDownLatch進行阻塞,直到這10個任務都通知已經完成了;
接著執行下一批任務。
Spring有一個內置的TaskExecutor機制提供與上面說的線程池類似的解決方案。
我大多數時候都是使用的C/C++/Java,我不知道在PHP,Ruby,ASP.NET或其它語言該如何實現,上面的代碼肯定不能拿去直接使用,我希望看到框架能夠提供解決這個問題的方案,以便對于一般的Web開發都可以用上Web頁面并行技術。
原文:The DB problem inherent to dynamic web pages
作者:Shlomi Noach
【推薦閱讀】