该文原创为新潮质量保障技术团队中的 “上进的中年软件测试从业者”,用于技术交流分享
有节操/洁癖的代码程序员会在扣了几天脑壳都无法解决一个技术问题,用了自己都看不下去的代码实现后加一个 TODO 的标识。或者赶一个肾上腺急剧飙升的紧急项目,写上那么一段流水账代码后,也会加上 TODO 的标识。提交代码的时候,IDE 会贴心的提醒你有 TODO 的代码是否需要返回处理。一段时间之后,信誓旦旦要做整改的的部分会石沉大海,直到某一个需求又要涉及这块代码的改动才又一起唤醒你,而这个时候你早就忘记了为什么要加个 TODO。
我个人是有这个问题的,直到现在代码里面还是存在 N 个 TODO。所以我给自己定了个小规矩,时间稍微宽裕的时候,我会去消化这些 TODO,就像之前的那篇测试平台 - 被 flask_admin 无情抛弃的 MongoEngine.Datefield 就是这个小规矩的产物,当然收获也是颇丰的。
今天我们要介绍的是 RPC 服务调用优化,是针对测试平台之服务拆分的一次优化。在那次服务拆分中,我们引入了 RPC 服务调用方式。由于历史原因,我们把模块引入过程都放到了测试平台的初始化那里。当时这样设计的目的为了让调用模块的出处统一。后面拆分服务时(这次就是肾上腺急剧飙升一次体验),为了不影响现有的流程,模块从 RPC 服务端获取的过程也是统一放在了测试平台的初始化那里。那么问题就来了:
- RPC 服务重启后,模块调用在初始化过程完成,业务调用时连接已经关闭。
- 开发人员在业务开发过程中不小心调用了 RPC 客户端并且植入了关闭连接的操作,那么所有业务与 RPC 调用都会因为连接已经关闭而无法调用。
那么这个 Big TODO 是到了该解决的时候了,否则每次都重启测试平台重新建立 RPC 客户端这种操作完全没有易用性。
我想尽可能的不重构业务代码来实现这次改动,那么方案就是在全局捕获 EOFError 的时候,与 RPC 重新建立连接。
- app 注册蓝图时引入异常
app.register_blueprint(Libs.exception, url_prefix='/error')
- 异常捕获
exception = Blueprint('exception', __name__)
@app.errorhandler(EOFError)
def eof_error(error):
logger.warning("EOFError被捕捉到!")
connect_to_rpc_server()
logger.info("RPC服务连接异常已经恢复!")
flash(Markup(u"服务器出现问题,请售后重试!"))
- 重写初始化过程的客户端
TestPlatform.rpc_client = connect_to_rpc_server()
- 测试
通过重启 RPC 服务来验证:是否重新建立连接;业务过程中与 RPC 通信是否正常。
- 结论:连接可以重新建立。但是业务过程与 RPC 通信还是会存在 EOFError:stream is closed, 原因是获取模块的过程在平台启动的初始化过程中,获取后赋值给变量并没有因为重新建立连接而重新赋值。
- 猜想:我总觉得有一种方式可以在服务运行过程中让 python package 重新初始化一次,有知道的同学可以告诉我一下。
方案 B 也就是与 RPC 服务端通信过程的重构,工作量和风险都是很大的。
- 我先进行了方案论证,通过测试可以解决方案 A 的问题。
- 然后通知了相关的平台开发人员暂停开发,并且暂时不要提交代码,避免冲突。
开始实施:
- 把获取模块的过程从初始化中复制一份到 Lib 里面
@logger.loggerDecorator
def connect_to_rpc_server():
Config.rpc_client = rpyc.connect(Config.config.tools_host, Config.config.tools_port,
config={"allow_public_attrs": True,
"allow_all_attrs": True,
'sync_request_timeout': None})
logger.info("与RPC服务端重新建立连接成功")
@logger.loggerDecorator
def get_rpc_object(rpc_module_name):
logger.info("start to get module object %s" % rpc_module_name)
try:
return Config.rpc_client.root.get_object(rpc_module_name)
except EOFError: # 与RPC断开连接后,尝试重新连接
connect_to_rpc_server()
return Config.rpc_client.root.get_object(rpc_module_name)
except AttributeError: # 与RPC断开连接后,尝试重新连接
connect_to_rpc_server()
return Config.rpc_client.root.get_object(rpc_module_name)
except Exception as e:
logger.error(e)
@logger.loggerDecorator
def get_jenkins_object():
HandleJenkins = get_rpc_object("HandleJenkins")
jenkins = HandleJenkins(url=Config.config.jenkins_host, username=Config.config.ad_username["username"],
password=Config.config.ad_username["password"])
return jenkins
在初始化过程里面挨个排查被调用的业务代码,进行整改
from ...Libs import get_jenkins_object
def get_artifact_data_from_jenkins_for_ccode_contribution():
jenkins = get_jenkins_object()
- 把初始化中从 RPC 服务端获取模块的过程移除
- 启动服务,查看是否报错,继续排查。
- 试用常用的功能的调用,如 testlink、JIRA、Jenkins 等。
- 合并代码到 master、生产发布。
第二天早上来了以后,不该发生的问题还是发生了。Daily 的项目 Bug 信息没有定时发送出去。经过排查,发现了方法返回的 Tuple 类型的值与接收的参数没有对应起来,修复后上线成功。根据经验,这种大面积的改动问题绝不止一个,那就只能发现一个解决一个了。
最近很累,希望能稍微休息一下,陪陪孩子。流年不利,祝安好!