Redis最佳实践 | kikoroc

标签: | 发表时间:2018-09-23 20:59 | 作者:
出处:https://kikoroc.com

redis是一款开源的内存数据存储系统,可以用作数据库、缓存甚至是消息中间件(pub/sub)来使用。与memcache相比,redis支持更多的数据结构,比如string,hash,list,set,bit map,sorted set甚至是geo等等,基本覆盖了日常开发中使用到的数据结构。而且redis十分高效,当然是建立在合理使用的前提下。由于redis单线程的设计特性,任何一条阻塞的命令都会引起redis整个实例的阻塞,所以在使用过程中,需要尽量避免过多使用时间复杂度高的命令(特别是高并发的环境下),一般我们可以通过查看redis command的时间复杂度,但实际使用情况中还是会遇到不少坑,本文主要记录工作中遇到的redis问题,持续更新~~~

找出引起redis慢的原因

slowlog get

通常我们可以通过redis慢日志来找到引起redis慢的命令,用法为 slowlog get 10来查看最慢的10条命令,然后针对性的进行优化。慢日志可以通过redis.conf或者运行时通过:

1      
2
CONFIG SET slowlog-log-slower-than 5000        
CONFIG SET slowlog-max-len 25

来设置slowlog参数,其中 slowlog-log-slower-than表示执行时间超过该值(单位毫秒)的命令记为慢查询, slowlog-max-len可以设置记录的最大条数。可以通过 slowlog reset命令来重置慢日志记录。

info commandstats

info commandstats命令会告诉你整个redis执行了哪些命令、分别执行了多少次、总计耗时、平均每次耗时等信息,同时可以通过 config resetstat命令来重置统计。

client list

redis-cli -h localhost -p 6379 client list | grep -v "omem=0"这条命令在排查redis慢的时候绝对是神技。一般阻塞的命令都会导致omem不断升高,这条命令能快速找到引起阻塞的命令,返回的数据:

1      
id=1212 addr=10.10.10.10:34234 fd=11 name= age=3242 idle=1 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=232432 omem=23132432 events=rw cmds=keys *      

引起阻塞的具体命令,甚至发起命令的机器ip都能查到。

scan instead of keys *

在key数量较少的情况下,我们可以偷懒直接使用 keys *来快速查看模式匹配的key列表,但是一旦key数量上升,或者在高并发的环境下, keys *会带来整个系统的阻塞。因为 keys命令时间复杂度是:

Time complexity:O(N) with N being the number of keys in the database, under the assumption that the key names in the database and the given pattern have limited length.

直接跟database中key数量相关的。替代方案是使用 scan命令,首先看看 scan的时间复杂度:

Time complexity:O(1) for every call. O(N) for a complete iteration, including enough command calls for the cursor to return back to 0. N is the number of elements inside the collection.

虽然scan不能一次性返回所有匹配的key,但是scan提供cursor机制来遍历整个database,最重要的是每次scan操作的时间复杂度是O(1)的,因此只需要多次scan即可得到所有匹配的key。类似的命令还有 sscan, hscan, zscan等分别用于增量迭代set,hash,sorted set等集合元素。

1      
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var scan = function(offset){      
(function(offset){
redis.scan([offset, 'match', 'key_pattern_*', 'count', 1000], function(err, ret){
if(!!err){
console.error(err);
process.exit(-1);
}
if(Number(ret[0] === 0)){
console.log('scan finished');
process.exit(0);
}
// matched keys in ret[1]
// deal with keys
// scan again.
scan(Number(ret[0]));
});
})(offset);
};

scan(0);

使用redis slave

同使用其他数据库一样,读写分离总是能极大的提升系统在高并发情况下的性能,redis也不例外。

一主多从

通常一主多从可以用来实现读写分离,而且能间接的保证数据安全性(master挂了slave还有数据),所以通常比较好的部署结构是M-S-S,即在slave下部署slave而不是全部部署为master的slave,这样master挂了可以将一级slave快速切换为master使用。

多写

多写一般用于写操作很频繁的情况,这时一般需要业务上进行额外的处理,或者更好的办法是增加redis proxy类的中间件来对业务隔离多写的复杂性。

DEL操作隐藏的问题

之前一直以为 del操作不会有性能问题,直到我的膝盖中了一箭…
因为只记得 del对于string和hash的时间复杂度是O(1)的,但是对于list,set,sorted set等居然是O(N)的,所以当你准备使用del来删除一个有百万级数据的集合,那你就准备阻塞吧…

Time complexity:O(N) where N is the number of keys that will be removed. When a key to remove holds a value other than a string, the individual complexity for this key is O(M) where M is the number of elements in the list, set, sorted set or hash. Removing a single key that holds a string value is O(1).

我们的方案是:不直接删除这种大的集合,而是将他们重命名(确认是O(1)的,不用担心:D),然后后台跑一个删除进程慢慢删。。。
首先,将程序中del大集合修改为rename:

1      
2
//cmds.push(['del', 'bigset']);        
cmds.push(['rename', 'bigset', 'gc:bigset']);

接下来部署删除函数:

1      
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var delHugeSet = function(key, cb){      
redis.scard(key, function(err, size){
if(size>500){
redis.srandmember(key, 500, function(err, ids){
//分批慢慢删
redis.srem(key, ids, function(err, ret){
delHugeSet(key, cb);
});
});
}else{
//数据量不大,直接del
redis.del(key, function(err, ret){
cb();
});
}
});
};

相关 [redis 最佳实践 kikoroc] 推荐:

Redis最佳实践 | kikoroc

- -
redis是一款开源的内存数据存储系统,可以用作数据库、缓存甚至是消息中间件(pub/sub)来使用. 与memcache相比,redis支持更多的数据结构,比如string,hash,list,set,bit map,sorted set甚至是geo等等,基本覆盖了日常开发中使用到的数据结构. 而且redis十分高效,当然是建立在合理使用的前提下.

Redis 高可用架构最佳实践 | 温国兵的随想录

- -
0x02 Sentinel 原理. 0x03 Redis 高可用架构. 3.1 Redis Sentinel 集群 + 内网 DNS + 自定义脚本. 3.2 Redis Sentinel 集群 + VIP + 自定义脚本. 3.3 封装客户端直连 Redis Sentinel 端口. 3.4 Redis Sentinel 集群 + Keepalived/Haproxy.

jQuery最佳实践

- andi - 阮一峰的网络日志
上周,我整理了《jQuery设计思想》. 那篇文章是一篇入门教程,从设计思想的角度,讲解"怎么使用jQuery". 今天的文章则是更进一步,讲解"如何用好jQuery". 我主要参考了Addy Osmani的PPT《提高jQuery性能的诀窍》(jQuery Proven Performance Tips And Tricks).

PHP最佳实践

- xiangqian - 阮一峰的网络日志
虽然名字叫《PHP最佳实践》,但是它主要谈的不是编程规则,而是PHP应用程序的合理架构. 它提供了一种逻辑和数据分离的架构模式,属于MVC模式的一种实践. 我觉得,这是很有参考价值的学习资料,类似的文章网上并不多,所以一边学习,一边就把它翻译了出来. 根据自己的理解,我总结了它的MVC模式的实现方式(详细解释见译文):.

MongoDB最佳实践

- - NoSQLFan
将 MongoDB加入到我们的服务支持列表中,是整个团队年初工作计划中的首要任务. 但我们感觉如果先添加一项对NoSQL存储的支持,而不是先升级已支持的关系型数据库,可能对用户不太好,毕竟目前的用户都使用关系型数据库. 所以我们决定将引入MongoDB这项工作放到升级MySQL和PostgreSQL之后来做.

文章: Grails最佳实践

- - InfoQ cn
我在IntelliGrape工作,这是一家专门使用Groovy & Grails进行开发的公司. 本文是我们Grails项目遵循的最佳实践的基本清单,收集自邮件列表、Stack Overflow、博文, 播客和 IntelliGrape的内部讨论. 它们分为控制器、服务、Domain、视图、TagLib、测试和其他.

PHP最佳实践(译)

- - CSDN博客Web前端推荐文章
原文:  PHP Best Practices-A short, practical guide for common and confusing PHP tasks. 译者: youngsterxyf. 本文档最后审阅于2013年3月8日. 由我, Alex Cabal,维护该文档. 我编写PHP程序已有很长一段时间了,当前我 经营着 Scribophile,由认真作家组成的一个在线写作团体,  Writerfolio,为自由职业者提供的一个易用写作工具集,以及  Standard Ebooks,一个图文并茂、无数字版权管理的公共领域电子书出版商.

Log4j最佳实践(原) - Mainz

- - 博客园_Mainz's Blog
本文是结合项目中使用 Log4j总结的最佳实践,非转载. 网上可以找到的是这一篇《 Log4j最佳实践》. 本来 Log4j使用是非常简单的,无需多介绍其用法,这只是在小型项目中;但在 大型的项目中使用 log4j不太一样. 大型项目非常依赖日志,因为解决线上问题必须依靠log,依靠大量的日志.

再谈RestAPI最佳实践

- - 企业架构 - ITeye博客
http://www.javacodegeeks.com/2014/05/rest-api-best-practices-reloaded.html ,仅供学习和参考,转载请注明出处. 近一年半,我参与了2到3个项目的工作,这些项目涉及到大量供“外部”使用的Rest API,稍后我们再来解释为什么要将“外部”这个词放在引号之中.

一些 REST 最佳实践

- - 鸟窝
原文: Some REST best practices, 作者: Pierre-Olivier Bourgeois. 译文: 一些REST最佳实践, 译者: yongx. 如今,REST APIs 已经非常普遍,几乎所有WEB应用都用到了它们. 提供简单,一致,实用的API是种义务,方便其它人很容易的使用.