Before

  • 需求设计
    • 数据量(amount of storage)
    • QPS
    • number of machine:从数据量和QPS两个角度考虑。
    • eviction策略 (may skip here)
    • access pattern
  • Design Goal
    • latency (most primary for caching system)
    • consistency
    • availability   [在一致性和可用性之间权衡]

Basic Design

Single Machine

  • 单线程:LRU cache -->  hashmap + linkedlist
  • 多线程:  
    • 多线程就避免不了加锁,但是为了效率(尤其是get的效率),需要尽量降低锁的粒度。
    • hashMap可以改成ConcurrentHashMap: 分段锁 + 以及对多读少写的情况十分友好。  [大概同理ConcurrentLinkedList]

Multiple Machines

  • sharding
    • 区间分片
    • HASH分片
    • SLOT分片
  • hash分片
    • 静态hash:算法简单。但加减节点时震荡严重,命中率下降厉害。
    • 一致性hash:加减节点时震荡小,保持较高命中率。但存在数据漂移现象(通过虚拟节点缓解)。
  • 分区实现方式
    • 客户端分区:在client端就已经决定数据会被存储到哪个redis节点或从哪个redis节点读取。   [可扩展性差]
    • 代理分区:client将请求发给代理,然后代理决定。redis和memcached的一种代理实现就是Twemproxy
    • 查询路由(Querying routing):client随即地请求任意一个redis实例,然后由redis将请求转发给正确的redis节点。Redis Cluster实现了一种混合形式的查询路由,但并不是直接将请求转发给正确的redis节点,而是在client端的帮助下直接redirected到正确的redis节点。
  • sharding的缺点:  [如下是针对k/v caching分析,如redis]

    • 涉及多个key的操作不被支持

    • 同时操作多个key,不能使用redis事务

    • 分区粒度是key,so it is not possible to shard a dataset with a single huge key like a very big sorted set

Consistent Hash

  • 需求:
    • 最简单的hash方法是,直接对server个数取模。但这种方法的问题是
      • 可扩展性太差:增加机器的时候会出现大量key迁移的情况。or 频繁出现缓存失效,降低性能。
      • 在有机器down的时候,也一样
    • in a word,就是普通hash的方法对增减机器不友好。
    • -->  consistent hash的主要思想是将每个缓存服务器与一个or多个哈希值域区间关联起来,其中区间边界通过计算缓存服务器对应的哈希值来决定。
  • 实现:
    • 一致哈希将每个对象映射到圆环边上的一个点,系统再将可用的节点机器映射到圆环的不同位置。    [key和server都映射到圆环上的一个点]
    • 一致哈希是对 2 ^ 32  - 1取模
    • 查找某个对象对应的机器时,需要用一致哈希算法计算得到对象对应圆环边上位置,沿着圆环边上查找直到遇到某个节点机器,这台机器即为对象应该保存的位置。
    • 如上图所示,把2^32想象成一个环,三个服务器A、B、C分别映射到圆环上的三个点(最好尽量是均匀的,可以负载均衡)。4个key先map到圆环上后(顺时针)找到第一个server。
  • 特点:
    • 冗余少
    • 负载均衡
    • 过渡平滑
    • 存储均衡
    • 关键词单调  (添加节点后,原有的hash结果要么不迁移,要么迁移到新的节点,不会迁移到旧的节点。)
  • 问题:
    • hash环的偏斜  -->  缓存分布不均匀
      • Solution:虚拟节点
        • 虚拟节点越多,hash环上的节点就越多,缓存被均匀分布的概率就越大。
        • 具体做法可以是:在服务器ip or 主机名后增加编号。

 

Others

Failover

  • replication
  • --> introduce consistency problem
  • replication model:
    • 读写分离:参考MySQL的读写分离   [应该一般来说是同步复制,否则会引入严重的一致性的问题。]   [好像]
    • 主从异步复制
  • Architecture
    • master-slave

Consistency

  • 强一致性
  • 最终一致性

proxy

  • why proxy:
    • 可用性&可运维:当server增缩容时,避免修改client配置并逐个重启。 [与client解耦]   --> 极大降低client的访问复杂性,将相关逻辑封装在proxy
  • 实例:codis -> proxy-based (见下方codis part)

 Highly Available Database

  • Aim at eventual consistency, highly available, no data loss.   [设计目标]
  • 首先,肯定需要shard,就利用上述一致性hash之类的就可以。
  • 下面的重点是:for each shard
  • Master-Slave
    • 如果主从之间异步更新的话,master slave之间可能存在不一致。
    • client读:可以从master or slave。
    • 该设计的问题是,if master goes down,写数据就有问题了,因为只有master能handle写数据。  [master单点问题]
    • 这里,如果直接升slave为主的话,可能会数据丢失。 更严重的是,如果master有硬件问题,那么数据就直接丢失了。
  • Multi-Master
    • 所有节点提供read and write。     [和p2p system的区别在于:这里还是对于某个数据节点,有master的概念(读写是对该data对应的那几个master)。而对p2p的系统,系统中所有节点都是等同的。如果没hit会转发到对应server。]
  • Peer to peer
    • We define a peer to peer system where every node is equally privileged and any two nodes can communicate.
    • --> 不再有SPOF(单点失效)问题。   [有兴趣的话可以看看Cassandra和Amazon的Dynamo,都是这方面的最佳实战]
    • 假设对每个数据,存P份副本。
      • 问题一:怎么选择这P个node?  --> 在一致性hash的场景下,我们选择key映射到的圆上,顺时针的P个server。  [这样可以在failover的时候,仍然hit]
      • 问题二:数据读写 
        • write request: client可以连接ring上任意一个点,该节点可以作为该request的coordinator,coordinator将该请求转发到相应节点,并等待W个(下面会解释W) ack,然后返回write-accepted消息给client。
        • read request:需要获取R个replies之后,再return consolidated data给client。
    • read and write consistency:
      • W and R are called write and read consistency number respectively.
      • W:写成功之前需要获取的ack个数 [the minimum number of nodes from which the coordinating node should get an ack before making a write successful]
      • R: 返回读结果之前需要获取的结果个数  [the minimum number of nodes from which the coordinating node should get back read values to return them back to the client.]
      • R, W together forms quorum of the system.
      • For a read to be consistent(return the latest write), we need to keep W + R > P.
      • 可以根据feature requirement来调整W和R。比如
        • 如果需要fast writes,可以keep W=1 and R=P
        • 如果系统是read heavy,可以keep R=1 and W=P    [provide强一致性,但是非高可用]
        • 如果读写均匀,可以keep R=W=(P + 1) / 2
    • failover: 没有单点问题。
      • 只要有W个以上的节点存活,就可以正常写
      • 只要有R个以上的节点存活,就可以正常读。
  • 以上,在当前这个highly available的system,我们保持W<P。 从而会存在一些unconsistence,只能保证最终一致性。 

Highly Consistent Database

  • Aim at strong consistency, no data loss.   [设计目标]
  • 假设我们保持2个副本(3个机器dying and having a corrupted disk的概率可以忽略不计),那么现在的问题就变成如果保证所有的副本 in absolute sync.
  • 最朴素的方法是:只有写到所有副本后才返回写成功。  --> high write latency
  • 改进一点的做法是:写到majority of machines就返回成功。
    • 为了保证一致性,需要一个master machine来track this information: track which machines have a particular block in each shard.
    • 但这里这个master又引入了单点问题(= =)。
      • Solution是:keep a standby master. (在当前master fail的时候升为master)。
      • 那么接下来的问题是,如何保证,master和standby master in sync?
      • -->  当active master执行namespace modification时,it durably logs a record of the modification to an edit log file stored in the shared directory.  [有没有感觉到很熟悉,HDFS的Journal Node啊!]
      • -->  standby node 不断watches该directory for edits,一旦edits发生,就把它apply到自己的namespace。
      • -->  failover时保证namespace state is fully synchronized.

 

Redis

Codis

  • Codis is a proxy based high performance Redis cluster solution. 
    • It supports multiple stateless proxy with multiple redis instances and is enginerred to elastically scale, easily add or remove redis or proxy instances on-demand/dynamicly.

  • Codis主要解决的是Redis单点、水平扩展的问题。
  • 各个部件是可以动态水平扩展的,尤其无状态的proxy对于动态的负载均衡。
  • redis的replication模型:主从异步复制。    [在master上写成功后,在slave上是否能读到这个数据是没有保证的,而让业务方处理一致性的问题还是蛮麻烦的。]
  • Codis的HA,并不能保证数据完全不丢失,因为是异步复制,所以master挂掉后,如果有没有同步到slave上的数据,此时将slave提升成master后,刚刚写入的还没来得及同步的数据就会丢失。

Proxy

  • proxy-based,无状态proxy,数据状态存储在zookeeper中,底层的数据存储变成了可插拔的部件。
  • Codis Proxy (codis-proxy),处理客户端请求,支持Redis协议,因此客户端访问Codis Proxy跟访问原生Redis没有什么区别

  • proxy无状态 —> 很容易搭多个proxy来实现ha和横向扩容。

    • 比如,可以使用jodis,来实现proxy层的HA(通过监控zk上的注册信息来实时获得当前可用的proxy列表,同时通过RoundRobin来实现proxy lb)。

Zookeeper

  • 对zookeeper的依赖:用zk作为强一致性存储。 
    • Codis的特点是动态的扩容缩容,对业务透明;zk除了存储路由信息,同时还作为一个事件同步的媒介服务,比如变更master或者数据迁移这样的事情,需要所有的proxy通过**特定zk事件来实现 可以说zk被我们当做了一个可靠的rpc的信道来使用。
    • 比如,Redis的主从切换是通过codis-ha在zk上遍历各个server group的master判断存活情况,来决定是否发起提升新master的命令。
  • --> 再codis后面版本的设计中可能的改进:使用proxy内置的Raft来代替外部的zk。 [减少依赖]

Slot

  • Slot是Codis数据分配、迁移等操作的基本单位,可以把slot看成一致性哈希中的Bucket、VNode等概念。

  • key —> crc32(key) % 1024 —> (corresponding) bucket

  • codis用的不是consistent hash,但是感觉原理差不太多。

Consistency

  • in a word: 不支持。
  • 说到一致性,这也是Codis支持的MGET/MSET无法保证原本单点时的原子语义的原因。 因为MSET所参与的key可能分不在不同的机器上,如果需要保证原来的语义,也就是要么一起成功,要么一起失败,这样就是一个分布式事务的问题,对于Redis来说,并没有WAL或者回滚这么一说,所以即使是一个最简单的二阶段提交的策略都很难实现,而且即使实现了,性能也没有保证。
  • (可以去看redis关于一致性的那部分,redis事务一致性就已经不能满足一致性了)

HA

  • codis HA
    • proxy HA:因为proxy本身无状态,所以proxy本身的HA是比较好做的。
      • 在生产环境中,我们使用的是jodis,这个是我们开发的一个jedis连接池,很简单,就是zk上面的存活proxy列表,挨个返回jedis对象,达到负载均衡和HA的效果。
    • redis HA:具体来说是各个server group的master。
      • 在一开始的时候codis本来就没有将这部分的HA设计进去,因为Redis在挂掉后,如果直接将slave提升上来的话,可能会造成数据不一致的情况,因为有新的修改可能在master中还没有同步到slave上,这种情况下需要管理员手动的操作修复数据。
      • 后来我们发现这个需求确实比较多的朋友反映,于是我们开发了一个简单的ha工具:codis-ha,用于监控各个server group的master的存活情况,如果某个master挂掉了,会直接提升该group的一个slave成为新的master。