titan0.1源码研究(1)

为什么是titan0.1?因为代码量小,后面的代码都是从0.1发展来的,代码量是增加了,但是代码结构基本没变。
titan-0.1有3万多行代码,而且还没有弄成maven的多模块。用来学习正好。等熟悉了再跟后面的版本比对,看看哪些地方更新了。

titan是一个图数据库,没有自己的存储后端,需要安装hbase,cassandra等数据库。我们使用cassandra。
下载cassandra-1.1.3.tar.gz。解压后,配置一个Cassandra_HOME环境变量,然后启动它:

cd $CASSANDRA_HOME\bin
cassandra 

先来看一个小demo吧,稍微熟悉一下titan。
注意titan0.1要想遍历结点的话,必须要建索引:
把源码test模块下的TitanTestBed修改成这样:

Configuration conf = new BaseConfiguration();
conf.setProperty("storage.backend","cassandra");
conf.setProperty("storage.hostname","127.0.0.1");
TitanGraph g = TitanFactory.open(conf);
// 给titan的vertex的name2属性建一个索引。
g.createKeyIndex("name2", Vertex.class);
// 新建一个结点,手动给结点的id赋值
Vertex v = g.addVertex(1);
v.setProperty("name2", "zhangsan");
// 如果上面没有建索引,这句会报错:java.lang.UnsupportedOperationException: The configured storage backend does not support global graph operations - use Faunus instead
// 参考这里: https://groups.google.com/forum/#!topic/aureliusgraphs/jU6-Yyxm9F4
System.out.println(g.getVertices("name2", "zhangsan").iterator().next().getId());
System.out.println(g.getVertex(1).getProperty("name"));
g.stopTransaction(Conclusion.SUCCESS);
g.shutdown();

好,正式开始。代码太多了,从哪儿下手呢?
一看readme.md文件。哦,内容比较少,没有什么干货。
二看包名,有5个大包:core, diskstorage, graphdb, tinkerpop, util。先看core包。
三看接口。core包里一共有15个接口。在eclipse选择类名,按F4会显示这个接口的继承关系及接口里的方法。

titan遵守tinkerpop规范,所以,必定会实现blueprints(新版本叫gremlin structure)里的接口。
在实现blueprints的接口之前,titan自己定义了一些接口。这些接口继承了blueprints的接口。
这样有一个好处,以后扩展的时候只需要修改自己的接口就行了,blueprints里的不用动。

core包的15个接口:
titan0.1源码研究(1)

我们知道图有两个要素,结点和边。
titan是这样抽象的:

最顶层的接口叫TitanElement
titan0.1源码研究(1)

这是所有元素都需要用到的方法。

然后InternalElement和TitanVertex两个接口继承了TitanElement。
InternalElement接口里只有一个方法setID。似乎这个接口并没有什么用。 这个暂时不用管,以后我们再好好研究一下它。因为源码里凡是以Internal开头的接口,代码里都没有注释,所以先不管它。

TitanVertex很明显是操作vertex用到的接口。于是乎我们猜测,代码里一定会有一个操作边的接口叫TitanEdge。
不好意思猜错了。没有一个接口叫TitanEdge的,只有一个TitanRelation。

为什么要这样安排?来看一下TitanVertex的继承关系。
titan0.1源码研究(1)

因为vertex和edge的属性都有键(key),vertex和edge本身都有标签(label)。所以将key和label抽象成TitanType,这样以后不管是结点还是边,都可以用这个父类接口了。

因为vertex不仅有属性(property),同时还有边的信息。将属性和边抽象成TitanRelation。这样以后不管是addProperty还是addEdge,都可以用这个父类接口了。

因为都跟vertex有关。所以TitanType和TitanRelation继承了TitanVertex。虽然乍一看有点怪,不过非常合理。

blueprints的Vertex接口里只有3个方法getEdges,getVertices,query。
TitanVertex:继承了blueprints的Vertex接口。
但是显然方法还是太少了,所以TitanVertex里又添加了许多方法,见上图。

这些方法就够了吗?当然不够。
TitanRelation里增加了getDirection, getType, isDirected, isEdge, isIncidentOn, isLoop, isModifiable, isProperty, isSimple, isUndirected, isUnidirected(对跟左边的方法就一字之差)
TitanEdge里增加了 getOtherVertex, getTitanLabel, getVertex 3个方法。
TitanProperty里增加了getAttribute, getPropertyKey, getVertex 3个方法。

TitanType里增加了getGroup, getName, isEdgeLabel, isFunctional, isModifiable, isPropertyKey, isSimple
TitanKey里增加了 getDataType, hasIndex, isUnique 3个方法。
TitanLabel增加了 isDirected, isUndirected, isUnidirected 3个方法。

不要觉得烦,这就是软件设计里的接口隔离原则。弄成一个个小的接口,一点一点的增加方法。

以Titan开头的8个接口就定出了titan基本元素的框架。core包里还有7个接口,分别是:
AttributeSerializer,DefaultTypeMaker,TitanGraph,TitanQuery,TitanTransaction,TypeMaker,VertexList

TitanQuery:毫无疑问,继承了blueprints的Query接口。blueprints Query接口只有9个方法。
count, direction, edges, has, interval, labels, limit, vertexIds, vertices。
TitanQuery增加了很多方法:
clone, count, directon, edges, group, has, inMemory, interval, keys, labels, limit, onlyModifiable, properties, propertyCount, relations, titanEdges, types, vertexIds.
这里虽然有些方法在父类接口里已经有了,不过没关系。 java接口里的方法都是抽象的。实现类只要实现了抽象方法就行。反正方法签名都是一样的,虽然父类接口和子类接口都有声明,编译器只会加载一次,所以不会有问题。

TitanTransaction:继承了blueprints的TransactionalGraph和KeyIndexableGraph两个接口。
TransactionalGraph接口里只有 shutdown, stopTransaction 2个方法。
KeyIndexableGraph 接口里只有 createKeyIndex, dropKeyIndex, getIndexedKeys 3个方法。
TitanTransaction 增加了很多方法:
abort, addEdge, addProperty, addVertex, commit, containsType, containsVertex, getEdgeLabel, getPropertyKey
getType, getVertex, getVertices, hasModifications, isClosed, isOpen, makeType, query

咦很多方法前面出现过了对不对? 比如addProperty, addEdge, getVertices。 是的,这些方法有一个要求,都要传入vertex对象,所以最后调用的还是vertex的方法,而不会是tx的。

VertexList:继承了java里的Iterable接口,遍历的时候要用到这个接口。有5个方法: get, getID, getIDs, size, sort
AttributeSerializer:为attribute的值序列化用的,允许用户自定义序列化器。只有read, writeObjectData两个方法。

TypeMaker:TitanType的工厂。TitanType可以被配置用来提高数据校验,更好的存储效率和更高的性能。 TitanType给某个类型所有的TitanRelation定义schema。
有这些方法: dataType, directed, functional, group, indexed, makeEdgeLabel, makePropertyKey, name, primaryKey, signature, simple,
undirected, unidirected, unique
用户可以用com.thinkaurelius.titan.core.TitanTransaction#makeType()自定义一个类型。

DefaultTypeMaker:当graph被配置成 边label和属性的key第一次被使用时自动创建边label和属性的key。分别使用DefaultTypeMaker实现类的makeLabel(String, TypeMaker)
或者makeKey(String, TypeMaker)来定义他们。 也可以自定义一个DefaultTypeMaker的实现类,该类指明了默认情况下这些类型是如何被定义的。
在配置里使用全路径指明实现类。

TitanGraph:继承了blueprints的Graph, KeyIndexableGraph, ThreadedTransactionalGraph 3个接口。 哦,人家blueprints也是接口隔离的。
Graph:addEdge, addVertex, getEdge, getEdges, getFeatures, getVertex, getVertices, removeEdge, removeVertex, shutdown。

KeyIndexableGraph:createKeyIndex, dropKeyIndex, getIndexedKeys

ThreadedTransactionalGraph: startTransaction

嗯,TitanGraph把vertex, edge, tx, serializer都封装起来了。我们大概知道titan要做哪些事了:

打开数据库,开启事务,构造vertex和edge,将数据序列化一下,然后将序列化后的数据提交到存储后端(hbase, cassandra等)。

这里最难的是事务。titan没有自己的存储后端,它是一个图数据层。
BigTable是很好,可惜就是没有原生地提供事务支持,所以titan自己实现了一个分布式事务。

本次收获:面向接口编程,接口隔离,接口继承。

titan源码有太多太多值得我们学习的地方:
设计模式,线程安全,maven多模块,maven各种插件的使用,单元测试,持续集成等。把这个项目研究好了,Java水平一定会有质的提升。

上一篇:【前端框架之Bootstrap01】我们一起来看看这个家伙是什么


下一篇:Dgraph0.9支持事务啦