脚本之家,脚本语言编程技术及教程分享平台!
分类导航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服务器之家 - 脚本之家 - Python - 基于Python实现配置热加载的方法详解

基于Python实现配置热加载的方法详解

2022-07-10 11:59烟熏柿子学编程 Python

所谓的配置热加载,也就是说当服务收到配置更新消息之后,我们不用重启服务就可以使用最新的配置去执行任务。本文将介绍如何用Python实现配置热加载,需要的可以参考一下

背景

由于最近工作需求,需要在已有项目添加一个新功能,实现配置热加载的功能。所谓的配置热加载,也就是说当服务收到配置更新消息之后,我们不用重启服务就可以使用最新的配置去执行任务。

如何实现

下面我分别采用多进程、多线程、协程的方式去实现配置热加载。

使用多进程实现配置热加载

如果我们代码实现上使用多进程, 主进程1来更新配置并发送指令,任务的调用是进程2,如何实现配置热加载呢?

使用signal信号量来实现热加载

基于Python实现配置热加载的方法详解

当主进程收到配置更新的消息之后(配置读取是如何收到配置更新的消息的? 这里我们暂不讨论), 主进程就向进子程1发送kill信号,子进程1收到kill的信号就退出,之后由信号处理函数来启动一个新的进程,使用最新的配置文件来继续执行任务。

main 函数

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def main():
    # 启动一个进程执行任务
    p1 = Process(target=run, args=("p1",))
    p1.start()
 
    monitor(p1, run) # 注册信号
    processes["case100"] = p1 #将进程pid保存
    num = 0
    while True: # 模拟获取配置更新
        print(
            f"{multiprocessing.active_children()=}, count={len(multiprocessing.active_children())}\n")
        print(f"{processes=}\n")
        sleep(2)
        if num == 4:
            kill_process(processes["case100"]) # kill 当前进程
        if num == 8:
            kill_process(processes["case100"]) # kill 当前进程
        if num == 12:
            kill_process(processes["case100"]) # kill 当前进程
        num += 1

signal_handler 函数

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def signal_handler(process: Process, func, signum, frame):
    # print(f"{signum=}")
    global counts
 
    if signum == 17# 17 is SIGCHILD
        # 这个循环是为了忽略SIGTERM发出的信号,避免抢占了主进程发出的SIGCHILD
        for signame in [SIGTERM, SIGCHLD, SIGQUIT]:
            signal.signal(signame, SIG_DFL)
 
        print("Launch a new process")
        p = multiprocessing.Process(target=func, args=(f"p{counts}",))
        p.start()
        monitor(p, run)
        processes["case100"] = p
        counts += 1
 
    if signum == 2:
        if process.is_alive():
            print(f"Kill {process} process")
            process.terminate()
        signal.signal(SIGCHLD, SIG_IGN)
        sys.exit("kill parent process")

完整代码如下

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#! /usr/local/bin/python3.8
from multiprocessing import Process
from typing import Dict
import signal
from signal import SIGCHLD, SIGTERM, SIGINT, SIGQUIT, SIG_DFL, SIG_IGN
import multiprocessing
from multiprocessing import Process
from typing import Callable
from data import processes
import sys
from functools import partial
import time
 
processes: Dict[str, Process] = {}
counts = 2
 
 
def run(process: Process):
    while True:
        print(f"{process} running...")
        time.sleep(1)
 
 
def kill_process(process: Process):
    print(f"kill {process}")
    process.terminate()
 
 
def monitor(process: Process, func: Callable):
    for signame in [SIGTERM, SIGCHLD, SIGINT, SIGQUIT]:
        # SIGTERM is kill signal.
        # No SIGCHILD is not trigger singnal_handler,
        # No SIGINT is not handler ctrl+c,
        # No SIGQUIT is RuntimeError: reentrant call inside <_io.BufferedWriter name='<stdout>'>
        signal.signal(signame, partial(signal_handler, process, func))
 
 
def signal_handler(process: Process, func, signum, frame):
    print(f"{signum=}")
    global counts
 
    if signum == 17# 17 is SIGTERM
        for signame in [SIGTERM, SIGCHLD, SIGQUIT]:
            signal.signal(signame, SIG_DFL)
        print("Launch a new process")
        p = multiprocessing.Process(target=func, args=(f"p{counts}",))
        p.start()
        monitor(p, run)
        processes["case100"] = p
        counts += 1
 
    if signum == 2:
        if process.is_alive():
            print(f"Kill {process} process")
            process.terminate()
        signal.signal(SIGCHLD, SIG_IGN)
        sys.exit("kill parent process")
 
 
def main():
    p1 = Process(target=run, args=("p1",))
    p1.start()
    monitor(p1, run)
    processes["case100"] = p1
    num = 0
    while True:
        print(
            f"{multiprocessing.active_children()=}, count={len(multiprocessing.active_children())}\n")
        print(f"{processes=}\n")
        time.sleep(2)
        if num == 4:
            kill_process(processes["case100"])
        if num == 8:
            kill_process(processes["case100"])
        if num == 12:
            kill_process(processes["case100"])
        num += 1
 
 
if __name__ == '__main__':
    main()

执行结果如下

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
multiprocessing.active_children()=[<Process name='Process-1' pid=2533 parent=2532 started>], count=1
 
processes={'case100': <Process name='Process-1' pid=2533 parent=2532 started>}
 
p1 running...
p1 running...
kill <Process name='Process-1' pid=2533 parent=2532 started>
multiprocessing.active_children()=[<Process name='Process-1' pid=2533 parent=2532 started>], count=1
 
processes={'case100': <Process name='Process-1' pid=2533 parent=2532 started>}
 
signum=17
Launch a new process
p2 running...
p2 running...
multiprocessing.active_children()=[<Process name='Process-2' pid=2577 parent=2532 started>], count=1
 
processes={'case100': <Process name='Process-2' pid=2577 parent=2532 started>}
 
p2 running...
p2 running...
multiprocessing.active_children()=[<Process name='Process-2' pid=2577 parent=2532 started>], count=1
 
processes={'case100': <Process name='Process-2' pid=2577 parent=2532 started>}
 
p2 running...
p2 running...
multiprocessing.active_children()=[<Process name='Process-2' pid=2577 parent=2532 started>], count=1
 
processes={'case100': <Process name='Process-2' pid=2577 parent=2532 started>}
 
p2 running...
p2 running...
kill <Process name='Process-2' pid=2577 parent=2532 started>
signum=17
Launch a new process
multiprocessing.active_children()=[<Process name='Process-2' pid=2577 parent=2532 stopped exitcode=-SIGTERM>], count=1
 
processes={'case100': <Process name='Process-3' pid=2675 parent=2532 started>}
 
p3 running...
p3 running...
multiprocessing.active_children()=[<Process name='Process-3' pid=2675 parent=2532 started>], count=1

总结

好处:使用信号量可以处理多进程之间通信的问题。

坏处:代码不好写,写出来代码不好理解。信号量使用必须要很熟悉,不然很容易自己给自己写了一个bug.(所有初学者慎用,老司机除外。)

还有一点不是特别理解的就是process.terminate() 发送出信号是SIGTERM number是15,但是第一次signal_handler收到信号却是number=17,如果我要去处理15的信号,就会导致前一个进程不能kill掉的问题。欢迎有对信号量比较熟悉的大佬,前来指点迷津,不甚感谢。

采用multiprocessing.Event 来实现配置热加载

实现逻辑是主进程1 更新配置并发送指令。进程2启动调度任务。

这时候当主进程1更新好配置之后,发送指令给进程2,这时候的指令就是用Event一个异步事件通知。

直接上代码

scheduler 函数

?
1
2
3
4
5
6
7
8
9
10
11
12
13
def scheduler():
    while True:
        print('wait message...')
        case_configurations = scheduler_notify_queue.get()
        print(f"Got case configurations {case_configurations=}...")
 
        task_schedule_event.set() # 设置set之后, is_set 为True
 
        print(f"Schedule will start ...")
        while task_schedule_event.is_set(): # is_set 为True的话,那么任务就会一直执行
            run(case_configurations)
 
        print("Clearing all scheduling job ...")

event_scheduler 函数

?
1
2
3
4
5
6
7
8
9
def event_scheduler(case_config):
 
    scheduler_notify_queue.put(case_config)
    print(f"Put cases config to the Queue ...")
 
    task_schedule_event.clear() # clear之后,is_set 为False
    print(f"Clear scheduler jobs ...")
 
    print(f"Schedule job ...")

完整代码如下

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import multiprocessing
import time
 
 
scheduler_notify_queue = multiprocessing.Queue()
task_schedule_event = multiprocessing.Event()
 
 
def run(case_configurations: str):
    print(f'{case_configurations} running...')
    time.sleep(3)
 
 
def scheduler():
    while True:
        print('wait message...')
        case_configurations = scheduler_notify_queue.get()
 
        print(f"Got case configurations {case_configurations=}...")
        task_schedule_event.set()
 
        print(f"Schedule will start ...")
        while task_schedule_event.is_set():
            run(case_configurations)
 
        print("Clearing all scheduling job ...")
 
 
def event_scheduler(case_config: str):
 
    scheduler_notify_queue.put(case_config)
    print(f"Put cases config to the Queue ...")
 
    task_schedule_event.clear()
    print(f"Clear scheduler jobs ...")
 
    print(f"Schedule job ...")
 
 
def main():
    scheduler_notify_queue.put('1')
    p = multiprocessing.Process(target=scheduler)
    p.start()
 
    count = 1
    print(f'{count=}')
    while True:
        if count == 5:
            event_scheduler('100')
        if count == 10:
            event_scheduler('200')
        count += 1
        time.sleep(1)
 
 
if __name__ == '__main__':
    main()

执行结果如下

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
wait message...
Got case configurations case_configurations='1'...
Schedule will start ...
1 running...
1 running...
Put cases config to the Queue ...
Clear scheduler jobs ...
Schedule job ...
Clearing all scheduling job ...
wait message...
Got case configurations case_configurations='100'...
Schedule will start ...
100 running...
Put cases config to the Queue ...
Clear scheduler jobs ...
Schedule job ...
Clearing all scheduling job ...
wait message...
Got case configurations case_configurations='200'...
Schedule will start ...
200 running...
200 running...

总结

使用Event事件通知,代码不易出错,代码编写少,易读。相比之前信号量的方法,推荐大家多使用这种方式。

使用多线程或协程的方式,其实和上述实现方式一致。唯一区别就是调用了不同库中,queue和 event.

?
1
2
3
4
5
6
7
# threading
scheduler_notify_queue = queue.Queue()
task_schedule_event = threading.Event()
 
# async
scheduler_notify_queue = asyncio.Queue()
task_schedule_event = asyncio.Event()

结语

具体的实现的方式有很多,也各自有各自的优劣势。我们需要去深刻理解到需求本身,才去做技术选型。

以上就是基于Python实现配置热加载的方法详解的详细内容,更多关于Python配置热加载的资料请关注服务器之家其它相关文章!

原文链接:https://www.cnblogs.com/aaron-948/p/16459059.html

延伸 · 阅读

精彩推荐
  • Pythonpython3对接mysql数据库实例详解

    python3对接mysql数据库实例详解

    这篇文章主要介绍了python3对接mysql数据库,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小...

    RainbowJhon11292021-06-22
  • PythonPython PyQt5中弹出子窗口解决子窗口一闪而过的问题

    Python PyQt5中弹出子窗口解决子窗口一闪而过的问题

    这篇文章主要介绍了Python PyQt5中弹出子窗口解决子窗口一闪而过的问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价...

    狂野小青年13162021-09-26
  • PythonPython深度学习神经网络基本原理

    Python深度学习神经网络基本原理

    人工神经网络(Artificial Neural Networks,简写为ANNs)也简称为神经网络(NNs)或称作连接模型(Connection Model),它是一种模仿动物神经网络行为特征,进行分...

    GSAU-深蓝工作室6262022-02-13
  • PythonPyQt5 关于Qt Designer的初步应用和打包过程详解

    PyQt5 关于Qt Designer的初步应用和打包过程详解

    Qt Designer中的操作方式十分灵活,其通过拖拽的方式放置控件可以随时查看控件效果。这篇文章主要介绍了PyQt5 关于Qt Designer的初步应用和打包,需要的朋友...

    氢键H-H6042022-01-11
  • Pythonpython处理图片之PIL模块简单使用方法

    python处理图片之PIL模块简单使用方法

    这篇文章主要介绍了python处理图片之PIL模块简单使用方法,涉及Python使用PIL模块实现针对图片的锐化、绘制直线、绘制椭圆等相关技巧,需要的朋友可以参考...

    像风一样的自由5042020-06-27
  • Pythonpython中数组和矩阵乘法及使用总结(推荐)

    python中数组和矩阵乘法及使用总结(推荐)

    这篇文章主要介绍了python中数组和矩阵乘法及使用总结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友...

    manjhOK7432021-06-28
  • Python解决python3输入的坑——input()

    解决python3输入的坑——input()

    这篇文章主要介绍了解决python3输入的坑——input(),具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    暴网雪易6552021-08-09
  • Pythonpython对html过滤处理的方法

    python对html过滤处理的方法

    今天小编就为大家分享一篇python对html过滤处理的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    striverpan4892021-04-10