• [BUGCASE]Phantom服务代码不健壮导致无法发送报表邮件


    一、问题描述

    广告平台中的发报表邮件功能偶尔会出现发送失败的情况,重启phantom服务之后就好了
    查看phantom服务的日志发现,在2017-12-12 03:29:45有访问记录,并且参数是异常的,queryJSON是%257B,经过url decode之后发现是:{


    ![](https://img2018.cnblogs.com/blog/296720/201901/296720-20190117170018751-1025403433.jpg)
    并且之后再进行发邮件操作,phantom不再打日志

    不想看分析过程的话,可直接看结论解决方案

    二、原因分析

    1.导火线

    日志中的url是后台传给phantom服务的,为什么会传一个异常参数的url过来?
    很明显不是人为点击发邮件触发的,而且时间发生在凌晨,会不会是安平扫描
    经过和后台同事确认,确实如此!


    原来后台没有做session校验,所以被扫描了,导致了这个问题

    2.一些猜测

    可是这似乎不是根本原因
    为什么安平扫描会导致phantom服务停止打日志?
    以下是一些猜测:

    (1)phantom服务崩溃

    一开始猜测是phantom服务接收到异常参数,内部报错,导致服务崩溃

    这个想法立即被导师否决了,因为phantom服务的进程还在,并没有崩溃

    (2)运维自动重启服务的锅

    因为phantom服务接了运维这边的自动重启服务,如果进程挂了会自动拉起

    会不会进程拉起的过程有问题,初始的phantom进程不再工作,又拉起了一个新进程,存在两个进程,导致无法发邮件?

    (3)进程僵死

    会不会是进程僵死了,导致进程虽然还在,实际上却不工作?

    (4)进程打开的文件句柄数超出限制

    会不会是进程打开的句柄数超过了限制?

    由于当时急着把服务重启了,没法获取出错时的上下文环境信息

    为了验证这些猜测,需要重现下这个问题,怎么重现呢?

    似乎没法真正地模拟安平的扫描过程

    当时有同事提供了一个解决方案:
    写一个脚本定时给phantom服务发包,然后看phantom服务有没有回包,没有回包说明phantom已经停止工作了,这时把phantom的进程杀死。运维的自动拉起服务这时会拉起phantom服务,如此便可解决这个问题

    听起来很有道理,怎么实施呢?

    打算先试着找到根本原因吧,实在没法重现这个问题,找不到根本原因再考虑发包的方案

    3.分析代码

    于是试着从phantom的代码入手
    phantom服务拿到后台传过来的url之后,会做一下事情:

    • 1.取url中的queryString
    • 2.将queryString转化成JSON格式,并取其中的queryJSON参数的值
    • 3.将queryJSON的值进行url解码(decodeURIComponent)
    • 4.将解码后的queryJSON转化成JSON格式(JSON.parse)JSON.parse
    • 5.取其中的platform参数的值
    • 6.通过判断platform拼接出不同的targetURL
    • 7.用phantom这个headless browser打开这个targetURL,并取其中的HTML内容
    • 8.将这个内容传给后台服务

    用安平扫描的异常参数模拟一遍上面的流程,发现第4步报错,JSON.parse方法没法解析{左大括号

    这个报错会导致程序不再往下执行,但并不会导致phantom服务不可用,下一次传入正确的参数依然可以发邮件


    安平扫描有可能会连续扫描多次,会不会是这个原因呢?

    4.问题重现

    于是打算写一个脚本给phantom服务发多次带错误参数的请求:

    for ((i=1;i<=100;i++));
    do curl -i "http://ip:port/generateAdsReport?sessionid=&queryJSON=%7B";
    done
    

    ip:port是phantom服务的套接字

    果然重现了!

    5.查看报错环境

    这时就可以看上下文环境信息

    (1)查看进程情况:

    ps -ef | grep phantom

    并没有出现之前猜测的两个进程的情况


    ps -A -o stat,ppid,pid,cmd | grep -e '^[Zz]'

    也没有变成僵尸进程

    (2)查看文件句柄数

    查看系统默认最大句柄数(单个进程最多允许打开的最大句柄数,默认是1024)
    ulimit -n


    查看总句柄数
    lsof|awk '{print $2}'|wc -l


    查看当前进程占用的句柄数
    lsof -n|awk '{print $2}'|sort|uniq -c|sort -nr|more

    根据ID产看进程名
    ps aef|grep 24204


    发现并没有哪个进程超过句柄限制

    (3)查看TCP连接数

    netstat -nat | grep 10021



    问题慢慢浮出水面!!!

    6.根本原因

    有很多状态为CLOSE_WAIT的TCP连接
    CLOSE_WAIT表示等待从本地用户发来的连接中断请求


    也就是说有很多TCP连接没有关闭,对照之前的phantom代码,出现报错之后,确实没有在执行任何关闭连接的代码

    所以根本原因还是:自己写的代码不健壮,没有捕捉异常,报错了没有及时关闭TCP连接


    一句话总结问题的原因:

    由于安平的扫描,phantom服务内部将字符串转成JSON时报错,不往下执行后面的代码,也就不会关闭已经建立的TCP连接。未关闭的TCP连接积累到一定的量,超出系统的最大限制,导致phantom服务不再接收和处理之后的请求,最终导致无法发送报表邮件。

    三、解决方案

    知道问题的根本原因,要解决就容易多了,使用try/catch语句将可能出错的代码包裹起来,如果报错就关闭TCP连接。

    try {
        ...
        var jsonParams = JSON.parse(decodedParams);//可能报错的语句
        var platform = jsonParams.platform;
    }catch(err) {
        response.close();//关闭TCP连接
    }
    
    

    ------------------------------------------------20171219 更新------------------------------------------------

    为了弄清楚TCP连接数的限制,在开发环境又测试了下

    状态为CLOSE_WAIT的TCP连接大概到了25个时,phantom服务就不再工作

    此时phantom占用的文件句柄数是61个

    实际上,phantom的最大并发连接数是10个,参考phantom官网

    以下是来自phantom官网的截图:

    以下是在开发环境上测试出来的TCP连接的边界值

    之前的一句话原因总结改为:

    结论

    由于安平的扫描,phantom服务内部将字符串转成JSON时报错,不往下执行后面的代码,也就不会关闭已经建立的TCP连接。未关闭的TCP连接积累到一定的量,超出phantom的最大并发连接数10个(之前写的是系统的最大限制),导致phantom服务不再接收和处理之后的请求,最终导致无法发送报表邮件。

  • 相关阅读:
    windows下端口映射(端口转发)
    SQLServer 2008 复制同步(发布、订阅)的几个问题
    SqlServer:此数据库处于单用户模式,导致数据库无法删除的处理
    jQuery函数的等价原生函数【转载】
    JavaScript学习第三天
    持续集成工具hudson【转载】
    linux-unzip命令【转载】
    javascript学习第一天
    java.util.Properties
    Eclipse快捷键【转载】
  • 原文地址:https://www.cnblogs.com/kagol/p/10283226.html
Copyright © 2020-2023  润新知