11.4 面向服务架构
考虑到前面阐述的问题和解决方案,Python在解决大型复杂应用的可扩展性方面的问题似乎难以规避。然而,Python在实现面向服务架构(Service-Oriented Architecture,SOA)方面的表现是非常优秀的。如果不熟悉这方面的话,线上有大量相关的文档和评论。
SOA是OpenStack所有组件都在使用的架构。组件通过HTTP REST和外部客户端(终端用户)进行通信,并提供一个可支持多个连接协议的抽象RPC机制,最常用的就是AMQP。
在你自己的场景中,模块之间沟通渠道的选择关键是要明确将要和谁进行通信。
当需要暴露API给外界时,目前最好的选择是HTTP,并且最好是无状态设计,例如REST(Representational state transfer)风格的架构。这类架构非常容易实现、扩展、部署和理解。
然而,当在内部暴露和使用API时,使用HTTP可能并非最好的协议。有大量针对应用程序的通信协议存在,对任何一个协议的详尽描述都需要一整本书的篇幅。
在Python中,有许多库可以用来构建RPC(Remote Procedure Call)系统。Kombu(http://kombu.readthedocs.org/en/latest/)与其他相比是最有意思的一个,因为它提供了一种基于很多后端的RPC机制。AMQ协议(http://www.amqp.org/)是主要的一个。但同样支持Redis(http://redis.io/)、MongoDB(https://www.mongodb.org/)、BeanStalk(http://kr.github.io/beanstalkd/)、Amazon SQS(http://aws.amazon.com/cn/sqs/)、CouchDB(http://couchdb.apache.org/)或者ZooKeeper(http://zookeeper.apache.org/)。
最后,使用这样松耦合架构的间接收益是巨大的。如果考虑让每个模块都提供并暴露API,那么可以运行多个守护进程暴露这些API。例如,Apache httpd将使用一个新的系统进程为每一个连接创建一个新worker,因而可以将连接分发到同一个计算节点的不同worker上。要做的只是需要有一个系统在worker之间负责分发工作,这个系统提供了相应的API。每一块都将是一个不同的Python进程,正如我们在上面看到的,在分发工作负载时这样做要比用多线程好。可以在每个计算节点上启动多个worker。尽管不必如此,但是在任何时候,能选择的话还是最好使用无状态的组件。
ZeroMQ(http://zeromq.org/)是个套接字库,可以作为并发框架使用。下面的例子实现了和前面例子中同样的worker,但是利用了ZeroMQ作为分发和通信的手段。
使用ZeroMQ的Worker
import multiprocessing import random import zmq def compute(): return sum( [random.randint(1, 100) for i in range(1000000)]) def worker(): context = zmq.Context() work_receiver = context.socket(zmq.PULL) work_receiver.connect("tcp://.0:5555") result_sender = context.socket(zmq.PUSH) result_sender.connect("tcp://.0:5556") poller = zmq.Poller() poller.register(work_receiver, zmq.POLLIN) while True: socks = dict(poller.poll()) if socks.get(work_receiver) == zmq.POLLIN: obj = work_receiver.recv_pyobj() result_sender.send_pyobj(obj()) context = zmq.Context() # Build a channel to send work to be done work_sender = context.socket(zmq.PUSH) work_sender.bind("tcp://.0:5555") # Build a channel to receive computed results result_receiver = context.socket(zmq.PULL) result_receiver.bind("tcp://.0:5556") # Start 8 workers processes = [] for x in range(8): p = multiprocessing.Process(target=worker) p.start() processes.append(p) # Start 8 jobs for x in range(8): work_sender.send_pyobj(compute) # Read 8 results results = [] for x in range(8): results.append(result_receiver.recv_pyobj()) # Terminate all processes for p in processes: p.terminate() print("Results: %s" % results)
如你所见,ZeroMQ提供了非常简单的方式来建立通信信道。我这里选用了TCP传输层,表明我们可以在网络中运行这个程序。应该注意的是,ZeroMQ也提供了利用Unix套接字的inproc信道。显然在这个例子中,基于ZeroMQ构造的通信协议是非常简单的,这是为了保持本书中的例子尽量清晰和简洁,不难想象基于其上建立一个更为复杂的通信层。
通过这种协议,不难想象通过网络消息总线(如ZeroMQ、AMQP等)构建一个完全分布式的应用程序间通信。
注意,类似HTTP、ZeroMQ或者AMQP这样的协议是同语言无关的。可以使用不同的语言和平台构建系统的各个部分。尽管我们都认同Python是一门优秀的语言,但其他团队也许有他们的偏好,或者对于问题的某个部分,其他语言可能是更好的选择。
最后,使用传输总线(transport bus)解耦应用是一个好的选择。它允许你建立同步和异步API,从而轻松地从一台计算机扩展到几千台。它不会将你限制在一种特定技术或语言上,现如今,没理由不将软件设计为分布式的,或者受任何一种语言的限制。
1或者如果多个CPU不存在的话,依次在某一个处理器上。
2用C语言开发的Python参考实现,也就是通常在shell中输入python之后运行的那个。
3尽管是使用最普遍的。
4关于这个的进一步阅读,可以看看C10K问题(http://www.kegel.com/c10k.html#nb.kqueue)。
5 Asynchronous IO Support Rebooted: “asyncio” Module, Guido van Rossum, 2012
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论