关于 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 彻底堵住

14367 次点击
所在节点    Redis
74 条回复
Mac
2014-06-10 17:18:08 +08:00
现在的速度快的像飞一样啊
keakon
2014-06-10 17:20:33 +08:00
更简单的方案是放在一个单独的 db 里。
ShunYea
2014-06-10 19:58:35 +08:00
40MS,相当不错
spoonwep
2014-06-10 22:19:53 +08:00
原来如此,最近正在倒腾redis,学习了!
sdjl
2014-06-10 22:22:38 +08:00
想知道v2ex每天pv有多少~~?
precisi0nux
2014-06-11 09:50:57 +08:00
14ms, nice!
wikimore
2014-06-11 17:01:44 +08:00
redis数据越多越觉得坑
nezhazheng
2014-06-12 09:17:45 +08:00
scan就是被设计来解决这个问题的,生产环境keys肯定得禁用,redis单线程,keys之后,其他连接全部等待。
geew
2014-06-30 10:57:26 +08:00
@cloudzhou
@Livid
@nezhazheng
@Yuansir
@KennyZJ
@est

keys = conn.keys(key + '*')
if keys:
conn.delete(*keys)

那使用keys的地方该用什么呢? 比如要删除一些缓存然后知道key的开头..如上代码. 该怎么优化呢??
Livid
2014-06-30 10:58:22 +08:00
@geew 如果 keys 这样的指令是每 10 分钟运行一次,那不会有太大问题。但是如果是每几秒就需要运行一次的话,你就需要考虑其他替代方案了。
geew
2014-06-30 11:09:44 +08:00
@Livid 目前主要用来删除某些相同前缀的缓存, 频率不是固定, 跟后台人员编辑新建数据有关, 问题我觉得倒不会有, 因为数据量不大而且更新也不频繁, 因此其实这条语句使用不多. 但看到了总觉得是个隐藏的坑, 因为这是个组件, 不排除以后会用在别的地方.
也看到了scan, 但不知道是不是我的使用方法有问题,一直报错: ResponseError: unknown command 'SCAN'

scan的原型不是这样的么
#con.scan(self, cursor=0, match=None, count=None)

这样用: con.scan(match=keys+'*')? 有问题?
cloudzhou
2014-06-30 11:21:57 +08:00
@geew
@Livid
按照我的观点,那就是根本不要在线上使用 keys 这个指令,哪怕为了未来考虑,这是定时炸弹。
按照你上面的例子,解决方法其实很简单,两种策略:

1 外部引用,举个例子来说,就是添加值的时候做一次引用:
对 namespace.xx.yy 设值,同时把这个 namespace.xx.yy 放入 sets (以 namespace 划分的 sets),当要批量引用 namespace 开头的值时,从 sets 里面遍历,然后第二次访问。
同理,删除的时候对应删除。
缺点,有时候很难保证一致性,需要做一些补偿方案,内存使用会增加。

2 版本号的概念,对于 redis,我一直还是推荐持久化数据的,并且严格控制数据的动态产生,也就是没有删除数据这个操作,但是如果你是作为 cache 使用并且数据本来就可以丢失的,那么就可以利用版本号。使用 EXPIRE ,也就是 KEY 是有一定生存周期的,并且命名是这样的: namespace.version.xx.yy 其中 version 是一个 hashes 的对应值 {namespace : version},当你要丢弃整个版本号的时候, version = version + 1,之前的 namespace version 版本全部不再使用,在过了一段时间之后(EXPIRE)自然回收。
缺点,只使用易失性数据,cache 使用,内存使用量在丢弃频繁的时候浪费过多。

总之,根据你的需求,有很多种方法,但是尽量不要使用 keys.
nezhazheng
2014-06-30 13:46:12 +08:00
@geew

@cloudzhou
说的是对的,绝对不要在线上使用keys,scan应该是2.8之后加上的指令,你看下你的版本。
geew
2014-06-30 15:11:11 +08:00
@nezhazheng

In [2]: redis.VERSION
Out[2]: (2, 9, 1)

我的用法有问题么?

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

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

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

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

© 2021 V2EX