反向分析
cassandra 写数据 API
cassandra 的结构类似 bigtable ,数据实际上是多层嵌套的 map,第一个 key 是 rowkey,第二层key 是 columnFamily,第三层key 是 column,第四层(也可以忽略) 是 timestamp,然后是 value。
写数据的 API 如下:
CTConnection conn = null;
try {
conn = pool.borrowObject(keySpaceName);
Cassandra.Client client = conn.getClient();
if (atomicBatch) {
client.atomic_batch_mutate(batch, consistency);
} else {
client.batch_mutate(batch, consistency);
}
} catch (Exception ex) {
throw CassandraThriftKeyColumnValueStore.convertException(ex);
} finally {
pool.returnObjectUnsafe(keySpaceName, conn);
}
这里的 batch 就是一个多层嵌套的map。final Map<ByteBuffer, Map<String, List<org.apache.cassandra.thrift.Mutation>>> batch = new HashMap<>(size);
这里看起来只有两层,第一层的 ByteBuffer 当然是 rowKey,第二层是 String 是 columnFamily。而 List<org.apache.cassandra.thrift.Mutation>
很明显就是添加或者删除的 key:value。
写入 cassandra 的数据格式
上面是写 cassandra 的 API,而最终调用这段代码的位置在 CassandraThriftStoreManager.mutateMany(Map<String, Map<StaticBuffer, KCVMutation>> mutations, StoreTransaction txh)
方法。
我们需要了解的就是 Map<String, Map<StaticBuffer, KCVMutation>> mutations
和 Map<ByteBuffer, Map<String, List<org.apache.cassandra.thrift.Mutation>>> batch
的对应关系。
从代码可以看出:
final Map<ByteBuffer, Map<String, List<org.apache.cassandra.thrift.Mutation>>> batch = new HashMap<>(size);
for (final Map.Entry<String, Map<StaticBuffer, KCVMutation>> keyMutation : mutations.entrySet()) {
// mutations 的 key 是 columnFamily
final String columnFamily = keyMutation.getKey();
for (final Map.Entry<StaticBuffer, KCVMutation> mutEntry : keyMutation.getValue().entrySet()) {
// mutations 的第二层 key 是 rowKey
ByteBuffer keyBB = mutEntry.getKey().asByteBuffer();
// Get or create the single Cassandra Mutation object responsible for this key
// Most mutations only modify the edgeStore and indexStore
final Map<String, List<org.apache.cassandra.thrift.Mutation>> cfmutation
= batch.computeIfAbsent(keyBB, k -> new HashMap<>(3));
final KCVMutation mutation = mutEntry.getValue();
final List<org.apache.cassandra.thrift.Mutation> thriftMutation = new ArrayList<>(mutations.size());
// 省略删除的代码。
if (mutation.hasAdditions()) {
for (final Entry ent : mutation.getAdditions()) {
final ColumnOrSuperColumn columnOrSuperColumn = new ColumnOrSuperColumn();
// mutations 的第三层 key 是 column
final Column column = new Column(ent.getColumnAs(StaticBuffer.BB_FACTORY));
// mutations 的 value 是 value
column.setValue(ent.getValueAs(StaticBuffer.BB_FACTORY));
column.setTimestamp(commitTime.getAdditionTime(times));
final Integer ttl = (Integer) ent.getMetaData().get(EntryMetaData.TTL);
if (null != ttl && ttl > 0) {
column.setTtl(ttl);
}
columnOrSuperColumn.setColumn(column);
org.apache.cassandra.thrift.Mutation m = new org.apache.cassandra.thrift.Mutation();
m.setColumn_or_supercolumn(columnOrSuperColumn);
thriftMutation.add(m);
}
}
cfmutation.put(columnFamily, thriftMutation);
}
}
我们可以看出 mutateMany 方法的参数和写到 cassandra 的结果不是完全一致,主要是 rowkey 和 columnFamily 的位置是反的。
传入 mutateMany 的数据
通过调试可以看出,调用 mutateMany 的地方主要是 CacheTransation.persist
,而调用 persist 的就是 flushInternal 方法。相应代码:
// 成员变量: Map<KCVSCache, Map<StaticBuffer, KCVEntryMutation>> mutations
// 新建Map,这个 map 就是上面 mutateMany 的参数,key 分别是 columnFamily 和 rowKey ,
final Map<String, Map<StaticBuffer, KCVMutation>> subMutations = new HashMap<>(mutations.size());
int numSubMutations = 0;
// 遍历 mutations
for (Map.Entry<KCVSCache,Map<StaticBuffer, KCVEntryMutation>> storeMutations : mutations.entrySet()) {
final Map<StaticBuffer, KCVMutation> sub = new HashMap<>();
// KCVSCache 的 getKey().getName() 就是 columnFamily
subMutations.put(storeMutations.getKey().getName(),sub);
// mutations 的 value
for (Map.Entry<StaticBuffer,KCVEntryMutation> mutationsForKey : storeMutations.getValue().entrySet()) {
if (mutationsForKey.getValue().isEmpty()) continue;
// 将 mutationsForKey 放进去,这个 convert 做了啥没有具体研究,可能只是一个适配。
sub.put(mutationsForKey.getKey(), convert(mutationsForKey.getValue()));
numSubMutations+=mutationsForKey.getValue().getTotalMutations();
if (numSubMutations>= persistChunkSize) {
numSubMutations = persist(subMutations);
sub.clear();
subMutations.put(storeMutations.getKey().getName(),sub);
}
}
}
mutations 的构造
上面我们看出了,其实基本上没复杂处理,接下来我们看看 mutations 数据哪里来的。
对于 mutations 的修改操作,来自于 mutate 方法,代码:
// 传入的是 store(包含了columnFamily) key(rowKey) additions 和 deletions
void mutate(KCVSCache store, StaticBuffer key, List<Entry> additions, List<Entry> deletions) throws BackendException {
Preconditions.checkNotNull(store);
if (additions.isEmpty() && deletions.isEmpty()) return;
// 构造 KCVEntryMutation
KCVEntryMutation m = new KCVEntryMutation(additions, deletions