进程管理工具Supervisor(二)Events
supervisor可以当做一个简单的进程启动、重启、控制工具使用,也可以作为一个进程监控框架使用,作为后者,需要使用supervisor的Events机制。
Event Listeners
supervisor对子程序的监控通过叫做event listener的程序实现。supervisor控制的子程序状态发生变化时,就会产生一些事件通知,event listener可以对这些事件通知进行订阅。
event listener本身也是作为supervisor的子程序运行的。事件通知协议的实现基于event listener子程序的stdin和stdout。supervisor发送特定格式的信息到event listener的stdin,然后从event listener的stdout获得特定格式的输出,从而形成一个请求/应答循环。
配置
event listener的配置放置于配置文件中的[eventlistener:x]块中。
1 2 3 | [eventlistener:mylistener] command = my_custom_listener.py events = PROCESS_STATE,TICK_60 |
x是listener的名称,command是执行listener脚本的命令,events是要监控的事件类型。
event listener本身是作为supervisor的子程序运行的,所以与配置子程序[program:x]块类似,官网例子:
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 | [eventlistener:theeventlistenername] command = / bin / eventlistener process_name = % (program_name)s_ % (process_num) 02d numprocs = 5 events = PROCESS_STATE buffer_size = 10 directory = / tmp umask = 022 priority = - 1 autostart = true autorestart = unexpected startsecs = 1 startretries = 3 exitcodes = 0 , 2 stopsignal = QUIT stopwaitsecs = 10 stopasgroup = false killasgroup = false user = chrism redirect_stderr = false stdout_logfile = / a / path stdout_logfile_maxbytes = 1MB stdout_logfile_backups = 10 stdout_events_enabled = false stderr_logfile = / a / path stderr_logfile_maxbytes = 1MB stderr_logfile_backups = 10 stderr_events_enabled = false environment = A = "1" ,B = "2" serverurl = AUTO |
事件通知协议
一个event listener可以处于三种状态,ACKNOWLEDGED、READY、BUSY,只有在READY状态下才可以接收事件通知。
event listener启动时处于ACKNOWLEDGED状态,直到event listener向stdout中输出“READY\n”字符串为止。
event listener向stdout中输出“READY\n”之后就处于READY状态,supervisor会向处于READY状态的listener发送listener订阅的事件通知。
listener接收事件通知之后就处于BUSY状态,期间listener对接收到的事件通知进行处理,处理结束后向stdout输出“RESULT 2\nOK”或者“RESULT 4\nFAIL”,前者代表处理成功,后者代表处理失败。
supervisor收到OK或者FAIL输出后,就将event listener的状态置于ACKNOWLEDGED。FAIL的事件通知会被缓存然后再次发送。
event listener的状态处于ACKNOWLEDGED后可以退出执行,也可以继续执行,继续执行就可以向stdout输出“READY\n”形成一个循环。
supervisor向listener发送的事件通知由两部分组成,header和body,由"\n"换行符分开。
一个header例子:
1 | ver: 3.0 server:supervisor serial: 21 pool:listener poolserial: 10 eventname:PROCESS_COMMUNICATION_STDOUT len : 54 |
ver:协议版本
server:supervisor的标识符,由[supervisord]块中的identifier选项设置。
serial:event的序列号
pool:listener的pool的名字。
poolserial:event在pool中的的序列号
eventname:event类型名称
len:header后面的body长度。
一个body例子:
1 2 | processname:foo groupname:bar pid: 123 This is the data that was sent between the tags |
processname:事件所属的子进程名字
groupname:子进程所属组名
pid:子进程pid
一个简单的listener脚本,listener.py:
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 | import sys def write_stdout(s): # only eventlistener protocol messages may be sent to stdout sys.stdout.write(s) sys.stdout.flush() def write_stderr(s): sys.stderr.write(s) sys.stderr.flush() def main(): while True : # 进入READY状态 ┆ write_stdout( 'READY\n' ) ┆ # 读取事件通知的header ┆ line = sys.stdin.readline() ┆ write_stderr(line) # 获取body长度,读取body ┆ headers = dict ([x.split( ':' ) for x in line.split() ]) ┆ data = sys.stdin.read( int (headers[ 'len' ])) ┆ write_stderr(data + '\n' ) # 发送OK进入ACKNOWLEDGED状态 ┆ write_stdout( 'RESULT 2\nOK' ) if __name__ = = '__main__' : main() |
在conf.d目录中建立一个listener配置文件mylistener.conf:
1 2 3 4 5 6 7 | [eventlistener:mylistener] command = python listener.py directory = / thedirectoroflistener.py user = user events = PROCESS_STATE,TICK_5 stdout_logfile = / path / to / mylistener_stdout.log stderr_logfile = / path / to / mylistener_stderr.log |
启动:
1 2 3 4 5 6 | ubuntu:$ sudo supervisorctl start all mylistener: started celerybeat: started ubuntu:$ sudo supervisorctl status celerybeat RUNNING pid 87729 , uptime 0 : 00 : 20 mylistener RUNNING pid 87728 , uptime 0 : 00 : 20 |
监控就开始了,可以到日志中查看事件通知的内容:
1 2 3 4 5 6 | ver: 3.0 server:supervisor serial: 15361 pool:mylistener poolserial: 15361 eventname:PROCESS_STATE_RUNNING len : 73 processname:mylistener groupname:mylistener from_state:STARTING pid: 87728 ver: 3.0 server:supervisor serial: 15362 pool:mylistener poolserial: 15362 eventname:TICK_5 len : 15 when: 1514313560 ver: 3.0 server:supervisor serial: 15364 pool:mylistener poolserial: 15364 eventname:PROCESS_STATE_RUNNING len : 73 processname:celerybeat groupname:celerybeat from_state:STARTING pid: 87729 |
可以根据自己的需要设定监控的事件类型,然后根据不同的事件类型和内容做出不同的应变,具体的事件类型可以官网查看。
python的supervisor.childutils模块对header和body的处理进行了包装:
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 | def get_headers(line): return dict ([ x.split( ':' ) for x in line.split() ]) def eventdata(payload): headerinfo, data = payload.split( '\n' , 1 ) headers = get_headers(headerinfo) return headers, data def get_asctime(now = None ): if now is None : # for testing now = time.time() # pragma: no cover msecs = (now - long (now)) * 1000 part1 = time.strftime( "%Y-%m-%d %H:%M:%S" , time.localtime(now)) asctime = '%s,%03d' % (part1, msecs) return asctime class ProcessCommunicationsProtocol: def send( self , msg, fp = sys.stdout): fp.write(ProcessCommunicationEvent.BEGIN_TOKEN) fp.write(msg) fp.write(ProcessCommunicationEvent.END_TOKEN) def stdout( self , msg): return self .send(msg, sys.stdout) def stderr( self , msg): return self .send(msg, sys.stderr) pcomm = ProcessCommunicationsProtocol() class EventListenerProtocol: def wait( self , stdin = sys.stdin, stdout = sys.stdout): self .ready(stdout) line = stdin.readline() headers = get_headers(line) payload = stdin.read( int (headers[ 'len' ])) return headers, payload def ready( self , stdout = sys.stdout): stdout.write(PEventListenerDispatcher.READY_FOR_EVENTS_TOKEN) stdout.flush() def ok( self , stdout = sys.stdout): self .send( 'OK' , stdout) def fail( self , stdout = sys.stdout): self .send( 'FAIL' , stdout) def send( self , data, stdout = sys.stdout): resultlen = len (data) result = '%s%s\n%s' % (PEventListenerDispatcher.RESULT_TOKEN_START, str (resultlen), data) stdout.write(result) stdout.flush() listener = EventListenerProtocol() |
listener脚本可以方便的写做:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | import sys from supervisor import childutils def write_stdout(s): # only eventlistener protocol messages may be sent to stdout sys.stdout.write(s) sys.stdout.flush() def write_stderr(s): sys.stderr.write(s) sys.stderr.flush() def main(): while True : ┆ headers, payload = childutils.listener.wait() ┆ write_stderr(payload + '\n' ) ┆ childutils.listener.ok(sys.stdout) if __name__ = = '__main__' : main() |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具
· Manus的开源复刻OpenManus初探