• Feeds

  • Archive for the ‘data’ Category


    分布式缓存的一起问题

    背景说明

    分布式缓存中为了可用性及高性能的考虑,可以使用如下一种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新的存储模式diskstore

    Redis作者antirez是一个非常勤奋的开发者,在Redis性能已经非常惊人的情况下持续不断开发新的特性,比如从新的cluster源代码看到,作者已经把Dynamo及Paxos一些核心的思想考虑进去并进行了一些简洁的实现。相比其它产品如Memcached则几年没什么大变化,在Web 2.0时代,Memcached已经非常不够用,技术人员需要考虑做很多额外工作才能让Memcached适应新的变化和需求。

    antirez在1月5日Google Groups发表了一篇Redis diskstore文章,对Redis VM方式进行了反省,思考是否有更好的方式来大数据的Redis访问。

    a few months after VM started to work my feeling about it started to be not very good… that VM was not the way to go for the future of Redis

    适合Web 2.0数据访问最佳的方式就是完全基于内存,比如用Memcached或者Redis snapshot方式。但是更多的业务场景是数据规模会超过RAM容量,因此有几种不同的设计模式。

    1. VM方式。将数据分页存放,由应用(如Redis)或者操作系统(如Varnish)将访问量较少的页即冷数据swap到磁盘上,访问多的页面由磁盘自动换出到内存中。应用实现VM缺点是代码逻辑复杂,如果业务上冷热数据边界并不分明,则换入换出代价太高,系统整体性能低。不少抢鲜的网友在微博上也反馈过使用VM种种不稳定情况。操作系统实现VM的缺点前文Redis几个认识误区已经有介绍。

    2. 磁盘方式,所有的数据读写访问都是基于磁盘,由操作系统来只能的缓存访问的数据。由于现代操作系统都非常聪明,会将频繁访问的数据加入到内存中,因此应用并不需要过多特殊逻辑。MongoDB就是这种设计方式。这种方式也有一些已知的缺点,比如操作MMap写入磁盘由操作系统控制,操作系统先写哪里后写哪里应用并不知情,如果写入过程中发生了crash则数据一致性会存在问题。这个也是MongoDB饱受争议的单机Durability问题,

    MongoDB is not designed around single-server durability, but rather multi-server durability.

    不过MongoDB自己并不觉得这是一个问题,他们的意见是,在目前时代有必要考虑单机完全可靠吗?有必要吗?

    3. 硬盘存储 + cache方式。实际原理和mysql+memcache方式类似,只不过将两者功能合二为一到一个底层服务中,简化了调用。

    在上面几种方式中,除去VM,antirez觉得MongoDB方式也不太适合,因此选择了disktore方式来实现新的磁盘存储,具体细节是

    1) 读操作,使用read through以及LRU方式。内存中不存在的数据从磁盘拉取并放入内存,内存中放不下的数据采用LRU淘汰。

    2) 写操作,采用另外spawn一个线程单独处理,写线程通常是异步的,当然也可以把cache-flush-delay配置设成0,Redis尽量保证即时写入。但是在很多场合延迟写会有更好的性能,比如一些计数器用Redis存储,在短时间如果某个计数反复被修改,Redis只需要将最终的结果写入磁盘。这种做法作者叫per key persistence。由于写入会按key合并,因此和snapshot还是有差异,disk store并不能保证时间一致性。

    由于写操作是单线程,即使cache-flush-delay设成0,多个client同时写则需要排队等待,如果队列容量超过cache-max-memory Redis设计会进入等待状态,造成调用方卡住。

    Google Group上有热心网友迅速完成了压力测试,当内存用完之后,set每秒处理速度从25k下降到10k再到后来几乎卡住。 虽然通过增加cache-flush-delay可以提高相同key重复写入性能;通过增加cache-max-memory可以应对临时峰值写入。但是diskstore写入瓶颈最终还是在IO。

    3) rdb 和新 diskstore 格式关系
    rdb是传统Redis内存方式的存储格式,diskstore是另外一种格式,那两者关系如何?

    • 通过BGSAVE可以随时将diskstore格式另存为rdb格式,而且rdb格式还用于Redis复制以及不同存储方式之间的中间格式。
    • 通过工具可以将rdb格式转换成diskstore格式。

    当然,diskstore原理很美好,但是目前还处于alpha版本,也只是一个简单demo,diskstore.c加上注释只有300行,实现的方法就是将每个value作为一个独立文件保存,文件名是key的hash值。因此diskstore需要将来有一个更高效稳定的实现才能用于生产环境。但由于有清晰的接口设计,diskstore.c也很容易换成一种B-Tree的实现。很多开发者也在积极探讨使用bdb或者innodb来替换默认diskstore.c的可行性。

    在Redis几个认识误区中也介绍过,Redis优势是丰富的内存数据结构,这个特性和数据持久保存天生是矛盾的,如用diskstore保存大list/set(如排行榜)性能会很差,每修改一个list元素则需要将整个大list重新保存,开销比使用传统RDBMS高很多。

    用MongoDB的一句设计哲学结尾

    Databases are specializing – the “one size fits all” approach no longer applies.