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