牛逼了!Python Web 接口优化,性能提升25倍!
WEB前端开发社区 昨天
背景
投石问路
TTFB 是 Time to First Byte 的缩写,指的是浏览器开始收到服务器响应数据的时间(后台处理时间+重定向时间),是反映服务端响应速度的重要指标。
Profile 火焰图 + 代码调优
第一波优化:功能交互重新设计
def get_max_cpus(project_code, gids):
"""
"""
...
# 再定义一个获取 cpu 的函数
def get_max_cpu(project_setting, gid, token, headers):
group_with_machines = utils.get_groups(...)
hostnames = get_info_from_machines_info(...)
res = fetchers.MonitorAPIFetcher.get(...)
vals = [
round(100 - val, 4)
for ts, val in res['series'][0]['data']
if not utils.is_nan(val)
]
max_val = max(vals) if vals else float('nan')
max_cpus[gid] = max_val
# 启动线程批量请求
for gid in gids:
t = Thread(target=get_max_cpu, args=(...))
threads.append(t)
t.start()
# 回收线程
for t in threads:
t.join()
return max_cpus
在一个 web api 做线程的 创建 和 销毁 是有很大成本的,因为接口会频繁被触发,线程的操作也会频繁发生,应该尽可能使用线程池之类的,降低系统花销; 该请求是加载某个 gid (群组) 下面的机器过去 7 天的 CPU 最大值,可以简单拍脑袋想下,这个值不是实时值也不是一个均值,而是一个最大值,很多时候可能并没有想象中那么大价值;
调整功能设计,不再默认加载 CPU 最大值,换成用户点击加载(一来降低并发的可能,二来不会影响整体); 因为 1 的调整,去掉多线程实现;
第二波优化:Mysql 操作优化处理
utils.py:get_group_profile_settings
这个函数引起的数据库操作热点。def get_group_profile_settings(project_code, gids): # 获取 Mysql ORM 操作对象 ProfileSetting = unpurview(sandman.endpoint_class('profile_settings')) session = get_postman_session() profile_settings = {} for gid in gids: compound_name = project_code + ':' + gid result = session.query(ProfileSetting).filter( ProfileSetting.name == compound_name ).first() if result: result = result.as_dict() tag_indexes = result.get('tag_indexes') profile_settings[gid] = { 'tag_indexes': tag_indexes, 'interval': result['interval'], 'status': result['status'], 'profile_machines': result['profile_machines'], 'thread_settings': result['thread_settings'] } ...(省略) return profile_settings
for gid in gids: ...
def get_group_profile_settings(project_code, gids): # 获取 Mysql ORM 操作对象 ProfileSetting = unpurview(sandman.endpoint_class('profile_settings')) session = get_postman_session() profile_settings = {} for gid in gids: compound_name = project_code + ':' + gid result = session.query(ProfileSetting).filter( ProfileSetting.name == compound_name ).first() ...
1. 数据库的查询没有批量查询;2. ORM 的对象太多重复的生成,导致性能损耗;3. 属性读取后没有复用,导致在遍历次数较大的循环体内频繁 getAttr,成本被放大;
def get_group_profile_settings(project_code, gids):
# 获取 Mysql ORM 操作对象
ProfileSetting = unpurview(sandman.endpoint_class('profile_settings'))
session = get_postman_session()
# 批量查询 并将 filter 提到循环之外
query_results = query_instance.filter(
ProfileSetting.name.in_(project_code + ':' + gid for gid in gids)
).all()
# 对全部的查询结果再单条处理
profile_settings = {}
for result in query_results:
if not result:
continue
result = result.as_dict()
gid = result['name'].split(':')[1]
tag_indexes = result.get('tag_indexes')
profile_settings[gid] = {
'tag_indexes': tag_indexes,
'interval': result['interval'],
'status': result['status'],
'profile_machines': result['profile_machines'],
'thread_settings': result['thread_settings']
}
...(省略)
return profile_settings
优化效果
优化总结
如果一个数据结构足够优秀,那么它是不需要多好的算法。
前端渲染和呈现的方式,因为整个表格是有很多数据组装后再呈现的,响应慢的单元格可以默认先显示 菊花,数据返回再更新; 火焰图看到还有挺多细节可以优化,可以替换请求数据的外部接口,比如再优化彻底GetAttr 相关的逻辑; 更极端就是直接 Python 转 GO;
赞 (0)