本文的例子来自 Pyro4 官网的 tutorial。这年头,Python的包各种强大,通过Pyro可以轻松进行分布式计算。具体说来,即使被调用方法的对象在远程服务器,Pyro 都可以让我们轻松调用。
+----------+ +----------+
| server A | | server B |
| | < network > | |
| Python | | Python |
| OBJECT ----------foo.invoke()--------> OBJECT |
| | | foo |
+----------+ +----------+
概念
- 代理(Proxy)
“代理”是真实对象的替代品。通过操作“代理”,好像被调用方法的对象就在本地。尽管实际操作是在远程服务器上完成的,之后结果会返回给本机调用“代理”的程序。 - URI
- Pyro对象(Pyro object)
是经过Pyro登记的Python对象,经过登记就可以在其他服务器上调用它。类也可以是Pyro对象,之后你就可以通过远程调用告诉Pyro如何创建该类的对象了。本文的示例中,是把类作为Pyro对象。 - Pyro守护进程(Pyro daemon)
这是 Pyro 负责监听调用的部分,并将调用分配给正确的对象,并将得到的结果返回给调用者。所有的Pyro对象,都应该登记到一个或者多个守护进程上。 - Pyro命名服务器(Pyro name server)
命名服务器为Pyro应用提供地址查询簿。远程对象在Pyro中的名字是逻辑名称,通过命名服务器可以得到精确地地址,从而跟远程对象进行交互。 - 序列化(Serialization)
就是将对象转换为比特流的过程,便于网络传输。接收方收到后会进行反序列化(deserialize),得到原来的对象。远程调用方法的参数和返回的数据都要进行序列化。注意,并不是所有对象都可以进行序列化,所有可能存在一些情况,本地可以正常调用,而无法通过 Pyro 进行远程调用。序列化的操作,由Pyro实现,我们无需操作。对Python中序列化有兴趣的,请见此文。
示例
1. 本地实现
首先在本地实现代码:有一个Warehouse
类,它的实例表示某个具体的仓库,负责保管物品;还有一个Person
类,它的实例表示某个人,他可以去仓库存取物品。
在这个例子中,Janet 和 Henry要去本地的仓库存取物品。既然仓库在本地,当然就很方便了。
class Warehouse(object):
def __init__(self):
self.contents = ["chair", "bike", "flashlight", "laptop", "couch"]
def list_contents(self):
return self.contents
def take(self, name, item):
self.contents.remove(item)
print("{0} took the {1}.".format(name, item))
def store(self, name, item):
self.contents.append(item)
print("{0} stored the {1}.".format(name, item))
import sys
class Person(object):
def __init__(self, name):
self.name = name
def visit(self, warehouse):
print("This is {0}.".format(self.name))
self.deposit(warehouse)
self.retrieve(warehouse)
print("Thank you, come again!")
def deposit(self, warehouse):
print("The warehouse contains:", warehouse.list_contents())
item = input("Type a thing you want to store (or empty): ").strip()
if item:
warehouse.store(self.name, item)
def retrieve(self, warehouse):
print("The warehouse contains:", warehouse.list_contents())
item = input("Type something you want to take (or empty): ").strip()
if item:
warehouse.take(self.name, item)
最后写一个脚本来运行,如下:
# This is the code that runs this example.
from warehouse import Warehouse
from person import Person
warehouse = Warehouse()
janet = Person("Janet")
henry = Person("Henry")
janet.visit(warehouse)
henry.visit(warehouse)
运行脚本,得到输出如下:
$ python visit.py
This is Janet.
The warehouse contains: ['chair', 'bike', 'flashlight', 'laptop', 'couch']
Type a thing you want to store (or empty): television # typed in
Janet stored the television.
The warehouse contains: ['chair', 'bike', 'flashlight', 'laptop', 'couch', >>> 'television']
Type something you want to take (or empty): couch # <-- typed in
Janet took the couch.
Thank you, come again!
This is Henry.
The warehouse contains: ['chair', 'bike', 'flashlight', 'laptop', 'television']
Type a thing you want to store (or empty): bricks # <-- typed in
Henry stored the bricks.
The warehouse contains: ['chair', 'bike', 'flashlight', 'laptop', 'television', 'bricks']
Type something you want to take (or empty): bike # <-- typed in
Henry took the bike.
Thank you, come again!
2. 分布式实现
但是顾客总比商家多,总有些小地方没有被商家覆盖。这该如何是好?
到分布式这个例子上,Janet和Henry两人在A地,他们要去仓库存取物品,但仓库主体都在B地。有Pyro在,就不用他们大老远跑了。首先仓库要拿到异地办理的资质(成为Pyro对象),然后在A地开一个门面(代理)。Jenet和Henry两人通过门面,就可以存取物品了。门面会把相应物品打包装箱(序列化),然后运到B地,再卸车拆包(反序列化)。仓库运营方(Pyro守护进程)会按要存入物品,再将两人要取的物品找出,装箱打包运到A地,再装卸取件。
import Pyro4
@Pyro4.expose
@Pyro4.behavior(instance_mode="single")
class Warehouse(object):
def __init__(self):
self.contents = ["chair", "bike", "flashlight", "laptop", "couch"]
def list_contents(self):
return self.contents
def take(self, name, item):
self.contents.remove(item)
print("{0} took the {1}.".format(name, item))
def store(self, name, item):
self.contents.append(item)
print("{0} stored the {1}.".format(name, item))
def main():
Pyro4.Daemon.serveSimple(
{
Warehouse: "example.warehouse"
},
ns = False)
if __name__=="__main__":
main()
首先在命令行中运行这部分代码,如下:
$ python warehouse.py
Object <__main__.Warehouse object at 0x025F4FF0>:
uri = PYRO:example.warehouse@localhost:51279
Pyro daemon running.
这部分代码中的 @Pyro4.expose
表示这个类可以进行远程操作,就是仓库要拿到异地办理的执照。因为这里是将一个类注册为Pyro对象,所以要添加@Pyro4.behavior
。instance_mode="single"
表示实例被创建后需要负责之后所有的方法调用,参考官方文档。
最后使用main
方法,将Warehouse
类这个Pyro对象登记到了一个守护进程。仓库有公司进行运营,正式开业啦。运行得到的uri,就是仓库的地址啦,后面需要通过这个地址来找它。
函数Person类
的代码无需修改。最后来看调用的脚本。
# This is the code that visits the warehouse.
import sys
import Pyro4
from person import Person
if sys.version_info<(3,0):
input = raw_input
uri = input("Enter the uri of the warehouse: ").strip()
warehouse = Pyro4.Proxy(uri)
janet = Person("Janet")
henry = Person("Henry")
janet.visit(warehouse)
henry.visit(warehouse)
通过warehouse = Pyro4.Proxy(uri)
,我们得到了Warehouse
实例的代理。Pyro4自动创建了对应的实例,并负责后面的调用。就是说,B地仓库在A地有门面了,并由该门面承接A地的业务。
大致做个示意图,见图1。可以看到,在Pyro的帮助下,在计算机A上可以轻松调用计算机B上的对象,只要实现将B上的对象进行一些操作(注册为Pyro对象,登记到守护进程),并把B上对象的uri交给A,并在A上直接把代理当做对象进行调用即可。