zoukankan      html  css  js  c++  java
  • 要想让计划任务“坑”死你的系统,你一定要这样写

    别拿计划任务不当干粮,小心分分钟干掉你的系统,想看看怎么样狗带最惨,请参考下面的手册

    获取大量的数据逐条处理

    许多计划任务是用于统计或者批处理的,经常需要遍历某个列表

    比如:

    //查找所有将要过期的用户,逐个发送邮件

           Iterable<UserEntity> users = userDAO.findExpireUser();
           for (UserEntity user : users) {

          //发送逻辑
                    Thread.sleep(5000);
               }
           }

    上面的这个例子里,coder可能认为同时过期的用户可能不多,不过你忽略的一个情况,如果系统里的用户是同一天批量创建的,他们中的大多数会在同一天过期,于是就可能出现几万,几十万用户都需要发邮件的壮观场面。。。。上面这个例子里,一次来了两百万。

    正确做法:

    取数据要分页取,分页处理,处理了立即释放,除非你非常确定不会增长的列表,都不许用listAllgetAll,因为生产环境的行为永远出乎你的意料。

    Iterable<UserEntity> users = userDAO.findExpireUserbyPage(i,pagesize);
           for (UserEntity user : users) {

           //发送逻辑
                    Thread.sleep(5000);
               }
           }

    从团队方面来讲,凡是DAO里面有listAllgetAll,无条件search的都是问题高发区,团队应该限制对这些方法的调用。

     

     

    在循环里Sleep一会儿

    还是上面的例子,每次发送邮件等待5秒,oh my god,双杀来了。假如有200w用户同时过期,还不算发送逻辑本身的时间,那是一五二十,二五一十,对不起我的手指头不够数,大概是115.7天的时间,关键是在这大概4个月的时间里,users这个列表妥妥的无法被回收,轻松吃掉一两个G内存呢,想想有点小激动呢,我亲爱的OutOfMemory可等不了4个月,分分钟在来的路上呢。

     

    正确做法:

    在循环里千万不要sleep或者调用太费时的任务,比如上面的逻辑,应该把邮件放到发送列表,然后立刻返回,这些耗时的操作,比如发送邮件这些事情,应该交给另外的进程去完成。术业有专攻,不要越俎代庖

     

    Iterable<UserEntity> users = userDAO.findExpireUserbyPage(i,pagesize);
           for (UserEntity user : users) {

           //发送逻辑
                    MailBox.put(new ExpireMail(user.mail))
               }
           }

    被直觉带节奏

    产品:请写一个schedule统计出所有用户每天的登录次数

    程序猿的设计:分页获取所有用户,查找该页用户今天的登录日志条数

    UserEntity users=GetAllUser(pagepageSize);

    For(UserEntity user in users){

    List<Log>  logs=getLoginLogForToday(user.username);

    System.out.println(“username:”+user.username+” logintimes:”+logs.size);

    }

    上面的需求、设计、代码看起来简洁有效,但是你肯定就上当了,这个实现是个坑,因为用户不会都登录,设想一个网站有1000万用户,每天登录的用户可能就100w人次,按照上面的实现,一定会循环1000万次,但是如果反过来,从日志入手,就只用循环100w

     

    正确做法:

    找到循环最少的路径,不要被语言表面的意思所干扰

     

    1次循环就能完成?不,让我多来几次

    比如每分钟我们要把登录日志取出来,分析下有没有异常情况,比如有没有ip重复登录啦,有没有应用访问量超标啊,有没有人在危险的地区登录啊,然后把危险情况产生预警,通知管理员。于是产生了这样的代码

    IpLoginAnalyzer implements Analyzer{

    Public void process(){

    List<log> logs=getLog(Now,lastCheckTime)

    。。。分析逻辑

    }

    }

    AppVisitBarrierAnalyzer implements Analyzer{

    Public void process(){

    List<log> logs=getLog(Now,lastCheckTime)

    。。。分析逻辑

    }

    }

    DangerLocationAnalyzer implements Analyzer{

    Public void process(){

    List<log> logs=getLog(Now,lastCheckTime)

    。。。分析逻辑

    }

    }

    。。。。。

    Foranalyzer in AnalyerList{

    Analyzer.process()

    }

     

    乍一看很顺眼,每个analyzer干自己的事情,但是具体执行起来,会多次查询和循环日志,考虑下假如有100个分析器,就会遍历日志100次,加上执行process本身需要时间,非常可能造成这分钟的还没有执行完,下次的分析又开始了,一波波最终叠加成海啸,让应用歇菜。

     

    正确做法:

    日志分析,如果至少要遍历一遍,那么最好也就只遍历一遍,写个遍历器,负责逐条遍历,把每条交给各个处理器处理,一次遍历解决所有问题。

    List<log> logs=getLog(Now,lastCheckTime)

    For(log in logs){

    Foranalyzer in AnalyerList{

    if(analyzer.canProcess(log))

    analyzer.process(log)

    }

    }

     

    可能你会问,如果每个处理器处理的日志类型不同怎么办呢,比如有的处理器处理登录,有的处理创建用户,有的要处理所有日志,其实只要有处理所有日志的情况,其他的处理就可以一起做了,反正都要循环一次,就不要再浪费cpu循环多次了,对开发团队来说,日志处理应该有统一的框架,有统一的人审核,处理类似数据,执行频率相同的要归并,否则各自为政就会出现上面的情况。

  • 相关阅读:
    关于解决win10安装mongodb启动服务失败的解决办法
    python3 Debug报错 Traceback (most recent call last)
    centos7 iptable开放端口失败的总结
    操作系统
    大数据医疗
    软件相关
    射频消融仪产品相关
    YY/T 0664—2020《医疗器械软件 软件生存周期过程》 相关
    ISO19001相关
    GMP现场指导相关
  • 原文地址:https://www.cnblogs.com/yizhu2000/p/7659596.html
Copyright © 2011-2022 走看看