2014-10-31 2 views
1

Я работаю над подсчетом количества пользователей на странице. В основном, когда пользователь просматривает URL-адрес localhost:8080/itemDetail.do?itemId=1, страница будет показывать, сколько пользователей просматривает эту страницу одновременно.Подсчет текущих пользователей, просматривающих страницу

Solution подход

Когда пользователь просматривает определенную страницу, эта страница будет продолжать посылать запрос AJAX каждый 1 второй обратно на сервер, то сервер будет использовать Map<String, Map<String, Integer>> (оба используют ConcurrentHashMap для инициализации) в содержат itemId (чтобы узнать, какая страница просматривается) и IP и timeout счет (в помещении 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(); 
     } 
    } 
} 
Смежные вопросы