关于Redis的应用
转载自: http://my.oschina.net/u/176507/blog/137022
写这篇文章主要是在开发www.ximalaya.com的feed(登录首页看到的好友动态,未登录是看不到的)模块使用Redis的一些经验。(www.ximalaya.com是音频为传播介质的SNS网站,喜欢的同学不妨用一用,也有APP的,还是一款非常不错的产品。音乐,相声,有声小说等等 一网打尽)。关于Feed本身的讨论以后再专门发博,有兴趣的同学也可以先开博,然后共同讨论。
Redis这个神器已经在互联网名声大作了,主要是对关系型数据库的补充。因为在大并发下,IO往往是性能的瓶颈,所以把数据尽量放到离cpu近的地方,是解决IO瓶颈的有效途径。呵呵,谁都想把数据放到寄存器,L1 L2 L3cache上,因为这些都是离cpu很近的地方,在一到几十个cpu时钟周期内能读到数据,但是这些缓存空间实在太小了。所以只能把最热的数据去征用这部分缓存。那么比这些数据稍微"冷"一点,但又经常访问的数据怎么办呢?目前最好的途径就是放在内存中。redis,memcache这类的内存数据库就是这样做的。放到内存中就意味着cpu可以在几百个时钟周期内能读到数据。一般服务器配置也是16个G内存吧,在64位系统下,理论可以让你的程序完全占用这16G内存。既然可以在这么快速的读到数据,那么采用什么算法读数据呢?线性查找?那又是浪费cpu时钟周期的。查找最快的莫过于hash表,它的时间复杂度是O(1)。所以本质来说Redis,memcache都是在内存上的hash数据库。Redis和memcache在互联网的应用都比较多,如何选择呢?当然两个都是解决缓存问题的神器,我更趋向于Redis这个神器。原因如下:
1,丰富的数据结构,string,hash,list,set,sortset等数据结构都支持,而memcache仅支持string(相当于key-value结构,这里说string是说的redis中的string),而其他数据结构也是在互联网中常用的。如果你想用就只能选Redis了。
2,可持久化,memcache是不支持持久化的。那么对于缓存的应用,一旦memcache服务挂掉了,内存的数据就丢失了,那么可以快速重启是吧,内存的数据全没了,这时候就有一个热点故障。或许你说可以通过预热来解决这个问题。但是Redis可以不需要预热,他会从rdb或aof中自动重建内存。之前的Redis版本中重启是需要花费很长时间的,但是在2.6版本后这个过程非常快了。即使是开启aof的情况下重启,9G的数据也只需要六分半的时间(www.ximalaya.com的真实案例)。这主要是Redis作者对aof的命令进行了合并,加快了重启的速度。对于需要持久化的应用,memcache就无能为力了,或许你可以选择memcached,当然Redis任然是更好的方案。
3,至于速度方面,Redis不会差于memcache,主要是Redis采用了epoll进行通信吧。
4,memcache是多线程的,Redis是单线程的,虽然更喜欢多线程的应用。但貌似Redis在单线程的操作下速度一点也不逊色,而且让操作更安全了,不需要锁,也不需要线程切换的开销了。Redis在备份rdb的时候会fork一个进程出来,让主进程不受任何影响。
Redis这个服务器,对于高并发快速读写,原子计数,消息队列,实时排名都是实用的。对于互联网网站来说这些都是频繁使用,也是关系型数据库比较头疼的。恰好以上提到的用途都在生成环境中有用到。根据实际经验来看,Redis还是实用于缓存,原子计数。对于持久化存储真的不太适合。因为它会把数据都加载到内存中(如果内存不够用会load到虚拟内存,但这是一个悲剧)。当然如果你用于持久存储的数据是有限的,那么也是非常适合的。但是对于数据需要无限增长的业务,这个真不太适合。网站登录首页的feed,收件箱列表全部放在Redis中。收件箱列表随着注册用户的增长而增长,需要消耗的内存也是逐步增长。为了缓解这个内存压力,客户端做了sharding,实现内存分片。这虽然能够解决内存问题,但需要更多的机器来做。所以我也一直在想怎么来缓解这块内存的问题。扯远了以后在把关于这方面的思考写出来吧。现在还是讲一讲Redis的应用。
在缓存中的应用:
缓存中的应用在memcache中就是对key-value的缓存。它之所以能快,是因为它是存放在内存中的hash数组。内存,hash。这两个关键词就足够解释为什么读写快了。现在对于速度和容量都比合适的存储也就内存最合适吧。ssd的速度快,容量也大。但需要在价格和寿命上有更大的突破才能被广泛使用。况且,ssd的速度比起主存来说还是有一定差距的。
下面对redis中string hash list set sortset分别说下使用场景。
string:这是使用最简单,也是非常频繁的一种数据类型。它就是一种单纯的key-value对。适用于那些通过键直接找值得业务场景。get string的时候需要用到mutil get,这是可以减少网络通信的,也是非常高效的。
hash:hash类型是对于string类型的补充,主要是有些key有共同的特征,不如一个用户对应的多个值,那么用户id只需要出现一次就可以了,对于string来说是需要出现多次的。所以hash是比string更节约了存储。同样hash也可以对多列mutil get,一次网络传输就可以得到所有值。
list:适用于不排重,且有序的操作。这其实就是一个队列,先进先出。放不排重的列表很有用。曾经还用它来做过队列。但队列还是用专门的消息队列更好,因为list不支持消息应答的,也就是不能确保你的消息被处理成功。
set:相当于java中的HashSet,用于排重的列表,但是无序的,在无序的列表还是很适合的。
sortset:这是有序,排重的列表,相当于java中的TreeSet。不过TreeSet是用的红黑树算法,sortset是用的skip table。sortset是很有用的一种数据结构,之前提到的收集箱列表就是用的sortset存放。不过这个数据结构很耗存储。特别是不是用ziplist时。后面谈优化的时候具体说。
Redis中的一些常见优化:
Redis中最好不要开启VM,在2.6版本也是默认关闭的。即使把vm指定到ssd中,redis作者也是反对的。
对于cache的redis,一定要设置最大内存。这样后台线程的LRU数据淘汰策略才能被触发。所以在未设置最大内存的情况下,对数据设置过期时间往往是无效的。这是一个坑,曾经跳到这个大坑里面,通宵解决性能问题。切记!
ziplist很有必要设置大一点。比如之前提到的收件箱长度是500条,但有些时候长度可能达到700条,所以把ziplist设置成1024会是一个好的策略。下面具体介绍下这几个参数:
hash-max-ziplist-entries:针对hash类型,只要hash的filed在这个范围内使用紧凑存储,这可以节约存储,也是为什么要尽量使用hash少使用string的理由,特别是在原子计数的时候,对一个用户可能要记录很多数量,一定要采用hash的策略,这是非常有用的。当然这个值绝不是越大越好,通常不要超过1024
hash-max-ziplist-value:hash的value的大小,在这个大小内会使用紧凑存储。这个值也不是越大越好,最好不要超过512。
list-max-ziplist-entries
list-max-ziplist-value
这两个参数和hash用法一样。不过这是针对list类型的。
zset-max-ziplist-entries
这个参数是针对sortset。在这个范围内,将不使用红黑树,是线性查找,所以这个值也不能太大,一般不要超过1024。
Redis的扩容问题:
Redis扩容目前来还是比较麻烦一点的。因为服务端是不支持数据的sharding的。需要在客户端做sharding。通常有两种sharding策略。一致性hash和非一致性hash。
对于非一致性hash:
可以实现把Redis实例事先切分成多个数据库,比如32或64个库。或者在同一个机器上启多台Redis实例,这样还可以利用多核系统多进程的优势提高并发。当单台机器上的Redis难以支撑时。把这些实例迁移到其他机器上,这样就变相的减轻了单台机器的压力。还可以把数据库切分开,分别放到多台机器上启动。这种做法最大的缺点是,当单台Redis实例的每个数据库都无法支撑数据时,麻烦就来了。这是需要程序把数据迁移出来,这个迁移往往是很耗时的,也是很容易出错的。对于线上的产品这往往是不可以接受的。那么就一致性hash登场吧。
对于一致性hash:
对于一致性hash的介绍可以搜索一下。主要的优势就是,在扩容的时候只需要移动受影响的节点。对于整个集群来说,这些受影响节点的数据是非常少的。如果对于cache来说,加一台机器,几乎不需要移动数据。让cache miss重建缓存也是可以的。当然这个得针对具体应用评估。不要因此而热点故障,挂网站了,呵呵。一致性hash的优势是可以任意加机器解决容量问题。理论上只要机器够,容量就不是问题。
嗯,暂时就写到这里吧,redis中的注意事项还很多,应用也绝不限于此,具体的官方文档已经介绍的很详细。用到的同学,可以发表和补充下Redis的其他用途。如有任何纰漏还多多指出,谢谢!