gunicorn+flask+selenium服务后台浏览器不断增加

1.背景说明

一信息门户网站,输入身份证号可查询相关信息,因此使用Selenium自动化完成该操作。使用flask将脚本制作成接口给业务方调用,flask通过Gunicorn管理。

2.踩坑经过

2.1 发现问题

服务部署上线后部署服务的Docker隔三差五重启,报错多为超时。经观察,该门户网站(非国内)网络经常不稳定,周末基本访问不了、周内偶尔响应慢,疑似为超时导致Gunicorn杀掉worker进程,进一步查看后台浏览器数量时,发现数量非常多,占用大量资源。

# 查看浏览器数量
ps -ef|grep chrome |awk -F' ' '{print $2}'| wc -l
# 手动清理
ps -ef|grep chrome |awk -F' ' '{print $2}'|xargs kill -9

为了不让野浏览器占用太多资源,我写了个脚本自动清理,并配置crontab每分钟检查

#!/bin/bash  
  
# 获取chrome进程数量  
chrome_num=$(ps -ef | grep chrome | awk '{print $2}' | wc -l)  
  
# 判断数量是否超过100  
if [ $chrome_num -gt 100 ]  
then  
    ps -ef | grep chrome | awk '{print $2}' | xargs kill -9  
fi

2.2 初次尝试解决

上一节中解决的办法非常粗暴,就是长出来的浏览器全部剪掉,而全部消掉会导致原本运行中的服(被)务(业)受(务)影(方)响(骂)。因此需要分析一下浏览器越长越多的原因:推测为Gunicorn过于粗暴的kill -9导致原本打开浏览器的进程被杀掉,而其启动的浏览器并未销毁。

找到了原因,试图说服Gunicorn温柔一点,毕竟kill -9我捕获不了

gunicorn -w 5 -b 0.0.0.0:8787 --graceful-timeout 20 --timeout 30 core.app.rest_service:app

结果:温柔的一刀和跳起来劈一刀都是kill

2.3 利用python自带的回收机制

由于并不理解python的回收机制,我傻傻的相信,只要许愿给gpt就会有好结果。于是我尝试了一下__del__方法

class Browser:
    def __init__(self, path: str, header: bool = False):
        self.path = path
        self.header = header

        self.core_driver = self.init_browser(path, header)

	def __del__(self):
		self.exit_browser()

	def exit_browser(self):
        if self.core_driver is not None:
            self.close_browser()
        logger.warning(f'browser #{os.getpid()} exit')

结果:gpt有时候自己都会编不下去

2.4 利用python的内置函数atexit

我不太理解为什么__del__方法没有起作用,查了些资料和论坛,大家都不觉得自己写__del__方法是个好主意,既然如此那我就用自带的函数好了,于是查到了atexit

class Browser:
    def __init__(self, path: str, header: bool = False):
        self.path = path
        self.header = header

        self.core_driver = self.init_browser(path, header)
        atexit.register(self.exit_browser)  # 新增了一行这个

	def __del__(self):
		self.exit_browser()

	def exit_browser(self):
        if self.core_driver is not None:
            self.close_browser()
        logger.warning(f'browser #{os.getpid()} exit')

按照我的理解,python就是读英语,读到啥就是啥意思,那么这个atexit就是退出时候执行。那么我超时退出的时候也会执行对吧?
结果:程序是被杀了,不是退出了

2.5 重写超时设置

此刻的我已经对sig有了非常深刻的理解,从温和善良的kill -2到超脱三界外的kill -9我都有了不亚于大一计算机实习生的理解(尝试捕获kill -9发现不可能
原来,atexit神功没有问题,只是Gunicorn过于狠辣,让我还没施展出来就将进程杀掉。既然如此,只要我自己退出就没有人能杀了我

def _timeout_handler(signum, frame):
    logger.critical(f'#{os.getpid()} TIMEOUT')
    exit()


signal.signal(signal.SIGALRM, _timeout_handler)


def timeout_deco(timeout):
    def deco(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            signal.alarm(timeout)
            result = func(*args, **kwargs)
            signal.alarm(0)
            return result
        return wrapper
    return deco

相应的服务做个修改

@app.route("/spider", methods=['POST'])
@timeout_deco(int(os.getenv('timeout', 20)))
def query():
    t0 = time.time()
    try:
        response = ...
    except Exception:
        logger.exception(f'REST_ERROR: {request.json}')
    t1 = time.time()

    logger.info({'pid': os.getpid(), 'use_time': (t1 - t0), 'request': request.json, 'response': response})
    return json.dumps(response, ensure_ascii=False, indent=2)

在外面再包一层超时、参数检验的服务,大功告成

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值