与正在运行的Python守护进程通信

53

我写了一个小的Python应用程序,作为一个守护进程运行。它利用了线程和队列。

我正在寻找通用的方法来修改此应用程序,以便在其运行时与其进行通信。主要是我想能够监视其健康状况。

简而言之,我想能够像这样做:

python application.py start  # launches the daemon

稍后,我想要能够进行如下操作:

python application.py check_queue_size  # return info from the daemonized process

明确一点,我没有问题实现类似Django的语法,但我不知道如何向守护进程发送信号(启动),或者如何编写守护进程以处理和响应此类信号。

像我上面所说的,我正在寻找普遍的方法。目前唯一能想到的方法是告诉守护进程始终将可能需要的所有内容记录到文件中,但我希望有更简单的方法。

更新: 哇,很多好答案,非常感谢。考虑到现在Twisted有些困难,我想看看Pyro和web.py/Werkzeug两种方法,下一个概念性挑战,我想是如何在不挂起工作线程的情况下与它们通信。

再次感谢。

8个回答

36
另一种方法:使用Pyro(Python远程对象)。
Pyro基本上允许您将Python对象实例发布为可以远程调用的服务。我已经使用Pyro来完成您描述的确切目的,并且我发现它工作得非常好。
默认情况下,Pyro服务器守护程序接受来自任何地方的连接。要限制此功能,请使用连接验证器(请参阅文档),或者在Daemon构造函数中提供host='127.0.0.1'以仅侦听本地连接。
从Pyro文档中获取的示例代码: 服务器
import Pyro.core

class JokeGen(Pyro.core.ObjBase):
        def __init__(self):
                Pyro.core.ObjBase.__init__(self)
        def joke(self, name):
                return "Sorry "+name+", I don't know any jokes."
Pyro.core.initServer() daemon=Pyro.core.Daemon() uri=daemon.connect(JokeGen(),"jokegen") print "The daemon runs on port:",daemon.port print "The object's uri is:",uri
daemon.requestLoop()
客户端
import Pyro.core

# you have to change the URI below to match your own host/port.
jokes = Pyro.core.getProxyForURI("PYROLOC://localhost:7766/jokegen")

print jokes.joke("Irmen")
另一个类似的项目是RPyC。我没有尝试过RPyC。

我认为Pyro对于这个问题来说完全是过度设计了。它确实提供了太多的权力和自由,但也在软件中引入了许多新的可能错误。我只会在不同服务器之间进行通信时使用Pyro,而不是在本地使用。你总是有更好的选择,比如Unix信号,在本地环境下更加健壮。根据你的应用逻辑的复杂程度,它可能是不足够的。如果你需要一种类似于中间人的东西(这就是Pyro代理在所有情况下的作用),我建议使用HTTP服务器来接收/发送请求。当然这是个人选择。 - DGoiko
无论如何,老式的TCP监听套接字就足够了,但是像往常一样,存在安全问题。我现在正在制作一个复杂的守护进程,我很想使用Pyro(因为该项目使用pyro创建多服务器远程工作池,因此大多数内容都是以Pyro风格编写的,并且序列化程序已经编写好了)。主类本身继承自线程并按照守护进程的方式工作,并且已经使用Pyro a进行调用并在名称服务器中注册,即使所有这些都完成了,我仍然不愿意将其用作我的本地守护进程入口点。 - DGoiko
7766 是默认端口号吗? - alper

18

那么运行一个 http 服务器怎么样呢?

这听起来很疯狂,但是使用 web.py 只需要几行代码就可以运行一个简单的 Web 服务器来管理您的服务器。

您还可以考虑创建一个 Unix 管道。


此外,HTTP接口也很方便。Python脚本可以解析命令行选项并向内部HTTP服务器发送XMLRPC命令。 - Van Gale
1
+1:HTTP。在守护进程中嵌入一个小的WSGI应用程序以响应请求。 - S.Lott
3
可以请@VanGale和@S.Lott提供一个运行HTTP服务器以接收像楼主描述的命令的参考/示例吗?我需要这样做,但希望有更多细节。 - synaptik
使用HTTP服务器获取错误跟踪日志不难吧? - alper

16

使用Werkzeug,将HTTP-based WSGI服务器包含在您的守护进程中。

您的守护程序有一系列小的WSGI应用程序来响应状态信息。

您的客户端只需使用urllib2向localhost:somePort进行POST或GET请求。 您的客户端和服务器必须就端口号(以及URL)达成共识。

这很容易实现并且非常可扩展。 添加新命令是一个微不足道的练习。

请注意,您的守护进程不必以HTML格式响应(尽管通常很简单)。 我们的守护程序使用JSON编码的状态对象响应WSGI请求。


在使用werkzeug时,我们如何获取错误响应及其跟踪信息? - alper

9
我会使用Twisted与命名管道或打开套接字。请查看回声服务器和客户端examples。您需要修改回声服务器以检查客户端传递的某些字符串,然后用请求的任何信息进行响应。
由于Python的线程问题,您在同时继续执行守护程序的任务时可能会遇到响应信息请求的麻烦。异步技术或分叉另一个进程是您唯一的真正选择。

1
+1 for Twisted,还可以参考twisted.manhole,它提供了直接进入运行解释器的telnet界面:http://twistedmatrix.com/projects/core/documentation/howto/telnet.html - Van Gale
如果解释器获取了GIL并执行某些长时间运行的操作,那么当然会阻止其他线程被服务。关键是普通人很难预测何时GIL会发挥作用并引起线程问题。 - MrEvil
在使用popen调用PGP命令行时,我曾经遇到过这个问题。因此,你关于GIL只被锁定一条指令的评论是胡说八道。同时,正如文档清楚地指出的那样,这种行为是不确定的。请参考Python/C API参考资料来证实。 - MrEvil
以下哪个不算是“线程问题”,是GIL、有缺陷的C代码或者是popen导致死锁?所有这些问题都会导致线程无法预测地失效,因此需要程序员要么分叉进程,要么使用 Twisted。 - MrEvil
1
只有在父进程中对管道进行顺序读/写的基本错误时,Popen才会导致死锁。这对于任何语言都是正确的,不仅仅是Python。在阻塞操作之前不释放锁定也是如此。因此,以上两点都不算是Python线程问题。 - Rafał Dowgird
显示剩余3条评论

7
# your server

from twisted.web import xmlrpc, server
from twisted.internet import reactor

class MyServer(xmlrpc.XMLRPC):

    def xmlrpc_monitor(self, params):        
        return server_related_info

if __name__ == '__main__':
    r = MyServer()
    reactor.listenTCP(8080, Server.Site(r))
    reactor.run()

客户端可以使用xmlrpclib编写,示例代码在这里检查。


你可以轻松地编写服务器和客户端,而不依赖于Twisted,但这是一个好的选择。 - Brian Cain

5
假设您正在使用*nix,您可以从shell中使用kill向正在运行的程序发送信号(在许多其他环境中也有类似的功能)。要从python内部处理它们,请查看signal模块。

你能通过 kill 发送任何信号吗?如果不能,也许可以将这个答案重新表述为“据我所知,kill 只能发送一个 'kill' 信号,在这里并不特别有用。” - puk
@puk,你可以使用'-s'参数发送其他信号,例如'kill -s QUIT <pid>'。 - Keith Hughitt
@puk kill并不是真正的杀死进程。它会向进程发送您指定的信号(例如kill -9,如果我没记错的话,这是默认值)。据我所知,它被称为kill是出于历史原因。 - DGoiko

5
你可以将它与Pyro(http://pythonhosted.org/Pyro4/)关联,这是一个Python远程对象。它允许你远程访问Python对象,易于实现,开销低,不像Twisted那样具有侵入性。

我觉得你提供的 Pyro 的链接是其他 pyro(热力学分析软件),而不是你认为的那个(或者至少现在是这样)。 - ironstein
七年时间可以改变很多事情。我已经更新了当前的代码库。 - directedition

0
你可以使用 multiprocessing 管理器(https://docs.python.org/3/library/multiprocessing.html#managers)来实现这一点:

管理器提供了一种创建可在不同进程之间共享数据的方式,包括在不同机器上运行的进程之间通过网络共享。管理器对象控制一个服务器进程,该进程管理共享对象。其他进程可以通过使用代理访问共享对象。

示例服务器:

from multiprocessing.managers import BaseManager

class RemoteOperations:
    def add(self, a, b):
        print('adding in server process!')
        return a + b

    def multiply(self, a, b):
        print('multiplying in server process!')
        return a * b

class RemoteManager(BaseManager):
    pass

RemoteManager.register('RemoteOperations', RemoteOperations)

manager = RemoteManager(address=('', 12345), authkey=b'secret')
manager.get_server().serve_forever()

示例客户端:

from multiprocessing.managers import BaseManager

class RemoteManager(BaseManager):
    pass

RemoteManager.register('RemoteOperations')
manager = RemoteManager(address=('localhost', 12345), authkey=b'secret')
manager.connect()

remoteops = manager.RemoteOperations()
print(remoteops.add(2, 3))
print(remoteops.multiply(2, 3))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接