Cassandra源码阅读(6)-数据读取流程

本文主要描述Cassandra的数据读取流程,包括节点的选定以及选定以后如何从Memtable和SSTable读取数据的过程.

节点的选定流程

主入口在SelectStatmemt.execute(NormalPager…)这里,最终调用到storageProxy.read上面。这里只讨论ConsistencyLevel不为serial的情况,且指定partitionKey没有aggressive的读取过程,在CQL中查询多个partitionKey的话,Cassandra会按照单个key逐个异步向对应机器查询,然后同步等待并返回结果。

整体思路

整体读过程的思路如下:

  1. 按照响应时间的快慢从snitch中获取副本节点的位置,snitch是cassandra中负责节点位置的组件。

  2. 按照partitionKey发送请求到最近的副本,在下列情况中,同时发送摘要请求到:
    2.1 如果打开了readrepair,发送到所有副本
    2.2 最近的R-1个副本,R是ConsistencyLevel指定的个数

  3. 等待从R个副本的返回

  4. 如果摘要与读取数据的摘要一致就返回,不一致就从所有副本中读数据并开始ReadRepair

代码细节

  1. 按照查询的key获取对应的所有副本节点

  2. 生成一个随机数,如果大于配置的readRepairChance就为此次查询开启一个全局的readrepair(readRepairDecision.GLOBAL),否则就不开启(readRepairDecision.NONE)

  3. 根据2的readRepairDecision来决定查询的副本,如果是NONE的话就只要ConsistencyLevel中指定的个数,如果是GLOBAL就对应该key的全部副本节点

  4. 根据3中的结果,第一个节点为数据请求(DataRequest),剩下的为摘要请求(DigestRequest)

  5. 向对应的节点发出请求,并等待回应。回应之后检查返回的数据的摘要是否和摘要请求返回的摘要相同(摘要请求之间也check),如果相同则返回数据给客户端,如果不相同就开始ReadRepair。

Memtable和SSTable读取数据

上面介绍的步骤2发的是一个
MessageOut<ReadCommand>,对应的节点收到请求之后由ReadCommandVerbHandler来处理。

下面介绍节点收到请求之后的处理逻辑,这里以单partition查询为例,ReadCommandVerbHandler最后调用到singlePartitionReadCommand.queryStorage方法。

  1. 如果被查询的表开启了cache,就先查cache,如果cache中的数据能够满足这次查询就直接返回,否则就执行queryMemtableAndDiskInternal方法

  2. queryMemtableAndDiskInternal简单来说就是把memtable先从memtable里面读,再从sstable里面读,最后把读到的结果合并成最终的结果。
    查询Memtable非常简单就是对应Memtable.partitions.get(Key).( Memtable的整体数据结构可以看 Cassandra源码阅读(2)-写流程解析 )

  3. 按照partition落到对应SSTable所持有key的区间,确定候选的sstable(内部实现是把所有的SSTable所持有key的区间构造一个intervalTree来查找这个key落在哪些SSTable里)。这一步还不能确定这些SSTable一定持有这个partitionKey。

  4. 对3返回的sstable排序,按照maxtimestamp倒排,也就是最新的排前面。逐一遍历这些SSTable,根据下面条件进一步缩小要查询的SSTable

    4.1 如果查询中有static column或者
    4.2 如果查询的clustering key在对应SSTable持有的区间中
    4.3 如果有partitionKey级别的delete,则记下对应的时间。后续遍历SSTable的maxTimeStamp,如果小于这个时间就停止遍历

  5. 根据4选出的SSSTable加上partitionKey构建一个迭代器返回。上层代码通过使用该迭代器来获取数据,对应的数据merge由该迭代器处理.(多个SSTable上的iterator用unfilteredRowMergeIterator把他们包起来,对于单个SSTable内部定位具体的文件position的时候,会用sstable的bloom filter过一下看是否查询的key在里面,如果不在就返回一个NoRowIterator).