程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> 並發頁面訪問量統計簡單實現,並發頁面訪問量

並發頁面訪問量統計簡單實現,並發頁面訪問量

編輯:JAVA綜合教程

並發頁面訪問量統計簡單實現,並發頁面訪問量


頁面訪問量統計,可能在上學的時候就講過如何簡單實現,例如在servletContext中保存一個頁頁面訪問次數,然後每訪問一次加1;或者每訪問一次就把操作記錄保存到數據庫,這樣的處理方式,做做實驗就算了,在實際應用中這樣應用對系統性能影響特別大。

  第一種方式,因為頁面訪問次數屬於一個公共變量,在對公共變量進行修改的時候,往往需要加上同步鎖;同步鎖會導致訪問速度明顯變慢;第二種方式也一樣,而且頻繁訪問數據庫也不是一種合理的方式。

  前不久,我一個朋友要我幫他們寫一個簡單的頁面統計代碼。1、需求是保存頁面訪問IP、時間、以及其他一些可用的信息,以後需要保存的訪問信息可擴展 2、不能影響當前的訪問速度 3、能支持一定量的並發訪問
  接到朋友給的這個需求,我想到了一下幾點:1、如何篩選我們需要統計的頁面;2、需要將訪問和統計分離,不在訪問的線程中來保存訪問信息,另外起一個線程將訪問信息保存到數據庫;3、可以使用一個公共的隊列來保存這個訪問信息;4、可以批量的保存一定量的訪問信息
  

解決方案:

  1、針對第一個問題,我給出了兩個方法。1、使用一個集合保存所有的需要統計的頁面,然後再在Filter裡面判斷當前請求是否在在統計之列;2、在JSP頁面中引入一段公共代碼,在代碼中使用類似這種CounterUtils.addCounter(request);這種方法有一個好處,維護需要統計的頁面比較方便,而且感覺上更加高效,不需要Filter攔截。但是朋友堅決用第一種方式,也是沒有辦法。

2、每訪問一次,我們將需要保存的信息保存成一個對象,然後放入到隊列當中,然後另起一個線程定期進行保存。

  於是我就寫了一個簡單的demo給朋友,沒過多久,就被退貨了。經過測試並發還沒到200就突然不保存數據庫了,訪問也變得特慢,最後竟然堆內存溢出了。
沒有辦法只能再在本機用loadRunner進行測試,同時通過jconsole java自帶工具來檢測內存變化情況。測試情況與朋友說的一樣,剛開始能夠正常運行,當並發達到一定量,就開始出現保存緩慢,最後不知道怎麼整的保存線程不再運行,就這樣隊列越來越大,自然堆內存大到溢出了。

  
  從上面的情況也可以想到,一個隊列有可能無法支持這麼大的並發訪問,於是就想使用多個隊列來進行保存,使用類似分表分庫的方法,將不同請求分配到不同的隊列中去,於是就變成了下面這種方式:

  
部分代碼如下:
1、初始化生成linkedList集合列表

復制代碼
    /**
     * 根據urls生成隊列數組
     * @return
     */
    private static LinkedList<RequestStc>[] initUris() {
        Digester digester = new Digester();
        String path = null;
        try {
            path = CounterUtils.class.getClassLoader().getResource("urls.xml").toURI().getPath();
        } catch (URISyntaxException e1) {
            e1.printStackTrace();
        }
        
        UriRuleSet ruleSet = new UriRuleSet();
        ruleSet.addRuleInstances(digester);
        try {
            // uri集合
            uris = digester.parse(new File(path));
            // hashCode基數
            BaseHash = uris!=null?uris.size()/3:1;
            
            LinkedList<RequestStc>[] listArr = new LinkedList[BaseHash];
            
            for(int i=0; i<listArr.length; i++)
                listArr[i] = new LinkedList<RequestStc>();
            return listArr;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
復制代碼

 

2、將請求封裝成統計所需對象

復制代碼
    /**
     * 添加一次請求統計
     * @param request
     */
    public static void addCounter(HttpServletRequest request) {
        
        // 封裝用戶統計的request,並且用hash算法分布到不同的隊列當中
        RequestStc stc = new RequestStc();
        stc.setIp(request.getRemoteAddr());
        stc.setUri(request.getRequestURI());
        stc.setNow(Calendar.getInstance().getTime());

        buffers[request.hashCode()%BaseHash].push(stc);
    }
復制代碼

 

3、輪詢LinkedList隊列集合

復制代碼
    /**
     * 執行統計
     */
    private void processCount() {
        try {
            // 輪詢隊列
            while(true) {
                Thread.sleep(Sleep_MS);
                if(buffers==null) {
                    break;
                }
                
                Thread th = null;
                for(int i=0,len = buffers.length; i<len; i++) {
                    LinkedList<RequestStc> stcList = null;
                    if(buffers[i].size()>=Execute_Base) {
                        // 復制buffers數組元素,然後清空,啟動一個線程對隊列進行保存
                        synchronized (buffers[i]) {
                            stcList = (LinkedList<RequestStc>) buffers[i].clone();
                            buffers[i].clear();
                        }
                        th = new Thread(new ExecuteThread(stcList));
                        th.start();
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        
    }        
復制代碼

 

4、ExecuteThread線程用於批量保存訪問日志

  // 批量保存數據庫

   這個分兩種方式1、保存詳細的訪問記錄,例如,某某時候某個IP對某個頁面進行了訪問  2、只保存某天每個頁面訪問的總數

  對於第一種方式,使用批量保存即可。對於第二種方式可以使用一個hashTable來維護所有頁面某個時間段內的對應頁面的訪問增量,具體維護方式可以如下:

  將reqestStc信息維護進HashTable當中,其中維護過程省略;再寫一個定時器,定時將HashTable中的增量數據flush到數據庫中;

  

復制代碼
  /**
     * 將數據刷出,保存到數據庫
     */
    public void flush() {
        Hashtable<String, RequestParam> saveTable = null;
     // 這個地方為什麼不用同步鎖,因為HashTable本身就是線程安全的,clone方法上加了同步鎖 saveTable = (Hashtable) counterTables.clone(); counterTables.clear(); if(saveTable.isEmpty()) return; for(Entry<String, RequestParam> ent:saveTable.entrySet()) { String url = ent.getKey(); RequestParam param = ent.getValue(); System.out.println("url:" + url); System.out.println("pv:" + param.getPv()); requestCount += param.getPv(); } System.out.println("ip:" + ips.size()); System.out.println("訪問總數為:" + requestCount); }
復制代碼

 

 

 

 

5、如何攔截需要統計的訪問請求
方法一:通過判斷uri是否在需要統計之列
方法二:在需要統計的jsp中添加JAVA代碼例如:CounterUtils.addCounter(request);

方法三:JS異步訪問,類似百度統計的這種方式,這種方式有個好處,就是不影響頁面加載速度

經過修改,在loadRunner和tomcat的測試下,基本上能夠達到tomcat最大的並發以上用戶,並且占用少量資源。

還有一種方式就是百度統計那種方式,在js端使用異步統計代碼,這樣做的好處是不影響頁面的加載速度,代碼如下圖,具體實現沒有去深究:

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved