pytho程序的热部署

知乎上面的回答

真正意义上的代码热部署应该是类似erlang那样的,将代码更新到节点后不停服务,不断连接的自动应用新代码。auto reload(代表django的autoreload)什么的还是会造成业务瞬间中断。我感觉是可以从wsgi容器级别上实现,比如更新代码后检测到文件变更,然后通知容器创建新的wsgi application的实例,之后所有新的请求都发送到新的wdgi application实例上。等旧wsgi application实例的最后一个请求返回后就将其回收掉。不过貌似没有看到类似的实现

爬虫程序的热部署的原理

主要使用了:reload ,importlib 两个模块

爬虫程序,尤其是多爬虫系统,比如自动同步系统(实时爬虫),需要经常修改爬虫规则(代码),如果使用重启的方式,对于实时爬虫来说,运维工作量大,而且还会造成服务中断。所以可以使用python的reload方法来实现热部署。


但是,由于对reloa的机制不是很了解,暂时还不清楚reload对程序的负面影响,尤其对于高并发程序的影响。

实现

实现很简单,程序使用多线程,热部署线程负责监听mq消息,收到消息,reload对应的模块。

代码

新建hotupdate包 修改__init__.py文件

# coding=utf-8
"""实现热更新功能"""
import importlib
import json
import sys
import threading
import logging

from hotupdate.hot_update import HotUpdateEventDriven, HotUpdateMessage

driven = HotUpdateEventDriven()


def _callback(ch, method, properties, body):
    """收到消息执行的回调函数"""
    try:
        msg = json.loads(body.decode('utf-8'))
        message = HotUpdateMessage(model_path=msg['model_path'], is_append_path=msg['is_append_path'], physical_path=msg['physical_path'])
        # 物理路径加入pythonPath
        if message.is_append_path:
            sys.path.append(message.physical_path)

        # 动态的引入该模块
        import_module = importlib.import_module(message.model_path)
        reload(import_module)
        # 打印热更新信息
        logging.warning('%s模块已经更新' % (message.model_path))
    except Exception, e:
        logging.warning('热部署失败,收到的消息%s,异常信息%s' % (body, e.message))
    # 消息确认
    driven.input_channel.basic_ack(delivery_tag=method.delivery_tag)


def _run():
    """线程的执行方法"""
    driven.receive(_callback)


def auto_reload_module():
    """reload a module.
    新开一个线程来进行reload操作
    动态更新python模块代码,依赖事件驱动,就是需要有人或者程序发送消息通知程序
    """
    t = threading.Thread(target=_run, name='autoReloadThread')
    t.start()


新增hot_update.py文件

# encoding=utf-8
# ---------------------------------------
#   语言:Python2.7
#   功能:事件驱动,触发热部署动作
# ---------------------------------------
import uuid

import pika

from queues import queueBase
from settings import MQ


class HotUpdateEventDriven(object):
    """监听mq消息,收到消息,自动reload"""

    def __init__(self):
        credentials = pika.PlainCredentials(MQ['userName'], MQ['password'])
        parameters = pika.ConnectionParameters(MQ['host'], MQ['port'], '/', credentials=credentials)
        self.connection = pika.BlockingConnection(parameters)
        self.__init_input__()

    def __init_input__(self):
        '''
        初始化入口通道
        '''
        self.input_channel = self.connection.channel()
        input_exchange = 'python.ppmoney.hotupdate'
        input_exchange_type = 'fanout'
        input_routing_key = '#'
        # 消息入口的队列
        self.input_queue = 'python.hotupdate.%s' % (uuid.uuid1())
        # 声明路由,如果存在就什么也不会做
        self.input_channel.exchange_declare(exchange=input_exchange, exchange_type=input_exchange_type, durable=True)
        # 声明队列
        self.input_channel.queue_declare(queue=self.input_queue, auto_delete=True)
        # 通过routing_key 将交换机与queue绑定起来
        self.input_channel.queue_bind(exchange=input_exchange, queue=self.input_queue, routing_key=input_routing_key)

    def receive(self, callback):
        '''
        入口通道:接受自动同步服务发送的任务消息
        '''
        # 入口通道订阅消费者
        self.input_channel.basic_consume(consumer_callback=callback, queue=self.input_queue, consumer_tag='hotupdate_client')
        self.input_channel.start_consuming()


class HotUpdateMessage(object):
    """收到的mq消息"""

    def __init__(self, model_path, is_append_path, physical_path):
        # 模块名称
        self.model_path = model_path
        # 是否需要添加到pythonPath
        self.is_append_path = is_append_path
        # 如果需要添加到PythonPath中,需要提供模块的物理路径
        self.physical_path = physical_path

使用起来很简单,只需要增加一行代码即可

# 开启热更新
hotupdate.auto_reload_module()
Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐