程序员人生 网站导航

视频学习网站学习时长实时记录-性能优化实践

栏目:互联网时间:2014-11-04 08:58:51

1、     利用场景描写

系统主要为教师在线学习提供服务,其中视频学习网站支持教师在线视频学习,教师在视频学习进程中其学习进程会被记录下来。每一个专题下对应多个教学视频,每一个教学视频时长不尽1致。现在的记录规则是:教师在看视频的时候,视频所在的页面每分钟提交1次要求,记录该视频已学习时长,并将该记录更新到http://www.wfuyu.com/db/。

目前http://www.wfuyu.com/db/中有8266个教师用户,在***政策下,极有可能在某1段时间内大部份教师用户同时在线看视频。这意味着在极端情况下,每分钟可能会提交6000+个要求,对利用http://www.wfuyu.com/server/带来了很大的压力。另外,我们在更新学习时长记录前,会将其与已学时长(需要实时的查询)相比较,如果当条件交的时长比已学时长大则更新,否则不更新,频繁的查询与更新http://www.wfuyu.com/db/严重下降了系统的响应速度。对学习时长的记录进程进行优化燃眉之急。

2、     硬软件情况

硬件:1台http://www.wfuyu.com/server/,核心4核,内存

软件:Windows Server 2008 64位操作系统, Tomcat 7, jdk1.6,http://www.wfuyu.com/access/5.5

3、     优化进程

1》第1阶段

分析:首先想到的是,这个进程主要的压力在于大量的http://www.wfuyu.com/server/要求和频繁的http://www.wfuyu.com/db/连接,那末,合并要求,利用缓存机制应当可以解决问题。

解决方案:将用户的要求置入缓存,定时集中处理,合并更新操作。

具体做法:利用线程安全的包装后的HashMap作为用户要求缓存。

public static Map<LearnTime, Integer> map = Collections.synchronizedMap(new LinkedHashMap<LearnTime,Integer>());

//用户要求来了以后将要求置入缓存

protected static void put(String userName, String videoId, int topicId, int totalTime, int learnTime) {

   LearnTime learn = new LearnTime(userName, videoId, topicId, totalTime);

   //可以保证缓存中的时间比看过的时长要大

   if(map.containsKey(learn)) {

      if(learnTime > map.get(learn)) {

         map.put(learn, learnTime);

      }

   }else {

//每一个"用户-视频"在1个缓存时间内只查1次

      int learnedTime = dao.getLearnedTime(userName, videoId, topicId);已学习的时间

      if(learnTime > learnedTime) {

         map.put(learn, learnTime);

      }else{

         map.put(learn, learnedTime);

      }     }

}

每隔1个小时,对缓存数据做1次处理,将学习记录更新到http://www.wfuyu.com/db/:遍历HashMap数据项,生成sql语句,拼接到1起,然后在1个连接以内处理完。

Iterator<LearnTime> i = s.iterator();

StringBuilder str = new StringBuilder();

while (i.hasNext()) {

         learn = i.next();

         userName = learn.getUserName();

         videoId = learn.getVideoId();

         topicId = learn.getTopicId();

         learnTime = map.get(learn);

      totalTime = learn.getTotalTime();

       if(learnTime < totalTime){

            //分数保存两位小数

            String sScore = new DecimalFormat("#.00").format(0.5*learnTime/totalTime);

            Double score = Double.valueOf(sScore);     

            str.append(sql语句);

         }else if(learnTime >= totalTime) {         

            learnedTime = dao.getLearnedTime(userName, videoId, topicId);已学习的时间

            if(learnedTime != totalTime) {

                str.append("sql语句");

         }

        }

}

这样处理以后,系统性能得到了1定的改良,但是http://www.wfuyu.com/db/连接的压力还是挺大的,从程序代码中可以看到,在往缓存中添加学习记录和更新之前,都有连接http://www.wfuyu.com/db/进行查询的操作,对http://www.wfuyu.com/db/连接也有较大的消耗。另外,用户端对http://www.wfuyu.com/server/的大量要求并没有得到较好的解决。因此还需要继续优化。

2》第2阶段

分析:前面已分析了依然存在的两个问题,其1是利用http://www.wfuyu.com/server/的大量并发要求,第2是http://www.wfuyu.com/db/的频繁访问。还有1个没有提到过的,当缓存正在被读取时,往缓存里面写数据是要被阻塞的,如果缓存遍历和更新处理过慢,则会致使长时间的要求阻塞。

解决方案:对利用http://www.wfuyu.com/server/的要求,由于每一个要求做的事情仅仅是做1个查询,然后向缓存里面更新数据,这个进程是非常短的,我们可以利用Tomcat配置1个较大的线程池,以响应如此多的要求;对http://www.wfuyu.com/db/的频繁访问,不难发现,其实更新进程已合并了,只是查询已学时长还是单个做的,查询仅仅为了校验是不是更新,若把是不是更新交给http://www.wfuyu.com/db/去决定,那末所有的查询要求都会合并到更新中去,这样这个问题就解决了;缓存的遍历快慢由缓存的大小决定,需要选择适合的缓存周期;更新的处理可以剥离出来,在遍历缓存的同时,将数据取出,另外开1个线程来处理更新操作,让HashMap的锁释放,减少阻塞时长,遍历并生成sql语句进程应当可以控制在几秒内,影响不大。

具体做法:

1.将要求置入缓存

public static Map<LearnTime, Integer> map = Collections.synchronizedMap(new LinkedHashMap<LearnTime,Integer>());

//用户要求来了以后将要求置入缓存

protected static void put(String userName, String videoId, int topicId, int totalTime, int learnTime) {

   LearnTime learn = new LearnTime(userName, videoId, topicId, totalTime);

   //可以保证缓存中的时间比看过的时长要大

   if(map.containsKey(learn)) {

      if(learnTime > map.get(learn)) {

         map.put(learn, learnTime);

      }

   }else {

        //直接放入缓存,判断是不是更新在http://www.wfuyu.com/db/处理

        map.put(learn, learnTime);

}

}

2.更新进程剥离

static class UpdateTask implements Runnable{

   private String sql;     

   public UpdateTask(String sql) {

      this.sql = sql;

   }    

   @Override

   public void run() {

      dao.updateLearnTime(sql);

   }    

}

3.缓存周期清算

count++;         //每处理1次更新,计数加1

                   //到达缓存周期时长,将缓存清算掉,计数清零

if(count % clearCycle == 0) {

      map.clear();

      count = 0;

}

4.校验进程都在http://www.wfuyu.com/db/做,即更新语句作限制,略。

5.利用http://www.wfuyu.com/server/Tomcat连接池配置。

1)下载tcnative⑴.dll,以支持APR要求

2)将dll文件复制到windows/system32下面,或将其加入path

3)配置Tomcat下的server.xml

<Executor name="tomcatThreadPool"  

        namePrefix="tomcatThreadPool-"  

        maxThreads="1000"  

        maxIdleTime="300000" 

        minSpareThreads="100"

   prestartminSpareThreads="true"

/>

<Connector executor="tomcatThreadPool"  URIEncoding="utf⑻" port="80" protocol="org.apache.coyote.http11.Http11AprProtocol" 

      connectionTimeout="20000" 

      redirectPort="8453" 

     maxThreads="1000"         

       minSpareThreads="200"

     acceptCount="1000"

/>

4、     优化总结

该实际场景的优化主要在4个方面:1、合并http://www.wfuyu.com/db/连接要求;2、增加利用http://www.wfuyu.com/server/响应线程数;3、实际更新处理与缓存周期剥离以减少阻塞;4、权衡缓存大小和用户使用习惯,公道设置缓存清算周期。

另外,该场景最初的瓶颈在于频繁的http://www.wfuyu.com/db/连接,而正好可以通过合并连接来优化。在极端情况下,没法合并连接呢?这就必须要在http://www.wfuyu.com/db/访问层利用连接池进行优化,在现有架构下,还不知道如何配置http://www.wfuyu.com/db/连接池,这是1个需要摸索的重要优化点。

5、     测试数据

实际场景下,假定8000+用户同时在线看视频,1分钟有8000+次要求,平均每秒140次。每一个要求由1个线程来履行,在测试时,摹拟这个进程。

测试用例:每100毫秒提交20个要求,也就是1秒200个要求,每一个要求开启1个新的线程履行,共提交2000000要求,测试时长10000秒。以3000用户(由于不好实际摹拟,采取将http://www.wfuyu.com/db/中数据提取出来的方式,用随机提交要求的方式在程序中测试)在线看视频,每3分钟处理1次更新,每8个处理周期清算1次缓存。测试结果毫无压力。可能在实际场景中,需要遍历的缓存项会多1些,但是根据经验,http://www.wfuyu.com/server/履行更新语句可以到达每秒3000条以上,况且其实不会对缓存阻塞,因此完全满足性能需求。

把要求频率改成每100毫秒提交50个要求,也就是每秒500个要求,其他条件不变,测试时长4000秒。测试结果也很理想,没有多大改变,也就是说支持3万用户的时长记录要求木有问题。http://www.wfuyu.com/server/资源有浪费啊!

测试代码以下:

Random random = new Random();    

      LearnTime learn;     

      int index = 0;

      int learnTime = 0;

      for(int i = 0;i < 2000000;i++){

         index = random.nextInt(3000);

         learn = learns[index];

         learnTime = random.nextInt(learn.getTotalTime()+1);

         Thread thread = new Thread(new PutTask(learn, learnTime));

         thread.start();

         if(i%50 == 0){

            Thread.sleep(100);

         }

       }

//要求线程摹拟以下:

   static class PutTaskimplements Runnable{     

      private LearnTimelearn;

      privateintlearnTime;     

      public PutTask(LearnTime learn,int learnTime){

         this.learn = learn;

         this.learnTime = learnTime;

      }

      publicvoid run() {

         LearnTimeHandleServlet.put(learn,learnTime);

      }    

}

 

------分隔线----------------------------
------分隔线----------------------------

最新技术推荐