牛逼了!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:continueresult = 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)
