ITKeyword,专注技术干货聚合推荐

注册 | 登录

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

crylearner 2015-07-20

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

之前我们制定了一个很简单的RPC消息 的格式,但是还遗留了两个问题,上一篇解决掉了一个,还留下一个 我们并没有实现相应的encode和decode方法,没有基于可以跨

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

  1. RPC数据的传输
  2. RPC消息 协议
  3. RPC服务注册
  4. RPC消息处理
接下来处理RPC服务的注册机制。所谓注册机制,就是Server需要声明支持哪些rpc方法,然后当客户端发送调用某个声明的rpc方法之后,服务端能自动找到执行该请求的具体方法。以实际的例子为例,这是现在server端处理RPC请求的代码
    def procRequest(self):
        # 循环读取并处理收到的客户端请求
        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即可。



基本想法已定,

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

这个实现很简单,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. 方案还是很粗糙的,实际有很多优化的地方。


开局篇我们说了,RPC框架的四个核心内容RPC数据的传输。RPC消息 协议RPC服务注册RPC消息处理接下来处理RPC服务的注册机制。所谓注册机制,就是Server需要声明支持哪些rpc方法,然后当

相关阅读排行


用户评论

游客

相关内容推荐

最新文章

×

×

请激活账号

为了能正常使用评论、编辑功能及以后陆续为用户提供的其他产品,请激活账号。

您的注册邮箱: 修改

重新发送激活邮件 进入我的邮箱

如果您没有收到激活邮件,请注意检查垃圾箱。