基于 protobuf 的消息传输,一般都有专门的数据结构去存储 protobuf 对象。类似于下图这种,在程序运行中往往需要一次性将所有的 protobuf 对象加载到内存中。再去初始化对象实例来使用,并且对象实例也没有复用,效率低下。
但是我们测试人员来讲,根本不需要用到所有的协议。尤其是在对协议进行压力测试的时候,宝贵的 CPU 资源和内存资源浪费在一些可能永远也不会用到的协议上,是非常可惜的。但是,不引入协议是没有办法解析 protobuf 的二进制流的。为了解决这个矛盾,我利用 python 的动态加载的语言特性,设计了一种动态引入 protobuf 对象并复用其实例的方法。
构造一个对象池,按需引入 protobuf 对象。对象的引入时机是在 protobuf 对象首次被使用的时候,从对应的 protobuf 的 py 文件中引入。
事先遍历所有 protobuf 自动生成的 py 文件,搜集所有对象,以 json 字典的形式写入到 protocol_dict.py 中备用。本操作属于预处理,且只有一次。如下图所示,json 字典存的只是对象的名称,以字符串的形式存放,程序运行时加载很快。
协议对象池的数据结构如下:
protocol_pool = {
str(protocol_num): {
"file_name": "prototocol_xxx_pb2",
"protocol_name": "xxx",
"instance": xxx_instance
},
}
引入模块的所需的 python 库为importlib
import importlib
protocol_module = importlib.import_module(file_name)
找到模块中的类,需要类的名称,还需要借助模块的__dict__
属性
protocol = protocol_module.__dict__.get(protocol_name)
最后返回对象的实例。
这里只提供了一种复用的思路,欢迎各种建设性意见,以及其他编程语言的实现。
file_name: protcol_manager.py
import importlib
class ProtocolManager(object):
protocol_instance_pool = dict()
def get_protocol_instance(self, protocol_num):
"""获取协议对象实例
:param protocol_num: 协议号
:return: 协议对象
"""
instance_dict = self.protocol_instance_pool.get(str(protocol_num))
if instance_dict is None:
from app.utils import protocol_dict
instance_dict = protocol_dict.protocol_dict.get(str(protocol_num))
if instance_dict is None:
return None
else:
self.__add_instance(instance_dict)
self.protocol_instance_pool.update({
str(protocol_num): instance_dict
})
if instance_dict.get("instance") is None:
self.__add_instance(instance_dict)
return instance_dict.get("instance")
@staticmethod
def __add_instance(instance_dict):
"""添加对象实例
:param instance_dict: 对象池的一个元素
"""
file_name = instance_dict.get("file_name")
protocol_name = instance_dict.get("protocol_name")
protocol_module = importlib.import_module(file_name)
protocol = protocol_module.__dict__.get(protocol_name)
instance_dict.update({"instance": protocol()})
protocol_manager = ProtocolManager()