Mproxy项目实录第7天

关于这个系列

这个项目实录系列是记录Mproxy项目的整个开发流程。项目最终的目标是开发一套代理服务器的API。这个系列中会记录项目的需求、设计、验证、实现、升级等等,包括设计决策的依据,开发过程中的各种坑。希望和大家共同交流,一起进步。

项目的源码我会同步更新到GitHub,项目地址:https://github.com/mrbcy/Mproxy

系列地址:

Mproxy项目实录第1天

Mproxy项目实录第2天

Mproxy项目实录第3天

Mproxy项目实录第4天

Mproxy项目实录第5天

Mproxy项目实录第6天

今日计划

本来想着要在代理服务器的收集系统里面加一个内建的web页面,这样就可以通过浏览器查看系统的运行状态。

后来想了想,我的主要目的是能够确保系统在正确的运行就可以了。要达到这个目标并不是一定要内建web页面。要确保系统的正常运行只要卡主两个关键指标就可以了。

第1个指标是可用的代理服务器数量。这个是重中之重,只要这个指标没问题,其他可以不用那么紧张。

第2个指标是last_validate_time中的最晚时间。如果这个时间离现在很远,表示系统很久没有重新验证代理服务器了,也就证明目前的可用代理服务器数量是不可靠的。

针对这两个指标,提出了如下的监测方案。

  1. 如果发现可用的代理服务器数量少于4000,就运行爬虫进行爬取。这个检查2小时执行1次。
  2. 如果last_validate_time中的最晚时间距今超过24小时,发送短信提醒。这个检查6小时执行1次。
  3. 如果发现可用的代理服务器数量少于2000,发送短信提醒。这个检查2小时执行1次。
  4. 如果在执行上述查询的过程中抛出异常,也发送短信提醒。

所以,今天的任务就是完成监测器的开发,并且微调之前的系统各个参数。为真正在ubuntu集群上的部署做好准备。

技术验证

还是有两个技术验证工作要做。第一个是通过Python执行其他的Python程序;第二个是用Python发送短信。

用Python执行其他的Python程序

原来以为这个很难,结果很简单,哈哈。

首先写一个test.bat文件,代码如下:

cd /d "D:\软件编程学习\Mproxy\代码\Spiders\kuaidaili"
scrapy crawl kuaidaili

看到路径里面有中文,则test.bat文件的编码必须跟cmd命令窗口的编码格式一致,否则会找不到路径。我这里的编码是GBK。

然后编写调用bat的代码:

#-*- coding: utf-8 -*-
import os


def func():
    os.system('test.bat')


if __name__ == '__main__':
    func()

使用Python发送短信

我这边选用了阿里大于的短信接口。主要优点是使用淘宝用户名直接登录,很方便,还有Python的SDK可以直接用。而且毕竟大厂出品,应该有保障。

在使用之前要完成应用创建,签名审批和短信模板审批几个流程。具体就不说了,基本都能过。

在申请的时候一定要申请合适的短信模板。经过反复测试,变量长度最多为15个字符(中英文都算1个)。

在申请的时候一定要申请合适的短信模板。经过反复测试,变量长度最多为15个字符(中英文都算1个)。

在申请的时候一定要申请合适的短信模板。经过反复测试,变量长度最多为15个字符(中英文都算1个)。

发送短信的API文档可以参考这里:https://api.alidayu.com/doc2/apiDetail.htm?spm=a3142.8062968.3.1.2fJlnI&apiId=25450

代码如下:

#-*- coding: utf-8 -*-
import top.api

def func():
    url = " gw.api.taobao.com"
    req = top.api.AlibabaAliqinFcSmsNumSendRequest(url, port=80)
    req.set_app_info(top.appinfo(appkey, secret))

    req.extend = "123456"
    req.sms_type = "normal"
    req.sms_free_sign_name = "阿里大于"
    req.sms_param = "{\"code\":\"1234\",\"product\":\"alidayu\"}"
    req.rec_num = "13000000000"
    req.sms_template_code = "SMS_585014"
    try:
        resp = req.getResponse()
        print(resp)
    except Exception, e:
        print(e)


if __name__ == '__main__':
    func()

真实运行时需要把appkey,secret等信息填成系统分配过来的值。

这里有点坑,我申请的短信模板是这样的:

系统运行异常,请处理。${alarm_info}

本来想发一条这样的短信:

系统运行异常,请处理。Mproxy系统提示,代理服务器数量已经不足2000

然后反复的收到isv.PARAM_LENGTH_LIMIT的异常,就是说变量长度限制。

所以我们在申请的时候一定要申请合适的短信模板。经过反复测试,变量长度最多为15个字符(中英文都算1个)。

配置文件工具类

为了后续维护的方便和上传GitHub时能不泄露我的关键信息,必须把发送短信关键的数据放到配置文件里,然后提供一个配置文件的工具类供其他代码使用。

#-*- coding: utf-8 -*-
import ConfigParser
import os


class ConfigLoader:
    def __init__(self):
        # get the project path
        dir_name = "Monitor" + os.sep
        thePath = os.getcwdu()
        if thePath.find(dir_name) > 0:
            thePath = thePath[:thePath.find(dir_name) + len(dir_name)]
        else:
            thePath += os.sep
        print thePath
        self.cp = ConfigParser.SafeConfigParser()
        self.cp.read(thePath + 'monitor.cfg')

    def get_app_key(self):
        return self.cp.get('sms','appkey')

    def get_secret_key(self):
        return self.cp.get('sms','secret')

    def get_sign_name(self):
        return self.cp.get('sms','sign_name')

    def get_sms_template_code(self):
        return self.cp.get('sms','sms_template_code')

    def get_phone_num(self):
        return self.cp.get('sms','phone_num')

    def get_mysql_host(self):
        return self.cp.get('mysql','host')

    def get_mysql_port(self):
        return int(self.cp.get('mysql','port'))

    def get_mysql_user(self):
        return self.cp.get('mysql','user')

    def get_mysql_pwd(self):
        return self.cp.get('mysql','password')

    def get_mysql_db_name(self):
        return self.cp.get('mysql','db_name')

配置文件的结构如下,具体内容请自行填写。

[sms]
appkey = 
secret = 
sign_name = 
sms_template_code = 
phone_num = 

[mysql]
host = amaster
port = 3306
user = 
password = 
db_name = mproxy

感觉经过了几个版本的迭代,classloader终于写得在各个路径下以及在Windows和Linux下都能用了。稍后把这个代码更新到其他组件上去。

封装数据库的查询工具类

接下来使用三层架构来封装需要查询数据库的工具类。这里从dispatcher中把dbpool,domain包都复制过来待用。

dao的代码如下:

#-*- coding: utf-8 -*-
import traceback

from dao.proxystatus import ProxyStatus
from dbpool.poolutil import PoolUtil
from domain.proxydaoitem import ProxyDaoItem


class ProxyDao:

    def find_proxy_last_validate_time(self):
        try:
            conn = PoolUtil.pool.connection()
            cur = conn.cursor()
            sql = "select * from proxy_list order by last_validate_time desc limit 1"
            count = cur.execute(sql)
            last_validate_time = None
            if count != 0:
                data = cur.fetchone()
                last_validate_time = data[5]

            cur.close()
            conn.close()
            return last_validate_time
        except Exception as e:
            return None

    def get_avaliable_proxy_count(self):
        try:
            conn = PoolUtil.pool.connection()
            cur = conn.cursor()
            sql = "select count(*) from proxy_list where status = %s"
            count = cur.execute(sql,ProxyStatus.AVAILABLE)
            result = None
            if count != 0:
                data = cur.fetchone()
                result = int(data[0])

            cur.close()
            conn.close()
            return result
        except Exception as e:
            return 0

然后写dao的单元测试:

#-*- coding: utf-8 -*-
import datetime

from dao.proxydao import ProxyDao
from dao.proxystatus import ProxyStatus
from domain.proxydaoitem import ProxyDaoItem

proxy_dao = ProxyDao()

def test_get_proxy_count():
    global proxy_dao
    print proxy_dao.get_avaliable_proxy_count()

def test_get_last_validate_time():
    global proxy_dao
    last_validate_time = proxy_dao.find_proxy_last_validate_time()
    print type(last_validate_time)
    print last_validate_time

if __name__ == '__main__':
    test_get_last_validate_time()

service就是在dao上包了一层,所以也不用写单元测试了。

#-*- coding: utf-8 -*-
from dao.proxydao import ProxyDao


class ProxyService():
    def __init__(self):
        self.proxy_dao = ProxyDao()

    def find_proxy_last_validate_time(self):
        return self.proxy_dao.find_proxy_last_validate_time()

    def get_avaliable_proxy_count(self):
        return self.proxy_dao.get_avaliable_proxy_count()

封装发送短信工具类

#-*- coding: utf-8 -*-
import traceback

import top
from conf.configloader import ConfigLoader


class SmsUtil:
    conf_loader = ConfigLoader()
    @classmethod
    def send_sms(cls,system_name,exception_name,key_prompt):
        url = "gw.api.taobao.com"
        appkey = cls.conf_loader.get_app_key()
        secret = cls.conf_loader.get_secret_key()
        req = top.api.AlibabaAliqinFcSmsNumSendRequest(url)
        req.set_app_info(top.appinfo(appkey, secret))

        req.sms_type = "normal"
        req.sms_free_sign_name = cls.conf_loader.get_sign_name()
        req.sms_param = """{"system_name":"%s","exception_name":"%s","key_prompt":"%s"}""" % (system_name,exception_name,key_prompt)
        req.rec_num = cls.conf_loader.get_phone_num()
        req.sms_template_code = cls.conf_loader.get_sms_template_code()

        try:
            resp = req.getResponse()
            print(resp)
        except Exception as e:
            traceback.print_exc()

这里后续还可以改进,如果发送短信报异常,可以再发送邮件,让运维人员知道短信接口也有问题。

封装检查任务线程类

检查可用代理服务器数量的线程类

#-*- coding: utf-8 -*-
import os
import threading

import time
import traceback

from conf.configloader import ConfigLoader
from service.proxyservice import ProxyService
from sms.smsutil import SmsUtil


class AvailableCountTask(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.proxy_service = ProxyService()
        self.conf_loader = ConfigLoader()

    def run(self):

        while True:
            count = self.proxy_service.get_avaliable_proxy_count()
            if count < 4500:
                try:
                    # start spiders
                    exit_code = os.system(self.conf_loader.get_start_kuaidaili_command())
                    print exit_code
                    if exit_code != 0:
                        SmsUtil.send_sms('Mproxy', '快代理爬虫运行出错', '无')

                    time.sleep(5)

                    exit_code = os.system(self.conf_loader.get_start_xicidaili_command())
                    print exit_code
                    if exit_code != 0:
                        SmsUtil.send_sms('Mproxy', '西刺代理爬虫运行出错', '无')
                except Exception as e:
                    traceback.print_exc()
                    SmsUtil.send_sms('Mproxy','启动爬虫出错','无')

            elif count < 2000:
                SmsUtil.send_sms('Mproxy', '代理服务器数量不足', str(count))

            time.sleep(60*60*2)

这里还对爬虫的代码进行了一点修改,如果启动过程出错(一般是连接Kafka集群失败)就以退出码1退出,让监测器能够知道确实出错了。

检查验证时间的任务类

#-*- coding: utf-8 -*-
import os
import threading

import time
import traceback

import datetime

from service.proxyservice import ProxyService
from sms.smsutil import SmsUtil


class ValidateCheckTask(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.proxy_service = ProxyService()

    def run(self):

        while True:
            last_validate_time = self.proxy_service.find_proxy_last_validate_time()
            now = datetime.datetime.now()
            seconds = (now - last_validate_time).total_seconds()

            if seconds > 12 * 60 * 60:
                SmsUtil.send_sms('Mproxy', '超过12小时未执行验证', '无')

            time.sleep(60*60*6)

写监控器的代码

剩下的部分很简单,只要把两个任务启动起来就行了。

#-*- coding: utf-8 -*-
from sms.smsutil import SmsUtil
from task.availablecounttask import AvailableCountTask
from task.validatechecktask import ValidateCheckTask


def func():
    validate_check_task = ValidateCheckTask()
    validate_check_task.start()

    available_count_check_task = AvailableCountTask()
    available_count_check_task.start()
    # SmsUtil.send_sms('Mproxy','代理服务器数量不足','200')


if __name__ == '__main__':
    func()

封装邮件发送的工具类

直接在网上找到了代码http://www.cnblogs.com/xiaowuyi/archive/2012/03/17/2404015.html,用一下吧。

#-*- coding: utf-8 -*-
import smtplib
from email.mime.text import MIMEText

from conf.configloader import ConfigLoader


class EmailUtil:
    conf_loader = ConfigLoader()

    @classmethod
    def send_email(cls,sms_content,exception_info):
        mailto_list = cls.conf_loader.get_mail_to_list()
        mail_host = "smtp.qq.com"
        mail_user = cls.conf_loader.get_mail_username()
        mail_pass = cls.conf_loader.get_mail_password()
        mail_postfix = "qq.com"
        sub = "Mproxy短信接口异常"

        content = "尊敬的管理员您好,您收到这封邮件是因为我们的短信接口出现了问题,无法向运维人员发送短信。" \
                  "\n\n尝试发送的短信内容如下:\n%s \n\n发送过程中出现的异常为:\n%s" % (sms_content,exception_info)

        me = "Mproxy" + "<" + mail_user + "@" + mail_postfix + ">"
        msg = MIMEText(content, _subtype='plain', _charset='utf-8')
        msg['Subject'] = sub
        msg['From'] = me
        msg['To'] = ";".join(mailto_list)
        try:
            server = smtplib.SMTP_SSL("smtp.qq.com", 465)
            server.connect(mail_host)
            server.login(mail_user, mail_pass)
            server.sendmail(me, mailto_list, msg.as_string())
            server.quit()

        except Exception, e:
            print str(e)

然后修改一下短信发送工具类,如果发送短信出错,就发送邮件。

#-*- coding: utf-8 -*-
import traceback

import top
from conf.configloader import ConfigLoader
from mail.emailutil import EmailUtil
from sms.tracebackcontainer import TracebackContainer


class SmsUtil:
    conf_loader = ConfigLoader()
    @classmethod
    def send_sms(cls,system_name,exception_name,key_prompt):
        url = "gw.api.taobao.com"
        appkey = cls.conf_loader.get_app_key()
        secret = cls.conf_loader.get_secret_key()
        req = top.api.AlibabaAliqinFcSmsNumSendRequest(url)
        req.set_app_info(top.appinfo(appkey, secret))

        req.sms_type = "normal"
        req.sms_free_sign_name = cls.conf_loader.get_sign_name()
        req.sms_param = """{"system_name":"%s","exception_name":"%s","key_prompt":"%s"}""" % (system_name,exception_name,key_prompt)
        req.rec_num = cls.conf_loader.get_phone_num()
        req.sms_template_code = cls.conf_loader.get_sms_template_code()

        try:
            resp = req.getResponse()
            print(resp)
        except Exception as e:
            traceback_container = TracebackContainer()
            traceback.print_exc(file=traceback_container)
            sms_content = "%s运行异常:%s,关键参数:%s,请尽快处理" % (system_name,exception_name,key_prompt)
            EmailUtil.send_email(sms_content, traceback_container.message)

这里额外定义了一个类来收集traceback的打印信息。

#-*- coding: utf-8 -*-

class TracebackContainer:
    def __init__(self):
        self.message = ""

    def write(self, str):
        '''
        把traceback信息存储必须的函数
        '''
        self.message += str

小结

到现在来说,应该是能够保证收集系统稳定运行了。下面真的要部署到Linux环境下面去了,再把文档补一补,差的太多了。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值