tape is dead,disk is tape,flash is disk,ram locality is king. — jim gray
redis不是比较成熟的memcache或者mysql的替代品,是对于大型互联网类应用在架构上很好的补充。现在有越来越多的应用也在纷纷基于redis做架构的改造。
可以简单公布一下redis平台实际情况
2200+亿 commands/day 5000亿read/day 500亿write/day
18tb+ memory
500+ servers in 6 idc 2000+instances
应该是国内外比较大的redis使用平台,今天主要从应用角度谈谈redis服务平台。
<a target="_blank"></a>
可以预见的是,有很多同学认为把计数全部存在内存中成本非常高,我在这里用个图表来表示下我的观点:

mem
很多情况大家都会设想纯使用内存的方案会很有很高成本,但实际情况往往会有一些不一样:
1.cost,对于有一定吞吐需求的应用来说,肯定会单独申请db、cache资源,很多担心db写入性能的同学还会主动将db更新记入异步队列,而这三块的资源的利用率一般都不会太高。资源算下来,你惊异的发现:反而纯内存的方案会更精简!
2.kiss原则,这对于开发是非常友好的,我只需要建立一套连接池,不用担心数据一致性的维护,不用维护异步队列。
3.cache穿透风险,如果后端使用db,肯定不会提供很高的吞吐能力,cache宕机如果没有妥善处理,那就悲剧了。
4.大多数的起始存储需求,容量较小。
面对微博常常出现的热点,如最近出现了较为火爆的短链,短时间有数以万记的人点击、跳转,而这里会常常涌现一些需求,比如我们向快速在跳转时判定用户等级,是否有一些账号绑定,性别爱好什么的,已给其展示不同的内容或者信息。
普通采用memcache+mysql的解决方案,当调用id合法的情况下,可支撑较大的吞吐。但当调用id不可控,有较多垃圾用户调用时,由于memcache未有命中,会大量的穿透至mysql服务器,瞬间造成连接数疯长,整体吞吐量降低,响应时间变慢。
这里我们可以用redis记录全量的用户判定信息,如string key:uid int:type,做一次反向的cache,当用户在redis快速获取自己等级等信息后,再去mc+mysql层去获取全量信息。如图:
concept2
当然这也不是最优化的场景,如用redis做bloomfilter,可能更加省用内存。
产品运营总会让你展示最近、最热、点击率最高、活跃度最高等等条件的top list。很多更新较频繁的列表如果使用mc+mysql维护的话缓存失效的可能性会比较大,鉴于占用内存较小的情况,使用redis做存储也是相当不错的。
用户最近访问记录也是redis list的很好应用场景,lpush lpop自动过期老的登陆记录,对于开发来说还是非常友好的。
这里把两个功能放在最后,因为这两个功能在现实问题当中遇到了一些困难,但在一定阶段也确实解决了我们很多的问题,故在这里只做说明。
pinterest使用redis存储社交graph信息:
<a href="http://blog.gopivotal.com/case-studies-2/using-redis-at-pinterest-for-billions-of-relationships" target="_blank">http://blog.gopivotal.com/case-studies-2/using-redis-at-pinterest-for-billions-of-relationships</a>
message queue就是通过list的lpop及lpush接口进行队列的写入和消费,由于本身性能较好也能解决大部分问题。
redis 的lua的功能扩展实际给redis带来了更多的应用场景,你可以编写若干command组合作为一个小型的非阻塞事务或者更新逻辑,如:在收到message推送时,同时1.给自己的增加一个未读的对话 2.给自己的私信增加一个未读消息 3.最后给发送人回执一个完成推送消息,这一层逻辑完全可以在redis server端实现。
但是,需要注意的是redis会将lua script的全部内容记录在aof和传送给slave,这也将是对磁盘,网卡一个不小的开销。
很多测试和应用均已证明,
1.在性能方面redis并没有落后memcache多少,而单线程的模型给redis反而带来了很强的扩展性。
2.在很多场景下,redis对同一份数据的内存开销是小于memcache的slab分配的。
3.redis提供的数据同步功能,其实是对cache的一个强有力功能扩展。
我们线上的redis 95%以上是承担后端存储功能的,我们不仅用作cache,而更为一种k-v存储,他完全替代了后端的存储服务(mysql),故其数据是非常重要的,如果出现数据污染和丢失,误操作等情况,将是难以恢复的。所以备份是非常必要的!为此,我们有共享的hdfs资源作为我们的备份池,希望能随时可以还原业务所需数据。
由于redis单线程(严格意义上不是单线程,但认为对request的处理是单线程的)的模型,大的数据结构list,sorted set,hash set的批量处理就意为着其他请求的等待,故使用redis的复杂数据结构一定要控制其单key-struct的大小。
另外,redis单实例的内存容量也应该有严格的限制。单实例内存容量较大后,直接带来的问题就是故障恢复或者rebuild从库的时候时间较长,而更糟糕的是,redis rewrite aof和save rdb时,将会带来非常大且长的系统压力,并占用额外内存,很可能导致系统内存不足等严重影响性能的线上故障。我们线上96g/128g内存服务器不建议单实例容量大于20/30g。
业界资料和使用比较多的是redis sentinel(哨兵)
<a href="http://www.huangz.me/en/latest/storage/redis_code_analysis/sentinel.html" target="_blank">http://www.huangz.me/en/latest/storage/redis_code_analysis/sentinel.html</a>
<a href="http://qiita.com/wellflat/items/8935016fdee25d4866d9" target="_blank">http://qiita.com/wellflat/items/8935016fdee25d4866d9</a>
2000行c实现了服务器状态检测,自动故障转移等功能。
hypnos是神话中的睡神,字面意思也是希望我们工程师无需在休息时间处理任何故障。:-)
其工作原理示意如下:
hypnos
talk is cheap, show me your code! 稍后将单独写篇博客细致讲下hypnos的实现。
发现一种情况,开发在沟通后端资源设计的时候,常常因为习惯使用和错误了解产品定位等原因,而忽视了对真实使用用户的评估。也许这是一份历史数据,只有最近一天的数据才有人进行访问,而把历史数据的容量和最近一天请求量都抛给内存类的存储现实是非常不合理的。
所以当你在究竟使用什么样的数据结构存储的时候,请务必先进行成本衡量,有多少数据是需要存储在内存中的?有多少数据是对用户真正有意义的。因为这其实对后端资源的设计是至关重要的,1g的数据容量和1t的数据容量对于设计思路是完全不一样的
全部改造线上master-slave数据同步机制,这一点我们借鉴了mysql replication的思路,使用rdb+aof+pos作为数据同步的依据,这里简要说明为什么官方提供的psync没有很好的满足我们的需求:
假设a有两个从库b及c,及 a `— b&c,这时我们发现master a服务器有宕机隐患需要重启或者a节点直接宕机,需要切换b为新的主库,如果a、b、c不共享rdb及aof信息,c在作为b的从库时,仍会清除自身数据,因为c节点只记录了和a节点的同步状况。
故我们需要有一种将a`–b&c 结构切换切换为a`–b`–c结构的同步机制,psync虽然支持断点续传,但仍无法支持master故障的平滑切换。
实际上 我们已经在我们定制的redis计数服务上使用了如上功能的同步,效果非常好,解决了运维负担,但仍需向所有redis服务推广,如果可能我们也会向官方redis提出相关sync slave的改进。
细心的同学发现我们除了使用dns作为命名系统,也在zookeeper中有一份记录,为什么不让用户直接访问一个系统,zk或者dns选择其一呢?
其实还是很简单,命名系统是个非常重要的组件,而dns是一套比较完善的命名系统,我们为此做了很多改进和试错,zk的实现还是相对复杂,我们还没有较强的把控粒度。我们也在思考用什么做命名系统更符合我们需求。
大内存的使用肯定是一个重要的成本优化方向,flash盘及分布式的存储也在我们未来计划之中。
原文发布时间为:2013-10-10
本文来自云栖社区合作伙伴“linux中国”