一个简单RPC框架是怎样炼成的(VI)——引入服务注冊机制

开局篇我们说了。RPC框架的四个核心内容

  1. RPC数据的传输
  2. RPC消息 协议
  3. RPC服务注冊
  4. RPC消息处理

接下来处理RPC服务的注冊机制。所谓注冊机制,就是Server须要声明支持哪些rpc方法。然后当client发送调用某个声明的rpc方法之后,服务端能自己主动找到运行该请求的详细方法。以实际的样例为例。这是如今server端处理RPC请求的代码

    def procRequest(self):
        # 循环读取并处理收到的client请求
        while True:
            req = self.conn.recv()
            rsp = Response()
            rsp.id = req.id
            if req.command == 'sayHello':
                rsp.result = self.sayHello()
            elif req.command == 'whoAreYou':
                rsp.result = self.whoAreYou()
            else:
                raise Exception("unknown command")

            self.conn.send(rsp)

上面的代码有一个非常不好的地方,非常难稳定。Server端每次新增一个支持的rpc方法,就要改动这个procRequest方法。

有什么办法能够避免吗?有,就是引入服务注冊机制。在这里。实际就是将command与详细的function object绑定起来,说穿了就是生成一个dict,

{‘sayHello’ : self.sayHello,     ‘whoAreYou‘: self.whoAreYou}。

有这种dict之后,收到req 之后,仅仅要提取出command字段。然后从dict中找出相应的function。调用该function就可以。

基本想法已定,

首先我们实现一个比較原始的服务注冊机制。

这个实现非常easy。self.services就是上面的dict。通过register()去注冊服务。通过get_service()去获取服务名相应的function

class ServiceRegister(object):
    '''
    @服务注冊  不考虑线程安全,这里简化起见,也不引入反射机制。
    '''

    def __init__(self):
        '''
        Constructor
        '''
        self.services = {}

    ## 注冊详细的服务
    #  @param servicename: 服务名
    #  @param obj: 详细的对象
    def register(self, obj, servicename):
        if servicename in self.services:
            print('warning: %s is already registered' % servicename)
        else:
            self.services[servicename] = obj

    def get_service(self, servicename):
        return self.services[servicename]

    def list_service(self, servicename=None):
        if servicename:
            return str({servicename, self.services[servicename]})
        else:
            return str(self.services)

使用时。就是这个样子的

服务注冊:

self.services.register(self.sayHello, 'Server.sayHello', )
        self.services.register(self.whoAreYou, 'Server.whoAreYou')
        self.services.register(self.add, 'Server.add')

服务查找

def proc(self, req):
        rsp = Response()
        rsp.id = req.id
        rsp.result = ServiceCaller.call(self.services.get_service(req.command), req.parameter)
......

上面serviceCaller的实现,就是在RPC消息,实现带參数的RPC请求中。提到的 func(**args)的技巧

class ServiceCaller():
    def __init__(self):
        pass

    @classmethod
    def call(cls, caller, parameter):
        if not parameter or len(parameter) == 0:
            return caller()
        return caller(**parameter)

以下我再引入一个自己主动注冊服务的实现

直接上代码

class AutoServiceRegister(AbstractServiceRegister):
    def register_class(self, obj, predicate=None):
        if not (hasattr(obj, '__class__') and inspect.isclass(obj.__class__)):
            return False
        servicename = obj.__class__.__name__
        for (name, attr) in inspect.getmembers(obj, predicate):
            # 系统方法或者私有方法,不加入
            if name.startswith('__') or name.startswith('_' + servicename + '__'): continue
            #print(name)
            if inspect.ismethod(attr): self.register_method(attr)
            elif inspect.isfunction(attr): self.register_function(attr, servicename)
        return True

使用

if __name__ == '__main__':
    class AServer(object):
        def __init__(self):
            pass

        def sayHello(self):
            return 'Hello World'

        def whoAreYou(self):
            return 'I am server'

        def __kaos(self):
            pass

        def _kaos(self):
            pass

    obj = AServer()

    service = AutoServiceRegister()
    print(service.register_class(obj))
    print(service.list_services())
    print(service.get_service('AServer.sayHello')) 

运行结果例如以下

True
{'AServer': {'sayHello': <bound method AServer.sayHello of <__main__.AServer object at 0x000000000294EA90>>, 'whoAreYou': <bound method AServer.whoAreYou of <__main__.AServer object at 0x000000000294EA90>>, '_kaos': <bound method AServer._kaos of <__main__.AServer object at 0x000000000294EA90>>}}
<bound method AServer.sayHello of <__main__.AServer object at 0x000000000294EA90>>

具体说明 一下原理,利用了类似的反射的技术。

有兴趣的同学能够先去了解一下inspect

  • register_class表示自己主动搜索一个类对象中的成员方法,并将其作为server端的rpc方法注冊进去。

    以上面AServer为例, 会自己主动将sayHello, whoAreYou 这两个方法自己主动注冊进来。

    同一时候像__init__, __kaos, _kaos之类的系统固有方法,或者私有方法。会自己主动剔除。

  • 注冊时。传入的參数obj必须是class的instance,也就是类实例。

    尽管在python中,也支持类对象,但假设直接传递类对象,就会遇到怎样初始化的难题。所以这里一致要求,必须是类的实例。

    if not (hasattr(obj, '__class__') and inspect.isclass(obj.__class__)):
                return False

    类实例的特点就是,包括__class__成员,并且__class__成员的值就是该类的类对象。

    inspect.isclass就是检測是不是类对象

  • inspect.getmembers()返回的是类对象的全部成员。包括系统固有方法以及私有方法

    所以,先要将系统方法和私有方法剔除。然后通过inspect,检查该成员是不是真的是function,就是能够被调用的。

    假设是,就注冊进来

  • register_fucntion, register_method与普通的服务注冊基本一样。就是加入(key,value)对。

总结:

1. 引入服务注冊的方式也是为了代码解耦,将req的处理与详细的req消息内容解耦。

2. 上面我们 引入了两种服务注冊的方式。一种方式是普通的方式,逐个加入方法。

还有一种方式通过python的“反射”技术,自己主动查找一个类里面的方法。并自己主动加入。

3. 方案还是非常粗糙的,实际有非常多优化的地方。

时间: 2024-10-09 08:19:51

一个简单RPC框架是怎样炼成的(VI)——引入服务注冊机制的相关文章

一个简单RPC框架是如何炼成的(VI)——引入服务注册机制

开局篇我们说了,RPC框架的四个核心内容 RPC数据的传输. RPC消息 协议 RPC服务注册 RPC消息处理 接下来处理RPC服务的注册机制.所谓注册机制,就是Server需要声明支持哪些rpc方法,然后当客户端发送调用某个声明的rpc方法之后,服务端能自动找到执行该请求的具体方法.以实际的例子为例,这是现在server端处理RPC请求的代码 def procRequest(self): # 循环读取并处理收到的客户端请求 while True: req = self.conn.recv()

一个简单RPC框架是如何炼成的(V)——引入传输层

开局篇我们说了,RPC框架的四个核心内容 RPC数据的传输. RPC消息 协议 RPC服务注册 RPC消息处理    接下来处理数据传输.实际应用场景一般都是基于socket.socket代码比较多,使用起来也比较麻烦.而且具体的传输通道使用socket或者其他的方式,如更上层的http,或者android里的binder,都是可替换的,只是具体的一种实现而已.所以,这里我就偷个懒,只是引入一个很简单的Connection类,用来描述一下如何将数据传输 这一层给独立出来. 首先简单列出Conne

一个简单RPC框架是怎样炼成的(II)——制定RPC消息

开局篇我们说了,RPC框架的四个核心内容 RPC数据的传输. RPC消息 协议 RPC服务注冊 RPC消息处理 以下,我们先看一个普通的过程调用 class Client(object): def __init__(self): self.remote = None ## # 内部是托付给远程remote对象来获取结果. def sayHello(self): if self.remote: return self.remote.sayHello() else : return None cla

一个简单RPC框架是如何炼成的(I)——开局篇

开场白,这是一个关于RPC的相关概念的普及篇系列,主要是通过一步步的调整,提炼出一个相对完整的RPC框架. RPC(Remote Procedure Call Protocol)--远程过程调用协议,基于C/S模型.网络上有一篇文章写得不错,可以去了解一下相关概念深入浅出RPC 这里,直接使用一下上面作者的一个示意图 总结下来就是有4块核心内容 RPC数据的传输.如上面的RPCConnector,RPCChannel.它们主要负责数据传输这一块, 具体客户端与服务器之间的连接是不是socket连

一个简单RPC框架是如何炼成的(II)——制定RPC消息

开局篇我们说了,RPC框架的四个核心内容 RPC数据的传输. RPC消息 协议 RPC服务注册 RPC消息处理 下面,我们先看一个普通的过程调用 class Client(object): def __init__(self): self.remote = None ## # 内部是委托给远程remote对象来获取结果. def sayHello(self): if self.remote: return self.remote.sayHello() else : return None cla

一个简单RPC框架是如何炼成的(IV)——实现RPC消息的编解码

之前我们制定了一个很简单的RPC消息 的格式,但是还遗留了两个问题,上一篇解决掉了一个,还留下一个 我们并没有实现相应的encode和decode方法,没有基于可以跨设备的字符串传输,而是直接的内存变量传递. 现在的RPC request不支持带参数的请求命令.如add(a, b), 如何在RPC消息中描述参数a,b . 下面我们处理掉这个编解码问题. 实际的RPC应用基本都是跨机器连接,所以无法直接传递内存变量,也就是说还需要将消息编码成 诸如字符串一类的可以跨设备传输的内容.具体的RPC消息

一个简单RPC框架是如何炼成的(III)——实现带参数的RPC调用

上一篇,我们制定了一个很简单的RPC消息 的格式,但是还遗留了两个问题 我们并没有实现相应的encode和decode方法,没有基于可以跨设备的字符串传输,而是直接的内存变量传递. 现在的RPC request不支持带参数的请求命令.如add(a, b), 如何在RPC消息中描述参数a,b . 我先来实现第二个问题,即带参数的RPC调用. 其实,也没什么太大不同.既然是要带参数,那只能扩展原来的Request消息了,加个parameter成员,用于表示参数,具体的格式采用字典方式,{ 'arg1

一个入门rpc框架的学习

一个入门rpc框架的学习 参考 huangyong-rpc 轻量级分布式RPC框架 该程序是一个短连接的rpc实现 简介 RPC,即 Remote Procedure Call(远程过程调用),说得通俗一点就是:调用远程计算机上的服务,就像调用本地服务一样. RPC 可基于 HTTP 或 TCP 协议,Web Service 就是基于 HTTP 协议的 RPC, 它具有良好的跨平台性,但其性能却不如基于 TCP 协议的 RPC.会两方面会直接影响 RPC 的性能,一是传输方式,二是序列化. 众所

基于Netty和SpringBoot实现一个轻量级RPC框架-Client篇

前提 前置文章: <基于Netty和SpringBoot实现一个轻量级RPC框架-协议篇> <基于Netty和SpringBoot实现一个轻量级RPC框架-Server篇> 前一篇文章相对简略地介绍了RPC服务端的编写,而这篇博文最要介绍服务端(Client)的实现.RPC调用一般是面向契约编程的,而Client的核心功能就是:把契约接口方法的调用抽象为使用Netty向RPC服务端通过私有协议发送一个请求.这里最底层的实现依赖于动态代理,因此动态代理是动态实现接口的最简单方式(如果