hugegraph 是百度开源的图数据库,支持hbase,mysql,rocksdb等作为存储后端。本文以EDGE 存储,hbase为存储后端,来探索是如何hugegraph是如何存取数据的。

存数据

序列化

首先需要序列化,hbase 使用BinarySerializer:

  • keyWithIdPrefix 和indexWithIdPrefix都是false

这个后面会用到。

public class HbaseSerializer extends BinarySerializer {

    public HbaseSerializer() {
super(false, true);
}
}

要存到db,首先需要序列化为BackendEntry,BackendEntry 是图数据库和后端存储的传输对象,Hbase对应的是BinaryBackendEntry:

public class BinaryBackendEntry implements BackendEntry {

    private static final byte[] EMPTY_BYTES = new byte[]{};

    private final HugeType type;
private final BinaryId id;
private Id subId;
private final List<BackendColumn> columns;
private long ttl; public BinaryBackendEntry(HugeType type, byte[] bytes) {
this(type, BytesBuffer.wrap(bytes).parseId(type));
} public BinaryBackendEntry(HugeType type, BinaryId id) {
this.type = type;
this.id = id;
this.subId = null;
this.columns = new ArrayList<>();
this.ttl = 0L;
}

我们来看序列化,序列化,其实就是要将数据放到entry的column列里。

  • hbasekeyWithIdPrefix是false,因此name不包含ownerVertexId(参考下面的EdgeId,去掉ownerVertexId)
 public BackendEntry writeEdge(HugeEdge edge) {
BinaryBackendEntry entry = newBackendEntry(edge);
byte[] name = this.keyWithIdPrefix ?
this.formatEdgeName(edge) : EMPTY_BYTES;
byte[] value = this.formatEdgeValue(edge);
entry.column(name, value); if (edge.hasTtl()) {
entry.ttl(edge.ttl());
} return entry;
}

EdgeId:

    private final Id ownerVertexId;
private final Directions direction;
private final Id edgeLabelId;
private final String sortValues;
private final Id otherVertexId; private final boolean directed;
private String cache;

backend 存储

生成BackendEntry后,通过store机制,交给后端的backend存储。

EDGE的保存,对应HbaseTables.Edge:

public static class Edge extends HbaseTable {

        @Override
public void insert(Session session, BackendEntry entry) {
long ttl = entry.ttl();
if (ttl == 0L) {
session.put(this.table(), CF, entry.id().asBytes(),
entry.columns());
} else {
session.put(this.table(), CF, entry.id().asBytes(),
entry.columns(), ttl);
}
}
}

CF 是固定的f:

    protected static final byte[] CF = "f".getBytes();

session.put 对应:

 @Override
public void put(String table, byte[] family, byte[] rowkey,
Collection<BackendColumn> columns) {
Put put = new Put(rowkey);
for (BackendColumn column : columns) {
put.addColumn(family, column.name, column.value);
}
this.batch(table, put);
}

可以看出,存储时,edgeid作为rowkey,然后把去除ownerVertexId后的edgeid作为column.name

EDGE 读取

从backend读取BackendEntry

读取就是从hbase读取result,转换为BinaryBackendEntry,再转成Edge。

读取,是scan的过程:

 /**
* Inner scan: send scan request to HBase and get iterator
*/
@Override
public RowIterator scan(String table, Scan scan) {
assert !this.hasChanges(); try (Table htable = table(table)) {
return new RowIterator(htable.getScanner(scan));
} catch (IOException e) {
throw new BackendException(e);
}
}

scan后,返回BackendEntryIterator

protected BackendEntryIterator newEntryIterator(Query query,
RowIterator rows) {
return new BinaryEntryIterator<>(rows, query, (entry, row) -> {
E.checkState(!row.isEmpty(), "Can't parse empty HBase result");
byte[] id = row.getRow();
if (entry == null || !Bytes.prefixWith(id, entry.id().asBytes())) {
HugeType type = query.resultType();
// NOTE: only support BinaryBackendEntry currently
entry = new BinaryBackendEntry(type, id);
}
try {
this.parseRowColumns(row, entry, query);
} catch (IOException e) {
throw new BackendException("Failed to read HBase columns", e);
}
return entry;
});
}

注意,new BinaryBackendEntry(type, id) 时,BinaryBackendEntry的id并不是rowkey,而是对rowkey做了处理:

public BinaryId parseId(HugeType type) {
if (type.isIndex()) {
return this.readIndexId(type);
}
// Parse id from bytes
int start = this.buffer.position();
/*
* Since edge id in edges table doesn't prefix with leading 0x7e,
* so readId() will return the source vertex id instead of edge id,
* can't call: type.isEdge() ? this.readEdgeId() : this.readId();
*/
Id id = this.readId();
int end = this.buffer.position();
int len = end - start;
byte[] bytes = new byte[len];
System.arraycopy(this.array(), start, bytes, 0, len);
return new BinaryId(bytes, id);
}

这里是先读取ownervertexId作为Id部分, 然后将剩余的直接放入bytes,组合成BinaryId,和序列化的时候有差别,为什么这么设计呢?原来不管是vertex还是edge,都是当成Vertex来读取的。

protected final BinaryBackendEntry newBackendEntry(HugeEdge edge) {
BinaryId id = new BinaryId(formatEdgeName(edge),
edge.idWithDirection());
return newBackendEntry(edge.type(), id);
} public EdgeId directed(boolean directed) {
return new EdgeId(this.ownerVertexId, this.direction, this.edgeLabelId,
this.sortValues, this.otherVertexId, directed);
}

序列化的时候是EdgeId

BackendEntryIterator迭代器支持对结果进行merge, 上面代码里的!Bytes.prefixWith(id, entry.id().asBytes())) 就是对比是否是同一个ownervertex,如果是同一个,则放到同一个BackendEntry的Columns里。

     public BinaryEntryIterator(BackendIterator<Elem> results, Query query,
BiFunction<BackendEntry, Elem, BackendEntry> m) @Override
protected final boolean fetch() {
assert this.current == null;
if (this.next != null) {
this.current = this.next;
this.next = null;
} while (this.results.hasNext()) {
Elem elem = this.results.next();
BackendEntry merged = this.merger.apply(this.current, elem);
E.checkState(merged != null, "Error when merging entry");
if (this.current == null) {
// The first time to read
this.current = merged;
} else if (merged == this.current) {
// The next entry belongs to the current entry
assert this.current != null;
if (this.sizeOf(this.current) >= INLINE_BATCH_SIZE) {
break;
}
} else {
// New entry
assert this.next == null;
this.next = merged;
break;
} // When limit exceed, stop fetching
if (this.reachLimit(this.fetched() - 1)) {
// Need remove last one because fetched limit + 1 records
this.removeLastRecord();
this.results.close();
break;
}
} return this.current != null;
}

从BackendEntry转换为edge

然后再来看读取数据readVertex,前面说了,就算是edge,其实也是当vertex来读取的:

 @Override
public HugeVertex readVertex(HugeGraph graph, BackendEntry bytesEntry) {
if (bytesEntry == null) {
return null;
}
BinaryBackendEntry entry = this.convertEntry(bytesEntry); // Parse id
Id id = entry.id().origin();
Id vid = id.edge() ? ((EdgeId) id).ownerVertexId() : id;
HugeVertex vertex = new HugeVertex(graph, vid, VertexLabel.NONE); // Parse all properties and edges of a Vertex
for (BackendColumn col : entry.columns()) {
if (entry.type().isEdge()) {
// NOTE: the entry id type is vertex even if entry type is edge
// Parse vertex edges
this.parseColumn(col, vertex);
} else {
assert entry.type().isVertex();
// Parse vertex properties
assert entry.columnsSize() == 1 : entry.columnsSize();
this.parseVertex(col.value, vertex);
}
} return vertex;
}

逻辑:

  • 先读取ownervertexid,生成HugeVertex,这个时候只知道id,不知道vertexlabel,所以设置为VertexLabel.NONE
  • 然后,读取BackendColumn,一个edge,一个Column(name是edgeid去除ownervertexid后的部分,value是边数据)

读取是在parseColumn:

protected void parseColumn(BackendColumn col, HugeVertex vertex) {
BytesBuffer buffer = BytesBuffer.wrap(col.name);
Id id = this.keyWithIdPrefix ? buffer.readId() : vertex.id();
E.checkState(buffer.remaining() > 0, "Missing column type");
byte type = buffer.read();
// Parse property
if (type == HugeType.PROPERTY.code()) {
Id pkeyId = buffer.readId();
this.parseProperty(pkeyId, BytesBuffer.wrap(col.value), vertex);
}
// Parse edge
else if (type == HugeType.EDGE_IN.code() ||
type == HugeType.EDGE_OUT.code()) {
this.parseEdge(col, vertex, vertex.graph());
}
// Parse system property
else if (type == HugeType.SYS_PROPERTY.code()) {
// pass
}
// Invalid entry
else {
E.checkState(false, "Invalid entry(%s) with unknown type(%s): 0x%s",
id, type & 0xff, Bytes.toHex(col.name));
}
}

从``col.name`读取type,如果是edge,则parseEdge:

protected void parseEdge(BackendColumn col, HugeVertex vertex,
HugeGraph graph) {
// owner-vertex + dir + edge-label + sort-values + other-vertex BytesBuffer buffer = BytesBuffer.wrap(col.name);
if (this.keyWithIdPrefix) {
// Consume owner-vertex id
buffer.readId();
}
byte type = buffer.read();
Id labelId = buffer.readId();
String sortValues = buffer.readStringWithEnding();
Id otherVertexId = buffer.readId(); boolean direction = EdgeId.isOutDirectionFromCode(type);
EdgeLabel edgeLabel = graph.edgeLabelOrNone(labelId); // Construct edge
HugeEdge edge = HugeEdge.constructEdge(vertex, direction, edgeLabel,
sortValues, otherVertexId); // Parse edge-id + edge-properties
buffer = BytesBuffer.wrap(col.value); //Id id = buffer.readId(); // Parse edge properties
this.parseProperties(buffer, edge); // Parse edge expired time if needed
if (edge.hasTtl()) {
this.parseExpiredTime(buffer, edge);
}
}

从col.name依次读取出type,labelId,sortValues和otherVertexId:

        byte type = buffer.read();
Id labelId = buffer.readId();
String sortValues = buffer.readStringWithEnding();
Id otherVertexId = buffer.readId();

然后根据labelid找到 EdgeLabel edgeLabel = graph.edgeLabelOrNone(labelId);

创建edge, 解析边属性parseProperties

最后读取Ttl, 处理结果的时候,会过滤过期数据。

hugegraph 数据存取数据解析的更多相关文章

  1. iOS开发系列--数据存取

    概览 在iOS开发中数据存储的方式可以归纳为两类:一类是存储为文件,另一类是存储到数据库.例如前面IOS开发系列-Objective-C之Foundation框架的文章中提到归档.plist文件存储, ...

  2. JavaScript数据存取的性能问题

    JavaScript中四种基本的数据存取位置: 字面量:只代表自身 字符串.数字.布尔值.对象.函数.数组.正则,以及null和undefined    快 本地变量:var定义的    快 数组元素 ...

  3. 高性能JS笔记2——数据存取

    数据存取性能而言: 字面量>本地变量>数组元素>对象成员 一.标识符解析的性能 标识符解析是有代价的,一个标识符的位置越深,它的读写速度也就越慢. 局部变量的读写速度是最快的,全局变 ...

  4. iOS本地数据存取

    应用沙盒 1)每个iOS应用都有自己的应用沙盒(应用沙盒就是文件系统目录),与其他文件系统隔离.应用必须待在自己的沙盒里,其他应用不能访问该沙盒 2)应用沙盒的文件系统目录,如下图所示(假设应用的名称 ...

  5. 【转】iOS开发系列--数据存取

    原文: http://www.cnblogs.com/kenshincui/p/4077833.html#SQLite 概览 在iOS开发中数据存储的方式可以归纳为两类:一类是存储为文件,另一类是存储 ...

  6. iOS基础 - 数据存取

    一.iOS应用数据存储的常用方式 XML属性列表(plist)归档 Preference(偏好设置) NSKeyedArchiver归档 SQLite3 Core Data 二.应用沙盒 每个iOS应 ...

  7. Pandas数据存取

    pd.read_excel('foo.xlsx', 'Sheet1', index_col=None, na_values=['NA']) Pandas数据存取 Pandas可以存取多种介质类型数据, ...

  8. iOS数据存取---iOS-Apple苹果官方文档翻译

    CHENYILONG Blog iOS数据存取---iOS-Apple苹果官方文档翻译 数据存取/*技术博客http://www.cnblogs.com/ChenYilong/ 新浪微博http:// ...

  9. 高性能Js—数据存取

    数据存取 JavaScript中四中基本的数据存取位置 字面量:不存于某个变量内 本地变量:var定义的 数组元素 对象成员 字面量.全局变量读取速度 > 数组项.对象成员 .因为局部变量存在于 ...

随机推荐

  1. 读 <The Lost Horizon> 感

    读它有两个契机.一是小组英语 pre 讲香格里拉,二是高二有个男生课前演讲讲过<消失的地平线>,彼时他一脸陶醉向我们描绘场景和人物.现在我只记得他 PPT 的风景图特别美.他好像去 thu ...

  2. Life is Strange 完结感想

    Life is Strange 是一款剧情游戏.steam上第一章是免费的,也正是因为这个我才开始了解到这个游戏,玩完第一章后觉得还可以,就趁打折花了17块钱买了2-5章.应该是18年6月20号买的, ...

  3. tcp timewait 问题 转载

    TIME WAIT 带来的问题 先引用一个名言: The TIME_WAIT state is our friend and is there to help us (i.e., to let old ...

  4. binary hacks读数笔记(dlopen、dlsym、dlerror、dlclose)

    1.dlopen是一个强大的库函数.该函数将打开一个动态库,并把它装入内存.该函数主要用来加载库中的符号,这些符号在编译的时候是不知道的.比如 Apache Web 服务器利用这个函数在运行过程中加载 ...

  5. 掉电后osdmap丢失无法启动osd的解决方案

    前言 本篇讲述的是一个比较极端的故障的恢复场景,在整个集群全部服务器突然掉电的时候,osd里面的osdmap可能会出现没刷到磁盘上的情况,这个时候osdmap的最新版本为空或者为没有这个文件 还有一种 ...

  6. 记最近一次ceph故障修复

    前言 所谓吃一堑长一智,每次面对问题才是最好的学习机会,在面对问题的时候,尽量是能够自己去解决,或者去尝试能够最接近答案,确实无法解决再去寻求他人帮助,这样成长的会更快一些,在学校读书做题的时候,老师 ...

  7. PHP代码审计入门(SQL注入漏洞挖掘基础)

    SQL注入漏洞 SQL注入经常出现在登陆页面.和获取HTTP头(user-agent/client-ip等).订单处理等地方,因为这几个地方是业务相对复杂的,登陆页面的注入现在来说大多数是发生在HTT ...

  8. tp5 生成随机数

    控制器调用 public function GetRanStr(){ if (request()->isPost()) { //生成6位数随机数 return GetRandStr(6); } ...

  9. 重新认识C++的"cin >>"、"cout <<" 简简单单 - 快快乐乐

    重新认识C++的"cin >>"."cout <<" 简简单单 - 快快乐乐 JERRY_Z. ~ 2020 / 11 / 24 转载请 ...

  10. FL Studio 插件使用技巧——Fruity Reeverb 2(下)

    了解大教堂声场的特点 上节教程中我们说到,混响具有营造空间感的作用.当我们想要在FL Studio软件中用Fruity Reeverb 2 插件有目标地模仿一个特定空间的环境时,我们需要充分了解该空间 ...