• Feeds

  • Posts Tagged ‘memcached’


    分布式缓存的一起问题

    背景说明

    分布式缓存中为了可用性及高性能的考虑,可以使用如下一种master/slave设计模式。

    图中的proxy是逻辑的概念,可以是基于client的包装实现,也可以是独立的proxy服务,但本文大部分是指独立的服务。几个主要的问题说明如下。

    为什么cache要使用两个集群((master/slave)来存放?

    主要出于可用性及高性能的考虑。传统的架构使用基于一致性哈希的分布式缓存,数据只存在一份副本,在出现cache节点单点故障时,虽然可以由一致性哈希算法将请求均匀落到其他节点,但由于穿透的请求较多,仍然给数据库带来较大的访问压力。为了避免对数据穿透带来的冲击,数据使用两份副本可以避免穿透的问题。同时在数据访问较大时候,也可以更好的分担流量,避免峰值单份数据跑满对系统带来的冲击。

    为什么两份副本要使用master/slave结构?

    由于大型系统中通常存在多个client同时操作同一份数据,需要确保所有client对数据修改时数据的一致性。为了避免两cluster两份副本数据不一致带来的困扰,使用了一个简单的做法,在配置中人为指定一个cluster为master,所有的数据以master为准。

    为什么一些场景需要使用CAS?

    CAS在计算机并发领域通常指Compare-and-swap,在memcached中,也称为Check And Set. 在分布式系统中,一份数据可能同时被多个调用修改,比如微博中的@箱,一个用户同时收到多个@的情况还是比较常见,比如当原来@箱里面记录是{1,2,3}时,4和5由不同的调用来源同时到达,如果没有同步的保护,系统的数据有可能最终被写成{1,2,3,4}或{1,2,3,5},由于memcached没有原生的list结构,list都是一个自定义的value, 则很容易出现client A覆盖了同时在写的client B的数据。因此假如两个调用方同时读到{1,2,3}时,第一个写入{1,2,3,4}会成功,后续的{1,2,3,5}CAS写入就会失败,因为此时服务器已经不是{1,2,3}了,失败的调用向服务端取回{1,2,3,4},最终写入{1,2,3,4,5}

    在master/slave场景,比起普通的memcache CAS有什么区别?

    目前的做法是master cas成功之后,直接修改slave,并不同时在slave执行cas操作。由于数据存在两份副本,当数据不一致时,无法自动处理数据的不一致冲突。因此在实践上只以master操作为准。

    为什么使用proxy?

    使用proxy主要是出于可用性、命中率以及可运维方面的考虑
    可用性与可运维:当进行服务器增容或缩容时,如果client的数量较大,如果未使用proxy模式,client所在服务器通常需要修改配置并且逐个重启。重启(系统维护)一方面带来可用性方面的问题,运维方面也较为繁琐。
    命中率:如果业务场景需要较高的命中率(比如>90%),则增容或缩容就变得较为复杂,需要client配合做一些策略,比如扩容后仍然访问扩容前旧的节点的数据以保证命中率。如果用proxy模式则极大降低client的访问复杂性,将相关逻辑都封装在proxy之后。

    分布式缓存的一起问题

    最近某业务有一起master单点故障,导致在问题的时间段内,用户看不到最近发生变更的数据。由于在上述场景中,实现cas时候的流程如下
    1) master.cas(k,v)
    2) 如果1成功,slave.set(k,v)
    3) 如果1失败,不执行slave.set(),直接return;

    由于第三步在失败时,并不会set slave,导致数据出现一致性问题,即使slave依然可用,新的数据不会写入cache。

    首先看在master failure时,为什么不切换到slave cas?
    先说自动切换的问题
    上文也提过,两份数据副本在出现数据不一致后,并不能自动仲裁达到最终一致性,但是指定master角色可以达到最终一致性。如果master角色可以由调用方自动切换,则会带来数据的混乱。调用方存在多个节点,至少需要统一的config server来保证切换的一致性。另外,自动切换发生后,无法达到两份数据的最终一致性。
    再说由运维手工切换
    由于不牵涉到代码的逻辑判断,虽然切换也会带来一些数据一致性问题,在具体场景下(比如master长久宕机)切换可以接受。

    在出现上述问题后,其他一些解决方案如下。
    1. proxy在master cas失败时候delete slave data
    2. client在master cas失败时set slave, 并且将数据过期时间设成5分钟

    上述方案很难完美,一些明显存在的问题如下
    方案1:
    命中率的问题。由于delete导致修改的数据迅速失效,会导致读取量的增加,在读写均密集的业务场景,可能会导致数据访问出现波动。
    接口职责单一性的问题。proxy在cas调用中隐藏了删除数据的逻辑,这是一个未在正常期望范围内的额外操作,在特殊情况下,可能会导致不可预料的情况出现。(尽管在实际操作中proxy提供配置开关选项)

    方案2:
    依然是命中率的问题,5分钟过期延缓了过期的访问数据库的压力,但相关压力仍然会传递到数据库。

    希望通过上面说明读者能理解这个场景的问题。在这个场景下,完美的方案应当如何设计?

    Cassandra代替Redis?

    最近用Cassandra的又逐渐多了,除了之前的360案例,在月初的QCon Shanghai 2013 篱笆网也介绍了其使用案例。而这篇百万用户时尚分享网站feed系统扩展实践文章则提到了Fashiolista和Instagram从Redis迁移到Cassandra的案例。考虑到到目前仍然有不少网友在讨论Redis的用法问题,Redis是一个数据库、内存、还是Key value store?以及Redis和memcache在实际场景的抉择问题,因此简单谈下相关区别。

    首先,Redis和Cassandra完全是适合不同的使用场景的NoSQL产品。前者是适用小规模内存型的key value或者key list数据,后者适合存储大规模数据。因此这篇文章提到切换主要原因或许还是前期Redis使用场景不合适,在初创公司项目初期,以顺手的工具快速实现功能的做法也可以理解。

    Redis的几种使用场景

    • 访问量大
    • key value或者key list数据结构
    • 容量小,可控,可以全部放入内存。由于Redis是单线程设计,因此大value会导致后续的请求一定的堵塞。另外hashset当hgetall时候由于存在遍历操作,也不适合集合太大。如果数据超过单机容量可以使用常规的sharding方法分布到多台机
    • 需持久化的场景

    上面四点一般情况下应是必要条件。因此常见网站的用户资料、好友列表就适用用Redis来保存。由于Redis具有memcached所有的特性,也有讨论说memcache是否可以退出了?在以下情况下,我会倾向于选择memcached而非redis

    • 简单无需持久化的key value,比如100字节以下。这种情况下使用memcached空间更节约且维护更简便。
    • 有滚动过期需求,如网站的session,每个新登录的用户定期过期。

    相关观点也可参考Memcached真的过时了吗

    几个问题

    1. 既然Redis可以持久化,用Redis保存的好友列表是否还需要保存到关系数据库?
    2. 手机游戏Clash of Clans中的城堡属性、及用户的金币、圣水、奖杯适用用什么数据结构保存?

    Redis容量及使用规划

    在使用Redis过程中,我们发现了不少Redis不同于Memcached,也不同于MySQL的特征。
    (本文主要讨论Redis未启用VM支持情况)

    1. Schema

    MySQL: 需事先设计
    Memcached: 无需设计
    Redis: 小型系统可以不用,但是如果要合理的规划及使用Redis,需要事先进行类似如下一些规划

    • 数据项: value保存的内容是什么,如用户资料
    • Redis数据类型: 如String, List
    • 数据大小: 如100字节
    • 记录数: 如100万条(决定是否需要拆分)
    • ⋯⋯

    上面的规划就是一种schema,为什么Redis在大型项目需要事先设计schema?因为Redis服务器有容量限制,数据容量不能超出物理内存大小,同时考虑到业务数据的可扩充性,记录数会持续增多、单条记录的内容也都会增长,因此需要提前规划好容量,数据架构师就是通过schema来判断当前业务的Redis是否需要“分库分表”以满足可扩展需求。

    2. 容量及带宽规划

    容量规划
    MySQL: < 硬盘大小
    Memcached: < RAM
    Redis: < RAM

    带宽规划
    由于Redis比MySQL快10倍以上,因此带宽也是需要事先规划,避免带宽跑满而出现瓶颈。

    3. 性能规划(QPS)

    当系统读写出现瓶颈,通常如何解决?
    MySQL
    写: 拆分到多服务器
    读: (1) 拆分 (2) 写少也可以通过增加Slave来解决

    Memcached
    读写: 都通过hash拆分到更多节点。

    Redis:
    写:拆分
    读: (1) 拆分 (2) 写少也可以通过增加Slave来解决

    4. 可扩展性

    MySQL: 分库分表
    Memcached: hash分布
    Redis:也可以分库,也可以hash分布

    小结

    通过以上分析,Redis在很多方面同时具备MySQL及Memcached使用特征,在某些方面则更像MySQL。
    由于Redis数据不能超过内存大小,一方面需要进行事先容量规划,保证容量足够;另外一方面设计上需要防止数据规模无限制增加,进而导致Redis不可扩展。
    Redis需要象MySQL一样预先设计好拆分方案。

    小问题

    在MySQL中,通过预先建立多表或者库可以在业务增长时候将这些表或库一分为二部署到更多服务器上。
    在Redis中,“分库分表”应当如何实现?有什么好的设计模式?

    123