uWSGI信号框架
警告
uwsgi信号的原始使用只提供给高级用户。对于一个更优雅的抽象,你应该看看 uWSGI API - Python装饰器 。
注解
uWSGI信号量与UNIX/Posix信号量毫无共同之处 (如果你找的是那些,那么 管理uWSGI服务器 才是你要看的)。
随着时间的推移,你的uWSGI堆栈越来越大,你添加spooler,更多的进程,更多的插件,等等。你添加的功能越多,你就越需要所有这些组件之间能彼此通信。
现今丰富/高级的web应用的另一个重要任务是响应不同事件。一个事件可能是一个文件修改,一个新的集群节点冒出来,另一个(黯然)死去,一个定时器时间已经到了……任何你能想象到的事件。
通信和事件管理都由相同的子系统管理 —— uWSGI信号框架。
uWSGI信号是由socket管理的,因此它们 够可靠 。当你发送一个uWSGI信号时,你可以保证它会被转发。
信号表
信号是简单的 1字节 消息,它可以由master进程路由给worker和spooler.
当一个workder接收到一个信号,它会搜索信号表,查找对应的处理程序并执行。
信号表由所有worker共享 (并通过共享锁对抗竞争条件)。
每个uWSGI进程 (虽然主要是master)可以写信号表,以设置信号处理程序和接收进程。
警告
要经常注意谁会运行信号处理程序。它必须能够访问信号处理程序本身。这意味着,如果你在 worker1
中定义一个新的函数,并将其注册为信号处理程序,那么只有 worker1
可以运行它。注册信号最好的方法是在master中定义它们,这样(多亏了 fork()
)所有的worker都能看到它们。
定义信号处理程序
要管理信号表,uWSGI API提供了一个简单的函数, uwsgi.register_signal()
.
下面是两个简单的定义信号表项的例子,分别用Python和Lua编写。
- import uwsgi
- def hello_signal(num):
- print "i am the signal %d" % num
- def hello_signal2(num):
- print "Hi, i am the signal %d" % num
- # define 2 signal table items (30 and 22)
- uwsgi.register_signal(30, "worker", hello_signal)
- uwsgi.register_signal(22, "workers", hello_signal2)
- function hello_signal(sig)
- print("i am Lua, received signal " .. sig ..)
- end
- # define a single signal table item (signal 1)
- uwsgi.register_signal(1, "worker", hello_signal)
信号目标
uwsgi.register_signal的第三个参数是’signal targer’。
它指示系统“谁”必须运行处理程序。默认情况下,目标是targer是表示“第一个可用worker”的’worker’。下面是可用的目标:
- workerN (只在workder N上运行信号处理程序)
- worker/worker0 (默认,在第一个可用的worker上运行信号处理程序)
- workers (在所有worker上运行信号处理程序)
- active-workers (在所有活跃的 [non-cheaped] worker上运行信号处理程序)
- spooler (在第一个可用的spooler上运行信号处理程序)
- mules (在所有的mule上运行信号处理程序)
- muleN (在mule N上运行信号处理程序)
- mule/mule0 (在第一个可用的mule上运行信号处理程序)
- farmN/farm_XXX (在mule farm N或者指定的XXX上运行信号处理程序)
引发信号
可以使用 uwsgi.signal()
引发信号。当你发送一个信号时,它会被拷贝到master的队列中。然后,master会检查信号表并调度消息。
外部事件
uWSGI信号最有用的特性是,它们可以用于宣布外部事件。
编写可用外部事件的时机是
- 文件系统修改
- timer/rb_timer
- cron
其他事件是通过插件暴露出来的,例如,每当一个postgres通知通道准备好时,https://github.com/unbit/uwsgi-pgnotifyj就会引发一个信号。
文件系统修改
要将一个特定的文件/目录修改事件映射到一个信号上,你可以使用 uwsgi.add_file_monitor()
。
一个例子:
- import uwsgi
- def hello_file(num):
- print "/tmp has been modified !!!"
- uwsgi.register_signal(17, "worker", hello_file)
- uwsgi.add_file_monitor(17, "/tmp")
从现在开始,每次 /tmp
被修改时,将会引发信号17,然后第一个可用worker将会运行 hello_file
。
定时器
定时器是web编程中另一个有用特性 —— 例如清理会话、购物车等诸如此类。
定时器是利用内核工具实现的(BSD系统上的kqueue,以及现代Linux内核上的timerfd())。uWSGI还包含对rb_timer的支持,这是一个在用户空间中,使用红黑树实现的定时器。
要注册一个定时器,可以使用 uwsgi.add_timer()
。要注册一个rb_timer,可以使用 uwsgi.add_rb_timer()
。
- import uwsgi
- def hello_timer(num):
- print "2 seconds elapsed, signal %d raised" % num
- def oneshot_timer(num):
- print "40 seconds elapsed, signal %d raised. You will never see me again." % num
- uwsgi.register_signal(26, "worker", hello_timer)
- uwsgi.register_signal(30, "", oneshot_timer)
- uwsgi.add_timer(26, 2) # never-ending timer every 2 seconds
- uwsgi.add_rb_timer(30, 40, 1) # one shot rb timer after 40 seconds
每2秒钟就会引发一次信号26,并且由第一个可用worker处理。40秒过后会引发一次信号30,然后只执行一次。
signal_wait和signal_received
未注册信号(那些没有相关处理程序的)将会路由到第一个可用worker,以使用 uwsgi.signal_wait()
函数。
- uwsgi.signal_wait()
- signum = uwsgi.signal_received()
你可以将外部事件(文件监控、定时器……)和这项技术结合起来,以实现基于事件的应用。一个很好的例子是聊天服务器,其中,每个核等待用户发送的文本。
你也可以通过传递一个信号数字给 signal_wait
来等待一个特定的(甚至注册了的)信号。
待办/已知问题
- 不能移除信号表项(这会尽快解决)
- 迭代只适用于rb_timer
- uwsgi.signal_wait()在异步模式下无效(将会解决)
- 添加迭代到文件监控(以允许定时器的一次事件)