http://hi.baidu.com/yandavid/blog/item/f5f72d9bcc93c1a4c8eaf41a.html
Cassandra的写性能表现卓越这一点众所周知,Cassandra最好运行在多台机器上.据知,Twitter使用了一个45台机器组成的集群.在一台机器上运行Cassandra可能不是很有意义,因为你将失去没有单点故障的系统的优势.
客户端向一个随机的Cassandra节点发出一个写请求.这个节点作为代理往集群写入数据.节点的集群存储在一个节点”环”上,写会按照复制放置策略(replication placement strategy)复制到N个节点上.当使用RackAwareStrategy策略时,为了保证可靠性(reliability)与可用性(Availability), Cassandra会按照复制节点到当前节点的距离将复制节点分为3个桶:与当前节点位于同一机架、与当前节点位于同一数据中心、或位于不同的数据中心.你配置了Cassandra写数据到N个节点来做冗余,Cassandra会将第一份拷贝写入到此数据的主节点,第二份拷贝到环上的位于另一个数据中心的节点,剩余的其它拷贝到与代理节点位于同一个数据中心的机器上.这样就可以确保单点故障不会导致整个集群不可用,即使在整个数据中心都不可用时集群仍然保持可用.
因此,写请求从你的客户端出发到单一随机节点,此节点根据复制放置策略将写操作发送到N个不同的节点.我没有在此讨论很多边缘用例(节点宕机、集群中新增节点、等等),但是,节点需要等待N个节点返回成功并返回成功给客户端.(此处的描述有问题,Cassandra中,还有另外一个W的参数,也就是需要等待几份写拷贝成功才返回成功给客户端,译者加).
节点中的每一个都会以“RowMutation“消息的形式接收到此写请求.对于此消息,节点会采取以下两种行动:
追加此变更到提交日志(Commit log)以满足事务性目的
使用此变更修改一个内存内的Memtable 结构
它的工作就此结束.这就是为什么Cassandra的写操作如此快的原因:最慢的部分就是追加变更日志到文件的操作.与关系型数据库不同的是,Cassandra不会修改存储在磁盘上的数据,也不会去更新索引,因此没有密集的同步磁盘操作来阻塞这次写操作.
还有多个定期发生的异步操作:
当Memtable结构数据满的时候需要写入到SSTable,一个基于磁盘的结构,因此我们不会有太多只存在于内存的数据.
每个给定列族(ColumnFamily)的一组临时的SSTable会被合并到一个大的SSTable.此时,临时的SSTable就没有用了,它们会在将来的某个时间点被当作垃圾回收掉.
读操作与一致性
Brewer的CAP定理是分布式系统中的一个基本定理:分布式系统可以有一致性(Consistency)、可用性(Availability)以及分区容错性(Partition-tolerance)这三种属性,但是只能同时确保其中的两个属性.在Cassandra中,保证可用性A+容错性P并弱化一致性. 考虑下面这种情况,读操作与写操作在时间上非常接近.假设你拥有一个Key “A”,它的值在你的集群中为“123”.现在,你将“A”更新为“456”.写操作被发送到N个不同的节点,每个节点都耗费部分时间来写这个值.现在,你发送一个读取Key “A”的请求.这些节点中的某些节点中这个Key对应的值可能仍然为“123”,而同时节点中的其他节点此Key的值为“456”.他们最终都会返回“456”,但并不能保证什么时候可以做到(在实践中,通常是几个毫秒的时间).接下来你就会发现为什么这一点很重要.
在你的客户端,读操作与写操作类似,客户端发送一个读请求到Cassandra集群中的任一随机节点(也就是存储代理,Storage Proxy).这个代理确定持有需要被读取数据的N份拷贝所在的环上的节点(根据复制放置策略),并对每一个节点发出一个读请求.由于最终一致性的限制,Cassadra允许客户端选择读一致性的强度:
单一读 – 代理返回它获得的第一份响应. 这样很可能返回过期的数据.
仲裁数读取 – 代理等待一个简单多数返回同样的数据.这样读取过期数据(除非有节点宕机)会变得更加困难,但也更慢了.
在后台,代理还会对任何不一致的响应执行读修复(read repair).代理会往任何返回较早的值的节点发送一个写请求,以确保这些节点在将来可以返回最新的值.
扫描范围
作为键值存储(Key/value store),Cassandra运转良好:给定一个键(Key),它就会为你返回这个键(Key)对应的值(Value).但这通常还不足以回答关键的问题:如果我想要读取所有姓(last name)从Z开始用户?或者读取所有在2010年2月1日到2010年3月1日之间下的订单?要回答这些问题,Cassandra必须知道如何来确定持有这些相关值的节点.这个工作是由分割器(Partitioner)来完成的.默认情况下,Cassandra会使用RandomPartitioner(随机分割器),它可以确保将负载均匀地分布在集群上,但是无法使用它来做范围扫描.作为替代,一个列族(Column Family)可以配置使用OrderPreservingPartitioner(保留顺序的分割器),它知道如何将一个范围的键(Key)映射(map)到一个或多个节点上.实际上,它知道哪个(些)节点持有你的按字母排序的用户的数据以及哪个(些)节点持有二月份的订单.
单一节点上的读取操作
因此,将分布式系统的所有胡说八道都放在一边,当执行读操作时每个节点在做什么? 回想一下, Cassandra有两个级别的存储:Memtable与SSTable. 从Memtable中读取相对无痛 – 我们是在内存中进行操作,数据量也相对较小,在这些内容中循环查找尽可能很快.扫描SSTable时,Cassandra使用一个更低级别的列索引与布隆过滤器(bloom filter)来查找磁盘上的必要的数据块,对数据进行反序列化(deserialize),并确定需要返回的真实数据. 这里会发生大量的磁盘IO,因此最终造成的读延时会比类似的DBMS还要高. Cassandra提供了部分行缓存(row caching),它确实解决大部分的延时.
闲话协议(Gossip)
Cassandra是一个有单个节点组成的集群 – 其中没有“主”节点或单点故障-因此,每个节点都必须积极地确认集群中其他节点的状态。它们使用一个称为闲话(Gossip)的机制来做此事.每个节点每秒中都会将集群中每个节点的状态“以闲话的方式传播”到1-3个其他节点.系统为闲话数据添加了版本,因此一个节点的任何变更都会快速地传播遍整个集群.通过这种方式,每个节点都能知道任一其他节点的当前状态:是在正在自举呢, 还是正常运行呢,等等.
提示移交(Hinted Handoff)
Cassandra会存储数据的拷贝到N个节点.客户端可以根据数据的重要性选择一个一致性级别(Consistency level),例如, ConsistencyLevel.QUORUM表示,只有这N个节点中的多数返回成功才表示这个写操作成功.
如果这些节点中的一个宕机了,会发生什么呢?写操作稍后将如何传递到此节点呢?Cassandra使用了一种称为提示移交(Hinted Handoff)的技术来解决此问题,其中数据会被写入并保存到另一个随机节点X,并提示这些数据需要被保存到节点Y,并在节点重新在线时进行重放(记住,当节点Y变成在线时,闲话机制会快速通知X节点).提示移交可以确保节点Y可以快速的匹配上集群中的其他节点.注意,如果提示移交由于某种原因没有起作用,读修复最终仍然会“修复”这些过期数据,不过只有当客户端访问这些数据时才会进行读修复.
提示的写是不可读的(因为节点X并不是这N份拷贝的其中一个正式节点),因此,它们并不会记入写一致性.如果Cassandra的配置了3份拷贝,而其中的两个节点不可用,就不可能实现一个ConsistencyLevel.QUORUM的写操作.
逆熵(Anti-Entropy)
Cassandra的最后一个众所周知的秘密武器是逆熵(Anti-entropy).逆熵明确保证集群中的节点一致认可当前数据.如果由于默认情况,读修复(read repair)与提示移交(hinted handoff)都没有生效,逆熵会确保节点达到最终一致性.逆熵服务是在“主压缩”(等价与关系数据库中的重建表)时运行的,因此,它是一个相对重量级但运行不频繁的进程.逆熵使用Merkle树(也称为散列树)来确定节点在列族(column family)数据树内的什么位置不能一致认可,接着修复该位置的每一个分支