• 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的文章,可通过页面右上方扫码订阅最新更新。

    « | »

    85 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. It’ll be a snap for you’… I greatly recognize this.

    32. redis

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

    33. This would absolutely have to be one of the most crude circumstances of belly training, but normally one of the most
      well known sort of midsection training is with
      a bodice!

    Leave a Comment