2014-10-31 102 views
1

我正在計算頁面上查看用戶的數量。基本上,當用戶查看URL localhost:8080/itemDetail.do?itemId=1時,該頁面將顯示有多少用戶同時在該頁面上查看。計數當前用戶查看頁面

解決辦法

當用戶在瀏覽特定網頁,該網頁會繼續發送Ajax請求每隔1秒回服務器,然後服務器將使用Map<String, Map<String, Integer>>(都使用ConcurrentHashMap用於初始化)至包含itemId(要知道哪個頁面被查看)和IPtimeout數(在Map的內部)。每次獲取請求時,計數將增加1.在請求過程中會有一個線程觸發,以每2秒減少超時計數。如果最後,超時計數等於0,則應用程序將認爲用戶停止查看該頁面,然後線程將刪除該條目,從而減少用戶數量。1.此方法的一個小問題是自超時如果用戶打開頁面足夠長時間並關閉頁面,則應用程序需要一段時間才能知道該用戶已經離開該頁面,因爲此刻的超時數量相當大

實施

// Controller 

    @RequestMapping(value = AppConstants.ACTION_NUMBER_VIEWING, method = GET) 
    @ResponseBody 
    public int userItemViewing(HttpServletRequest request, @RequestParam(value = "itemId") String itemId) { 
     try { 
      return eventService.countUserOnline(request, itemId); 
     } catch (CAServiceException e) { 
      e.printStackTrace(); 
      LOG.error("========== Error when counting number of online user ==========" + e.getMessage()); 
     } 

     return 0; 
    } 

// Service 

private static Map<String, Map<String, Integer>> numberOfCurrentViews; 
private Thread countDownOnlineThread; 

class OnlineCountingDownRunnable implements Runnable { 

    private List<String> timeoutList = new ArrayList<String>(); 

    private void cleanTimeoutIps() { 
     for (String itemId : numberOfCurrentViews.keySet()) { 
      Map<String, Integer> currentIps = numberOfCurrentViews.get(itemId); 

      for(String ip : timeoutList){ 
       currentIps.remove(ip); 
      } 
     } 
    } 

    @Override 
    public void run() { 
     try { 
      for (String itemId : numberOfCurrentViews.keySet()) { 
       Map<String, Integer> currentIps = numberOfCurrentViews.get(itemId); 

       for(String ip : currentIps.keySet()){ 
        Integer timeout = new Integer(currentIps.get(ip).intValue() - 1); 

        if (timeout == 0) { 
         timeoutList.add(ip); 
        } 

        currentIps.put(ip, timeout); 
       } 
      } 

      cleanTimeoutIps(); 

      // counting down time must be double increasing time 
      Thread.sleep(2000); 
     } catch (InterruptedException e) { 
      e.printStackTrace(); 
      LOG.error("---------------- Thread error in counting down online user: " + e.getMessage()); 
     } 

    } 
} 

public int countUserOnline(HttpServletRequest request, String itemId) throws CAServiceException { 
    // create a count down timer to detect if the user does not view the page anymore 
    String ip = request.getRemoteAddr(); 

    // init counting down online user map 
    if (numberOfCurrentViews == null) { 
     numberOfCurrentViews = new ConcurrentHashMap<String, Map<String, Integer>>(); 
    } 

    // start thread to check user online 
    if (countDownOnlineThread == null) { 
     countDownOnlineThread = new Thread(new OnlineCountingDownRunnable()); 
     countDownOnlineThread.start(); 
    } 

    LOG.debug("---------------- Requested IP: " + ip); 
    if (ip == null || ip.isEmpty()) { 
     throw new CAServiceException("======= Cannot detect Ip of the client ======="); 
    } 

    if (numberOfCurrentViews.get(itemId) != null) { 
     Map<String, Integer> userView = numberOfCurrentViews.get(itemId); 

     if (userView.get(ip) != null) { 
      userView.put(ip, userView.get(ip).intValue() + 1); 
     } else { 
      userView.put(ip, 1); 
     } 

     numberOfCurrentViews.put(itemId, userView); 
    } else { 
     Map<String, Integer> ips = new ConcurrentHashMap<String, Integer>(); 
     ips.put(ip, 1); 

     numberOfCurrentViews.put(itemId, ips); 
    } 

    LOG.debug(String.format(
     "============= %s is seeing there is %s users viewing item %s =============", 
     ip, numberOfCurrentViews.get(itemId).size(), itemId 
    )); 

    return numberOfCurrentViews.get(itemId).size(); 
} 

問題

我不知道如何測試這個功能,因爲它需要多個IP地址來查看頁面。我曾試圖建立的JMeter並設置IP欺騙這樣link,但沒有成功,所以我做了一個小的模擬測試像這樣

@Test 
public void testCountUserOnline() throws Exception { 

    List<HttpServletRequest> requests = new ArrayList<HttpServletRequest>(); 

    for (int i = 0; i < 10; i ++) { 
     HttpServletRequest request = Mockito.mock(HttpServletRequest.class); 
     Mockito.when(request.getRemoteAddr()).thenReturn(String.format("192.168.1.%s", i)); 
     requests.add(request); 
    } 

    List<Thread> threads = new ArrayList<Thread>(); 
    for (int i = 0; i < 10; i ++) { 
     Thread thread = new Thread(new RequestRunnable(requests.get(i))); 
     threads.add(thread); 
     thread.start(); 
    } 

    for (Thread thread : threads) { 
     thread.join(); 
    } 
} 

class RequestRunnable implements Runnable { 
    private HttpServletRequest request; 

    public RequestRunnable(HttpServletRequest request) { 
     this.request = request; 
    } 

    public void run() { 
     try { 
      int i = 0; 
      while (i < 10) { 
       eventService.countUserOnline(request, "1"); 
       i++; 
       System.out.println(i); 
       Thread.sleep(1000); 
      } 

     } catch (CAServiceException e) { 
      e.printStackTrace(); 
     } catch (InterruptedException e) { 
      e.printStackTrace(); 
     } 
    } 
} 

但再次查看日誌,我沒有那麼自信了實施

此外,這是人類用來計算頁面上查看用戶數量的正常方式嗎?我只是想確保我不會錯過任何事情,以防這部分有任何捷徑。我正在使用Spring MVC 3.x。我需要支持IE 9以及:(((,所以Web Socket不能被廣泛使用

回答

0

在第一時間實施這個時候我很愚蠢嗎? 。我不應該使用基於線程的計數器來使問題變得如此複雜,相反,我真正需要的僅僅是在每個請求上保存timestamp,然後創建一個線程來比較當前時間和最後一個時間戳如果時間差超過一個超時數(本例中爲10秒),那麼用戶已經離開頁面,然後,我只需要t o從Map中刪除該條目。

// make it as static, since this needs to be singleton 
private static Map<String, Map<String, Date>> numberOfCurrentViews; 

// make it as static, since this needs to be singleton 
private static Thread cleaningOffLineUserThread; 

class OnlineCountingDownRunnable implements Runnable { 

    @Override 
    public void run() { 
     try { 
      while(true) { 
       for (String itemId : numberOfCurrentViews.keySet()) { 
        Map<String, Date> userView = numberOfCurrentViews.get(itemId); 

        Iterator<Map.Entry<String, Date>> iterator = userView.entrySet().iterator(); 
        while (iterator.hasNext()) { 
         Map.Entry<String, Date> views = iterator.next(); 

         Date lastViewTime = views.getValue(); 
         Date currentTime = new Date(); 
         long seconds = (currentTime.getTime() - lastViewTime.getTime())/1000; 

         if (seconds > TIMEOUT) { 
          iterator.remove(); 
         } 
        } 
       } 

       // make the cleaning worker running every 2 seconds 
       Thread.sleep(2000); 
      } 
     } catch (InterruptedException e) { 
      e.printStackTrace(); 
      LOG.error("---------------- Thread error in counting down online user: " + e.getMessage()); 
     } 

    } 
} 

public int countUserOnline(HttpServletRequest request, String itemId) throws CAServiceException { 
    // create a count down timer to detect if the user does not view the page anymore 
    String ip = request.getRemoteAddr(); 

    // init counting down online user map 
    if (numberOfCurrentViews == null) { 
     numberOfCurrentViews = new ConcurrentHashMap<String, Map<String, Date>>(); 
    } 

    // start thread to check user online 
    if (cleaningOffLineUserThread == null) { 
     cleaningOffLineUserThread = new Thread(new OnlineCountingDownRunnable()); 
     cleaningOffLineUserThread.start(); 
    } 

    LOG.debug("---------------- Requested IP: " + ip); 
    if (ip == null || ip.isEmpty()) { 
     throw new CAServiceException("======= Cannot detect Ip of the client ======="); 
    } 

    Map<String, Date> userView; 

    if (numberOfCurrentViews.get(itemId) != null) { 
     userView = numberOfCurrentViews.get(itemId); 
    } else { 
     userView = new ConcurrentHashMap<String, Date>(); 
    } 

    userView.put(ip, new Date()); 
    numberOfCurrentViews.put(itemId, userView); 

    LOG.debug(String.format(
     "============= %s is seeing there is %s users, %s viewing item %s =============", 
     ip, numberOfCurrentViews.get(itemId).size(), 
     String.valueOf(numberOfCurrentViews.get(itemId).keySet()), itemId 
    )); 

    return numberOfCurrentViews.get(itemId).size(); 
} 

我用模擬測試,測試功能,它很好地反映了通過日誌的結果

@Test 
public void testCountUserOnline() throws Exception { 

    List<HttpServletRequest> requests = new ArrayList<HttpServletRequest>(); 

    HttpServletRequest request1 = Mockito.mock(HttpServletRequest.class); 
    HttpServletRequest request2 = Mockito.mock(HttpServletRequest.class); 
    HttpServletRequest request3 = Mockito.mock(HttpServletRequest.class); 

    Mockito.when(request1.getRemoteAddr()).thenReturn(String.format("192.168.1.%s", 1)); 
    Mockito.when(request2.getRemoteAddr()).thenReturn(String.format("192.168.1.%s", 2)); 
    Mockito.when(request3.getRemoteAddr()).thenReturn(String.format("192.168.1.%s", 3)); 

    requests.add(request1); 
    requests.add(request2); 
    requests.add(request3); 

    List<Thread> threads = new ArrayList<Thread>(); 

    Thread thread1 = new Thread(new RequestRunnable(request1, 50, "1", "2")); 
    Thread thread2 = new Thread(new RequestRunnable(request2, 20, "1", "3")); 
    Thread thread3 = new Thread(new RequestRunnable(request3, 10, "3", "1")); 

    threads.add(thread1); 
    threads.add(thread2); 
    threads.add(thread3); 


    for (Thread thread : threads) { 
     thread.start(); 
    } 

    for (Thread thread : threads) { 
     thread.join(); 
    } 
} 

class RequestRunnable implements Runnable { 
    private HttpServletRequest request; 
    private String [] pageSet; 
    private int duration; 


    public RequestRunnable(HttpServletRequest request, int duration, String ... pageSet) { 
     this.request = request; 
     this.pageSet = pageSet; 
     this.duration = duration; 
    } 

    public void run() { 
     try { 
      int i = 0; 
      while(i < duration) { 
       i ++; 
       for (String page : pageSet) { 
        eventService.countUserOnline(request, page); 
       } 

       LOG.debug("Second " + i); 
       Thread.sleep(1000); 
      } 

     } catch (CAServiceException e) { 
      e.printStackTrace(); 
     } catch (InterruptedException e) { 
      e.printStackTrace(); 
     } 
    } 
} 
相關問題