Python进阶 | 实时计算 | 如何从几百到几千再到几万吞吐量
前言
这次是慢查询的故事,一些情况下很难避免,我们屏蔽来源,只讨论量。从最开始只要简单处理能过滤报警(几百),坏个盘也就是这么量。到小 Region灰度,单Client 峰值8K,再到大集群单 Client 峰值几万,程序一路翻车。
对于报警而言,我们更关注各个来源每秒可以处理的量,总体意义不大,数据来源主要为双机压测结果(非Server吞吐量,且包含网络请求耗时)。
一些环境下是不存也不方便引入其他中间件的,一个Centos7 自带的 Python 2.7.5 一路黑到底。
项目不同阶段
日常需求阶段(几百)
慢查询有一定格式,我们简单抽象为 A 和 B,其中含义如下:
A: 检测到一个查询还没结束,无法判定,因此记录到日志中
B: 检测到一个很耗时的查询,并且已结束,记录到日志中
因此常见组合方式为:
- AA...AB (多次受到慢查询存在告警,最后受到结束告警)
- B (刚好检测到一个耗时的查询,并且已结束)
- A...A (异常情况)
消息消费大致是B已完成,需要实时处理,A则需要抑制并判断阈值在报警,因此可以将服务分为 Server 和 Client:
Client 读取日志,记录 Offset,过滤无效日志,发送成功则刷新 Offset,否则暂停读取,重发。
Server则分为Master 和 Monitor,Master处理实时数据,其他的写入文件,完成就返回,剩下由有Monitor 消费,由此借助文件实现跨进程通信,以及消息实时落盘。(消息生成主键,因此重复写不影响,Monitor 读取时候直接去重即可。)
有很多成熟文件DB,简单的操作背后都是时延增大,本文是直接读写文件。因写文件而速成,也会因文件也受限。
多客户端冲突期
第一版有个很大的竞争点,就是所有消息公用一个文件作为跨进程通信与落盘。因此多客户端已接入,立马互相竞争文件锁,性能瞬间只剩几十,惨不忍睹。
提示:一个 Region 两台统计点,日志是随机打过去的,因此采用放在同一个文件进行比对。当然这是不合理的,当对同一个消息AB很短时间间隔内到达两个节点。再经过采集合并发送到报警服务器,基本无法保证有序。这个阶段已调整,单节点保序。
按 Client 分消息队列,就是分文件了,性能又恢复了。
小故障爆发期(几千,比如2-3千)
这个时候就是把以前偷的懒都给补上了,当然不是每个思路都有效,比较有效的有:
- 各类批量操作: 批量写消息、批量查数据库等
- 更换序列化,比如由 Json 改为 Marshal
- 优化时间格式处理, 自带的 strptime 性能很差,可以小用 panda
- cache,集群、节点等相关属性变化很低,适合放内存
替换实现是一个系统活,有一些测压对比统计,可以供大家参考,真正有提升在替换。
包 |
内置 |
单条序列化长度 |
1K列表序列化长度 |
单条1W次 dumps |
单条1W次 loads |
单次1K条 dumps |
单次1K条 loads |
json |
是 |
754 |
771393 |
0.1193 |
0.2423 |
0.0095 |
0.0402 |
pickle |
是 |
975 |
318576 |
1.8495 |
1.6626 |
0.1121 |
0.0681 |
marshal |
是 |
791 |
495263 |
0.0505 |
0.0606 |
0.0026 |
0.0039 |
ujson |
否 |
698 |
712394 |
0.0451 |
0.0597 |
0.0048 |
0.0105 |
msgpack |
否 |
629 |
640259 |
1.5178 |
2.6460 |
0.1622 |
0.4593 |
marshal 是不安全的,不通用的,不同版本也不兼容的。不过这速度,又是内网环境,我最终义无反顾选择了它。
插入一个无效的优化手段,乍一看我是认为有效的。来个简单常见,就是消息记录,然后按 A、B分类,参考代码如下:
def cls_message(handler, raws):"""对日志分类,然后简单格式化下"""logger = logging.getLogger(handler)slow_dict = {}pending_dict = {}for message in raws:key = make_key(message)if message.get('flag') == 'Pending':pending_dict[key] = formatter(message)logger.debug("save client Pending message <%s> success!" % key)elif message.get('flag') == 'Slow':slow_dict[key] = formatter(message)logger.debug("got client slow message <%s>" % key)else:logger.warning("Message flag %s not define" % message.get('flag'))
这个坑在多客户端就已经出现了,因为多进程写日志,采用文件锁。多客户端同时写,大家应该能猜到,效果就看表格吧。
场景(2000条) | 耗时 |
不打日志 | 0.002063274383544922 |
单线程打日志 | 0.12387228012084961 |
多线程打日志(4) | 0.4494140148162842 |
多进程打日志(4) | 36.29296827316284 |
偷懒点,可以直接关闭细节日志。深入思考也可以看出,单线程性能是牛逼的,不过为了写日志,上下文切换,性能直接降低近百倍。采用多线程,性能在降几倍,说明这种场景下,线程切换开销大于收益。而多进程受限于原先使用的文件锁,不太适合比较。
分布式实时报警(几万)
当各类优化到极致,细节日志都关掉,这样单实例的瓶颈已经出现了,很难在大幅度提升了。而基于文件的跨进程通信也是一个很慢的效率,CPU 密集型的 多线程也不会有效果,多进程创建销毁开销也很大,基本不太适用这种场景。因此基于 socket 的并行内存实时计算,可以预见是一个简单有效的方法。抽象下基础服务,我们大致需要如下:
- Master 服务,接收 Client 信息,然后转发给指定 Consumer 服务
- Consumer服务,只接收指定来源信息,消息独立而有序,避免跨进程内存共享
- LogServer服务,日志统一接收处理,提高效率,也方便排查问题。
- Monitor服务,对符合条件消息,进行报警
服务拆分好,接着选 RPC 了,可以直接 Socket 封装,也可以使用已有的。当前 LogServer 采用Socket 通信,因为 logging 自带客户端,只需要实现服务端就可以了。
场景(2000条) | 耗时 |
单线程写 socket 日志 | 0.11522245407104492 |
其他的采用 Zeromq,这个性能与Socket一样,不过使用更方便,为了支持多客户端同时请求,才用了 ROUTER - DEALER 作为通信框架,参考了 locust_rpc 实现,简单易用。
关于消息拆分粒度,我也做了个尝试,就是Master 接收到消息后,在按保序前提下分为 M 块,然后发送个 M 个Consumer,结果速度反而更慢了。我总结了下消耗:
虽然 Consumer 变快了,但是路径更长了,速度反而更慢了
进入内存计算后,关于如何不丢失日志,我其实困惑了一段时间。
方案一、预写日志。Consumer 收到消息,先写日志,在处理。这样又有新问题,比如网络不好,Client 未收到指定码触发重发,那么如何实现日志回滚?第二就是运行很长一段时间,一旦重启,这加载速度也太慢了。
方案二、快照。这个好理解,缺点不及时?相对于预写日志,每条都写。然而真是这样吗?
再回到方案一的问题,是不是负负得正,一的问题不就是二的答案吗!
因为最近一直优化细节,反而忘记了整体,在最初设计中,为了保证数据不丢失,我们设计的原子操作是每次请求,而不是每条日志!跳出每次日志的来看,直接对每次请求执行快照,不就可以保证数据不丢失了。而覆盖写单次效率很不错,参考数据如下:
类型 |
次数 |
单次大小 |
虚拟机(三次) |
覆盖写 |
1000 |
1024 |
0.12、0.12、0.13 |
覆盖写 |
1000 |
1024 * 4 |
0.12、0.11、0.13 |
覆盖写 |
1000 |
1024 * 10 |
0.14、0.13、0.16 |
覆盖写 |
1000 |
1024 * 1000 |
2.1、2.2、1.8 |
Consumer 初始化的时候加载快照,之后每次消息过来,都在内存中计算。计算完毕,触发一次快照覆盖,然后返回 Master,失败返回异常码,则重发再来。
支持更大并发
考虑到在 Master 端进行数据在分割并没有效果,那么把 Client 包含进来,当做一个整体看待。正常按行读日志,效率也够了。毕竟写日志的上限在那里摆着,不用过度优化。不过日志格式转化是个CPU密集型任务,是可以通过 RPC 改为并行计算,顺带也实现了数据分片,简单的实现更高并发。
优化总结:在数据流向路径不变的情况,优化原先模块或者拆分为并行计算,效果会很明显;反之为了并行而增大路径,额外增加的序列化成本,以及并行开销可能导致速度更慢。
参考文献:
zmq_rpc: https://github.com/locustio/locust/tree/master/locust/rpc
Python进阶 | 实时计算 | 如何从几百到几千再到几万吞吐量相关推荐
- 阿里巴巴双11数据大屏背后的实时计算处理
作者:藏六.黄晓锋.同杰 1.双11数据大屏的实时计算架构 1.1 背景 2016年的双11我们的实时数据直播大屏有三大战场,它们分别是面向媒体的数据大屏.面向商家端的数据大屏.面向阿里巴巴内部业务运 ...
- 【双11背后的技术】双11数据大屏背后的实时计算处理
选自<不一样的技术创新--阿里巴巴2016双11背后的技术>,全书目录:https://yq.aliyun.com/articles/68637 本文作者:藏六 黄晓锋 同杰 1. 双 ...
- 第六章 大数据,6.1 双11数据大屏背后的实时计算处理(作者:藏六 黄晓锋 同杰)...
6.1 双11数据大屏背后的实时计算处理 1. 双11数据大屏的实时计算架构 1.1 背景 2016年的双11我们的实时数据直播大屏有三大战场,它们分别是面向媒体的数据大屏.面向商家端的数据大屏.面向 ...
- 运维学python哪部分_初入运维的小伙伴,别再问需不需要学Python了
这篇文章来自百度知道(你可以搜索"运维需不需要学Python"),或许有些观点不一定正确,但运维学Python这事毫无疑问是主流了,不信可以到各大招聘网站,看看但凡招聘运维有编程技 ...
- 运维学python用不上_运维朋友们,别再问需不需要学 Python 了!
运维人员需不需要学开发?需不需要学 Python?PythonN 和 Shell 有什么区别?天天问这种好水的问题,我实在受不了,决定帮大家扫扫盲. 现阶段,掌握一门开发语言已经成为高级运维工程师的必 ...
- 基于python快速简便地实时计算金融技术指标
从简单应用到平台框架应用,不同场景下,基于python快速简便地实时计算金融技术指标的方法,总结如下(鄙人之前走了不少弯路,以下五种场景实现及避坑方法,分别介绍给各位朋友,请借鉴): 一.tradin ...
- 日均百亿级日志处理:微博基于Flink的实时计算平台建设
来自:DBAplus社群 作者介绍 吕永卫,微博广告资深数据开发工程师,实时数据项目组负责人. 黄鹏,微博广告实时数据开发工程师,负责法拉第实验平台数据开发.实时数据关联平台.实时算法特征数据计算.实 ...
- python进阶练习题:IRR计算 - 盈利能力的评价【难度:2级】--景越Python编程实例训练营,不同难度Python习题,适合自学Python的新手进阶
python进阶练习题:IRR计算 - 盈利能力的评价[难度:2级]: <跨度风格="字体重量:粗体;颜色:金"> IRR </跨度>或内部收益率,是基于所 ...
- python进阶——AI视觉实现口罩检测实时语音报警系统
大家好,我是csdn的博主:lqj_本人 这是我的个人博客主页:lqj_本人的博客_CSDN博客-微信小程序,前端,vue领域博主lqj_本人擅长微信小程序,前端,vue,等方面的知识https:// ...
最新文章
- 141. Linked List Cycle
- 畅捷通t+标准版操作手册_外网IP访问畅捷通T+教程
- UiAutomator喷射事件的源代码分析
- JSP自定义标签(2)
- 在WPF中集成OpenTK
- java spark 项目_spark应用程序如何在Java项目中运行
- C语言C++编程软件推荐及下载方式
- RapidScada免费开源Scada组态软件系列教程1-初识RapidScada
- Echarts中使用china.js
- vivado基本知识
- 金碟打印凭证或多联纸格式时十几张后错位
- python 使用 pyodbc 访问 Sql Server
- 2020年ICPC辽宁省赛- 最长回文串(Java)
- AI可以自动生成绘画吗?分享几款AI绘画软件
- JavaScript异步编程(1)- ECMAScript 6的Promise对象
- LMDI 理论推导详解【从理论到Python-MATLAB实现(理论)】
- excel解析html代码,使用excel vba解析HTML的错误
- 小白也能学会的调色教程,你学会了吗?
- 应用全屏启动页适配小米9(水滴屏)
- 计算机企业照片墙设计效果图,怎么用自己的照片做电脑墙纸