Python 软件热更新

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

Redis
threading 多线程
PyQt5

importlib 热更新

场景

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

什么是热更新

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

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

Python的代码是通过module进行组织的,所以,对某些功能的热更新就是可以通过对module更新就可以了。
在Python中,如果重新import 一个已经被import的模块时,并不会重新执行import新的模块。所以,在这个时候,我们希望可以重新加载模块的时候,就需要对模块进行删除后,再重新import进来。
而在sys.modules保存了已经加载过的模块。

为了方便看到展示,我就沿用上次客户端的界面,进行简单修改后,展示给大家看,热更新的效果。

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

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

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

完成了本地测试热更新成功后,就着手实现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这样的浏览器一样,安装插件什么的。

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

扫码关注微信公众号

Python 软件热更新的更多相关文章

  1. python基于函数替换的热更新原理介绍

    热更新即在不重启进程或者不离开Python interpreter的情况下使得被编辑之后的python源码能够直接生效并按照预期被执行新代码.平常开发中,热更能极大提高程序开发和调试的效率,在修复线上 ...

  2. 飘逸的python - 使用reload进行热更新

    一开始我们的游戏商城配置是从txt读取解析的. 后来为了方便运营修改配置,改成从数据库读取并提供后台可视化编辑配置. 如果为了使配置生效而重启游戏进程那太麻烦了. 这时候reload就派上用途了. 下 ...

  3. Java 热更新 Groovy 实践及踩坑指南

    Groovy 是什么? Apache的Groovy是Java平台上设计的面向对象编程语言.这门动态语言拥有类似Python.Ruby和Smalltalk中的一些特性,可以作为Java平台的脚本语言使用 ...

  4. ios WaxPatch热更新原理

    以下是引用他人文章内容: 为什么需要 WaxPatch 很多情况下,已经在 AppStore 上线的应用需要紧急缺陷修复,此时便需要使用某些技术手段,使应用程序能够动态下载补丁,进行缺陷修复. 什么是 ...

  5. paip.提升用户体验--提升java的热部署热更新能力

    paip.提升用户体验--提升java的热部署热更新能力 想让java做到php那么好的热部署能力  "fix online"/在线修复吗??直接在服务器上修改源码生效,无需重启应 ...

  6. Ubuntu系统下面软件安装更新命令

    在ubuntu服务器下安装包的时候,经常会用到sudo apt-get install 包名 或 sudo pip install 包名,那么两者有什么区别呢? 1.区别 pip用来安装来自PyPI( ...

  7. 【Quick 3.3】资源脚本加密及热更新(三)热更新模块

    [Quick 3.3]资源脚本加密及热更新(三)热更新模块 注:本文基于Quick-cocos2dx-3.3版本编写 一.介绍 lua相对于c++开发的优点之一是代码可以在运行的时候才加载,基于此我们 ...

  8. 【Quick 3.3】资源脚本加密及热更新(二)资源加密

    [Quick 3.3]资源脚本加密及热更新(二)资源加密 注:本文基于Quick-cocos2dx-3.3版本编写 一.介绍 在前一篇文章中介绍了代码加密,加密方式是XXTEA.对于资源文件来说,同样 ...

  9. 【Quick 3.3】资源脚本加密及热更新(一)脚本加密

    [Quick 3.3]资源脚本加密及热更新(一)脚本加密 注:本文基于Quick-cocos2dx-3.3版本编写 一.脚本加密 quick框架已经封装好加密模块,与加密有关的文件在引擎目录/quic ...

随机推荐

  1. 异常之: The server time zone value '�й���׼ʱ��' is unrecognized or represents more than one time zone.

    在 MySQL 中执行命令试下: set global time_zone=’+8:00’  设置为东8区 就不报错了. show variables like '%time_zone%'; 解释:在 ...

  2. 数学建模--matlab基础知识

    虽然python也能做数据分析,不过参加数学建模,咱还是用专业的 1. Matlab-入门篇:Hello world! 程序员入门第一式: disp(‘hello world!’) 2. 基本运算 先 ...

  3. loj517 计算几何瞎暴力

    在序列上维护4个操作 1.在序列的尾端添加x 2.输出Al~Ar的和 3.将所有数异或x 4.将序列从小到大排序 第一眼看上去是Splay于是头铁硬刚了一发 后来发现splay没法异或 去百度“维护异 ...

  4. luogu1754卡特兰数

    卡特兰数 打表 滑稽 #include<iostream> #include<cstdio> #include<algorithm> #include<cst ...

  5. html事件绑定总结以及window.onload和document.body.onload事件

    //1 document.onkeydown如果多次监听同样的事件,那么前面的监听函数都会被最后一次的监听函数所覆盖. //如下所示: document.onkeydown = function(ev ...

  6. vijos1779国王游戏

    描述 恰逢H国国庆,国王邀请n位大臣来玩一个有奖游戏.首先,他让每个大臣在左.右手上面分别写下一个整数,国王自己也在左.右手上各写一个整数.然后,让这n位大臣排成一排,国王站在队伍的最前面.排好队后, ...

  7. 二 kafka设计原理

    kafka的设计初衷是希望作为一个统一的信息收集平台,能够实时的收集反馈信息,并需要能够支撑较大的数据量,且具备良好的容错能力. 1.持久性     kafka使用文件存储消息,这就直接决定kafka ...

  8. SQL一对多特殊查询,取唯一一条

    主表: 辅表: 一个app对应多个apk,现在要取上线(Apk_Status最大的)的应用 select * from [dbo].[tbl_APP] as app join ( * from tbl ...

  9. 洛谷-铺地毯-NOIP2011提高组复赛

    题目描述 为了准备一个独特的颁奖典礼,组织者在会场的一片矩形区域(可看做是平面直角坐标系的第一象限)铺上一些矩形地毯.一共有 n 张地毯,编号从 1 到n .现在将这些地毯按照编号从小到大的顺序平行于 ...

  10. mkfs在特定的分区上建立 linux 文件系统

    Linux mkfs命令用于在特定的分区上建立 linux 文件系统 使用方式 : mkfs [-V] [-t fstype] [fs-options] filesys [blocks]参数 :    ...