Python 软件热更新

Python 软件热更新

本篇文章涉及技术知识以下:python

Redis
threading 多线程
PyQt5git

importlib 热更新github

场景

我们在平时运行一些长时间都会一直运行的软件(如:某些云同步软件)的时候,某些功能由于考虑的状况可能不充分,致使体验不够好的时候,不少人都会忽视这个问题,除非这个问题影响到他正常使用了。可是也有部分用户会在软件的反馈框里面将问题反馈给开发者,顺带将错误日志也一并提交给开发者。而后过了一天或者半天,你再运行那部分功能的时候,发现问题已经解决了。但是,咱们都没有更新软件呀,甚至连软件都没有重启,难道前面遇到的那个状况真的是由于本身太幸运踩中bug了吗?
其实,咱们以前遇到的问题,可能的确就是一个bug,可是在反馈问题给开发者后,开发者快速定位问题所在后,经过热更新将问题解决了。至关于咱们使用的软件自动fix了一些bug,更新了一次版本。
那么,今天我们聊一下热更新这个东西怎么样?咱们也随意作个小demo看看这个有意思的功能是怎么作到的。redis

什么是热更新

热更新就是能够在进程不重启的状况下,让其从新加载修改后的程序代码,且能按照预期正确执行。在实际开发中,热更新的最主要用途有,数据库

  • 能够提高开发效率,让改动后的代码效果马上实现,避免频繁重启
  • 对于bug修复来讲,在CS模式下,若是不是大的bug而是小bug的修复就不用发布比较大的补丁和更新文件了,直接使用服务器修正问题后,通知客户端从新加载修正后代码便可。

Python的代码是经过module进行组织的,因此,对某些功能的热更新就是能够经过对module更新就能够了。
在Python中,若是从新import 一个已经被import的模块时,并不会从新执行import新的模块。因此,在这个时候,咱们但愿能够从新加载模块的时候,就须要对模块进行删除后,再从新import进来。
而在sys.modules保存了已经加载过的模块。浏览器

 

 

为了方便看到展现,我就沿用上次客户端的界面,进行简单修改后,展现给你们看,热更新的效果。服务器

 

 

左边的按钮是运行模块加载进来的函数,右边的按钮是手动点一下热更新。便于本地手动调试热更新。在后面实现的“发布订阅”状况中,服务端发布更新消息后,不用手动点 热更新 就能够对软件进行自动更新了。微信

简单实现一个demo,引用myfunction这个模块,运行里面的某个函数一两次后,修改那个被运行的函数实现,而后对myfunction这个模块进行热更新,看看效果怎么样?多线程

 

 

在热更新前,随机产生的数字在原函数里面,版本号为0.0.1,是能够比较明显看出 两个数 是运行 “相加” 操做的。
点击了热更新Button后,软件并未重启,在更新后,能够 看到功能版本号发生了改变,变成了0.1.1,说明已是热更新完成了的。再点击运行功能,能够看到结果已经变成了 两个数字 进行了 “相减‘ 操做。app

完成了本地测试热更新成功后,就着手实现CS模式下的“发布订阅”消息通知功能,利用服务器对客户端推送一个更新指令,客户端就会自动更新模块。
用过Redis的同窗应该都知道Redis自己就自带了“发布订阅”功能,借助它,能够很方便的实现出远程推送消息的需求,咱们甚至能够用这个功能实现一个简单的聊天室软件哦。
在Redis服务端中,建立一个 update频道:

SUBSCRIBE update

而后在Python中导入Redis模块后,连接到远程Redis数据库后,订阅咱们的update频道,再启动一个新的线程去监听update频道的消息。
由于若是直接在代码里面用单线程去监听消息的话,会形成线程阻塞在监听消息哪里,致使界面刷新不出来。因此,咱们只要导入threading库,再把监听消息作成一个函数,放到thread中去运行就能够了。由此避免线程阻塞问题。

接着我在本地修改一下myfunction模块后,就到Redis服务的终端中,发布一个消息,reload。
这个时候,软件就会收到reload消息,对刚才被我修改后的模块进行热更新,即删掉源模块,再从新导入一次。
在这里我就不写一次从服务器中下载新的模块文件的代码了,假设我刚才修改后的那个文件就从服务器下载下来的。同窗们能够借助前面两篇写软件更新服务的文章来本身实现一个文件下载更新的代码。很简单的,只要你愿意写。

在Redis中发布重载的消息后,订阅了这个频道的客户端,将会接收到更新信息,好比我这个客户端,将会对模块从新加载进行热更新了。
在这里我给你们随意扯一下“灰度测试”吧,这个灰度测试就是软件即将要更新某个功能,可是可能这个功能还不够稳定,不能向所有用户推送新的功能。因此,这个时候,就须要对部分用户更新这个功能,经过这部分用户使用状况来决定灰度测试的范围,好比将5%的范围扩展到10%这样。
至于怎么筛选出那些用户为测试用户呢?其中有个办法是能够用个哈希函数对用户某个值,如用户名,进行处理,符合的就推送。固然还有不少不少的策略,实现执行起来的时候,也不会像我说的那么简单,感兴趣的同窗能够自行查阅资料。

接下来,咱们来测试一下发布更新功能的消息后,有没正常热更新功能。

 

 

 

 

在这里要提醒一下,若是你在热更新前导入的模块生成了一个对象x,这个时候,你热更新了,而后又生成一个对象y。这个时候,你会发现,x指向的仍旧是旧的那个类,而y则指向了新的类。这个时候,能够经过修改x的__class__属性来对 x 的类进行强制修改,能够这样写:

x.class == y.你的类

可是即便你是这样写,你x里面的数据仍旧不会发生改变的哦。咱们只更改了代码的执行逻辑。
代码放这里了:

    python    61行

# -*- coding: utf-8 -*-
# @Time    : 4/12/2019 14:02
# @Author  : MARX·CBR
# @File    : __init__.py.py
import threading
import sys
from PyQt5 import QtWidgets
from updateServer.HotUpdate import myfunction
import redis
import random
import importlib
from updateServer.HotUpdate.HotFixSample import Ui_MainWindow


class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.setupUi(self)
        self.fun = importlib.import_module("myfunction")
        self.pushButton.clicked.connect(self.runFunction)
        self.pushButton_2.clicked.connect(self.hotfix)
        self.ip = "47.xxx.xxx.xx"
        self.redisport = 2017
        self.redis_manager = redis.StrictRedis(self.ip, port=self.redisport)
        # self.textBrowser.append(str(sys.modules))
        print(sys.modules)
        self.tunnel = self.redis_manager.pubsub()
        self.tunnel.subscribe(["update"])
        self.threads = []
        self.t1 = threading.Thread(target=self.autoReload, )
        self.threads.append(self.t1)
        self.threads[0].setDaemon(True)
        self.threads[0].start()

    def autoReload(self):

        for k in self.tunnel.listen():
            if k.get('data') == b'reload':
                self.hotfix()

    def runFunction(self):
        version = self.fun.AllFunction().version
        self.textBrowser.append("功能运行,当前版本为:" + version)
        for i in range(4):
            x = random.randint(-454, 994)
            y = random.randint(-245, 437)
            self.textBrowser.append(str(x) + "\tfunction version {}\t".format(version) + str(y) + " = " + str(
                self.fun.AllFunction().second(x, y)))
        # self.textBrowser.append(self.fun.AllFunction().first())

    def hotfix(self):
        del sys.modules["myfunction"]
        self.fun = importlib.import_module('myfunction')
        self.textBrowser.append("热更新完毕")


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    mainWindow = MainWindow()
    mainWindow.show()
    sys.exit(app.exec_())
 

总结


 在本篇文章中,咱们讲到了Python的热更新的一些简单用法,以及一些值得注意的坑。顺手应用了Redis的“发布订阅”功能来通知客户端更新功能,扯了一下“灰度测试”。本篇文章代码已同步上传到我GitHub中,欢迎你们fork使用,顺手给个star就更好了,
项目连接:https://github.com/97CBR/SoftwareUpdateServer

    其实,有心思的小伙伴确定会有更多的想法。好比:咱们既然能够动态从新加载一个类来fix bug,也确定能够动态添加咱们要的功能啦。这意味着,咱们能够编写出一个软件,具备插件功能的软件。在主体软件上面,运行插件来扩展更加多的功能,和Chrome这样的浏览器同样,安装插件什么的。

本文对你有没帮助呀,喜欢的话,记得留言、点赞、转发哟。谢谢各位!

扫码关注微信公众号

相关文章
相关标签/搜索