2014-10-31 103 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(); 
     } 
    } 
} 
相关问题