关于 V2EX 在 2014 年 6 月初遇到的性能问题

2014-06-10 11:41:41 +08:00
 Livid
大概是从 5 月中开始,页面的生成时间就越来越长,但是我一直没有找到原因。因为同样的代码,在开发环境里跑是 8ms,到了生产环境就变成 180ms,这太奇怪了。

而且这个性能问题在过去的几个星期里呈现恶化趋势。终于在 2014 年 6 月 10 号这天让网站彻底打不开了。

彻底打不开的那段时间,我看到 CPU 占用率最高的一个进程始终是 redis-server。所以这种情况下,需要的是一个能够 profiling redis 的工具,我在 GitHub 上找到了 Instagram 开源的这个工具:

https://github.com/Instagram/redis-faina

然后,在经历了之前半个月的各种难受和猜测之后,终于找到了原因:

我在代码里滥用了 KEYS 这条指令。

http://redis.io/commands/KEYS

找到问题根源之后,修复就很容易了。

所以几点总结就是:

* KEYS 的性能随着数据库尺寸的增大而越来越慢
* 在一个足够大的数据库上,连续的 KEYS 指令足以让 Redis 彻底堵住

13975 次点击
所在节点    Redis
74 条回复
soli
2014-06-10 13:35:48 +08:00
@Livid 非常感谢。我也回去找找我代码里的 KEYS 。。。
missdeer
2014-06-10 13:36:59 +08:00
75ms
Numbcoder
2014-06-10 13:41:55 +08:00
keys 相当于 select * ,在产品环境几乎是要禁用的。
还有 Instagram 那个脚本也会对性能有很大影响,在产品环境还是少用!
Los
2014-06-10 14:32:55 +08:00
@Livid 在线人数统计为什么不用 Sorted Set 呢?Sorted Set 天然适合干这事
r.zadd("v2ex:online", int(time.time()), member.id)
r.zrangebyscore("v2ex:online", int(time.time()-60*10), "+inf")
Livid
2014-06-10 14:39:00 +08:00
@Los 明白了。谢谢。
Los
2014-06-10 14:44:29 +08:00
@Livid 汗,对 Python 不熟悉,第二段代码应该对取 Unix timestamp 的函数搞错了。
yueyoum
2014-06-10 14:51:26 +08:00
这要是别人来说这个问题,估计早就被你们喷死了吧……
9
2014-06-10 14:57:23 +08:00
@yueyoum 应该不会吧, v2ex 气氛还是挺好的,不过 Livid 比较特殊, 自带主角光环, 所以你懂的.
Livid
2014-06-10 14:58:33 +08:00
@yueyoum
@9

我每天各种压力也很大。
Tinet
2014-06-10 15:05:39 +08:00
看来在做开发的时候还是要抽空多读读官方的文档啊
westup
2014-06-10 15:20:55 +08:00
速度好快啊现在
est
2014-06-10 15:31:14 +08:00
@Livid 可以改用SCAN
jevonszmx
2014-06-10 15:47:51 +08:00
哈,记得一定要看redis手册中的时间复杂度啊,比如ltrim、hgetall之类的,就是陷阱啊,分分钟卡死redis。
Los
2014-06-10 16:09:24 +08:00
#44
总在线人数统计使用 ZCOUNT
r.zcount("v2ex:online", int(time.time())-60*10, "+inf")
某用户是否在线使用 ZSCORE 取得最后活动时间进行判断
r.zscore("v2ex:online", member.id)
wenbinwu
2014-06-10 16:13:46 +08:00
昨天我也碰到一个django的性能问题
ModelFormSet没有指定queryset就会默认使用all()
在production直接100%cpu然后死在那了
ipconfiger
2014-06-10 16:33:26 +08:00
keys 是 O(n)级别的操作啊,绝对不能直接暴露出去调用的
ngn999
2014-06-10 16:38:03 +08:00
不适应这么快的速度。=͟͟͞͞ʕ•̫͡•ʔ
cloudzhou
2014-06-10 16:52:52 +08:00
@Livid
"这次的解决方法是,需要用到 KEYS 的地方,其实是我们目前的在线人数统计,现在这个地方已经加上了缓存,所以对 KEYS 的调用就大大减少了。"
这么看起来,你只是减少了 KEYS 的调用次数,可能就是加入缓存,每3,5分钟 keys() 一次。

我认为这样的做法还不够优雅,对你现在说的这个需求,以下是我的做法:
引入 Sorted sets,创建一个名字叫: user:online
当用户 user(id: user_id),进行一次页面操作的时候,timestamp_now 就是当前时间戳:
> ZADD user:online timestamp_now user_id
对于每一个用户的页面操作都是做这样的操作

* 最新的用户在线列表(精确列出在线用户,以下统计前 1000 个在线用户,按照时间戳逆序)*:
> ZREVRANGE 0 1000
1) user_id_1
2) user_id_2
...

* 统计 5 分钟内的用户数(其实在线是个虚幻的概念,只能说 x 分钟内活跃认为在线)*
> ZCOUNT myzset (timestamp_5_minutes_ago timestamp_now

* 定期清除 x 分钟内没有活跃的用户,控制 Sorted sets 的长度 *
> ZREMRANGEBYSCORE myzset -inf (timestamp_10_minutes_ago

上面操作复杂度:O(log(N)+M) 这是可以控制的,并且数据非常及时和准确。

其实这是一个很好的面试题目。
广告:我需要前端工程师,设计师: http://v2ex.com/t/115602
holy_sin
2014-06-10 16:55:28 +08:00
这个兔子好大
RoCry
2014-06-10 17:01:24 +08:00
早知道有这么大的图就不点开了... 我的流量...

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/116657

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX