1. 程式人生 > >janus原始碼分析5-複雜操作分析

janus原始碼分析5-複雜操作分析

原始碼分析

查詢操作

之前已經遇到過很多查詢操作

mgmt.containsVertexLabel(vType.toString())
    transaction.containsVertexLabel(name);
        return getSchemaVertex(JanusGraphSchemaCategory.VERTEXLABEL.getSchemaName(name))!=null;
        1. JanusGraphSchemaCategory.VERTEXLABEL.getSchemaName(name) // 這一步就是在 name 前面加上標識,例如 vl rt
2. JanusGraphSchemaVertex getSchemaVertex(String schemaName) graph.getSchemaCache().getSchemaId(schemaName) 1. getSchemaCache 2. StandardSchemaCache.getSchemaId id = retriever.retrieveSchemaByName(schemaName); // 這個 retriever 是 StandardJanusGraph 中的變數 typeCacheRetrieval ,
typeCacheRetrieval.retrieveSchemaByName StandardJanusGraph.this.newTransaction QueryUtil.getVertices(consistentTx, BaseKey.SchemaName, typeName) return v!=null?v.longId():null;
iterator
    return
new ResultSetIterator(getUnfoldedIterator(),(query.hasLimit()) ? query.getLimit() : Query.NO_LIMIT); 1. QueryProcessor (org.janusgraph.graphdb.query).getUnfoldedIterator:107, Iterator<R> subiter = new LimitAdjustingIterator(subq); 2. this.next = nextInternal(); hasNext:68, LimitAdjustingIterator (org.janusgraph.graphdb.query) getNewIterator:209, QueryProcessor$LimitAdjustingIterator (org.janusgraph.graphdb.query) execute:1150, StandardJanusGraphTx$elementProcessorImpl (org.janusgraph.graphdb.transaction) new SubqueryIterator indexCache.getIfPresent(subQuery); // 這裡的 schema 應該都是在啟動的時候 cache 到了記憶體中,所以直接得到了,如果是 資料,應該要查詢

其實查詢操作並不複雜,這是有很多層的巢狀,導致我們看起來很麻煩而已,前面我們已經大概介紹了: 首先是 AbstractIterator 和 Iterators 類,然後是 ResultSetIterator LimitAdjustingIterator SubqueryIterator ,然後還有一個 Stream 類。

我們一層一層進行檢視:

Query

繼承體系:

Query (org.janusgraph.graphdb.query)
	ElementQuery (org.janusgraph.graphdb.query)
	    GraphCentricQuery (org.janusgraph.graphdb.query.graph)
	    VertexCentricQuery (org.janusgraph.graphdb.query.vertex)
	BaseQuery (org.janusgraph.graphdb.query)
	    MultiKeySliceQuery (org.janusgraph.graphdb.query.graph)
	    JointIndexQuery (org.janusgraph.graphdb.query.graph)
	    RawQuery (org.janusgraph.diskstorage.indexing)
	    BaseVertexCentricQuery (org.janusgraph.graphdb.query.vertex)
	        VertexCentricQuery (org.janusgraph.graphdb.query.vertex)
	    SliceQuery (org.janusgraph.diskstorage.keycolumnvalue)
	        KeyRangeQuery (org.janusgraph.diskstorage.keycolumnvalue)
	        KeySliceQuery (org.janusgraph.diskstorage.keycolumnvalue)
	    KVQuery (org.janusgraph.diskstorage.keycolumnvalue.keyvalue)
	    IndexQuery (org.janusgraph.diskstorage.indexing)
	    IndexQueryBuilder (org.janusgraph.graphdb.query.graph)
	    GraphCentricQuery (org.janusgraph.graphdb.query.graph)
	BackendQuery (org.janusgraph.graphdb.query)
	    MultiKeySliceQuery (org.janusgraph.graphdb.query.graph)
	    JointIndexQuery (org.janusgraph.graphdb.query.graph)
	    SliceQuery (org.janusgraph.diskstorage.keycolumnvalue)
	        KeyRangeQuery (org.janusgraph.diskstorage.keycolumnvalue)
	        KeySliceQuery (org.janusgraph.diskstorage.keycolumnvalue)
	    IndexQuery (org.janusgraph.diskstorage.indexing)
	    Subquery in JointIndexQuery (org.janusgraph.graphdb.query.graph)

我們主要能發現 BaseQuery 和 BackendQuery 兩大子類,

BaseQuery 比較簡單,裡面就一個 limit 屬性,應該是指返回的條數。而 BackendQuery 介面更簡單,只有一個方法 updateLimit(int newLimit),返回一個新的 BackendQuery。至於有什麼用後續才能知道。

基於 BaseQuery 和 BackendQuery ,有很多子類。

SliceQuery 有兩個 StaticBuffer 型別的屬性: sliceStart 和 sliceEnd 。這應該是 bigtable 模型的 nosql 都會提供的一種功能,給一個 slice 進行查詢。

KeySliceQuery 繼承自 SliceQuery ,擴充套件 SliceQuery ,增加了 StaticBuffer 型別的 key,能夠查詢某個 key 的 slice。

KeyRangeQuery 繼承自 SliceQuery ,擴充套件 SliceQuery ,增加了兩個 StaticBuffer 型別的 keyStart keyEnd 。為何這樣就要查詢 bigtable 相關資料了。

MultiKeySliceQuery 繼承自 BaseQuery 和 BackendQuery ,內部有一個 List queries。很明顯這是多個 key 一起查。

IndexQuery 官方註釋 在 IndexProvider 中執行的外部 query,query 由兩部分組成:一個是查詢應該執行的 store 的識別符號,另一個是查詢的條件。 IndexProvider 的程式碼我們介紹過,是指外部索引,例如 ElasticSearchIndex ,主要有 register mutate restore query 等方法,很明顯是提供一些查詢。

JointIndexQuery 的靜態內部類 Subquery 繼承自 BackendQuery ,內部有兩個主要屬性: IndexType index; BackendQuery query; Index 可以是 MixedIndexType 或者 CompositeIndexType,對應的 query 分別是 IndexQuery 和 MultiKeySliceQuery JointIndexQuery 則有 List queries 屬性代表很多個 Subquery。 我們可以看出其實 Subquery 代表的是可以在一種索引平臺上執行的查詢。而 JointIndexQuery 則是很多個這樣的查詢,可以在各自的平臺上進行查詢。

GraphCentricQuery 包含了一個 Condition condition 作為條件,一個 BackendQueryHolder indexQuery 儲存 Query 資訊。 BaseVertexCentricQuery 包含了 Condition condition 作為新增 ,List<BackendQueryHolder> queries 儲存 Query 資訊 VertexCentricQuery 繼承自 BaseVertexCentricQuery ,新增一個 InternalVertex vertex ,至於幹啥的還不知道。 他們都是 ElementQuery。

看到這裡我們大概能看出 : GraphCentricQuery 是基於 JanusGraphElement 的,查詢需要 JointIndexQuery , JointIndexQuery 內部則是 Subquery,Subquery 主要分為 MixedQuery 和 CompositeQuery,對應的查詢分別為 IndexQuery 和 MultiKeySliceQuery,對應的索引分別為 MixedIndexType 和 CompositeIndexType VertexCentricQuery 是基於 JanusGraphRelation 的,查詢需要 SliceQuery ,SliceQuery 就是查詢 key + cf 對應的所有的 keyvalue 。

RawQuery 繼承自 BaseQuery ,沒什麼特殊引數,我想應該是值一些粗糙的直接查詢。 剩下的 IndexQueryBuilder 和 KVQuery 先不說了。

我們可以看出這些 Query 只是一些描述性的東西,並沒有任何執行呼叫的方法。通過類的關係我們也大概能總結一下:

  • 一切都是為了查出 janus 中的元素,所以 是圍繞 ElementQuery 展開,ElementQuery 有兩個子類,GraphCentricQuery 和 VertexCentricQuery。 GraphCentricQuery 代表以 graph 為中心的查詢,例如查詢 name=aaa 的所有頂點,VertexCentricQuery 代表以 vertex-centric 的查詢,例如查和某個人關係為同事的所有人。 為了完成 GraphCentricQuery 包括兩類:IndexQuery 和 MultiKeySliceQuery ,IndexQuery 代表使用外部索引的查詢,MultiKeySliceQuery 代表使用 bigtable 自帶索引的查詢。 這兩種合在一起就是 Subquery ,而 JointIndexQuery 內部有多個 Subquery,GraphCentricQuery 中有一個 JointIndexQuery 物件。 為了完成 VertexCentricQuery,也就是加快基於 PropertyKey 和 EdgeLabel 的查詢,需要使用 SliceQuery 進行配合。SliceQuery 有很多實現,除了本身還有 KeySliceQuery 和 KeyRangeQuery。
  • 而 RawQuery 看名字猜測是直接查詢。
  • IndexQueryBuilder 就是一個 Builder,內部有一個 IndexSerializer ,它的 execute 方法,實際上就是呼叫 IndexSerializer 的 executeQuery。

IndexSerializer

從 JointIndexQuery 我們能看出,SubQuery 是在 IndexSerializer 中執行的,我們大概瞭解一下 IndexSerializer。

內部有一個 Map<String, ? extends IndexInformation> mixedIndexes,IndexInformation 有很多子類,例如 ElasticSearchIndex, 還有很多內部類 IndexInfoRetriever IndexRecords IndexUpdate RecordEntry。這應該是一直設計模式吧。 而它的 executeQuery 方法,最終會呼叫 backendTx.rawQuery(index.getBackingIndexName(), rawQuery) 方法。這裡有點奇怪的是為什麼只有 MixedIndexType

另外 query 方法 有兩種情況,如果是 isCompositeIndex ,會得到 MultiKeySliceQuery 並呼叫 sq.execute(tx),如果是 MixedQuery ,呼叫 tx.indexQuery。 然後都是呼叫 BackendTransaction 的 indexQuery,CompositeIndex 對應的是 indexQuery(final KeySliceQuery query),MixedIndex 是 indexQuery(final String index, final IndexQuery query)。 這兩個方法將會分別跳轉到 KeyColumnValueStore.getSlice(KeySliceQuery query, StoreTransaction txh) 和 IndexProvider.query(IndexQuery query, KeyInformation.IndexRetriever information, BaseTransaction tx)

Stream

Stream 是 java 自帶的類,目的是實現 lambda 程式設計,如 map filter reduce 等。java.util.list 呼叫 stream() 方法就返回一個 Stream 物件。Stream 的部分方法: peek(Consumer) 方法主要用來除錯。類似 map ,但是它返回原物件。例如:

Stream.of("one", "two", "three", "four")
    .filter(e -> e.length() > 3)
    .peek(e -> System.out.println("Filtered value: " + e)) // 列印
    .map(String::toUpperCase)
    .peek(e -> System.out.println("Mapped value: " + e))
    .collect(Collectors.toList());

limit(long ) 類似 sql 的 limit。 iterator() 返回一個迭代器。

SubqueryIterator

根據名字大概可以判斷 SubqueryIterator 是一個查詢結果迭代器,這裡的 Subquery 就是上面我們介紹的,它的成員變數:

private final JointIndexQuery.Subquery subQuery;
private final Cache<JointIndexQuery.Subquery, List<Object>> indexCache;
private Iterator<? extends JanusGraphElement> elementIterator;
private List<Object> currentIds;
private QueryProfiler profiler;

SubqueryIterator 的構造方法如下:

// 傳入了 subQuery 和 indexSerializer
public SubqueryIterator(JointIndexQuery.Subquery subQuery, IndexSerializer indexSerializer, BackendTransaction tx,
        Cache<JointIndexQuery.Subquery, List<Object>> indexCache, int limit,
        Function<Object, ? extends JanusGraphElement> function, List<Object> otherResults) {
    this.subQuery = subQuery;
    this.indexCache = indexCache;
    // 先從快取裡面取
    final List<Object> cacheResponse = indexCache.getIfPresent(subQuery);
    final Stream<?> stream;
    if (cacheResponse != null) {
        stream = cacheResponse.stream();
    } else {
        try {
            currentIds = new ArrayList<>();
            profiler = QueryProfiler.startProfile(subQuery.getProfiler(), subQuery);
            isTimerRunning = true;
            // 快取沒有就查
            stream = indexSerializer.query(subQuery, tx).peek(r -> currentIds.add(r));
        } catch (final Exception e) {
            throw new JanusGraphException("Could not call index", e.getCause());
        }
    }
    // 生成 elementIterator
    elementIterator = stream.limit(limit).filter(e -> otherResults == null || otherResults.contains(e)).map(function).map(r -> (JanusGraphElement) r).iterator();
}

StandardJanusGraphTx

之前我們已經見到介紹過 StandardJanusGraphTx ,實際上這個代表的就是一個事務,內部有很多操作圖的方法,我們這次主要是看看他的 elementProcessorImpl 和 edgeProcessorImpl。 他的定義:QueryExecutor<GraphCentricQuery, JanusGraphElement, JointIndexQuery> elementProcessorImpl , QueryExecutor<VertexCentricQuery, JanusGraphRelation, SliceQuery> edgeProcessorImpl 聽名字就知道大概是執行查詢的?這是一個匿名內部類,繼承自 QueryExecutor,主要方法是 execute。

elementProcessorImpl

我們只看 execute 方法,如果 indexQuery.isEmpty() 會告訴你 “Query requires iterating over all vertices [{}]. For better performance, use indexes”。說明我們上面說 RawQuery 是直接查詢不利用索引是錯誤判斷。 最後返回了一個 new SubqueryIterator(indexQuery.getQuery(0), indexSerializer, txHandle, indexCache, indexQuery.getLimit(), getConversionFunction(query.getResultType()),retrievals.isEmpty() ? null: QueryUtil.processIntersectingRetrievals(retrievals, indexQuery.getLimit()));

這裡 SubQueryIterator 就是上面講的。

edgeProcessorImpl

他的 execute 方法:


final InternalVertex v = query.getVertex();
final EntryList iterable = v.loadRelations(sq, query1 -> QueryProfiler.profile(profiler, query1, q -> graph.edgeQuery(v.longId(), q, txHandle)));
return RelationConstructor.readRelation(v, iterable, StandardJanusGraphTx.this).iterator();

最終會呼叫 BackendTransation 的 edgeStoreQuery(final KeySliceQuery query)。

LimitAdjustingIterator

QueryProcessor$LimitAdjustingIterator

QueryProcessor 主要有兩個屬性:

private final Q query;
private final QueryExecutor<Q, R, B> executor;

這裡的 query 就是上面講的 query ,一般是 GraphCentricQuery 或者 VertexCentricQuery,executor 就是我們上面講的 edgeProcessorImpl 和 elementProcessorImpl。 它的 iterator 方法返回一個 ResultSetIterator。 LimitAdjustingIterator 初始化的時候會呼叫 getNewIterator ,這時候執行 executor.execute(query, backendQuery, executionInfo, profiler)。

和它類似的還有 PreSortingIterator ,加了一個排序 。

ResultSetIterator

ResultSetIterator 只是類似 guava 的一個封裝,通過 nextInternal 方法實現 iterator 提前載入。

VertexCentricQueryBuilder 和 GraphCentricQueryBuilder

GraphCentricQueryBuilder 是用來構造一個 Query 的。它的很多方法都和 gremin 對接,最重要的方法還是 constructQuery ,用來構造上面我們講解的 Query。

BasicVertexCentricQueryBuilder 是 VertexCentricQueryBuilder 的父類,StandardJanusGraphTx 的 query(JanusGraphVertex vertex) 會產生一個 VertexCentricQueryBuilder。

總結

到這裡我們基本搞清楚了整個查詢過程。 首先我們的程式碼的查詢會生成 GraphCentricQueryBuilder 或者 BasicVertexCentricQueryBuilder, 然後 我們呼叫 builder 的查詢時會生成 GraphCentricQuery 或者 VertexCentricQuery,並 new QueryProcessor<>(query, tx.elementProcessor)。

QueryProcessor 的 iterator 方法生成一個 ResultSetIterator 封裝的 LimitAdjustingIterator , LimitAdjustingIterator 的 getNewIterator 會呼叫 QueryExecutor 的 execute 方法,生成 SubqueryIterator 或者 graph.edgeQuery(v.longId(), q, txHandle) 最終呼叫 edgeStore 的查詢 SubqueryIterator 構造方法會呼叫 indexSerializer.query(subQuery, tx),最終呼叫 edgeStore 或者 IndexProvider 的查詢。

以上使我們檢視原始碼的心得,要想深入瞭解還需要進一步 debug 程式碼。

更新索引

Index

Index 類繼承自 JanusGraphSchemaElement ,後者我們已經講過代表 schema 的元素,它的子類如 PropertyKeyVertex 代表 schema 的一部分。 Index 有兩個子類 JanusGraphIndex 和 RelationTypeIndex ,分別代表 Graph index 和 基於 Relation 的 Index ,實現類分別是 :JanusGraphIndexWrapper 和 RelationTypeIndexWrapper。

JanusGraphIndexWrapper 包括了 composite indexes 和 mixed indexes。可以通過 JanusGraphManagement#buildIndex(String, Class) 構造, 通過 JanusGraphManagement#getGraphIndex(String) 或者 JanusGraphManagement#getGraphIndexes(Class) 獲得。注意方法包括:

getBackingIndex
getFieldKeys
getIndexedElement
getIndexStatus
getParametersFor
isCompositeIndex
isMixedIndex
isUnique
name

RelationTypeIndex 包括 EdgeIndex 和 PropertyKeyIndex ,通過 JanusGraphManagement#buildEdgeIndex(org.janusgraph.core.EdgeLabel …)和 JanusGraphManagement#buildPropertyIndex(org.janusgraph.core.PropertyKey…) 構造, 通過JanusGraphManagement#getRelationIndex(org.janusgraph.core.RelationType, String) 獲得。主要方法包括:

getDirection
getIndexStatus
getSortKey
getSortOrder
getType

IndexType InternalRelationType

JanusGraphIndex 和 RelationTypeIndex 中分別有一個 IndexType 和 InternalRelationType 的屬性。

IndexType 又有 CompositeIndexType 和 MixedIndexTypeWrapper 兩大子類, CompositeIndexType 還有一個子類是 BaseKey 的索引, 也就是 schema 預設有的索引。

CompositeIndexTypeWrapper 和 MixedIndexTypeWrapper 的構造方法需要傳入一個 SchemaSource 物件,也就是 JanusGraphSchemaVertex 的物件。

IndexBuilder

IndexBuilder 是 JanusGraphManagement 內部介面,顧名思義是用來構建索引的,建造者模式。裡面封裝了索引的屬性,例如: addKey indexOnly unique 等。

實現類在 ManagementSystem 中,實現類 主要屬性:

private final String indexName;
private final ElementCategory elementCategory;
private boolean unique = false;
private JanusGraphSchemaType constraint = null;
private final Map<PropertyKey, Parameter[]> keys = new HashMap<>();

主要方法還是 createCompositeIndex 和 buildMixedIndex 。都會呼叫宿主類的方法。 實際上建立索引過程就是建立一個 INDEX 型別的 SchemaVertex ,然後建立到 對應的 PropertyKey 的 Edge。

UpdateStatusTrigger

根據名字判斷是更新 status 的觸發器。它的屬性:

private final StandardJanusGraph graph;
private final long schemaVertexId;
private final SchemaStatus newStatus;
private final Set<Long> propertyKeys;

構造方法:

private UpdateStatusTrigger(StandardJanusGraph graph, JanusGraphSchemaVertex vertex, SchemaStatus newStatus, Iterable<PropertyKeyVertex> keys) {
    this.graph = graph;
    this.schemaVertexId = vertex.longId();
    this.newStatus = newStatus;
    this.propertyKeys = Sets.newHashSet(Iterables.transform(keys, new Function<PropertyKey, Long>() {
        @Nullable
        @Override
        public Long apply(@Nullable PropertyKey propertyKey) {
            return propertyKey.longId();
        }
    }));
}

call 方法主要就是:

management.setStatus(schemaVertex, newStatus, keys);
management.updatedTypes.addAll(keys);
management.updatedTypes.add(schemaVertex);
management.commit();

它被使用的地方是在 updateIndex 的時候,有一步: setUpdateTrigger(new UpdateStatusTrigger(graph, schemaVertex, SchemaStatus.REGISTERED, keySubset)) 這裡的 set 方法只是將它 add 到了一個 List 中,而在呼叫 commit 的時候,會有個判斷,然後呼叫 mgmtLogger.sendCacheEviction(updatedTypes, updatedTypeTriggers, getOpenInstancesInternal()); 這裡 ManagementLogger 實際上又呼叫 evictionTriggerMap.put(evictionId,new EvictionTrigger(evictionId,updatedTypeTriggers,openInstances)) 將它封裝為 EvictionTrigger 放進一個 map 中。

這要從新建 StandardJanusGraph 開始說起,在它的構造方法有一句:mgmtLog.registerReader(ReadMarker.fromNow(), mgmtLogger); 然後呼叫 KCVSLog 的 registerReader 方法,然後呼叫 msgPullers[pos]=new MessagePuller(partitionId,bucketId); 新建 MessagePuller 後,呼叫 readExecutor.scheduleWithFixedDelay 放進執行緒池 MessagePuller 的 run 方法會呼叫 prepareMessageProcessing ,然後呼叫 readExecutor.submit(new ProcessMessageJob(message,reader)) 放進執行緒池。 ProcessMessageJob 的 run 方法呼叫 ManagementLogger 的 read 方法, 然後會呼叫 EvictionTrigger evictTrigger = evictionTriggerMap.get(evictionId),這裡就取出了我們上面放進去的 evictTrigger, 呼叫 receivedAcknowledgement 方法,會呼叫 trigger.call() 方法,然後會 setStatus。

我們稍微總結一下。 StandardJanusGraph 的構造方法實際上會 new 一個 KCVSLog managementLog 和一個 new ManagementLogger managementLogger,前者是日誌,後者是 management 的日誌。 然後呼叫 managementLog.registerReader(ReadMarker.fromNow(), managementLogger),這個 managementLogger 實現了 MessageReader 介面, 也就是將 managementLogger 註冊到 KCVSLog 上。 註冊以後,會通過一個 ScheduledThreadPoolExecutor 定時排程,將 KCVSLog 按照分割槽分桶拆分成多個快,傳送到 KCVSLog 的訊息都會發送給 ManagementLogger。 ManagementLogger 呼叫 read 方法,判斷 MgmtLogType,根據不同的型別,做出不同的響應。當收到 CACHED_TYPE_EVICTION_ACK 型別的訊息,將會得到 evictTrigger,並且呼叫 call 方法。

StandardScanner

看名字是一個掃描器。內部有 KeyColumnValueStoreManager manager 和 Set openStores ,應該是構造的時候傳進來的,來自 graph。 我們比較關心的是他的內部類: Builder ,內部有 ScanJob job,job 有 process 方法,而 Builder 則有 execute 方法,executor 會 new 一個 StandardScannerExecutor, StandardScannerExecutor executor = new StandardScannerExecutor(job, finishJob, kcvs, storeTx,manager.getFeatures(), numProcessingThreads, workBlockSize, jobConfiguration, graphConfiguration); executor 是繼承自 Runnable 的,然後呼叫它的 start 方法啟動這個執行緒。executor 的 run 方法就是關鍵, StandardScannerExecutor 的 run 方法會 new Processor(job.clone(),processorQueue),Processor 也是 Runnable ,然後呼叫 start ,Processor 的 run 中呼叫了 job 的 process。

這個 job 的 process 方法就是重點。例如 SimpleScanJob 的 process 方法,就是掃描一遍資料庫。

StandardScanner 的使用主要是在 updateIndex 的時候,有一步: builder.setJob(VertexJobConverter.convert(graph, new IndexRepairJob(indexId.indexName, indexId.relationTypeName))); 這裡會設定 job,然後呼叫 builder.execute(), 裡面會 new StandardScannerExecutor,這是一個 Runnable,然後 start。 它的 run 方法會 new Processor(job.clone(),processorQueue) ,這是一個 Runnable ,然後 start。 然後呼叫 job.process(row.key,row.entries,metrics)。 例如 IndexRepairJob 的 process 方法,會呼叫 BackendTransaction.mutateIndex 或者 restore 方法,和 IndexSerializer.reindexElement 方法,其實就是重新索引。

想要了解可以在 CassandraScanJobIT 中進行簡單測試。

我們可以看出其實 StandardScanner 和 UpdateStatusTrigger 完成工作類似,都是通過執行緒呼叫執行緒,完成所以更新,只不過前者比較簡單,後者操作複雜一點。

ManagementSystem

有關索引的操作也是在 ManagementSystem 中完成,最重要的就是 updateIndex 方法,

reindex

mgmt.updateIndex(mgmt.getGraphIndex(indexName), SchemaAction.REINDEX).get();

我們發現這個步驟特別久,就算沒有資料也要很久,這不科學。而且打斷點也進不去,我們只能直接拍快照,通過分析某個時刻的快照,分析有沒有執行緒死鎖的情況。

我們每次在程式執行的時候拍快照都會有兩個執行緒:

"[email protected]" prio=5 tid=0x51 nid=NA waiting
  java.lang.Thread.State: WAITING
	  at sun.misc.Unsafe.park(Unsafe.java:-1)
	  at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
	  at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078)
	  at java.util.concurrent.LinkedBlockingQueue.poll(LinkedBlockingQueue.java:467)
	  at org.janusgraph.diskstorage.keycolumnvalue.scan.StandardScannerExecutor.run(StandardScannerExecutor.java:148)
	  at java.lang.Thread.run(Thread.java:745)

"[email protected]" prio=5 tid=0x55 nid=NA waiting
  java.lang.Thread.State: WAITING
	  at sun.misc.Unsafe.park(Unsafe.java:-1)
	  at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
	  at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078)
	  at java.util.concurrent.LinkedBlockingQueue.poll(LinkedBlockingQueue.java:467)
	  at org.janusgraph.diskstorage.keycolumnvalue.scan.StandardScannerExecutor$Processor.run(StandardScannerExecutor.java:272)

偶爾還能發現一個:

"[email protected]" daemon prio=5 tid=0x18 nid=NA sleeping
  java.lang.Thread.State: TIMED_WAITING
	  at java.lang.Thread.sleep(Thread.java:-1)
	  at java.lang.Thread.sleep(Thread.java:340)
	  at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
	  at org.janusgraph.diskstorage.util.time.TimestampProviders.sleepPast(TimestampProviders.java:152)
	  at org.janusgraph.graphdb.database.management.ManagementLogger$SendAckOnTxClose.run(ManagementLogger.java:208)
	  at java.lang.Thread.run(Thread.java:745)

前兩個是常在的執行緒,在 index 的過程中幾乎一致都在,後面那個是偶爾會有出現。

中間還報:

2018-06-30 14:16:35.282 ERROR   --- [      Thread-66] o.j.g.d.management.ManagementLogger      : 
Evicted [23@c0a8007113617-dengzimings-MacBook-Pro-local1] from cache but waiting too long for transactions to close. 
Stale transaction alert on: [standardjanusgraphtx[0x0fd51357], standardjanusgraphtx[0x42d0f747], 
standardjanusgraphtx[0x54168b3c], standardjanusgraphtx[0x27eff5b4], standardjanusgraphtx[0x20cfedd2], 
standardjanusgraphtx[0x7bd7769a], standardjanusgraphtx[0x1095d23a]]

這三個可以給我們提供比較多的資訊。前面兩個可能是由於 poll 的引數等待時間是 100 ms 比較長,所以每次拍快照很大概率剛好在等待。

debug