并发访问量的统计方法

    作者:课课家教育更新于: 2017-03-31 18:48:24

    想学设计?来看看大师是如何完成作品的?

      并发用户数量,有两种常见的错误观点。一种错误观点是把并发用户数量理解为使用系统的全部用户的数量,理由是这些用户可能同时使用系统;还有一种比较接近正确的观点是把用户在线数量理解为并发用户数量。实际上,在线用户不一定会和其他用户发生并发,例如正在浏览网页的用户,对服务器是没有任何影响的。但是,用户在线数量是统计并发用户数量的主要依据之一。

      并发主要是针对服务器而言,是否并发的关键是看用户操作是否对服务器产生了影响。因此,并发用户数量的正确理解为:在同一时刻与服务器进行了交互的在线用户数量。这些用户的最大特征是和服务器产生了交互,这种交互既可以是单向的传输数据,也可以是双向的传送数据。

      并发用户数量的统计的方法目前还没有准确的公式,因为不同系统会有不同的并发特点。例如OA系统统计并发用户数量的经验公式为:使用系统用户数量*(5%~20%)。对于这个公式是没有必要拘泥于计算的结果,因为为了保证系统的扩展空间,测试时的并发用户数量要稍微大一些,除非是要测试系统能承载的最大并发用户数量。举例说明:如果一个OA系统的期望用户为1000个,只要测试出系统能支持200个并发用户就可以了。

    并发访问量的统计方法_网络系统分析与设计_网络工程师_网络规划设计师_课课家教育

         页面访问量统计,可能在上学的时候就讲过如何简单实现,例如在servletContext中保存一个页页面访问次数,然后每访问一次加1;或者每访问一次就把操作记录保存到数据库,这样的处理方式,做做实验就算了,在实际应用中这样应用对系统性能影响特别大。

      第一种方式,因为页面访问次数属于一个公共变量,在对公共变量进行修改的时候,往往需要加上同步锁;同步锁会导致访问速度明显变慢;第二种方式也一样,而且频繁访问数据库也不是一种合理的方式。

      前不久,我一个朋友要我帮他们写一个简单的页面统计代码。1、需求是保存页面访问IP、时间、以及其他一些可用的信息,以后需要保存的访问信息可扩展2、不能影响当前的访问速度3、能支持一定量的并发访问

      接到朋友给的这个需求,我想到了一下几点:1、如何筛选我们需要统计的页面;2、需要将访问和统计分离,不在访问的线程中来保存访问信息,另外起一个线程将访问信息保存到数据库;3、可以使用一个公共的队列来保存这个访问信息;4、可以批量的保存一定量的访问信息

      解决方案:

      1、针对第一个问题,我给出了两个方法。1、使用一个集合保存所有的需要统计的页面,然后再在Filter里面判断当前请求是否在在统计之列;2、在JSP页面中引入一段公共代码,在代码中使用类似这种CounterUtils.addCounter(request);这种方法有一个好处,维护需要统计的页面比较方便,而且感觉上更加高效,不需要Filter拦截。但是朋友坚决用第一种方式,也是没有办法。

      2、每访问一次,我们将需要保存的信息保存成一个对象,然后放入到队列当中,然后另起一个线程定期进行保存。

      于是我就写了一个简单的demo给朋友,没过多久,就被退货了。经过测试并发还没到200就突然不保存数据库了,访问也变得特慢,最后竟然堆内存溢出了。

      没有办法只能再在本机用loadRunner进行测试,同时通过jconsolejava自带工具来检测内存变化情况。测试情况与朋友说的一样,刚开始能够正常运行,当并发达到一定量,就开始出现保存缓慢,最后不知道怎么整的保存线程不再运行,就这样队列越来越大,自然堆内存大到溢出了。

      从上面的情况也可以想到,一个队列有可能无法支持这么大的并发访问,于是就想使用多个队列来进行保存,使用类似分表分库的方法,将不同请求分配到不同的队列中去,于是就变成了下面这种方式:

      部分代码如下:

      1、初始化生成linkedList集合列表

      复制代码

      /**

      *根据urls生成队列数组

      *@return

      */

      privatestaticLinkedList[]initUris(){

      Digesterdigester=newDigester();

      Stringpath=null;

      try{

      path=CounterUtils.class.getClassLoader().getResource("urls.XML").toURI().getPath();

      }catch(URISyntaxExceptione1){

      e1.printStackTrace();

      }

      UriRuleSetruleSet=newUriRuleSet();

      ruleSet.addRuleInstances(digester);

      try{

      //uri集合

      uris=digester.parse(newFile(path));

      //hashCode基数

      BaseHash=uris!=null?uris.size()/3:1;

      LinkedList[]listArr=newLinkedList[BaseHash];

      for(inti=0;i

      listArr[i]=newLinkedList();

      returnlistArr;

      }catch(Exceptione){

      e.printStackTrace();

      returnnull;

      }

      }

      复制代码

      2、将请求封装成统计所需对象

      复制代码

      /**

      *添加一次请求统计

      *@paramrequest

      */

      publicstaticvoidaddCounter(HttpServletRequestrequest){

      //封装用户统计的request,并且用hash算法分布到不同的队列当中

      RequestStcstc=newRequestStc();

      stc.setIp(request.getRemoteAddr());

      stc.setUri(request.getRequestURI());

      stc.setNow(Calendar.getInstance().getTime());

      buffers[request.hashCode()%BaseHash].push(stc);

      }

      复制代码

      3、轮询LinkedList队列集合

      复制代码

      /**

      *执行统计

      */

      privatevoidprocessCount(){

      try{

      //轮询队列

      while(true){

      Thread.sleep(Sleep_MS);

      if(buffers==null){

      break;

      }

      Threadth=null;

      for(inti=0,len=buffers.length;i

      LinkedListstcList=null;

      if(buffers[i].size()>=Execute_Base){

      //复制buffers数组元素,然后清空,启动一个线程对队列进行保存

      synchronized(buffers[i]){

      stcList=(LinkedList)buffers[i].clone();

      buffers[i].clear();

      }

      th=newThread(newExecuteThread(stcList));

      th.start();

      }

      }

      }

      }catch(Exceptione){

      e.printStackTrace();

      }

      }

      复制代码

      4、ExecuteThread线程用于批量保存访问日志

      //批量保存数据库

      这个分两种方式1、保存详细的访问记录,例如,某某时候某个IP对某个页面进行了访问2、只保存某天每个页面访问的总数

      对于第一种方式,使用批量保存即可。对于第二种方式可以使用一个hashTable来维护所有页面某个时间段内的对应页面的访问增量,具体维护方式可以如下:

      将reqestStc信息维护进HashTable当中,其中维护过程省略;再写一个定时器,定时将HashTable中的增量数据flush到数据库中;

    //批量保存数据库    这个分两种方式1、保存详细的访问记录,例如,某某时候某个IP对某个页面进行了访问2、只保存某天每个页面访问的总数    对于第一种方式,使用批量保存即可。对于第二种方式可以使用一个hashTable来维护所有页面某个时间段内的对应页面的访问增量,具体维护方式可以如下:    将reqestStc信息维护进HashTable当中,其中维护过程省略;再写一个定时器,定时将HashTable中的增量数据flush到数据库中;

      复制代码

      /**

      *将数据刷出,保存到数据库

      */

      publicvoidflush(){

      HashtablesaveTable=null;

      //这个地方为什么不用同步锁,因为HashTable本身就是线程安全的,clone方法上加了同步锁

      saveTable=(Hashtable)counterTables.clone();

      counterTables.clear();

      if(saveTable.isEmpty())

      return;

      for(Entryent:saveTable.entrySet()){

      Stringurl=ent.getKey();

      RequestParamparam=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端使用异步统计代码,这样做的好处是不影响页面的加载速度,代码如下图,具体实现没有去深究

      首先LR上设置的虚拟用户指的是在系统中运行的用户数,如果你在脚本中设置了集合点,那么脚本运行到那个点上时,就开始做你提的那个并发操作了

      比如说:我们有一个这样的场景,我系统在线用户是150个,但是同时操作某一个事物(比如说登陆操作)的人是20个

      那么场景怎么设计了?在Controller中设置150个虚拟用户执行这个脚本,然后登陆操作之前放一个集合点,然后设置集合点的策略(20个用户到达时即执行集合点)

    首先LR上设置的虚拟用户指的是在系统中运行的用户数,如果你在脚本中设置了集合点,那么脚本运行到那个点上时,就开始做你提的那个并发操作了    比如说:我们有一个这样的场景,我系统在线用户是150个,但是同时操作某一个事物(比如说登陆操作)的人是20个    那么场景怎么设计了?在Controller中设置150个虚拟用户执行这个脚本,然后登陆操作之前放一个集合点,然后设置集合点的策略(20个用户到达时即执行集合点)

      并发用户实际从应用角度来分析可以分为如下几种

      并发用户:是同时执行一个操作的用户,或者是同时执行脚本的用户,这个并发在设置不同场景的时候并发的情况是不一样的,在实际的测试中需要根据具体的需求进行设计;

      集合点:如果脚本中设置集合点,可以达到绝对的并发,但是集合点并不是并发用户的代名词,设置集合点和不设置集合点,需要看你站在什么角度上来看待并发,使整个服务器,还是提供服务的一个事务;

      虚拟用户,就是LR通过某种仿真机制虚拟出来的用来仿真用户行为的用户,一般以线程或者进程来实现用户的仿真!

      更多详细内容,尽在课课家教育,我们期待您的咨询!

课课家教育

未登录