• Feeds

  • Redis几个认识误区

    前几天微博发生了一起大的系统故障,很多技术的朋友都比较关心,其中的原因不会超出James Hamilton在On Designing and Deploying Internet-Scale Service(1)概括的那几个范围,James第一条经验“Design for failure”是所有互联网架构成功的一个关键。互联网系统的工程理论其实非常简单,James paper中内容几乎称不上理论,而是多条实践经验分享,每个公司对这些经验的理解及执行力决定了架构成败。

    题外话说完,最近又研究了Redis。去年曾做过一个MemcacheDB, Tokyo Tyrant, Redis performance test,到目前为止,这个benchmark结果依然有效。这1年我们经历了很多眼花缭乱的key value存储产品的诱惑,从Cassandra的淡出(Twitter暂停在主业务使用)到HBase的兴起(Facebook新的邮箱业务选用HBase(2)),当再回头再去看Redis,发现这个只有1万多行源代码的程序充满了神奇及大量未经挖掘的特性。Redis性能惊人,国内前十大网站的子产品估计用1台Redis就可以满足存储及Cache的需求。除了性能印象之外,业界其实普遍对Redis的认识存在一定误区。本文提出一些观点供大家探讨。

    1. Redis是什么

    这个问题的结果影响了我们怎么用Redis。如果你认为Redis是一个key value store, 那可能会用它来代替MySQL;如果认为它是一个可以持久化的cache, 可能只是它保存一些频繁访问的临时数据。Redis是REmote DIctionary Server的缩写,在Redis在官方网站的的副标题是A persistent key-value database with built-in net interface written in ANSI-C for Posix systems,这个定义偏向key value store。还有一些看法则认为Redis是一个memory database,因为它的高性能都是基于内存操作的基础。另外一些人则认为Redis是一个data structure server,因为Redis支持复杂的数据特性,比如List, Set等。对Redis的作用的不同解读决定了你对Redis的使用方式。

    互联网数据目前基本使用两种方式来存储,关系数据库或者key value。但是这些互联网业务本身并不属于这两种数据类型,比如用户在社会化平台中的关系,它是一个list,如果要用关系数据库存储就需要转换成一种多行记录的形式,这种形式存在很多冗余数据,每一行需要存储一些重复信息。如果用key value存储则修改和删除比较麻烦,需要将全部数据读出再写入。Redis在内存中设计了各种数据类型,让业务能够高速原子的访问这些数据结构,并且不需要关心持久存储的问题,从架构上解决了前面两种存储需要走一些弯路的问题。

    2. Redis不可能比Memcache快

    很多开发者都认为Redis不可能比Memcached快,Memcached完全基于内存,而Redis具有持久化保存特性,即使是异步的,Redis也不可能比Memcached快。但是测试结果基本是Redis占绝对优势。一直在思考这个原因,目前想到的原因有这几方面。

    • Libevent。和Memcached不同,Redis并没有选择libevent。Libevent为了迎合通用性造成代码庞大(目前Redis代码还不到libevent的1/3)及牺牲了在特定平台的不少性能。Redis用libevent中两个文件修改实现了自己的epoll event loop(4)。业界不少开发者也建议Redis使用另外一个libevent高性能替代libev,但是作者还是坚持Redis应该小巧并去依赖的思路。一个印象深刻的细节是编译Redis之前并不需要执行./configure。
    • CAS问题。CAS是Memcached中比较方便的一种防止竞争修改资源的方法。CAS实现需要为每个cache key设置一个隐藏的cas token,cas相当value版本号,每次set会token需要递增,因此带来CPU和内存的双重开销,虽然这些开销很小,但是到单机10G+ cache以及QPS上万之后这些开销就会给双方相对带来一些细微性能差别(5)。

    3. 单台Redis的存放数据必须比物理内存小

    Redis的数据全部放在内存带来了高速的性能,但是也带来一些不合理之处。比如一个中型网站有100万注册用户,如果这些资料要用Redis来存储,内存的容量必须能够容纳这100万用户。但是业务实际情况是100万用户只有5万活跃用户,1周来访问过1次的也只有15万用户,因此全部100万用户的数据都放在内存有不合理之处,RAM需要为冷数据买单。

    这跟操作系统非常相似,操作系统所有应用访问的数据都在内存,但是如果物理内存容纳不下新的数据,操作系统会智能将部分长期没有访问的数据交换到磁盘,为新的应用留出空间。现代操作系统给应用提供的并不是物理内存,而是虚拟内存(Virtual Memory)的概念。

    基于相同的考虑,Redis 2.0也增加了VM特性。让Redis数据容量突破了物理内存的限制。并实现了数据冷热分离。

    4. Redis的VM实现是重复造轮子

    Redis的VM依照之前的epoll实现思路依旧是自己实现。但是在前面操作系统的介绍提到OS也可以自动帮程序实现冷热数据分离,Redis只需要OS申请一块大内存,OS会自动将热数据放入物理内存,冷数据交换到硬盘,另外一个知名的“理解了现代操作系统(3)”的Varnish就是这样实现,也取得了非常成功的效果。

    作者antirez在解释为什么要自己实现VM中提到几个原因(6)。主要OS的VM换入换出是基于Page概念,比如OS VM1个Page是4K, 4K中只要还有一个元素即使只有1个字节被访问,这个页也不会被SWAP, 换入也同样道理,读到一个字节可能会换入4K无用的内存。而Redis自己实现则可以达到控制换入的粒度。另外访问操作系统SWAP内存区域时block进程,也是导致Redis要自己实现VM原因之一。

    5. 用get/set方式使用Redis

    作为一个key value存在,很多开发者自然的使用set/get方式来使用Redis,实际上这并不是最优化的使用方法。尤其在未启用VM情况下,Redis全部数据需要放入内存,节约内存尤其重要。

    假如一个key-value单元需要最小占用512字节,即使只存一个字节也占了512字节。这时候就有一个设计模式,可以把key复用,几个key-value放入一个key中,value再作为一个set存入,这样同样512字节就会存放10-100倍的容量。

    这就是为了节约内存,建议使用hashset而不是set/get的方式来使用Redis,详细方法见参考文献(7)。

    6. 使用aof代替snapshot

    Redis有两种存储方式,默认是snapshot方式,实现方法是定时将内存的快照(snapshot)持久化到硬盘,这种方法缺点是持久化之后如果出现crash则会丢失一段数据。因此在完美主义者的推动下作者增加了aof方式。aof即append only mode,在写入内存数据的同时将操作命令保存到日志文件,在一个并发更改上万的系统中,命令日志是一个非常庞大的数据,管理维护成本非常高,恢复重建时间会非常长,这样导致失去aof高可用性本意。另外更重要的是Redis是一个内存数据结构模型,所有的优势都是建立在对内存复杂数据结构高效的原子操作上,这样就看出aof是一个非常不协调的部分。

    其实aof目的主要是数据可靠性及高可用性,在Redis中有另外一种方法来达到目的:Replication。由于Redis的高性能,复制基本没有延迟。这样达到了防止单点故障及实现了高可用。

    小结

    要想成功使用一种产品,我们需要深入了解它的特性。Redis性能突出,如果能够熟练的驾驭,对国内很多大型应用具有很大帮助。希望更多同行加入到Redis使用及代码研究行列。

    参考文献

    1. On Designing and Deploying Internet-Scale Service(PDF)
    2. Facebook’s New Real-Time Messaging System: HBase To Store 135+ Billion Messages A Month
    3. What’s wrong with 1975 programming
    4. Linux epoll is now supported(Google Groups)
    5. CAS and why I don’t want to add it to Redis(Google Groups)
    6. Plans for Virtual Memory(Google Groups)
    7. Full of keys(Salvatore antirez Sanfilippo)

    -EOF-
    上一篇博文多IDC数据时序问题及方法论在新浪微博上面有更多讨论及留言,有兴趣可以去围观。http://t.sina.com.cn/10503/zF0tex7z7b(需登录)

    如想及时阅读Tim Yang的文章,可通过页面右上方扫码订阅最新更新。

    « | »

    83 Comments  »

    1. Eguo Wang

      我之前项目选用的是mongodb,相对于常规kv数据库提供了更多实用的查询操作。我有一个问题想请教你:redis中高效的条件搜索如何做呢?难道真的要自己解数据后构建吗?或使用附加的搜索引擎?

    2. Eguo Wang

      看到wiki中有几个指令大概是做搜索操作。

    3. kula

      redis的vm模式在实践中存在一些问题.

      我使用过redis2.0.2, 发现当vm模式打开的时候, 并发连接数在1500以上时, redis latency会大大增加.平均每个请求的latency会超过4000ms, 观察redis的进程cpu占用率, 会超过100%. 最后迫于无奈,关掉了redis的vm功能. 此时并发连接不变的情况下,redis的latency下降到2ms以下. cpu占用率下降到1%.

      没有深入研究过这个症状,但初步怀疑是redis的vm实现有点不靠谱.

    4. kula

      redis的vm模式在实践中存在一些问题.
      我使用过redis2.0.2, 发现当vm模式打开的时候, 并发连接数在1500以上时, redis latency会大大增加.平均每个请求的latency会超过4000ms, 观察redis的进程cpu占用率, 会超过100%. 最后迫于无奈,关掉了redis的vm功能. 此时并发连接不变的情况下,redis的latency下降到2ms以下. cpu占用率下降到1%.
      没有深入研究过这个症状,但初步怀疑是redis的vm实现有点不靠谱.

    5. 是不是笔误,呵呵。

      “没一行需要存储一些重复信息” ==> “每一行需要存储一些重复信息”

    6. empyreaner

      @Eguo Wang
      redis的搜索都需要自己做索引。使用keys指令做搜索消耗比较大,不能作为常用指令。

    7. VM还是不好用,频繁访问老会报错,
      hashset更适合像cache这样一对一的存储,批量get的方法只能针对一个具体的hash值

    8. fzuslide

      Redis在写snapshot的时候CPU使用率很高,数据库本身很大的情况下,回写磁盘过程持续的时间会比较久。这种情况下AOF开销相对较小,然后定时的BGrewriteAOF来减小aof大小,replication还是必须要做的。

    9. davies

      Redis 并不是神奇, 它有点像个拥有十八般武艺的四不像, 想要解决所有问题, 却没有完全好地解决掉任何一个问题.

    10. Hanson Lu

      以后有个开源哦项目Prevayler,也是使用snapshot + 日志的方式实现持久化,也是挺不错的,不过由于开启了fsync操作,导致效率很低,不知redis是否使用了fsync

    11. Bob

      最近也在关注这方面的资料,有在InfoQ上看到天涯团队的一篇文章:
      http://www.infoq.com/cn/news/2010/11/tianya-memlink

      里面提到了2点,
      1. Redis的复制机制不完善,失步之后要重新同步所有数据。
      2. Redis的操作时单线程的,没有办法利用多核的优势。

      不知Tim怎么看这两个问题?对实际应用的影响大吗?

    12. Tim

      @bob

      1. 可以架两级slave解决
      2. 通过单服务器多端口刚好发挥这种优势,我上次微博公开演讲也介绍过。

    13. test

      我觉得你的很多想法都非常奇怪。

    14. 支持作者的观点,深有体会。

    15. 用get/set方式使用Redis:
      最好不要在redis client上封装get set的方式使用hash的方式,否则sort操作的时候可能会有问题。

    16. 补充:
      sort支持根据hashs filed进行排序 例如:
      SORT mylist BY weight_*->fieldname GET object_*->fieldname
      如果client上封装了set方法使其使用hashes的方式存储以节省内存,此时hashes的key是根据一定的算法确定的,假如是sha1,那么set方法的key会分布到不同的hashes的key中,此时如果做排序会导致 需要遍历hashes。

    17. 接上:
      在启用vm的情况下 如果按照参文献7中所说 ,通过sha1算法将key value放入到不同的hashes中,这时会出现一个问题:vm的换出是根据age*log asize 来选出最适合的key,将对应的value放入swap中,由于hashes的key是通过算法算出来的,有可能导致部分的冷filed因为与hot fileld使用同一个key 而无法被交换出去。内存开销是小了,但可能无法换出了。

    18. domiguo

      我们也打算使用redis,但是对他的persistence有一些担心,
      请问你们使用redis的方法后端还有其它设备的备份吗,比如Mysql之类的,还是完全靠redis本身的persistence加上一些业务级别的保证?

    19. 云集国内redis大佬的QQ群正在招募各路redis朋友,不管你是正在使用redis,还是在研究redis,或者是对redis感兴趣,你都可以加入到我们的阵营。大家一起讨论redis各类用法,key-value、list、set、map使用,redis优化、内存监控、主从配置等等问题,这里都会有高手给你解决。

      注意啦,国内顶尖级别的redisQQ群是 43016055

    20. 访问操作系统的 SWAP 需要 block 进程。实际上自己实现 VM,访问存在磁盘上的冷数据时肯定是要 block 进程的。所以这点理由有点牵强。

    21. geelou

      redis java分群 163264749 是java的
      Redis PHP分群 163265386 是php的
      redis c,c++,c#分群 163269313 是.net的
      redis shell,python群 69287882 是shell,python,prel脚本类的

    22. 非常好,尤其是对于redis的性能超过memcached的描述。

    23. 写得非常好,我们也准备使用REDIS,对一些概念又更加清晰

    24. pally

      very good!
      happy to see it!

    25. 看了评论,我都不怎么敢用了,本身出于用缓存来减少服务器的压力,而不是用NoSQL,这货貌似是NoSQL。。

    26. 学习了,刚开始看redis。

    27. GF

      MySQL 最新开发版 5.6.6 目前尚未发布,但从官网给出的 CHANGES 文档可得知,该版本将内嵌 memcached 的支持,以后可以用No SQL的方式使用mysql,在数据库中充分利用memcached的优点。缓存和数据的一致性问题不再是个问题。

    28. Eguo Wang

      DDDDD!!

    29. julie

      大家有没有在广域网中使用过redis?master和slave的同步性能情况怎样,有人知道吗?

    30. 读测试,我们的测到的是8k/s,并不是意料中的快(C++)

    31. redis

      关注这方面技术的同学可以加QQ群479189837讨论

    Leave a Comment