網站靜態化

找了一些關於網站靜態化的文章,分享一下。

網友方案一:

  做程序也做了將近4年時間了,從來沒有發過什麼技術性的文章,今天發一個只在大家一起共同學習進步,如有錯誤地方請指正。 
  最近自己做了一個做網絡廣告的網站叫全方位商機平臺的 項目,由於網站首頁上板塊劃分很多,不同板塊的數據庫查詢方式不同,首頁內容量巨大,如果按照一般的動態jsp頁面的話那麼數據庫查詢將是巨大的開銷,會 導致首頁訪問速度的下降。於是考慮將這個首頁全部靜態化。參考地址:http://www.31pt.com/。首頁的速度是非常快的。 
  整個網站才用struts2 + spring + hibernate + freemarker + urlrewrite完成。首頁純靜態化,頻道及其他頁面通過urlrewrite僞靜態。現在廢話少說。我先給出首頁jsp body源代碼: 
  • Java code


  • <body>
    <div id="wrap">
    <!--頭部開始-->
    <jsp:include page="/html/top.html" flush="true"></jsp:include>
    <!--頭部結束-->
    <!--導航開始-->
    <jsp:include page="/html/channel.html" flush="true"></jsp:include>
    <!--導航結束-->
    <jsp:include page="/html/center.html" flush="true"></jsp:include>
    <!--友情連接開始-->
    <jsp:include page="/html/index_link.html" flush="true"></jsp:include>
    <!--友情結束-->
    <!--底部開始-->
    <jsp:include page="/html/bottom.html" flush="true"></jsp:include>
    <!--底部結束-->
    </div>
    </body>



整個網站首頁的基本結構是通過jsp的include標籤將所有通過freemarker生成的靜態頁面組織起來。後臺控制各個部分的靜態頁生成。這樣做 將首頁進行了拆分,便於了靜態頁面的維護,當我們需要生成“友情鏈接”部分的時候就只生成友情鏈接部分,而不需要將整個頁面都從新生成一次。 
  以下是我生成靜態頁最核心的方法,使用freemarker。 
  • Java code


  • /**
    * 生成靜態頁面主方法
    *
    @param context ServletContext
    *
    @param data 一個Map的數據結果集
    *
    @param templatePath ftl模版路徑
    *
    @param targetHtmlPath 生成靜態頁面的路徑
    */
    public static void crateHTML(ServletContext context,Map<String,Object> data,String templatePath,String targetHtmlPath){
    Configuration freemarkerCfg
    = new Configuration();
    //加載模版
           freemarkerCfg.setServletContextForTemplateLoading(context, "/");
    freemarkerCfg.setEncoding(Locale.getDefault(),
    "UTF-8");
    try {
    //指定模版路徑
               Template template = freemarkerCfg.getTemplate(templatePath,"UTF-8");
    template.setEncoding(
    "UTF-8");
    //靜態頁面路徑
               String htmlPath = context.getRealPath("/html")+"/"+targetHtmlPath;
    File htmlFile
    = new File(htmlPath);
    Writer out
    = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(htmlFile), "UTF-8"));
    //處理模版  
               template.process(data, out);
    out.flush();
    out.close();
    }
    catch (Exception e) {
    e.printStackTrace();
    }
    }



其實很簡單,只要Google一下就有很多這方面的代碼。我也是Google的代碼然後自己再根據實際情況修改。簡單說明一下參數: 
ServletContext :這個不用說了吧。做java web的應該都知道,只不過struts2中這樣獲取ServletActionContext.getServletContext() 
Map <String,Object> data : 模版的數據來源。freemarker通過一個Map給ftl模版送數據。 
現在已友情鏈接爲列子詳細介紹靜態頁面如何生成。其他模塊以此類推。 
String templatePath : ftl所在的路徑。我這裏相對於網站的一個相對路徑然後通過ServerContext獲取絕對路徑。 
String targetHtmlPath : 最後生成靜態頁的路徑:我這裏相對於網站的一個相對路徑然後通過ServerContext獲取絕對路徑。 

友情鏈接根據這段代碼 <jsp:include page="/html/index_link.html" flush="true"> </jsp:include>我們需要freemarker生成一個index_link.html文件。友情鏈接數據來源通過數據庫查詢獲 取。 
然後再寫一個方法專門生成友情鏈接靜態頁面: 
  • Java code


  • /**
    * 生成友情鏈接的靜態頁index_link.html
    *
    @param context
    *
    @param data
    */
    public static void createIndexFriendLink(ServletContext context,Map<String,Object> data){
    crateHTML(context,data,
    "index_link.ftl","index_link.html");
    }



此方法調用上面的createHTML方法。 
然後根據以上方法我們就可以再Struts2的action裏面從數據庫查詢數據放入map調用createIndexFriendLink()方法生成靜態頁了。 
這是action中的一個方法: 
  • Java code


  • /**
    * 生成友情鏈接靜態頁index_link.html
    *
    @return
    */
    public String createLink(){
    //權限驗證
    if(! this.isAccess())
    return "error";
    try{
    //得到友情鏈接
               List links = friendLinkDAO.findAll();
    //準備數據
               HashMap<String,Object> data = new HashMap<String,Object>();
    data.put(
    "links", links);
    //調用靜態頁面方法
               HTML.createIndexFriendLink(ServletActionContext.getServletContext(), data);
    addActionMessage(
    "靜態頁面生成成功!");
    return "message";
    }
    catch(Exception e){
    e.printStackTrace();
    return "failure";
    }
    }



List links = friendLinkDAO.findAll();通過spring注入action的hiberate DAO獲取數據給list然後通過以下代碼 
HashMap <String,Object> data = new HashMap <String,Object>(); 
data.put("links", links); 
準備數據調用createIndexFriendLink()方法。 
以下是:ftl模版源碼: 
  • Java code


  • <#if links?size != 0>
    <div class="link">
    <strong>友情鏈接:</strong>
    <#list links as link>
    <a href="${link.linkUrl}" target="_blank" title="${link.linkName}">${link.linkName}</a>
    </#list>
    </div>
    <#else>
    <div class="link"></div>
    </#if>



這樣友情鏈接靜態頁就生成了。然後其他靜態頁依此葫蘆畫瓢。 

總結:雖然靜態頁訪問速度快和其他的好處,但實現起來畢竟還是很麻煩了,維護也是一個麻煩事情。如果您的站點更新速度快那麼就需要在你的後臺數據更新部分 調用相應的createHTML方法實時的生成靜態頁面。如果更新速度不慢可以在後臺手動更新或者利用操作系統的定時任務功能去執行你的靜態頁面生成程 序。www.361pt.com這個網站我是才用了這兩種方式。


網友方案二:

不 知道因爲什麼原因,當系統的訪問量達到了8000IP的時候,tomcat的內存總是會莫名其妙的增加到上限,而且到最後垃圾回收也會非常費力,最後導致 系統停止響應,我調試過各種參數,包括垃圾回收策略,並行回收,修改各內存配置的參數,結果都一樣,沒辦法,絕對對這一訪問量最大的部分進行全部靜態化。

一、靜態化要考慮三個方面的事情,

1 對SEO

  能夠讓搜索引擎更方便的進行信息採集和分類,提升其速度和準確性

2 對用戶

  不能影響版面的展示,不能爲了速度影響了美觀和可用性

3 對維護

  一些內容可以方便的更換,比如菜單調整,連接調整,廣告位的調整等

二、最終採用瞭如下的方案進行

1 使用freemarker進行靜態化

2 將菜單,廣告位等以後可能變化的地方,使用JS進行操作。 一些地方採用多個連續的JS,方便以後進行整體調整。

3 通過urlrewrite 將訪問轉到靜態化後的頁面

4 通過404的錯誤處理,對尚未靜態化的頁面進行處理

5 靜態化時,考慮到帖子量在百萬級別,爲了防止一個文件夾下面有太多文件,決定根據帖子編號的末尾2位數字進行文件夾分組,比如12345 則分配到 45這個子文件夾裏面,67890 則分配到90這個文件夾裏面。這樣就將帖子近似均分到100個文件夾,每個文件夾的帖子在1萬個,還可以接受。如果以後帖子再多,我就再考慮分成1000 個文件夾了。這個設置要爲以後的擴容留有餘地。


三、具體操作

1 根據現有帖子頁面製作ftl的模板,裏面要考慮js的腳本位置。

2 製作批量生成的程序,爲減輕服務器的壓力,以某個數據庫備份爲藍本,在本地完成這部分帖子的初始化,生成htm文件,然後將靜態化好的目錄整個打包上傳到服務器。

3 啓動404的處理,比較新的帖子如果沒有被靜態化,則由這個程序進行處理

4 帖子管理程序改造,在帖子保存時自動進行靜態化

5 啓動urlRewrite 其中比較關鍵的部分是

RewriteRule ^/view-([0-9]*?)([0-9]{2}).htm /laozizhu/$2/$1$2.htm [PT]

裏面將帖子編號的最後2位單獨分組出來,作爲目錄名,如果以後帖子多了,可以用最後三位來分組了。只需要再次生成帖子,然後簡單的修改這個規則即可,前臺訪問不受任何影響。


四、效果:

目前網站訪問量已經達到每天7w的獨立IP,tomcat佔用內存不超過300M,系統線程數一直在最低的線上。

五、總結

靜態化,還是針對大訪問量的最佳方案,不是僞靜態化,而是真正的靜態化。


網友方案三:

靜態化是解決減輕網站壓力,提高網站訪問速度的常用方案,但在強調交互的We2.0 時代,對靜態化提出了更高的要求,靜態不僅要能靜,還要能動,下面我通過一個項目,談談網站靜態化後的架構設計方案,同時和大家探討一下,在開源產品大行其道,言架構必稱MemberCache, Nginx,的時代,微軟技術在網站架構設計中的運用.

靜態化的設計原則和步驟

靜態化是解決減輕網站壓力,但是靜態化也會帶來一系列的問題,包括開發上複雜度的增加,維護難度的增加,運用不的當,更可能適得其反,而許多替代方案,比如頁面緩存,如果運用得當,也能起到很好的效果,所以在開始之前,必須進行詳細的考察,確定是否適合靜態化,並制定適合的靜態化方式,下面先介紹一下

l         考查讀寫比:

讀寫比,準確的說是讀寫負荷比,是否值得靜態化的最終考慮,由於一般寫入的壓力明顯大於讀出的壓力,如果寫入太頻繁,或者每次寫入消耗的資源太多,都不能達到效果,我覺得讀寫比例10:1應該是個上限.具體情況需要根據自己的業務邏輯判斷

 

l         確定頁面呈現的內容是否適合靜態化:

在設計方案時,必須詳細考慮每個原型頁面,找到頁面上展示的信息,和他的更新方式,更新時機,更新頻率,一定要注意那些不起眼的信息,他們可能左右你的設計,

比如:我們以CSDN的論壇的任意一篇帖子爲例,進行分析

上面的帖子中呈現的內容主要是這樣幾塊,帖子內容,回覆內容,發帖人回覆人的用戶信息

n         帖子內容和回覆內容在發帖時更新,發帖後用戶可以修改其內容,更新頻率高

n         用戶信息,用戶修改個人信息時可能會發生更改,用戶等級增加時也可能發生更改,比如加星,更新頻率低

n         回覆數將每次回覆後都要更改,更新頻率高

n         設計時要注意細節,如上圖中圈出來的部分,這些部分是怎麼修改的,頻率有多大,一個都不能放過.

l         確定生成方式:

在上面帖子一例中.每次更改都重新生成頁面是不可取的,一篇比回覆數多的帖子,需要的數據量是巨大的(每層樓的用戶信息,回覆內容),任何修改,都需要重新取出數據進行生成是不能允許的.一般除非你的頁面基本不用更新,或者更新開銷極小,(比如一段嵌入的廣告代碼)才能採用整體更新的方式,不然就需要我們找到合適的更新頁面局部區域的方法:

一般有下面兩個方法:

1)      正則修改法:

        比如,如果帖子中的回覆數,html代碼是這樣
        <label>回覆數<var id="replyCount">34</var></label>
        我們可以通過用下面正則來查找並替換計數
         (?<=id="replyCount">)\d{1,}

2)      頁面區域分塊:

把頁面分成很多小塊,在顯示時組裝起來,比如DotText就採用這個方法

這是一篇典型的Dottext blog頁面,其中紅色標定部分是一個獨立的文件,而***框內的是腳本動態加載,這些部分在最終顯示的時候組合起來,最終構成了一篇Blog,具體的組合方法也有多種,可以使用Include,也可以自己來實現.DotText就自己實現了一套加載機制

 

上面的兩種方法並不孤立,並可以根據需要,配合使用

 

l         確定需要動態加載的信息:

頁面上總有一些內容看起來不太適合靜態化,最典型的是一些統計結果,比如如果你在做一個圖書介紹頁面,可能就會需要展示圖書的當天綜合評分,或者書籍排名,這些內容需要用腳本進行動態加載

既然做了靜態化,就是希望減少服務器負載,動態加載的數據總是不得已而爲之,有的時候在需求允許的情況下,我們在數據在實時性和性能方面做一些妥協,比如上面帖子中的用戶星級和暱稱,從數據實時性上說,當用戶的星級增長,他發言的所有帖子都應該發生變化,所以應該用動態加載.然而其實上這些信息如果不發生變化,也無傷大雅,用戶反而能夠看到自己在多年前發帖時的級別和暱稱.

現實中的項目

X網站是大型的電影資訊,電影社區,向外提供電影相關信息服務,以及用戶社區,其中信息服務部分其中大部分頁面屬於信息呈現頁,讀取量比較大,百萬級別pv,信息主要由編輯在後臺發佈,更新較少,但其頁面上有大量的交互性的內容,比如評論,收藏列表,同時許多內容允許用戶創造,比如上傳圖片,添加註釋.交互內容的數量和交互的頻繁程度,都超過了普通的諮詢頁面,這次調整,準備將其中訪問量最大的幾塊:電影資料頁,影人資料頁,進行靜態化,如果成功,還將運用到更多的頻道,基本實現全站靜態化

 

通過對頁面設計和前一版本的分析,下面是具有挑戰性的地方.這些特點基本使用於大多數web2.0的站點,很具有典型意義

 

l         頁面生成的觸發條件複雜

一般論壇中的帖子或者blog,更新方式比較單一:主要是由回覆進行觸發還有少數的修改動作,然而該網站一個頁面上需要根據不同觸發條件就有20多個比如光二級菜單:用戶發佈圖片,刪除圖片,發佈或者刪除影片信息,發佈或者修改視頻,後臺修改電影信息,都有可能觸發

 

l         一個動作觸發生成的頁面可能很多而且相互交疊

每一個動作都會觸發一系列的生成,並且不同動作可能都會涉及同一個頁面或者區域的生成.

比如:用戶給一步電影評分,需要生成評分更多頁,評分統計更多頁,首頁右側誰還關注此影片小區域,等等.用戶收藏一個影片,也需要更新首頁右側誰還關注此影片小區域

 

l         觸發頻繁:

雖然不及某些更大規模的網站,但是由於涉及衆多用戶參與的內容,評論,收藏等等,觸發點多,發生頻度相當頻繁

 

l         頁面多,結構複雜,空間佔用大:

通常,需要生成的頁面規模是這樣粗略估算的,Rn*P,Rn爲資源數,P爲每個資源的頁面數,所謂資源,可以看做一個生成單位,其頁面數可以簡單看做發佈一個資源,就需要生成其所有相關頁面數量,比如:發佈一個blog,就需要生成一個Blog,同時還需要生成或者更新個人主頁的blog列表,算上個人主頁右側的分類文章數的小塊,也就是最多10來個頁面或者區域,但是發佈一個電影,其相關的頁面至少有50個以上,而且有的頁面還帶有分頁,一個信息比較豐富的電影,其頁面竟可以達到千個以上,空間10~20M,而且資源總數也不少,電影80000左右,電影人雖然P值較少,但是總量確有幾十萬之巨,估計靜態頁面磁盤佔用量幾百個G

 

l         向下兼容

這是一個已有系統,舊系統的框框需要突破,但又沒有時間,或者不能完全突破,比如Url,已經被收錄到搜索引擎,就不能隨便調整,還有一些地方,原本沒有爲靜態生成考慮,另一些地方又需要兼容舊的設計.

 

l         多臺前端Web

這種結構要求生成的文件可能需要分佈到多個服務器(另一個方案是放在幾臺專用的機器上,等前端來取)

 

l         任務緊迫

架構討論結束儀式六月初,離奧運開幕上線只有兩月,也就是說所有底層框架實現,頁面模板開發,調試測試,動作的整理,必須在7月底全部完成,按我原來估計,光實現這幾塊的上百個頁面模板和填充方法,也需要那麼長的時間

 

綜合考慮上述因素,架構必須要有以下幾個方面的特點

l         動作可以靈活擴展配置,某個動作對應哪些生成,應該可以配置,並且可以分組

l         文件必須有分發機制

l         分發和生成必須獨立出來,並且支持分佈式

l         各種的動作,必須轉化爲消息,發送到生成和分發服務器進行處理

l         針對同意資源頻繁動作,在變量相同的情況下能夠具有合併的能力

l         動作必須有記錄

l         儘量考慮使用已有成熟技術,節省開發時間

下面是設計的第一個架構

用戶的動作經過MSMQ[1]傳入到生成分發中心(途中綠色箭頭)進行處理,,處理中心接受到消息後,負責生成對應的頁面或者頁面區域,並將頁面分發到各個服務器,負載均衡沿用以前的架構,採用微軟的NLB[2]

 

之所以用MSMQ,就是看上了他提供的完整的消息存儲恢復機制,這樣我們能確保即使服務器down掉重啓後,消息依然能正常處理,碰巧我們cms組的同事MSMQ非常熟悉,並且真準備在另外一個項目中使用類似的架構於是一拍即合

 

頁面採用分塊存儲,這樣能保證生成時目標小,開銷小,也能重用性,然後再藉由SSI[3](shtml include)進行整合,之所以採取這樣的方案,而不採用Dottext的整合方式,是因爲如果採用Dottext的方式,就必須走IIS.Net的管道[4],而據測試,經過管道和直接返回html性能有非常大的差異,而使用ssi,在性能上是一個折中,並且可以Light HTTPd等高性能web服務器

 

模板生成方式,採用了XSLT和另外一種自定義的模板(我的同事開發的機制,很有趣理論上能把傳統模板替換的性能開銷全部消除),生成的最終產物是shtml,之所以生成shtml是爲了使用其ssi(Server Side Include)的特性,保證一定的靈活性,並實現熱點數據的分離:某些頁面上的部分可能會頻繁更新和生成,而其它地方不變,或者某個部分是所有頁面通用的(比如頁頭和頁腳),較之php下常常使用smarty,生成php文件,雖然靈活性不如php,但是性能上不相上下,還略高.

 

但是這個設計的問題是動態內容和靜態內容沒有分開,生成的html頁面,和動態頁面都放在前端服務器上,通過負載均衡訪問,也造成了分發服務器需要分發到多臺服務器,網絡IO效率較低,而且靜態內容需要的磁盤空間很大,且小文件非常多,和動態頁面混在一起不便於優化,所以第二個方案對生成的靜態內容與動態內容使用不同的服務器

 

方案二:

我們把生成的靜態文件單獨放置,可以看到,前端增加Nginx,作爲跳轉,把電影,影人資料庫的頁面轉向靜態服務器,其他的調用轉向動態服務器,這樣我們就可以單獨爲靜態服務器進行優化,比如採用更高效的服務器等等.

 

同時減少了文件分發的次數(甚至可以只分發到本機),提高生成分發的處理能力

 

更進一步,可以把圖片服務分到另外一組機器上,使用獨立的域名,比如img.xxx.com,這樣可以有效的減少帶寬

 

最終完整架構:

 

 

文件生成分發中心

下圖是文件生成分發中心的工作流程圖

 


生成服務對外只有一個輸入,就是消息,一個輸出:靜態文件,內部根據消息,從配置文件中找到對應的生成方法,取出相應的模板,進行數據填充

 

分發服務主要吧生成服務產生的文件進行分發,分發到前端的N臺服務器上,開始考慮得比較複雜,希望分發服務可以跨越協議(本地文件系統,局域網,http協議),跨越多種存儲介質(文件系統,數據庫),實際最後定下來基本是本地文件系統或者局域網傳輸

 

:上圖中文件分發的部分也可以通過定製MogileFS,來實現分佈式文件系統

 

馬後炮:

總結起來,靜態化除了對架構方面的影響,對開發和測試流程也有影響

對測試提出更高的要求:

因爲一旦上線後,某個頁面發現問題,即使是文字的修改,也需要重新生成許多頁面,所以測試人員必須非常仔細,測試周期也需要延長

 

開發人員需要掌握模板語言

需要掌握一種模板預言,無論是Xslt還是自己開發的模板語言,都需要花一定的時間掌握

 

需要給第一次生成騰出足夠時間:

如果不是新系統,那麼數據遷移和生成的過程就比較痛苦,由於頁面衆多,第一次生成的過程可能需要以天來計算,在制定上線方案是就需要考慮到這個方面

 

Nginx作爲前端的跳轉,根據其他網站的經驗,應該可以達到2-3萬併發連接,但是使用之後,常常有卡殼的情況發生,具體症狀爲在瀏覽器中訪問頁面時,連接超時,或者一直不響應,此時Nginx連接數並不高,好在還有第一套方案可以備用,讓我們有時間去解決這個問題,如果大家對這個問題有什麼心得,歡迎交流

我的聯繫方式

MSN:[email protected]

Gtalk:[email protected]

 

篇後:

在大型web開發上,我感到微軟產品結構(包括微軟開源社區的成果)在某些方面還存在一些不足:

 

高性能服務器選擇太少

Linux下可以採用Light HTTPd,Nginx等諸多服務器,這些服務器在很多方面的表現會讓Windows下唯一的選擇--IIS相形見絀

 

分佈式文件系統

微軟及其社區沒有比較著名的產品出現,Linux下有MogileFS

 

微軟架構下,文件系統選擇太少:

Linux下我們可以選擇諸如Ext3,ReiserFS,Windows環境下,NTFS是唯一的選擇,不過值得稱道的是.NTFS的效率和穩定性都相當不錯.

 

開源技術對windows版本的支持態度不積極

諸多在Linux下名聲卓著的開源產品,又懶於爲Windows提供相應的版本,或者提供的windows版本效果差強人意.使得采用微軟服務器的廠商少了很多選擇

 

現在的Web開發已經進入了各種技術大混合,大整合的時代,任何一個廠商都不可能涵蓋所有方面,在後端架構和邏輯方面.NetJava嚴謹,良好的編程風格,清晰的設計思路,較高的運行效率,以及穩定的配套服務支持,是其最大的優勢,對主要擅長微軟技術的Web工程師和架構師而言,應該增進對Linux及開源社區的瞭解,才能根據需求設計出合理的架構

 



[1] Message Queuing: A Scalable, Highly Available Load-Balancing Solution

http://msdn.microsoft.com/en-us/library/ms811052.aspx

[2] 網絡負載平衡(NLB)詳解,注意文章後給出的參考鏈接

http://blog.chinaitlab.com/user1/563173/archives/2007/132713.html

[3] 怎樣使用ssi,及其語法:

http://blog.csdn.net/dadou2007/archive/2008/06/08/2521365.aspx

Nginx下的ssimodule

http://www.nginx.cn/NginxChsHttpSsiModule

[4] asp.net的處理機制http://www.microsoft.com/china/msdn/library/webservices/asp.net/dnvs05Internals.mspx?mfr=true

html.asp.aspx運行效率比較

http://iamlibai.blogbus.com/logs/2017870.html


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章