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节点读取。 [可扩展性差]
- 代理分区:
-
sharding的缺点: [如下是针对k/v caching分析,如redis]
-
涉及多个key的操作不被支持
-
同时操作多个key,不能使用redis事务
-
-
Consistent Hash
- 需求:
- 最简单的hash方法是,直接对server个数取模。但这种方法的问题是
- 可扩展性太差:增加机器的时候会出现大量key迁移的情况。or 频繁出现缓存失效,降低性能。
- 在有机器down的时候,也一样
- in a word,就是普通hash的方法对增减机器不友好。
- --> consistent hash的主要思想是将每个缓存服务器与一个or多个哈希值域区间关联起来,其中区间边界通过计算缓存服务器对应的哈希值来决定。
- 最简单的hash方法是,直接对server个数取模。但这种方法的问题是
- 实现:
- 一致哈希将每个对象映射到圆环边上的一个点,系统再将可用的节点机器映射到圆环的不同位置。 [key和server都映射到圆环上的一个点]
- 一致哈希是对 2 ^ 32 - 1取模。
- 查找某个对象对应的机器时,需要用一致哈希算法计算得到对象对应圆环边上位置,沿着圆环边上查找直到遇到某个节点机器,这台机器即为对象应该保存的位置。
![]()
- 如上图所示,把2^32想象成一个环,三个服务器A、B、C分别映射到圆环上的三个点(最好尽量是均匀的,可以负载均衡)。4个key先map到圆环上后(顺时针)找到第一个server。
- 特点:
- 冗余少
- 负载均衡
- 过渡平滑
- 存储均衡
- 关键词单调 (添加节点后,原有的hash结果要么不迁移,要么迁移到新的节点,不会迁移到旧的节点。)
- 问题:
- hash环的偏斜 --> 缓存分布不均匀
- Solution:虚拟节点
![]()
- 虚拟节点越多,hash环上的节点就越多,缓存被均匀分布的概率就越大。
- 具体做法可以是:在服务器ip or 主机名后增加编号。
- Solution:虚拟节点
- hash环的偏斜 --> 缓存分布不均匀
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.
- reference:
Redis
- 看这篇link.
Codis
- Codis is a proxy based high performance Redis cluster solution.
-
- Codis主要解决的是Redis单点、水平扩展的问题。
- 各个部件是可以动态水平扩展的,尤其无状态的proxy对于动态的负载均衡。
- redis的replication模型:主从异步复制。 [在master上写成功后,在slave上是否能读到这个数据是没有保证的,而让业务方处理一致性的问题还是蛮麻烦的。]
- Codis的HA,并不能保证数据完全不丢失,因为是异步复制,所以master挂掉后,如果有没有同步到slave上的数据,此时将slave提升成master后,刚刚写入的还没来得及同步的数据就会丢失。
Proxy
- proxy-based,无状态proxy,数据状态存储在zookeeper中,底层的数据存储变成了可插拔的部件。
-
-
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
-
-
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。
- proxy HA:因为proxy本身无状态,所以proxy本身的HA是比较好做的。
满地都是六便士,她却抬头看见了月亮。




浙公网安备 33010602011771号