1、前言
在之前介绍我自己封装框架的时候,提到我们的注册相关的服务,本质就是实现类似的 Greeter()添加到add_GreeterServicer_to_server里面的一个过程。 如下代码示例:
serverstrem_pb2_grpc.add_GreeterServicer_to_server(Greeter(), server)
复制代码
但是有些时候,我们如果相关的服务比较多的时候,通常看业务订,可能也不是很多,毕竟微服务讲究的是服务精细化,粒度更小,但是这个尺寸把控通常需要结合你实际来度量。
主要是考虑当服务数比较多的时候,该如何处理呢?
下面我是围绕几个实现的方式来说明一下我这个小框架实现服务的几种方式。(之前其实也有提到过,但是实现的过程有一些小问题,刚好这几天把问题掰开了总结整理一下)
本小节主要是讲关于批量导入的时候遇到一些问题,方便自己理解记录。
2、服务注册方式
2.1 服务注册的前提:
如我示例框架中示例,是统一把重新实现的服务统一的放到我们的handler,当然你也可以随意的防到你自己定义的模块下。
示例里面我定义三个服务,(其实是重复的,只是改了类名),只是为了方便演示。
上面定义好我们的服务之后,那我们的就可以根据需要来进行服务注册了!
2.2 装饰器
和我们的fastapi使用一样,不过服务可能你需要看自己需要来进行放置, 示例如:
import grpc
from example.servicers import hello_pb2_grpc
from example.servicers import hello_pb2
@app.add_servicer
class CeshiGreeter(hello_pb2_grpc.GreeterServicer):
# 实现 proto 文件中定义的 rpc 调用
def SayHello(self, request, context):
# 返回是我们的定义的响应体的对象
return hello_pb2.HelloReply(message='hello {msg}'.format(msg=request.name))
def SayHelloAgain(self, request, context):
# 解析请求参信息
# 校验请求的参数信息
return hello_pb2.HelloReply(message='hello {msg}'.format(msg=request.name))
复制代码
2.3 使用函数添加
和我们的fastapi使用一样。
from example.server.handler.ser1 import AGreeter
app.add_servicer(AGreeter)
复制代码
2.4 批量进行导入
关于批量导入之前我们的我们的实现的方式是:
from grpcframe.utils import import_string
app.load_servicers_handlers_in_module(import_string('example.server.handler'))
复制代码
再改进优化之后就是,把import_string实现迁移到我们的load_servicers_handlers_in_module里面去处理。
# 批量进行模块下的服务的导入
def load_servicers_handlers_in_module(self, module):
# 批量进行模块下的导入
if isinstance(module, str):
# 如果传去的是模块的目录,需要转换处理下
from grpcframe.utils.import_string import import_string
module = import_string(module)
# 判断模块是否存在某个属性值
assert module.__file__.endswith('__init__.py') and hasattr(module,'auto'),'批量导入需要包含自动加载方法调用'
# 变量模块当前的所属的属性信息
for _, _servicer in inspect.getmembers(module, inspect.isclass):
# 获取当前服务模块的名称
curr_servicer_name = _servicer.__name__
# 对于如果是直接导入了具体模块下类的情况:example.ghandler.ser1,则直接进行处理即可,但这情况需要排除其他模块处理
if curr_servicer_name.endswith('Servicer'):
add_func = self._get_servicer_add_func(_servicer)
self.user_servicers[curr_servicer_name] = (add_func, _servicer)
# 如果是导入的整个外层的模块下的需要再执行一次变量
elif curr_servicer_name not in [name for name in self.user_servicers.keys()]:
add_func = self._get_servicer_add_func(_servicer)
# 添加到服务列表中
self.user_servicers[curr_servicer_name] = (add_func, _servicer)
return self.user_servicers
复制代码
使用修改为了,更加简约:
# 批量注册导入服务模块
app.load_servicers_handlers_in_module('example.server.handler')
复制代码
但是批量导入的时候,又有手动挡和自动挡的方式。这个开始的时候我是使用手动挡。
批量加载的其实原理就是查询__init__相关的有没有相关属性或方法或类,通过对模块的现场处理机制,来进行相关的服务信息的提取。
所谓的现场处理其实就是使用:
# inspect模块也被称为 检查现场对象。这里的重点在于“现场”二字,也就是当前运行的状态。
# inspect模块提供了一些函数来了解现场对象,包括 模块、类、实例、函数和方法。
复制代码
2.4.1 手动挡
我需要在导入的模块的下面__init__.py下面手动的引入需要加载相关的类 如下代码示例:
# ==============手动挡
from .ser2 import BatchGreeter2
from .ser1 import AGreeter
from .ser3 import DayeGreeter3
复制代码
只有通过上面手动的进行才可以进行批量的加载我们的BatchGreeter2、AGreeter、DayeGreeter3三个服务的注册。
2.4.2 自动挡
处于懒惰的思想,还是想着我能不能每次新增一个服务都不需要在__init__.py再导入一次的呐?
其实还是回归到我们批量导入的问题,所以还是差多类似的实现的方式。
首先看一下自动挡的导入代码:
import os
import importlib
import inspect
def auto(pro_path,createVar):
for root, dirs, files in os.walk(pro_path):
for file in files:
name, ext = os.path.splitext(file)
# 进行模块查询
if ext == '.py' and name != '__init__' and pro_path == root:
module = importlib.import_module(name)
# ==========必须使用不一样的变量来存
# if name =='ser1':
# Greeter = getattr(module, 'AGreeter') # 获取module
# else:
# BatchGreeter = getattr(module, 'BatchGreeter2') # 获取module
# 进行模块下过滤非特点的类的导入
for _servicer_name, _servicer in inspect.getmembers(module, inspect.isclass):
# 获取当前服务模块的名称
for classitem in _servicer.__bases__:
# 查询需要是基于Servicer类的特点的类
if classitem.__name__.endswith('Servicer'):
pass
# inspect模块提供了一些函数来了解现场对象,包括 模块、类、实例、函数和方法。
# 动态变量的创建---必须是一个动态变量,因为getattr(module, _servicer_name)必须需要一个变量来存在,才可以正确的导入
# getattr(module, _servicer_name)
# _servicer
# createVar[_servicer_name] = getattr(module, _servicer_name)
# 都可以
createVar[_servicer_name] = _servicer
else:
# 必须-删除其他非endswith('Servicer')的模块,引入再加载服务的时候必须是指定类型的服务类的才可以加载
del classitem
# 删除因为使用inspect.getmembers导入的其他的模块(可选,也可以不删,但是建议删了!)
del _servicer
复制代码
但是在这处理的过程中我遇到了问题有几个点:
- inspect.getmembers导入了无关的类,导致服务注册失败
for _servicer_name, _servicer in inspect.getmembers(module, inspect.isclass):
复制代码
中的inspect.getmembers会把不属于我需要的类也会进行加载进来,因为他内部也是实现了:
问题处理的话:目前我处理的方式,你导入进来我我就先一次性的全部删除,再根据过滤我只要我需要的那些
- 动态加载如的模块里面的类,必须有一个不同名的变量存贮,才算导入类成功
所以解决的方式就是需要一个动态变量存贮器来存储我们的导入的类
所以有了最终自动挡的导入最终方式就是如下:
# ==============自动挡
import os
import sys
pro_path = os.path.split(os.path.realpath(__file__))[0]
sys.path.append(pro_path)
from grpcframe.utils.import_module_class import auto
# 自动批量的加载
auto(pro_path,locals())
复制代码
只需要在我们的__init__.py存在上面的代码调用,就可以自动导入上面的需要注册的三个服务,甚至更多的服务,不过前提是这些服务是继承于父类如下的格式:
xxxxx_pb2_grpc.xxxxxServicer
复制代码
最终调用批量加载:
# 批量注册导入服务模块
app.load_servicers_handlers_in_module('example.server.handler')
if __name__ == '__main__':
# 单独的进行加载
app.run_serve()
复制代码
启动的时候则可以自动注册了:
以上是我关于自动批量注册服务的一些扩展说明!目前框架还在继续优化整理中!等整理的差不多就开源!希望大佬们不会嫌弃!哈哈
3.总结
以上仅仅是个人结合自己的实际需求,做学习的实践笔记!如有笔误!欢迎批评指正!感谢各位大佬!
结尾
END
简书:www.jianshu.com/u/d6960089b…
公众号:微信搜【小儿来一壶枸杞酒泡茶】
小钟同学 | 文 【欢迎一起学习交流】| QQ:308711822