Python Pyro4

最近在项目中用到一个python RPC框架 Pyro4, 中文文档比较少,在这里记录一下关于学习这个库的理解。

RPC的概念

首先什么是RPC,RPC是远程过程调用。在分布式系统中,有两台服务器A,B 。 一个应用部署在A服务器上,想要调用B服务器上应用提供的函数和方法。由于不在一个内存空间里,不能直接调用。需要网络来传递调用的语义和传达调用的数据。

基于上述问题,我们抛出了一个RPC框架需要解决以下五点问题:

  1. 首先,由于分在不同的机器上,我们首先要解决的是通讯问题。主要是通过在客户机和服务器之间建立TCP连接。远程过程调用中交换的数据都是通过这个连接去传输。连接可以是按需连接。每次远程调用结束后关闭。也可以是长连接,多次远程过程调用共享一个连接。
  2. 第二个要解决寻址的问题,也就是说,A服务器的应用该怎么样去告诉底层的RPC框架。如何连接到B服务器(主机名/IP)以及特定的端口。方法的名称是什么。这样才能完成调用。比如我们下面要说的Pyro4 ,就需要提供一个end point URI , 或者是从nameserver上进行查找。
  3. 当A服务器上的应用发起远程过程调用时,方法的参数需要通过底层的网络协议如TCP传递到B服务器,由于网络协议是基于二进制的。内存中的参数的值要序列化成二进制的形式。也就是serialize,通过寻址和传输将序列化的二进制发送给B服务器。
  4. 第四,B服务器接收到请求后,需要对参数进行反序列化(序列化的逆操作),恢复为内存中的表达方式,然后找到对应的方法(寻址的一部分)进行本地调用。然后得到返回值。
  5. 第五,返回值还要发送回A服务器上的应用,也要经过序列化的方式发送。服务器A接到后,再反序列化,恢复为内存中的表达方式,交给A服务器上的应用。

Pyro4

关于上述的五点,在Pyro4中已经进行了完善的封装。用户只需要使用Pyro4提供的API就可以轻松的使用Python完成RPC调用。
下面主要记录一下Pyro4中的主要模块(client, server,nameserver,以及Pyro4提供的命令行工具+
)的作用以及以及给出一个实际的分布式环境的实例(相对于官方文档中给出的都在localHost的单机伪分布式示例)。
实际上Pyro4提供了很强大的功能,如果需要详细了解其功能,可以访问
官方文档

我们先以一个本地的普通调用为例,然后将其改写为RPC调用

warehouse.py

from __future__ import print_function

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))

person.py

from __future__ import print_function
import sys

if sys.version_info < (3, 0):
    input = raw_input


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)

visit.py

# 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!

接下来我们将其改写为分布式的RPC调用,我这里有三台host, ip分别是

  • host1:10.133.1.224
  • host2:10.133.1.220
  • host3:10.133.1.221

其中host1 执行visit.py(同时person.py也在这台机器上),担任RPC 中client的角色。
host2 执行warehouse.py, 担任RPC中server的角色。
host3 执行nameserver.py,担任nameserver的角色。这里要特地说一下nameserver的作用:提供远程调用对象的逻辑名与URI的映射关系,使得client 在进行远程过程调用时只需要指定调用的远程对象的逻辑名称(而不用复杂的URI )和对应的nameserver就可以轻松的完成远程过程调用。

改为RPC调用代码如下:

visit.py

 # This is the code that visits the warehouse.
 import sys
 import Pyro4
 from person import Person
 sys.excepthook = Pyro4.util.excepthook 
 warehouse=Pyro4.Proxy("PYRONAME:example.warehouse")
 janet = Person("Janet")
 henry = Person("Henry")
 janet.visit(warehouse)
 henry.visit(warehouse)

person.py 无改动,同上

warehouse.py

from __future__ import print_function
 import Pyro4
 #import person


 @Pyro4.expose
 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.naming.locateNS(host='10.133.1.221')
     Pyro4.reDaemon.serveSimple(
              {
                  Warehouse: "example.warehouse"
              },
              ns = True,
              host = '10.133.1.220'
              )


 if __name__=="__main__":
     main()
 ~              

nameserver.py

import Pyro4

 Pyro4.naming.startNSloop(host='10.133.1.221')

首先执行nameserver.py,
使用命令行工具在执行nameserver.py的这台机器上查看

wxl@dev:~$ pyro4-nsc ping
Name server ping ok.

发现此时nameserver已经成功运行。

再执行

wxl@dev:~$ pyro4-nsc list
--------START LIST 
Pyro.NameServer --> PYRO:Pyro.NameServer@10.133.1.221:9090
    metadata: [u'class:Pyro4.naming.NameServer']
--------END LIST 

发现此时nameserver上没有远程过程调用对象。

在host2上启动warehouse.py

再次在nameserver上查看发现:

wxl@dev:~$ pyro4-nsc list
--------START LIST 
Pyro.NameServer --> PYRO:Pyro.NameServer@10.133.1.221:9090
    metadata: [u'class:Pyro4.naming.NameServer']
example.warehouse --> PYRO:obj_cc4b9c4bf9a148ca9cf2f1445e578a75@10.133.1.220:36178
--------END LIST 

已经注册到这台nameserver上了。

此时host1执行visit.py,发现可以调用远程warehouse对象

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页