Sentry整理杂记

本讨论均基于Sentry 7.7版本

插件机制

自带插件

src/sentry/plugins/ 每插件一个目录
自带插件loader:src/sentry/conf/server.py 里的 INSTALLED_APPS tuple
外装插件loader:utils/runner.py 里的 install_plugins()函数,对iter_entry_points()遍历并将其加入 INSTALLED_APPS tuple 中
外装插件注册:插件的 setup.py 里执行注册 entry_points 的过程:

例:https://github.com/getsentry/sentry-groveio/blob/master/setup.py
参考资料:https://pythonhosted.org/setuptools/pkg_resources.html

#!/usr/bin/env python
import pkg_resources

for ep in pkg_resources.iter_entry_points('sentry.apps'):
    print str(ep)

for ep in pkg_resources.iter_entry_points('sentry.plugins'):
    print str(ep)

sentry-jira 插件

插件基本配置

每 Project 分别配置,需要输入 jira 的 instance URL、用户名和密码。以上内容 base64 存在数据库 sentry_projectoptions 表里 where `key` like ‘jira%’
配置用户名密码之后,选择 Sentry project 关联到哪个 JIRA Project,保存设置,并 Enable Plugin 即可。

Due Date 问题

在 Sentry Event 页面右边点击 Create JIRA Issue 进入创建页面。但下面 Due Date 总提示 Operation value must be a string 。
经 tcpdump,插件发送格式为

"duedate": {"id": "2015-07-31"}

根据 /rest/api/2/issue/createmeta?projectKeys=<我们的KEY>&expand=projects.issuetypes.fields 的说明,duedate 的 schema 为

"duedate": {
    "hasDefaultValue": false,
    "name": "Due Date",
    "operations": [
        "set"
    ],
    "required": true,
    "schema": {
        "system": "duedate",
        "type": "date"
    }
}

根据网上流传的消息(https://answers.atlassian.com/questions/319018/jira-eclipse-mylyn-transition-fails-when-date-is-required-french-format#comment-319464),Jira期望的格式应该是

"duedate":"2014-07-23"

发送格式和期望格式的不同导致了无法创建 Issue。不过 sentry-jira 插件创建的内容是 id,而上述 bugreport 中是 name,也不太一样。
倒推检查插件源代码,发现从 Form 提交到 jira 客户端对象的数据就已经是u’duedate’: {‘id’: u’2015-07-31′} 了
sentry_jira:forms.py里

elif (schema.get("type") != "string"
  or schema.get("item") != "string"
  or schema.get("custom") == CUSTOM_FIELD_TYPES.get("select")):
    v = {"id": v}

这一段缺乏对 date 类型的 Jira 字段的特殊处理,导致前面多加了 id。
也就是说,如果不改 sentry-jira 插件,就无解。所以我提交了pull request https://github.com/thurloat/sentry-jira/pull/71
5日下午突然发现,目前线上 Sentry 6.4.4 的 duedate 显示为 django 的 SelectDateWidget,而不像我自己的测试装 Sentry 7.7 一样直接用文本框。经过仔细对比,发现服务器上的 sentry-jira 插件是修改过的版本,forms.py 文件class JIRAIssueForm 新增了一段

duedate = forms.DateField(
    label="duedate",
    #widget=adminwidgets.AdminDateWidget(),
    widget=SelectDateWidget(),
    initial=datetime.datetime.now(),
    #widget=DateWidget(usel10n=True)
    required=True
)

但这段代码没在公司的git库里保存。

SSO集成

厂家SSO讨论:https://github.com/getsentry/sentry/issues/1372
新版Sentry有 auth backend 基类https://github.com/getsentry/sentry/tree/master/src/sentry/auth/
SENTRY_FEATURES[‘organizations:sso’]改为 True 可以开启 Auth 页面,设置sso。
然后我就参考 sentry-sso-google 写了一个插件。

SENTRY_SINGLE_ORGANIZATION=True 会导致 /auth/login/ 跳转到 /auth/login/org_slug/ ,从而无法登录 非 SSO 的用户(如系统自带的名为 sentry 的超级用户)

Organizations

当 SENTRY_SINGLE_ORGANIZATION=False 时,utils/runner.py加载配置之后会将 SENTRY_FEATURES[‘organizations:create’] 强制改为 False,从而禁用了右上角新 创建 Organization 的“加号”链接。如果在此状态下删除了最后一个 Organization,则其中的  Team会变成游离状态,只能改掉参数重启服务重新创建了,而且重建之后某种情况下会导致游离状态的 Team 丢失。切记不要删除 Organization!!
根据 getsentry 原厂GH-1372号 issue,每个 Organization 只能开启一个 SSO AuthProvider

Posted in 默认分类 | Tagged , | Leave a comment

某种情况下PATH不生效的问题

今天遇到的情况,我同事在 virtualenv 里执行 gunicorn 运行 Django app,出来的 Django 错误信息却是 /usr/local/ 路径下面那套 Python 里的:

2015-09-18 15:32:47 [11538] [ERROR] Error handling request
Traceback (most recent call last):
File “/usr/local/lib/python2.7/dist-packages/gunicorn/workers/sync.py“, line 131, in handle_request
respiter = self.wsgi(environ, resp.start_response)
File “/usr/local/lib/python2.7/dist-packages/django/core/handlers/wsgi.py“, line 236, in __call__
self.load_middleware()
File “/usr/local/lib/python2.7/dist-packages/django/core/handlers/base.py“, line 57, in load_middleware
raise exceptions.ImproperlyConfigured(‘Middleware module “%s” does not define a “%s” class’ % (mw_module, mw_classname))
ImproperlyConfigured: Middleware module “django.contrib.auth.middleware” does not define a “SessionAuthenticationMiddleware” class

尝试 which gunicorn 返回结果是 virtualenv 里的那个 gunicorn;尝试 strace gunicorn 执行,却能正常执行。

我试了试,把 /usr/local/bin/gunicorn 的x权限取消,再让同事执行,就返回 Permission denied 说明执行的不是 Virtualenv 里的 gunicorn。改试 type gunicorn 返回结果:

gunicorn is hashed (/usr/local/bin/gunicorn)

目前尚不清楚是什么原因造成了该命令被 bash 给 hash 了。source virtualenv的 activate 命令的时候 hash 表会被清空的。

Posted in 默认分类 | Tagged , , | Leave a comment

Python import语句一处文档和行为不一致的问题

话说,我团运维组曾一度极为昌盛,可惜后来公司里有人搞政治,好好的一个组被拆成了好几个,后来竟分属不同大部门,工作关系也开始形同陌路。考虑到丰富经验,换换方向,今年7月,我转岗到了运维开发组,开始真正把Python作为职业。

无奈,基础差,还得学,8月18日,这么吉利的一天,我第n次在看Python的文档,看着看着,注意到一些标准库的名字是带点的,比如os.path,再比如logging.config等。然后就开始help各个模块,发现os.path的名字居然不叫path而是posixpath!!再看,logging是个package,但os居然是个module!!

于是,问题就来了:

Python语言里对 import A.B.C 这种形式是这么规定的:

when using syntax like import item.subitem.subsubitem, each item except for the last must be a package; the last item can be a module or a package but can’t be a class or function or variable defined in the previous item.

于是那就奇怪了,这个os既然不是package,怎么能 import os.path 呢?

本着技术事问咕果的原则,搜到这么一篇,其中提到,python在初始化时会加载一堆模块,其中就包括os,而os模块判断系统类型之后,选定了posixpath模块来配合工作,让它在本命名空间里叫path,还又把它以os.path的名字加入了sys.modules之中;待我执行import os.path时,import语句发现sys.modules里已经有了,于是直接做个symbol过来就了事。

本来事情到这里已经结束了。但是水木上有朋友在参与讨论的时候,为了说明上述其实是语言引擎的行为,而不是os的特殊性导致的,例子如下:

a.py文件:

import sys

import json as shit

sys.modules[‘a.shit’] = shit

然后从另一处import a.shit 居然也成功!

如果从a.py里去掉第三行,虽然import a.shit时会报告NameError: name ‘a’ is not defined,但sys.modules里已经出现了a,这说明a模块已经被import进来了!

 

如果说os.path含着金钥匙是因为它爸叫os,那a.shit又算老几,凭什么和os.path享受一样的待遇?

经过我精心构造的实验,不用a.py文件,而用一个空目录a代替,在python里import a.shit时,用strace偷偷的录下它的可耻行径,发现python居然先尝试找名为a的package(打开a目录找__init__.py)未果之后又尝试找各种形式文件名的名为a的module。

最后的结论是:虽然os.path它爸叫os,但其实即使它爸不叫os,也照样能成功import进来,根源在于import命令并不是像文档所说的,对 import A.B.C这种形式,坚持了A.B必须为package的原则,而是宽进(语言行为)严出(文档叙述),言行不一。

这样不好。

Posted in 默认分类 | Tagged , , | Leave a comment

我团年会2014

我是在2013年底开始接触这事的。当时钟sir找到我们组同事说想搞个直播,给来不了总部的同事们看;因为我和CDN服务商联系比较多,就把这活儿揽过来了。这期间经历了经费问题、场地和供应商考察、采购谈判、几轮试播(还发现了服务商的一次故障),直至最终正式播出。播放器设在一个限制了权限的wiki页里,由销售支持部在每个分支机构指定一名同事负责给大家播放。

2014年2月20日,我从IT部领了网线、NAT路由器、AP,从我们组拿了交换机,交付年会组待运。21日上午,我从五道口第一次用了快的打车,支付了一元钱到北京大学,以舞台旁边的机房为中心,向化妆室、百年讲堂前厅、观众席最后排分别拉了一条长线。下午,八台取票机运输到了前厅。22日上午,在化妆室安装了AP,在前厅安装了交换机,连接了取票机,在舞台机房安装了NAT路由器并把其放在音箱上用于覆盖观众区提供WiFi;直播服务商搭手把观众区网线贴在地上,并连接了视频信号开始上传。因为网线钳子的问题,取票机一直未能成功上网,一直到12点20分才修复,随后就开门放人进场,真的是好险。后来NAT掉地上了,导致观众区无WiFi,工作人员都没法上网了,于是把化妆室AP挪过来。

照例,签名照相,但今年的拍立得似乎有些感光不足。年会节目期间因为时常要开电脑检查直播情况,其实也没好好看。后来收到钟sir的通报感谢邮件,他老人家以为忘记写我了,又单独发邮件把直播这几个人夸了一通,哈哈哈。28日拷贝了原始录像,可以再回味一下。

我最喜欢的是西游那个小品,和《最初的梦想》歌曲连唱。

 

这一年,我团拥有了超过一半的市场占有率,八分之一的中国电影票房销售额,并开始扩展业务到酒店、外卖等。今天在微博看到“O2O行业今年将迎来决战之年,3大团购网都开展 年会KlCK OFF大会。美团的口号是"安心、成长平天下",点评的口号是"抢美团、争上市",糯米的囗号是"瓦解美团,立争第二",去哪儿口号是"全线抗美,誓死奋战",目前都在全力备战,面对众多对手,美团显得更加从容平静⋯⋯”,觉得真是有意思。团购已在身后,对手们还在抗美,我们已心怀天下了。

希望公司能保持目前的成绩,及时调整架构和业务形态,使之适应进一步商业斗争。我的工作虽和前线的业务形态关系不大,但仍要自省努力,为前线业务提供稳定的技术后盾。

动摇时,要不忘初心。否定追求,就是否定自己。谨以此告诫自己。

Posted in 默认分类 | Tagged | Leave a comment

PHP——成也Web,败也Web

早年我并不知道Python写的Web应用是怎么部署的,总觉得像PHP、ASP一样,仅仅提供一个语言级别的执行模块,直接嵌入Web服务器运行,甚至于直接对外提供带扩展名的URL都是自然而然的事情。

前一阵学习了Python,总是如别人一样,不自觉的和PHP进行对比。随着学习的逐步深入,更发现PHP的发展受限于其Web出身,恐将来难成正经的通用开发语言。

先说说PHP从历史到今天分别是怎么运行的:

  • CGI SAPI:由Web服务器的请求处理进程fork+exec这个CGI,用环境变量单向传递Headers给应用程序,应用程序从stdin读request body,其stdout会被Web服务器作为response headers和body输出给客户端。Web服务器和PHP CGI的关系是父子进程间通信,当应用程序的响应速度较慢时(忙于计算、外部I/O等),会阻塞对应的Web服务器进程/线程;而且因为CGI进程的输入之一是环境变量,导致其只能一次性使用,需要承担频繁fork()的开销
  • Apache2Handler等Web服务器模块类SAPI:Apache2调用Handler,并向其传递server_context;Handler利用server_context读到Header、body等内容之后构造起PHP脚本的context,也就是$_SERVER之类,然后开始执行PHP脚本。而脚本执行的输出也直接被Web服务器收集。PHP和Web服务器在同一个进程内执行,处于完全从属的地位。Apache2Handler方式本来可以通过多线程等方式让Apache不受PHP阻塞,但由于PHP社区的坚持,目前常见的Apache2+PHP配置都是prefork MPM的,以至于性能相当一般
  • FastCGI SAPI:原理是把每次fork处理一个请求,变成一个不退出的循环,每循环一轮就处理一个请求;多进程listen在同一个socket上,实现并发。PHP-FPM是后来的一个民间补丁项目,提供了更好的进程管理和附加功能,处理完License问题之后,FPM合并入PHP主线。Web服务器和PHP的关系是网络通信的关系,可以分离到不同服务器上部署。Web服务器可以使用多线程或者I/O复用技术来处理别的请求,性能会>比前二者好很多。但FPM的实现仍然不够好。

PHP的各种扩展模块,大都是偏重于数据库、字符串处理等应用层面的功能,充分体现了其作为Web开发利器的特点;而对于I/O、POSIX基础功能,则关注较少。
Web开发之外的领域,用PHP写独立运行的服务器,并不是主流的用法。其依赖的pcntl、pthread等模块也是较晚加进来的,而且也没啥人用。用

  1. PEAR Net_Server(无人维护) 通用服务器框架
  2. PEAR HTTP2 协议解析库
  3. PEAR HTTP_Server(无人维护) HTTP服务器框架
  4. PECL pecl_HTTP(需要编译) HTTP服务器框架,我没编译出来……
    等几个模块,可以很容易的创作一个独立的HTTP静态文件或SOA服务;但如果想在其中运行一般的Web PHP脚本,就需要用PHP本身实现一个SAPI:
  • 输入方面是很容易仿制的,除了需要构造Predefined/Reserved Variables(感谢作者,$_SERVER等只是普通数组,只须填充内容即可)再写一下上传文件自动存盘的处理就差不多了
  • 输出方面就比较麻烦,因为“写”是动作,是对外部环境施加影响的。考虑到PHP深深的“假设stdout”情结,须使用output buffer机制收集输出内容,副作用是chunked输出的程序会丧失分段效果,变成缓冲输出;header()函数需要用pecl-apd扩展中的override_function()函数,但此扩展却无法兼容当前的ZendAPI,无法编译成功

传统上,协议解析是SAPI的工作,PHP的语言引擎部分从属于SAPI,以至于没积累出什么好用的协议解析库;而PHP书写的Web应用向来都是直接调用SAPI提供的函数,积习难改,以至于想用PHP本身来写Web服务器就须要去实现一个SAPI,但又会遭遇到这个语言本身的Web烙印太深、假设太多,以及不够“动态”的问题。
传统上,网络通信向来也不须由PHP来处理,以至于也没积累出什么好用的通信框架库。

最终,PHP成了一个Web only的开发语言。真可谓成也Web、败也Web。

一些新兴的PECL扩展提供了高性能I/O框架,比如swoole,但因为上述SAPI的问题,目前尚未出现像Python那样用PHP本身写的PHP Web服务器。

相比之下,不用写<%的Python语言,其发展道路就更加general purpose一些:

  1. 标准库中有os、multiprocess、threading、select等模块,一看就是面向系统编程而非仅仅针对Web开发
  2. 标准库中的SocketServer及其MixIn classes、HTTPServer等可用于写服务器;CGI模块可用于解析HTTP协议请求
  3. 标准库中的wsgiref是纯Python的。也就是说,用Python语言写一个WSGI Server很容易且是提倡的做法
  4. yield语法加入语言比较早,可以实现协程
    在Web应用方面,Python的WSGI接口已经基本统一江湖。WSGI不是通信协议,而是语言级别的调用约定(关于这个函数后面带几个什么样的参数的约定),而WSGI可以用纯Python实现,因此不会遇见用PHP实现SAPI的尴尬。由于Python没有像PHP那样深深的Web烙印(甚至早年在Web上的部署并不太方便)加上标准库中很早就带有各种基础模块,导向结果就是出现了很多优秀的高性能框架和服务器软件。现在流行的组合是用 gunicorn WSGI Server运行WSGI应用程序,管理多个worker充分利用多处理器,用gevent消除I/O等待时的浪费;对外提供HTTP服务,前面套一个nginx提供静态文件和访问控制、rewrite等功能。
Posted in 默认分类 | Tagged | 1 Comment

墨菲定律之多处保存必然会出现不一致——谈Linux上的时区问题

技术部的工作日报,原本是早晨9点发,脚本换了一台服务器运行之后变成17点了。算算时差也知道是时区问题。

检查了一下, /etc/timezone 内容误为 Etc/UTC,而 /etc/localtime 是从 /usr/share/zoneinfo/Asia/Shanghai 复制过来的,二者不一致。date命令使用 /etc/localtime 而 cron 按 /etc/timezone 文件执行,于是造成了执行时间不对的问题

另外需要注意的是,cron 只在开始运行时读取一次该文件,而每分钟唤醒时不再读取,所以改过文件之后还得重启该服务;又得注意的是,service cron stop 似乎并不会终止cron进程,所以……你懂的

以上就是墨菲定律之:多处保存,必然会出现不一致

Posted in 默认分类 | Tagged , | Leave a comment

六月台湾游

不知怎么的,突然就想去台湾了,于是4月底急吼吼的准备了户口本等资料,预约了东城分局出入境办理台湾通行证和护照。看了看日历,5月4日星期六,我不上班公安上班,就这天吧。结果交了申请之后直接告诉我5月31号再来取……期间去了两次都催不出来,直到拿到证件,看了签发日期,才知道异地办理的确不如在家办理快。不过咱不就是为了从北京出入境办个G个人旅游签注嘛!忍了。这期间先买了从北京到厦门的飞机票,也挺贵的,1371元呢!

下一步就是订票和办理入台许可。5月31日拿到证当天扫描完毕,把资料发给中青旅淘宝店,然后就是焦急的等待。6月5日,终于拿到了,才订了金门的住宿、金门到台北松山的机票。

大概的日程记录如下:

6月10日 北京-厦门 CA1809;厦门五通码头-金门水头码头 乘船;参观金门民防坑道、莒光楼、民国福建省政府,还驱车看了一眼八二三炮战纪念堂的外观

6月11日 金门马山观测所、翟山军用坑道;乘飞机去台北,当天去淡水渔人码头、情人桥游玩

6月12日 国父纪念馆、淡水红毛城、真理大学、淡江高中、台北故宫、中正纪念堂、台北101、饶河街观光夜市

6月13日 办了TR PASS Y火车票;去了北投温泉博物馆、露天温泉,又去朵儿咖啡馆喝杯咖啡,还去松山机场三层观景台看了一会儿飞机。下午去基隆,感觉太村了,吃了个夜市又回台北住,在基隆火车站拍了铁路0公里标志

6月14日 参观总统府,乘火车去看了暖暖火车站、平溪线小铁路放天灯,最后去了九份住下,出来吃饭时看了一下阿妹茶馆,也就是《千与千寻》的取景地

6月15日 参观黄金博物馆,淘了个金,然后去了野柳地质公园。穷酸的只剩下五六百块现金,看到7-ELEVEN特别激动,赶快补充了现金去吃了个螃蟹。夜间又回九份住,比昨天贵

6月16日 乘火车去了苏澳,泡了个坑人的冷泉。还在苏澳新火车站见到了Aroean,真是太巧了。经花莲到达台东,在花莲火车站广场拍了些照片,喝了一杯用本地瑞穗咖啡豆做的咖啡;路过池上站太晚了,没便当

6月17日 乘船去绿岛逛了一下,晚上回来在台东城区逛街,结果发现不太兴旺,而且公交车也很早就收车。和我一起等车的还有五个小姑娘,我表示可以拼车一起走。她们于是去7-ELEVEN用ibon机叫出租车,未果。好不容易街上拦了一辆出租车,本来想只上一半人,结果遇到彪司机了,五个小姑娘装后排座上,我坐前面,一车全拉走!于是我就请了车资,没让她们花钱。

6月18日 早晨在台东火车站看到宪兵服务台,我看了一眼,大兵就要给我发人才招募的广告。难道我暴露了?没理他,继续等车,坐到高雄,转捷运到桥头糖厂参观了一下,买了好多黑糖(其实就是红糖吗?)随后就开始下雨。转战西子湾,参观了打狗铁道故事馆(原高雄港火车站)、英国打狗领事馆官邸旧址,去了一趟旗津,吃了大碗冰,然后才开始找住宿。在火车站附近住下之后又乘地铁去了瑞丰夜市吃饭

6月19日 早晨捷运转高铁,直达桃园,然后在高铁站7-ELEVEN买了“我的美丽日记”面膜。说实话我真不知道她们为什么都喜欢这个品牌。随后转公交车时遇到不能刷卡的加班车,付现金到机场。面膜放在行李里,直挂北京;手里只拿相机和iPad,然后去免税店买了烟酒。吃饭时时间已经快到了,于是牛肉面只吃了牛肉我就跑了,结果发现还没开始登机,唉我的面啊!到香港机场,我主动报告我带了超量香烟,海关没为难我。在机场吃了个许留山,再去乘机的时候,手里的液体被要求托运,于是又花了50块钱打包费。到北京都夜里了,提了货就出来回家

过了几天,发现身份证、备用电池等东西丢失了。我觉得大概是丢在飞机上或者首都机场T2了。呵呵

 

这几天的体验,觉得台湾是个不紧张的地方,即使台北高雄这种大城市,也不像北京。各处景点的游客都很少,而且基本上都不要钱或者很便宜;吃饭也很便宜。他们那里大量依赖硬币,于是有了《听说》电影里吃夜市数了半天零钱的典故。台湾还很依赖机车(摩托车),红灯放绿的时候,噪音跟飞机起飞差不多。派出所厕所随便用、宪兵降旗可以紧密围观、路上垃圾桶很少主要是扔给7-ELEVEN。金融方面,高端洋气上档次的银行都受理人民币和银联业务,大部分商户也可以刷银联信用卡,算是被大陆经济入侵了吧。

Posted in 默认分类 | Tagged , , , , | 4 Comments

postfix 中一次查询多个 maps 需要注意的地方

今天遇到的一个情况,需要配置 postfix 完成下列功能:

  1. 对于特定域名的收件人地址,把信发给特定的服务器
  2. 其它的随机选择出口 IP 往外发送

这两个功能分别都很容易实现:

第一个用 transport_maps 的域后缀匹配功能

example.com smtp:chsMX.example.com

第二个在 main.cf 里创建多个指定 IP 地址的 smtp 出口,然后在 transport_maps 里查询一个 tcp 类型的外部服务,在该外部服务里用随机算法选择出口

但是,两个一起用的时候会出现问题。因为 postfix 会先拿完整的邮件地址去依次查询两个 maps ,第一个匹配不上,然后匹配第二个,成功后直接返回了,导致特殊的邮件域路由功能失效。解决方法是把第一个改为 pcre 或者 regexp 格式的查询表:

/[email protected]/ smtp:chsMX.example.com

这样就可以确保需要特殊处理的邮件域匹配在第一个查询表上,而不会去查询第二个查询表。

Posted in 默认分类 | Tagged , | Leave a comment

我团年会 2013

今年的年会的日子是2月23日,又延续了我团在春节后鼓舞士气,而不是春节前年终放松的传统。

CJ同学想让我运他和她媳妇(也就是我同事)一起去,但是考虑到运输风险,以及我想在全集团单身MMs面前展示自己still available这个原因,所以我还是自己开车去的。午饭时YLM姑娘说找不到地方了,但我当时不知道是有私心还是赶时间,居然没去接她,实在是罪过。

到了现场,把票给CJ,然后开始找弥新锋索要第一年年会的徽章。凑齐三个之后,冒充资深老员工跑去照相。二楼签名墙的摄影师是王程,但快排到我时她说我用蓝票应该去一楼,于是又转一楼,在场地里签名拍照。

虽是年会,但我有点走神,因为要把CJ同学介绍给我们部门,作为潜在招聘对象。所以,年会开始前和前几十分钟我一直在联系给他引见我们的面试官。

 

这次年会的片头很好,我们一贯钟爱的《让子弹飞》再次成为创作基础。听着激昂的音乐,大家的情绪也逐渐亢奋起来。此时,再插入一段回顾历史的老照片,我的泪都出来了。我拿出手机,发了一条微博#美团年会#我以为我们已经习惯优秀,回顾过去,才知道我们走过多远。我庆幸,在这进步的历程中,有我一份力

节目很好,PLMMs也很多,但不知道哪一个属于我。这就是传说中的职场得意情场失意吗?

有个节目,正在上演中,一个演员突然晕倒,被抬下去了。那个晕倒的姑娘正是去年我补入职培训的时候,在课上调戏我的那个。

年会后,是销售部 kickoff 会议,我退场吃饭去了;饭后,回来接了好友LK姑娘,路上聊了好多。这时我发现有些事得有人倾听才能说,有些事说到了才能理清。这次聊过之后,对自己的认识又清晰起来了。

Posted in 默认分类 | Tagged , | 3 Comments

复习了一下ApacheBench的用法,兼谈服务性能测试的多因素问题

我最近有点关注 python web 开发,看了不少关于高性能IO库的对比评测,没怎么看明白,突然觉得自己对于 ab 的用法似乎不怎么想得起来了,于是复习了一下。

实验环境要尽量简单,要能凸显影响性能的几个因素,并能通过改变其中的单个因子再观察性能变化,来得到各因子和性能之间的关系。

服务器端 gunicorn -w 4 -k sync 执行一个只有一句 time.sleep(1) 的服务器程序,确保每次请求的服务时间不低于一秒

ab -c4 的情况下:

Concurrency Level: 4
Time taken for tests: 25.248 seconds
Complete requests: 100
Failed requests: 2
(Connect: 0, Receive: 2, Length: 0, Exceptions: 0)
Write errors: 0
Total transferred: 16000 bytes
HTML transferred: 1400 bytes
Requests per second: 3.96 [#/sec] (mean)
Time per request: 1009.932 [ms] (mean)
Time per request: 252.483 [ms] (mean, across all concurrent requests)
Transfer rate: 0.62 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 951 217.0 1009 1013

测试结果中需要注意的:

  1. Time taken for tests * Concurrency Level == Time per request * Complete requests都是处理所有的请求的总耗时。
  2. Time per request(mean) == Time taken for tests/Complete requests

注意:建立连接耗时最大值接近一秒。

并发数提高一倍,ab -c8 的情况下:

Concurrency Level: 8
Time taken for tests: 25.114 seconds
Complete requests: 100
Failed requests: 11
(Connect: 0, Receive: 11, Length: 0, Exceptions: 0)
Write errors: 2
Total transferred: 16000 bytes
HTML transferred: 1400 bytes
Requests per second: 3.98 [#/sec] (mean)
Time per request: 2009.158 [ms] (mean)
Time per request: 251.145 [ms] (mean, across all concurrent requests)
Transfer rate: 0.62 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 1507 756.6 2005 2015

需要注意的是:

  1. Time per request 翻倍了,而 Time per request * Completed requests 自然也就翻倍了。而 Requests per second 并没有随着并发数的增加而提高。服务器端 sync worker 每次只能处理一个请求,在客户端并发超过服务器端 worker 数,而请求又不能快速处理完毕的情况下,会造成请求积累,所以处理时间会变长
  2. 建立连接耗时的最大值也翻倍了

服务器改用 -k eventlet 试试呢?

客户端先用四个并发:

Concurrency Level: 4
Time taken for tests: 25.097 seconds
Complete requests: 100
Failed requests: 0
Write errors: 0
Total transferred: 16000 bytes
HTML transferred: 1400 bytes
Requests per second: 3.98 [#/sec] (mean)
Time per request: 1003.861 [ms] (mean)
Time per request: 250.965 [ms] (mean, across all concurrent requests)
Transfer rate: 0.62 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.2 0 1

注意建立连接耗时比 -k sync 时大大降低

八并发呢?

Concurrency Level: 8
Time taken for tests: 13.046 seconds
Complete requests: 100
Failed requests: 0
Write errors: 0
Total transferred: 16000 bytes
HTML transferred: 1400 bytes
Requests per second: 7.67 [#/sec] (mean)
Time per request: 1043.689 [ms] (mean)
Time per request: 130.461 [ms] (mean, across all concurrent requests)
Transfer rate: 1.20 [Kbytes/sec] received

Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.5 0 3

处理能力翻倍了;建立连接耗时依然很小

这是服务器端异步处理的好处:一个 worker 可以处理多个并发请求,也就是说目前服务器的处理能力是高于 -w 4 参数指明的 worker 数的。

我认为:

  • Concurrency Level 和 Requests per second 比较接近的情况,说明服务器还没到性能极限
  • ab 压力测试过程中出现大量错误,可能是服务器过载的表现
  • sync 的情况下,显然并发数是不能超过 worker 数的
  • async 类型的 worker,并发数和 worker 数的比例关系,和服务程序的复杂度有关,和 CPU 核数也有关
Posted in 默认分类 | Leave a comment