diff --git a/hugegraph-store/.gitattributes b/hugegraph-store/.gitattributes new file mode 100644 index 000000000..4fd3cf5c2 --- /dev/null +++ b/hugegraph-store/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/hugegraph-store/.gitignore b/hugegraph-store/.gitignore new file mode 100644 index 000000000..901795e22 --- /dev/null +++ b/hugegraph-store/.gitignore @@ -0,0 +1,53 @@ +**/target/ +**/output/ +.DS_Store +**/tmp/ +**/tmp*/ + +*.log +.idea/ +*.iml +**[^storage]/log/ +**/rocksdb-data/ +dist/ +.settings/ +.project +.classpath +.factorypath + +# vscode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + + +# Compiled class file +*.class + +# Log file + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +/logs/ +.flattened-pom.xml diff --git a/hugegraph-store/.mvn/wrapper/MavenWrapperDownloader.java b/hugegraph-store/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 000000000..66c0ffa55 --- /dev/null +++ b/hugegraph-store/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,120 @@ +/* + * Copyright 2007-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.6"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = + "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the + * wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if (mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if (mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if (!outputFile.getParentFile().exists()) { + if (!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } +} diff --git a/hugegraph-store/.mvn/wrapper/maven-wrapper.properties b/hugegraph-store/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 000000000..a0c0a3761 --- /dev/null +++ b/hugegraph-store/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,18 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to You under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/hugegraph-store/LICENSE b/hugegraph-store/LICENSE new file mode 100644 index 000000000..58d78a52c --- /dev/null +++ b/hugegraph-store/LICENSE @@ -0,0 +1,216 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +============================================================================ + APACHE HUGEGRAPH (Incubating) SUBCOMPONENTS: + + The Apache HugeGraph(Incubating) project contains subcomponents with separate copyright + notices and license terms. Your use of the source code for the these + subcomponents is subject to the terms and conditions of the following + licenses. + +======================================================================== +Apache 2.0 licenses +======================================================================== + +The following file are provided under the Apache 2.0 License. + hugegraph-xxxx \ No newline at end of file diff --git a/hugegraph-store/NOTICE b/hugegraph-store/NOTICE new file mode 100644 index 000000000..67f995276 --- /dev/null +++ b/hugegraph-store/NOTICE @@ -0,0 +1,7 @@ +Apache HugeGraph(incubating) +Copyright 2022-2023 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). + +The initial codebase was donated to the ASF by HugeGraph Authors, copyright 2017-2021. \ No newline at end of file diff --git a/hugegraph-store/README-dev.md b/hugegraph-store/README-dev.md new file mode 100644 index 000000000..1bc07cd3e --- /dev/null +++ b/hugegraph-store/README-dev.md @@ -0,0 +1,43 @@ +## 启用无PD模式 + +- hgstore设置yml文件 **app.fake-pd: true** +- hugegraph设置properties文件 **pd.fake=true**,设置**hstore.peers=** 指向hgstore的grpc地址,多个地址逗号分割 + +```` + backend=hstore + serializer=binary + pd.peers=localhost:9000 + pd.fake=true + hstore.peers=127.0.0.1:9081,127.0.0.1:9082,127.0.0.1:9083 + store=hugegraph +```` + +###FakePD集群配置 +如果启用fakePD模式的集群部署,需要在hgstore的yml文件增加fake-pd配置 + ++ **store-list:** store集群列表 ++ **peers-list:** raft集群列表 + +```` + fake-pd: + store-list: 127.0.0.1:9080,127.0.0.1:9081,127.0.0.1:9082 + peers-list: 127.0.0.1:8080,127.0.0.1:8081,127.0.0.1:8082 +```` + +###本地打包 + +项目根目录下执行: + +```` +./mvnw -Dmaven.test.skip=true package +```` + +文件保存在项目根目录下的 dist 文件夹中。 + +###本地单测 + +- 在跑单测之前需要启动一个pd和store服务 +- 所以的单测统一在hg-store-test模块中撰写,client、common、core等与项目的模块一一对应,相应模块下的单测写到对应的文件夹下 +- 保持目录结构与项目模块对应,文件命名必须包含"Test"字样,把编写好的类加到对应的SuiteTest类中,由Suite调度执行 +- 单测可以用编译工具单个执行,也可以到代码的根目录下执行:mvn verify -Pjacoco +- 代码覆盖率查看,需要先执行:mvn verify -Pjacoco 后,在到根目录的target->site->jacoco->index.html \ No newline at end of file diff --git a/hugegraph-store/README.md b/hugegraph-store/README.md index e69de29bb..caf163f76 100644 --- a/hugegraph-store/README.md +++ b/hugegraph-store/README.md @@ -0,0 +1,579 @@ +# HStore存储部署说明 + +##安装etcd + +- 配置etcd时需要确保其他服务器可以访问,注意配置文件中以下修改 + +```` + name: etcd-1 #节点名称 + data-dir: default.etcd/ #节点数据存储目录 + listen-client-urls: http://0.0.0.0:2379 #监听etcd客户发送信息的地址 + advertise-client-urls: http://0.0.0.0:2379 #客户与本节点交互信息所用地址 +```` + +## PD配置 + +- 配置文件在application.yml + +```` + grpc: + port: 9000 #内部grpc通信用端口,如心跳等使用 + max-inbound-message-size: 100MB #grpc最大入参的大小 + server: + port : 9001 #restful端口,如通过http的get方法获取storeNode信息(/v1/stores)等 + etcd: + address: http://localhost:2379 #etcd地址 + timeout: 3 #etcd连接超时时间 + store: + keepAlive-timeout: 60 #store心跳超时时间,超过该时间,认为store临时不可用,转移Leader到其他副本,单位秒 + down-time: 1800 #store下线时间。超过该时间,认为store永久不可用,分配副本到其他机器,单位秒 + partition: + default-total-count: 12 #默认分区总数 + default-shard-count: 3 #默认每个分区副本数 +```` + +## StoreNode配置 + +```` + grpc: + port: 9080 + host: 127.0.0.1 #grpc的服务地址,给HugeServer使用 + netty-server: + max-inbound-message-size: 100MB #grpc最大入参的大小 + server: + port : 9090 #restful端口,如通过http的get方法获取监控统计信息(/metrics)等 + app: + rocks: + config-path: "/hugegraph.properties" #写入Rocksdb的参数配置文件 + raft: + address: 127.0.0.1:8081 #raft通信地址 + data-path: /test/raft/8081 #raft日志写入目录 + timeout: 10 #raft读写超时时间,单位秒 + metrics: false #统计raft监控统计信息,打开可能对性能有影响 + pdserver: + address: localhost:9000 #PD的grpc地址 +```` + +StoreNode在启动后,如果在PD的日志中打印出其注册信息,则代表注册成功,如:Store register, id = **** +address = **** + +## Hugegraph配置 + +- 配置文件在properties文件,如hugegraph.properties + +```` + backend=hstore #后端分布式存储类型,固定为hstore + serializer=binary #序列化器,固定为binary + pd.peers=localhost:9000 #PD的grpc地址 +```` + +## RESTFUL API + +- pd提供了一些restful API可以获取分区、存储节点等一系列信息 + +###图相关 + +#### 获取图的partition、shard信息 + +###### Method & Url + +``` +GET http://localhost:9001/v1/graphs +``` + +###### Response Status + +```json +200 +``` + +###### Response Body + +```json +{ + "status": 0, + "graphs": [ + { + "graphName": "default/hugegraph/g", + "partitionCount": 60, + "shardCount": 3 + }, + { + "graphName": "default/hugegraph/m", + "partitionCount": 60, + "shardCount": 3 + }, + { + "graphName": "default/hugegraph/s", + "partitionCount": 60, + "shardCount": 3 + }, + { + "graphName": "default/system/g", + "partitionCount": 60, + "shardCount": 3 + }, + { + "graphName": "default/system/m", + "partitionCount": 60, + "shardCount": 3 + }, + { + "graphName": "default/system/s", + "partitionCount": 60, + "shardCount": 3 + } + ] +} +``` + +#### 获取指定图的partition、shard信息 + +###### Method & Url + +``` +GET http://localhost:9001/v1/graph/default/hugegraph/g +``` + +###### Response Status + +```json +200 +``` + +###### Response Body + +```json +{ + "status": 0, + "graph": { + "graphName": "default/hugegraph/g", + "partitionCount": 60, + "shardCount": 3 + } +} +``` + +#### 修改指定图的partition、shard信息 + +###### Method & Url + +``` +POST http://localhost:9001/v1/graph/default/hugegraph/g +``` + +###### Request Body + +```json +{ + "partitionCount":90, + "shardCount": 4, + "workMode":"Batch_Import" +} +``` + +###### Response Status + +```json +200 +``` + +###### Response Body + +```json +{ + "status": 0, + "graph": { + "graphName": "default/hugegraph/g", + "partitionCount": 60, + "shardCount": 4, + "workMode": "Batch_Import" + } +} +``` + +### 分区相关 + +#### 获取分区信息 + +###### Method & Url + +``` +GET http://localhost:9001/v1/partitions +``` + +###### Response Status + +```json +200 +``` + +###### Response Body + +```json +{ + "partitions": [ + { + "endKey": 2731, + "graphName": "DEFAULT/hugegraph18e1/g", + "id": 0, + "shards": [ + { + "address": "10.14.139.56:8500", + "committedIndex": "7396553", + "role": "Follower", + "state": "online", + "storeId": "6140115507143799097" + }, + { + "address": "10.14.139.61:8500", + "committedIndex": "7399964", + "role": "Follower", + "state": "online", + "storeId": "3661113370728329034" + }, + { + "address": "10.14.139.57:8500", + "committedIndex": "7394615", + "role": "Leader", + "state": "online", + "storeId": "3384290400781292563" + } + ], + "startKey": 0, + "timestamp": "2022-03-28 17:29:56", + "version": 23, + "workMode": "Normal", + "workState": "State_Normal" + } + ], + "status": 0 +} +``` + +###存储节点相关 + +#### 获取存储节点信息 + +###### Method & Url + +``` +GET http://localhost:9001/v1/stores +``` + +###### Response Status + +```json +200 +``` + +###### Response Body + +```json +{ + "status": 0, + "stores": [ + { + "address": "10.14.139.60:8500", + "dataVersion": 1, + "id": "1779882393067551022", + "labels": [ + { + "key": "rest.port", + "value": "8520" + } + ], + "lastHeartbeat": "1648459470389", + "raftAddress": "10.14.139.60:8510", + "startTimestamp": "1648395903516", + "state": "Up", + "stats": { + "available": "4081010491392", + "capacity": "7545950388224", + "graphStats": [ + { + "approximateKeys": "1933169027", + "approximateSize": "191628815980", + "committedIndex": "7301460", + "graphName": "DEFAULT/hugegraph18e1/g", + "partitionId": 1, + "role": "Leader", + "workMode": "Normal", + "workState": "State_Normal" + }, + { + "approximateKeys": "1919472248", + "approximateSize": "189192672333", + "committedIndex": "7301705", + "graphName": "DEFAULT/hugegraph18e1/g", + "partitionId": 2, + "role": "Leader", + "workMode": "Normal", + "workState": "State_Normal" + } + ], + "partitionCount": 12, + "startTime": 1648395902, + "storeId": "1779882393067551022", + "usedSize": "4851786575920" + }, + "version": "3.6.3" + } + ] +} +``` + +#### 删除存储节点 + +###### Method & Url + +``` +DELETE http://localhost:9001/v1/store/2549332866463202176 +``` + +###### Response Status + +```json +200 +``` + +###### Response Body + +```` +OK +```` + +#### 服务注册 + +###### Method & Url + +``` +post http://127.0.0.1:8620/v1/registry +``` + +###### Request Body + +```json +{ + "appName":"aaaa", + "version":"version1", + "address":"address1", + "interval":"9223372036854775807", + "labels": { + "aaa": "aaaavalue" + } +} +``` + +```` +appName:所属服务名 +version:所属服务版本号 +address:服务实例地址+端口 +interval:实例心跳间隔,字符串,最大9223372036854775807 +labels: 自定义标签 +```` + +###### Response Status + +```json +200 +``` + +###### Response Body + +```json +{ + "errorType": "OK", + "message": "", + "data": null +} +```` + +```` +errorType:状态码 +message:状态码为错误时的具体出错信息 +data:无返回数据 +```` + +#### 服务实例获取 + +###### Method & Url + +``` +post http://127.0.0.1:8620/v1/registryInfo +``` + +###### Request Body + +```json +{ + "appName":"aaaa", + "version":"version1", + "labels": { + "aaa": "aaaavalue" + } +} +``` + +```` +以下三项可全部为空,则获取所有服务节点的信息 + appName:过滤所属服务名的条件 + version:过滤所属服务版本号的条件,此项有值,则appName不能为空 + labels: 过滤自定义标签的条件 +```` + +###### Response Status + +```json +200 +``` + +###### Response Body + +```json +{ + "errorType": "OK", + "message": null, + "data": [ + { + "id": null, + "appName": "aaaa", + "version": "version1", + "address": "address1", + "interval": "9223372036854775807", + "labels": { + "aaa": "aaaavalue" + } + } + ] +} +``` + +```` +errorType:状态码 +message:状态码为错误时的具体出错信息 +data:获取的服务节点信息 +```` + +#### 服务注册 + +###### Method & Url + +``` +post http://127.0.0.1:8620/v1/registry +``` + +###### Request Body + +```json +{ + "appName":"aaaa", + "version":"version1", + "address":"address1", + "interval":"9223372036854775807", + "labels": { + "aaa": "aaaavalue" + } +} +``` + +```` +appName:所属服务名 +version:所属服务版本号 +address:服务实例地址+端口 +interval:实例心跳间隔,字符串,最大9223372036854775807 +labels: 自定义标签 +```` + +###### Response Status + +```json +200 +``` + +###### Response Body + +```json +{ + "errorType": "OK", + "message": "", + "data": null +} +```` + +```` +errorType:状态码 +message:状态码为错误时的具体出错信息 +data:无返回数据 +```` + +#### 获取pd成员 + +###### Method & Url + +``` +GET http://127.0.0.1:8620/v1/members +``` + +###### Response Status + +```json +200 +``` + +###### Response Body + +```json +{ + "status": 0, + "graphs": { + "members": [ + { + "clusterId": "1", + "raftUrl": "10.14.139.56:8620", + "grpcUrl": "10.14.139.56:8686", + "restUrl": "10.14.139.56:8620", + "dataPath": "./pd_data", + "state": "Up" + }, + { + "clusterId": "1", + "raftUrl": "10.14.139.57:8620", + "grpcUrl": "10.14.139.57:8686", + "restUrl": "10.14.139.57:8620", + "dataPath": "./pd_data", + "state": "Up" + }, + { + "clusterId": "1", + "raftUrl": "10.14.139.58:8620", + "grpcUrl": "10.14.139.58:8686", + "restUrl": "10.14.139.58:8620", + "dataPath": "./pd_data", + "state": "Up" + } + ], + "leader": { + "clusterId": "1", + "raftUrl": "10.14.139.56:8610", + "grpcUrl": "10.14.139.56:8686", + "dataPath": "./pd_data", + "state": "Up" + } + } +} +``` + +```` +clusterId:pd集群ID +raftUrl、grpcUrl、restUrl:pd节点的地址信息 +dataPath:pd数据存储目录 +state: 节点当前状态 +```` + +#### Store Mertics 地址 + +``` +GET http://127.0.0.1:8520/actuator/prometheus + +``` \ No newline at end of file diff --git a/hugegraph-store/build-change.sh b/hugegraph-store/build-change.sh new file mode 100644 index 000000000..e88f92022 --- /dev/null +++ b/hugegraph-store/build-change.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to You under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +export PATH=$MAVEN_3_5_3_BIN:$ORACLEJDK_11_0_7_BIN:$PATH +export JAVA_HOME=$ORACLEJDK_11_0_7_HOME +export MAVEN_HOME=$MAVEN_3_5_3_HOME + +readonly VER=3.6.3 +# TODO: remove thie repo +readonly REPO_URL=http://maven.baidu-int.com/nexus/content/repositories/Baidu_Local_Snapshots + +$MAVEN_3_5_3_BIN/mvn clean install -Dmaven.test.skip=true -DaltDeploymentRepository=Baidu_Local_Snapshots::default::${REPO_URL} + +echo "mv dist...." +mv dist output +ls output +echo "mv dist done" +echo "show output...." +ls output +echo "show output done" \ No newline at end of file diff --git a/hugegraph-store/build-pre.sh b/hugegraph-store/build-pre.sh new file mode 100644 index 000000000..1d89ff87b --- /dev/null +++ b/hugegraph-store/build-pre.sh @@ -0,0 +1,105 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to You under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +mkdir output +touch output/1 +export JAVA_HOME=$ORACLEJDK_1_8_0_HOME + +readonly VER=3.6.3 +readonly REPO_URL=http://10.14.139.8:8081/artifactory/star-local + +$MAVEN_3_5_3_BIN/mvn -DremoveSnapshot=true -DprocessAllModules=true -DgenerateBackupPoms=true versions:set +$MAVEN_3_5_3_BIN/mvn --settings ./settings.xml -Dmaven.test.skip=true -DaltDeploymentRepository=star-local::default::${REPO_URL} clean deploy +$MAVEN_3_5_3_BIN/mvn versions:revert + +#------------------repo-server----------------------- +readonly FILE_NAME=hugegraph-store-3.6.3.tar.gz +readonly REPO_URL_FILE=http://10.14.139.8:8081/artifactory/star-file + +localFilePath=dist/${FILE_NAME} +targetFolder="${REPO_URL_FILE}/dist/$(date '+%Y-%m-%d')/" +artifactoryUser="admin" +artifactoryPassword="JFrog12345" + +md5Value="$(md5sum "$localFilePath")" +md5Value="${md5Value:0:32}" +sha1Value="$(sha1sum "$localFilePath")" +sha1Value="${sha1Value:0:40}" +sha256Value="$(sha256sum "$localFilePath")" +sha256Value="${sha256Value:0:65}" + +#curl -X PUT -u admin:JFrog12345 -T ${localFilePath} "${REPO_URL_FILE}/dist/${data_folder}/" +echo "INFO: Uploading $localFilePath to $targetFolder" +curl -i -X PUT -u "$artifactoryUser:$artifactoryPassword" \ + -H "X-Checksum-Md5: $md5Value" \ + -H "X-Checksum-Sha1: $sha1Value" \ + -H "X-Checksum-Sha256: $sha256Value" \ + -T "$localFilePath" \ + "$targetFolder" + + + #------------------repo-client----------------------- + # TODO: remove thie repo +readonly REPO_SNAPSHOT_PATH=http://10.14.139.8:8082/artifactory/star +readonly REPO_FILE_PATH=http://10.14.139.8:8082/artifactory/star-file +readonly REPO_BAIDU_LOCAL_PATH=http://maven.baidu-int.com/nexus/content/repositories/Baidu_Local +readonly PACKAGE_PATH=${REPO_SNAPSHOT_PATH}/com/baidu/hugegraph + +dest=store-client-${VER} +mkdir ${dest} + +curl ${PACKAGE_PATH}/hg-store-client/${VER}/hg-store-client-${VER}.jar -o ./${dest}/hg-store-client-${VER}.jar +curl ${PACKAGE_PATH}/hg-store-client/${VER}/hg-store-client-${VER}.pom -o ./${dest}/hg-store-client-pom.xml + +curl ${PACKAGE_PATH}/hg-store-grpc/${VER}/hg-store-grpc-${VER}.jar -o ./${dest}/hg-store-grpc-${VER}.jar +curl ${PACKAGE_PATH}/hg-store-grpc/${VER}/hg-store-grpc-${VER}.pom -o ./${dest}/hg-store-grpc-pom.xml + +curl ${PACKAGE_PATH}/hg-store-common/${VER}/hg-store-common-${VER}.jar -o ./${dest}/hg-store-common-${VER}.jar +curl ${PACKAGE_PATH}/hg-store-common/${VER}/hg-store-common-${VER}.pom -o ./${dest}/hg-store-common-pom.xml + +curl ${PACKAGE_PATH}/hg-pd-client/${VER}/hg-pd-client-${VER}.jar -o ./${dest}/hg-pd-client-${VER}.jar +curl ${PACKAGE_PATH}/hg-pd-client/${VER}/hg-pd-client-${VER}.pom -o ./${dest}/hg-pd-client-pom.xml + +curl ${PACKAGE_PATH}/hg-pd-grpc/${VER}/hg-pd-grpc-${VER}.jar -o ./${dest}/hg-pd-grpc-${VER}.jar +curl ${PACKAGE_PATH}/hg-pd-grpc/${VER}/hg-pd-grpc-${VER}.pom -o ./${dest}/hg-pd-grpc-pom.xml + +curl ${PACKAGE_PATH}/hg-pd-common/${VER}/hg-pd-common-${VER}.jar -o ./${dest}/hg-pd-common-${VER}.jar +curl ${PACKAGE_PATH}/hg-pd-common/${VER}/hg-pd-common-${VER}.pom -o ./${dest}/hg-pd-common-pom.xml + +tar -cvf ${dest}.tar ${dest} + +localFilePath=${dest}.tar +targetFolder="${REPO_URL_FILE}/dist/$(date '+%Y-%m-%d')/" +artifactoryUser="admin" +artifactoryPassword="JFrog12345" + +md5Value="$(md5sum "$localFilePath")" +md5Value="${md5Value:0:32}" +sha1Value="$(sha1sum "$localFilePath")" +sha1Value="${sha1Value:0:40}" +sha256Value="$(sha256sum "$localFilePath")" +sha256Value="${sha256Value:0:65}" + +#curl -X PUT -u admin:JFrog12345 -T ${localFilePath} "${REPO_URL_FILE}/dist/${data_folder}/" +echo "INFO: Uploading $localFilePath to $targetFolder" +curl -i -X PUT -u "$artifactoryUser:$artifactoryPassword" \ + -H "X-Checksum-Md5: $md5Value" \ + -H "X-Checksum-Sha1: $sha1Value" \ + -H "X-Checksum-Sha256: $sha256Value" \ + -T "$localFilePath" \ + "$targetFolder" \ No newline at end of file diff --git a/hugegraph-store/build.sh b/hugegraph-store/build.sh new file mode 100755 index 000000000..a8129c618 --- /dev/null +++ b/hugegraph-store/build.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to You under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +export PATH=$MAVEN_3_5_3_BIN:$ORACLEJDK_11_0_7_BIN:$PATH +export JAVA_HOME=$ORACLEJDK_11_0_7_HOME +export MAVEN_HOME=$MAVEN_3_5_3_HOME + +readonly VER=3.6.3 +readonly REPO_URL=http://maven.baidu-int.com/nexus/content/repositories/Baidu_Local_Snapshots + +$MAVEN_3_5_3_BIN/mvn -Dmaven.test.skip=true -DaltDeploymentRepository=Baidu_Local_Snapshots::default::${REPO_URL} -U clean deploy +echo "mv dist...." +mv dist output +ls output +echo "mv dist done" +echo "show output...." +ls output +echo "show output done" \ No newline at end of file diff --git a/hugegraph-store/ci.yml b/hugegraph-store/ci.yml new file mode 100644 index 000000000..0937e20c0 --- /dev/null +++ b/hugegraph-store/ci.yml @@ -0,0 +1,26 @@ +Global: + tool: build_submitter + languages: + # Java Version: 1.7, 1.8, 9, 10 + - language: java + version: 11 + envs: + # Maven Version: 3.0.4, 3.1.1, 3.2.5, 3.3.9, 3.5.3 + - env: maven + version: 3.5.3 +Default: + profile: [ dev ] +Profiles: + - profile: + name: dev + tool: build_submitter + env: DECK_CENTOS7U5_K3 + command: sh build.sh + release: true + + - profile: + name: change + tool: build_submitter + env: DECK_CENTOS7U5_K3 + command: sh build-change.sh + release: true \ No newline at end of file diff --git a/hugegraph-store/conf/application-pd.yml b/hugegraph-store/conf/application-pd.yml new file mode 100644 index 000000000..59c677d92 --- /dev/null +++ b/hugegraph-store/conf/application-pd.yml @@ -0,0 +1,17 @@ +management: + metrics: + export: + prometheus: + enabled: true + endpoints: + web: + exposure: + include: "*" + +rocksdb: + # rocksdb使用的总内存大小,达到该值强制写盘 + total_memory_size: 32000000000 + # rocksdb使用的memtable大小 + write_buffer_size: 32000000 + # 对于每个rocksdb来说,memtable个数达到该值进行写盘 + min_write_buffer_number_to_merge: 16 diff --git a/hugegraph-store/deploy-release.sh b/hugegraph-store/deploy-release.sh new file mode 100755 index 000000000..e9421724a --- /dev/null +++ b/hugegraph-store/deploy-release.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to You under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +readonly VER=3.6.3 +readonly REPO_URL=http://10.14.139.8:8081/artifactory/star-local +#mvn -DnewVersion=${VER}-SNAPSHOT -DprocessAllModules=true -DgenerateBackupPoms=false versions:set + +./mvnw -DremoveSnapshot=true -DprocessAllModules=true -DgenerateBackupPoms=true versions:set +./mvnw --settings ./settings.xml -Dmaven.test.skip=true -DaltDeploymentRepository=star-local::default::${REPO_URL} clean deploy +./mvnw versions:revert diff --git a/hugegraph-store/deploy-snapshot.sh b/hugegraph-store/deploy-snapshot.sh new file mode 100644 index 000000000..14ff59239 --- /dev/null +++ b/hugegraph-store/deploy-snapshot.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to You under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +readonly REPO_URL=http://10.14.139.8:8081/artifactory/star-snapshot + +mvn --settings ./settings.xml -Dmaven.test.skip=true -DaltDeploymentRepository=star-snapshot::default::${REPO_URL} clean deploy + diff --git a/hugegraph-store/hg-store-cli/deploy-snapshot.sh b/hugegraph-store/hg-store-cli/deploy-snapshot.sh new file mode 100644 index 000000000..ce2277626 --- /dev/null +++ b/hugegraph-store/hg-store-cli/deploy-snapshot.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to You under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +readonly REPO_URL=http://10.14.139.8:8081/artifactory/star-snapshot + +mvn --settings ../settings.xml -Dmaven.test.skip=true -DaltDeploymentRepository=star-local::default::${REPO_URL} clean deploy + diff --git a/hugegraph-store/hg-store-cli/pom.xml b/hugegraph-store/hg-store-cli/pom.xml new file mode 100644 index 000000000..d28bd89b6 --- /dev/null +++ b/hugegraph-store/hg-store-cli/pom.xml @@ -0,0 +1,80 @@ + + + + + + org.apache.hugegraph + hugegraph-store-root + ${revision} + ../pom.xml + + 4.0.0 + + hg-store-cli + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-logging + + + + + org.apache.hugegraph + hg-store-client + ${revision} + + + org.apache.hugegraph + hg-store-grpc + ${revision} + + + org.apache.hugegraph + hg-pd-client + ${revision} + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + org.apache.hugegraph.store.cli.StoreConsoleApplication + + + + + + + + \ No newline at end of file diff --git a/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/AppConfig.java b/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/AppConfig.java new file mode 100644 index 000000000..5a6cf4d2e --- /dev/null +++ b/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/AppConfig.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.cli; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import lombok.Data; + +@Data +@Component +public class AppConfig { + + @Value("${pd.address}") + private String pdAddress; + + @Value("${net.kv.scanner.page.size}") + private int scannerPageSize; + + @Value("${scanner.graph}") + private String scannerGraph; + + @Value("${scanner.table}") + private String scannerTable; + + @Value("${scanner.max}") + private int scannerMax; + + @Value("${scanner.mod}") + private int scannerModNumber; + + @Value("${committer.graph}") + private String committerGraph; + + @Value("${committer.table}") + private String committerTable; + + @Value("${committer.amount}") + private int committerAmount; +} diff --git a/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/StoreConsoleApplication.java b/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/StoreConsoleApplication.java new file mode 100644 index 000000000..1a620b27f --- /dev/null +++ b/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/StoreConsoleApplication.java @@ -0,0 +1,118 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.cli; + +import java.io.IOException; + +import org.apache.hugegraph.pd.client.PDConfig; +import org.apache.hugegraph.pd.common.PDException; +import org.apache.hugegraph.store.HgStoreClient; +import org.apache.hugegraph.store.cli.loader.HgThread2DB; +import org.apache.hugegraph.store.cli.scan.GrpcShardScanner; +import org.apache.hugegraph.store.cli.scan.HgStoreCommitter; +import org.apache.hugegraph.store.cli.scan.HgStoreScanner; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +import lombok.extern.slf4j.Slf4j; + + +/** + * 2022/2/14 + */ +@SpringBootApplication +@Slf4j +public class StoreConsoleApplication implements CommandLineRunner { + + @Autowired + private AppConfig appConfig; + + public static void main(String[] args) { + log.info("Starting StoreConsoleApplication"); + SpringApplication.run(StoreConsoleApplication.class, args); + log.info("StoreConsoleApplication finished."); + } + + @Override + public void run(String... args) throws IOException, InterruptedException, PDException { + if (args.length <= 0) { + // HgThread2DB hB = new HgThread2DB("localhost:8686"); + // hB.startMultiprocessQuery("12", "10"); + System.out.println("参数类型 cmd[-load, -query, -scan]"); + } else { + switch (args[0]) { + case "-load": + HgThread2DB hgThread2DB = new HgThread2DB(args[1]); + if (!args[3].isEmpty()) { + hgThread2DB.setGraphName(args[3]); + } + try { + if ("order".equals(args[2])) { + hgThread2DB.testOrder(args[4]); + } else { + hgThread2DB.startMultiprocessInsert(args[2]); + } + } catch (IOException e) { + e.printStackTrace(); + } + break; + case "-query": + HgThread2DB hgDB = new HgThread2DB(args[1]); + try { + hgDB.startMultiprocessQuery("12", args[2]); + } catch (IOException e) { + e.printStackTrace(); + } + break; + case "-scan": + if (args.length < 4) { + System.out.println("参数类型 -scan pd graphName tableName"); + } else { + doScan(args[1], args[2], args[3]); + } + break; + case "-shard": + GrpcShardScanner scanner = new GrpcShardScanner(); + scanner.getData(); + break; + case "-shard-single": + scanner = new GrpcShardScanner(); + scanner.getDataSingle(); + break; + default: + System.out.println("参数类型错误,未执行任何程序"); + } + } + } + + private void doCommit() { + HgStoreCommitter committer = HgStoreCommitter.of(appConfig.getCommitterGraph()); + committer.put(appConfig.getScannerTable(), appConfig.getCommitterAmount()); + } + + private void doScan(String pd, String graphName, String tableName) throws PDException { + HgStoreClient storeClient = HgStoreClient.create(PDConfig.of(pd) + .setEnableCache(true)); + + HgStoreScanner storeScanner = HgStoreScanner.of(storeClient, graphName); + storeScanner.scanTable2(tableName); + // storeScanner.scanHash(); + } +} diff --git a/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/loader/HgThread2DB.java b/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/loader/HgThread2DB.java new file mode 100644 index 000000000..842d74141 --- /dev/null +++ b/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/loader/HgThread2DB.java @@ -0,0 +1,587 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.cli.loader; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.hugegraph.pd.client.PDClient; +import org.apache.hugegraph.pd.client.PDConfig; +import org.apache.hugegraph.store.HgKvEntry; +import org.apache.hugegraph.store.HgKvIterator; +import org.apache.hugegraph.store.HgOwnerKey; +import org.apache.hugegraph.store.HgScanQuery; +import org.apache.hugegraph.store.HgStoreClient; +import org.apache.hugegraph.store.HgStoreSession; +import org.apache.hugegraph.store.cli.util.HgCliUtil; +import org.apache.hugegraph.store.client.grpc.KvCloseableIterator; +import org.apache.hugegraph.store.client.util.MetricX; + +import lombok.extern.slf4j.Slf4j; + + +/** + * 使用pd,支持raft + * 读取文件并多线程进行入库 + */ +@Slf4j +public class HgThread2DB { + /*正在进行和在排队的任务的总数*/ + private static final AtomicInteger taskTotal = new AtomicInteger(0); + private static final AtomicInteger queryTaskTotal = new AtomicInteger(0); + private static final AtomicLong insertDataCount = new AtomicLong(); + private static final AtomicLong queryCount = new AtomicLong(); + private static final AtomicLong totalQueryCount = new AtomicLong(); + private static final AtomicLong longId = new AtomicLong(); + private static final CountDownLatch countDownLatch = null; + private static PDClient pdClient; + private static ThreadPoolExecutor threadPool = null; + private static ThreadPoolExecutor queryThreadPool = null; + private static int limitScanBatchCount = 100; + private static ArrayBlockingQueue listQueue = null; + private final HgStoreClient storeClient; + public String graphName = "hugegraphtest"; + volatile long startTime = System.currentTimeMillis(); + + public HgThread2DB(String pdAddr) { + int threadCount = Runtime.getRuntime().availableProcessors(); + + listQueue = new ArrayBlockingQueue>(100000000); + queryThreadPool = new ThreadPoolExecutor(500, 1000, + 200, TimeUnit.SECONDS, + new ArrayBlockingQueue<>(1000)); + threadPool = new ThreadPoolExecutor(threadCount * 2, threadCount * 3, + 200, TimeUnit.SECONDS, + new ArrayBlockingQueue<>(threadCount + 100)); + storeClient = HgStoreClient.create(PDConfig.of(pdAddr) + .setEnableCache(true)); + pdClient = storeClient.getPdClient(); + } + + public void setGraphName(String graphName) { + this.graphName = graphName; + log.info("setGraphName {}", graphName); + } + + public boolean singlePut(String tableName + , List keys) throws InterruptedException { + HgStoreSession session = storeClient.openSession(graphName); + session.beginTx(); + + keys.forEach((strKey) -> { + insertDataCount.getAndIncrement(); + int j = strKey.indexOf("\t"); +// byte[] key = HgCliUtil.toBytes(strKey.substring(0, j)); + HgOwnerKey hgKey = HgCliUtil.toOwnerKey(strKey.substring(0, j), strKey); + byte[] value = HgCliUtil.toBytes(strKey.substring(j + 1)); + session.put(tableName, hgKey, value); + + }); + if (insertDataCount.get() > 10000000) { + synchronized (insertDataCount) { + long count = insertDataCount.get(); + insertDataCount.set(0); + if (count > 10000000) { + log.info("count : " + count + " qps : " + + count * 1000 / (System.currentTimeMillis() - startTime) + + " threadCount : " + taskTotal); + startTime = System.currentTimeMillis(); + } + } + } +// Thread.sleep(2000); + if (!keys.isEmpty()) { + if (session.isTx()) { + session.commit(); + } else { + session.rollback(); + } + } + + return true; + } + + public boolean singlePut(String tableName) throws InterruptedException { + HgStoreSession session = storeClient.openSession(graphName); + session.beginTx(); + + int maxlist = 100; + + for (int y = 0; y < maxlist; y++) { + insertDataCount.getAndIncrement(); + String strLine = getLong() + getLong() + getLong() + getLong(); +// log.info("========{}",strLine); + HgOwnerKey hgKey = HgCliUtil.toOwnerKey(strLine, strLine); + byte[] value = HgCliUtil.toBytes(strLine); + session.put(tableName, hgKey, value); + } + + if (insertDataCount.get() > 10000000) { + synchronized (insertDataCount) { + long count = insertDataCount.get(); + insertDataCount.set(0); + if (count > 10000000) { + log.info("count : " + count + " qps : " + + count * 1000 / (System.currentTimeMillis() - startTime) + + " threadCount : " + taskTotal); + startTime = System.currentTimeMillis(); + } + } + } + + if (session.isTx()) { + session.commit(); + } else { + session.rollback(); + } + + + return true; + } + + public boolean testOrder(String input) { + String tableName = "hugegraph02"; + HgStoreSession session = storeClient.openSession(graphName); + session.beginTx(); + int loop = Integer.parseInt(input); + if (loop == 0) { + loop = 2000; + } + for (int i = 0; i < loop; i++) { + long startTime = System.currentTimeMillis(); + HgOwnerKey hgOwnerKey = + HgCliUtil.toOwnerKey(startTime + "owner:" + i, startTime + "k:" + i); + session.put(tableName, hgOwnerKey, HgCliUtil.toBytes(i)); + } + + if (session.isTx()) { + session.commit(); + } else { + session.rollback(); + } + + try { + HgKvIterator iterable = session.scanIterator(tableName); + int x = 0; + while (iterable.hasNext()) { + HgKvEntry entry = iterable.next(); +// log.info("data:{}-{}", toStr(entry.key()), entry.value()); + x++; + } + log.info("x={}", x); + } catch (Exception e) { + log.error("query error, message "); +// e.printStackTrace(); + } + + + return true; + } + + /** + * 多线程读取文件入库 + * + * @throws IOException + * @throws InterruptedException + */ + public void startMultiprocessInsert(String filepath) throws IOException { + log.info("--- start startMultiprocessInsert---"); + startTime = System.currentTimeMillis(); + File readfile = new File(filepath); + MetricX metrics = null; + long dataCount = 0; + if (readfile.exists()) { + // 读取文件 + InputStreamReader isr = new InputStreamReader(new FileInputStream(readfile), + StandardCharsets.UTF_8); + BufferedReader reader = new BufferedReader(isr); + + String strLine = null; + String tableName = HgCliUtil.TABLE_NAME; + // 积攒到多少个后执行线程入库,10万 + int maxlist = 100000; + List keys = new ArrayList<>(maxlist); + metrics = MetricX.ofStart(); + try { + while ((strLine = reader.readLine()) != null) { + keys.add(strLine); + dataCount++; + + // 读取文件中的10000条数据,启一个线程入库 + if (dataCount % maxlist == 0) { + List finalKeys = keys; + Runnable task = new Runnable() { + @Override + public void run() { + try { + if (!finalKeys.isEmpty()) { + boolean ret = singlePut(tableName, finalKeys); + } + } catch (Exception e) { + e.printStackTrace(); + } + taskTotal.decrementAndGet(); + synchronized (taskTotal) { + taskTotal.notifyAll(); + } + } + }; + taskTotal.getAndIncrement(); + threadPool.execute(task); + + while (taskTotal.get() > 100) { + synchronized (taskTotal) { + taskTotal.wait(); + } + } + // keys.remove(0); + keys = new ArrayList<>(maxlist); + } + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + isr.close(); + reader.close(); + // 把剩余的入库 + if (!keys.isEmpty()) { + List finalKeys1 = keys; + Runnable task = new Runnable() { + @Override + public void run() { + try { + boolean ret = singlePut(tableName, finalKeys1); + } catch (Exception e) { + e.printStackTrace(); + } + taskTotal.decrementAndGet(); + synchronized (taskTotal) { + taskTotal.notifyAll(); + } + } + }; + threadPool.execute(task); + taskTotal.getAndIncrement(); + } + while (taskTotal.get() > 0) { + synchronized (taskTotal) { + try { + taskTotal.wait(1000); + if (taskTotal.get() > 0) { + System.out.println("wait thread exit " + taskTotal.get()); + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + + threadPool.shutdown(); + + } else { + System.out.println("样本文件不存在:" + filepath); + } + metrics.end(); + log.info("*************************************************"); + log.info(" 主进程执行时间:" + metrics.past() / 1000 + "秒,一共执行:" + dataCount + "条"); + log.info("*************************************************"); + System.out.println(" 主进程执行时间 " + metrics.past() / 1000 + "秒"); + System.out.println("-----主进程执行结束---------"); + } + + /** + * 多线程读取文件入库 + * + * @throws IOException + * @throws InterruptedException + */ + public void autoMultiprocessInsert() throws IOException { + log.info("--- start autoMultiprocessInsert---"); + startTime = System.currentTimeMillis(); + + + MetricX metrics = null; + long dataCount = 0; + + String strLine = null; + String tableName = HgCliUtil.TABLE_NAME; + // 积攒到多少个后执行线程入库,10万 + int maxlist = 100000; + List keys = new ArrayList<>(maxlist); + for (int x = 0; x < 10000000; x++) { + metrics = MetricX.ofStart(); + try { + Runnable task = new Runnable() { + @Override + public void run() { + try { + boolean ret = singlePut(tableName); + } catch (Exception e) { + e.printStackTrace(); + } + taskTotal.decrementAndGet(); + synchronized (taskTotal) { + taskTotal.notifyAll(); + } + } + }; + taskTotal.getAndIncrement(); + threadPool.execute(task); + + while (taskTotal.get() > 100) { + synchronized (taskTotal) { + taskTotal.wait(); + } + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + while (taskTotal.get() > 0) { + synchronized (taskTotal) { + try { + taskTotal.wait(1000); + if (taskTotal.get() > 0) { + System.out.println("wait thread exit " + taskTotal.get()); + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + + threadPool.shutdown(); + + + metrics.end(); + log.info("*************************************************"); + log.info(" 主进程执行时间:" + metrics.past() / 1000 + "秒,一共执行:" + dataCount + "条"); + log.info("*************************************************"); + System.out.println(" 主进程执行时间 " + metrics.past() / 1000 + "秒"); + System.out.println("-----主进程执行结束---------"); + } + + public String getLong() { + //如果需要更长 或者更大冗余空间, 只需要 time * 10^n 即可 + //当前可保证1毫秒 生成 10000条不重复 + return String.format("%019x", longId.getAndIncrement()); + } + + + /** + * 执行查询,并将查询的结果做为下一次迭代的点放入队列 + */ + private void queryAnd2Queue() { + try { + HgStoreSession session = storeClient.openSession(graphName); + HashSet hashSet = new HashSet<>(); + while (!listQueue.isEmpty()) { + + log.info(" ====== start scanBatch2 count:{} list:{}=============", + queryThreadPool.getActiveCount(), listQueue.size()); + List keys = (List) listQueue.take(); + List newQueryList = new ArrayList<>(); + + KvCloseableIterator> iterators = + session.scanBatch2( + HgScanQuery.prefixIteratorOf(HgCliUtil.TABLE_NAME, keys.iterator()) + ); + + while (iterators.hasNext()) { + HgKvIterator iterator = iterators.next(); + int insertQueueCount = 0; + while (iterator.hasNext()) { + HgKvEntry entry = iterator.next(); + String newPoint = HgCliUtil.toStr(entry.value()); +// log.info("query_key =" + newPoint); + // 统计查询次数 + if (!newPoint.isEmpty() && hashSet.add(newPoint)) { + queryCount.getAndIncrement(); + totalQueryCount.getAndIncrement(); + + HgOwnerKey hgKey = HgCliUtil.toOwnerKey(newPoint, newPoint); + newQueryList.add(hgKey); + + if (queryCount.get() > 1000000) { + synchronized (queryCount) { + long count = queryCount.get(); + queryCount.set(0); + if (count > 1000000) { + log.info("count : " + count + " qps : " + count * 1000 / + (System.currentTimeMillis() - + startTime) + + " threadCount : " + + queryThreadPool.getActiveCount() + " queueSize:" + + listQueue.size()); + startTime = System.currentTimeMillis(); + } + } + } + // 达到1万个点后,去查询一次 + if (newQueryList.size() > 10000 && listQueue.size() < 10000) { + listQueue.put(newQueryList); + insertQueueCount++; + newQueryList = new ArrayList<>(); + if (insertQueueCount > 2) { + break; + } + } + } + } + } + // 一次查询如果不够1万,单独提交一次查询,确保所有的结果都能执行查询 + if (!newQueryList.isEmpty() && listQueue.size() < 1000) { + listQueue.put(newQueryList); + } + + iterators.close(); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + log.info("============= thread done =============="); + countDownLatch.countDown(); + } + + /** + * 多线程查询 + * + * @param point 起始查询点,后续根据这个点查询到的value做为下一次的查询条件进行迭代 + * @param scanCount 允许启动的线程数量 + * @throws IOException + * @throws InterruptedException + */ + public void startMultiprocessQuery(String point, String scanCount) throws IOException, + InterruptedException { + log.info("--- start startMultiprocessQuery---"); + startTime = System.currentTimeMillis(); + MetricX metrics = MetricX.ofStart(); + limitScanBatchCount = Integer.parseInt(scanCount); + + CountDownLatch latch = new CountDownLatch(limitScanBatchCount); + HgStoreSession session = storeClient.openSession(graphName); + + final AtomicLong[] counter = {new AtomicLong()}; + final long[] start = {System.currentTimeMillis()}; + + LinkedBlockingQueue[] queue = new LinkedBlockingQueue[limitScanBatchCount]; + for (int i = 0; i < limitScanBatchCount; i++) { + queue[i] = new LinkedBlockingQueue(); + } + List strKey = Arrays.asList( + "20727483", "50329304", "26199460", "1177521", "27960125", + "30440025", "15833920", "15015183", "33153097", "21250581"); + strKey.forEach(key -> { + log.info("newkey:{}", key); + HgOwnerKey hgKey = HgCliUtil.toOwnerKey(key, key); + queue[0].add(hgKey); + }); + + for (int i = 0; i < limitScanBatchCount; i++) { + int finalI = i; + KvCloseableIterator> iterators = + session.scanBatch2( + HgScanQuery.prefixIteratorOf(HgCliUtil.TABLE_NAME, + new Iterator() { + HgOwnerKey current = null; + + @Override + public boolean hasNext() { + while (current == null) { + try { + current = + (HgOwnerKey) queue[finalI].poll( + 1, + TimeUnit.SECONDS); + } catch ( + InterruptedException e) { + // + } + } + if (current == null) { + log.info( + "===== current is " + + "null =========="); + } + return current != null; + } + + @Override + public HgOwnerKey next() { + return current; + } + }) + ); + + new Thread(() -> { + while (iterators.hasNext()) { + HgKvIterator iterator = iterators.next(); + long c = 0; + while (iterator.hasNext()) { + String newPoint = HgCliUtil.toStr(iterator.next().value()); + HgOwnerKey newHgKey = HgCliUtil.toOwnerKey(newPoint, newPoint); + if (queue[(int) (c % limitScanBatchCount)].size() < 1000000) { + queue[(int) (c % limitScanBatchCount)].add(newHgKey); + } + c++; + } + if (counter[0].addAndGet(c) > 1000000) { + synchronized (counter) { + if (counter[0].get() > 10000000) { + log.info("count {}, qps {}", counter[0].get(), + counter[0].get() * 1000 / + (System.currentTimeMillis() - start[0])); + start[0] = System.currentTimeMillis(); + counter[0].set(0); + } + } + } + } + }, "client query thread:" + i).start(); + log.info("===== read thread exit =========="); + } + latch.await(); + + + metrics.end(); + log.info("*************************************************"); + log.info(" 主进程执行时间:" + metrics.past() / 1000 + "秒; 查询:" + totalQueryCount.get() + + "次,qps:" + totalQueryCount.get() * 1000 / metrics.past()); + log.info("*************************************************"); + System.out.println("-----主进程执行结束---------"); + } + +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/scan/GrpcShardScanner.java b/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/scan/GrpcShardScanner.java new file mode 100644 index 000000000..deaacc63f --- /dev/null +++ b/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/scan/GrpcShardScanner.java @@ -0,0 +1,170 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.cli.scan; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.hugegraph.store.grpc.GraphStoreGrpc; +import org.apache.hugegraph.store.grpc.GraphStoreGrpc.GraphStoreStub; +import org.apache.hugegraph.store.grpc.Graphpb.ScanPartitionRequest; +import org.apache.hugegraph.store.grpc.Graphpb.ScanPartitionRequest.Reply; +import org.apache.hugegraph.store.grpc.Graphpb.ScanResponse; + +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; +import io.grpc.stub.StreamObserver; +import lombok.extern.slf4j.Slf4j; + +/** + * @date 2022/11/1 + **/ + +@Slf4j +public class GrpcShardScanner { + private final boolean closed = false; + private final AtomicInteger sum = new AtomicInteger(); + private final ConcurrentHashMap> + observers = new ConcurrentHashMap<>(); + + public void getData() { + ExecutorService service = new ThreadPoolExecutor(500, Integer.MAX_VALUE, + 0L, + TimeUnit.MILLISECONDS, + new LinkedBlockingQueue<>()); + long start = System.currentTimeMillis(); + + String[] addresses = new String[]{"10.14.139.71:8500", + "10.14.139.70:8500", + "10.14.139.69:8500"}; + int pSize = 72; + int size = pSize * addresses.length; + CountDownLatch latch = new CountDownLatch(size); + for (int j = 0; j < pSize; j++) { + for (int i = 0; i < addresses.length; i++) { + String address = addresses[i]; + int finalJ = j; + service.execute(() -> getData(finalJ, latch, address)); + } + } + try { + latch.await(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + long end = System.currentTimeMillis(); + long cost = end - start; + log.info("all rows are: {}, cost: {},avg: {}", sum.get(), + cost, sum.get() / cost * 1000); + } + + public void getData(int pId, CountDownLatch latch, String address) { + try { + ScanPartitionRequest.Builder builder = + ScanPartitionRequest.newBuilder(); + ScanPartitionRequest.Request.Builder srb = + ScanPartitionRequest.Request.newBuilder(); + ScanPartitionRequest.Request request = + srb.setGraphName("DEFAULT/hugegraph2/g") + .setScanType( + ScanPartitionRequest.ScanType.SCAN_EDGE) + .setTable("g+oe").setBoundary(0x10) + .setPartitionId(pId).build(); + ManagedChannel c = + ManagedChannelBuilder.forTarget(address) + .usePlaintext().build(); + int maxSize = 1024 * 1024 * 1024; + GraphStoreStub stub; + stub = GraphStoreGrpc.newStub(c) + .withMaxInboundMessageSize(maxSize) + .withMaxOutboundMessageSize(maxSize); + + AtomicInteger count = new AtomicInteger(); + long start = System.currentTimeMillis(); + long id = Thread.currentThread().getId(); + StreamObserver ro = + new StreamObserver() { + @Override + public void onNext(ScanResponse value) { + try { + int edgeSize = value.getEdgeCount(); + int vertexSize = value.getVertexCount(); + if (request.getScanType().equals( + ScanPartitionRequest.ScanType.SCAN_VERTEX)) { + count.getAndAdd(vertexSize); + } else { + count.getAndAdd(edgeSize); + } + // String print = JsonFormat.printer().print + // (value); + // System.out.println(print); + ScanPartitionRequest.Builder builder = + ScanPartitionRequest.newBuilder(); + builder.setScanRequest(request); + Reply.Builder reply = Reply.newBuilder(); + reply.setSeqNo(1); + builder.setReplyRequest(reply); + observers.get(id).onNext(builder.build()); + + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void onError(Throwable t) { + log.warn("调用grpc接口发生错误", t); + latch.countDown(); + } + + @Override + public void onCompleted() { + long time = System.currentTimeMillis() - start; + log.info("scan id : {}, complete: {} ,time:{}", + pId, count.get(), time); + sum.addAndGet(count.get()); + latch.countDown(); + } + }; + StreamObserver observer = + stub.scanPartition(ro); + observers.put(id, observer); + builder.setScanRequest(request); + observer.onNext(builder.build()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void getDataSingle() { + CountDownLatch latch = new CountDownLatch(1); + new Thread(() -> getData(58, latch, "10.14.139.71:8500")).start(); + try { + latch.await(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + log.info("all rows are: {}", sum.get()); + } + +} diff --git a/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/scan/HgStoreCommitter.java b/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/scan/HgStoreCommitter.java new file mode 100644 index 000000000..3161d7419 --- /dev/null +++ b/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/scan/HgStoreCommitter.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.cli.scan; + +import org.apache.hugegraph.store.HgOwnerKey; +import org.apache.hugegraph.store.HgSessionManager; +import org.apache.hugegraph.store.HgStoreSession; +import org.apache.hugegraph.store.cli.util.HgCliUtil; +import org.apache.hugegraph.store.client.HgStoreNodeManager; + +/** + * 2022/2/28 + */ +public class HgStoreCommitter { + + protected final static HgStoreNodeManager nodeManager = HgStoreNodeManager.getInstance(); + + private final String graph; + + + private HgStoreCommitter(String graph) { + this.graph = graph; + } + + public static HgStoreCommitter of(String graph) { + return new HgStoreCommitter(graph); + } + + protected HgStoreSession getStoreSession() { + return HgSessionManager.getInstance().openSession(this.graph); + } + + protected HgStoreSession getStoreSession(String graphName) { + return HgSessionManager.getInstance().openSession(graphName); + } + + public void put(String tableName, int amount) { + //*************** Put Benchmark **************//* + String keyPrefix = "PUT-BENCHMARK"; + HgStoreSession session = getStoreSession(); + + int length = String.valueOf(amount).length(); + + session.beginTx(); + + long start = System.currentTimeMillis(); + for (int i = 0; i < amount; i++) { + HgOwnerKey key = HgCliUtil.toOwnerKey( + keyPrefix + "-" + HgCliUtil.padLeftZeros(String.valueOf(i), length)); + byte[] value = HgCliUtil.toBytes(keyPrefix + "-V-" + i); + + session.put(tableName, key, value); + + if ((i + 1) % 100_000 == 0) { + HgCliUtil.println("---------- " + (i + 1) + " --------"); + HgCliUtil.println( + "Preparing took: " + (System.currentTimeMillis() - start) + " ms."); + session.commit(); + HgCliUtil.println( + "Committing took: " + (System.currentTimeMillis() - start) + " ms."); + start = System.currentTimeMillis(); + session.beginTx(); + } + } + + if (session.isTx()) { + session.commit(); + } + + + } +} diff --git a/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/scan/HgStoreScanner.java b/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/scan/HgStoreScanner.java new file mode 100644 index 000000000..c02956df4 --- /dev/null +++ b/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/scan/HgStoreScanner.java @@ -0,0 +1,237 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.cli.scan; + +import java.util.Arrays; +import java.util.List; + +import org.apache.hugegraph.pd.client.PDClient; +import org.apache.hugegraph.pd.common.PDException; +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.store.HgKvEntry; +import org.apache.hugegraph.store.HgKvIterator; +import org.apache.hugegraph.store.HgKvStore; +import org.apache.hugegraph.store.HgScanQuery; +import org.apache.hugegraph.store.HgSessionManager; +import org.apache.hugegraph.store.HgStoreClient; +import org.apache.hugegraph.store.HgStoreSession; +import org.apache.hugegraph.store.cli.util.HgCliUtil; +import org.apache.hugegraph.store.cli.util.HgMetricX; +import org.apache.hugegraph.store.client.grpc.KvCloseableIterator; +import org.apache.hugegraph.store.client.util.HgStoreClientConfig; +import org.apache.hugegraph.store.client.util.MetricX; + +import lombok.extern.slf4j.Slf4j; + +/** + * 2022/2/14 + */ +@Slf4j +public class HgStoreScanner { + + public static final byte[] EMPTY_BYTES = new byte[0]; + private final HgStoreClient storeClient; + private final String graphName; + private long modNumber = 1_000_000; + private int max = 10_000_000; + + private HgStoreScanner(HgStoreClient storeClient, String graph) { + this.storeClient = storeClient; + this.graphName = graph; + } + + public static HgStoreScanner of(HgStoreClient storeClient, String graph) { + return new HgStoreScanner(storeClient, graph); + } + + public long getModNumber() { + return modNumber; + } + + public void setModNumber(int modNumber) { + if (modNumber <= 0) { + return; + } + this.modNumber = modNumber; + } + + public int getMax() { + return max; + } + + public void setMax(int max) { + if (modNumber <= 0) { + return; + } + this.max = max; + } + + protected HgStoreSession getStoreSession() { + return HgSessionManager.getInstance().openSession(this.graphName); + } + + protected HgStoreSession getStoreSession(String graphName) { + return HgSessionManager.getInstance().openSession(graphName); + } + + public void scanTable(String tableName) { + log.info("Starting scan table [{}] of graph [{}] ...", tableName, graphName); + HgMetricX hgMetricX = HgMetricX.ofStart(); + HgStoreSession session = getStoreSession(); + int count = 0; + KvCloseableIterator> iterator = + session.scanBatch2(HgScanQuery.tableOf(tableName)); + + long start = System.currentTimeMillis(); + while (iterator.hasNext()) { + HgKvIterator iterator2 = iterator.next(); + while (iterator2.hasNext()) { + + count++; + iterator2.next(); + if (count % (modNumber) == 0) { + log.info("Scanning keys: " + count + " time is " + modNumber * 1000 + / + (System.currentTimeMillis() - + start)); + start = System.currentTimeMillis(); + } + if (count == max) { + break; + } + + } + } + iterator.close(); + + hgMetricX.end(); + log.info("*************************************************"); + log.info("************* Scanning Completed **************"); + log.info("Graph: {}", graphName); + log.info("Table: {}", tableName); + log.info("Keys: {}", count); + log.info("Max: {}", max); + log.info("Waiting: {} seconds.", MetricX.getIteratorWait() / 1000); + log.info("Total: {} seconds.", hgMetricX.past() / 1000); + log.info("Iterator: [{}]", iterator.getClass().getSimpleName()); + log.info("Page: {}", HgStoreClientConfig.of().getNetKvScannerPageSize()); + log.info("*************************************************"); + } + + public void scanHash() { + + String tableName = "g+i"; + HgMetricX hgMetricX = HgMetricX.ofStart(); + String graphName = "/DEFAULT/graphs/hugegraph1/"; + HgStoreSession session = getStoreSession(graphName); + int count = 0; + String query = + "{\"conditions\":[{\"cls\":\"S\",\"el\":{\"key\":\"ID\",\"relation\":\"SCAN\"," + + "\"value\"" + + ":{\"start\":\"61180\",\"end\":\"63365\",\"length\":0}}}]," + + "\"optimizedType\":\"NONE\",\"ids\":[]," + + "\"mustSortByInput\":true,\"resultType\":\"EDGE\",\"offset\":0," + + "\"actualOffset\":0,\"actualStoreOffset\":" + + "0,\"limit\":9223372036854775807,\"capacity\":-1,\"showHidden\":false," + + "\"showDeleting\":false," + + "\"showExpired\":false,\"olap\":false,\"withProperties\":false,\"olapPks\":[]}"; + //HgKvIterator iterator = session.scanIterator(tableName,0,715827883, + // HgKvStore.SCAN_ANY,null); + + //HgKvIterator iterator = session.scanIterator(tableName,61180,63365, 348, null); + //HgKvIterator iterator = session.scanIterator(tableName,0,65535, 348, null); + HgKvIterator iterator = session.scanIterator(tableName); + while (iterator.hasNext()) { + + count++; + //iterator.next(); + // if (count % (modNumber) == 0) { + // log.info("Scanning keys: " + count); + HgCliUtil.println(Arrays.toString(iterator.next().key())); + // } + if (count == max) { + break; + } + + } + + hgMetricX.end(); + log.info("*************************************************"); + log.info("************* Scanning Completed **************"); + log.info("Graph: {}", this.graphName); + log.info("Table: {}", tableName); + log.info("Keys: {}", count); + log.info("Max: {}", max); + log.info("Waiting: {} seconds.", MetricX.getIteratorWait() / 1000); + log.info("Total: {} seconds.", hgMetricX.past() / 1000); + log.info("Iterator: [{}]", iterator.getClass().getSimpleName()); + log.info("Page: {}", HgStoreClientConfig.of().getNetKvScannerPageSize()); + log.info("*************************************************"); + } + + public void scanTable2(String tableName) throws PDException { + // java -jar hg-store-cli-3.6.0-SNAPSHOT.jar -scan 10.45.30.212:8989 "DEFAULT/case_112/g" + // g+ie + PDClient pdClient = storeClient.getPdClient(); + List partitions = pdClient.getPartitions(0, graphName); + HgStoreSession session = storeClient.openSession(graphName); + int count = 0; + byte[] position = null; + HgMetricX hgMetricX = HgMetricX.ofStart(); + for (Metapb.Partition partition : partitions) { + while (true) { + try (HgKvIterator iterator = session.scanIterator(tableName, + (int) (partition.getStartKey()), + (int) (partition.getEndKey()), + HgKvStore.SCAN_HASHCODE, + EMPTY_BYTES)) { + if (position != null) { + iterator.seek(position); + } + while (iterator.hasNext()) { + iterator.next(); + count++; + if (count % 3000 == 0) { + if (iterator.hasNext()) { + iterator.next(); + position = iterator.position(); + System.out.println("count is " + count); + } else { + position = null; + } + break; + } + } + if (!iterator.hasNext()) { + position = null; + break; + } + } + } + } + hgMetricX.end(); + log.info("*************************************************"); + log.info("************* Scanning Completed **************"); + log.info("Graph: {}", graphName); + log.info("Table: {}", tableName); + log.info("Keys: {}", count); + log.info("Total: {} seconds.", hgMetricX.past() / 1000); + log.info("*************************************************"); + } + +} diff --git a/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/util/HgCliUtil.java b/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/util/HgCliUtil.java new file mode 100644 index 000000000..05f8a056a --- /dev/null +++ b/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/util/HgCliUtil.java @@ -0,0 +1,259 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.cli.util; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import org.apache.hugegraph.store.HgKvEntry; +import org.apache.hugegraph.store.HgOwnerKey; +import org.apache.hugegraph.store.HgStoreSession; +import org.apache.hugegraph.store.client.util.HgStoreClientConst; + + +/** + * 2022/2/14 + */ +public class HgCliUtil { + + public final static String TABLE_NAME = "cli-table"; + + + public static Map batchPut(HgStoreSession session, String keyPrefix) { + return batchPut(session, keyPrefix, 100); + } + + public static Map batchPut(HgStoreSession session, String keyPrefix, + int loop) { + return batchPut(session, TABLE_NAME, keyPrefix, loop); + } + + public static Map batchPut(HgStoreSession session, String tableName + , String keyPrefix, int loop) { + return batchPut(session, tableName, keyPrefix, loop, 1, key -> toOwnerKey(key)); + } + + public static Map batchPut(HgStoreSession session, String tableName + , String keyPrefix, int loop, int start) { + return batchPut(session, tableName, keyPrefix, loop, start, key -> toOwnerKey(key)); + } + + public static Map batchPut(HgStoreSession session, String tableName + , String keyPrefix, int loop, Function f) { + return batchPut(session, tableName, keyPrefix, loop, 1, f); + } + + public static Map batchPut(HgStoreSession session, String tableName + , String keyPrefix, int loop, int start, Function f) { + + Map res = new LinkedHashMap<>(); + + int length = String.valueOf(loop).length(); + + session.beginTx(); + for (int i = start; i <= loop; i++) { + + HgOwnerKey key = f.apply(keyPrefix + "-" + padLeftZeros(String.valueOf(i), length)); + + byte[] value = toBytes(keyPrefix + "-V-" + i); + res.put(key, value); + session.put(tableName, key, value); + + if ((i + 1) % 10000 == 0) { + println("commit: " + (i + 1)); + session.commit(); + session.beginTx(); + } + } + if (session.isTx()) { + session.commit(); + } + + return res; + } + + public static void printNum(List list, String title) { + if (list == null) { + return; + } + + println(title + " size: " + list.size()); + } + + public static void println(Iterator iterator) { + if (iterator == null) { + return; + } + while (iterator.hasNext()) { + println(iterator.next()); + } + + } + + public static void printOwner(List list) { + if (list == null) { + return; + } + + for (HgOwnerKey entry : list) { + println(entry); + } + } + + public static void println(List list) { + if (list == null) { + return; + } + + for (HgKvEntry entry : list) { + println(entry); + } + } + + public static void println(List list, int mod) { + if (list == null) { + return; + } + + for (int i = 0; i < list.size(); i++) { + if (i % mod == 0) { + println(list.get(i)); + } + } + } + + public static void println(HgKvEntry kv) { + if (kv == null) { + System.out.println("null"); + return; + } + println("[ " + toStr(kv.key()) + " : " + toStr(kv.value()) + " ]"); + } + + public static void println(HgOwnerKey key) { + if (key == null) { + System.out.println("null"); + return; + } + println("[ " + toInt(key.getOwner()) + " : " + toStr(key.getKey()) + " ]"); + } + + public static void println(String str) { + System.out.println(str); + } + + public static HgOwnerKey toOwnerKey(String key) { + return new HgOwnerKey(getOwner(key), toBytes(key)); + } + + public static HgOwnerKey toOwnerKey(byte[] key) { + return new HgOwnerKey(getOwner(key), key); + } + + private static byte[] getOwner(String key) { + return getOwner(toBytes(key)); + } + + private static byte[] getOwner(byte[] key) { + return toBytes(Arrays.hashCode(key)); + } + + public static HgOwnerKey toAllPartitionKey(String key) { + return HgOwnerKey.of(HgStoreClientConst.ALL_PARTITION_OWNER, toBytes(key)); + } + + public static HgOwnerKey toOwnerKey(String owner, String key) { + return HgOwnerKey.of(toBytes(owner), toBytes(key)); + } + + public static String toStr(byte[] b) { + if (b == null) { + return ""; + } + if (b.length == 0) { + return ""; + } + return new String(b, StandardCharsets.UTF_8); + } + + public static byte[] toBytes(String str) { + if (str == null) { + return null; + } + return str.getBytes(StandardCharsets.UTF_8); + } + + public static byte[] toBytes(long l) { + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.putLong(l); + return buffer.array(); + } + + private static byte[] toBytes(final int i) { + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.putInt(i); + return buffer.array(); + } + + public static long toLong(byte[] bytes) { + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.put(bytes); + buffer.flip();//need flip + return buffer.getLong(); + } + + public static long toInt(byte[] bytes) { + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.put(bytes); + buffer.flip();//need flip + return buffer.getInt(); + } + + public static String padLeftZeros(String str, int n) { + return String.format("%1$" + n + "s", str).replace(' ', '0'); + } + + public static String toSuffix(int num, int length) { + return "-" + padLeftZeros(String.valueOf(num), length); + } + + public static int amountOf(List list) { + if (list == null) { + return 0; + } + return list.size(); + } + + public static int amountOf(Iterator iterator) { + if (iterator == null) { + return 0; + } + int count = 0; + while (iterator.hasNext()) { + iterator.next(); + count++; + } + return count; + } +} diff --git a/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/util/HgMetricX.java b/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/util/HgMetricX.java new file mode 100644 index 000000000..0586aa09c --- /dev/null +++ b/hugegraph-store/hg-store-cli/src/main/java/org/apache/hugegraph/store/cli/util/HgMetricX.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.cli.util; + +/** + * 2022/1/29 + */ +public class HgMetricX { + private long start; + private long end; + + private long waitStart = System.currentTimeMillis(); + private long waitTotal; + + private HgMetricX(long start) { + this.start = start; + } + + public static HgMetricX ofStart() { + return new HgMetricX(System.currentTimeMillis()); + } + + public long start() { + return this.start = System.currentTimeMillis(); + } + + public long end() { + return this.end = System.currentTimeMillis(); + } + + public long past() { + return this.end - this.start; + } + + public long getWaitTotal() { + return this.waitTotal; + } + + public void startWait() { + this.waitStart = System.currentTimeMillis(); + } + + public void appendWait() { + this.waitTotal += System.currentTimeMillis() - waitStart; + } +} diff --git a/hugegraph-store/hg-store-cli/src/main/resources/application.yml b/hugegraph-store/hg-store-cli/src/main/resources/application.yml new file mode 100644 index 000000000..3f0e4be22 --- /dev/null +++ b/hugegraph-store/hg-store-cli/src/main/resources/application.yml @@ -0,0 +1,23 @@ +net: + kv: + scanner: + buffer.size: 10000 + page.size: 20000 + chan.size: 1 + +pd: + address: 10.14.139.71:8686 + +scanner: + #graph: 'default/icbc_hstoregraph/g' + graph: 'DEFAULT/hugegraph/g' + + table: 'g+ie' + #table: 'g+v' + max: 10_000_000 + mod: 1_000_000 + +committer: + graph: 'STORE_CLI' + table: 'benchmark' + amount: 1_000_000 \ No newline at end of file diff --git a/hugegraph-store/hg-store-cli/src/main/resources/hg-store-client.properties b/hugegraph-store/hg-store-cli/src/main/resources/hg-store-client.properties new file mode 100644 index 000000000..f72fbbb82 --- /dev/null +++ b/hugegraph-store/hg-store-cli/src/main/resources/hg-store-client.properties @@ -0,0 +1,23 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to You under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +#grpc.timeout.seconds=10 +#grpc.max.inbound.message.size= +#grpc.max.outbound.message.size= +net.kv.scanner.buffer.size=10000 +net.kv.scanner.page.size=20000 +#Unit:second +net.kv.scanner.have.next.timeout=1000 \ No newline at end of file diff --git a/hugegraph-store/hg-store-cli/src/main/resources/log4j2.xml b/hugegraph-store/hg-store-cli/src/main/resources/log4j2.xml new file mode 100644 index 000000000..050766fb3 --- /dev/null +++ b/hugegraph-store/hg-store-cli/src/main/resources/log4j2.xml @@ -0,0 +1,77 @@ + + + + + + + + logs + hg-store-client + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hugegraph-store/hg-store-client/deploy-snapshot.sh b/hugegraph-store/hg-store-client/deploy-snapshot.sh new file mode 100644 index 000000000..d4f643995 --- /dev/null +++ b/hugegraph-store/hg-store-client/deploy-snapshot.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to You under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +readonly REPO_URL=http://10.14.139.8:8081/artifactory/star-snapshot + +mvn --settings ../settings.xml -Dmaven.test.skip=true -DaltDeploymentRepository=star-snapshot::default::${REPO_URL} clean deploy + diff --git a/hugegraph-store/hg-store-client/pom.xml b/hugegraph-store/hg-store-client/pom.xml new file mode 100644 index 000000000..8397deb23 --- /dev/null +++ b/hugegraph-store/hg-store-client/pom.xml @@ -0,0 +1,107 @@ + + + + + 4.0.0 + + + org.apache.hugegraph + hugegraph-store-root + ${revision} + ../pom.xml + + + hg-store-client + + + 11 + 11 + true + 2.15.0 + 1.18.20 + + + + + org.apache.hugegraph + hg-store-grpc + ${revision} + + + org.apache.hugegraph + hg-store-common + ${revision} + + + org.apache.hugegraph + hg-pd-client + ${revision} + + + org.apache.hugegraph + hg-pd-grpc + ${revision} + + + + org.projectlombok + lombok + ${lombok.version} + + + org.apache.logging.log4j + log4j-slf4j-impl + ${log4j2.version} + + + + junit + junit + 4.13.2 + test + + + + com.google.protobuf + protobuf-java-util + 3.17.2 + + + + commons-io + commons-io + 2.7 + test + + + com.fasterxml.jackson.core + jackson-databind + 2.13.0 + test + + + commons-codec + commons-codec + 1.15 + test + + + + \ No newline at end of file diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgKvEntry.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgKvEntry.java new file mode 100644 index 000000000..ed07167b2 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgKvEntry.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store; + + +public interface HgKvEntry { + byte[] key(); + + byte[] value(); + + default int code() { + return -1; + } +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgKvIterator.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgKvIterator.java new file mode 100644 index 000000000..29842beee --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgKvIterator.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store; + +import java.io.Closeable; +import java.util.Iterator; + +/** + * created on 2021/10/21 + */ +public interface HgKvIterator extends Iterator, HgSeekAble, Closeable { + + byte[] key(); + + byte[] value(); + + @Override + void close(); + +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgKvOrderedIterator.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgKvOrderedIterator.java new file mode 100644 index 000000000..1c656f16f --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgKvOrderedIterator.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store; + +/** + * created on 2022/03/10 + */ +public interface HgKvOrderedIterator extends HgKvIterator, Comparable { + long getSequence(); +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgKvPagingIterator.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgKvPagingIterator.java new file mode 100644 index 000000000..6d43d8f68 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgKvPagingIterator.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store; + +/** + * created on 2021/10/24 + */ +public interface HgKvPagingIterator extends HgKvIterator, HgPageSize { +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgKvStore.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgKvStore.java new file mode 100644 index 000000000..ae79ed828 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgKvStore.java @@ -0,0 +1,125 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store; + +import java.util.List; + +import org.apache.hugegraph.store.client.grpc.KvCloseableIterator; +import org.apache.hugegraph.store.grpc.stream.ScanStreamReq; + +/** + * @version 0.2.0 + */ +public interface HgKvStore { + /** + * CAUTION: THE CONST BELOW MUST KEEP CONSISTENCE TO ScanIterator.Trait. + */ + int SCAN_ANY = 0x80; + int SCAN_PREFIX_BEGIN = 0x01; + int SCAN_PREFIX_END = 0x02; + int SCAN_GT_BEGIN = 0x04; + int SCAN_GTE_BEGIN = 0x0c; + int SCAN_LT_END = 0x10; + int SCAN_LTE_END = 0x30; + int SCAN_KEYONLY = 0x40; + int SCAN_HASHCODE = 0x100; + + boolean put(String table, HgOwnerKey ownerKey, byte[] value); + + /** + * 该版本被store内部使用。向分区写入数据, + * partitionId与key.keyCode必须与pd存储的分区信息保持一致。 + */ + boolean directPut(String table, int partitionId, HgOwnerKey key, byte[] value); + + byte[] get(String table, HgOwnerKey ownerKey); + + boolean clean(int partId); + + boolean delete(String table, HgOwnerKey ownerKey); + + boolean deleteSingle(String table, HgOwnerKey ownerKey); + + boolean deletePrefix(String table, HgOwnerKey prefix); + + boolean deleteRange(String table, HgOwnerKey start, HgOwnerKey end); + + boolean merge(String table, HgOwnerKey key, byte[] value); + + @Deprecated + List batchGetOwner(String table, List keyList); + + HgKvIterator scanIterator(String table); + + HgKvIterator scanIterator(String table, byte[] query); + + HgKvIterator scanIterator(String table, long limit); + + HgKvIterator scanIterator(String table, long limit, byte[] query); + + HgKvIterator scanIterator(String table, HgOwnerKey keyPrefix); + + HgKvIterator scanIterator(String table, HgOwnerKey keyPrefix, long limit); + + HgKvIterator scanIterator(String table, HgOwnerKey keyPrefix, long limit, + byte[] query); + + HgKvIterator scanIterator(String table, HgOwnerKey startKey, HgOwnerKey endKey); + + HgKvIterator scanIterator(String table, HgOwnerKey startKey, HgOwnerKey endKey, + long limit); + + HgKvIterator scanIterator(String table, HgOwnerKey startKey, HgOwnerKey endKey, + long limit, byte[] query); + + HgKvIterator scanIterator(String table, HgOwnerKey startKey, HgOwnerKey endKey, + long limit, int scanType, byte[] query); + + HgKvIterator scanIterator(String table, int codeFrom, int codeTo, int scanType, + byte[] query); + + // HgKvIterator scanIterator(ScanStreamReq scanReq); + + HgKvIterator scanIterator(ScanStreamReq.Builder scanReqBuilder); + + long count(String table); + + boolean truncate(); + + default boolean existsTable(String table) { + return false; + } + + boolean createTable(String table); + + boolean deleteTable(String table); + + boolean dropTable(String table); + + boolean deleteGraph(String graph); + + List> scanBatch(HgScanQuery scanQuery); + + KvCloseableIterator> scanBatch2(HgScanQuery scanQuery); + + KvCloseableIterator> scanBatch3(HgScanQuery scanQuery, + KvCloseableIterator iterator); + + HgKvIterator batchPrefix(String table, List prefixList); +} + diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgOwnerKey.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgOwnerKey.java new file mode 100644 index 000000000..c4d78f48e --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgOwnerKey.java @@ -0,0 +1,149 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store; + +import static org.apache.hugegraph.store.client.util.HgStoreClientConst.EMPTY_BYTES; +import static org.apache.hugegraph.store.client.util.HgStoreClientConst.EMPTY_OWNER_KEY; + +import java.io.Serializable; +import java.util.Arrays; + +import org.apache.hugegraph.store.client.util.HgStoreClientUtil; + +/** + * created on 2021/10/14 + * + * @version 1.3.0 add canceled assert + */ +public class HgOwnerKey implements Serializable { + private final byte[] owner; + private int keyCode = 0;// TODO: Be here OK? + private byte[] key; + private int serialNo; //顺序号,用于批量查询保证返回结果的顺序性 + + /** + * @param owner + * @param key + * @see HgOwnerKey:of(byte[] owner, byte[] key) + */ + @Deprecated + public HgOwnerKey(byte[] owner, byte[] key) { + if (owner == null) { + owner = EMPTY_BYTES; + } + if (key == null) { + key = EMPTY_BYTES; + } + this.owner = owner; + this.key = key; + } + + public HgOwnerKey(int code, byte[] key) { + if (key == null) { + key = EMPTY_BYTES; + } + this.owner = EMPTY_BYTES; + this.key = key; + this.keyCode = code; + } + + public static HgOwnerKey emptyOf() { + return EMPTY_OWNER_KEY; + } + + public static HgOwnerKey newEmpty() { + return HgOwnerKey.of(EMPTY_BYTES, EMPTY_BYTES); + } + + public static HgOwnerKey ownerOf(byte[] owner) { + return new HgOwnerKey(owner, EMPTY_BYTES); + } + + public static HgOwnerKey codeOf(int code) { + return HgOwnerKey.of(EMPTY_BYTES, EMPTY_BYTES).setKeyCode(code); + } + + public static HgOwnerKey of(byte[] owner, byte[] key) { + return new HgOwnerKey(owner, key); + } + + public static HgOwnerKey of(int keyCode, byte[] key) { + return new HgOwnerKey(keyCode, key); + } + + public byte[] getOwner() { + return owner; + } + + public byte[] getKey() { + return key; + } + + public int getKeyCode() { + return keyCode; + } + + public HgOwnerKey setKeyCode(int keyCode) { + this.keyCode = keyCode; + return this; + } + + public HgOwnerKey codeToKey(int keyCode) { + this.keyCode = keyCode; + this.key = HgStoreClientUtil.toIntBytes(keyCode); + return this; + } + + public int getSerialNo() { + return this.serialNo; + } + + public HgOwnerKey setSerialNo(int serialNo) { + this.serialNo = serialNo; + return this; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + HgOwnerKey that = (HgOwnerKey) o; + return Arrays.equals(owner, that.owner) && Arrays.equals(key, that.key); + } + + @Override + public int hashCode() { + int result = Arrays.hashCode(owner); + result = 31 * result + Arrays.hashCode(key); + return result; + } + + @Override + public String toString() { + return "HgOwnerKey{" + + "owner=" + Arrays.toString(owner) + + ", key=" + Arrays.toString(key) + + ", code=" + keyCode + + '}'; + } + +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgPageSize.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgPageSize.java new file mode 100644 index 000000000..25296f459 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgPageSize.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store; + +/** + * Return the amount of records returned by one query in pageable-query. + *

+ * created on 2021/10/24 + */ +public interface HgPageSize { + long getPageSize(); + + default boolean isPageEmpty() { + return false; + } +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgPrivate.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgPrivate.java new file mode 100644 index 000000000..e6fe6170f --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgPrivate.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store; + + +public final class HgPrivate { + + private static final HgPrivate INSTANCE = new HgPrivate(); + + private HgPrivate() { + } + + static HgPrivate of() { + return INSTANCE; + } + +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgScanQuery.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgScanQuery.java new file mode 100644 index 000000000..810155ef2 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgScanQuery.java @@ -0,0 +1,332 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store; + +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import org.apache.hugegraph.store.client.util.HgAssert; +import org.apache.hugegraph.store.grpc.common.ScanOrderType; + +/** + * 2022/3/4 + * + * @version 0.5.0 + */ +public interface HgScanQuery { + static HgScanQuery tableOf(String table) { + return ScanBuilder.tableOf(table).build(); + } + + static HgScanQuery rangeOf(String table, List startList, List endList) { + return ScanBuilder.rangeOf(table, startList, endList).build(); + } + + static HgScanQuery prefixOf(String table, List prefixList) { + return ScanBuilder.prefixOf(table, prefixList).build(); + } + + static HgScanQuery prefixOf(String table, List prefixList, + ScanOrderType orderType) { + return ScanBuilder.prefixOf(table, prefixList).setOrderType(orderType).build(); + } + + static HgScanQuery prefixIteratorOf(String table, Iterator prefixItr) { + return ScanBuilder.prefixIteratorOf(table, prefixItr).build(); + } + + static HgScanQuery prefixIteratorOf(String table, Iterator prefixItr, + ScanOrderType orderType) { + return ScanBuilder.prefixIteratorOf(table, prefixItr).setOrderType(orderType).build(); + } + + String getTable(); + + HgScanQuery.ScanMethod getScanMethod(); + + List getPrefixList(); + + Iterator getPrefixItr(); + + List getStartList(); + + List getEndList(); + + long getLimit(); + + long getPerKeyLimit(); + + long getPerKeyMax(); + + long getSkipDegree(); + + int getScanType(); + + ScanOrderType getOrderType(); + + boolean isOnlyKey(); + + byte[] getQuery(); + + ScanBuilder builder(); + + enum ScanMethod { + ALL, + PREFIX, + RANGE + } + + enum SortType { + UNSORTED, + SORT_BY_EDGE, + SORT_BY_VERTEX + } + + class ScanBuilder { + private final String table; + private final HgScanQuery.ScanMethod sanMethod; + private long limit = Integer.MAX_VALUE; + private long perKeyLimit = Integer.MAX_VALUE; + private long perKeyMax = Integer.MAX_VALUE; + private int scanType; + private ScanOrderType orderType; + + private long skipDegree; + + private boolean onlyKey; + private byte[] query; + private List prefixList; + private List startList; + private List endList; + private Iterator prefixItr; + + ScanBuilder(HgScanQuery.ScanMethod sanMethod, String table) { + this.table = table; + this.sanMethod = sanMethod; + this.orderType = ScanOrderType.ORDER_NONE; + } + + public static ScanBuilder rangeOf(String table, List startList, + List endList) { + HgAssert.isArgumentValid(table, "table"); + HgAssert.isArgumentValid(startList, "startList"); + HgAssert.isArgumentValid(endList, "endList"); + HgAssert.isTrue(startList.size() == endList.size() + , "The size of startList not equals endList's."); + + ScanBuilder res = new ScanBuilder(HgScanQuery.ScanMethod.RANGE, table); + res.startList = startList; + res.endList = endList; + res.scanType = HgKvStore.SCAN_GTE_BEGIN | HgKvStore.SCAN_LTE_END; + return res; + } + + public static ScanBuilder prefixOf(String table, List prefixList) { + HgAssert.isArgumentValid(table, "table"); + HgAssert.isArgumentValid(prefixList, "prefixList"); + + ScanBuilder res = new ScanBuilder(HgScanQuery.ScanMethod.PREFIX, table); + res.prefixList = prefixList; + return res; + + } + + public static ScanBuilder prefixIteratorOf(String table, Iterator prefixItr) { + HgAssert.isArgumentValid(table, "table"); + + ScanBuilder res = new ScanBuilder(HgScanQuery.ScanMethod.PREFIX, table); + res.prefixItr = prefixItr; + return res; + + } + + public static ScanBuilder tableOf(String table) { + HgAssert.isArgumentValid(table, "table"); + + return new ScanBuilder(HgScanQuery.ScanMethod.ALL, table); + } + + public ScanBuilder setLimit(long limit) { + this.limit = limit; + return this; + } + + public ScanBuilder setPerKeyLimit(long limit) { + this.perKeyLimit = limit; + return this; + } + + public ScanBuilder setPerKeyMax(long max) { + this.perKeyMax = max; + return this; + } + + public ScanBuilder setScanType(int scanType) { + this.scanType = scanType; + return this; + } + + public ScanBuilder setOrderType(ScanOrderType orderType) { + this.orderType = orderType; + return this; + } + + public ScanBuilder setQuery(byte[] query) { + this.query = query; + return this; + } + + public ScanBuilder setSkipDegree(long skipDegree) { + this.skipDegree = skipDegree; + return this; + } + + public ScanBuilder setOnlyKey(boolean onlyKey) { + this.onlyKey = onlyKey; + return this; + } + + + public HgScanQuery build() { + return this.new BatchScanQuery(); + } + + private class BatchScanQuery implements HgScanQuery { + + @Override + public String getTable() { + return table; + } + + @Override + public HgScanQuery.ScanMethod getScanMethod() { + return sanMethod; + } + + @Override + public List getPrefixList() { + if (prefixList == null) { + return Collections.EMPTY_LIST; + } else { + return Collections.unmodifiableList(prefixList); + } + } + + @Override + public Iterator getPrefixItr() { + return prefixItr; + } + + @Override + public List getStartList() { + if (startList == null) { + return Collections.EMPTY_LIST; + } else { + return Collections.unmodifiableList(startList); + } + } + + @Override + public List getEndList() { + if (endList == null) { + return Collections.EMPTY_LIST; + } else { + return Collections.unmodifiableList(endList); + } + } + + @Override + public long getLimit() { + return limit; + } + + @Override + public long getPerKeyLimit() { + return perKeyLimit; + } + + @Override + public long getPerKeyMax() { + return perKeyMax; + } + + @Override + public long getSkipDegree() { + return skipDegree; + } + + @Override + public int getScanType() { + return scanType; + } + + @Override + public ScanOrderType getOrderType() { + return orderType; + } + + @Override + public boolean isOnlyKey() { + return onlyKey; + } + + @Override + public byte[] getQuery() { + return query; + } + + @Override + public ScanBuilder builder() { + return ScanBuilder.this; + } + + @Override + public String toString() { + final StringBuffer sb = new StringBuffer("HgScanQuery{"); + sb.append("table='").append(getTable()).append('\''); + sb.append(", scanMethod=").append(getScanMethod()); + sb.append(", prefixList=").append(getPrefixList()); + sb.append(", startList=").append(getStartList()); + sb.append(", endList=").append(getEndList()); + sb.append(", limit=").append(getLimit()); + sb.append(", perKeyLimit=").append(getPerKeyLimit()); + sb.append(", perKeyMax=").append(getPerKeyMax()); + sb.append(", skipDegree=").append(getSkipDegree()); + sb.append(", scanType=").append(getScanType()); + sb.append(", orderType=").append(getOrderType()); + sb.append(", onlyKey=").append(isOnlyKey()); + sb.append(", query="); + if (query == null) { + sb.append("null"); + } else { + sb.append('['); + for (int i = 0; i < query.length; ++i) { + sb.append(i == 0 ? "" : ", ").append(query[i]); + } + sb.append(']'); + } + sb.append('}'); + return sb.toString(); + } + } + + + } +} + diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgSeekAble.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgSeekAble.java new file mode 100644 index 000000000..87cfe0168 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgSeekAble.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store; + +/** + * created on 2022/03/11 + */ +public interface HgSeekAble { + byte[] position(); + + void seek(byte[] position); +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgSessionManager.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgSessionManager.java new file mode 100644 index 000000000..237930187 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgSessionManager.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store; + +import javax.annotation.concurrent.ThreadSafe; + +import org.apache.hugegraph.store.client.HgStoreSessionProvider; + +/** + * Maintain HgStoreSession instances. + * // TODO: Holding more than one HgSessionManager is available,if you want to connect multi + * HgStore-clusters. + */ + +@ThreadSafe +public final class HgSessionManager { + private final static HgSessionManager INSTANCE = new HgSessionManager(); + private final HgSessionProvider sessionProvider; + + private HgSessionManager() { + // TODO: constructed by SPI + this.sessionProvider = new HgStoreSessionProvider(); + } + + + public static HgSessionManager getInstance() { + return INSTANCE; + } + + /** + * Retrieve or create a HgStoreSession. + * + * @param graphName + * @return + */ + public HgStoreSession openSession(String graphName) { + return this.sessionProvider.createSession(graphName); + } + +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgSessionProvider.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgSessionProvider.java new file mode 100644 index 000000000..75cbbfd20 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgSessionProvider.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store; + +import javax.annotation.concurrent.ThreadSafe; + +/** + * created on 2021/10/12 + */ +@ThreadSafe +public interface HgSessionProvider { + HgStoreSession createSession(String graphName); +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgStoreClient.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgStoreClient.java new file mode 100644 index 000000000..ca139b850 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgStoreClient.java @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store; + +import javax.annotation.concurrent.ThreadSafe; + +import org.apache.hugegraph.pd.client.PDClient; +import org.apache.hugegraph.pd.client.PDConfig; +import org.apache.hugegraph.store.client.HgStoreNodeManager; +import org.apache.hugegraph.store.client.HgStoreNodePartitionerImpl; +import org.apache.hugegraph.store.client.HgStoreSessionProvider; + +/** + * Maintain HgStoreSession instances. + * // TODO: Holding more than one HgSessionManager is available,if you want to connect multi + * HgStore-clusters. + */ + +@ThreadSafe +public final class HgStoreClient { + private final HgSessionProvider sessionProvider; + private PDClient pdClient; + + public HgStoreClient() { + this.sessionProvider = new HgStoreSessionProvider(); + } + + public HgStoreClient(PDConfig config) { + this.sessionProvider = new HgStoreSessionProvider(); + pdClient = PDClient.create(config); + setPdClient(pdClient); + } + + public HgStoreClient(PDClient pdClient) { + this.sessionProvider = new HgStoreSessionProvider(); + setPdClient(pdClient); + } + + public static HgStoreClient create(PDConfig config) { + return new HgStoreClient(config); + } + + public static HgStoreClient create(PDClient pdClient) { + return new HgStoreClient(pdClient); + } + + public static HgStoreClient create() { + return new HgStoreClient(); + } + + public void setPDConfig(PDConfig config) { + pdClient = PDClient.create(config); + setPdClient(pdClient); + } + + /** + * Retrieve or create a HgStoreSession. + * + * @param graphName + * @return + */ + public HgStoreSession openSession(String graphName) { + return this.sessionProvider.createSession(graphName); + } + + public PDClient getPdClient() { + return pdClient; + } + + public void setPdClient(PDClient client) { + this.pdClient = client; + HgStoreNodeManager nodeManager = + HgStoreNodeManager.getInstance(); + + HgStoreNodePartitionerImpl p = new HgStoreNodePartitionerImpl(pdClient, nodeManager); + nodeManager.setNodeProvider(p); + nodeManager.setNodePartitioner(p); + nodeManager.setNodeNotifier(p); + } + +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgStoreSession.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgStoreSession.java new file mode 100644 index 000000000..b3599dedd --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgStoreSession.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store; + +import org.apache.hugegraph.store.client.type.HgStoreClientException; + + +public interface HgStoreSession extends HgKvStore { + + void beginTx(); + + /** + * @throws IllegalStateException when the tx hasn't been beginning. + * @throws HgStoreClientException when failed to commit . + */ + void commit(); + + /** + * @throws IllegalStateException when the tx hasn't been beginning. + * @throws HgStoreClientException when failed to rollback. + */ + void rollback(); + + boolean isTx(); +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgTkvEntry.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgTkvEntry.java new file mode 100644 index 000000000..50220abe3 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgTkvEntry.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store; + + +public interface HgTkvEntry { + String table(); + + byte[] key(); + + byte[] value(); +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgTokvEntry.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgTokvEntry.java new file mode 100644 index 000000000..3c2c765af --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/HgTokvEntry.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store; + + +public interface HgTokvEntry { + String table(); + + HgOwnerKey ownerKey(); + + byte[] value(); +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgNodePartition.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgNodePartition.java new file mode 100644 index 000000000..d060f2c0c --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgNodePartition.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client; + +import java.util.Objects; + +/** + * Immutable Object Pattern + *

+ * created on 2021/10/26 + */ +public final class HgNodePartition { + private final Long nodeId; + //当前key的hashcode + private final Integer keyCode; + + //分区的开始结束范围 + private final Integer startKey; + private final Integer endKey; + private int hash = -1; + + HgNodePartition(Long nodeId, Integer keyCode) { + this.nodeId = nodeId; + this.keyCode = keyCode; + this.startKey = this.endKey = keyCode; + } + + HgNodePartition(Long nodeId, Integer keyCode, Integer startKey, Integer endKey) { + this.nodeId = nodeId; + this.keyCode = keyCode; + this.startKey = startKey; + this.endKey = endKey; + } + + public static HgNodePartition of(Long nodeId, Integer keyCode) { + return new HgNodePartition(nodeId, keyCode); + } + + public static HgNodePartition of(Long nodeId, Integer keyCode, Integer startKey, + Integer endKey) { + return new HgNodePartition(nodeId, keyCode, startKey, endKey); + } + + public Long getNodeId() { + return nodeId; + } + + public Integer getKeyCode() { + return keyCode; + } + + public Integer getStartKey() { + return startKey; + } + + public Integer getEndKey() { + return endKey; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + HgNodePartition that = (HgNodePartition) o; + return Objects.equals(nodeId, that.nodeId) && Objects.equals(keyCode, that.keyCode); + } + + @Override + public int hashCode() { + if (this.hash == -1) { + this.hash = Objects.hash(nodeId, keyCode); + } + return this.hash; + } + + @Override + public String toString() { + return "HgNodePartition{" + + "nodeId=" + nodeId + + ", partitionId=" + keyCode + + '}'; + } +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgNodePartitionerBuilder.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgNodePartitionerBuilder.java new file mode 100644 index 000000000..d0042e9bb --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgNodePartitionerBuilder.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client; + +import static org.apache.hugegraph.store.client.util.HgAssert.isFalse; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import javax.annotation.concurrent.NotThreadSafe; + +/** + * created on 2021/10/26 + * + * @version 1.0.0 + */ +@NotThreadSafe +public final class HgNodePartitionerBuilder { + + private Set partitions = null; + + static HgNodePartitionerBuilder resetAndGet() { + return new HgNodePartitionerBuilder(); + } + + /** + * @param nodeId + * @param keyCode + * @return + * @see HgNodePartitionerBuilder:setPartitions(Set partitions) + */ + @Deprecated + public HgNodePartitionerBuilder add(Long nodeId, Integer keyCode) { + isFalse(nodeId == null, "The argument is invalid: nodeId"); + isFalse(keyCode == null, "The argument is invalid: keyCode"); + + if (this.partitions == null) { + this.partitions = new HashSet<>(16, 1); + } + + this.partitions.add(HgNodePartition.of(nodeId, keyCode)); + return this; + } + + Collection getPartitions() { + return this.partitions; + } + + public void setPartitions(Set partitions) { + isFalse(partitions == null, "The argument is invalid: partitions"); + this.partitions = partitions; + } + +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgPrivate.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgPrivate.java new file mode 100644 index 000000000..597f13fb9 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgPrivate.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client; + +/** + * created on 2021/10/26 + */ +public class HgPrivate { + private final static HgPrivate instance = new HgPrivate(); + + private HgPrivate() { + + } + + static HgPrivate getInstance() { + return instance; + } + +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreNode.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreNode.java new file mode 100644 index 000000000..da9d3aa3d --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreNode.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client; + +import org.apache.hugegraph.store.HgStoreSession; + +/** + * created on 2021/10/11 + * + * @version 0.2.0 + */ +public interface HgStoreNode { + + /** + * Return boolean value of being online or not + * + * @return + */ + default boolean isHealthy() { + return true; + } + + /** + * Return the unique ID of store-node. + * + * @return + */ + Long getNodeId(); + + /** + * A string value concatenated by host and port: "host:port" + * + * @return + */ + String getAddress(); + + /** + * Return a new HgStoreSession instance, that is not Thread safe. + * Return null when the node is not in charge of the graph that was passed from argument. + * + * @return + */ + HgStoreSession openSession(String graphName); + +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreNodeBuilder.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreNodeBuilder.java new file mode 100644 index 000000000..46833d82a --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreNodeBuilder.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client; + +/** + * created on 2021/10/11 + */ +public interface HgStoreNodeBuilder { + + HgStoreNodeBuilder setNodeId(Long nodeId); + + HgStoreNodeBuilder setAddress(String address); + + /** + * To build a HgStoreNode instance. + * + * @return + */ + HgStoreNode build(); + +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreNodeCandidates.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreNodeCandidates.java new file mode 100644 index 000000000..54e7e5950 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreNodeCandidates.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client; + +import java.util.List; + +/** + * created on 2021/10/12 + */ +public final class HgStoreNodeCandidates { + + List nodeList; + + HgStoreNodeCandidates(List nodeList) { + this.nodeList = nodeList; + } + + public int size() { + return this.nodeList.size(); + } + + public HgStoreNode getNode(int index) { + return this.nodeList.get(index); + } + +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreNodeManager.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreNodeManager.java new file mode 100644 index 000000000..1f0abb43e --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreNodeManager.java @@ -0,0 +1,264 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import javax.annotation.concurrent.ThreadSafe; + +import org.apache.hugegraph.store.client.grpc.GrpcStoreNodeBuilder; +import org.apache.hugegraph.store.client.type.HgNodeStatus; +import org.apache.hugegraph.store.client.type.HgStoreClientException; +import org.apache.hugegraph.store.client.util.HgAssert; +import org.apache.hugegraph.store.client.util.HgStoreClientConst; + +import lombok.extern.slf4j.Slf4j; + +/** + * // TODO: Mapping to Store-Node-Cluster, one to one. + *

+ * created on 2021/10/11 + * + * @version 0.2.0 + */ +@ThreadSafe +@Slf4j +public final class HgStoreNodeManager { + private final static Set CLUSTER_ID_SET = new HashSet<>(); + private final static HgStoreNodeManager instance = new HgStoreNodeManager(); + + private final String clusterId; + private final Map addressMap = new ConcurrentHashMap<>(); + private final Map nodeIdMap = new ConcurrentHashMap<>(); + private final Map> graphNodesMap = new ConcurrentHashMap<>(); + + private HgStoreNodeProvider nodeProvider; + private HgStoreNodePartitioner nodePartitioner; + private HgStoreNodeNotifier nodeNotifier; + + private HgStoreNodeManager() { + this.clusterId = HgStoreClientConst.DEFAULT_NODE_CLUSTER_ID; + } + + private HgStoreNodeManager(String clusterId) { + synchronized (CLUSTER_ID_SET) { + if (CLUSTER_ID_SET.contains(clusterId)) { + throw new RuntimeException("The cluster [" + clusterId + "] has been existing."); + } + CLUSTER_ID_SET.add(clusterId); + this.clusterId = clusterId; + } + } + + public static HgStoreNodeManager getInstance() { + return instance; + } + + /** + * Return the HgStoreNodeBuilder + * + * @return + */ + public HgStoreNodeBuilder getNodeBuilder() { + // TODO: Constructed by a provider that retrieved by SPI + return new GrpcStoreNodeBuilder(this, HgPrivate.getInstance()); + } + + /** + * Return an instance of HgStoreNode whose ID is matched to the argument. + * + * @param nodeId + * @return null when none of instance is matched to the argument,or argument is invalid. + */ + public HgStoreNode getStoreNode(Long nodeId) { + if (nodeId == null) { + return null; + } + return this.nodeIdMap.get(nodeId); + } + + /** + * Apply a HgStoreNode instance with graph-name and node-id. + * CAUTION: + * It won't work when user haven't set a HgStoreNodeProvider via setNodeProvider method. + * + * @param graphName + * @param nodeId + * @return + */ + HgStoreNode applyNode(String graphName, Long nodeId) { + HgStoreNode node = this.nodeIdMap.get(nodeId); + + if (node != null) { + return node; + } + + if (this.nodeProvider == null) { + return null; + } + + node = this.nodeProvider.apply(graphName, nodeId); + + if (node == null) { + + log.warn("Failed to apply a HgStoreNode instance form the nodeProvider [ " + + this.nodeProvider.getClass().getName() + " ]."); + notifying(graphName, nodeId, HgNodeStatus.NOT_EXIST); + return null; + } + + this.addNode(graphName, node); + + return node; + } + + private void notifying(String graphName, Long nodeId, HgNodeStatus status) { + if (this.nodeNotifier != null) { + try { + this.nodeNotifier.notice(graphName, HgStoreNotice.of(nodeId, status)); + } catch (Throwable t) { + log.error("Failed to invoke " + this.nodeNotifier.getClass().getSimpleName() + + ":notice(" + nodeId + "," + status + ")", t); + } + } + } + + /** + * @param graphName + * @param notice + * @return null: when there is no HgStoreNodeNotifier in the nodeManager; + * @throws HgStoreClientException + */ + public Integer notifying(String graphName, HgStoreNotice notice) { + + if (this.nodeNotifier != null) { + + synchronized (Thread.currentThread()) { + try { + return this.nodeNotifier.notice(graphName, notice); + } catch (Throwable t) { + String msg = + "Failed to invoke " + this.nodeNotifier.getClass().getSimpleName() + + ", notice: [ " + notice + " ]"; + log.error(msg, t); + throw new HgStoreClientException(msg); + } + } + + } + + return null; + } + + /** + * Return a collection of HgStoreNode who is in charge of the graph passed in the argument. + * + * @param graphName + * @return null when none matched to argument or any argument is invalid. + */ + public List getStoreNodes(String graphName) { + if (HgAssert.isInvalid(graphName)) { + return null; + } + + return this.graphNodesMap.get(graphName); + } + + /** + * Adding a new Store-Node, return the argument's value if the host+port was not existing, + * otherwise return the HgStoreNode-instance added early. + * + * @param storeNode + * @return + * @throws IllegalArgumentException when any argument is invalid. + */ + public HgStoreNode addNode(HgStoreNode storeNode) { + HgAssert.isFalse(storeNode == null, "the argument: storeNode is null."); + + Long nodeId = storeNode.getNodeId(); + + HgStoreNode node = null; + + synchronized (this.nodeIdMap) { + node = this.addressMap.get(nodeId); + if (node == null) { + node = storeNode; + this.nodeIdMap.put(nodeId, node); + this.addressMap.put(storeNode.getAddress(), node); + } + } + + return node; + } + + /** + * @param graphName + * @param storeNode + * @return + * @throws IllegalArgumentException when any argument is invalid. + */ + public HgStoreNode addNode(String graphName, HgStoreNode storeNode) { + HgAssert.isFalse(HgAssert.isInvalid(graphName), "the argument is invalid: graphName"); + HgStoreNode node = this.addNode(storeNode); + + List nodes = null; + + synchronized (this.graphNodesMap) { + nodes = this.graphNodesMap.get(graphName); + if (nodes == null) { + nodes = new ArrayList<>(); + this.graphNodesMap.put(graphName, nodes); + } + nodes.add(node); + } + + + return node; + } + + public HgStoreNodePartitioner getNodePartitioner() { + return nodePartitioner; + } + + public HgStoreNodeManager setNodePartitioner(HgStoreNodePartitioner nodePartitioner) { + HgAssert.isFalse(nodePartitioner == null, "the argument is invalid: nodePartitioner"); + this.nodePartitioner = nodePartitioner; + return this; + } + + public HgStoreNodeNotifier getNodeNotifier() { + return nodeNotifier; + } + + public HgStoreNodeManager setNodeNotifier(HgStoreNodeNotifier nodeNotifier) { + HgAssert.isFalse(nodeNotifier == null, "the argument is invalid: nodeNotifier"); + this.nodeNotifier = nodeNotifier; + return this; + } + + public HgStoreNodeManager setNodeProvider(HgStoreNodeProvider nodeProvider) { + this.nodeProvider = nodeProvider; + return this; + } + +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreNodeNotifier.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreNodeNotifier.java new file mode 100644 index 000000000..91d3c6e3c --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreNodeNotifier.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client; + +/** + * created on 2021/10/12 + * + * @version 1.0.0 + */ +public interface HgStoreNodeNotifier { + + /** + * It will be invoked by NodeManager, when some exception or issue was happened. + * + * @param graphName + * @param storeNotice + * @return return 0 please, for no matter what. + */ + int notice(String graphName, HgStoreNotice storeNotice); + +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreNodePartitioner.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreNodePartitioner.java new file mode 100644 index 000000000..23ac8f95d --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreNodePartitioner.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client; + +import org.apache.hugegraph.store.client.util.HgStoreClientConst; + +/** + * created on 2021/10/12 + * + * @version 1.0.0 + */ +public interface HgStoreNodePartitioner { + + /** + * The partition algorithm implementation, that specialized by user. + * + * @param builder The builder of HgNodePartitionerBuilder. It's supposed to be invoked + * directly by user. + * e.g. builder.add(nodeId,address,partitionId); + * @param graphName + * @param startKey + * @param endKey + * @return status: + *

    + *
  • 0: The partitioner is OK.
  • + *
  • 10: The partitioner is not work.
  • + *
+ */ + int partition(HgNodePartitionerBuilder builder, String graphName, byte[] startKey, + byte[] endKey); + + /** + * @param builder + * @param graphName + * @param startCode hash code + * @param endCode hash code + * @return + */ + default int partition(HgNodePartitionerBuilder builder, String graphName, int startCode, + int endCode) { + return this.partition(builder, graphName + , HgStoreClientConst.ALL_PARTITION_OWNER + , HgStoreClientConst.ALL_PARTITION_OWNER); + } + + default int partition(HgNodePartitionerBuilder builder, String graphName, int partitionId) { + return this.partition(builder, graphName + , HgStoreClientConst.ALL_PARTITION_OWNER + , HgStoreClientConst.ALL_PARTITION_OWNER); + } +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreNodePartitionerImpl.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreNodePartitionerImpl.java new file mode 100644 index 000000000..964119d1e --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreNodePartitionerImpl.java @@ -0,0 +1,200 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; + +import org.apache.hugegraph.pd.client.PDClient; +import org.apache.hugegraph.pd.common.KVPair; +import org.apache.hugegraph.pd.common.PDException; +import org.apache.hugegraph.pd.common.PartitionUtils; +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.store.client.type.HgNodeStatus; +import org.apache.hugegraph.store.client.util.HgStoreClientConst; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class HgStoreNodePartitionerImpl implements HgStoreNodePartitioner, + HgStoreNodeProvider, + HgStoreNodeNotifier { + private PDClient pdClient; + private HgStoreNodeManager nodeManager; + + protected HgStoreNodePartitionerImpl() { + } + + public HgStoreNodePartitionerImpl(PDClient pdClient, HgStoreNodeManager nodeManager) { + this.pdClient = pdClient; + this.nodeManager = nodeManager; + } + + /** + * 查询分区信息,结果通过HgNodePartitionerBuilder返回 + */ + @Override + public int partition(HgNodePartitionerBuilder builder, String graphName, + byte[] startKey, byte[] endKey) { + try { + HashSet partitions = null; + if (HgStoreClientConst.ALL_PARTITION_OWNER == startKey) { + List stores = pdClient.getActiveStores(graphName); + partitions = new HashSet<>(stores.size()); + for (Metapb.Store store : stores) { + partitions.add(HgNodePartition.of(store.getId(), -1)); + } + + } else if (endKey == HgStoreClientConst.EMPTY_BYTES + || startKey == endKey || Arrays.equals(startKey, endKey)) { + KVPair partShard = + pdClient.getPartition(graphName, startKey); + Metapb.Shard leader = partShard.getValue(); + partitions = new HashSet<>(); + partitions.add(HgNodePartition.of(leader.getStoreId(), + pdClient.keyToCode(graphName, startKey))); + } else { + log.warn( + "StartOwnerkey is not equal to endOwnerkey, which is meaningless!!, It is" + + " a error!!"); + List stores = pdClient.getActiveStores(graphName); + partitions = new HashSet<>(stores.size()); + for (Metapb.Store store : stores) { + partitions.add(HgNodePartition.of(store.getId(), -1)); + } + } + builder.setPartitions(partitions); + } catch (PDException e) { + log.error("An error occurred while getting partition information :{}", e.getMessage()); + throw new RuntimeException(e.getMessage(), e); + } + return 0; + } + + @Override + public int partition(HgNodePartitionerBuilder builder, String graphName, + int startKey, int endKey) { + try { + HashSet partitions = new HashSet<>(); + Metapb.Partition partition = null; + while ((partition == null || partition.getEndKey() < endKey) + && startKey < PartitionUtils.MAX_VALUE) { + KVPair partShard = + pdClient.getPartitionByCode(graphName, startKey); + if (partShard != null) { + partition = partShard.getKey(); + Metapb.Shard leader = partShard.getValue(); + partitions.add(HgNodePartition.of(leader.getStoreId(), startKey, + (int) partition.getStartKey(), + (int) partition.getEndKey())); + startKey = (int) partition.getEndKey(); + } else { + break; + } + } + builder.setPartitions(partitions); + } catch (PDException e) { + log.error("An error occurred while getting partition information :{}", e.getMessage()); + throw new RuntimeException(e.getMessage(), e); + } + return 0; + } + + + @Override + public int partition(HgNodePartitionerBuilder builder, String graphName, + int partitionId) { + try { + HashSet partitions = new HashSet<>(); + Metapb.Partition partition = null; + + KVPair partShard = + pdClient.getPartitionById(graphName, partitionId); + if (partShard != null) { + partition = partShard.getKey(); + Metapb.Shard leader = partShard.getValue(); + partitions.add( + HgNodePartition.of(leader.getStoreId(), (int) partition.getStartKey())); + } + builder.setPartitions(partitions); + } catch (PDException e) { + log.error("An error occurred while getting partition information :{}", e.getMessage()); + throw new RuntimeException(e.getMessage(), e); + } + return 0; + } + + /** + * 查询hgstore信息 + * + * @return hgstore + */ + @Override + public HgStoreNode apply(String graphName, Long nodeId) { + try { + Metapb.Store store = pdClient.getStore(nodeId); + return nodeManager.getNodeBuilder().setNodeId(store.getId()) + .setAddress(store.getAddress()).build(); + } catch (PDException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + /** + * 通知更新缓存 + */ + @Override + public int notice(String graphName, HgStoreNotice storeNotice) { + log.warn(storeNotice.toString()); + if (storeNotice.getPartitionLeaders() != null) { + storeNotice.getPartitionLeaders().forEach((partId, leader) -> { + pdClient.updatePartitionLeader(graphName, partId, leader); + log.warn("updatePartitionLeader:{}-{}-{}", + graphName, partId, leader); + }); + } + if (storeNotice.getPartitionIds() != null) { + storeNotice.getPartitionIds().forEach(partId -> { + pdClient.invalidPartitionCache(graphName, partId); + }); + } + if (!storeNotice.getNodeStatus().equals( + HgNodeStatus.PARTITION_COMMON_FAULT) + && !storeNotice.getNodeStatus().equals( + HgNodeStatus.NOT_PARTITION_LEADER)) { + pdClient.invalidPartitionCache(); + log.warn("invalidPartitionCache:{} ", storeNotice.getNodeStatus()); + } + return 0; + } + + public Metapb.Graph delGraph(String graphName) { + try { + return pdClient.delGraph(graphName); + } catch (PDException e) { + log.error("delGraph {} exception, {}", graphName, e.getMessage()); + } + return null; + } + + public void setNodeManager(HgStoreNodeManager nodeManager) { + this.nodeManager = nodeManager; + } +} + diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreNodeProvider.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreNodeProvider.java new file mode 100644 index 000000000..46d78fd6c --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreNodeProvider.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client; + +/** + * created on 2021/10/27 + */ +public interface HgStoreNodeProvider { + + /** + * Applying a new HgStoreNode instance + * + * @param graphName + * @param nodeId + * @return + */ + HgStoreNode apply(String graphName, Long nodeId); + +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreNodeSession.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreNodeSession.java new file mode 100644 index 000000000..73872dfc5 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreNodeSession.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client; + +import org.apache.hugegraph.store.HgStoreSession; + +/** + * created on 2021/10/11 + * + * @version 0.1.0 + */ +public interface HgStoreNodeSession extends HgStoreSession { + + /** + * Return the name of graph. + * + * @return + */ + String getGraphName(); + + /** + * Return an instance of HgStoreNode, which provided the connection of Store-Node machine. + * + * @return + */ + HgStoreNode getStoreNode(); + + +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreNotice.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreNotice.java new file mode 100644 index 000000000..6fb4b699a --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreNotice.java @@ -0,0 +1,99 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client; + +import java.util.List; +import java.util.Map; + +import javax.annotation.concurrent.NotThreadSafe; + +import org.apache.hugegraph.store.client.type.HgNodeStatus; +import org.apache.hugegraph.store.client.util.HgAssert; + +/** + * 2021/11/16 + */ +@NotThreadSafe +public class HgStoreNotice { + private final Long nodeId; + private final HgNodeStatus nodeStatus; + private final String message; + private Map partitionLeaders; + private List partitionIds; + + private HgStoreNotice(Long nodeId, HgNodeStatus nodeStatus, String message) { + this.nodeId = nodeId; + this.nodeStatus = nodeStatus; + this.message = message; + } + + public static HgStoreNotice of(Long nodeId, HgNodeStatus nodeStatus) { + HgAssert.isArgumentNotNull(nodeId, "nodeId"); + HgAssert.isArgumentNotNull(nodeStatus, "nodeStatus"); + return new HgStoreNotice(nodeId, nodeStatus, ""); + } + + public static HgStoreNotice of(Long nodeId, HgNodeStatus nodeStatus, String message) { + HgAssert.isArgumentNotNull(nodeId, "nodeId"); + HgAssert.isArgumentNotNull(nodeStatus, "nodeStatus"); + HgAssert.isArgumentNotNull(message, "message"); + + return new HgStoreNotice(nodeId, nodeStatus, message); + } + + public Long getNodeId() { + return nodeId; + } + + public HgNodeStatus getNodeStatus() { + return nodeStatus; + } + + public String getMessage() { + return message; + } + + public Map getPartitionLeaders() { + return partitionLeaders; + } + + public HgStoreNotice setPartitionLeaders(Map partitionLeaders) { + this.partitionLeaders = partitionLeaders; + return this; + } + + public List getPartitionIds() { + return partitionIds; + } + + public HgStoreNotice setPartitionIds(List partitionIds) { + this.partitionIds = partitionIds; + return this; + } + + @Override + public String toString() { + return "HgStoreNotice{" + + "nodeId=" + nodeId + + ", nodeStatus=" + nodeStatus + + ", message='" + message + '\'' + + ", partitionLeaders=" + partitionLeaders + + ", partitionIds=" + partitionIds + + '}'; + } +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreService.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreService.java new file mode 100644 index 000000000..02b2d2931 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreService.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client; + + +public class HgStoreService { + private static final HgStoreService instance = new HgStoreService(); + + private HgStoreService() { + } + + static HgStoreService of() { + return instance; + } + + +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreSessionProvider.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreSessionProvider.java new file mode 100644 index 000000000..26785083e --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgStoreSessionProvider.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client; + +import javax.annotation.concurrent.ThreadSafe; + +import org.apache.hugegraph.store.HgSessionProvider; +import org.apache.hugegraph.store.HgStoreSession; + +/** + * created on 2021/10/12 + */ +@ThreadSafe +public class HgStoreSessionProvider implements HgSessionProvider { + private final MultiNodeSessionFactory sessionFactory = MultiNodeSessionFactory.getInstance(); + + @Override + public HgStoreSession createSession(String graphName) { + return this.sessionFactory.createStoreSession(graphName); + } +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgTkvEntryImpl.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgTkvEntryImpl.java new file mode 100644 index 000000000..5541b7031 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgTkvEntryImpl.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client; + +import java.util.Arrays; +import java.util.Objects; + +import org.apache.hugegraph.store.HgTkvEntry; + +/** + * created on 2021/10/14 + */ +class HgTkvEntryImpl implements HgTkvEntry { + private final String table; + private final byte[] key; + private final byte[] value; + + HgTkvEntryImpl(String table, byte[] key, byte[] value) { + this.table = table; + this.key = key; + this.value = value; + } + + @Override + public String table() { + return this.table; + } + + @Override + public byte[] key() { + return this.key; + } + + @Override + public byte[] value() { + return this.value; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + HgTkvEntryImpl that = (HgTkvEntryImpl) o; + return Objects.equals(table, that.table) && Arrays.equals(key, that.key) && + Arrays.equals(value, that.value); + } + + @Override + public int hashCode() { + int result = Objects.hash(table); + result = 31 * result + Arrays.hashCode(key); + result = 31 * result + Arrays.hashCode(value); + return result; + } + + @Override + public String toString() { + return "HgTkvEntryImpl{" + + "table='" + table + '\'' + + ", key=" + Arrays.toString(key) + + ", value=" + Arrays.toString(value) + + '}'; + } +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgTokvEntryImpl.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgTokvEntryImpl.java new file mode 100644 index 000000000..a948bc163 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/HgTokvEntryImpl.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client; + +import java.util.Arrays; +import java.util.Objects; + +import org.apache.hugegraph.store.HgOwnerKey; +import org.apache.hugegraph.store.HgTokvEntry; + +/** + * created on 2021/10/14 + */ +class HgTokvEntryImpl implements HgTokvEntry { + + private final String table; + private final HgOwnerKey ownerKey; + private final byte[] value; + + HgTokvEntryImpl(String table, HgOwnerKey ownerKey, byte[] value) { + this.table = table; + this.ownerKey = ownerKey; + this.value = value; + } + + @Override + public String table() { + return this.table; + } + + @Override + public HgOwnerKey ownerKey() { + return this.ownerKey; + } + + @Override + public byte[] value() { + return this.value; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + HgTokvEntryImpl that = (HgTokvEntryImpl) o; + return Objects.equals(table, that.table) && Objects.equals(ownerKey, that.ownerKey) && + Arrays.equals(value, that.value); + } + + @Override + public int hashCode() { + int result = Objects.hash(table, ownerKey); + result = 31 * result + Arrays.hashCode(value); + return result; + } + + @Override + public String toString() { + return "HgTokvEntryImpl{" + + "table='" + table + '\'' + + ", okv=" + ownerKey + + ", value=" + Arrays.toString(value) + + '}'; + } +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/MultiNodeSessionFactory.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/MultiNodeSessionFactory.java new file mode 100644 index 000000000..7237e096f --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/MultiNodeSessionFactory.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client; + +import javax.annotation.concurrent.ThreadSafe; + +import org.apache.hugegraph.store.HgStoreSession; + +/** + * created on 2021/10/12 + */ +@ThreadSafe +public final class MultiNodeSessionFactory { + // TODO multi-instance ? + private final static MultiNodeSessionFactory INSTANCE = new MultiNodeSessionFactory(); + // TODO multi-instance ? + private final HgStoreNodeManager nodeManager = HgStoreNodeManager.getInstance(); + // TODO: to be a chain assigned to each graph + //private HgStoreNodeDispatcher storeNodeDispatcher; + + private MultiNodeSessionFactory() { + } + + static MultiNodeSessionFactory getInstance() { + return INSTANCE; + } + + HgStoreSession createStoreSession(String graphName) { + return buildProxy(graphName); + } + + private HgStoreSession buildProxy(String graphName) { + //return new MultiNodeSessionProxy(graphName, nodeManager, storeNodeDispatcher); + //return new NodePartitionSessionProxy(graphName,nodeManager); + //return new NodeRetrySessionProxy(graphName,nodeManager); + return new NodeTxSessionProxy(graphName, nodeManager); + } +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/NodeTkv.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/NodeTkv.java new file mode 100644 index 000000000..00c3faade --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/NodeTkv.java @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client; + +import java.util.Objects; + +import javax.annotation.concurrent.ThreadSafe; + +import org.apache.hugegraph.store.HgOwnerKey; +import org.apache.hugegraph.store.HgStoreSession; + +/** + * created on 2021/10/26 + */ +@ThreadSafe +class NodeTkv { + private final HgNodePartition nodePartition; + private final String table; + private final HgOwnerKey key; + private final HgOwnerKey endKey; + private HgStoreSession session; + + NodeTkv(HgNodePartition nodePartition, String table, HgOwnerKey key) { + this.nodePartition = nodePartition; + this.table = table; + this.key = key; + this.endKey = key; + this.key.setKeyCode(this.nodePartition.getKeyCode()); + } + + NodeTkv(HgNodePartition nodePartition, String table, HgOwnerKey key, int keyCode) { + this.nodePartition = nodePartition; + this.table = table; + this.key = key; + this.endKey = key; + + this.key.setKeyCode(keyCode); + } + + NodeTkv(HgNodePartition nodePartition, String table, HgOwnerKey startKey, + HgOwnerKey endKey) { + this.nodePartition = nodePartition; + this.table = table; + this.key = startKey; + this.endKey = endKey; + this.key.setKeyCode(nodePartition.getStartKey()); + this.endKey.setKeyCode(nodePartition.getEndKey()); + } + + public Long getNodeId() { + return this.nodePartition.getNodeId(); + } + + public String getTable() { + return table; + } + + public HgOwnerKey getKey() { + return key; + } + + public HgOwnerKey getEndKey() { + return endKey; + } + + public NodeTkv setKeyCode(int code) { + this.key.setKeyCode(code); + return this; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + NodeTkv nptKv = (NodeTkv) o; + return Objects.equals(nodePartition, nptKv.nodePartition) && + Objects.equals(table, nptKv.table) + && Objects.equals(key, nptKv.key) + && Objects.equals(endKey, nptKv.endKey); + } + + @Override + public int hashCode() { + int result = Objects.hash(nodePartition, table, key, endKey); + return result; + } + + @Override + public String toString() { + return "NptKv{" + + "nodePartition=" + nodePartition + + ", table='" + table + '\'' + + ", key=" + key + + ", endKey=" + endKey + + '}'; + } + + public HgStoreSession getSession() { + return session; + } + + public void setSession(HgStoreSession session) { + this.session = session; + } +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/NodeTxExecutor.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/NodeTxExecutor.java new file mode 100644 index 000000000..1d58097c6 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/NodeTxExecutor.java @@ -0,0 +1,434 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client; + +import static org.apache.hugegraph.store.client.util.HgStoreClientConst.EMPTY_LIST; +import static org.apache.hugegraph.store.client.util.HgStoreClientConst.NODE_MAX_RETRYING_TIMES; +import static org.apache.hugegraph.store.client.util.HgStoreClientConst.TX_SESSIONS_MAP_CAPACITY; + +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collector; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import javax.annotation.concurrent.NotThreadSafe; + +import org.apache.hugegraph.store.HgOwnerKey; +import org.apache.hugegraph.store.HgStoreSession; +import org.apache.hugegraph.store.client.type.HgStoreClientException; +import org.apache.hugegraph.store.client.util.HgAssert; +import org.apache.hugegraph.store.client.util.HgStoreClientConst; +import org.apache.hugegraph.store.term.HgPair; +import org.apache.hugegraph.store.term.HgTriple; + +import lombok.extern.slf4j.Slf4j; + + +/** + * 2021/11/18 + */ +@Slf4j +@NotThreadSafe +final class NodeTxExecutor { + private static final String maxTryMsg = + "the number of retries reached the upper limit : " + NODE_MAX_RETRYING_TIMES + + ",caused by:"; + private static final String msg = + "Not all tx-data delivered to real-node-session successfully."; + + static { + System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", + String.valueOf(Runtime.getRuntime().availableProcessors() * 2)); + } + + private final String graphName; + NodeTxSessionProxy proxy; + Collector>> collector = Collectors.groupingBy( + nkv -> nkv.getNodeId(), Collectors.mapping(NodeTkv::getKey, Collectors.toList())); + private Map sessions = new HashMap<>(TX_SESSIONS_MAP_CAPACITY, 1); + private boolean isTx; + private List, + Function>> entries = new LinkedList<>(); + + + private NodeTxExecutor(String graphName, NodeTxSessionProxy proxy) { + this.graphName = graphName; + this.proxy = proxy; + } + + static NodeTxExecutor graphOf(String graphName, NodeTxSessionProxy proxy) { + return new NodeTxExecutor(graphName, proxy); + } + + public boolean isTx() { + return isTx; + } + + void setTx(boolean tx) { + isTx = tx; + } + + void commitTx() { + if (!this.isTx) { + throw new IllegalStateException("It's not in tx state"); + } + + this.doCommit(); + } + + void rollbackTx() { + if (!this.isTx) { + return; + } + try { + this.sessions.values().stream().filter(HgStoreSession::isTx) + .forEach(HgStoreSession::rollback); + } catch (Throwable t) { + throw t; + } finally { + this.isTx = false; + this.sessions.clear(); + } + } + + void doCommit() { + try { + this.retryingInvoke(() -> { + if (this.entries.isEmpty()) { + return true; + } + AtomicBoolean allSuccess = new AtomicBoolean(true); + for (HgPair, Function> e : + this.entries) { + doAction(e.getKey(), e.getValue()); + } + if (!allSuccess.get()) { + throw HgStoreClientException.of(msg); + } + AtomicReference throwable = new AtomicReference<>(); + Collection sessions = this.sessions.values(); + sessions.parallelStream().forEach(e -> { + if (e.isTx()) { + try { + e.commit(); + } catch (Throwable t) { + throwable.compareAndSet(null, t); + allSuccess.set(false); + } + } + }); + if (!allSuccess.get()) { + if (isTx) { + try { + sessions.stream().forEach(HgStoreSession::rollback); + } catch (Exception e) { + + } + } + Throwable cause = throwable.get(); + if (cause.getCause() != null) { + cause = cause.getCause(); + } + if (cause instanceof HgStoreClientException) { + throw (HgStoreClientException) cause; + } + throw HgStoreClientException.of(cause); + } + return true; + }); + + } catch (Throwable t) { + throw t; + } finally { + this.isTx = false; + this.entries = new LinkedList<>(); + this.sessions = new HashMap<>(TX_SESSIONS_MAP_CAPACITY, 1); + } + } + + // private Function, + // List>> nodeStreamWrapper = nodeParams -> { + // if (nodeParams.getZ() == null) { + // return this.proxy.getNode(nodeParams.getX(), + // nodeParams.getY()); + // } else { + // if (nodeParams.getZ() instanceof HgOwnerKey) { + // return this.proxy.getNode(nodeParams.getX(), + // nodeParams.getY(), + // (HgOwnerKey) nodeParams.getZ()); + // } if ( nodeParams.getZ() instanceof Integer ){ + // return this.proxy.doPartition(nodeParams.getX(), (Integer) nodeParams.getZ()) + // .stream() + // .map(e -> new NodeTkv(e, nodeParams.getX(), nodeParams.getY(), + // nodeParams.getY() + // .getKeyCode())) + // .map( + // e -> new HgPair<>(this.proxy.getStoreNode(e.getNodeId + // ()), e) + // ); + // }else { + // HgAssert.isTrue(nodeParams.getZ() instanceof byte[], + // "Illegal parameter to get node id"); + // throw new NotImplementedException(); + // } + // } + // }; + + // private Function, + // List>> nodeStreamWrapper = nodeParams -> { + // if (nodeParams.getZ() == null) { + // return this.proxy.getNode(nodeParams.getX(), nodeParams.getY()); + // } else { + // if (nodeParams.getZ() instanceof HgOwnerKey) { + // return this.proxy.getNode(nodeParams.getX(), nodeParams.getY(), + // (HgOwnerKey) nodeParams.getZ()); + // } + // if (nodeParams.getZ() instanceof Integer) { + // Collection nodePartitions = this.proxy.doPartition(nodeParams + // .getX(), + // (Integer) + // nodeParams + // .getZ()); + // ArrayList> hgPairs = new ArrayList<> + // (nodePartitions.size()); + // for (HgNodePartition nodePartition : nodePartitions) { + // NodeTkv nodeTkv = new NodeTkv(nodePartition, nodeParams.getX(), nodeParams + // .getY(), + // nodeParams.getY().getKeyCode()); + // hgPairs.add(new HgPair<>(this.proxy.getStoreNode(nodeTkv.getNodeId()), + // nodeTkv)); + // + // } + // return hgPairs; + // } else { + // HgAssert.isTrue(nodeParams.getZ() instanceof byte[], "Illegal parameter to get + // node id"); + // throw new RuntimeException("not implemented"); + // } + // } + // }; + + private boolean doAction(HgTriple nodeParams, + Function action) { + if (nodeParams.getZ() == null) { + return this.proxy.doAction(nodeParams.getX(), nodeParams.getY(), nodeParams.getY(), + action); + } else { + if (nodeParams.getZ() instanceof HgOwnerKey) { + boolean result = this.proxy.doAction(nodeParams.getX(), nodeParams.getY(), + (HgOwnerKey) nodeParams.getZ(), action); + return result; + } + if (nodeParams.getZ() instanceof Integer) { + return this.proxy.doAction(nodeParams.getX(), nodeParams.getY(), + (Integer) nodeParams.getZ(), action); + } else { + HgAssert.isTrue(nodeParams.getZ() instanceof byte[], + "Illegal parameter to get node id"); + throw new RuntimeException("not implemented"); + } + } + } + + boolean prepareTx(HgTriple nodeParams, + Function sessionMapper) { + if (this.isTx) { + return this.entries.add(new HgPair(nodeParams, sessionMapper)); + } else { + return this.isAllTrue(nodeParams, sessionMapper); + } + } + + public HgStoreSession openNodeSession(HgStoreNode node) { + HgStoreSession res = this.sessions.get(node.getNodeId()); + if (res == null) { + this.sessions.put(node.getNodeId(), (res = node.openSession(this.graphName))); + } + if (this.isTx) { + res.beginTx(); + } + + return res; + } + + R limitOne( + Supplier>> nodeStreamSupplier, + Function, R> sessionMapper, R emptyObj) { + + Optional res = retryingInvoke( + () -> nodeStreamSupplier.get() + .parallel() + .map( + pair -> new SessionData( + openNodeSession(pair.getKey()), + pair.getValue()) + ).map(sessionMapper) + .filter( + r -> isValid(r) + ) + .findAny() + .orElseGet(() -> emptyObj) + ); + return res.orElse(emptyObj); + } + + List toList(Function nodeFunction + , List keyList + , Function> flatMapper + , Function>, List> sessionMapper) { + Optional> res = retryingInvoke( + () -> keyList.stream() + .flatMap(flatMapper) + .collect(collector) + .entrySet() + .stream() + .map( + e -> new SessionData<> + ( + openNodeSession( + nodeFunction.apply(e.getKey())), + e.getValue() + ) + ) + .parallel() + .map(sessionMapper) + .flatMap( + e -> e.stream() + ) + //.distinct() + .collect(Collectors.toList()) + ); + + return res.orElse(EMPTY_LIST); + } + + private boolean isAllTrue(HgTriple nodeParams, + Function action) { + Optional res = retryingInvoke(() -> doAction(nodeParams, action)); + return res.orElse(false); + } + + boolean isAllTrue(Supplier>> dataSource, + Function, Boolean> action) { + Optional res = retryingInvoke( + () -> dataSource.get() + .parallel() + .map( + pair -> new SessionData( + openNodeSession(pair.getKey()), + pair.getValue()) + ).map(action) + .allMatch(Boolean::booleanValue) + ); + + return res.orElse(false); + } + + boolean ifAnyTrue(Supplier>> nodeStreamSupplier + , Function, Boolean> sessionMapper) { + + Optional res = retryingInvoke( + () -> nodeStreamSupplier.get() + .parallel() + .map( + pair -> new SessionData( + openNodeSession(pair.getKey()), + pair.getValue()) + ) + .map(sessionMapper) + .anyMatch(Boolean::booleanValue) + ); + + return res.orElse(false); + } + + Optional retryingInvoke(Supplier supplier) { + return IntStream.rangeClosed(0, NODE_MAX_RETRYING_TIMES).boxed() + .map( + i -> { + T buffer = null; + try { + buffer = supplier.get(); + } catch (Throwable t) { + if (i + 1 <= NODE_MAX_RETRYING_TIMES) { + try { + int sleepTime; + // 前三次每隔一秒做一次尝试 + if (i < 3) { + sleepTime = 1; + } else { + // 后面逐次递增 + sleepTime = i - 1; + } + log.info("Waiting {} seconds " + + "for the next try.", + sleepTime); + Thread.sleep(sleepTime * 1000L); + } catch (InterruptedException e) { + log.error("Failed to sleep", e); + } + } else { + log.error(maxTryMsg, t); + throw HgStoreClientException.of( + t.getMessage(), t); + } + } + return buffer; + } + ) + .filter(e -> e != null) + .findFirst(); + + } + + private boolean isValid(Object obj) { + if (obj == null) { + return false; + } + + if (HgStoreClientConst.EMPTY_BYTES.equals(obj)) { + return false; + } + + return !EMPTY_LIST.equals(obj); + } + + class SessionData { + HgStoreSession session; + T data; + + SessionData(HgStoreSession session, T data) { + this.session = session; + this.data = data; + } + + } +} + + + diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/NodeTxSessionProxy.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/NodeTxSessionProxy.java new file mode 100644 index 000000000..3379d6653 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/NodeTxSessionProxy.java @@ -0,0 +1,890 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client; + +import static java.util.stream.Collectors.groupingBy; +import static org.apache.hugegraph.store.client.util.HgAssert.isArgumentNotNull; +import static org.apache.hugegraph.store.client.util.HgAssert.isArgumentValid; +import static org.apache.hugegraph.store.client.util.HgAssert.isFalse; +import static org.apache.hugegraph.store.client.util.HgStoreClientConst.EMPTY_STRING; +import static org.apache.hugegraph.store.client.util.HgStoreClientUtil.err; +import static org.apache.hugegraph.store.client.util.HgStoreClientUtil.toStr; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import javax.annotation.concurrent.NotThreadSafe; + +import org.apache.hugegraph.store.HgKvEntry; +import org.apache.hugegraph.store.HgKvIterator; +import org.apache.hugegraph.store.HgKvOrderedIterator; +import org.apache.hugegraph.store.HgOwnerKey; +import org.apache.hugegraph.store.HgScanQuery; +import org.apache.hugegraph.store.HgStoreSession; +import org.apache.hugegraph.store.client.grpc.KvBatchScanner; +import org.apache.hugegraph.store.client.grpc.KvCloseableIterator; +import org.apache.hugegraph.store.client.util.HgAssert; +import org.apache.hugegraph.store.client.util.HgStoreClientConst; +import org.apache.hugegraph.store.client.util.HgStoreClientUtil; +import org.apache.hugegraph.store.grpc.stream.ScanStreamReq.Builder; +import org.apache.hugegraph.store.term.HgPair; +import org.apache.hugegraph.store.term.HgTriple; + +import lombok.extern.slf4j.Slf4j; + +/** + * created on 2022/01/19 + * + * @version 0.6.0 added batch scan on 2022/03/03 + */ +@Slf4j +@NotThreadSafe +class NodeTxSessionProxy implements HgStoreSession { + private final HgStoreNodeManager nodeManager; + private final HgStoreNodePartitioner nodePartitioner; + private final String graphName; + private final NodeTxExecutor txExecutor; + + NodeTxSessionProxy(String graphName, HgStoreNodeManager nodeManager) { + this.nodeManager = nodeManager; + this.graphName = graphName; + this.nodePartitioner = this.nodeManager.getNodePartitioner(); + this.txExecutor = NodeTxExecutor.graphOf(this.graphName, this); + + isFalse(this.nodePartitioner == null, + "Failed to retrieve the node-partitioner from node-manager."); + } + + @Override + public void beginTx() { + this.txExecutor.setTx(true); + } + + @Override + public void commit() { + this.txExecutor.commitTx(); + } + + @Override + public void rollback() { + this.txExecutor.rollbackTx(); + } + + @Override + public boolean isTx() { + return this.txExecutor.isTx(); + } + + @Override + public boolean put(String table, HgOwnerKey ownerKey, byte[] value) { + // isArgumentValid(table, "table"); + // isArgumentNotNull(ownerKey, "ownerKey"); + // log.info("put -> graph: {}, table: {}, key: {}, value: {}", + // graphName, table, ownerKey, toByteStr(value)); + // return this.txExecutor.prepareTx( + // () -> getNodeStream(table, ownerKey), + // e -> e.session.put(table, e.data.getKey(), value) + // ); + return this.txExecutor.prepareTx(new HgTriple(table, ownerKey, null), + e -> e.getSession().put(table, + e.getKey(), + value)); + } + + @Override + public boolean directPut(String table, int partitionId, HgOwnerKey ownerKey, byte[] value) { + isArgumentValid(table, "table"); + isArgumentNotNull(ownerKey, "ownerKey"); + + return this.txExecutor.prepareTx( + new HgTriple(table, ownerKey, partitionId), + e -> e.getSession().put(table, e.getKey(), value) + ); + } + + @Override + public boolean delete(String table, HgOwnerKey ownerKey) { + HgAssert.isFalse(HgAssert.isInvalid(table), "The argument is invalid: table"); + HgAssert.isFalse(ownerKey == null, "The argument is invalid: ownerKey"); + + if (log.isDebugEnabled()) { + log.debug("delete -> graph: {}, table: {}, key: {}" + , graphName, table, toStr(ownerKey)); + } + + return this.txExecutor + .prepareTx( + new HgTriple(table, ownerKey, null), + e -> e.getSession().delete(table, e.getKey()) + ); + } + + + @Override + public boolean deleteSingle(String table, HgOwnerKey ownerKey) { + HgAssert.isFalse(HgAssert.isInvalid(table), "The argument is invalid: table"); + HgAssert.isFalse(ownerKey == null, "The argument is invalid: ownerKey"); + + if (log.isDebugEnabled()) { + log.debug("deleteSingle -> graph: {}, table: {}, key: {}" + , graphName, table, toStr(ownerKey)); + } + + return this.txExecutor + .prepareTx( + new HgTriple(table, ownerKey, null), + e -> e.getSession().deleteSingle(table, e.getKey()) + ); + } + + + @Override + public boolean deletePrefix(String table, HgOwnerKey prefix) { + HgAssert.isFalse(HgAssert.isInvalid(table), "The argument is invalid: table"); + HgAssert.isFalse(prefix == null, "The argument is invalid: prefix"); + + if (log.isDebugEnabled()) { + log.debug("deletePrefix -> graph: {}, table: {}, prefix: {}" + , graphName, table, toStr(prefix)); + } + + return this.txExecutor + .prepareTx( + new HgTriple(table, prefix, null), + e -> e.getSession().deletePrefix(table, e.getKey()) + ); + } + + + @Override + public boolean deleteRange(String table, HgOwnerKey start, HgOwnerKey end) { + HgAssert.isFalse(HgAssert.isInvalid(table), "The argument is invalid: table"); + HgAssert.isFalse(start == null, "The argument is invalid: start"); + HgAssert.isFalse(end == null, "The argument is invalid: end"); + + if (log.isDebugEnabled()) { + log.debug("deleteRange -> graph: {}, table: {}, start: {}, end: {}" + , graphName, table, toStr(start), toStr(end)); + } + + return this.txExecutor + .prepareTx( + new HgTriple(table, start, end), + e -> e.getSession().deleteRange(table, e.getKey(), e.getEndKey()) + ); + } + + @Override + public boolean merge(String table, HgOwnerKey key, byte[] value) { + HgAssert.isFalse(HgAssert.isInvalid(table), "The argument is invalid: table"); + HgAssert.isFalse(key == null, "The argument is invalid: key"); + HgAssert.isFalse(value == null, "The argument is invalid: value"); + + if (log.isDebugEnabled()) { + log.debug("merge -> graph: {}, table: {}, key: {}, value: {}" + , graphName, table, toStr(key), toStr(value)); + } + + return this.txExecutor + .prepareTx( + new HgTriple(table, key, value), + e -> e.getSession().merge(table, e.getKey(), value) + ); + } + + /*--- tx end ---*/ + + @Override + public byte[] get(String table, HgOwnerKey ownerKey) { + isArgumentValid(table, "table"); + isArgumentNotNull(ownerKey, "ownerKey"); + + return this.txExecutor + .limitOne( + () -> this.getNodeStream(table, ownerKey), + e -> e.session.get(table, e.data.getKey()), HgStoreClientConst.EMPTY_BYTES + ); + } + + @Override + public boolean clean(int partId) { + Collection nodes = this.doPartition("", partId); + return nodes.parallelStream() + .map( + e -> this.getStoreNode(e.getNodeId()).openSession(this.graphName) + .clean(partId) + ).findFirst().get(); + } + + @Override + @Deprecated + public List batchGetOwner(String table, List keyList) { + HgAssert.isFalse(HgAssert.isInvalid(table), "The argument is invalid: table"); + HgAssert.isFalse(HgAssert.isInvalid(keyList), "The argument is invalid: keyList"); + + return this.txExecutor + .toList( + (l) -> this.getStoreNode(l), + keyList, + key -> this.toNodeTkvList(table, key, key).stream(), + e -> e.session.batchGetOwner(table, e.data) + ); + } + + @Override + public HgKvIterator batchPrefix(String table, List keyList) { + HgAssert.isFalse(HgAssert.isInvalid(table), "The argument is invalid: table"); + HgAssert.isFalse(HgAssert.isInvalid(keyList), "The argument is invalid: keyList"); + return this.toHgKvIteratorProxy( + this.txExecutor + .toList( + (l) -> this.getStoreNode(l), + keyList, + key -> this.toNodeTkvList(table, key, key).stream(), + e -> Collections.singletonList(e.session.batchPrefix(table, e.data)) + ) + , Long.MAX_VALUE); + } + + @Override + public boolean truncate() { + return this.txExecutor + .isAllTrue( + () -> this.getNodeStream(EMPTY_STRING), + e -> e.session.truncate() + ); + } + + @Override + public boolean existsTable(String table) { + return this.txExecutor + .ifAnyTrue( + () -> this.getNodeStream(EMPTY_STRING), + e -> e.session.existsTable(table) + ); + } + + @Override + public boolean createTable(String table) { + return this.txExecutor + .isAllTrue( + () -> this.getNodeStream(EMPTY_STRING), + e -> e.session.createTable(table) + ); + } + + @Override + public boolean deleteTable(String table) { + return this.txExecutor + .isAllTrue( + () -> this.getNodeStream(EMPTY_STRING), + e -> e.session.deleteTable(table) + ); + } + + @Override + public boolean dropTable(String table) { + return this.txExecutor + .isAllTrue( + () -> this.getNodeStream(table), + e -> e.session.dropTable(table) + ); + } + + @Override + public boolean deleteGraph(String graph) { + return this.txExecutor + .isAllTrue( + () -> this.getNodeStream(EMPTY_STRING), + e -> e.session.deleteGraph(graph) + ); + } + + @Override + public HgKvIterator scanIterator(String table) { + return scanIterator(table, 0); + } + + @Override + public HgKvIterator scanIterator(String table, byte[] query) { + return scanIterator(table, 0, query); + } + + @Override + public HgKvIterator scanIterator(String table, long limit) { + HgAssert.isFalse(HgAssert.isInvalid(table), "The argument is invalid: table"); + + return this.toHgKvIteratorProxy( + this.toNodeTkvList(table) + .parallelStream() + .map( + e -> this.getStoreNode(e.getNodeId()).openSession(this.graphName) + .scanIterator(e.getTable(), limit) + ) + .collect(Collectors.toList()) + , limit); + } + + @Override + public HgKvIterator scanIterator(String table, long limit, byte[] query) { + HgAssert.isFalse(HgAssert.isInvalid(table), "The argument is invalid: table"); + + return this.toHgKvIteratorProxy( + this.toNodeTkvList(table) + .parallelStream() + .map( + e -> this.getStoreNode(e.getNodeId()).openSession(this.graphName) + .scanIterator(e.getTable(), e.getKey(), limit, query) + ) + .collect(Collectors.toList()) + , limit); + } + + @Override + public HgKvIterator scanIterator(String table, HgOwnerKey keyPrefix) { + return scanIterator(table, keyPrefix, 0); + } + + @Override + public HgKvIterator scanIterator(String table, HgOwnerKey keyPrefix, long limit) { + HgAssert.isFalse(HgAssert.isInvalid(table), "The argument is invalid: table"); + HgAssert.isFalse(keyPrefix == null, "The argument is invalid: keyPrefix"); + + return this.toHgKvIteratorProxy( + this.toNodeTkvList(table, keyPrefix) + .parallelStream() + .map( + e -> this.getStoreNode(e.getNodeId()).openSession(this.graphName) + .scanIterator(e.getTable(), e.getKey(), limit) + ) + .collect(Collectors.toList()) + , limit); + + } + + @Override + public HgKvIterator scanIterator(String table, HgOwnerKey keyPrefix, long limit, + byte[] query) { + HgAssert.isFalse(HgAssert.isInvalid(table), "The argument is invalid: table"); + HgAssert.isFalse(keyPrefix == null, "The argument is invalid: keyPrefix"); + + return this.toHgKvIteratorProxy( + this.toNodeTkvList(table, keyPrefix) + .parallelStream() + .map( + e -> this.getStoreNode(e.getNodeId()).openSession(this.graphName) + .scanIterator(e.getTable(), e.getKey(), limit, query) + ) + .collect(Collectors.toList()) + , limit); + } + + @Override + public HgKvIterator scanIterator(String table, HgOwnerKey startKey, + HgOwnerKey endKey) { + return this.scanIterator(table, startKey, endKey, 0, null); + } + + @Override + public HgKvIterator scanIterator(String table, HgOwnerKey startKey, + HgOwnerKey endKey, long limit) { + HgAssert.isFalse(HgAssert.isInvalid(table), "The argument is invalid: table"); + HgAssert.isFalse(startKey == null, "The argument is invalid: startKey"); + HgAssert.isFalse(endKey == null, "The argument is invalid: endKey"); + + return this.toHgKvIteratorProxy( + this.toNodeTkvList(table, startKey, endKey) + .parallelStream() + .map( + e -> this.getStoreNode(e.getNodeId()).openSession(this.graphName) + .scanIterator(e.getTable(), e.getKey(), e.getEndKey(), limit) + ) + .collect(Collectors.toList()) + , limit); + } + + @Override + public HgKvIterator scanIterator(String table, HgOwnerKey startKey, HgOwnerKey endKey + , long limit, byte[] query) { + HgAssert.isFalse(HgAssert.isInvalid(table), "The argument is invalid: table"); + HgAssert.isFalse(startKey == null, "The argument is invalid: startKey"); + HgAssert.isFalse(endKey == null, "The argument is invalid: endKey"); + + return this.toHgKvIteratorProxy( + this.toNodeTkvList(table, startKey, endKey) + .parallelStream() + .map( + e -> this.getStoreNode(e.getNodeId()).openSession(this.graphName) + .scanIterator(e.getTable(), e.getKey(), e.getEndKey(), limit, + query) + ) + .collect(Collectors.toList()) + , limit); + + } + + @Override + public HgKvIterator scanIterator(String table, HgOwnerKey startKey, HgOwnerKey endKey + , long limit, int scanType, byte[] query) { + HgAssert.isFalse(HgAssert.isInvalid(table), "The argument is invalid: table"); + HgAssert.isFalse(startKey == null, "The argument is invalid: startKey"); + HgAssert.isFalse(endKey == null, "The argument is invalid: endKey"); + + return this.toHgKvIteratorProxy( + this.toNodeTkvList(table, startKey, endKey) + .parallelStream() + .map( + e -> this.getStoreNode(e.getNodeId()).openSession(this.graphName) + .scanIterator(e.getTable(), e.getKey(), e.getEndKey(), limit, + scanType, query) + ) + .collect(Collectors.toList()) + , limit); + + } + + @Override + public HgKvIterator scanIterator(String table, int codeFrom, int codeTo, + int scanType, byte[] query) { + if (log.isDebugEnabled()) { + log.debug("graph: {}, table: {}, codeFrom: {}, codeTo: {}, scanType: {}, query: {}" + , graphName, table, codeFrom, codeTo, scanType, HgStoreClientUtil.toStr(query)); + } + + HgAssert.isFalse(HgAssert.isInvalid(table), "The argument is invalid: table"); + return this.toHgKvIteratorProxy( + this.toNodeTkvList(table, codeFrom, codeTo) + .parallelStream() + .map( + e -> this.getStoreNode(e.getNodeId()).openSession(this.graphName) + .scanIterator(e.getTable() + , e.getKey().getKeyCode() + , e.getEndKey().getKeyCode(), + scanType, query) + ) + .collect(Collectors.toList()) + , 0); + + } + + @Override + public HgKvIterator scanIterator(Builder scanReqBuilder) { + List nodeTKvs = this.toNodeTkvList(scanReqBuilder); + Function> hgKvIteratorFunction = e -> { + HgStoreSession session = this.getStoreNode(e.getNodeId()) + .openSession(this.graphName); + return session.scanIterator(scanReqBuilder); + }; + List iterators = nodeTKvs.parallelStream() + .map(hgKvIteratorFunction) + .collect(Collectors.toList()); + return this.toHgKvIteratorProxy(iterators, scanReqBuilder.getLimit()); + } + + @Override + public long count(String table) { + return this.toNodeTkvList(table) + .parallelStream() + .map( + e -> this.getStoreNode(e.getNodeId()).openSession(this.graphName) + .count(e.getTable()) + ) + .collect(Collectors.summingLong(l -> l)); + } + + @Override + public List> scanBatch(HgScanQuery scanQuery) { + HgAssert.isArgumentNotNull(scanQuery, "scanQuery"); + + return this.toTkvMapFunc(scanQuery.getScanMethod()) + .apply(scanQuery) + .entrySet() + .stream() + .map(e -> + this.getStoreNode(e.getKey()) + .openSession(this.graphName) + .scanBatch(toScanQueryFunc(scanQuery.getScanMethod()) + .apply(scanQuery.getTable(), e.getValue()) + .setQuery(scanQuery.getQuery()) + .setLimit(scanQuery.getLimit()) + .setPerKeyLimit(scanQuery.getPerKeyLimit()) + .setPerKeyMax((scanQuery.getPerKeyMax())) + .setScanType(scanQuery.getScanType()) + .build() + ) + ) + //.peek(e->log.info("{}",e)) + .flatMap(List::stream) + .collect(Collectors.toList()); + + } + + @Override + public KvCloseableIterator> scanBatch2(HgScanQuery scanQuery) { + return scanBatch3(scanQuery, null); + } + + @Override + public KvCloseableIterator> scanBatch3(HgScanQuery scanQuery, + KvCloseableIterator iterator) { + KvCloseableIterator notifierWrap = KvBatchScanner.ofMerger(scanQuery, (query, notifier) -> { + Map> nodeTkvs = this.toTkvMapFunc(scanQuery.getScanMethod()) + .apply(query); + + nodeTkvs.forEach((storeId, tkvs) -> { + this.getStoreNode(storeId) + .openSession(this.graphName) + .scanBatch3(toScanQueryFunc(scanQuery.getScanMethod()) + .apply(scanQuery.getTable(), tkvs) + .setQuery(scanQuery.getQuery()) + .setLimit(scanQuery.getLimit()) + .setSkipDegree(scanQuery.getSkipDegree()) + .setPerKeyLimit(scanQuery.getPerKeyLimit()) + .setPerKeyMax((scanQuery.getPerKeyMax())) + .setScanType(scanQuery.getScanType()) + .setOrderType(scanQuery.getOrderType()) + .build(), notifier + ); + }); + return true; + }); + return notifierWrap; + } + + private Function>> toTkvMapFunc( + HgScanQuery.ScanMethod scanMethod) { + switch (scanMethod) { + case RANGE: + return scanQuery -> { + List starts = scanQuery.getStartList(); + List ends = scanQuery.getEndList(); + int size = starts.size(); + return IntStream.range(0, size) + .boxed() + .map(i -> this.toNodeTkvList(scanQuery.getTable(), + starts.get(i), ends.get(i))) + .flatMap(List::stream) + .collect(groupingBy(NodeTkv::getNodeId)); + }; + case PREFIX: + return scanQuery -> + scanQuery.getPrefixList() + .stream() + .map(keyPrefix -> this.toNodeTkvList(scanQuery.getTable(), + keyPrefix)) + .flatMap(List::stream) + .collect(groupingBy(NodeTkv::getNodeId)); + + default: + return scanQuery -> this.toNodeTkvList(scanQuery.getTable()) + .stream() + .collect(groupingBy(NodeTkv::getNodeId)); + } + } + + private BiFunction, HgScanQuery.ScanBuilder> toScanQueryFunc( + HgScanQuery.ScanMethod scanMethod) { + switch (scanMethod) { + case RANGE: + return (table, tkvList) -> { + List startList = new LinkedList(); + List endList = new LinkedList(); + + tkvList.stream().forEach(e -> { + startList.add(e.getKey()); + endList.add(e.getEndKey()); + }); + + return HgScanQuery.ScanBuilder.rangeOf(table, startList, endList); + }; + case PREFIX: + return (table, tkvList) -> + HgScanQuery.ScanBuilder.prefixOf(table, + tkvList.stream() + .map(e -> e.getKey()) + .collect(Collectors.toList()) + ); + default: + return (table, tkvList) -> HgScanQuery.ScanBuilder.tableOf(table); + } + + } + + /*-- common --*/ + private HgKvIterator toHgKvIteratorProxy(List iteratorList, long limit) { + boolean isAllOrderedLimiter = iteratorList.stream() + .allMatch( + e -> e instanceof HgKvOrderedIterator); + + HgKvIterator iterator; + if (isAllOrderedLimiter) { + iterator = new SequencedIterator(iteratorList.stream() + .map(e -> (HgKvOrderedIterator) e) + .collect(Collectors.toList()), limit); + } else { + iterator = new TopWorkIteratorProxy(iteratorList, limit); + } + + return iterator; + } + + HgStoreNode getStoreNode(Long nodeId) { + HgStoreNode res = this.nodeManager.applyNode(this.graphName, nodeId); + + if (res == null) { + throw err("Failed to apply for an instance of HgStoreNode from node-manager."); + } + + return res; + } + + public boolean doAction(String table, HgOwnerKey startKey, HgOwnerKey endKey, + Function action) { + Collection partitions = + doPartition(table, startKey.getOwner(), endKey.getOwner()); + for (HgNodePartition partition : partitions) { + HgStoreNode storeNode = this.getStoreNode(partition.getNodeId()); + HgStoreSession session = this.txExecutor.openNodeSession(storeNode); + NodeTkv data = new NodeTkv(partition, table, startKey, endKey); + data.setSession(session); + if (!action.apply(data)) { + return false; + } + } + return true; + } + + public boolean doAction(String table, HgOwnerKey startKey, Integer code, + Function action) { + Collection partitions = this.doPartition(table, code); + for (HgNodePartition partition : partitions) { + HgStoreNode storeNode = this.getStoreNode(partition.getNodeId()); + HgStoreSession session = this.txExecutor.openNodeSession(storeNode); + NodeTkv data = new NodeTkv(partition, table, startKey, code); + data.setSession(session); + if (!action.apply(data)) { + return false; + } + } + return true; + } + + private List toNodeTkvList(Builder scanReqBuilder) { + // TODO 使用builder获取owner + String table = scanReqBuilder.getTable(); + HgOwnerKey ownerKey = HgStoreClientConst.ALL_PARTITION_OWNER_KEY; + byte[] allOwner = ownerKey.getOwner(); + Collection partitions = doPartition(table, + allOwner, + allOwner); + List nodeTkvs = new ArrayList<>(partitions.size()); + for (HgNodePartition partition : partitions) { + nodeTkvs.add(new NodeTkv(partition, table, ownerKey, ownerKey)); + } + return nodeTkvs; + } + + private List toNodeTkvList(String table) { + Collection partitions = doPartition(table, + HgStoreClientConst.ALL_PARTITION_OWNER_KEY.getOwner(), + HgStoreClientConst.ALL_PARTITION_OWNER_KEY.getOwner()); + ArrayList nodeTkvs = new ArrayList<>(partitions.size()); + for (HgNodePartition partition : partitions) { + nodeTkvs.add(new NodeTkv(partition, table, HgStoreClientConst.ALL_PARTITION_OWNER_KEY, + HgStoreClientConst.ALL_PARTITION_OWNER_KEY)); + } + return nodeTkvs; + } + + private List toNodeTkvList(String table, HgOwnerKey ownerKey) { + Collection partitions = + doPartition(table, ownerKey.getOwner(), ownerKey.getOwner()); + ArrayList nodeTkvs = new ArrayList<>(partitions.size()); + for (HgNodePartition partition : partitions) { + nodeTkvs.add(new NodeTkv(partition, table, ownerKey, ownerKey)); + } + + return nodeTkvs; + } + + private List toNodeTkvList(String table, HgOwnerKey startKey, HgOwnerKey endKey) { + Collection partitions = + doPartition(table, startKey.getOwner(), endKey.getOwner()); + ArrayList nodeTkvs = new ArrayList<>(partitions.size()); + for (HgNodePartition partition : partitions) { + nodeTkvs.add(new NodeTkv(partition, table, startKey, endKey)); + } + return nodeTkvs; + } + + + private List toNodeTkvList(String table, int startCode, int endCode) { + Collection partitions = this.doPartition(table, startCode, endCode); + ArrayList nodeTkvs = new ArrayList<>(partitions.size()); + for (HgNodePartition partition : partitions) { + nodeTkvs.add( + new NodeTkv(partition, table, HgOwnerKey.codeOf(startCode), + HgOwnerKey.codeOf(endCode))); + } + return nodeTkvs; + } + + /** + * @return not null + */ + private Collection doPartition(String table, byte[] startKey, byte[] endKey) { + HgNodePartitionerBuilder partitionerBuilder = HgNodePartitionerBuilder.resetAndGet(); + + int status = this.nodePartitioner.partition(partitionerBuilder, this.graphName, startKey, + endKey); + + if (status != 0) { + throw err("The node-partitioner is not work."); + } + + Collection partitions = partitionerBuilder.getPartitions(); + + if (partitions.isEmpty()) { + throw err("Failed to get the collection of HgNodePartition from node-partitioner."); + } + + return partitions; + } + + /** + * @return @return not null + */ + private Collection doPartition(String table, int startCode, int endCode) { + HgNodePartitionerBuilder partitionerBuilder = HgNodePartitionerBuilder.resetAndGet(); + int status = this.nodePartitioner.partition(partitionerBuilder, this.graphName, startCode, + endCode); + + if (status != 0) { + throw err("The node-partitioner is not work."); + } + + Collection partitions = partitionerBuilder.getPartitions(); + + if (partitions.isEmpty()) { + throw err("Failed to get the collection of HgNodePartition from node-partitioner."); + } + + return partitions; + } + + Collection doPartition(String table, int partitionId) { + HgNodePartitionerBuilder partitionerBuilder = HgNodePartitionerBuilder.resetAndGet(); + int status = + this.nodePartitioner.partition(partitionerBuilder, this.graphName, partitionId); + + if (status != 0) { + throw err("The node-partitioner is not work."); + } + + Collection partitions = partitionerBuilder.getPartitions(); + + if (partitions.isEmpty()) { + throw err("Failed to get the collection of HgNodePartition from node-partitioner."); + } + + return partitions; + } + + private Stream> getNodeStream(String table) { + return this.toNodeTkvList(table) + .stream() + .map( + e -> new HgPair<>(this.getStoreNode(e.getNodeId()), e) + ); + } + + Stream> getNodeStream(String table, + HgOwnerKey ownerKey) { + return this.toNodeTkvList(table, ownerKey) + .stream() + .map( + e -> new HgPair<>(this.getStoreNode(e.getNodeId()), e) + ); + } + + Stream> getNodeStream(String table, HgOwnerKey startKey, + HgOwnerKey endKey) { + return this.toNodeTkvList(table, startKey, endKey) + .stream() + .map( + e -> new HgPair<>(this.getStoreNode(e.getNodeId()), e) + ); + + } + + // private List> getNode(String table) { + // List nodeTkvList = this.toNodeTkvList(table); + // return nodeTkv2Node(nodeTkvList); + // } + + List> getNode(String table, HgOwnerKey ownerKey) { + List nodeTkvList = this.toNodeTkvList(table, ownerKey); + return nodeTkv2Node(nodeTkvList); + } + + List> getNode(String table, HgOwnerKey startKey, + HgOwnerKey endKey) { + List nodeTkvList = this.toNodeTkvList(table, startKey, endKey); + return nodeTkv2Node(nodeTkvList); + + } + // + //boolean doAction(String table, HgOwnerKey startKey, HgOwnerKey endKey, + // Function action) { + // return this.doAction(table, startKey, endKey, action); + // + //} + + // List> getNode(String table, Integer endKey) { + // .stream() + // .map(e -> new NodeTkv(e, nodeParams.getX(), nodeParams.getY(), nodeParams.getY + // ().getKeyCode())) + // .map( + // e -> new HgPair<>(this.proxy.getStoreNode(e.getNodeId()), e) + // ); + // Collection nodePartitions = this.doPartition(table, endKey); + // for (HgNodePartition nodePartition: nodePartitions) { + // + // } + // return nodeTkv2Node(nodeTkvList); + // + // } + + private List> nodeTkv2Node(Collection nodeTkvList) { + ArrayList> hgPairs = new ArrayList<>(nodeTkvList.size()); + for (NodeTkv nodeTkv : nodeTkvList) { + hgPairs.add(new HgPair<>(this.getStoreNode(nodeTkv.getNodeId()), nodeTkv)); + } + return hgPairs; + } + +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/SequencedIterator.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/SequencedIterator.java new file mode 100644 index 000000000..f2725e268 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/SequencedIterator.java @@ -0,0 +1,148 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Queue; + +import org.apache.hugegraph.store.HgKvEntry; +import org.apache.hugegraph.store.HgKvIterator; +import org.apache.hugegraph.store.HgKvOrderedIterator; + +import lombok.extern.slf4j.Slf4j; + +/** + * Proxy iterator orderly, to switch next one will happen when the current one is empty. + *

+ * created on 2022/03/10 + * + * @version 0.1.0 + */ +@Slf4j +public class SequencedIterator implements HgKvIterator { + private static final byte[] EMPTY_BYTES = new byte[0]; + private final Queue queue; + private final long limit; + private HgKvOrderedIterator iterator; + private HgKvEntry entry; + private int count; + private byte[] position = EMPTY_BYTES; + private byte[] position4Seeking = EMPTY_BYTES; + + SequencedIterator(List iterators, long limit) { + Collections.sort(iterators); + this.queue = new LinkedList(iterators); + this.limit = limit <= 0 ? Integer.MAX_VALUE : limit; + } + + private HgKvOrderedIterator getIterator() { + if (this.queue.isEmpty()) { + return null; + } + HgKvOrderedIterator buf; + while ((buf = this.queue.poll()) != null) { + buf.seek(this.position4Seeking); + if (buf.hasNext()) { + break; + } + } + return buf; + } + + private void closeIterators() { + if (this.queue.isEmpty()) { + return; + } + HgKvOrderedIterator buf; + while ((buf = this.queue.poll()) != null) { + buf.close(); + } + + } + + @Override + public byte[] key() { + if (this.entry != null) { + return this.entry.key(); + } + return null; + } + + @Override + public byte[] value() { + if (this.entry != null) { + return this.entry.value(); + } + return null; + } + + @Override + public byte[] position() { + return this.position; + } + + @Override + public void seek(byte[] pos) { + if (pos != null) { + this.position4Seeking = pos; + } + } + + @Override + public boolean hasNext() { + if (this.count >= this.limit) { + return false; + } + if (this.iterator == null) { + this.iterator = this.getIterator(); + } else if (!this.iterator.hasNext()) { + this.iterator.close(); + this.iterator = this.getIterator(); + } + return this.iterator != null; + } + + @Override + public Object next() { + if (this.iterator == null) { + hasNext(); + } + if (this.iterator == null) { + throw new NoSuchElementException(); + } + this.entry = this.iterator.next(); + this.position = this.iterator.position(); + if (!this.iterator.hasNext()) { + this.iterator.close(); + this.iterator = null; + } + this.count++; + return this.entry; + } + + @Override + public void close() { + if (this.iterator != null) { + this.iterator.close(); + } + this.closeIterators(); + } +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/ShiftWorkIteratorProxy.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/ShiftWorkIteratorProxy.java new file mode 100644 index 000000000..47e8195f9 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/ShiftWorkIteratorProxy.java @@ -0,0 +1,162 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client; + +import java.util.LinkedList; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Queue; + +import org.apache.hugegraph.store.HgKvEntry; +import org.apache.hugegraph.store.HgKvIterator; +import org.apache.hugegraph.store.HgKvPagingIterator; + +import lombok.extern.slf4j.Slf4j; + +/** + * created on 2021/10/24 + * + * @version 0.1.1 + */ +@Slf4j +public class ShiftWorkIteratorProxy implements HgKvIterator { + private static final byte[] EMPTY_BYTES = new byte[0]; + private final int limit; + private HgKvPagingIterator iterator; + private Queue queue = new LinkedList<>(); + private HgKvEntry entry; + private int count; + private int shiftCount; + + ShiftWorkIteratorProxy(List iterators, int limit) { + this.queue = new LinkedList<>(iterators); + this.limit = limit <= 0 ? Integer.MAX_VALUE : limit; + } + + private HgKvPagingIterator getIterator() { + if (this.queue.isEmpty()) { + return null; + } + + HgKvPagingIterator buf = null; + + while ((buf = this.queue.poll()) != null) { + if (buf.hasNext()) { + break; + } + } + + if (buf == null) { + return null; + } + + this.queue.add(buf); + + return buf; + } + + private void closeIterators() { + if (this.queue.isEmpty()) { + return; + } + + HgKvPagingIterator buf; + + while ((buf = this.queue.poll()) != null) { + buf.close(); + } + + } + + private void setIterator() { + + // if (++this.shiftCount >= this.iterator.getPageSize() / 2) { + if (++this.shiftCount >= this.iterator.getPageSize()) { + this.iterator = null; + this.shiftCount = 0; + } + + } + + private void doNext() { + + } + + @Override + public byte[] key() { + if (this.entry != null) { + return this.entry.key(); + } + return null; + } + + @Override + public byte[] value() { + if (this.entry != null) { + return this.entry.value(); + } + return null; + } + + @Override + public byte[] position() { + return this.iterator != null ? this.iterator.position() : EMPTY_BYTES; + } + + @Override + public void seek(byte[] position) { + if (this.iterator != null) { + this.iterator.seek(position); + } + } + + @Override + public boolean hasNext() { + if (this.count >= this.limit) { + return false; + } + if (this.iterator == null + || !this.iterator.hasNext()) { + this.iterator = this.getIterator(); + } + return this.iterator != null; + } + + @Override + public Object next() { + if (this.iterator == null) { + hasNext(); + } + if (this.iterator == null) { + throw new NoSuchElementException(); + } + this.entry = this.iterator.next(); + this.setIterator(); + this.count++; + //log.info("next - > {}",this.entry); + return this.entry; + } + + @Override + public void close() { + if (this.iterator != null) { + this.iterator.close(); + } + this.closeIterators(); + } +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/TopWorkIteratorProxy.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/TopWorkIteratorProxy.java new file mode 100644 index 000000000..3c2872df9 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/TopWorkIteratorProxy.java @@ -0,0 +1,146 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client; + +import java.util.LinkedList; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Queue; + +import org.apache.hugegraph.store.HgKvEntry; +import org.apache.hugegraph.store.HgKvIterator; + +/** + * created on 2021/10/21 + * + * @version 0.1.0 + */ +class TopWorkIteratorProxy implements HgKvIterator { + private static final byte[] EMPTY_BYTES = new byte[0]; + private final Queue queue; + private final long limit; + private HgKvIterator iterator; + private HgKvEntry entry; + private int count; + + TopWorkIteratorProxy(List iterators, long limit) { + this.queue = new LinkedList<>(iterators); + this.limit = limit <= 0 ? Integer.MAX_VALUE : limit; + } + + private HgKvIterator getIterator() { + if (this.queue.isEmpty()) { + return null; + } + + HgKvIterator buf = null; + + while ((buf = this.queue.poll()) != null) { + if (buf.hasNext()) { + break; + } + } + + if (buf == null) { + return null; + } + + this.queue.add(buf); + + return buf; + } + + private void closeIterators() { + if (this.queue.isEmpty()) { + return; + } + + HgKvIterator buf; + + while ((buf = this.queue.poll()) != null) { + buf.close(); + } + + } + + private void setIterator() { + this.iterator = null; + } + + @Override + public byte[] key() { + if (this.entry != null) { + return this.entry.key(); + } + return null; + } + + @Override + public byte[] value() { + if (this.entry != null) { + return this.entry.value(); + } + return null; + } + + @Override + public byte[] position() { + return this.iterator != null ? this.iterator.position() : EMPTY_BYTES; + } + + @Override + public void seek(byte[] position) { + if (this.iterator != null) { + this.iterator.seek(position); + } + } + + @Override + public boolean hasNext() { + if (this.count >= this.limit) { + return false; + } + if (this.iterator == null) { + this.iterator = this.getIterator(); + } + return this.iterator != null; + + } + + @Override + public Object next() { + if (this.iterator == null) { + hasNext(); + } + if (this.iterator == null) { + throw new NoSuchElementException(); + } + this.entry = this.iterator.next(); + this.setIterator(); + this.count++; + return this.entry; + } + + @Override + public void close() { + if (this.iterator != null) { + this.iterator.close(); + } + this.closeIterators(); + } +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/AbstractGrpcClient.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/AbstractGrpcClient.java new file mode 100644 index 000000000..b29b5c837 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/AbstractGrpcClient.java @@ -0,0 +1,179 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client.grpc; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.IntStream; + +import org.apache.hugegraph.store.client.util.ExecutorPool; +import org.apache.hugegraph.store.client.util.HgStoreClientConfig; +import org.apache.hugegraph.store.term.HgPair; + +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; +import io.grpc.stub.AbstractAsyncStub; +import io.grpc.stub.AbstractBlockingStub; +import io.grpc.stub.AbstractStub; + +/** + * @date 2023/3/28 + **/ +public abstract class AbstractGrpcClient { + + private static Map channels = new ConcurrentHashMap<>(); + private static int n = 5; + private static int concurrency = 1 << n; + private static AtomicLong counter = new AtomicLong(0); + private static long limit = Long.MAX_VALUE >> 1; + private Map[]> blockingStubs = + new ConcurrentHashMap<>(); + private Map[]> asyncStubs = new ConcurrentHashMap<>(); + private static HgStoreClientConfig config = HgStoreClientConfig.of(); + private ThreadPoolExecutor executor; + + { + executor = ExecutorPool.createExecutor("common", 60, concurrency, concurrency); + } + + public AbstractGrpcClient() { + + } + + public ManagedChannel[] getChannels(String target) { + ManagedChannel[] tc; + if ((tc = channels.get(target)) == null) { + synchronized (channels) { + if ((tc = channels.get(target)) == null) { + try { + ManagedChannel[] value = new ManagedChannel[concurrency]; + CountDownLatch latch = new CountDownLatch(concurrency); + for (int i = 0; i < concurrency; i++) { + int fi = i; + executor.execute(() -> { + try{ + value[fi] = getManagedChannel(target); + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + latch.countDown(); + } + }); + } + latch.await(); + channels.put(target, tc = value); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + } + return tc; + } + + public abstract AbstractBlockingStub getBlockingStub(ManagedChannel channel); + + public AbstractBlockingStub getBlockingStub(String target) { + ManagedChannel[] channels = getChannels(target); + HgPair[] pairs = blockingStubs.get(target); + long l = counter.getAndIncrement(); + if (l >= limit) { + counter.set(0); + } + int index = (int) (l & (concurrency - 1)); + if (pairs == null) { + synchronized (blockingStubs) { + pairs = blockingStubs.get(target); + if (pairs == null) { + HgPair[] value = new HgPair[concurrency]; + IntStream.range(0, concurrency).forEach(i -> { + ManagedChannel channel = channels[index]; + AbstractBlockingStub stub = getBlockingStub(channel); + value[i] = new HgPair<>(channel, stub); + // log.info("create channel for {}",target); + }); + blockingStubs.put(target, value); + AbstractBlockingStub stub = value[index].getValue(); + return (AbstractBlockingStub) setBlockingStubOption(stub); + } + } + } + return (AbstractBlockingStub) setBlockingStubOption(pairs[index].getValue()); + } + + private AbstractStub setBlockingStubOption(AbstractBlockingStub stub) { + return stub.withDeadlineAfter(config.getGrpcTimeoutSeconds(),TimeUnit.SECONDS) + .withMaxInboundMessageSize( + config.getGrpcMaxInboundMessageSize()) + .withMaxOutboundMessageSize( + config.getGrpcMaxOutboundMessageSize()); + } + + public AbstractAsyncStub getAsyncStub(ManagedChannel channel) { + return null; + } + + public AbstractAsyncStub getAsyncStub(String target) { + ManagedChannel[] channels = getChannels(target); + HgPair[] pairs = asyncStubs.get(target); + long l = counter.getAndIncrement(); + if (l >= limit) { + counter.set(0); + } + int index = (int) (l & (concurrency - 1)); + if (pairs == null) { + synchronized (asyncStubs) { + pairs = asyncStubs.get(target); + if (pairs == null) { + HgPair[] value = new HgPair[concurrency]; + IntStream.range(0, concurrency).parallel().forEach(i -> { + ManagedChannel channel = channels[index]; + AbstractAsyncStub stub = getAsyncStub(channel); + // stub.withMaxInboundMessageSize(config.getGrpcMaxInboundMessageSize()) + // .withMaxOutboundMessageSize(config.getGrpcMaxOutboundMessageSize()); + value[i] = new HgPair<>(channel, stub); + // log.info("create channel for {}",target); + }); + asyncStubs.put(target, value); + AbstractAsyncStub stub = + (AbstractAsyncStub) setStubOption(value[index].getValue()); + return stub; + } + } + } + return (AbstractAsyncStub) setStubOption(pairs[index].getValue()); + + } + + private AbstractStub setStubOption(AbstractStub value) { + return value.withMaxInboundMessageSize( + config.getGrpcMaxInboundMessageSize()) + .withMaxOutboundMessageSize( + config.getGrpcMaxOutboundMessageSize()); + } + + private ManagedChannel getManagedChannel(String target) { + return ManagedChannelBuilder.forTarget(target).usePlaintext().build(); + } + + +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcKvEntryImpl.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcKvEntryImpl.java new file mode 100644 index 000000000..65bf97c2d --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcKvEntryImpl.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client.grpc; + +import java.util.Arrays; + +import org.apache.hugegraph.store.HgKvEntry; + + +class GrpcKvEntryImpl implements HgKvEntry { + private final byte[] key; + private final byte[] value; + private final int code; + + GrpcKvEntryImpl(byte[] k, byte[] v, int code) { + this.key = k; + this.value = v; + this.code = code; + } + + @Override + public int code() { + return code; + } + + @Override + public byte[] key() { + return key; + } + + @Override + public byte[] value() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + GrpcKvEntryImpl hgKvEntry = (GrpcKvEntryImpl) o; + return Arrays.equals(key, hgKvEntry.key) && Arrays.equals(value, hgKvEntry.value); + } + + @Override + public int hashCode() { + int result = Arrays.hashCode(key); + result = 31 * result + Arrays.hashCode(value); + return result; + } + + @Override + public String toString() { + return "HgKvEntryImpl{" + + "key=" + Arrays.toString(key) + + ", value=" + Arrays.toString(value) + + ", code=" + code + + '}'; + } +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcKvIteratorImpl.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcKvIteratorImpl.java new file mode 100644 index 000000000..6d19c6b55 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcKvIteratorImpl.java @@ -0,0 +1,162 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client.grpc; + +import java.util.List; + +import org.apache.hugegraph.store.HgKvEntry; +import org.apache.hugegraph.store.HgKvIterator; +import org.apache.hugegraph.store.HgKvOrderedIterator; +import org.apache.hugegraph.store.HgKvPagingIterator; +import org.apache.hugegraph.store.HgPageSize; +import org.apache.hugegraph.store.HgSeekAble; +import org.apache.hugegraph.store.client.HgStoreNodeSession; +import org.apache.hugegraph.store.client.util.HgStoreClientConst; +import org.apache.hugegraph.store.client.util.HgStoreClientUtil; +import org.apache.hugegraph.store.grpc.common.Kv; + +import lombok.extern.slf4j.Slf4j; + +/** + * created on 2021/10/20 + * + * @version 0.2.1 + */ +@Slf4j +class GrpcKvIteratorImpl implements HgKvPagingIterator, HgKvOrderedIterator { + + private final byte[] emptyBytes = HgStoreClientConst.EMPTY_BYTES; + private final KvCloseableIterator iterator; + private final HgPageSize pageLimiter; + private final HgStoreNodeSession session; + private HgKvEntry element; + + private GrpcKvIteratorImpl(HgStoreNodeSession session, KvCloseableIterator iterator, + HgPageSize pageLimiter) { + this.iterator = iterator; + this.pageLimiter = pageLimiter; + this.session = session; + } + + public static HgKvIterator of(HgStoreNodeSession nodeSession, + KvCloseableIterator iterator) { + if (iterator instanceof HgPageSize) { + return of(nodeSession, iterator, (HgPageSize) iterator); + } + return new GrpcKvIteratorImpl(nodeSession, iterator, () -> 1); + } + + public static HgKvIterator of(HgStoreNodeSession nodeSession, + KvCloseableIterator iterator, + HgPageSize pageLimiter) { + return new GrpcKvIteratorImpl(nodeSession, iterator, pageLimiter); + } + + public static HgKvIterator of(HgStoreNodeSession nodeSession, List kvList) { + int pageSize = kvList.size(); + return new GrpcKvIteratorImpl(nodeSession, new KvListIterator(kvList), () -> pageSize); + } + + @Override + public boolean hasNext() { + // if (log.isDebugEnabled()) { + // if (!this.iterator.hasNext() && !nodeSession.getGraphName().endsWith("/s")) { + // log.debug("[ANALYSIS GrpcKv hasNext-> FALSE] "); + // } + // } + return this.iterator.hasNext(); + } + + @Override + public HgKvEntry next() { + Kv kv = this.iterator.next(); + this.element = new GrpcKvEntryImpl(kv.getKey().toByteArray(), kv.getValue().toByteArray(), + kv.getCode()); + return this.element; + } + + @Override + public byte[] key() { + if (this.element == null) { + return null; + } + return this.element.key(); + } + + @Override + public byte[] value() { + if (this.element == null) { + return null; + } + return this.element.value(); + } + + @Override + public byte[] position() { + if (this.element == null) { + return emptyBytes; + } + byte[] key = this.element.key(); + if (key == null) { + return emptyBytes; + } + if (!(this.iterator instanceof HgSeekAble)) { + return emptyBytes; + } + byte[] upstream = ((HgSeekAble) this.iterator).position(); + byte[] code = HgStoreClientUtil.toIntBytes(this.element.code()); + byte[] result = new byte[upstream.length + Integer.BYTES + key.length]; + System.arraycopy(upstream, 0, result, 0, upstream.length); + System.arraycopy(code, 0, result, upstream.length, Integer.BYTES); + System.arraycopy(key, 0, result, upstream.length + Integer.BYTES, key.length); + return result; + } + + @Override + public void seek(byte[] position) { + if (this.iterator instanceof HgSeekAble) { + ((HgSeekAble) this.iterator).seek(position); + } + } + + @Override + public long getPageSize() { + return pageLimiter.getPageSize(); + } + + @Override + public boolean isPageEmpty() { + return !iterator.hasNext(); + } + + + @Override + public int compareTo(HgKvOrderedIterator o) { + return Long.compare(this.getSequence(), o.getSequence()); + } + + @Override + public long getSequence() { + return this.session.getStoreNode().getNodeId().longValue(); + } + + @Override + public void close() { + this.iterator.close(); + } +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcNodeHealthyClient.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcNodeHealthyClient.java new file mode 100644 index 000000000..fcb2f4456 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcNodeHealthyClient.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client.grpc; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import javax.annotation.concurrent.ThreadSafe; + +import org.apache.hugegraph.store.grpc.HealthyGrpc; +import org.apache.hugegraph.store.grpc.HealthyOuterClass; + +import com.google.protobuf.Empty; + +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; + +/** + * + */ +@ThreadSafe +public final class GrpcNodeHealthyClient { + + private final static Map CHANNEL_MAP = new ConcurrentHashMap<>(); + private final static Map STUB_MAP = + new ConcurrentHashMap<>(); + + // TODO: Forbid constructing out of the package. + public GrpcNodeHealthyClient() { + + } + + private ManagedChannel getChannel(String target) { + ManagedChannel channel = CHANNEL_MAP.get(target); + if (channel == null) { + channel = ManagedChannelBuilder.forTarget(target).usePlaintext().build(); + CHANNEL_MAP.put(target, channel); + } + return channel; + } + + private HealthyGrpc.HealthyBlockingStub getStub(String target) { + HealthyGrpc.HealthyBlockingStub stub = STUB_MAP.get(target); + if (stub == null) { + stub = HealthyGrpc.newBlockingStub(getChannel(target)); + STUB_MAP.put(target, stub); + } + return stub; + } + + +/* boolean isHealthy(GrpcStoreNodeImpl node) { + String target = node.getAddress(); + + HealthyOuterClass.StringReply response = getStub(target).isOk(Empty.newBuilder().build()); + String res = response.getMessage(); + + if ("ok".equals(res)) { + return true; + } else { + System.out.printf("gRPC-res-msg: %s%n", res); + return false; + } + }*/ + + public boolean isHealthy() { + String target = "localhost:9080"; + ManagedChannel channel = ManagedChannelBuilder.forTarget(target).usePlaintext().build(); + HealthyGrpc.HealthyBlockingStub stub = HealthyGrpc.newBlockingStub(channel); + HealthyOuterClass.StringReply response = stub.isOk(Empty.newBuilder().build()); + + String res = response.getMessage(); + System.out.printf("gRPC response message:%s%n", res); + + return "ok".equals(res); + } +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcStoreNodeBuilder.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcStoreNodeBuilder.java new file mode 100644 index 000000000..7f8bb2f6e --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcStoreNodeBuilder.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client.grpc; + +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.hugegraph.store.client.HgPrivate; +import org.apache.hugegraph.store.client.HgStoreNode; +import org.apache.hugegraph.store.client.HgStoreNodeBuilder; +import org.apache.hugegraph.store.client.HgStoreNodeManager; +import org.apache.hugegraph.store.client.util.HgAssert; + +/** + * created on 2021/10/12 + */ +public class GrpcStoreNodeBuilder implements HgStoreNodeBuilder { + + private static final GrpcStoreSessionClient sessionClient = new GrpcStoreSessionClient(); + private static final GrpcStoreStreamClient streamClient = new GrpcStoreStreamClient(); + private static final AtomicLong ids = new AtomicLong(0); + private final HgStoreNodeManager nodeManager; + private Long nodeId; + private String address; + + public GrpcStoreNodeBuilder(HgStoreNodeManager nodeManager, HgPrivate hgPrivate) { + HgAssert.isArgumentNotNull(hgPrivate, "hgPrivate"); + HgAssert.isArgumentNotNull(nodeManager, "nodeManager"); + this.nodeManager = nodeManager; + } + + @Override + public GrpcStoreNodeBuilder setAddress(String address) { + HgAssert.isFalse(HgAssert.isInvalid(address), "The argument is invalid: address."); + this.address = address; + return this; + } + + @Override + public GrpcStoreNodeBuilder setNodeId(Long nodeId) { + HgAssert.isFalse(nodeId == null, "The argument is invalid: nodeId."); + this.nodeId = nodeId; + return this; + } + + @Override + public HgStoreNode build() { + // TODO: delete + if (this.nodeId == null) { + this.nodeId = ids.addAndGet(-1L); + } + + HgAssert.isFalse(this.nodeId == null, "nodeId can't to be null"); + HgAssert.isFalse(this.address == null, "address can't to be null"); + + GrpcStoreNodeImpl node = + new GrpcStoreNodeImpl(this.nodeManager, sessionClient, streamClient); + node.setNodeId(this.nodeId); + node.setAddress(this.address); + + return node; + } +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcStoreNodeImpl.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcStoreNodeImpl.java new file mode 100644 index 000000000..2c9df47a8 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcStoreNodeImpl.java @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client.grpc; + +import java.util.Objects; + +import org.apache.hugegraph.store.HgStoreSession; +import org.apache.hugegraph.store.client.HgStoreNode; +import org.apache.hugegraph.store.client.HgStoreNodeManager; + +/** + * created on 2021/10/11 + */ +class GrpcStoreNodeImpl implements HgStoreNode { + private final GrpcStoreSessionClient sessionClient; + private final GrpcStoreStreamClient streamClient; + private final HgStoreNodeManager nodeManager; + private String address; + private Long nodeId; + + GrpcStoreNodeImpl(HgStoreNodeManager nodeManager, GrpcStoreSessionClient sessionClient, + GrpcStoreStreamClient streamClient) { + this.nodeManager = nodeManager; + this.sessionClient = sessionClient; + this.streamClient = streamClient; + } + + @Override + public Long getNodeId() { + return this.nodeId; + } + + GrpcStoreNodeImpl setNodeId(Long nodeId) { + this.nodeId = nodeId; + return this; + } + + @Override + public String getAddress() { + return this.address; + } + + GrpcStoreNodeImpl setAddress(String address) { + this.address = address; + return this; + } + + @Override + public HgStoreSession openSession(String graphName) { + // HgAssert.isFalse(HgAssert.isInvalid(graphName), "the argument: graphName is invalid."); + // return new GrpcStoreNodeSessionImpl2(this, graphName,this.nodeManager, this + // .sessionClient, this + // .streamClient); + return new GrpcStoreNodeSessionImpl(this, graphName, this.nodeManager, this.sessionClient, + this.streamClient); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + GrpcStoreNodeImpl that = (GrpcStoreNodeImpl) o; + return Objects.equals(address, that.address) && Objects.equals(nodeId, that.nodeId); + } + + @Override + public int hashCode() { + return Objects.hash(address, nodeId); + } + + @Override + public String toString() { + return "storeNode: {" + + "address: \"" + address + "\"" + + ", nodeId: " + nodeId + + "}"; + } +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcStoreNodeSessionImpl.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcStoreNodeSessionImpl.java new file mode 100644 index 000000000..afa831573 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcStoreNodeSessionImpl.java @@ -0,0 +1,546 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client.grpc; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import javax.annotation.concurrent.NotThreadSafe; + +import org.apache.hugegraph.store.HgKvEntry; +import org.apache.hugegraph.store.HgKvIterator; +import org.apache.hugegraph.store.HgKvStore; +import org.apache.hugegraph.store.HgOwnerKey; +import org.apache.hugegraph.store.HgScanQuery; +import org.apache.hugegraph.store.client.HgStoreNode; +import org.apache.hugegraph.store.client.HgStoreNodeManager; +import org.apache.hugegraph.store.client.HgStoreNodeSession; +import org.apache.hugegraph.store.client.util.HgAssert; +import org.apache.hugegraph.store.client.util.HgStoreClientConfig; +import org.apache.hugegraph.store.client.util.HgStoreClientConst; +import org.apache.hugegraph.store.client.util.HgStoreClientUtil; +import org.apache.hugegraph.store.client.util.HgUuid; +import org.apache.hugegraph.store.grpc.common.GraphMethod; +import org.apache.hugegraph.store.grpc.common.Key; +import org.apache.hugegraph.store.grpc.common.OpType; +import org.apache.hugegraph.store.grpc.common.TableMethod; +import org.apache.hugegraph.store.grpc.session.BatchEntry; +import org.apache.hugegraph.store.grpc.stream.HgStoreStreamGrpc.HgStoreStreamStub; +import org.apache.hugegraph.store.grpc.stream.ScanStreamReq; + +import com.google.protobuf.ByteString; +import com.google.protobuf.UnsafeByteOperations; + +import lombok.extern.slf4j.Slf4j; + +/** + * created on 2022/01/19 + * + * @version 0.6.0 added batch get on 2022/04/06 + */ +@Slf4j +@NotThreadSafe +class GrpcStoreNodeSessionImpl implements HgStoreNodeSession { + + private static final HgStoreClientConfig hgStoreClientConfig = HgStoreClientConfig.of(); + private static final ConcurrentHashMap tables = new ConcurrentHashMap<>() {{ + put("unknown", 0); + put("g+v", 1); + put("g+oe", 2); + put("g+ie", 3); + put("g+index", 4); + put("g+task", 5); + put("g+olap", 6); + }}; + private final HgStoreNode storeNode; + private final String graphName; + private final GrpcStoreSessionClient storeSessionClient; + private final GrpcStoreStreamClient storeStreamClient; + private final HgStoreNodeManager nodeManager; + private final NotifyingExecutor notifier; + private final SwitchingExecutor switcher; + private final BatchEntry.Builder batchEntryBuilder = BatchEntry.newBuilder(); + private final Key.Builder builder = Key.newBuilder(); + private boolean isAutoCommit = true; + private String batchId; + private LinkedList batchEntries = new LinkedList<>(); + + GrpcStoreNodeSessionImpl(HgStoreNode storeNode, String graphName, + HgStoreNodeManager nodeManager, + GrpcStoreSessionClient sessionClient, + GrpcStoreStreamClient streamClient) { + HgAssert.isFalse(HgAssert.isInvalid(graphName), "the argument: graphName is invalid."); + HgAssert.isFalse(nodeManager == null, "the argument: nodeManager is null."); + HgAssert.isFalse(storeNode == null, "the argument: storeNode is null."); + HgAssert.isFalse(sessionClient == null, "the argument: sessionClient is null."); + HgAssert.isFalse(streamClient == null, "the argument: streamClient is null."); + + this.graphName = graphName; + this.storeNode = storeNode; + this.storeSessionClient = sessionClient; + this.storeStreamClient = streamClient; + this.nodeManager = nodeManager; + + this.notifier = new NotifyingExecutor(this.graphName, this.nodeManager, this); + this.switcher = SwitchingExecutor.of(); + } + + @Override + public String getGraphName() { + return graphName; + } + + @Override + public HgStoreNode getStoreNode() { + return storeNode; + } + + public Key toKey(HgOwnerKey ownerKey) { + if (ownerKey == null) { + return null; + } + return builder + .setKey(UnsafeByteOperations.unsafeWrap(ownerKey.getKey())) + .setCode(ownerKey.getKeyCode()) + .build(); + } + + @Override + public void beginTx() { + this.isAutoCommit = false; + } + + @Override + public void commit() { + try { + if (this.isAutoCommit) { + throw new IllegalStateException("It's not in tx state"); + } + if (this.batchEntries.isEmpty()) { + this.resetTx(); + return; + } + if (!this.doCommit(this.batchEntries)) { + throw new Exception("Failed to invoke doCommit"); + } + } catch (Throwable t) { + throw new RuntimeException(t); + } finally { + this.resetTx(); + } + + } + + @Override + public void rollback() { + if (this.isAutoCommit) { + throw new IllegalStateException("It's not in tx state"); + } + this.resetTx(); + } + + @Override + public boolean isTx() { + return !this.isAutoCommit; + } + + private void resetTx() { + this.isAutoCommit = true; + this.batchId = null; + this.batchEntries = new LinkedList<>(); + } + + //TODO: not support distributed tx yet. + private String getBatchId() { + if (this.isAutoCommit) { + this.batchId = HgUuid.newUUID(); + } else { + if (this.batchId == null) { + this.batchId = HgUuid.newUUID(); + } + } + return this.batchId; + } + + @Override + public boolean put(String table, HgOwnerKey ownerKey, byte[] value) { + return this.prepareBatchEntry(OpType.OP_TYPE_PUT, table, ownerKey, null, value); + } + + @Override + public boolean directPut(String table, int partitionId, HgOwnerKey key, byte[] value) { + return false; + } + + @Override + public boolean delete(String table, HgOwnerKey ownerKey) { + return this.prepareBatchEntry(OpType.OP_TYPE_DEL, table, ownerKey, null, null); + } + + @Override + public boolean deleteSingle(String table, HgOwnerKey ownerKey) { + return this.prepareBatchEntry(OpType.OP_TYPE_DEL_SINGLE, table, ownerKey, null, null); + } + + @Override + public boolean deletePrefix(String table, HgOwnerKey prefix) { + return this.prepareBatchEntry(OpType.OP_TYPE_DEL_PREFIX, table, prefix, null, null); + } + + @Override + public boolean deleteRange(String table, HgOwnerKey start, HgOwnerKey end) { + return this.prepareBatchEntry(OpType.OP_TYPE_DEL_RANGE, table, start, end, null); + } + + @Override + public boolean merge(String table, HgOwnerKey key, byte[] value) { + return this.prepareBatchEntry(OpType.OP_TYPE_MERGE, table, key, null, value); + } + + private boolean prepareBatchEntry(OpType opType, String table + , HgOwnerKey startKey, HgOwnerKey endKey, byte[] value) { + this.batchEntryBuilder.clear().setOpType(opType); + this.batchEntryBuilder.setTable(tables.get(table)); + if (startKey != null) { + this.batchEntryBuilder.setStartKey(toKey(startKey)); + } + if (endKey != null) { + this.batchEntryBuilder.setEndKey(toKey(endKey)); + } + if (value != null) { + this.batchEntryBuilder.setValue(ByteString.copyFrom(value)); + } + if (this.isAutoCommit) { + return this.doCommit(Collections.singletonList(this.batchEntryBuilder.build())); + } else { + return this.batchEntries.add(this.batchEntryBuilder.build()); + } + + } + + private boolean doCommit(List entries) { + return this.notifier.invoke( + () -> this.storeSessionClient.doBatch(this, this.getBatchId(), entries), + e -> true + ).orElse(false); + } + + @Override + public byte[] get(String table, HgOwnerKey ownerKey) { + return this.notifier.invoke( + () -> this.storeSessionClient.doGet(this, table, ownerKey) + , + e -> e.getValueResponse().getValue().toByteArray() + ).orElse(HgStoreClientConst.EMPTY_BYTES); + } + + @Override + public boolean clean(int partId) { + return this.notifier.invoke( + () -> this.storeSessionClient.doClean(this, partId) + , + e -> true + + ).orElse(false); + } + + @Override + public List batchGetOwner(String table, List keyList) { + return this.notifier.invoke( + () -> this.storeSessionClient.doBatchGet(this, table, keyList), + e -> e.getKeyValueResponse().getKvList() + .stream() + .map(kv -> (HgKvEntry) new GrpcKvEntryImpl(kv.getKey().toByteArray() + , kv.getValue().toByteArray(), kv.getCode()) + ) + .collect(Collectors.toList())) + .orElse((List) HgStoreClientConst.EMPTY_LIST); + } + + @Override + public HgKvIterator batchPrefix(String table, List keyList) { + return GrpcKvIteratorImpl.of(this, + this.storeStreamClient.doBatchScanOneShot(this, + HgScanQuery.prefixOf( + table, + keyList)) + ); + } + + @Override + public boolean existsTable(String table) { + return this.notifier.invoke( + () -> this.storeSessionClient.doTable(this, table, + TableMethod.TABLE_METHOD_EXISTS), + e -> true) + .orElse(false); + } + + @Override + public boolean createTable(String table) { + return this.notifier.invoke( + () -> this.storeSessionClient.doTable(this, table, + TableMethod.TABLE_METHOD_CREATE), + e -> true) + .orElse(false); + } + + @Override + public boolean deleteTable(String table) { + return this.notifier.invoke( + () -> this.storeSessionClient.doTable(this, table, + TableMethod.TABLE_METHOD_DELETE), + e -> true) + .orElse(false); + } + + @Override + public boolean dropTable(String table) { + return this.notifier.invoke( + () -> this.storeSessionClient.doTable(this, table, + TableMethod.TABLE_METHOD_DROP), + e -> true) + .orElse(false); + } + + @Override + public boolean deleteGraph(String graph) { + return this.notifier.invoke( + () -> this.storeSessionClient.doGraph(this, graph, + GraphMethod.GRAPH_METHOD_DELETE), + e -> true) + .orElse(false); + } + + @Override + public boolean truncate() { + return this.notifier.invoke( + () -> this.storeSessionClient.doTable(this, + HgStoreClientConst.EMPTY_TABLE + , TableMethod.TABLE_METHOD_TRUNCATE), + e -> true) + .orElse(false); + } + + @Override + public HgKvIterator scanIterator(String table) { + return GrpcKvIteratorImpl.of(this, this.storeStreamClient.doScan(this, table, 0)); + } + + @Override + public HgKvIterator scanIterator(String table, long limit) { + return this.switcher.invoke(getSwitcherSupplier(limit) + , () -> GrpcKvIteratorImpl.of(this, this.storeStreamClient.doScan + (this, table, + limit)) + , () -> GrpcKvIteratorImpl.of(this, this.storeStreamClient.doScanOneShot + (this, table, + limit)) + ).get(); + } + + @Override + public HgKvIterator scanIterator(ScanStreamReq.Builder builder) { + HgStoreStreamStub stub = getStub(); + KvPageScanner scanner = new KvPageScanner(this, + stub, + builder); + return GrpcKvIteratorImpl.of(this, scanner); + } + + @Override + public long count(String table) { + return this.storeSessionClient.count(this, table); + } + + + @Override + public HgKvIterator scanIterator(String table, byte[] query) { + return GrpcKvIteratorImpl.of(this, this.storeStreamClient.doScan(this, table, 0, query)); + } + + private HgStoreStreamStub getStub() { + return this.storeStreamClient.getStub(this); + } + + // @Override + // public HgKvIterator scanIterator(ScanStreamReq scanReq) { + // KvPageScanner6 scanner = new KvPageScanner6(this, + // getStub(), + // scanReq.toBuilder()); + // return GrpcKvIteratorImpl.of(this, scanner); + // } + + @Override + public HgKvIterator scanIterator(String table, long limit, byte[] query) { + return this.switcher.invoke(getSwitcherSupplier(limit) + , () -> GrpcKvIteratorImpl.of(this, this.storeStreamClient.doScan + (this, table, + limit, query)) + , () -> GrpcKvIteratorImpl.of(this, this.storeStreamClient.doScanOneShot + (this, table, + limit, query)) + ).get(); + } + + @Override + public HgKvIterator scanIterator(String table, HgOwnerKey keyPrefix) { + return GrpcKvIteratorImpl.of(this, + this.storeStreamClient.doScan(this, table, keyPrefix, 0)); + } + + @Override + public HgKvIterator scanIterator(String table, HgOwnerKey keyPrefix, long limit) { + return this.switcher.invoke(getSwitcherSupplier(limit), + () -> GrpcKvIteratorImpl.of(this, + this.storeStreamClient.doScan(this, + table, + keyPrefix, + limit)), + () -> GrpcKvIteratorImpl.of(this, + this.storeStreamClient.doScanOneShot( + this, + table, + keyPrefix, + limit))) + .get(); + } + + @Override + public HgKvIterator scanIterator(String table, HgOwnerKey keyPrefix, long limit, + byte[] query) { + return this.switcher.invoke(getSwitcherSupplier(limit), + () -> GrpcKvIteratorImpl.of(this, + this.storeStreamClient.doScan( + this, + table, + keyPrefix, + limit, + query)), + () -> GrpcKvIteratorImpl.of(this, + this.storeStreamClient.doScanOneShot( + this, + table, + keyPrefix, + limit, + query))) + .get(); + } + + @Override + public HgKvIterator scanIterator(String table, HgOwnerKey startKey, + HgOwnerKey endKey) { + return scanIterator(table, startKey, endKey, 0, HgKvStore.SCAN_ANY, null); + } + + @Override + public HgKvIterator scanIterator(String table, HgOwnerKey startKey, + HgOwnerKey endKey, long limit) { + return scanIterator(table, startKey, endKey, limit, + HgStoreClientUtil.isValid(endKey) ? HgStoreClientConst.SCAN_TYPE_RANGE : + HgStoreClientConst.SCAN_TYPE_ANY, null); + } + + @Override + public HgKvIterator scanIterator(String table, HgOwnerKey startKey, + HgOwnerKey endKey, long limit, byte[] query) { + return scanIterator(table, startKey, endKey, limit, + HgStoreClientUtil.isValid(endKey) ? HgStoreClientConst.SCAN_TYPE_RANGE : + HgStoreClientConst.SCAN_TYPE_ANY, query); + } + + @Override + public HgKvIterator scanIterator(String table, HgOwnerKey startKey, + HgOwnerKey endKey, + long limit, int scanType, byte[] query) { + + return this.switcher.invoke(getSwitcherSupplier(limit), + () -> GrpcKvIteratorImpl.of(this, + this.storeStreamClient.doScan( + this, + table, + startKey, + endKey, + limit, + scanType, + query)), + () -> GrpcKvIteratorImpl.of(this, + this.storeStreamClient.doScanOneShot( + this, + table, + startKey, + endKey, + limit, + scanType, + query))) + .get(); + + } + + @Override + public HgKvIterator scanIterator(String table, int codeFrom, int codeTo, + int scanType, byte[] query) { + //TODO: Should be changed when start using hashcode as partitionId. + if (log.isDebugEnabled()) { + log.debug("scanIterator-scanType: {}", scanType); + } + return GrpcKvIteratorImpl.of(this, + this.storeStreamClient.doScan(this, table + , HgOwnerKey.newEmpty().codeToKey(codeFrom) + , HgOwnerKey.newEmpty().codeToKey(codeTo) + , HgStoreClientConst.NO_LIMIT + , HgKvStore.SCAN_PREFIX_BEGIN | + HgKvStore.SCAN_HASHCODE | scanType + , query + ) + ); + } + + + @Override + public List> scanBatch(HgScanQuery scanQuery) { + return Collections.singletonList(GrpcKvIteratorImpl.of(this, + this.storeStreamClient.doBatchScan( + this, scanQuery) + )); + } + + @Override + public KvCloseableIterator> scanBatch2(HgScanQuery scanQuery) { + throw new RuntimeException("not implemented"); + } + + @Override + public KvCloseableIterator> scanBatch3(HgScanQuery scanQuery, + KvCloseableIterator iterator) { + return this.storeStreamClient.doBatchScan3(this, scanQuery, iterator); + } + + private Supplier getSwitcherSupplier(long limit) { + return () -> limit <= 0 || limit > hgStoreClientConfig.getNetKvScannerPageSize(); + } + + @Override + public String toString() { + return "storeNodeSession: {" + storeNode + ", graphName: \"" + graphName + "\"}"; + } +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcStoreSessionClient.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcStoreSessionClient.java new file mode 100644 index 000000000..9a3b03f24 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcStoreSessionClient.java @@ -0,0 +1,160 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client.grpc; + +import static org.apache.hugegraph.store.client.grpc.KvBatchUtil.getHeader; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +import javax.annotation.concurrent.ThreadSafe; + +import org.apache.hugegraph.store.HgOwnerKey; +import org.apache.hugegraph.store.client.HgStoreNodeSession; +import org.apache.hugegraph.store.grpc.common.GraphMethod; +import org.apache.hugegraph.store.grpc.common.ScanMethod; +import org.apache.hugegraph.store.grpc.common.TableMethod; +import org.apache.hugegraph.store.grpc.session.Agg; +import org.apache.hugegraph.store.grpc.session.BatchEntry; +import org.apache.hugegraph.store.grpc.session.BatchGetReq; +import org.apache.hugegraph.store.grpc.session.BatchReq; +import org.apache.hugegraph.store.grpc.session.BatchWriteReq; +import org.apache.hugegraph.store.grpc.session.CleanReq; +import org.apache.hugegraph.store.grpc.session.FeedbackRes; +import org.apache.hugegraph.store.grpc.session.GetReq; +import org.apache.hugegraph.store.grpc.session.GraphReq; +import org.apache.hugegraph.store.grpc.session.HgStoreSessionGrpc; +import org.apache.hugegraph.store.grpc.session.HgStoreSessionGrpc.HgStoreSessionBlockingStub; +import org.apache.hugegraph.store.grpc.session.TableReq; +import org.apache.hugegraph.store.grpc.stream.ScanStreamReq; + +import io.grpc.Deadline; +import io.grpc.ManagedChannel; +import lombok.extern.slf4j.Slf4j; + +/** + * created on 2021/11/18 + * + * @version 0.5.0 + */ +@Slf4j +@ThreadSafe +class GrpcStoreSessionClient extends AbstractGrpcClient { + + @Override + public HgStoreSessionBlockingStub getBlockingStub(ManagedChannel channel) { + HgStoreSessionBlockingStub stub; + stub = HgStoreSessionGrpc.newBlockingStub(channel); + return stub; + } + + private HgStoreSessionBlockingStub getBlockingStub(HgStoreNodeSession nodeSession) { + HgStoreSessionBlockingStub stub = + (HgStoreSessionBlockingStub) getBlockingStub( + nodeSession.getStoreNode().getAddress()); + return stub; + } + + FeedbackRes doGet(HgStoreNodeSession nodeSession, String table, HgOwnerKey ownerKey) { + if (log.isDebugEnabled()) { + log.debug("doGet: {}-{}-{}-{}", nodeSession, table, ownerKey, GetReq.newBuilder() + .setHeader( + GrpcUtil.getHeader( + nodeSession)) + .setTk(GrpcUtil.toTk( + table, + ownerKey)) + .build()); + } + return this.getBlockingStub(nodeSession) + .get2(GetReq.newBuilder() + .setHeader(GrpcUtil.getHeader(nodeSession)) + .setTk(GrpcUtil.toTk(table, ownerKey)) + .build() + ); + } + + FeedbackRes doClean(HgStoreNodeSession nodeSession, int partId) { + return this.getBlockingStub(nodeSession) + .clean(CleanReq.newBuilder() + .setHeader(GrpcUtil.getHeader(nodeSession)) + .setPartition(partId) + .build() + ); + } + + FeedbackRes doBatchGet(HgStoreNodeSession nodeSession, String table, List keyList) { + BatchGetReq.Builder builder = BatchGetReq.newBuilder(); + builder.setHeader(GrpcUtil.getHeader(nodeSession)).setTable(table); + + for (HgOwnerKey key : keyList) { + builder.addKey(GrpcUtil.toKey(key)); + } + + if (log.isDebugEnabled()) { + log.debug("batchGet2: {}-{}-{}-{}", nodeSession, table, keyList, builder.build()); + } + return this.getBlockingStub(nodeSession).batchGet2(builder.build()); + + } + + FeedbackRes doBatch(HgStoreNodeSession nodeSession, String batchId, List entries) { + BatchWriteReq.Builder writeReq = BatchWriteReq.newBuilder(); + writeReq.addAllEntry(entries); + return this.getBlockingStub(nodeSession) + .batch(BatchReq.newBuilder() + .setHeader(GrpcUtil.getHeader(nodeSession)) + .setWriteReq(writeReq) + .setBatchId(batchId) + .build() + ); + } + + FeedbackRes doTable(HgStoreNodeSession nodeSession, String table, TableMethod method) { + return this.getBlockingStub(nodeSession) + .table(TableReq.newBuilder() + .setHeader(GrpcUtil.getHeader(nodeSession)) + .setTableName(table) + .setMethod(method) + .build() + ); + } + + FeedbackRes doGraph(HgStoreNodeSession nodeSession, String graph, GraphMethod method) { + return this.getBlockingStub(nodeSession) + .graph(GraphReq.newBuilder() + .setHeader(GrpcUtil.getHeader(nodeSession)) + .setGraphName(graph) + .setMethod(method) + .build() + ); + } + + public long count(HgStoreNodeSession nodeSession, String table) { + Agg agg = this.getBlockingStub(nodeSession).withDeadline(Deadline.after(24, TimeUnit.HOURS)) + .count(ScanStreamReq.newBuilder() + .setHeader(getHeader(nodeSession)) + .setTable(table) + .setMethod(ScanMethod.ALL) + .build() + ); + return agg.getCount(); + } +} + + diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcStoreStateClient.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcStoreStateClient.java new file mode 100644 index 000000000..e53f30e0e --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcStoreStateClient.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client.grpc; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.annotation.concurrent.ThreadSafe; + +import org.apache.hugegraph.pd.client.PDClient; +import org.apache.hugegraph.pd.client.PDConfig; +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.store.grpc.state.HgStoreStateGrpc; +import org.apache.hugegraph.store.grpc.state.HgStoreStateGrpc.HgStoreStateBlockingStub; +import org.apache.hugegraph.store.grpc.state.ScanState; +import org.apache.hugegraph.store.grpc.state.SubStateReq; + +import io.grpc.ManagedChannel; +import io.grpc.stub.AbstractBlockingStub; +import lombok.extern.slf4j.Slf4j; + +/** + * + */ +@Slf4j +@ThreadSafe +public class GrpcStoreStateClient extends AbstractGrpcClient { + + private final PDConfig pdConfig; + private final PDClient pdClient; + + public GrpcStoreStateClient(PDConfig pdConfig) { + this.pdConfig = pdConfig; + pdClient = PDClient.create(this.pdConfig); + } + + + public Set getScanState() throws Exception { + try { + List activeStores = pdClient.getActiveStores(); + Set states = activeStores.parallelStream().map(node -> { + String address = node.getAddress(); + HgStoreStateBlockingStub stub = (HgStoreStateBlockingStub) getBlockingStub(address); + SubStateReq req = SubStateReq.newBuilder().build(); + return stub.getScanState(req); + }).collect(Collectors.toSet()); + return states; + } catch (Exception e) { + throw e; + } + + + } + + @Override + public AbstractBlockingStub getBlockingStub(ManagedChannel channel) { + HgStoreStateBlockingStub stub; + stub = HgStoreStateGrpc.newBlockingStub(channel); + return stub; + } +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcStoreStreamClient.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcStoreStreamClient.java new file mode 100644 index 000000000..e7be4ac94 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcStoreStreamClient.java @@ -0,0 +1,219 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client.grpc; + +import javax.annotation.concurrent.ThreadSafe; + +import org.apache.hugegraph.store.HgKvEntry; +import org.apache.hugegraph.store.HgKvIterator; +import org.apache.hugegraph.store.HgOwnerKey; +import org.apache.hugegraph.store.HgScanQuery; +import org.apache.hugegraph.store.client.HgStoreNodeSession; +import org.apache.hugegraph.store.grpc.common.Kv; +import org.apache.hugegraph.store.grpc.stream.HgStoreStreamGrpc; +import org.apache.hugegraph.store.grpc.stream.HgStoreStreamGrpc.HgStoreStreamBlockingStub; +import org.apache.hugegraph.store.grpc.stream.HgStoreStreamGrpc.HgStoreStreamStub; + +import io.grpc.ManagedChannel; +import io.grpc.stub.AbstractAsyncStub; +import io.grpc.stub.AbstractBlockingStub; +import lombok.extern.slf4j.Slf4j; + +/** + * created on 2021/10/19 + * + * @version 1.1.1 added synchronized in getChannel. + */ +@Slf4j +@ThreadSafe +public class GrpcStoreStreamClient extends AbstractGrpcClient { + + public HgStoreStreamStub getStub(HgStoreNodeSession nodeSession) { + return (HgStoreStreamStub) getAsyncStub(nodeSession.getStoreNode().getAddress()); + } + + @Override + public AbstractAsyncStub getAsyncStub(ManagedChannel channel) { + return HgStoreStreamGrpc.newStub(channel); + } + + private HgStoreStreamBlockingStub getBlockingStub(HgStoreNodeSession nodeSession) { + return (HgStoreStreamBlockingStub) getBlockingStub(nodeSession.getStoreNode().getAddress()); + } + + @Override + public AbstractBlockingStub getBlockingStub(ManagedChannel channel) { + return HgStoreStreamGrpc.newBlockingStub(channel); + } + + KvCloseableIterator doScanOneShot(HgStoreNodeSession nodeSession, String table, long limit, + byte[] query) { + return KvOneShotScanner.scanAll(nodeSession + , this.getBlockingStub(nodeSession) + , table + , limit + , query + ); + } + + KvCloseableIterator doScanOneShot(HgStoreNodeSession nodeSession, String table, + long limit) { + return KvOneShotScanner.scanAll(nodeSession + , this.getBlockingStub(nodeSession) + , table + , limit + , null + ); + } + + KvCloseableIterator doScanOneShot(HgStoreNodeSession nodeSession, String table, + HgOwnerKey prefix, long limit) { + return KvOneShotScanner.scanPrefix(nodeSession + , this.getBlockingStub(nodeSession) + , table + , prefix + , limit + , null + ); + } + + KvCloseableIterator doScanOneShot(HgStoreNodeSession nodeSession, String table, + HgOwnerKey prefix, long limit, + byte[] query) { + return KvOneShotScanner.scanPrefix(nodeSession + , this.getBlockingStub(nodeSession) + , table + , prefix + , limit + , query + ); + } + + KvCloseableIterator doScanOneShot(HgStoreNodeSession nodeSession, String table, + HgOwnerKey startKey, + HgOwnerKey endKey + , long limit + , int scanType + , byte[] query) { + + return KvOneShotScanner.scanRange(nodeSession + , this.getBlockingStub(nodeSession) + , table + , startKey + , endKey + , limit + , scanType + , query + ); + } + + + KvCloseableIterator doScan(HgStoreNodeSession nodeSession + , String table + , long limit + , byte[] query) { + + return KvPageScanner.scanAll(nodeSession + , this.getStub(nodeSession) + , table + , limit + , query + ); + } + + KvCloseableIterator doScan(HgStoreNodeSession nodeSession + , String table + , long limit) { + + return KvPageScanner.scanAll(nodeSession + , this.getStub(nodeSession) + , table + , limit + , null + ); + } + + KvCloseableIterator doScan(HgStoreNodeSession nodeSession + , String table + , HgOwnerKey prefix + , long limit) { + + return KvPageScanner.scanPrefix(nodeSession + , this.getStub(nodeSession) + , table + , prefix + , limit + , null + ); + } + + KvCloseableIterator doScan(HgStoreNodeSession nodeSession + , String table + , HgOwnerKey prefix + , long limit + , byte[] query) { + + return KvPageScanner.scanPrefix(nodeSession + , this.getStub(nodeSession) + , table + , prefix + , limit + , query + ); + } + + KvCloseableIterator doScan(HgStoreNodeSession nodeSession + , String table + , HgOwnerKey startKey + , HgOwnerKey endKey + , long limit + , int scanType + , byte[] query) { + + return KvPageScanner.scanRange(nodeSession + , this.getStub(nodeSession) + , table + , startKey + , endKey + , limit + , scanType + , query + ); + } + + KvCloseableIterator doBatchScan(HgStoreNodeSession nodeSession, HgScanQuery scanQuery) { + return KvBatchScanner5.scan(nodeSession, this.getStub(nodeSession), scanQuery); + } + + + // 返回多个小的迭代器,允许上层并行处理 + KvCloseableIterator> doBatchScan3(HgStoreNodeSession nodeSession, + HgScanQuery scanQuery, + KvCloseableIterator iterator) { + KvBatchScanner.scan(this.getStub(nodeSession), nodeSession.getGraphName(), scanQuery, + iterator); + return iterator; + } + + KvCloseableIterator doBatchScanOneShot(HgStoreNodeSession nodeSession, + HgScanQuery scanQuery) { + return KvBatchOneShotScanner.scan(nodeSession, this.getBlockingStub(nodeSession), + scanQuery); + } + +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcUtil.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcUtil.java new file mode 100644 index 000000000..766aa4ae3 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/GrpcUtil.java @@ -0,0 +1,149 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client.grpc; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.apache.hugegraph.store.HgKvEntry; +import org.apache.hugegraph.store.HgOwnerKey; +import org.apache.hugegraph.store.client.HgStoreNodeSession; +import org.apache.hugegraph.store.client.util.HgStoreClientConst; +import org.apache.hugegraph.store.grpc.common.Header; +import org.apache.hugegraph.store.grpc.common.Key; +import org.apache.hugegraph.store.grpc.common.Kv; +import org.apache.hugegraph.store.grpc.common.Tk; +import org.apache.hugegraph.store.grpc.common.Tkv; +import org.apache.hugegraph.store.grpc.common.Tp; +import org.apache.hugegraph.store.grpc.common.Tse; + +import com.google.protobuf.ByteString; + +import io.grpc.Status; +import io.grpc.StatusRuntimeException; + +/** + * 2022/1/19 + */ +final class GrpcUtil { + + private static final ThreadLocal keyBuilder = new ThreadLocal(); + + static Header getHeader(HgStoreNodeSession nodeSession) { + return Header.newBuilder() + .setGraph(nodeSession.getGraphName()) + .build(); + } + + static Tk toTk(String table, HgOwnerKey ownerKey) { + return Tk.newBuilder() + .setTable(table) + .setKey(ByteString.copyFrom(ownerKey.getKey())) + .setCode(ownerKey.getKeyCode()) + .build(); + } + + static Key.Builder getOwnerKeyBuilder() { + Key.Builder builder = keyBuilder.get(); + if (builder == null) { + builder = Key.newBuilder(); + // TODO 线程级变量,寻找删除时机 + keyBuilder.set(builder); + } + return builder; + } + + static Key toKey(HgOwnerKey ownerKey, Key.Builder builder) { + if (ownerKey == null) { + return null; + } + return builder + .setKey(ByteString.copyFrom(ownerKey.getKey())) + .setCode(ownerKey.getKeyCode()) + .build(); + } + + + static Key toKey(HgOwnerKey ownerKey) { + if (ownerKey == null) { + return null; + } + Key.Builder builder = keyBuilder.get(); + if (builder == null) { + builder = Key.newBuilder(); + // TODO 线程级变量,寻找删除时机 + keyBuilder.set(builder); + } + return builder + .setKey(ByteString.copyFrom(ownerKey.getKey())) + .setCode(ownerKey.getKeyCode()) + .build(); + } + + static Tkv toTkv(String table, HgOwnerKey ownerKey, byte[] value) { + return Tkv.newBuilder() + .setTable(table) + .setKey(ByteString.copyFrom(ownerKey.getKey())) + .setValue(ByteString.copyFrom(value)) + .setCode(ownerKey.getKeyCode()) + .build(); + } + + static Tp toTp(String table, HgOwnerKey ownerKey) { + return Tp.newBuilder() + .setTable(table) + .setPrefix(ByteString.copyFrom(ownerKey.getKey())) + .setCode(ownerKey.getKeyCode()) + .build(); + } + + static Tse toTse(String table, HgOwnerKey startKey, HgOwnerKey endKey) { + return Tse.newBuilder() + .setTable(table) + .setStart(toKey(startKey)) + .setEnd(toKey(endKey)) + .build(); + + } + + static List toList(List kvList) { + if (kvList == null || kvList.isEmpty()) { + return HgStoreClientConst.EMPTY_LIST; + } + + Iterator iter = kvList.iterator(); + List resList = new ArrayList<>(kvList.size()); + + while (iter.hasNext()) { + Kv entry = iter.next(); + resList.add(new GrpcKvEntryImpl(entry.getKey().toByteArray(), + entry.getValue().toByteArray(), entry.getCode())); + } + + return resList; + } + + static StatusRuntimeException toErr(String msg) { + return new StatusRuntimeException(Status.UNKNOWN.withDescription(msg)); + } + + static ByteString toBs(byte[] bytes) { + return ByteString.copyFrom((bytes != null) ? bytes : HgStoreClientConst.EMPTY_BYTES); + } +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/KvBatchOneShotScanner.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/KvBatchOneShotScanner.java new file mode 100644 index 000000000..26b4e2019 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/KvBatchOneShotScanner.java @@ -0,0 +1,123 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client.grpc; + +import static org.apache.hugegraph.store.client.grpc.KvBatchUtil.EMPTY_POSITION; +import static org.apache.hugegraph.store.client.grpc.KvBatchUtil.createQueryReq; +import static org.apache.hugegraph.store.client.grpc.KvBatchUtil.getHeader; + +import java.util.Iterator; +import java.util.List; + +import javax.annotation.concurrent.NotThreadSafe; + +import org.apache.hugegraph.store.HgPageSize; +import org.apache.hugegraph.store.HgScanQuery; +import org.apache.hugegraph.store.HgSeekAble; +import org.apache.hugegraph.store.client.HgStoreNodeSession; +import org.apache.hugegraph.store.grpc.common.Kv; +import org.apache.hugegraph.store.grpc.stream.HgStoreStreamGrpc; +import org.apache.hugegraph.store.grpc.stream.ScanStreamBatchReq; + +import lombok.extern.slf4j.Slf4j; + +/** + * created on 2022/04/08 + */ +@Slf4j +@NotThreadSafe +class KvBatchOneShotScanner implements KvCloseableIterator, HgPageSize, HgSeekAble { + private final HgStoreNodeSession nodeSession; + private final HgStoreStreamGrpc.HgStoreStreamBlockingStub stub; + private final HgScanQuery scanQuery; + + private Iterator iterator; + private List list = null; + + private KvBatchOneShotScanner(HgStoreNodeSession nodeSession, + HgStoreStreamGrpc.HgStoreStreamBlockingStub stub, + HgScanQuery scanQuery) { + + this.nodeSession = nodeSession; + this.stub = stub; + this.scanQuery = scanQuery; + } + + public static KvCloseableIterator scan(HgStoreNodeSession nodeSession, + HgStoreStreamGrpc.HgStoreStreamBlockingStub stub, + HgScanQuery scanQuery) { + + return new KvBatchOneShotScanner(nodeSession, stub, scanQuery); + } + + private ScanStreamBatchReq createReq() { + return ScanStreamBatchReq.newBuilder() + .setHeader(getHeader(this.nodeSession)) + .setQueryRequest(createQueryReq(this.scanQuery, Integer.MAX_VALUE)) + .build(); + } + + private Iterator createIterator() { + this.list = this.stub.scanBatchOneShot(this.createReq()).getDataList(); + return this.list.iterator(); + } + + /*** Iterator ***/ + @Override + public boolean hasNext() { + if (this.iterator == null) { + this.iterator = this.createIterator(); + } + return this.iterator.hasNext(); + } + + @Override + public Kv next() { + if (this.iterator == null) { + this.iterator = this.createIterator(); + } + return this.iterator.next(); + } + + @Override + public long getPageSize() { + return Integer.MAX_VALUE; + } + + @Override + public boolean isPageEmpty() { + return !this.iterator.hasNext(); + } + + @Override + public byte[] position() { + //TODO: to implement + return EMPTY_POSITION; + } + + @Override + public void seek(byte[] position) { + //TODO: to implement + } + + @Override + public void close() { + //Nothing to do + } + +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/KvBatchScanner.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/KvBatchScanner.java new file mode 100644 index 000000000..0a69d7cbc --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/KvBatchScanner.java @@ -0,0 +1,406 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client.grpc; + +import static org.apache.hugegraph.store.client.grpc.KvBatchUtil.createQueryReq; + +import java.io.Closeable; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.function.BiFunction; +import java.util.function.Supplier; + +import javax.annotation.concurrent.NotThreadSafe; + +import org.apache.hugegraph.store.HgKvEntry; +import org.apache.hugegraph.store.HgKvIterator; +import org.apache.hugegraph.store.HgKvOrderedIterator; +import org.apache.hugegraph.store.HgOwnerKey; +import org.apache.hugegraph.store.HgScanQuery; +import org.apache.hugegraph.store.buffer.KVByteBuffer; +import org.apache.hugegraph.store.client.util.PropertyUtil; +import org.apache.hugegraph.store.grpc.common.Header; +import org.apache.hugegraph.store.grpc.common.ScanOrderType; +import org.apache.hugegraph.store.grpc.stream.HgStoreStreamGrpc; +import org.apache.hugegraph.store.grpc.stream.KvStream; +import org.apache.hugegraph.store.grpc.stream.ScanReceiptRequest; +import org.apache.hugegraph.store.grpc.stream.ScanStreamBatchReq; + +import io.grpc.stub.StreamObserver; +import lombok.extern.slf4j.Slf4j; + +/** + * 批量流式查询客户端实现类 + *

+ * created on 2022/07/23 + * + * @version 3.0.0 + */ +@Slf4j +@NotThreadSafe +public class KvBatchScanner implements Closeable { + + static final Supplier> NO_DATA = () -> null; + static int maxTaskSizePerStore = PropertyUtil.getInt("net.kv.scanner.task.size", 8); + private final StreamObserver sender; // 命令发送器 + private final KvBatchScannerMerger notifier; // 数据通知 + private final String graphName; // 图名 + private final HgScanQuery scanQuery; + private final ScanReceiptRequest.Builder responseBuilder = ScanReceiptRequest.newBuilder(); + private final KvBatchReceiver receiver; + volatile int currentSeqNo = 0; + private volatile boolean running; + + public KvBatchScanner( + HgStoreStreamGrpc.HgStoreStreamStub stub, + String graphName, + HgScanQuery scanQuery, + KvCloseableIterator iterator) { + + this.graphName = graphName; + this.notifier = (KvBatchScannerMerger) iterator; + this.notifier.registerScanner(this); + this.running = true; + this.scanQuery = scanQuery; + receiver = + new KvBatchReceiver(this, scanQuery.getOrderType() == ScanOrderType.ORDER_STRICT); + sender = stub.scanBatch2(receiver); + sendQuery(this.scanQuery); // 发送查询请求 + } + + /** + * 构建流式查询迭代器 + * scanQuery进行拆分,启动多个流式请求,提升store的并发性 + * + * @param scanQuery scanQuery + * @param handler task handler + * @return data merger iterator + */ + public static KvCloseableIterator ofMerger( + HgScanQuery scanQuery, BiFunction handler) { + KvBatchScannerMerger merger; + if (scanQuery.getOrderType() == ScanOrderType.ORDER_STRICT) { + merger = new KvBatchScannerMerger.SortedScannerMerger( + new TaskSplitter(scanQuery, handler)); + } else { + merger = new KvBatchScannerMerger(new TaskSplitter(scanQuery, handler)); + } + merger.startTask(); + return merger; + } + + public static void scan( + HgStoreStreamGrpc.HgStoreStreamStub stub, + String graphName, + HgScanQuery scanQuery, + KvCloseableIterator iterator) { + new KvBatchScanner(stub, graphName, scanQuery, iterator); + } + + /** + * 发送查询请求 + * + * @param query scan query + */ + public void sendQuery(HgScanQuery query) { + synchronized (this.sender) { + if (running) { + this.sender.onNext( + ScanStreamBatchReq.newBuilder() + .setHeader( + Header.newBuilder().setGraph(graphName).build()) + .setQueryRequest(createQueryReq(query, 0)) + .build()); + } + } + } + + /** + * 发送应答 + */ + public void sendResponse() { + try { + sendResponse(currentSeqNo); + } catch (Exception e) { + log.error("exception", e); + } + } + + public void sendResponse(int seqNo) { + currentSeqNo = seqNo; + synchronized (this.sender) { + if (running) { + this.sender.onNext( + ScanStreamBatchReq.newBuilder() + .setHeader( + Header.newBuilder().setGraph(graphName).build()) + .setReceiptRequest( + responseBuilder.setTimes(seqNo).build()) + .build()); + } + } + } + + public void dataArrived(Supplier> supplier) throws + InterruptedException { + notifier.dataArrived(this, supplier); + } + + /** + * 数据接收结束 + */ + public void dataComplete() { + close(); + } + + // 流被关闭 + @Override + public void close() { + try { + if (notifier.unregisterScanner(this) < 0) { + notifier.dataArrived(this, NO_DATA); // 任务结束,唤醒队列 + } + } catch (InterruptedException e) { + log.error("exception ", e); + } + synchronized (this.sender) { + try { + if (running) { + sender.onCompleted(); + } + } catch (Exception e) { + } + running = false; + } + } + + /** + * 任务拆分器 + */ + static class TaskSplitter { + final HgScanQuery scanQuery; + final BiFunction taskHandler; + private KvBatchScannerMerger notifier; + private Iterator prefixItr; + private int maxTaskSize = 0; // 最大并行任务数 + private int maxBatchSize = PropertyUtil.getInt("net.kv.scanner.batch.size", 1000); + // 每批次最大点数量 + private volatile boolean finished = false; + private volatile boolean splitting = false; + private volatile int nextKeySerialNo = 1; + + public TaskSplitter(HgScanQuery scanQuery, + BiFunction handler) { + this.scanQuery = scanQuery; + this.taskHandler = handler; + if (scanQuery.getScanMethod() == HgScanQuery.ScanMethod.PREFIX) { + if (scanQuery.getPrefixItr() != null) { + prefixItr = scanQuery.getPrefixItr(); + } else { + prefixItr = scanQuery.getPrefixList().listIterator(); + } + } + } + + public void setNotifier(KvBatchScannerMerger notifier) { + this.notifier = notifier; + } + + public boolean isFinished() { + return finished; + } + + /** + * 评估最大任务数 + */ + private void evaluateMaxTaskSize() { + if (maxTaskSize == 0) { // 根据第一批次任务,得到store数量,然后计算最大任务数 + if (scanQuery.getOrderType() == ScanOrderType.ORDER_STRICT) { + maxTaskSize = 1; // 点排序,每台机器一个流, 所有store流结束后才能启动其他流 + } else { + maxTaskSize = this.notifier.getScannerCount() * maxTaskSizePerStore; + } + maxBatchSize = this.notifier.getScannerCount() * maxBatchSize; // 每台机器最多1000条 + + /* + * Limit少于10000时启动一个流,节省网络带宽 + */ + if (scanQuery.getLimit() < maxBatchSize * 30L) { + maxTaskSize = 1; + } + } + } + + /** + * 拆分任务,任务拆分为多个grpc请求 + */ + public void splitTask() { + if (this.finished || this.splitting) { + return; + } + synchronized (this) { + if (this.finished) { + return; + } + this.splitting = true; + if (scanQuery.getScanMethod() == HgScanQuery.ScanMethod.PREFIX) { + if (prefixItr.hasNext() && + (maxTaskSize == 0 || notifier.getScannerCount() < maxTaskSize)) { + List keys = new ArrayList<>(maxBatchSize); + for (int i = 0; i < maxBatchSize && prefixItr.hasNext(); i++) { + keys.add(prefixItr.next().setSerialNo(nextKeySerialNo++)); + } + taskHandler.apply( + HgScanQuery.prefixOf(scanQuery.getTable(), keys, + scanQuery.getOrderType()), this.notifier); + // 评估最大任务数 + evaluateMaxTaskSize(); + if (this.notifier.getScannerCount() < this.maxTaskSize) { + splitTask(); // 未达到最大任务数,继续拆分 + } + } + this.finished = !prefixItr.hasNext(); + } else { + taskHandler.apply(scanQuery, this.notifier); + this.finished = true; + } + this.splitting = false; + } + } + + public synchronized void close() { + finished = true; + } + } + + /** + * 查询结果接收器 + */ + static class KvBatchReceiver implements StreamObserver { + KvBatchScanner scanner; + boolean sortByVertex; + + KvBatchReceiver(KvBatchScanner scanner, boolean sortByVertex) { + this.scanner = scanner; + this.sortByVertex = sortByVertex; + } + + @Override + public void onNext(KvStream value) { + try { + ByteBuffer buffer = value.getStream(); + int seqNo = value.getSeqNo(); + boolean isOver = value.getOver(); + scanner.dataArrived( + () -> { + scanner.sendResponse(seqNo); + if (isOver) { + scanner.dataComplete(); + } + return new KVBytesIterator(buffer, sortByVertex, scanner); + }); + } catch (InterruptedException e) { + close(); + log.error("exception ", e); + throw new RuntimeException(e); + } + } + + @Override + public void onError(Throwable t) { + log.error("exception ", t); + close(); + } + + @Override + public void onCompleted() { + close(); + } + + private void close() { + if (scanner != null) { + scanner.close(); + } + } + } + + static class KVBytesIterator implements HgKvOrderedIterator { + private final KvBatchScanner scanner; + KVByteBuffer buffer; + HgKvEntry entry; + int sn; + boolean hasSN; + + public KVBytesIterator(ByteBuffer buffer, boolean hasNo, KvBatchScanner scanner) { + this.buffer = new KVByteBuffer(buffer); + this.hasSN = hasNo; + this.scanner = scanner; + } + + @Override + public void close() { + // this.scanner.close(); + } + + @Override + public byte[] key() { + return entry.key(); + } + + @Override + public byte[] value() { + return entry.value(); + } + + @Override + public byte[] position() { + return new byte[0]; + } + + @Override + public void seek(byte[] position) { + throw new RuntimeException("not implemented"); + } + + @Override + public boolean hasNext() { + return buffer.hasRemaining(); + } + + @Override + public HgKvEntry next() { + if (hasSN) { + sn = buffer.getInt(); + } + entry = new GrpcKvEntryImpl(buffer.getBytes(), buffer.getBytes(), 0); + return entry; + } + + @Override + public long getSequence() { + return sn; + } + + @Override + public int compareTo(HgKvOrderedIterator o) { + return Long.compare(this.getSequence(), o.getSequence()); + } + } +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/KvBatchScanner5.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/KvBatchScanner5.java new file mode 100644 index 000000000..8908c0928 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/KvBatchScanner5.java @@ -0,0 +1,450 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client.grpc; + +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Supplier; + +import javax.annotation.concurrent.NotThreadSafe; + +import org.apache.hugegraph.store.HgPageSize; +import org.apache.hugegraph.store.HgScanQuery; +import org.apache.hugegraph.store.client.HgStoreNodeSession; +import org.apache.hugegraph.store.client.type.HgStoreClientException; +import org.apache.hugegraph.store.client.util.Base58; +import org.apache.hugegraph.store.client.util.HgStoreClientConfig; +import org.apache.hugegraph.store.grpc.common.Kv; +import org.apache.hugegraph.store.grpc.stream.HgStoreStreamGrpc; +import org.apache.hugegraph.store.grpc.stream.KvPageRes; +import org.apache.hugegraph.store.grpc.stream.ScanCancelRequest; +import org.apache.hugegraph.store.grpc.stream.ScanReceiptRequest; +import org.apache.hugegraph.store.grpc.stream.ScanStreamBatchReq; + +import io.grpc.stub.StreamObserver; +import lombok.extern.slf4j.Slf4j; + +/** + * created on 2022/03/23 + * + * @version 5.0.0 + */ +@Slf4j +@NotThreadSafe +class KvBatchScanner5 { + private final static HgStoreClientConfig storeClientConfig = HgStoreClientConfig.of(); + //private final static int HAVE_NEXT_TIMEOUT_SECONDS = storeClientConfig + // .getNetKvScannerHaveNextTimeout(); + private final static int HAVE_NEXT_TIMEOUT_SECONDS = 60; + private final static long PAGE_SIZE = storeClientConfig.getNetKvScannerPageSize(); + + public static KvCloseableIterator scan(HgStoreNodeSession nodeSession, + HgStoreStreamGrpc.HgStoreStreamStub stub, + HgScanQuery scanQuery) { + return new OrderConsumer(new OrderBroker(stub, scanQuery, nodeSession)); + } + + private enum OrderState { + NEW(0), + WORKING(1), + COMPLETED(10); + + int value; + + OrderState(int value) { + this.value = value; + } + } + + /*** Broker ***/ + private static class OrderBroker { + public final OrderKeeper keeper = new OrderKeeper(); + private final HgScanQuery scanQuery; + private final StreamObserver requestObserver; + private final ScanStreamBatchReq.Builder reqBuilder; + private final ReentrantLock senderLock = new ReentrantLock(); + private final AtomicBoolean serverFinished = new AtomicBoolean(); + private final AtomicBoolean clientFinished = new AtomicBoolean(); + private final ScanReceiptRequest.Builder receiptReqBuilder = + ScanReceiptRequest.newBuilder(); + private final ScanCancelRequest cancelReq = ScanCancelRequest.newBuilder().build(); + private final HgStoreNodeSession nodeSession; + private final OrderAgent agent; + private final AtomicLong receivedCount = new AtomicLong(); + private final AtomicInteger receivedLastTimes = new AtomicInteger(); + private final BlockingQueue timesQueue = new LinkedBlockingQueue(); + String brokerId = ""; + private OrderState state = OrderState.NEW; + + OrderBroker(HgStoreStreamGrpc.HgStoreStreamStub stub, + HgScanQuery scanQuery, + HgStoreNodeSession nodeSession) { + + if (log.isDebugEnabled()) { + if (scanQuery.getPrefixList() != null && scanQuery.getPrefixList().size() > 0) { + brokerId = Base58.encode(scanQuery.getPrefixList().get(0).getKey()); + + log.debug( + "[ANALYSIS START] [{}] firstKey: {}, keyLength: {}, table: {}, node: {}" + , brokerId + , scanQuery.getPrefixList().get(0) + , scanQuery.getPrefixList().size() + , scanQuery.getTable() + , nodeSession.getStoreNode().getAddress()); + } + } + + this.scanQuery = scanQuery; + this.reqBuilder = KvBatchUtil.getRequestBuilder(nodeSession); + this.nodeSession = nodeSession; + this.agent = new OrderAgent(brokerId); + this.requestObserver = stub.scanBatch(agent); + + } + + List oneMore() { + + if (this.state == OrderState.NEW) { + synchronized (this.state) { + if (this.state == OrderState.NEW) { + this.makeADeal(); + this.state = OrderState.WORKING; + } + } + } else { + this.sendReceipt(); + } + + return this.keeper.pickUp(); + } + + void receipt(int times) { + this.timesQueue.offer(times); + receivedLastTimes.set(times); + } + + void sendReceipt() { + Integer buf = this.timesQueue.poll(); + + if (buf == null) { + buf = this.receivedLastTimes.get(); + } + + AtomicInteger timesBuf = new AtomicInteger(buf); + + if (!this.clientFinished.get()) { + this.send(() -> + getReqBuilder().setReceiptRequest( + this.receiptReqBuilder.setTimes(timesBuf.get()).build()) + .build() + ); + } + } + + private void makeADeal() { + this.send(() -> getReqBuilder() + .setQueryRequest(KvBatchUtil.createQueryReq(scanQuery, PAGE_SIZE)).build() + ); + } + + private void finish(long tookAmt) { + this.clientFinished.set(true); + if (log.isDebugEnabled()) { + log.debug("[ANALYSIS END] [{}] times: {}, received: {}, took: {}" + , this.brokerId + , this.receivedLastTimes.get() + , this.receivedCount.get() + , tookAmt + ); + } + if (this.receivedCount.get() != tookAmt) { + if (log.isDebugEnabled()) { + log.debug("[ANALYSIS END] [{}] times: {}, received: {}, took: {}" + , this.brokerId + , this.receivedLastTimes.get() + , this.receivedCount.get() + , tookAmt + ); + } + } + synchronized (this.state) { + if (this.state.value < OrderState.COMPLETED.value) { + this.send(() -> getReqBuilder().setCancelRequest(this.cancelReq).build()); + this.state = OrderState.COMPLETED; + } + } + } + + private ScanStreamBatchReq.Builder getReqBuilder() { + return this.reqBuilder.clearQueryRequest(); + } + + private void send(Supplier supplier) { + this.senderLock.lock(); + try { + if (!this.serverFinished.get()) { + this.requestObserver.onNext(supplier.get()); + } + Thread.yield(); + } finally { + this.senderLock.unlock(); + } + } + + private class OrderAgent implements StreamObserver { + private final AtomicInteger count = new AtomicInteger(0); + private final AtomicBoolean over = new AtomicBoolean(false); + private final String agentId; + + OrderAgent(String agentId) { + this.agentId = agentId; + } + + @Override + public void onNext(KvPageRes value) { + if (log.isDebugEnabled()) { + log.debug("Scan [ {} ] [ {} ] times, received: [ {} ]" + , nodeSession.getStoreNode().getAddress(), value.getTimes(), + value.getDataList().size()); + } + + serverFinished.set(value.getOver()); + + List buffer = value.getDataList(); + count.addAndGet(buffer.size()); + if (log.isDebugEnabled()) { + if (value.getOver()) { + log.debug("[ANALYSIS OVER] [{}] count: {}", agentId, count); + } + } + keeper.receive(buffer, value.getTimes()); + this.over.set(value.getOver()); + this.checkOver(value.getTimes()); + } + + private void checkOver(int times) { + if (this.over.get()) { + requestObserver.onCompleted(); + keeper.done(times); + } + } + + @Override + public void onError(Throwable t) { + log.error("received server onError event, Throwable:", t); + keeper.shout(t); + } + + @Override + public void onCompleted() { + if (log.isDebugEnabled()) { + log.debug("received sever completed event."); + } + serverFinished.set(true); + + } + + } + + /*** Inventory Keeper ***/ + private class OrderKeeper { + private final BlockingQueue>> queue = new LinkedBlockingQueue<>(); + private final ReentrantLock pickUpLock = new ReentrantLock(); + private final AtomicBoolean done = new AtomicBoolean(); + private final AtomicBoolean stop = new AtomicBoolean(); + private int timesOfOver; + private int lastTimes; + private Throwable serverErr; + + void receive(List data, int times) { + receivedCount.addAndGet(data.size()); + this.queue.offer(() -> data); + receipt(times); + + this.lastTimes = times; + } + + private List pickUp() { + Supplier> res; + + pickUpLock.lock(); + try { + + if (this.done.get()) { + if (this.stop.get()) { + log.warn("Invoking pickUp method after OrderKeeper has bean closing."); + } + res = this.queue.poll(); + if (res == null) { + res = () -> null; + } + } else { + res = this.queue.poll(HAVE_NEXT_TIMEOUT_SECONDS, TimeUnit.SECONDS); + if (res == null) { + if (this.done.get()) { + res = () -> null; + } else { + throw HgStoreClientException.of( + "Timeout, max time: " + HAVE_NEXT_TIMEOUT_SECONDS + + " seconds" + + ", isOver: " + this.done.get() + + ", isStop: " + this.stop.get() + + ", last-times: " + this.lastTimes + + ", over-times: " + this.timesOfOver); + } + } + + } + } catch (InterruptedException e) { + log.error( + "Failed to receive List from queue because of interruption of " + + "current thread [" + + Thread.currentThread().getName() + "]"); + + Thread.currentThread().interrupt(); + + throw HgStoreClientException.of( + "Failed to receive List from queue, cause by:", e); + } finally { + pickUpLock.unlock(); + } + + checkServerErr(); + return res.get(); + + } + + void done(int times) { + this.timesOfOver = times; + this.done.set(true); + this.queue.offer(() -> null); + } + + void shout(Throwable t) { + this.serverErr = t; + log.error("Failed to receive from sever", t); + this.queue.offer(() -> null); + } + + private void checkServerErr() { + if (this.serverErr != null) { + throw HgStoreClientException.of(this.serverErr); + } + } + } + + } + + + /* iterator */ + private static class OrderConsumer implements KvCloseableIterator, HgPageSize { + private final OrderBroker broker; + private final String consumerId; + private Iterator dataIterator; + private long tookCount = 0; + + OrderConsumer(OrderBroker broker) { + this.broker = broker; + consumerId = broker.brokerId; + } + + private Iterator getIterator() { + List list = this.broker.oneMore(); + + if (log.isDebugEnabled()) { + if (list != null && list.isEmpty()) { + log.debug("[ANALYSIS EMPTY] [{}] , tookCount: {}", consumerId, tookCount); + } + } + + if (list == null || list.isEmpty()) { + return null; + } else { + return list.iterator(); + } + } + + @Override + public void close() { + this.broker.finish(this.tookCount); + } + + @Override + public long getPageSize() { + return PAGE_SIZE; + } + + @Override + public boolean hasNext() { + + if (this.dataIterator == null) { + this.dataIterator = this.getIterator(); + } else { + if (this.dataIterator.hasNext()) { + return true; + } else { + this.dataIterator = this.getIterator(); + } + } + + if (this.dataIterator == null) { + if (log.isDebugEnabled()) { + log.debug("[ANALYSIS NULL -> FALSE] [{}] , tookCount: {}", consumerId, + tookCount); + } + return false; + } else { + if (log.isDebugEnabled()) { + if (!this.dataIterator.hasNext()) { + log.debug("[ANALYSIS hasNext -> FALSE] [{}] , tookCount: {}", consumerId, + tookCount); + } + } + return this.dataIterator.hasNext(); + } + + } + + @Override + public Kv next() { + if (this.dataIterator == null) { + if (!this.hasNext()) { + throw new NoSuchElementException(); + } + } + + if (log.isDebugEnabled()) { + tookCount++; + if (tookCount % 10000 == 0) { + log.debug("[ANALYSIS NEXT] [{}] , tookCount: {}", consumerId, tookCount); + } + } + return this.dataIterator.next(); + } + + } + +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/KvBatchScannerMerger.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/KvBatchScannerMerger.java new file mode 100644 index 000000000..555f7bd39 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/KvBatchScannerMerger.java @@ -0,0 +1,333 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client.grpc; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +import org.apache.hugegraph.store.HgKvEntry; +import org.apache.hugegraph.store.HgKvIterator; +import org.apache.hugegraph.store.HgKvOrderedIterator; +import org.apache.hugegraph.store.HgPageSize; +import org.apache.hugegraph.store.client.util.PropertyUtil; + +import lombok.extern.slf4j.Slf4j; + +/** + * 批量查询结果归并,阻塞队列工作模式 + * 对请求任务的拆分,创建多个请求队列 + */ +@Slf4j +public class KvBatchScannerMerger implements KvCloseableIterator>, + HgPageSize { + + static int maxWaitCount = PropertyUtil.getInt("net.kv.scanner.wait.timeout", 60); + protected final BlockingQueue>> queue = + new LinkedBlockingQueue<>(); + private final KvBatchScanner.TaskSplitter taskSplitter; + private final List scanners = new CopyOnWriteArrayList<>(); + private Supplier> current = null; + + public KvBatchScannerMerger(KvBatchScanner.TaskSplitter splitter) { + this.taskSplitter = splitter; + splitter.setNotifier(this); + } + + public void startTask() { + taskSplitter.splitTask(); + } + + public void dataArrived(KvBatchScanner scanner, Supplier> supplier) + throws InterruptedException { + queue.put(supplier); + } + + @Override + public boolean hasNext() { + int waitTime = 0; + while (current == null) { + try { + // 队列有数据,还有活跃的查询器,任务未分配完 + if (queue.size() != 0 || scanners.size() > 0 || !taskSplitter.isFinished()) { + current = queue.poll(1, TimeUnit.SECONDS); //定期检查client是否被关闭了 + } else { + break; + } + if (current == null) { + // 超时重试 + sendTimeout(); + if (++waitTime > maxWaitCount) { + log.error( + "KvBatchScanner wait data timeout {}, closeables is {}, task is {}", + waitTime, scanners.size(), taskSplitter.isFinished()); + break; + } + } + } catch (InterruptedException e) { + log.error("hasNext interrupted {}", e); + throw new RuntimeException(e.getMessage(), e); + } + } + return current != null && current != KvBatchScanner.NO_DATA; + } + + + @Override + public HgKvIterator next() { + HgKvIterator iterator = null; + if (current != null) { + iterator = current.get(); + } + current = null; + return iterator; + } + + @Override + public void close() { + taskSplitter.close(); + scanners.forEach(c -> c.close()); + } + + private void sendTimeout() { + scanners.forEach(v -> { + v.sendResponse(); + }); + } + + @Override + public long getPageSize() { + return 0; + } + + public void registerScanner(KvBatchScanner closeable) { + this.scanners.add(closeable); + } + + /** + * 返回值<0表示任务结束 + * + * @param closeable + * @return + */ + public int unregisterScanner(KvBatchScanner closeable) { + this.scanners.remove(closeable); + try { + taskSplitter.splitTask(); + } catch (Exception e) { + log.error("exception ", e); + } + return taskSplitter.isFinished() && this.scanners.size() == 0 ? + -1 : this.scanners.size(); + } + + public int getScannerCount() { + return this.scanners.size(); + } + + /** + * 组装一个Scanner的多个有序迭代器为一个迭代器 + */ + static class ScannerDataQueue { + private BlockingQueue>> queue; + private HgKvOrderedIterator iterator = null; + private int currentSN = 0; + private HgKvEntry entry; + + public ScannerDataQueue() { + queue = new LinkedBlockingQueue<>(); + } + + public int sn() { + return currentSN; + } + + public void add(Supplier> supplier) { + if (queue != null) { + queue.add(supplier); + } + } + + /** + * 迭代器是否有效,如果没有数据,等待数据到达 + * + * @return + */ + public boolean hasNext() { + while (entry == null && queue != null) { + try { + int waitTime = 0; + Supplier> current; + current = queue.poll(1, TimeUnit.SECONDS); //定期检查client是否被关闭了 + if (current == null) { + if (++waitTime > maxWaitCount) { + break; + } + } else if (current == KvBatchScanner.NO_DATA) { + queue = null; + break; + } else { + iterator = (HgKvOrderedIterator) current.get(); + if (iterator != null && iterator.hasNext()) { + moveNext(); + } else { + iterator = null; + } + } + } catch (InterruptedException e) { + log.error("hasNext interrupted {}", e); + throw new RuntimeException(e.getMessage(), e); + } + } + return entry != null; + } + + public HgKvEntry next() { + HgKvEntry current = entry; + moveNext(); + return current; + } + + private void moveNext() { + if (iterator.hasNext()) { + entry = iterator.next(); + currentSN = (int) iterator.getSequence(); + } else { + entry = null; + iterator = null; + } + } + } + + /** + * 对多个Scanner返回结果进行归并排序 + */ + static class SortedScannerMerger extends KvBatchScannerMerger { + // 每一个流对应一个接收队列 + private final Map scannerQueues = + new ConcurrentHashMap<>(); + + public SortedScannerMerger(KvBatchScanner.TaskSplitter splitter) { + super(splitter); + queue.add(() -> { + // 对store返回结果进行归并排序 + return new HgKvIterator<>() { + private ScannerDataQueue iterator; + private int currentSN = 0; + private HgKvEntry entry; + + @Override + public byte[] key() { + return entry.key(); + } + + @Override + public byte[] value() { + return entry.value(); + } + + @Override + public void close() { + + } + + @Override + public byte[] position() { + return new byte[0]; + } + + @Override + public void seek(byte[] position) { + throw new RuntimeException("not implemented"); + } + + @Override + public boolean hasNext() { + // + if (iterator == null || !iterator.hasNext() || currentSN != iterator.sn()) { + iterator = selectIterator(); + } + + if (iterator != null) { + currentSN = iterator.sn(); + } + return iterator != null; + } + + @Override + public HgKvEntry next() { + entry = iterator.next(); + return entry; + } + }; + }); + } + + /** + * 从多个Scanner中挑选一个sn最小的迭代器 + * 如果Scanner没有数据,等待数据到达。 + * + * @return + */ + private ScannerDataQueue selectIterator() { + int sn = Integer.MAX_VALUE; + ScannerDataQueue current = null; + while (current == null && !scannerQueues.isEmpty()) { + Iterator itr = scannerQueues.keySet().iterator(); + while (itr.hasNext()) { + KvBatchScanner key = itr.next(); + ScannerDataQueue kvItr = scannerQueues.get(key); + if (!kvItr.hasNext()) { + scannerQueues.remove(key); + continue; + } + if (kvItr.sn() <= sn) { + sn = kvItr.sn(); + current = kvItr; + } + } + } + return current; + } + + @Override + public void registerScanner(KvBatchScanner scanner) { + super.registerScanner(scanner); + scannerQueues.putIfAbsent(scanner, new ScannerDataQueue()); + } + + @Override + public int unregisterScanner(KvBatchScanner scanner) { + dataArrived(scanner, KvBatchScanner.NO_DATA); + return super.unregisterScanner(scanner); + } + + @Override + public void dataArrived(KvBatchScanner scanner, + Supplier> supplier) { + scannerQueues.putIfAbsent(scanner, new ScannerDataQueue()); + scannerQueues.get(scanner).add(supplier); + } + } +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/KvBatchUtil.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/KvBatchUtil.java new file mode 100644 index 000000000..6df426962 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/KvBatchUtil.java @@ -0,0 +1,155 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client.grpc; + +import java.util.List; + +import javax.annotation.concurrent.NotThreadSafe; + +import org.apache.hugegraph.store.HgOwnerKey; +import org.apache.hugegraph.store.HgScanQuery; +import org.apache.hugegraph.store.client.HgStoreNodeSession; +import org.apache.hugegraph.store.client.util.HgStoreClientConst; +import org.apache.hugegraph.store.grpc.common.Header; +import org.apache.hugegraph.store.grpc.common.ScanMethod; +import org.apache.hugegraph.store.grpc.stream.ScanCondition; +import org.apache.hugegraph.store.grpc.stream.ScanQueryRequest; +import org.apache.hugegraph.store.grpc.stream.ScanStreamBatchReq; + +import com.google.protobuf.ByteString; + +import lombok.extern.slf4j.Slf4j; + +/** + * created on 2022/04/23 + */ +@Slf4j +@NotThreadSafe +class KvBatchUtil { + static final byte[] EMPTY_POSITION = HgStoreClientConst.EMPTY_BYTES; + + static ScanStreamBatchReq.Builder getRequestBuilder(HgStoreNodeSession nodeSession) { + return ScanStreamBatchReq.newBuilder().setHeader(getHeader(nodeSession)); + } + + static ScanQueryRequest createQueryReq(HgScanQuery scanQuery, long pageSize) { + + ScanQueryRequest.Builder qb = ScanQueryRequest.newBuilder(); + ScanCondition.Builder cb = ScanCondition.newBuilder(); + + qb.setLimit(getLimit(scanQuery.getLimit())); + qb.setPerKeyLimit(getLimit(scanQuery.getPerKeyLimit())); + qb.setPerKeyMax(getLimit(scanQuery.getPerKeyMax())); + + switch (scanQuery.getScanMethod()) { + case ALL: + qb.setMethod(ScanMethod.ALL); + break; + case PREFIX: + qb.setMethod(ScanMethod.PREFIX); + addPrefixCondition(scanQuery, qb, cb); + break; + case RANGE: + qb.setMethod(ScanMethod.RANGE); + addRangeCondition(scanQuery, qb, cb); + break; + default: + throw new RuntimeException("Unsupported ScanType: " + scanQuery.getScanMethod()); + } + + qb.setTable(scanQuery.getTable()); + qb.setPageSize(pageSize); + qb.setQuery(toBs(scanQuery.getQuery())); + qb.setScanType(scanQuery.getScanType()); + qb.setOrderType(scanQuery.getOrderType()); + qb.setSkipDegree(scanQuery.getSkipDegree()); + + return qb.build(); + } + + static long getLimit(long limit) { + return limit <= HgStoreClientConst.NO_LIMIT ? Integer.MAX_VALUE : limit; + } + + static Header getHeader(HgStoreNodeSession nodeSession) { + return Header.newBuilder().setGraph(nodeSession.getGraphName()).build(); + } + + static void addPrefixCondition(HgScanQuery scanQuery, ScanQueryRequest.Builder qb, + ScanCondition.Builder cb) { + List prefixList = scanQuery.getPrefixList(); + + if (prefixList == null || prefixList.isEmpty()) { + throw new RuntimeException( + "The start-list of ScanQuery shouldn't to be invalid in ScanMethod.PREFIX " + + "mode."); + } + + prefixList.forEach((e) -> { + qb.addCondition(cb.clear() + .setPrefix(toBs(e.getKey())) + .setCode(e.getKeyCode()) + .setSerialNo(e.getSerialNo()) + .build() + ); + }); + + } + + static void addRangeCondition(HgScanQuery scanQuery, ScanQueryRequest.Builder qb, + ScanCondition.Builder cb) { + List startList = scanQuery.getStartList(); + List endList = scanQuery.getEndList(); + + if (startList == null || startList.isEmpty()) { + throw new RuntimeException( + "The start-list of ScanQuery shouldn't to be invalid in ScanMethod.RANGE mode" + + "."); + } + + if (endList == null || endList.isEmpty()) { + throw new RuntimeException( + "The end-list of ScanQuery shouldn't to be invalid in ScanMethod.RANGE mode."); + } + + if (startList.size() != endList.size()) { + throw new RuntimeException("The size of start-list not equals end-list's."); + } + + for (int i = 0, s = startList.size(); i < s; i++) { + HgOwnerKey start = startList.get(i); + HgOwnerKey end = endList.get(i); + qb.addCondition(cb.clear().setCode(start.getKeyCode()) + .setStart(toBs(start.getKey())) + .setEnd(toBs(end.getKey())) + .setSerialNo(start.getSerialNo()) + .build() + ); + } + + } + + static HgOwnerKey toOk(HgOwnerKey key) { + return key == null ? HgStoreClientConst.EMPTY_OWNER_KEY : key; + } + + static ByteString toBs(byte[] bytes) { + return ByteString.copyFrom((bytes != null) ? bytes : HgStoreClientConst.EMPTY_BYTES); + } + +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/KvCloseableIterator.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/KvCloseableIterator.java new file mode 100644 index 000000000..ac90b47ac --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/KvCloseableIterator.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client.grpc; + +import java.io.Closeable; +import java.util.Iterator; + +/** + * 2022/3/16 + */ +public interface KvCloseableIterator extends Iterator, Closeable { + @Override + void close(); +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/KvListIterator.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/KvListIterator.java new file mode 100644 index 000000000..cc33dfa63 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/KvListIterator.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client.grpc; + +import java.util.Iterator; +import java.util.List; + +/** + * 2022/4/6 + */ +class KvListIterator implements KvCloseableIterator { + + private final Iterator iterator; + + KvListIterator(List list) { + this.iterator = list.iterator(); + } + + @Override + public void close() { + /*Nothing to do.*/ + } + + @Override + public boolean hasNext() { + return this.iterator.hasNext(); + } + + @Override + public T next() { + return this.iterator.next(); + } +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/KvOneShotScanner.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/KvOneShotScanner.java new file mode 100644 index 000000000..be4243a42 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/KvOneShotScanner.java @@ -0,0 +1,212 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client.grpc; + +import java.util.Iterator; +import java.util.List; + +import javax.annotation.concurrent.NotThreadSafe; + +import org.apache.hugegraph.store.HgKvStore; +import org.apache.hugegraph.store.HgOwnerKey; +import org.apache.hugegraph.store.HgPageSize; +import org.apache.hugegraph.store.HgSeekAble; +import org.apache.hugegraph.store.client.HgStoreNodeSession; +import org.apache.hugegraph.store.client.util.HgStoreClientConfig; +import org.apache.hugegraph.store.client.util.HgStoreClientConst; +import org.apache.hugegraph.store.client.util.HgStoreClientUtil; +import org.apache.hugegraph.store.grpc.common.Header; +import org.apache.hugegraph.store.grpc.common.Kv; +import org.apache.hugegraph.store.grpc.common.ScanMethod; +import org.apache.hugegraph.store.grpc.stream.HgStoreStreamGrpc.HgStoreStreamBlockingStub; +import org.apache.hugegraph.store.grpc.stream.ScanStreamReq; + +import com.google.protobuf.ByteString; + +import lombok.extern.slf4j.Slf4j; + +/** + * created on 2021/12/1 + */ +@Slf4j +@NotThreadSafe +class KvOneShotScanner implements KvCloseableIterator, HgPageSize, HgSeekAble { + private static final HgStoreClientConfig storeClientConfig = HgStoreClientConfig.of(); + private final HgStoreNodeSession session; + private final HgStoreStreamBlockingStub stub; + private final ScanStreamReq.Builder reqBuilder = ScanStreamReq.newBuilder(); + private final String table; + private final HgOwnerKey startKey; + private final HgOwnerKey endKey; + private final HgOwnerKey prefix; + private final ScanMethod scanMethod; + private final long limit; + private final int partition; + private final int scanType; + private final byte[] query; + private final int pageSize; + private ScanStreamReq req; + private Iterator iterator; + private List list = null; + private boolean in = true; + private byte[] nodePosition = HgStoreClientConst.EMPTY_BYTES; + + private KvOneShotScanner(ScanMethod scanMethod, HgStoreNodeSession session, + HgStoreStreamBlockingStub stub, + String table, HgOwnerKey prefix, HgOwnerKey startKey, + HgOwnerKey endKey, long limit, + int partition, int scanType, byte[] query) { + this.scanMethod = scanMethod; + this.session = session; + this.stub = stub; + this.table = table; + this.startKey = toOk(startKey); + this.endKey = toOk(endKey); + this.prefix = toOk(prefix); + this.partition = partition; + this.scanType = scanType; + this.query = query != null ? query : HgStoreClientConst.EMPTY_BYTES; + this.limit = limit <= HgStoreClientConst.NO_LIMIT ? Integer.MAX_VALUE : + limit; // <=0 means no limit + this.pageSize = storeClientConfig.getNetKvScannerPageSize(); + + } + + public static KvCloseableIterator scanAll(HgStoreNodeSession session, + HgStoreStreamBlockingStub stub, + String table, long limit, byte[] query) { + return new KvOneShotScanner(ScanMethod.ALL, session, stub, table, null, null, null, limit, + -1, HgKvStore.SCAN_ANY, + query); + } + + public static KvCloseableIterator scanPrefix(HgStoreNodeSession session, + HgStoreStreamBlockingStub stub, + String table, HgOwnerKey prefix, long limit, + byte[] query) { + return new KvOneShotScanner(ScanMethod.PREFIX, session, stub, table, prefix, null, null, + limit, + prefix.getKeyCode(), HgKvStore.SCAN_PREFIX_BEGIN, query); + } + + public static KvCloseableIterator scanRange(HgStoreNodeSession nodeSession, + HgStoreStreamBlockingStub stub, + String table, HgOwnerKey startKey, + HgOwnerKey endKey, long limit, + int scanType, byte[] query) { + return new KvOneShotScanner(ScanMethod.RANGE, nodeSession, stub, table, null, startKey, + endKey, limit, + startKey.getKeyCode(), scanType, query); + } + + static HgOwnerKey toOk(HgOwnerKey key) { + return key == null ? HgStoreClientConst.EMPTY_OWNER_KEY : key; + } + + static ByteString toBs(byte[] bytes) { + return ByteString.copyFrom((bytes != null) ? bytes : HgStoreClientConst.EMPTY_BYTES); + } + + private Header getHeader(HgStoreNodeSession nodeSession) { + return Header.newBuilder().setGraph(nodeSession.getGraphName()).build(); + } + + private void createReq() { + this.req = this.reqBuilder + .setHeader(this.getHeader(this.session)) + .setMethod(this.scanMethod) + .setTable(this.table) + .setStart(toBs(this.startKey.getKey())) + .setEnd(toBs(this.endKey.getKey())) + .setLimit(this.limit) + .setPrefix(toBs(this.prefix.getKey())) + .setCode(this.partition) + .setScanType(this.scanType) + .setQuery(toBs(this.query)) + .setPageSize(pageSize) + .setPosition(toBs(this.nodePosition)) + .build(); + } + + private void init() { + + if (this.iterator == null) { + this.createReq(); + this.list = this.stub.scanOneShot(this.req).getDataList(); + this.iterator = this.list.iterator(); + } + + } + + @Override + public boolean hasNext() { + if (!this.in) { + return false; + } + if (this.iterator == null) { + this.init(); + } + return this.iterator.hasNext(); + } + + @Override + public Kv next() { + if (this.iterator == null) { + this.init(); + } + return this.iterator.next(); + } + + @Override + public long getPageSize() { + return this.limit; + } + + @Override + public boolean isPageEmpty() { + return !this.iterator.hasNext(); + } + + @Override + public byte[] position() { + return HgStoreClientUtil.toBytes(this.session.getStoreNode().getNodeId().longValue()); + } + + @Override + public void seek(byte[] position) { + if (position == null || position.length < Long.BYTES) { + return; + } + byte[] nodeIdBytes = new byte[Long.BYTES]; + System.arraycopy(position, 0, nodeIdBytes, 0, Long.BYTES); + long nodeId = this.session.getStoreNode().getNodeId().longValue(); + long pId = HgStoreClientUtil.toLong(nodeIdBytes); + this.in = nodeId >= pId; + if (this.in && nodeId == pId) { + this.nodePosition = new byte[position.length - Long.BYTES]; + System.arraycopy(position, Long.BYTES, this.nodePosition, 0, this.nodePosition.length); + } else { + this.nodePosition = HgStoreClientConst.EMPTY_BYTES; + } + } + + @Override + public void close() { + //TODO: implements + } +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/KvPageScanner.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/KvPageScanner.java new file mode 100644 index 000000000..c9e99d9b7 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/KvPageScanner.java @@ -0,0 +1,310 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client.grpc; + +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.annotation.concurrent.NotThreadSafe; + +import org.apache.hugegraph.store.HgKvStore; +import org.apache.hugegraph.store.HgOwnerKey; +import org.apache.hugegraph.store.HgPageSize; +import org.apache.hugegraph.store.HgSeekAble; +import org.apache.hugegraph.store.client.HgStoreNodeSession; +import org.apache.hugegraph.store.client.util.HgBufferProxy; +import org.apache.hugegraph.store.client.util.HgStoreClientConfig; +import org.apache.hugegraph.store.client.util.HgStoreClientConst; +import org.apache.hugegraph.store.client.util.HgStoreClientUtil; +import org.apache.hugegraph.store.client.util.MetricX; +import org.apache.hugegraph.store.grpc.common.Header; +import org.apache.hugegraph.store.grpc.common.Kv; +import org.apache.hugegraph.store.grpc.common.ScanMethod; +import org.apache.hugegraph.store.grpc.stream.HgStoreStreamGrpc.HgStoreStreamStub; +import org.apache.hugegraph.store.grpc.stream.KvPageRes; +import org.apache.hugegraph.store.grpc.stream.ScanStreamReq; +import org.apache.hugegraph.store.grpc.stream.SelectParam; + +import com.google.protobuf.ByteString; + +import io.grpc.stub.StreamObserver; +import lombok.extern.slf4j.Slf4j; + +/** + * created on 2021/10/21 + * + * @version 0.6.1 lynn.bond@hotamil.com on 2022/04/05 + */ +@Slf4j +@NotThreadSafe +class KvPageScanner implements KvCloseableIterator, HgPageSize, HgSeekAble { + + private static final HgStoreClientConfig clientConfig = HgStoreClientConfig.of(); + private static final int nextTimeout = clientConfig.getNetKvScannerHaveNextTimeout(); + private final HgStoreNodeSession session; + private final HgStoreStreamStub stub; + private final AtomicBoolean completed = new AtomicBoolean(false); + private final SelectParam.Builder selectBuilder = SelectParam.newBuilder(); + private final BlockingQueue reqQueue = new LinkedBlockingQueue<>(); + private int pageSize = clientConfig.getNetKvScannerPageSize(); + private HgBufferProxy> proxy; + private Iterator iterator; + private StreamObserver observer; + private ScanStreamReq.Builder reqBuilder = ScanStreamReq.newBuilder(); + private boolean in = true; + private byte[] nodePosition = HgStoreClientConst.EMPTY_BYTES; + + private KvPageScanner(ScanMethod scanMethod, HgStoreNodeSession session, HgStoreStreamStub stub, + String table, + HgOwnerKey prefix, HgOwnerKey startKey, HgOwnerKey endKey, long limit, + int partition, + int scanType, byte[] query) { + this.session = session; + this.stub = stub; + this.pageSize = clientConfig.getNetKvScannerPageSize(); + this.reqBuilder.setHeader(this.getHeader(this.session)) + .setMethod(scanMethod) + .setTable(table) + .setStart(toBs(toOk(startKey).getKey())) + .setEnd(toBs(toOk(endKey).getKey())) + .setLimit(limit <= HgStoreClientConst.NO_LIMIT ? Integer.MAX_VALUE : limit) + .setPrefix(toBs(toOk(prefix).getKey())) + .setCode(partition) + .setScanType(scanType) + .setQuery(toBs(query != null ? query : HgStoreClientConst.EMPTY_BYTES)) + .setPageSize(pageSize) + .setPosition(toBs(this.nodePosition)); + this.init(); + } + + public KvPageScanner(HgStoreNodeSession session, HgStoreStreamStub stub, + ScanStreamReq.Builder reqBuilder) { + this.session = session; + this.stub = stub; + reqBuilder.setPageSize(pageSize); + reqBuilder.setPosition(toBs(this.nodePosition)); + this.reqBuilder = reqBuilder; + this.init(); + } + + public static KvCloseableIterator scanAll(HgStoreNodeSession nodeSession, + HgStoreStreamStub stub, String table, + long limit, byte[] query) { + return new KvPageScanner(ScanMethod.ALL, nodeSession, stub, table, null, null, null, limit, + -1, HgKvStore.SCAN_ANY, query); + } + + public static KvCloseableIterator scanPrefix(HgStoreNodeSession nodeSession, + HgStoreStreamStub stub, + String table, HgOwnerKey prefix, long limit, + byte[] query) { + return new KvPageScanner(ScanMethod.PREFIX, nodeSession, stub, table, prefix, null, null, + limit, + prefix.getKeyCode(), HgKvStore.SCAN_PREFIX_BEGIN, query); + } + + public static KvCloseableIterator scanRange(HgStoreNodeSession nodeSession, + HgStoreStreamStub stub, + String table, HgOwnerKey startKey, + HgOwnerKey endKey, long limit, + int scanType, byte[] query) { + return new KvPageScanner(ScanMethod.RANGE, nodeSession, stub, table, null, startKey, endKey, + limit, + startKey.getKeyCode(), scanType, query); + } + + static HgOwnerKey toOk(HgOwnerKey key) { + return key == null ? HgStoreClientConst.EMPTY_OWNER_KEY : key; + } + + static ByteString toBs(byte[] bytes) { + return ByteString.copyFrom((bytes != null) ? bytes : HgStoreClientConst.EMPTY_BYTES); + } + + private ScanStreamReq createScanReq() { + return this.reqBuilder.setPosition(toBs(this.nodePosition)).build(); + } + + private ScanStreamReq createStopReq() { + return this.reqBuilder.setHeader(this.getHeader(this.session)).setCloseFlag(1).build(); + } + + private void init() { + this.proxy = HgBufferProxy.of(() -> this.serverScan()); + this.observer = this.stub.scan(new ServeObserverImpl()); + + } + + /*** Server Event End ***/ + + private void serverScan() { + if (this.completed.get()) { + this.proxy.close(); + return; + } + if (this.proxy.isClosed()) { + return; + } + this.send(this.createScanReq()); + } + + private void stopSever() { + this.send(this.createStopReq()); + } + + private void send(ScanStreamReq req) { + if (!this.completed.get()) { + try { + this.observer.onNext(req); + } catch (IllegalStateException | IllegalArgumentException e) { + + } catch (Exception e) { + throw e; + } + } + } + + private void clientError(String msg) { + this.observer.onError(GrpcUtil.toErr(msg)); + } + + /*** Iterator ***/ + @Override + public boolean hasNext() { + if (!this.in) { + return false; + } + if (this.iterator != null && this.iterator.hasNext()) { + return true; + } + long start = 0; + boolean debugEnabled = log.isDebugEnabled(); + if (debugEnabled) { + start = System.nanoTime(); + } + List data = this.proxy.receive(nextTimeout, (sec) -> { + String msg = "failed to receive data from net scanning, because of timeout [ " + sec + + " ] sec."; + log.error(msg); + this.clientError(msg); + throw new RuntimeException(msg); + }); + if (debugEnabled) { + MetricX.plusIteratorWait(System.nanoTime() - start); + } + if (data != null) { + this.iterator = data.iterator(); + } else { + this.iterator = Collections.emptyIterator(); + } + return this.iterator.hasNext(); + } + + @Override + public Kv next() { + if (this.iterator == null && !this.hasNext()) { + throw new NoSuchElementException(); + } + return this.iterator.next(); + } + + @Override + public long getPageSize() { + return this.pageSize; + } + + @Override + public boolean isPageEmpty() { + return !this.iterator.hasNext(); + } + + @Override + public byte[] position() { + return HgStoreClientUtil.toBytes(this.session.getStoreNode().getNodeId().longValue()); + } + + @Override + public void seek(byte[] position) { + if (position == null || position.length < Long.BYTES) { + return; + } + byte[] nodeIdBytes = new byte[Long.BYTES]; + System.arraycopy(position, 0, nodeIdBytes, 0, Long.BYTES); + long nodeId = this.session.getStoreNode().getNodeId().longValue(); + long pId = HgStoreClientUtil.toLong(nodeIdBytes); + this.in = nodeId >= pId; + if (this.in && nodeId == pId) { + this.nodePosition = new byte[position.length - Long.BYTES]; + System.arraycopy(position, Long.BYTES, this.nodePosition, 0, this.nodePosition.length); + } else { + this.nodePosition = HgStoreClientConst.EMPTY_BYTES; + } + } + + @Override + public void close() { + this.stopSever(); + } + + /*** commons ***/ + private Header getHeader(HgStoreNodeSession nodeSession) { + return Header.newBuilder().setGraph(nodeSession.getGraphName()).build(); + } + + /*** Server event Start ***/ + private class ServeObserverImpl implements StreamObserver { + + @Override + public void onNext(KvPageRes value) { + if (value.getOver()) { + completed.set(true); + observer.onCompleted(); + } + proxy.send(value.getDataList()); + if (completed.get()) { + proxy.close(); + } + } + + @Override + public void onError(Throwable t) { + completed.set(true); + try { + observer.onCompleted(); + } catch (Exception e) { + log.warn("failed to invoke requestObserver.onCompleted(), reason:", e.getMessage()); + } + proxy.close(); + proxy.setError(t); + log.error("failed to complete scan of session: " + session, t); + } + + @Override + public void onCompleted() { + completed.set(true); + proxy.close(); + } + } + +} + diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/NotifyingExecutor.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/NotifyingExecutor.java new file mode 100644 index 000000000..119bfc806 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/NotifyingExecutor.java @@ -0,0 +1,254 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client.grpc; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import org.apache.hugegraph.store.client.HgStoreNodeManager; +import org.apache.hugegraph.store.client.HgStoreNodeSession; +import org.apache.hugegraph.store.client.HgStoreNotice; +import org.apache.hugegraph.store.client.type.HgNodeStatus; +import org.apache.hugegraph.store.client.type.HgStoreClientException; +import org.apache.hugegraph.store.grpc.common.ResStatus; +import org.apache.hugegraph.store.grpc.session.FeedbackRes; +import org.apache.hugegraph.store.grpc.session.PartitionFaultResponse; +import org.apache.hugegraph.store.grpc.session.PartitionFaultType; +import org.apache.hugegraph.store.grpc.session.PartitionLeader; + +import com.google.protobuf.util.JsonFormat; + +import lombok.extern.slf4j.Slf4j; + +/** + * 2021/11/18 + * + * @version 0.3.0 on 2022/01/27 + */ +@Slf4j +final class NotifyingExecutor { + private final String graphName; + private final HgStoreNodeManager nodeManager; + private final HgStoreNodeSession nodeSession; + + private Map> partitionFaultHandlers; + + NotifyingExecutor(String graphName, HgStoreNodeManager nodeManager, + HgStoreNodeSession nodeSession) { + this.graphName = graphName; + this.nodeManager = nodeManager; + this.nodeSession = nodeSession; + } + + private void intHandler() { + this.partitionFaultHandlers = new HashMap<>(); + + this.partitionFaultHandlers.put( + PartitionFaultType.PARTITION_FAULT_TYPE_NOT_LEADER, notifyPartitionLeaderConsumer() + ); + + } + + Optional invoke(Supplier supplier, Function okFunction) { + FeedbackRes res = null; + + try { + res = supplier.get(); + } catch (Throwable t) { + log.error("Failed to invoke: " + supplier.toString() + ", caused " + + "by:", t); + handleErr(t); + throw err(t); + } + + if (log.isDebugEnabled()) { + log.debug("gRPC [{}] status: {}" + , this.nodeSession.getStoreNode().getAddress(), res.getStatus().getCode()); + } + + Optional option = null; + + switch (res.getStatus().getCode()) { + case RES_CODE_OK: + option = Optional.of(okFunction.apply(res)); + break; + case RES_CODE_FAIL: + handleFail(res); + break; + case RES_CODE_NOT_EXIST: + break; + case RES_CODE_EXCESS: + normalFail(res); + break; + default: + log.error("gRPC [{}] status-msg: {}" + , nodeSession.getStoreNode().getAddress(), res.getStatus().getMsg()); + } + + if (option == null) { + option = Optional.empty(); + } + + return option; + } + + private void handleErr(Throwable t) { + try { + notifyErrConsumer(HgNodeStatus.NOT_WORK).accept(t); + } catch (Throwable tt) { + log.error("Failed to notify error to HgStoreNodeNotifier, cause:", tt); + } + } + + private void handleFail(FeedbackRes feedbackRes) { + Supplier exSup; + + if ( + (exSup = handlePartitionFault(feedbackRes)) != null + // add more fault-handler here. + || (exSup = defaultExceptionSupplier(feedbackRes)) != null + ) { + throw exSup.get(); + } + + } + + private void normalFail(FeedbackRes res) { + ResStatus status = res.getStatus(); + HgStoreClientException ex; + try { + String msg = JsonFormat.printer().omittingInsignificantWhitespace() + .print(res); + ex = err(msg); + } catch (Exception e) { + ex = err(status.getCode() + ", " + status.getMsg()); + } + throw ex; + } + + private Supplier defaultExceptionSupplier(FeedbackRes feedbackRes) { + return () -> HgStoreClientException.of(err(feedbackRes.getStatus().getMsg())); + } + + private Supplier handlePartitionFault( + FeedbackRes feedbackRes) { + PartitionFaultResponse res = feedbackRes.getPartitionFaultResponse(); + if (res == null) { + return null; + } + if (this.partitionFaultHandlers == null) { + intHandler(); + } + Consumer consumer = + this.partitionFaultHandlers.get(res.getFaultType()); + if (consumer == null) { + consumer = notifyPartitionConsumer(); + } + String msg = res.toString(); + if (msg == null || msg.length() == 0) { + msg = feedbackRes.getStatus().getMsg(); + } + consumer.accept(res); + String finalMsg = msg; + return () -> HgStoreClientException.of( + err(res.getFaultType() + ", " + + finalMsg)); + } + + private HgStoreClientException err(String msg) { + return err(msg, null); + } + + private HgStoreClientException err(Throwable t) { + return err(t.getMessage(), t); + } + + private HgStoreClientException err(String reason, Throwable t) { + StringBuilder builder = new StringBuilder().append( + "{sessionInfo: {" + this.nodeSession.toString() + + "}, reason: "); + if (reason.startsWith("{")) { + builder.append(reason); + } else { + builder.append("\"").append(reason).append("\""); + } + String msg = builder.append("}").toString(); + if (t != null) { + return HgStoreClientException.of(msg, t); + } + return HgStoreClientException.of(msg); + } + + private Consumer notifyPartitionLeaderConsumer() { + return res -> { + log.info("partitions' leader have changed: [partitionId - leaderId] "); + nodeManager.notifying( + this.graphName, + HgStoreNotice.of(this.nodeSession.getStoreNode().getNodeId(), + HgNodeStatus.NOT_PARTITION_LEADER) + .setPartitionLeaders( + res.getPartitionLeadersList() + .stream() + .peek((e) -> { + log.info("[{} - {}]", e.getPartitionId(), + e.getLeaderId()); + } + ) + .collect( + Collectors.toMap( + PartitionLeader::getPartitionId, + PartitionLeader::getLeaderId + ) + ) + ) + ); + }; + } + + private Consumer notifyPartitionConsumer() { + return notifyPartitionConsumer(HgNodeStatus.PARTITION_COMMON_FAULT); + } + + private Consumer notifyPartitionConsumer(HgNodeStatus status) { + return res -> { + nodeManager.notifying( + this.graphName, + HgStoreNotice.of(this.nodeSession.getStoreNode().getNodeId(), status) + .setPartitionIds(res.getPartitionIdsList()) + ); + }; + } + + private Consumer notifyErrConsumer(HgNodeStatus status) { + return t -> { + nodeManager.notifying( + this.graphName, + HgStoreNotice.of(this.nodeSession.getStoreNode().getNodeId(), status, + t.getMessage()) + ); + }; + } + +} + + diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/ScanUtil.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/ScanUtil.java new file mode 100644 index 000000000..4bed99dba --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/ScanUtil.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client.grpc; + +import org.apache.hugegraph.store.HgOwnerKey; +import org.apache.hugegraph.store.client.HgStoreNodeSession; +import org.apache.hugegraph.store.client.util.HgStoreClientConst; +import org.apache.hugegraph.store.grpc.common.Header; + +import com.google.protobuf.ByteString; + +/** + * @date 2022/10/8 + **/ +public class ScanUtil { + + + public static Header getHeader(HgStoreNodeSession nodeSession) { + return Header.newBuilder().setGraph(nodeSession.getGraphName()).build(); + } + + public static HgOwnerKey toOk(HgOwnerKey key) { + return key == null ? HgStoreClientConst.EMPTY_OWNER_KEY : key; + } + + public static ByteString toBs(byte[] bytes) { + return ByteString.copyFrom((bytes != null) ? bytes : HgStoreClientConst.EMPTY_BYTES); + } + + public static ByteString getHgOwnerKey(HgOwnerKey ownerKey) { + return toBs(toOk(ownerKey).getKey()); + } + + public static byte[] getQuery(byte[] query) { + return query != null ? query : HgStoreClientConst.EMPTY_BYTES; + } + + public static long getLimit(long limit) { + return limit <= HgStoreClientConst.NO_LIMIT ? Integer.MAX_VALUE : limit; + } +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/SeekAbleIterator.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/SeekAbleIterator.java new file mode 100644 index 000000000..05beec0a0 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/SeekAbleIterator.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client.grpc; + +import java.util.Iterator; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import org.apache.hugegraph.store.HgSeekAble; +import org.apache.hugegraph.store.client.util.HgAssert; + +/** + * 2022/3/11 + */ +class SeekAbleIterator implements Iterator, HgSeekAble { + private final Iterator iterator; + private final Consumer seeker; + private final Supplier positioner; + + private SeekAbleIterator(Iterator iterator, Supplier positioner, + Consumer seeker) { + this.iterator = iterator; + this.positioner = positioner; + this.seeker = seeker; + } + + public static SeekAbleIterator of(Iterator iterator, Supplier positioner, + Consumer seeker) { + HgAssert.isArgumentNotNull(iterator, "iterator"); + HgAssert.isArgumentNotNull(positioner, "positioner"); + HgAssert.isArgumentNotNull(seeker, "seeker"); + return new SeekAbleIterator(iterator, positioner, seeker); + } + + @Override + public byte[] position() { + return this.positioner.get(); + } + + @Override + public void seek(byte[] position) { + this.seeker.accept(position); + } + + @Override + public boolean hasNext() { + return this.iterator.hasNext(); + } + + @Override + public E next() { + return this.iterator.next(); + } +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/SwitchingExecutor.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/SwitchingExecutor.java new file mode 100644 index 000000000..7fecbe5bc --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/grpc/SwitchingExecutor.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client.grpc; + +import java.util.Optional; +import java.util.function.Supplier; + +import javax.annotation.concurrent.ThreadSafe; + +/** + * 2021/12/1 + */ +@ThreadSafe +final class SwitchingExecutor { + + private SwitchingExecutor() { + } + + static SwitchingExecutor of() { + return new SwitchingExecutor(); + } + + Optional invoke(Supplier switcher, Supplier trueSupplier, + Supplier falseSupplier) { + Optional option = null; + + if (switcher.get()) { + option = Optional.of(trueSupplier.get()); + } else { + option = Optional.of(falseSupplier.get()); + } + if (option == null) { + option = Optional.empty(); + } + + return option; + } +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/type/HgNodeStatus.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/type/HgNodeStatus.java new file mode 100644 index 000000000..8ff10bc03 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/type/HgNodeStatus.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client.type; + +/** + * created on 2021/10/26 + */ +public enum HgNodeStatus { + + UNKNOWN(0, "UNKNOWN"), + NOT_EXIST(100, "NOT_EXIST"), // Failed to apply for an instance via node-id from NodeManager. + NOT_ONLINE(105, "NOT_ONLINE"), // Failed to connect to Store-Node at the first time. + NOT_WORK(110, "NOT_WORK"), // When a Store-Node to be not work anymore. + + PARTITION_COMMON_FAULT(200, "PARTITION_COMMON_FAULT"), // + NOT_PARTITION_LEADER(205, + "NOT_PARTITION_LEADER") // When a Store-Node is not a specific partition + // leader. + + ; + + private final int status; + private final String name; + + HgNodeStatus(int status, String name) { + this.status = status; + this.name = name; + } + +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/type/HgStoreClientException.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/type/HgStoreClientException.java new file mode 100644 index 000000000..4b77dbe5d --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/type/HgStoreClientException.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client.type; + +/** + * created on 2021/10/27 + */ +public class HgStoreClientException extends RuntimeException { + + public HgStoreClientException(String msg) { + super(msg); + } + + public HgStoreClientException(Throwable cause) { + super(cause); + } + + public HgStoreClientException(String message, Throwable cause) { + super(message, cause); + } + + public static HgStoreClientException of(String msg) { + return new HgStoreClientException(msg); + } + + public static HgStoreClientException of(String msg, Throwable cause) { + return new HgStoreClientException(msg, cause); + } + + public static HgStoreClientException of(Throwable cause) { + return new HgStoreClientException(cause); + } +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/util/Base58.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/util/Base58.java new file mode 100644 index 000000000..08674d5ff --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/util/Base58.java @@ -0,0 +1,172 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client.util; + +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; + +/** + * TODO: refer license later, 78% match, maybe refer to google? ensure it later + */ +public class Base58 { + + public static final char[] ALPHABET = + "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray(); + private static final int[] INDEXES = new int[128]; + + static { + for (int i = 0; i < INDEXES.length; i++) { + INDEXES[i] = -1; + } + for (int i = 0; i < ALPHABET.length; i++) { + INDEXES[ALPHABET[i]] = i; + } + } + + /** + * Encodes the given bytes in base58. No checksum is appended. + */ + public static String encode(byte[] input) { + if (input.length == 0) { + return ""; + } + input = copyOfRange(input, 0, input.length); + // Count leading zeroes. + int zeroCount = 0; + while (zeroCount < input.length && input[zeroCount] == 0) { + ++zeroCount; + } + // The actual encoding. + byte[] temp = new byte[input.length * 2]; + int j = temp.length; + + int startAt = zeroCount; + while (startAt < input.length) { + byte mod = divmod58(input, startAt); + if (input[startAt] == 0) { + ++startAt; + } + temp[--j] = (byte) ALPHABET[mod]; + } + + // Strip extra '1' if there are some after decoding. + while (j < temp.length && temp[j] == ALPHABET[0]) { + ++j; + } + // Add as many leading '1' as there were leading zeros. + while (--zeroCount >= 0) { + temp[--j] = (byte) ALPHABET[0]; + } + + byte[] output = copyOfRange(temp, j, temp.length); + return new String(output, StandardCharsets.US_ASCII); + } + + public static byte[] decode(String input) throws IllegalArgumentException { + if (input.length() == 0) { + return new byte[0]; + } + byte[] input58 = new byte[input.length()]; + // Transform the String to a base58 byte sequence + for (int i = 0; i < input.length(); ++i) { + char c = input.charAt(i); + + int digit58 = -1; + if (c >= 0 && c < 128) { + digit58 = INDEXES[c]; + } + if (digit58 < 0) { + throw new IllegalArgumentException("Illegal character " + c + " at " + i); + } + + input58[i] = (byte) digit58; + } + // Count leading zeroes + int zeroCount = 0; + while (zeroCount < input58.length && input58[zeroCount] == 0) { + ++zeroCount; + } + // The encoding + byte[] temp = new byte[input.length()]; + int j = temp.length; + + int startAt = zeroCount; + while (startAt < input58.length) { + byte mod = divmod256(input58, startAt); + if (input58[startAt] == 0) { + ++startAt; + } + + temp[--j] = mod; + } + // Do no add extra leading zeroes, move j to first non null byte. + while (j < temp.length && temp[j] == 0) { + ++j; + } + + return copyOfRange(temp, j - zeroCount, temp.length); + } + + public static BigInteger decodeToBigInteger(String input) throws IllegalArgumentException { + return new BigInteger(1, decode(input)); + } + + // + // number -> number / 58, returns number % 58 + // + private static byte divmod58(byte[] number, int startAt) { + int remainder = 0; + for (int i = startAt; i < number.length; i++) { + int digit256 = (int) number[i] & 0xFF; + int temp = remainder * 256 + digit256; + + number[i] = (byte) (temp / 58); + + remainder = temp % 58; + } + + return (byte) remainder; + } + + // + // number -> number / 256, returns number % 256 + // + private static byte divmod256(byte[] number58, int startAt) { + int remainder = 0; + for (int i = startAt; i < number58.length; i++) { + int digit58 = (int) number58[i] & 0xFF; + int temp = remainder * 58 + digit58; + + number58[i] = (byte) (temp / 256); + + remainder = temp % 256; + } + + return (byte) remainder; + } + + private static byte[] copyOfRange(byte[] source, int from, int to) { + byte[] range = new byte[to - from]; + System.arraycopy(source, from, range, 0, range.length); + + return range; + } + + +} + diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/util/ExecutorPool.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/util/ExecutorPool.java new file mode 100644 index 000000000..f0aa85f48 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/util/ExecutorPool.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client.util; + +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public final class ExecutorPool { + + public static ThreadFactory newThreadFactory(String namePrefix) { + HgAssert.isArgumentNotNull(namePrefix, "namePrefix"); + return new DefaultThreadFactory(namePrefix); + } + + public static ThreadPoolExecutor createExecutor(String name, long keepAliveTime, + int coreThreads, int maxThreads) { + return new ThreadPoolExecutor(coreThreads, maxThreads, keepAliveTime, TimeUnit.SECONDS, + new SynchronousQueue<>(), + newThreadFactory(name), + new ThreadPoolExecutor.CallerRunsPolicy() + ); + } + + public static class DefaultThreadFactory implements ThreadFactory { + + private final AtomicInteger threadNumber = new AtomicInteger(1); + private final String namePrefix; + + public DefaultThreadFactory(String threadNamePrefix) { + this.namePrefix = threadNamePrefix + "-"; + } + + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(null, r, namePrefix + threadNumber.getAndIncrement(), 0); + t.setDaemon(true); + t.setPriority(Thread.NORM_PRIORITY); + return t; + } + } +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/util/HgAssert.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/util/HgAssert.java new file mode 100644 index 000000000..83e4c65bc --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/util/HgAssert.java @@ -0,0 +1,134 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client.util; + +import java.util.Collection; +import java.util.Map; +import java.util.function.Supplier; + + +public final class HgAssert { + public static void isTrue(boolean expression, String message) { + if (message == null) { + throw new IllegalArgumentException("message is null"); + } + if (!expression) { + throw new IllegalArgumentException(message); + } + } + + public static void isTrue(boolean expression, Supplier msg) { + if (msg == null) { + throw new IllegalArgumentException("message supplier is null"); + } + if (!expression) { + throw new IllegalArgumentException(msg.get()); + } + } + + public static void isFalse(boolean expression, String message) { + isTrue(!expression, message); + } + + public static void isFalse(boolean expression, Supplier msg) { + isTrue(!expression, msg); + } + + public static void isArgumentValid(byte[] bytes, String parameter) { + isFalse(isInvalid(bytes), () -> "The argument is invalid: " + parameter); + } + + public static void isArgumentValid(String str, String parameter) { + isFalse(isInvalid(str), () -> "The argument is invalid: " + parameter); + } + + public static void isArgumentValid(Collection collection, String parameter) { + isFalse(isInvalid(collection), () -> "The argument is invalid: " + parameter); + } + + public static void isArgumentNotNull(Object obj, String parameter) { + isTrue(obj != null, () -> "The argument is null: " + parameter); + } + + public static void istValid(byte[] bytes, String msg) { + isFalse(isInvalid(bytes), msg); + } + + public static void isValid(String str, String msg) { + isFalse(isInvalid(str), msg); + } + + public static void isNotNull(Object obj, String msg) { + isTrue(obj != null, msg); + } + + public static boolean isContains(Object[] objs, Object obj) { + if (objs == null || objs.length == 0 || obj == null) { + return false; + } + for (Object item : objs) { + if (obj.equals(item)) { + return true; + } + } + return false; + } + + public static boolean isInvalid(String... strs) { + if (strs == null || strs.length == 0) { + return true; + } + for (String item : strs) { + if (item == null || "".equals(item.trim())) { + return true; + } + } + return false; + } + + public static boolean isInvalid(byte[] bytes) { + return bytes == null || bytes.length == 0; + } + + public static boolean isInvalid(Map map) { + return map == null || map.isEmpty(); + } + + public static boolean isInvalid(Collection list) { + return list == null || list.isEmpty(); + } + + public static boolean isContains(Collection list, T item) { + if (list == null || item == null) { + return false; + } + return list.contains(item); + } + + public static boolean isNull(Object... objs) { + if (objs == null) { + return true; + } + for (Object item : objs) { + if (item == null) { + return true; + } + } + return false; + } +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/util/HgBufferProxy.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/util/HgBufferProxy.java new file mode 100644 index 000000000..d9240d0e5 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/util/HgBufferProxy.java @@ -0,0 +1,161 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client.util; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import javax.annotation.CheckForNull; + +import org.apache.hugegraph.store.client.type.HgStoreClientException; + +import lombok.extern.slf4j.Slf4j; + +/** + * 2022/3/15 + * + * @version 0.1.0 + */ +@Slf4j +public final class HgBufferProxy { + private final BlockingQueue> queue; + private final AtomicBoolean closed = new AtomicBoolean(false); + private final ReentrantLock lock = new ReentrantLock(); + private final Runnable task; + private Throwable err; + + private HgBufferProxy(Runnable task) { + this.task = task; + this.queue = new LinkedBlockingQueue<>(); + } + + public static HgBufferProxy of(Runnable task) { + HgAssert.isArgumentNotNull(task, "task"); + return new HgBufferProxy(task); + } + + public void send(T t) { + if (t == null) { + throw new IllegalArgumentException("the argument t is null"); + } + if (this.closed.get()) { + return; + } + this.lock.lock(); + try { + this.queue.offer(() -> t); + } finally { + lock.unlock(); + } + } + + private void apply() { + this.lock.lock(); + try { + if (!this.closed.get()) { + this.task.run(); + Thread.yield(); + } + } finally { + this.lock.unlock(); + } + } + + /** + * return an item from the chan + * + * @return null when the chan has been closed + * @throws RuntimeException + */ + @CheckForNull + public T receive(int time, Consumer callback) { + Supplier s; + if (this.closed.get()) { + s = this.queue.poll(); + this.checkErr(); + return s != null ? s.get() : null; + } + if (this.queue.size() <= 1) { + this.apply(); + } + lock.lock(); + try { + if (this.isClosed()) { + s = this.queue.poll(); + this.checkErr(); + return s != null ? s.get() : null; + } + } finally { + lock.unlock(); + } + try { + s = this.queue.poll(time, TimeUnit.SECONDS); + } catch (Throwable t) { + log.error("failed to receive a item from channel, cause by: ", t); + throw HgStoreClientException.of(t); + } + if (s == null) { + if (this.closed.get()) { + s = this.queue.poll(); + } else { + if (callback == null) { + throw new RuntimeException("timeout, max time: " + time + " seconds"); + } else { + callback.accept(time); + } + } + } + this.checkErr(); + return s != null ? s.get() : null; + } + + public boolean isClosed() { + return this.closed.get(); + } + + /** + * @throws RuntimeException when fail to close the chan + */ + public void close() { + if (this.closed.get()) { + return; + } + lock.lock(); + this.closed.set(true); + try { + this.queue.offer(() -> null); + } finally { + lock.unlock(); + } + } + + public void setError(Throwable streamErr) { + this.err = streamErr; + } + + private void checkErr() { + if (this.err != null) { + throw HgStoreClientException.of(this.err); + } + } +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/util/HgStoreClientConfig.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/util/HgStoreClientConfig.java new file mode 100644 index 000000000..69818f90e --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/util/HgStoreClientConfig.java @@ -0,0 +1,191 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client.util; + +import java.util.PropertyResourceBundle; + +import lombok.extern.slf4j.Slf4j; + +/** + * 2021/11/29 + */ +@Slf4j +public final class HgStoreClientConfig { + + private static final int GRPC_DEFAULT_TIMEOUT_SECONDS = 100; + private static final int GRPC_DEFAULT_MAX_INBOUND_MESSAGE_SIZE = 1024 * 1024 * 1024; + private static final int GRPC_DEFAULT_MAX_OUTBOUND_MESSAGE_SIZE = 1024 * 1024 * 1024; + + private static final int NET_KV_SCANNER_PAGE_SIZE = 10_000; + private static final int NET_KV_SCANNER_HAVE_NEXT_TIMEOUT = 30 * 60; + private static final String fileName = "hg-store-client"; + private static PropertyResourceBundle prb = null; + private static HgStoreClientConfig defaultInstance; + private Integer grpcTimeoutSeconds = GRPC_DEFAULT_TIMEOUT_SECONDS; + private Integer grpcMaxInboundMessageSize = GRPC_DEFAULT_MAX_INBOUND_MESSAGE_SIZE; + private Integer grpcMaxOutboundMessageSize = GRPC_DEFAULT_MAX_OUTBOUND_MESSAGE_SIZE; + private Integer netKvScannerPageSize = NET_KV_SCANNER_PAGE_SIZE; + private Integer netKvScannerHaveNextTimeout = NET_KV_SCANNER_HAVE_NEXT_TIMEOUT; + + + private HgStoreClientConfig() { + } + + public synchronized static HgStoreClientConfig of() { + + if (defaultInstance != null) { + return defaultInstance; + } + + defaultInstance = new HgStoreClientConfig(); + + overrideViaProperties(defaultInstance); + + return defaultInstance; + } + + private static void overrideViaProperties(HgStoreClientConfig config) { + try { + prb = (PropertyResourceBundle) PropertyResourceBundle.getBundle(fileName); + } catch (Throwable t) { + log.warn("Failed to load " + fileName + ".properties."); + log.info("Default configuration was activated."); + return; + } + PropertiesWrapper wrapper = new PropertiesWrapper(prb); + + log.info("grpc.timeout.seconds = " + + (config.grpcTimeoutSeconds = wrapper.getInt("grpc.timeout.seconds" + , config.grpcTimeoutSeconds)) + ); + log.info("grpc.max.inbound.message.size = " + + (config.grpcMaxInboundMessageSize = GRPC_DEFAULT_MAX_INBOUND_MESSAGE_SIZE) + ); + log.info("grpc.max.outbound.message.size = " + + (config.grpcMaxOutboundMessageSize = GRPC_DEFAULT_MAX_OUTBOUND_MESSAGE_SIZE) + ); + log.info("net.kv.scanner.page.size = " + + (config.netKvScannerPageSize = wrapper.getInt("net.kv.scanner.page.size" + , config.netKvScannerPageSize)) + ); + log.info("net.kv.scanner.have.next.timeout = {}", config.netKvScannerHaveNextTimeout); + } + + public Integer getGrpcTimeoutSeconds() { + return grpcTimeoutSeconds; + } + + public HgStoreClientConfig setGrpcTimeoutSeconds(Integer grpcTimeoutSeconds) { + this.grpcTimeoutSeconds = grpcTimeoutSeconds; + return this; + } + + public Integer getGrpcMaxInboundMessageSize() { + return grpcMaxInboundMessageSize; + } + + public HgStoreClientConfig setGrpcMaxInboundMessageSize(Integer grpcMaxInboundMessageSize) { + this.grpcMaxInboundMessageSize = grpcMaxInboundMessageSize; + return this; + } + + public Integer getGrpcMaxOutboundMessageSize() { + return grpcMaxOutboundMessageSize; + } + + public HgStoreClientConfig setGrpcMaxOutboundMessageSize(Integer grpcMaxOutboundMessageSize) { + this.grpcMaxOutboundMessageSize = grpcMaxOutboundMessageSize; + return this; + } + + public Integer getNetKvScannerPageSize() { + return netKvScannerPageSize; + } + + public HgStoreClientConfig setNetKvScannerPageSize(Integer netKvScannerPageSize) { + this.netKvScannerPageSize = netKvScannerPageSize; + return this; + } + + public Integer getNetKvScannerHaveNextTimeout() { + return netKvScannerHaveNextTimeout; + } + + public HgStoreClientConfig setNetKvScannerHaveNextTimeout(Integer netKvScannerHaveNextTimeout) { + this.netKvScannerHaveNextTimeout = netKvScannerHaveNextTimeout; + return this; + } + + private static class PropertiesWrapper { + private final PropertyResourceBundle prb; + + PropertiesWrapper(PropertyResourceBundle prb) { + this.prb = prb; + } + + Integer getInt(String key, Integer defaultValue) { + + String buf = this.getStr(key); + if (buf == null || buf.isEmpty()) { + return defaultValue; + } + + Integer res = null; + try { + res = Integer.valueOf(buf); + } catch (Throwable t) { + log.error("Failed to parse a int value[ " + buf + " ] of the key[ " + key + " ].", + t); + } + + return res; + + } + + String getStr(String key, String defaultValue) { + String res = getStr(key); + + if (res == null && defaultValue != null) { + return defaultValue; + } + + return res; + } + + String getStr(String key) { + String value = null; + + if (!prb.containsKey(key)) { + return null; + } + + try { + value = prb.getString(key); + } catch (Exception e) { + log.warn("Failed to get value with key: [" + key + "]"); + return null; + } + + if (value != null) { + value = value.trim(); + } + + return value; + } + } +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/util/HgStoreClientConst.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/util/HgStoreClientConst.java new file mode 100644 index 000000000..47955f597 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/util/HgStoreClientConst.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client.util; + +import java.util.Collections; +import java.util.List; + +import org.apache.hugegraph.store.HgKvStore; +import org.apache.hugegraph.store.HgOwnerKey; + + +public final class HgStoreClientConst { + public final static String DEFAULT_NODE_CLUSTER_ID = "default-node-cluster"; + + public final static String EMPTY_STRING = ""; + public final static String EMPTY_TABLE = ""; + public final static byte[] EMPTY_BYTES = new byte[0]; + public final static byte[] MAX_BYTES = new byte[]{(byte) 0b11111111}; + public final static List EMPTY_LIST = Collections.EMPTY_LIST; + + public final static byte[] ALL_PARTITION_OWNER = new byte[0]; + // means to dispatch to all partitions. + public final static HgOwnerKey EMPTY_OWNER_KEY = HgOwnerKey.of(EMPTY_BYTES, EMPTY_BYTES); + public final static HgOwnerKey ALL_PARTITION_OWNER_KEY = + HgOwnerKey.of(ALL_PARTITION_OWNER, ALL_PARTITION_OWNER); + + //public final static int SCAN_GTE_BEGIN_LT_END = SCAN_GTE_BEGIN | SCAN_LT_END; + public final static int SCAN_TYPE_RANGE = HgKvStore.SCAN_GTE_BEGIN | HgKvStore.SCAN_LTE_END; + public final static int SCAN_TYPE_ANY = HgKvStore.SCAN_ANY; + public final static int NO_LIMIT = 0; + + public final static int TX_SESSIONS_MAP_CAPACITY = 32; + public static final int NODE_MAX_RETRYING_TIMES = 10; + + +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/util/HgStoreClientUtil.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/util/HgStoreClientUtil.java new file mode 100644 index 000000000..71001d5bc --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/util/HgStoreClientUtil.java @@ -0,0 +1,174 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client.util; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +import org.apache.hugegraph.store.HgOwnerKey; +import org.apache.hugegraph.store.client.type.HgStoreClientException; + +import lombok.extern.slf4j.Slf4j; + +/** + * created on 2021/10/14 + */ +@Slf4j +public final class HgStoreClientUtil { + public static HgOwnerKey toOwnerKey(byte[] key) { + return new HgOwnerKey(HgStoreClientConst.EMPTY_BYTES, key); + } + + public static HgOwnerKey toOwnerKey(String key) { + return new HgOwnerKey(HgStoreClientConst.EMPTY_BYTES, toBytes(key)); + } + + public static HgOwnerKey toAllNodeKey(String key) { + return new HgOwnerKey(HgStoreClientConst.ALL_PARTITION_OWNER, toBytes(key)); + } + + public static HgOwnerKey toOwnerKey(String owner, String key) { + return new HgOwnerKey(toBytes(owner), toBytes(key)); + } + + public static HgStoreClientException err(String msg) { + log.error(msg); + return HgStoreClientException.of(msg); + } + + public static boolean isValid(HgOwnerKey key) { + if (key == null) { + return false; + } + if (key.getKey() == null) { + return false; + } + return key.getKey().length != 0; + } + + public static String toStr(byte[] b) { + if (b == null) { + return ""; + } + if (b.length == 0) { + return ""; + } + return new String(b, StandardCharsets.UTF_8); + } + + public static String toByteStr(byte[] b) { + if (b == null) { + return ""; + } + if (b.length == 0) { + return ""; + } + return Arrays.toString(b); + } + + public static String toStr(HgOwnerKey ownerKey) { + if (ownerKey == null) { + return ""; + } + return "{ " + + "owner: " + Arrays.toString(ownerKey.getOwner()) + + ", key: " + toStr(ownerKey.getKey()) + + " }"; + } + + public static byte[] toBytes(String str) { + if (str == null) { + return null; + } + return str.getBytes(StandardCharsets.UTF_8); + } + + public static byte[] toBytes(long l) { + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.putLong(l); + return buffer.array(); + } + + public static byte[] toIntBytes(final int i) { + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.putInt(i); + return buffer.array(); + } + + public static long toLong(byte[] bytes) { + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.put(bytes); + buffer.flip();//need flip + return buffer.getLong(); + } + + public static int toInt(byte[] bytes) { + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.put(bytes); + buffer.flip();//need flip + return buffer.getInt(); + } + + public static String getHostAddress() { + String res = null; + + try { + res = InetAddress.getLocalHost().getHostAddress(); + } catch (UnknownHostException e) { + e.printStackTrace(); + res = ""; + } + + return res; + } + + public static byte[] combine(byte[] first, byte[] second) { + if (first == null) { + first = HgStoreClientConst.EMPTY_BYTES; + } + + if (second == null) { + second = HgStoreClientConst.EMPTY_BYTES; + } + + byte[] result = new byte[first.length + second.length]; + System.arraycopy(first, 0, result, 0, first.length); + System.arraycopy(second, 0, result, first.length, second.length); + return result; + } + + public static void printCallStack(String txt, Throwable ex) { + StackTraceElement[] stackElements = ex.getStackTrace(); + StringBuilder sb = new StringBuilder(); + sb.append(txt).append(":\n"); + if (stackElements != null) { + for (int i = 0; i < stackElements.length; i++) { + sb.append(stackElements[i].getClassName()).append(" : ") + .append(stackElements[i].getMethodName()).append(" [ "); + sb.append(stackElements[i].getLineNumber()).append(" ]\n"); + + } + sb.append( + "--------------------------------------------------------------------------------------\n"); + } + log.error(sb.toString()); + } +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/util/HgUuid.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/util/HgUuid.java new file mode 100644 index 000000000..f92e688ee --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/util/HgUuid.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client.util; + +import java.nio.ByteBuffer; +import java.util.UUID; + + +public final class HgUuid { + + private static String encode(UUID uuid) { + ByteBuffer bb = ByteBuffer.wrap(new byte[16]); + bb.putLong(uuid.getMostSignificantBits()); + bb.putLong(uuid.getLeastSignificantBits()); + return Base58.encode(bb.array()); + } + + /** + * Get a UUID in Base58 FORM + * + * @return + */ + public static String newUUID() { + return encode(UUID.randomUUID()); + } + +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/util/MetricX.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/util/MetricX.java new file mode 100644 index 000000000..a50a7110b --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/util/MetricX.java @@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client.util; + +import java.util.concurrent.atomic.AtomicLong; + +/** + * 2022/1/29 + */ +public class MetricX { + public static AtomicLong iteratorSum = new AtomicLong(); + public static AtomicLong iteratorCount = new AtomicLong(); + public static AtomicLong iteratorMax = new AtomicLong(); + + public AtomicLong failureCount = new AtomicLong(); + + private long start; + private long end; + + private MetricX(long start) { + this.start = start; + } + + public static MetricX ofStart() { + return new MetricX(System.currentTimeMillis()); + } + + public static void plusIteratorWait(long nanoSeconds) { + iteratorSum.addAndGet(nanoSeconds); + iteratorCount.getAndIncrement(); + if (iteratorMax.get() < nanoSeconds) { + iteratorMax.set(nanoSeconds); + } + } + + /** + * amount of waiting + * + * @return millisecond + */ + public static long getIteratorWait() { + return iteratorSum.get() / 1_000_000; + } + + /** + * average of waiting + * + * @return millisecond + */ + public static long getIteratorWaitAvg() { + if (iteratorCount.get() == 0) { + return -1; + } + return getIteratorWait() / iteratorCount.get(); + } + + /** + * maximum of waiting + * + * @return millisecond + */ + public static long getIteratorWaitMax() { + return iteratorMax.get() / 1_000_000; + } + + public static long getIteratorCount() { + return iteratorCount.get(); + } + + public long start() { + return this.start = System.currentTimeMillis(); + } + + public long end() { + return this.end = System.currentTimeMillis(); + } + + public long past() { + return this.end - this.start; + } + + public void countFail() { + this.failureCount.getAndIncrement(); + } + + public long getFailureCount() { + return this.failureCount.get(); + } + +} diff --git a/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/util/PropertyUtil.java b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/util/PropertyUtil.java new file mode 100644 index 000000000..f6f297a58 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/java/org/apache/hugegraph/store/client/util/PropertyUtil.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client.util; + +import java.security.AccessController; +import java.security.PrivilegedAction; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +public class PropertyUtil { + private static final Logger LOG = LoggerFactory.getLogger(PropertyUtil.class); + + public static String get(String key) { + return get(key, null); + } + + public static String get(final String key, String def) { + if (key == null) { + throw new NullPointerException("key"); + } + if (key.isEmpty()) { + throw new IllegalArgumentException("key must not be empty."); + } + + String value = null; + try { + if (System.getSecurityManager() == null) { + value = System.getProperty(key); + } else { + value = AccessController.doPrivileged( + (PrivilegedAction) () -> System.getProperty(key)); + } + } catch (Exception e) { + LOG.error("exception {}", e); + } + + if (value == null) { + return def; + } + + return value; + } + + + public static boolean getBoolean(String key, boolean def) { + String value = get(key, Boolean.toString(def)); + value = value.trim().toLowerCase(); + if (value.isEmpty()) { + return true; + } + + if ("true".equals(value) || "yes".equals(value) || "1".equals(value)) { + return true; + } + + if ("false".equals(value) || "no".equals(value) || "0".equals(value)) { + return false; + } + return def; + } + + public static int getInt(String key, int def) { + String value = get(key); + if (value == null) { + return def; + } + + value = value.trim().toLowerCase(); + try { + return Integer.parseInt(value); + } catch (Exception e) { + LOG.warn("exception ", e); + } + return def; + } + + public static Object setProperty(String key, String value) { + return System.getProperties().setProperty(key, value); + } + +} diff --git a/hugegraph-store/hg-store-client/src/main/resources/hg-store-client.properties b/hugegraph-store/hg-store-client/src/main/resources/hg-store-client.properties new file mode 100644 index 000000000..aee00cf90 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/resources/hg-store-client.properties @@ -0,0 +1,22 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to You under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +#grpc.timeout.seconds=10 +#grpc.max.inbound.message.size= +#grpc.max.outbound.message.size= +#net.kv.scanner.page.size = 2000 +#Unit:second +#net.kv.scanner.have.next.timeout=60 \ No newline at end of file diff --git a/hugegraph-store/hg-store-client/src/main/resources/log4j2.xml b/hugegraph-store/hg-store-client/src/main/resources/log4j2.xml new file mode 100644 index 000000000..54096d41f --- /dev/null +++ b/hugegraph-store/hg-store-client/src/main/resources/log4j2.xml @@ -0,0 +1,102 @@ + + + + + + + + logs + hg-store-client + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hugegraph-store/hg-store-client/src/test/java/org/apache/hugegraph/store/HgSessionManagerOneRaftFakePDTest.java b/hugegraph-store/hg-store-client/src/test/java/org/apache/hugegraph/store/HgSessionManagerOneRaftFakePDTest.java new file mode 100644 index 000000000..7d40dd5ac --- /dev/null +++ b/hugegraph-store/hg-store-client/src/test/java/org/apache/hugegraph/store/HgSessionManagerOneRaftFakePDTest.java @@ -0,0 +1,481 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store; + +import static org.apache.hugegraph.store.client.util.HgAssert.isInvalid; +import static org.apache.hugegraph.store.client.util.HgStoreClientConst.ALL_PARTITION_OWNER; +import static org.apache.hugegraph.store.client.util.HgStoreClientConst.EMPTY_BYTES; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +import org.apache.hugegraph.pd.common.PartitionUtils; +import org.apache.hugegraph.store.client.HgStoreNodeManager; +import org.apache.hugegraph.store.client.util.HgStoreClientConst; +import org.apache.hugegraph.store.util.HgStoreTestUtil; +import org.junit.Assert; + +/** + * 使用fake-pd,支持raft的单元测试 + */ +public class HgSessionManagerOneRaftFakePDTest { + private static final Map leaderMap = new ConcurrentHashMap<>(); + private static final Map storeMap = new ConcurrentHashMap<>(); + + private static final int partitionCount = 3; + // 需要与store的application.yml的fake-pd.partition-count保持一致 + private static final String[] storeAddress = { + "127.0.0.1:8500" + }; + + // @BeforeClass + public static void init() { + for (String address : storeAddress) { + storeMap.put((long) address.hashCode(), address); + } + for (int i = 0; i < partitionCount; i++) { + leaderMap.put(i, storeMap.keySet().iterator().next()); + } + + HgStoreNodeManager nodeManager = HgStoreNodeManager.getInstance(); + nodeManager.setNodePartitioner((builder, graphName, startKey, endKey) -> { + int startCode = PartitionUtils.calcHashcode(startKey); + int endCode = PartitionUtils.calcHashcode(endKey); + if (ALL_PARTITION_OWNER == startKey) { + storeMap.forEach((k, v) -> { + builder.add(k, -1); + }); + } else if (endKey == HgStoreClientConst.EMPTY_BYTES || startKey == endKey || + Arrays.equals(startKey, endKey)) { + builder.add(leaderMap.get(startCode % partitionCount), startCode); + } else { + Assert.fail("OwnerKey转成HashCode后已经无序了, 按照OwnerKey范围查询没意义"); + builder.add(leaderMap.get(startCode % partitionCount), startCode); + builder.add(leaderMap.get(endCode % partitionCount), endCode); + } + return 0; + }); + nodeManager.setNodeProvider((graphName, nodeId) -> { + System.out.println("HgStoreNodeProvider apply " + graphName + " " + nodeId + " " + + storeMap.get(nodeId)); + return nodeManager.getNodeBuilder().setNodeId(nodeId) + .setAddress(storeMap.get(nodeId)).build(); + }); + nodeManager.setNodeNotifier((graphName, storeNotice) -> { + System.out.println("recv node notifier " + storeNotice); + if (storeNotice.getPartitionLeaders().size() > 0) { + leaderMap.putAll(storeNotice.getPartitionLeaders()); + System.out.println("leader changed "); + leaderMap.forEach((k, v) -> { + System.out.print(" " + k + " " + v + ","); + }); + System.out.println(); + } + return 0; + }); + } + + private static HgStoreSession getStoreSession() { + return HgSessionManager.getInstance().openSession(HgStoreTestUtil.GRAPH_NAME); + } + + private static HgStoreSession getStoreSession(String graph) { + return HgSessionManager.getInstance().openSession(graph); + } + + // @Test + public void put_get() { + System.out.println("--- test put & get ---"); + + HgStoreSession session = getStoreSession(); + + HgOwnerKey key = HgStoreTestUtil.toOwnerKey("FOR-PUT-KEY"); + //HgOwnerKey key = toAllNodeKey("FOR-PUT-KEY"); + byte[] value = HgStoreTestUtil.toBytes("FOR-PUT-VALUE"); + + Assert.assertTrue(session.put(HgStoreTestUtil.TABLE_NAME, key, value)); + + Assert.assertEquals(HgStoreTestUtil.toStr(value), HgStoreTestUtil.toStr(session.get( + HgStoreTestUtil.TABLE_NAME, key))); + } + + // @Test + public void put_get2() { + System.out.println("--- test put & get 2---"); + + String GRAPH_NAME = "default/hugegraph/g2"; + String TABLE_NAME = "put_get2"; + + HgStoreSession session = getStoreSession(GRAPH_NAME); + + HgOwnerKey key = HgStoreTestUtil.toOwnerKey("FOR-PUT-KEY"); + //HgOwnerKey key = toAllNodeKey("FOR-PUT-KEY"); + byte[] value = HgStoreTestUtil.toBytes("FOR-PUT-VALUE"); + + Assert.assertTrue(session.put(TABLE_NAME, key, value)); + + Assert.assertEquals(HgStoreTestUtil.toStr(value), + HgStoreTestUtil.toStr(session.get(TABLE_NAME, key))); + + HgKvIterator iterator = session.scanIterator(TABLE_NAME); + while (iterator.hasNext()) { + HgKvEntry entry = iterator.next(); + + System.out.println(new String(entry.key())); + Assert.assertEquals(HgStoreTestUtil.toStr(key.getKey()), + HgStoreTestUtil.toStr(entry.key())); + } + + } + + + // @Test + public void batchGet() { + System.out.println("--- test batchGet ---"); + HgStoreSession session = getStoreSession(); + String keyPrefix = "BATCH-GET-UNIT"; + + Map map = HgStoreTestUtil.batchPut(session, keyPrefix); + List keyList = + map.entrySet().stream().map(e -> e.getKey()).collect(Collectors.toList()); + + //printOwner(keyList); + List resList = session.batchGetOwner(HgStoreTestUtil.TABLE_NAME, keyList); + + Assert.assertFalse(isInvalid(resList)); + Assert.assertEquals(resList.size(), keyList.size()); + + // println(list); + HgStoreTestUtil.println("--- batch-get result ---"); + Assert.assertTrue((resList.stream() + .map(e -> map.containsKey(HgStoreTestUtil.toOwnerKey(e.key()))) + .allMatch(Boolean::booleanValue)) + ); + + } + + // @Test + //CAUTION: ONLY FOR LONG! + //注意:目前只可以对long类型value进行Merge操作。 + public void merge() { + System.out.println("--- test merge (1+1=2)---"); + HgStoreSession session = getStoreSession(); + String mergeKey = "merge-key"; + HgOwnerKey key = HgStoreTestUtil.toOwnerKey(mergeKey); + byte[] value = HgStoreTestUtil.toBytes(1L); + + System.out.println("- put " + mergeKey + ":1 -"); + session.put(HgStoreTestUtil.TABLE_NAME, key, value); + System.out.println("- merge " + mergeKey + ":1 -"); + session.merge(HgStoreTestUtil.TABLE_NAME, key, value); + long res = HgStoreTestUtil.toLong(session.get(HgStoreTestUtil.TABLE_NAME, key)); + System.out.printf("after merge " + mergeKey + "=%s%n", res); + Assert.assertEquals(2L, res); + + String putStr = "19"; + session.put(HgStoreTestUtil.TABLE_NAME, key, HgStoreTestUtil.toBytes(putStr)); + byte[] b1 = session.get(HgStoreTestUtil.TABLE_NAME, key); + Assert.assertEquals(putStr, HgStoreTestUtil.toStr(b1)); + } + + // @Test + public void delete() { + System.out.println("--- test delete ---"); + HgStoreSession session = getStoreSession(); + + String delKey = "del-key"; + String delValue = "del-value"; + HgOwnerKey key = HgStoreTestUtil.toOwnerKey(delKey); + byte[] value = HgStoreTestUtil.toBytes(delValue); + + HgStoreTestUtil.println("- put " + delKey + ": " + delValue); + session.put(HgStoreTestUtil.TABLE_NAME, key, value); + HgStoreTestUtil.println("- delete " + delKey); + session.delete(HgStoreTestUtil.TABLE_NAME, key); + value = session.get(HgStoreTestUtil.TABLE_NAME, key); + HgStoreTestUtil.println("- get " + delKey + ": " + HgStoreTestUtil.toStr(value)); + Assert.assertEquals(EMPTY_BYTES, value); + } + + // @Test + public void deleteSingle() { + System.out.println("--- test deleteSingle ---"); + HgStoreSession session = getStoreSession(); + + String delKey = "del-single-key"; + String delValue = "del-single-value"; + HgOwnerKey key = HgStoreTestUtil.toOwnerKey(delKey); + byte[] value = HgStoreTestUtil.toBytes(delValue); + + HgStoreTestUtil.println("- put [" + delKey + "] = " + delValue); + session.put(HgStoreTestUtil.TABLE_NAME, key, value); + + value = session.get(HgStoreTestUtil.TABLE_NAME, key); + HgStoreTestUtil.println( + "- before del, get [" + delKey + "] = " + HgStoreTestUtil.toStr(value)); + Assert.assertEquals(delValue, HgStoreTestUtil.toStr(value)); + + HgStoreTestUtil.println("- delete-single : [" + delKey + "]"); + session.deleteSingle(HgStoreTestUtil.TABLE_NAME, key); + value = session.get(HgStoreTestUtil.TABLE_NAME, key); + HgStoreTestUtil.println( + "- after del, get [" + delKey + "] = " + HgStoreTestUtil.toStr(value)); + Assert.assertEquals("", HgStoreTestUtil.toStr(value)); + + } + + // @Test + public void deleteRange() { + HgStoreTestUtil.println("--- test deleteRange ---"); + HgStoreSession session = getStoreSession(); + + String rangePrefix = "DEL-RANGE-KEY"; + String owner = "batch-delete-owner"; + Map map = + HgStoreTestUtil.batchPut(session, HgStoreTestUtil.TABLE_NAME, rangePrefix, 10, + key -> { + return HgStoreTestUtil.toOwnerKey(owner, key); + }); + + HgOwnerKey startKey = HgStoreTestUtil.toOwnerKey(owner, rangePrefix + "-00"); + HgOwnerKey endKey = HgStoreTestUtil.toOwnerKey(owner, rangePrefix + "-05"); + + Assert.assertTrue(session.deleteRange(HgStoreTestUtil.TABLE_NAME, startKey, endKey)); + + HgStoreTestUtil.println("- after delete range from [" + + HgStoreTestUtil.toStr(startKey.getKey()) + + "] to [" + + HgStoreTestUtil.toStr(endKey.getKey()) + "]"); + + for (int i = 0; i < 10; i++) { + HgOwnerKey key = HgStoreTestUtil.toOwnerKey(owner, rangePrefix + "-" + + HgStoreTestUtil.padLeftZeros( + String.valueOf(i), 2)); + String value = HgStoreTestUtil.toStr(session.get(HgStoreTestUtil.TABLE_NAME, key)); + HgStoreTestUtil.println( + "- get [" + HgStoreTestUtil.toStr(key.getKey()) + "] = " + value); + + // TODO: [start,end)? + if (i < 5) { + Assert.assertEquals("", value); + } else { + //println(value); + Assert.assertNotEquals("", value); + } + } + + } + + // @Test + public void deletePrefix() { + System.out.println("--- test deletePrefix ---"); + HgStoreSession session = getStoreSession(); + + String prefixStr = "DEL-PREFIX-KEY"; + String owner = "batch-delete-owner"; + Map map = + HgStoreTestUtil.batchPut(session, HgStoreTestUtil.TABLE_NAME, prefixStr, 10, + key -> { + return HgStoreTestUtil.toOwnerKey(owner, key); + }); + + //printOwner(map.entrySet().stream().map(e -> e.getKey()).collect(Collectors.toList())); + + HgOwnerKey prefix = HgStoreTestUtil.toOwnerKey(owner, prefixStr); + + Assert.assertEquals(10, HgStoreTestUtil.amountOf(session.scanIterator( + HgStoreTestUtil.TABLE_NAME, prefix))); + session.deletePrefix(HgStoreTestUtil.TABLE_NAME, prefix); + Assert.assertEquals(0, HgStoreTestUtil.amountOf(session.scanIterator( + HgStoreTestUtil.TABLE_NAME, prefix))); + + HgStoreTestUtil.println("- after delete by prefix:[" + prefixStr + "]"); + + for (int i = 0; i < 10; i++) { + HgOwnerKey key = + HgStoreTestUtil.toOwnerKey(owner, prefixStr + HgStoreTestUtil.toSuffix(i, 2)); + String value = HgStoreTestUtil.toStr(session.get(HgStoreTestUtil.TABLE_NAME, key)); + System.out.println("- get [" + HgStoreTestUtil.toStr(key.getKey()) + "] = " + value); + Assert.assertEquals("", value); + } + + } + + // @Test + public void scanIterator() { + HgStoreTestUtil.println("--- test scanIterator ---"); + String tableName = "UNIT_SCAN"; + String keyName = "SCAN-ITER"; + HgStoreSession session = getStoreSession(); + HgStoreTestUtil.batchPut(session, tableName, keyName, 10000); + int count = 0; + int limit = 0; + int max = 99999; + HgKvIterator iterator = null; + + HgStoreTestUtil.println("-- test 0 element --"); + iterator = session.scanIterator(tableName, HgStoreTestUtil.toAllPartitionKey("__SCAN-001"), + HgStoreTestUtil.toAllPartitionKey("__SCAN-100"), 0); + Assert.assertFalse(iterator.hasNext()); + try { + iterator.next(); + Assert.fail(); + } catch (Throwable t) { + HgStoreTestUtil.println("-- test NoSuchElementException --"); + Assert.assertTrue(t instanceof NoSuchElementException); + } + + HgStoreTestUtil.println("-- test limit 1 to 10 --"); + for (int i = 1; i <= 10; i++) { + HgStoreTestUtil.println("- limit " + i + " -"); + limit = i; + iterator = session.scanIterator(tableName, + HgStoreTestUtil.toAllPartitionKey(keyName + "-0"), + HgStoreTestUtil.toAllPartitionKey(keyName + "-1"), + limit); + count = 0; + while (iterator.hasNext()) { + count++; + HgKvEntry entry = iterator.next(); + HgStoreTestUtil.println(entry); + } + Assert.assertEquals(limit, count); + } + + HgStoreTestUtil.println("-- test limit 1 to 10 not enough --"); + for (int i = 1; i <= 10; i++) { + HgStoreTestUtil.println("- limit " + i + " -"); + limit = i; + iterator = session.scanIterator(tableName, + HgStoreTestUtil.toAllPartitionKey(keyName + "-00001"), + HgStoreTestUtil.toAllPartitionKey(keyName + "-00005"), + limit); + count = 0; + while (iterator.hasNext()) { + count++; + HgKvEntry entry = iterator.next(); + HgStoreTestUtil.println(entry); + } + if (i <= 5) { + Assert.assertEquals(limit, count); + } else { + Assert.assertEquals(5, count); + } + + } + + HgStoreTestUtil.println("-- test limit 0 (no limit) --"); + limit = 0; + iterator = + session.scanIterator(tableName, HgStoreTestUtil.toAllPartitionKey(keyName + "-0"), + HgStoreTestUtil.toAllPartitionKey(keyName + "-1"), limit); + + count = 0; + while (iterator.hasNext()) { + count++; + HgKvEntry entry = iterator.next(); + if (count % 1000 == 0) { + HgStoreTestUtil.println(entry); + } + if (count >= max) break; + } + Assert.assertEquals(10000, count); + + HgStoreTestUtil.println("-- test scan all --"); + iterator = session.scanIterator(tableName); + count = 0; + while (iterator.hasNext()) { + count++; + HgKvEntry entry = iterator.next(); + if (count % 1000 == 0) { + HgStoreTestUtil.println(entry); + } + if (count >= max) break; + } + Assert.assertEquals(10000, count); + + HgStoreTestUtil.println("-- test scan prefix --"); + iterator = + session.scanIterator(tableName, HgStoreTestUtil.toAllPartitionKey(keyName + "-01")); + count = 0; + while (iterator.hasNext()) { + count++; + HgKvEntry entry = iterator.next(); + if (count % 100 == 0) { + HgStoreTestUtil.println(entry); + } + if (count >= max) break; + } + Assert.assertEquals(1000, count); + } + + + // @Test + public void truncate() { + HgStoreTestUtil.println("--- test truncate ---"); + String graph = "graph_truncate"; + String tableName = "UNIT_TRUNCATE_1"; + String keyName = "KEY_TRUNCATE"; + + HgStoreSession session = getStoreSession(graph); + HgStoreTestUtil.batchPut(session, tableName, keyName, 100); + Assert.assertEquals(100, HgStoreTestUtil.amountOf(session.scanIterator(tableName))); + + String tableName2 = "UNIT_TRUNCATE_2"; + HgStoreTestUtil.batchPut(session, tableName2, keyName, 100); + Assert.assertEquals(100, HgStoreTestUtil.amountOf(session.scanIterator(tableName2))); + + + session.truncate(); + Assert.assertEquals(0, HgStoreTestUtil.amountOf(session.scanIterator(tableName))); + Assert.assertEquals(0, HgStoreTestUtil.amountOf(session.scanIterator(tableName2))); + } + + //// @Test + public void scanIteratorHuge() { + + /*************** test no limit, with 10 millions **************/ + String tableName = "UNIT_HUGE"; + String keyName = "SCAN-HUGE"; + int amount = 10_000_000; + HgStoreSession session = getStoreSession(); + + if (HgStoreTestUtil.amountOf( + session.scanIterator(tableName, HgStoreTestUtil.toAllPartitionKey(keyName), 10)) < + 10) { + HgStoreTestUtil.batchPut(session, tableName, keyName, amount); + } + + int count = 0; + HgKvIterator iterator = session.scanIterator(tableName); + + while (iterator.hasNext()) { + count++; + HgKvEntry entry = iterator.next(); + if (count % (amount / 10) == 0) { + HgStoreTestUtil.println(entry); + } + } + + Assert.assertEquals(amount, count); + } +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-client/src/test/java/org/apache/hugegraph/store/HgSessionManagerRaftFakePDTest.java b/hugegraph-store/hg-store-client/src/test/java/org/apache/hugegraph/store/HgSessionManagerRaftFakePDTest.java new file mode 100644 index 000000000..73377a552 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/test/java/org/apache/hugegraph/store/HgSessionManagerRaftFakePDTest.java @@ -0,0 +1,648 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store; + +import static org.apache.hugegraph.store.client.util.HgAssert.isInvalid; +import static org.apache.hugegraph.store.client.util.HgStoreClientConst.ALL_PARTITION_OWNER; +import static org.apache.hugegraph.store.client.util.HgStoreClientConst.EMPTY_BYTES; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +import org.apache.hugegraph.pd.common.PartitionUtils; +import org.apache.hugegraph.store.client.HgStoreNodeManager; +import org.apache.hugegraph.store.client.util.HgStoreClientConst; +import org.apache.hugegraph.store.util.HgStoreTestUtil; +import org.junit.Assert; + +/** + * 使用fake-pd,支持raft的单元测试 + */ +public class HgSessionManagerRaftFakePDTest { + private static final Map leaderMap = new ConcurrentHashMap<>(); + private static final Map storeMap = new ConcurrentHashMap<>(); + + private static final int partitionCount = 3; + // 需要与store的application.yml的fake-pd.partition-count保持一致 + private static final String[] storeAddress = + { // 需要与store的application.yml的fake-pd.store-list保持一致 + "127.0.0.1:8501", "127.0.0.1:8502", "127.0.0.1:8503" + }; + + /*private static String[] storeAddress = { + "127.0.0.1:9080" + };*/ + // @BeforeClass + public static void init() { + for (String address : storeAddress) { + storeMap.put((long) address.hashCode(), address); + } + for (int i = 0; i < partitionCount; i++) { + leaderMap.put(i, storeMap.keySet().iterator().next()); + } + + HgStoreNodeManager nodeManager = HgStoreNodeManager.getInstance(); + nodeManager.setNodePartitioner((builder, graphName, startKey, endKey) -> { + int startCode = PartitionUtils.calcHashcode(startKey); + int endCode = PartitionUtils.calcHashcode(endKey); + if (ALL_PARTITION_OWNER == startKey) { + storeMap.forEach((k, v) -> { + builder.add(k, -1); + }); + } else if (endKey == HgStoreClientConst.EMPTY_BYTES || startKey == endKey || + Arrays.equals(startKey, endKey)) { + builder.add(leaderMap.get(startCode % partitionCount), startCode); + } else { + Assert.fail("OwnerKey转成HashCode后已经无序了, 按照OwnerKey范围查询没意义"); + builder.add(leaderMap.get(startCode % partitionCount), startCode); + builder.add(leaderMap.get(endCode % partitionCount), endCode); + } + return 0; + }); + nodeManager.setNodeProvider((graphName, nodeId) -> { + System.out.println("HgStoreNodeProvider apply " + graphName + " " + nodeId + " " + + storeMap.get(nodeId)); + return nodeManager.getNodeBuilder().setNodeId(nodeId) + .setAddress(storeMap.get(nodeId)).build(); + }); + nodeManager.setNodeNotifier((graphName, storeNotice) -> { + System.out.println("recv node notifier " + storeNotice); + if (storeNotice.getPartitionLeaders().size() > 0) { + leaderMap.putAll(storeNotice.getPartitionLeaders()); + System.out.println("leader changed "); + leaderMap.forEach((k, v) -> { + System.out.print(" " + k + " " + v + ","); + }); + System.out.println(); + } + return 0; + }); + } + + private static HgStoreSession getStoreSession() { + return HgSessionManager.getInstance().openSession(HgStoreTestUtil.GRAPH_NAME); + } + + private static HgStoreSession getStoreSession(String graph) { + return HgSessionManager.getInstance().openSession(graph); + } + + // @Test + public void put_get() { + System.out.println("--- test put & get ---"); + + HgStoreSession session = getStoreSession(); + + HgOwnerKey key = HgStoreTestUtil.toOwnerKey("FOR-PUT-KEY"); + //HgOwnerKey key = toAllNodeKey("FOR-PUT-KEY"); + byte[] value = HgStoreTestUtil.toBytes("FOR-PUT-VALUE"); + + Assert.assertTrue(session.put(HgStoreTestUtil.TABLE_NAME, key, value)); + + Assert.assertEquals(HgStoreTestUtil.toStr(value), HgStoreTestUtil.toStr(session.get( + HgStoreTestUtil.TABLE_NAME, key))); + } + +// // @Test +// public void batchPutOwner() { +// Map> entries = new HashMap<>(2); +// +// int ownerNum = 10; +// int keyNum = 10; +// String keyPrefix = "BATCH-UNIT-TEST-"; +// long amount = ownerNum * keyNum; +// Map kv = new HashMap<>(keyNum, 1); +// +// for (int i = 0; i < ownerNum; i++) { +// +// for (int ii = 0; ii < keyNum; ii++) { +// HgOwnerKey ownerKey = new HgOwnerKey(toBytes("OWNER-" + i) +// , toBytes(keyPrefix + i + "-" + ii)); +// byte[] value = toBytes(keyPrefix + "VALUE-" + ownerNum + "-" + ii); +// kv.put(ownerKey, value); +// } +// +// } +// +// entries.put(TABLE_NAME, kv); +// HgStoreSession session = getStoreSession(); +// Assert.assertTrue(session.batchPutOwner(entries)); +// System.out.println("put ok"); +// Assert.assertEquals(amount, amountOf(session.scanIterator(TABLE_NAME, toAllPartitionKey +// (keyPrefix)))); +// +// } + + // // @Test +// +// public void batchPutWithoutStop() { +// final int loops = 6 * 10; +// final int interval = 1000 * 10; +// for(int j=0;j> entries = new HashMap<>(2); +// +// int ownerNum = 10; +// int keyNum = 10; +// String keyPrefix = "BATCH-UNIT-TEST-"; +// long amount = ownerNum * keyNum; +// Map kv = new HashMap<>(keyNum, 1); +// +// for (int i = 0; i < ownerNum; i++) { +// +// for (int ii = 0; ii < keyNum; ii++) { +// HgOwnerKey ownerKey = new HgOwnerKey(toBytes("OWNER-" + i * j) +// , toBytes(keyPrefix + i + "-" + ii)); +// byte[] value = toBytes(keyPrefix + "VALUE-" + ownerNum + "-" + ii*j); +// kv.put(ownerKey, value); +// } +// } +// +// entries.put(TABLE_NAME, kv); +// HgStoreSession session = getStoreSession(); +// Assert.assertTrue(session.batchPutOwner(entries)); +// System.out.println("put ok"); +// +// try { +// Thread.sleep(interval); +// } catch (InterruptedException e) { +// System.out.println(e.getMessage()); +// } +// } +// } + + + // @Test + public void batchGet() { + System.out.println("--- test batchGet ---"); + HgStoreSession session = getStoreSession(); + String keyPrefix = "BATCH-GET-UNIT"; + + Map map = HgStoreTestUtil.batchPut(session, keyPrefix); + List keyList = + map.entrySet().stream().map(e -> e.getKey()).collect(Collectors.toList()); + + //printOwner(keyList); + List resList = session.batchGetOwner(HgStoreTestUtil.TABLE_NAME, keyList); + + Assert.assertFalse(isInvalid(resList)); + Assert.assertEquals(resList.size(), keyList.size()); + + // println(list); + HgStoreTestUtil.println("--- batch-get result ---"); + Assert.assertTrue((resList.stream() + .map(e -> map.containsKey(HgStoreTestUtil.toOwnerKey(e.key()))) + .allMatch(Boolean::booleanValue)) + ); + + } + + // @Test + //CAUTION: ONLY FOR LONG! + //注意:目前只可以对long类型value进行Merge操作。 + public void merge() { + System.out.println("--- test merge (1+1=2)---"); + HgStoreSession session = getStoreSession(); + String mergeKey = "merge-key"; + HgOwnerKey key = HgStoreTestUtil.toOwnerKey(mergeKey); + byte[] value = HgStoreTestUtil.toBytes(1L); + + System.out.println("- put " + mergeKey + ":1 -"); + session.put(HgStoreTestUtil.TABLE_NAME, key, value); + System.out.println("- merge " + mergeKey + ":1 -"); + session.merge(HgStoreTestUtil.TABLE_NAME, key, value); + long res = HgStoreTestUtil.toLong(session.get(HgStoreTestUtil.TABLE_NAME, key)); + System.out.printf("after merge " + mergeKey + "=%s%n", res); + Assert.assertEquals(2L, res); + + String putStr = "19"; + session.put(HgStoreTestUtil.TABLE_NAME, key, HgStoreTestUtil.toBytes(putStr)); + byte[] b1 = session.get(HgStoreTestUtil.TABLE_NAME, key); + Assert.assertEquals(putStr, HgStoreTestUtil.toStr(b1)); + } + + // @Test + public void delete() { + System.out.println("--- test delete ---"); + HgStoreSession session = getStoreSession(); + + String delKey = "del-key"; + String delValue = "del-value"; + HgOwnerKey key = HgStoreTestUtil.toOwnerKey(delKey); + byte[] value = HgStoreTestUtil.toBytes(delValue); + + HgStoreTestUtil.println("- put " + delKey + ": " + delValue); + session.put(HgStoreTestUtil.TABLE_NAME, key, value); + HgStoreTestUtil.println("- delete " + delKey); + session.delete(HgStoreTestUtil.TABLE_NAME, key); + value = session.get(HgStoreTestUtil.TABLE_NAME, key); + HgStoreTestUtil.println("- get " + delKey + ": " + HgStoreTestUtil.toStr(value)); + Assert.assertEquals(EMPTY_BYTES, value); + } + + // @Test + public void deleteSingle() { + System.out.println("--- test deleteSingle ---"); + HgStoreSession session = getStoreSession(); + + String delKey = "del-single-key"; + String delValue = "del-single-value"; + HgOwnerKey key = HgStoreTestUtil.toOwnerKey(delKey); + byte[] value = HgStoreTestUtil.toBytes(delValue); + + HgStoreTestUtil.println("- put [" + delKey + "] = " + delValue); + session.put(HgStoreTestUtil.TABLE_NAME, key, value); + + value = session.get(HgStoreTestUtil.TABLE_NAME, key); + HgStoreTestUtil.println( + "- before del, get [" + delKey + "] = " + HgStoreTestUtil.toStr(value)); + Assert.assertEquals(delValue, HgStoreTestUtil.toStr(value)); + + HgStoreTestUtil.println("- delete-single : [" + delKey + "]"); + session.deleteSingle(HgStoreTestUtil.TABLE_NAME, key); + value = session.get(HgStoreTestUtil.TABLE_NAME, key); + HgStoreTestUtil.println( + "- after del, get [" + delKey + "] = " + HgStoreTestUtil.toStr(value)); + Assert.assertEquals("", HgStoreTestUtil.toStr(value)); + + } + + // @Test + public void deleteRange() { + HgStoreTestUtil.println("--- test deleteRange ---"); + HgStoreSession session = getStoreSession(); + + String rangePrefix = "DEL-RANGE-KEY"; + String owner = "batch-delete-owner"; + Map map = + HgStoreTestUtil.batchPut(session, HgStoreTestUtil.TABLE_NAME, rangePrefix, 10, + key -> { + return HgStoreTestUtil.toOwnerKey(owner, key); + }); + + HgOwnerKey startKey = HgStoreTestUtil.toOwnerKey(owner, rangePrefix + "-00"); + HgOwnerKey endKey = HgStoreTestUtil.toOwnerKey(owner, rangePrefix + "-05"); + + Assert.assertTrue(session.deleteRange(HgStoreTestUtil.TABLE_NAME, startKey, endKey)); + + HgStoreTestUtil.println("- after delete range from [" + + HgStoreTestUtil.toStr(startKey.getKey()) + + "] to [" + + HgStoreTestUtil.toStr(endKey.getKey()) + "]"); + + for (int i = 0; i < 10; i++) { + HgOwnerKey key = HgStoreTestUtil.toOwnerKey(owner, rangePrefix + "-" + + HgStoreTestUtil.padLeftZeros( + String.valueOf(i), 2)); + String value = HgStoreTestUtil.toStr(session.get(HgStoreTestUtil.TABLE_NAME, key)); + HgStoreTestUtil.println( + "- get [" + HgStoreTestUtil.toStr(key.getKey()) + "] = " + value); + + // TODO: [start,end)? + if (i < 5) { + Assert.assertEquals("", value); + } else { + //println(value); + Assert.assertNotEquals("", value); + } + } + + } + + // @Test + public void deletePrefix() { + System.out.println("--- test deletePrefix ---"); + HgStoreSession session = getStoreSession(); + + String prefixStr = "DEL-PREFIX-KEY"; + String owner = "batch-delete-owner"; + Map map = + HgStoreTestUtil.batchPut(session, HgStoreTestUtil.TABLE_NAME, prefixStr, 10, + key -> { + return HgStoreTestUtil.toOwnerKey(owner, key); + }); + + //printOwner(map.entrySet().stream().map(e -> e.getKey()).collect(Collectors.toList())); + + HgOwnerKey prefix = HgStoreTestUtil.toOwnerKey(owner, prefixStr); + + Assert.assertEquals(10, HgStoreTestUtil.amountOf(session.scanIterator( + HgStoreTestUtil.TABLE_NAME, prefix))); + session.deletePrefix(HgStoreTestUtil.TABLE_NAME, prefix); + Assert.assertEquals(0, HgStoreTestUtil.amountOf(session.scanIterator( + HgStoreTestUtil.TABLE_NAME, prefix))); + + HgStoreTestUtil.println("- after delete by prefix:[" + prefixStr + "]"); + + for (int i = 0; i < 10; i++) { + HgOwnerKey key = + HgStoreTestUtil.toOwnerKey(owner, prefixStr + HgStoreTestUtil.toSuffix(i, 2)); + String value = HgStoreTestUtil.toStr(session.get(HgStoreTestUtil.TABLE_NAME, key)); + System.out.println("- get [" + HgStoreTestUtil.toStr(key.getKey()) + "] = " + value); + Assert.assertEquals("", value); + } + + } +// // @Test +// public void batchDeleteOwner() { +// System.out.println("--- test batchDelete ---"); +// HgStoreSession session = getStoreSession(); +// String batchPrefix = "DEL-BATCH-KEY"; +// batchPut(session, batchPrefix, 10); +// +// for (int i = 0; i < 10; i++) { +// HgOwnerKey key = toOwnerKey(batchPrefix + toSuffix(i, 2)); +// String value = toStr(session.get(TABLE_NAME, key)); +// println("- get [" + toStr(key.getKey()) + "] = " + value); +// Assert.assertNotEquals("", value); +// } +// +// Set keys = new HashSet<>(); +// for (int i = 0; i < 5; i++) { +// keys.add(toOwnerKey(batchPrefix + toSuffix(i, 2))); +// } +// +// Map> map = new HashMap<>(1); +// map.put(TABLE_NAME, keys); +// session.batchDeleteOwner(map); +// +// for (int i = 0; i < 10; i++) { +// HgOwnerKey key = toOwnerKey(batchPrefix + toSuffix(i, 2)); +// String value = toStr(session.get(TABLE_NAME, key)); +// println("- get [" + toStr(key.getKey()) + "] = " + value); +// // TODO: [,)? +// if (i < 5) { +// Assert.assertEquals("", value); +// } else { +// Assert.assertNotEquals("", value); +// } +// +// } +// } + +// // @Test +// public void batchDeleteRangeOwner() { +// System.out.println("--- test batchDeleteRange ---"); +// HgStoreSession session = getStoreSession(); +// String rangePrefix = "DEL-RANGE-KEY"; +// String owner="batch-delete-owner"; +// +// batchPut(session, TABLE_NAME,rangePrefix, 10,key->{ +// return toOwnerKey(owner,key); +// }); +// batchPut(session, TABLE_NAME2, rangePrefix, 10,key->{ +// return toOwnerKey(owner,key); +// }); +// +// HgOwnerKey startKey1 = toOwnerKey(owner,rangePrefix + "-03"); +// HgOwnerKey endKey1 = toOwnerKey(owner,rangePrefix + "-05"); +// +// HgOwnerKey startKey2 = toOwnerKey(owner,rangePrefix + "-06"); +// HgOwnerKey endKey2 = toOwnerKey(owner,rangePrefix + "-09"); +// +// Map> map = new HashMap<>(); +// map.put(TABLE_NAME, new HgPair<>(startKey1, endKey1)); +// map.put(TABLE_NAME2, new HgPair<>(startKey2, endKey2)); +// +// session.batchDeleteRangeOwner(map); +// +// for (int i = 0; i < 10; i++) { +// HgOwnerKey key = toOwnerKey(owner,rangePrefix + toSuffix(i, 2)); +// String value = toStr(session.get(TABLE_NAME, key)); +// println("- get [" + toStr(key.getKey()) + "] = " + value); +// +// if (i >= 3 && i < 5) { +// Assert.assertEquals("", value); +// } else { +// Assert.assertNotEquals("", value); +// } +// +// } +// +// for (int i = 0; i < 10; i++) { +// HgOwnerKey key = toOwnerKey(owner,rangePrefix + toSuffix(i, 2)); +// String value = toStr(session.get(TABLE_NAME2, key)); +// println("- get [" + toStr(key.getKey()) + "] = " + value); +// if (i >= 6 && i < 9) { +// Assert.assertEquals("", value); +// } else { +// Assert.assertNotEquals("", value); +// } +// +// } +// } + +// // @Test +// public void batchDeletePrefix() { +// System.out.println("--- test batchDeletePrefix ---"); +// HgStoreSession session = getStoreSession(); +// String batchKey1 = "DEL-PREFIX-BATCH-1-KEY"; +// String batchKey2 = "DEL-PREFIX-BATCH-2-KEY"; +// String owner="batch-delete-owner"; +// +// batchPut(session, TABLE_NAME, batchKey1, 10,key->{ +// return toOwnerKey(owner,key); +// }); +// batchPut(session, TABLE_NAME2, batchKey2, 10,key->{ +// return toOwnerKey(owner,key); +// }); +// +// HgOwnerKey prefix1 = toOwnerKey(owner,batchKey1); +// HgOwnerKey prefix2 = toOwnerKey(owner,batchKey2); +// +// Set set = new HashSet<>(); +// +// set.add(prefix1); +// set.add(prefix2); +// +// Map> map = new HashMap<>(); +// map.put(TABLE_NAME, set); +// map.put(TABLE_NAME2, set); +// +// Assert.assertEquals(10, amountOf(session.scanIterator(TABLE_NAME, prefix1))); +// Assert.assertEquals(10, amountOf(session.scanIterator(TABLE_NAME2, prefix2))); +// +// session.batchDeletePrefixOwner(map); +// +// Assert.assertEquals(0, amountOf(session.scanIterator(TABLE_NAME, prefix1))); +// Assert.assertEquals(0, amountOf(session.scanIterator(TABLE_NAME2, prefix2))); +// +// } + + // @Test + public void scanIterator() { + HgStoreTestUtil.println("--- test scanIterator ---"); + String tableName = "UNIT_SCAN"; + String keyName = "SCAN-ITER"; + HgStoreSession session = getStoreSession(); + HgStoreTestUtil.batchPut(session, tableName, keyName, 10000); + int count = 0; + int limit = 0; + int max = 99999; + HgKvIterator iterator = null; + + HgStoreTestUtil.println("-- test 0 element --"); + iterator = session.scanIterator(tableName, HgStoreTestUtil.toAllPartitionKey("__SCAN-001"), + HgStoreTestUtil.toAllPartitionKey("__SCAN-100"), 0); + Assert.assertFalse(iterator.hasNext()); + try { + iterator.next(); + Assert.fail(); + } catch (Throwable t) { + HgStoreTestUtil.println("-- test NoSuchElementException --"); + Assert.assertTrue(t instanceof NoSuchElementException); + } + + HgStoreTestUtil.println("-- test limit 1 to 10 --"); + for (int i = 1; i <= 10; i++) { + HgStoreTestUtil.println("- limit " + i + " -"); + limit = i; + iterator = session.scanIterator(tableName, + HgStoreTestUtil.toAllPartitionKey(keyName + "-0"), + HgStoreTestUtil.toAllPartitionKey(keyName + "-1"), + limit); + count = 0; + while (iterator.hasNext()) { + count++; + HgKvEntry entry = iterator.next(); + HgStoreTestUtil.println(entry); + } + Assert.assertEquals(limit, count); + } + + HgStoreTestUtil.println("-- test limit 1 to 10 not enough --"); + for (int i = 1; i <= 10; i++) { + HgStoreTestUtil.println("- limit " + i + " -"); + limit = i; + iterator = session.scanIterator(tableName, + HgStoreTestUtil.toAllPartitionKey(keyName + "-00001"), + HgStoreTestUtil.toAllPartitionKey(keyName + "-00005"), + limit); + count = 0; + while (iterator.hasNext()) { + count++; + HgKvEntry entry = iterator.next(); + HgStoreTestUtil.println(entry); + } + if (i <= 5) { + Assert.assertEquals(limit, count); + } else { + Assert.assertEquals(5, count); + } + + } + + HgStoreTestUtil.println("-- test limit 0 (no limit) --"); + limit = 0; + iterator = + session.scanIterator(tableName, HgStoreTestUtil.toAllPartitionKey(keyName + "-0"), + HgStoreTestUtil.toAllPartitionKey(keyName + "-1"), limit); + + count = 0; + while (iterator.hasNext()) { + count++; + HgKvEntry entry = iterator.next(); + if (count % 1000 == 0) { + HgStoreTestUtil.println(entry); + } + if (count >= max) break; + } + Assert.assertEquals(10000, count); + + HgStoreTestUtil.println("-- test scan all --"); + iterator = session.scanIterator(tableName); + count = 0; + while (iterator.hasNext()) { + count++; + HgKvEntry entry = iterator.next(); + if (count % 1000 == 0) { + HgStoreTestUtil.println(entry); + } + if (count >= max) break; + } + Assert.assertEquals(10000, count); + + HgStoreTestUtil.println("-- test scan prefix --"); + iterator = + session.scanIterator(tableName, HgStoreTestUtil.toAllPartitionKey(keyName + "-01")); + count = 0; + while (iterator.hasNext()) { + count++; + HgKvEntry entry = iterator.next(); + if (count % 100 == 0) { + HgStoreTestUtil.println(entry); + } + if (count >= max) break; + } + Assert.assertEquals(1000, count); + } + + + // @Test + public void truncate() { + HgStoreTestUtil.println("--- test truncate ---"); + String graph = "graph_truncate"; + String tableName = "UNIT_TRUNCATE_1"; + String keyName = "KEY_TRUNCATE"; + + HgStoreSession session = getStoreSession(graph); + HgStoreTestUtil.batchPut(session, tableName, keyName, 100); + Assert.assertEquals(100, HgStoreTestUtil.amountOf(session.scanIterator(tableName))); + + String tableName2 = "UNIT_TRUNCATE_2"; + HgStoreTestUtil.batchPut(session, tableName2, keyName, 100); + Assert.assertEquals(100, HgStoreTestUtil.amountOf(session.scanIterator(tableName2))); + + + session.truncate(); + Assert.assertEquals(0, HgStoreTestUtil.amountOf(session.scanIterator(tableName))); + Assert.assertEquals(0, HgStoreTestUtil.amountOf(session.scanIterator(tableName2))); + } + + //// @Test + public void scanIteratorHuge() { + + /*************** test no limit, with 10 millions **************/ + String tableName = "UNIT_HUGE"; + String keyName = "SCAN-HUGE"; + int amount = 10_000_000; + HgStoreSession session = getStoreSession(); + + if (HgStoreTestUtil.amountOf( + session.scanIterator(tableName, HgStoreTestUtil.toAllPartitionKey(keyName), 10)) < + 10) { + HgStoreTestUtil.batchPut(session, tableName, keyName, amount); + } + + int count = 0; + HgKvIterator iterator = session.scanIterator(tableName); + + while (iterator.hasNext()) { + count++; + HgKvEntry entry = iterator.next(); + if (count % (amount / 10) == 0) { + HgStoreTestUtil.println(entry); + } + } + + Assert.assertEquals(amount, count); + } +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-client/src/test/java/org/apache/hugegraph/store/HgSessionManagerRaftPDTest.java b/hugegraph-store/hg-store-client/src/test/java/org/apache/hugegraph/store/HgSessionManagerRaftPDTest.java new file mode 100644 index 000000000..51ff11a41 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/test/java/org/apache/hugegraph/store/HgSessionManagerRaftPDTest.java @@ -0,0 +1,981 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store; + +import static org.apache.hugegraph.store.client.util.HgStoreClientConst.EMPTY_BYTES; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.stream.Collectors; + +import org.apache.commons.io.FileUtils; +import org.apache.hugegraph.pd.client.PDClient; +import org.apache.hugegraph.pd.client.PDConfig; +import org.apache.hugegraph.store.client.grpc.KvCloseableIterator; +import org.apache.hugegraph.store.client.util.HgStoreClientConfig; +import org.apache.hugegraph.store.client.util.HgStoreClientConst; +import org.apache.hugegraph.store.client.util.MetricX; +import org.apache.hugegraph.store.grpc.common.ScanOrderType; +import org.apache.hugegraph.store.util.HgStoreTestUtil; +import org.junit.Assert; +import org.junit.Test; + +import lombok.extern.slf4j.Slf4j; + + +/** + * 使用pd,支持raft的单元测试 + */ +@Slf4j +public class HgSessionManagerRaftPDTest { + public final static String GRAPH_NAME_X = "default/hugegraph/x"; + public final static String GRAPH_NAME_Y = "default/hugegraph/y"; + public final static String GRAPH_NAME_Z = "default/hugegraph/z"; + public final static String TABLE_NAME_1 = "table1"; + public final static String TABLE_NAME_2 = "table2"; + public final static String TABLE_NAME_3 = "table3"; + private static final String pdAddress = "localhost:8686"; + public static HgStoreClient storeClient; + private static PDClient pdClient; + + // @BeforeClass + public static void init() { + pdClient = PDClient.create(PDConfig.of(pdAddress).setEnableCache(true)); + storeClient = HgStoreClient.create(pdClient); + } + + private static HgStoreSession getStoreSession() { + return storeClient.openSession(HgStoreTestUtil.GRAPH_NAME); + } + + private static HgStoreSession getStoreSession(String graph) { + return storeClient.openSession(graph); + } + + // @Test + public void put_get() { + System.out.println("--- test put & get ---"); + + HgStoreSession session = getStoreSession(HgStoreTestUtil.GRAPH_NAME); + + HgOwnerKey key = HgStoreTestUtil.toOwnerKey("FOR-PUT-KEY"); + //HgOwnerKey key = toAllNodeKey("FOR-PUT-KEY"); + byte[] value = HgStoreTestUtil.toBytes("FOR-PUT-VALUE"); + + // Assert.assertTrue(session.put(TABLE_NAME, key, value)); + + // Assert.assertEquals(toStr(value), toStr(session.get(TABLE_NAME, key))); + + long stime = System.currentTimeMillis(); + HgStoreTestUtil.batchPut(session, HgStoreTestUtil.TABLE_NAME, "BATCH-PUT-TEST", 30000); + System.out.println("Time is " + (System.currentTimeMillis() - stime)); + } + + public void put_get2() { + System.out.println("--- test put & get ---"); + + HgStoreSession session = getStoreSession("testGraph"); + HgOwnerKey key = HgStoreTestUtil.toOwnerKey("FOR-PUT-KEY"); + byte[] value = HgStoreTestUtil.toBytes("FOR-PUT-VALUE"); + long stime = System.currentTimeMillis(); + HgStoreTestUtil.batchPut(session, "testTable", "BATCH-PUT-TEST", 30000); + System.out.println("Time is " + (System.currentTimeMillis() - stime)); + } + + // @Test + public void scanPartition() { + HgStoreTestUtil.println("--- test scanPartition ---"); + + HgStoreSession session = getStoreSession(); + // System.out.println(amountOf(session.scanIterator(TABLE_NAME))); + + + Iterator iterator = session.scanIterator(HgStoreTestUtil.TABLE_NAME, 0, 65535, + HgKvStore.SCAN_HASHCODE, EMPTY_BYTES); + System.out.println(HgStoreTestUtil.amountOf(iterator)); + } + + // @Test + public void check() { + System.out.println("--- test check ---"); + + HgStoreSession session = getStoreSession(); + HgKvIterator iterator = session.scanIterator(HgStoreTestUtil.TABLE_NAME); + int count = 0; + while (iterator.hasNext()) { + iterator.next(); + count++; + } + System.out.println(count); + } + + // @Test + public void put_get_unique() { + System.out.println("--- test put & get ---"); + + HgStoreSession session = getStoreSession(); + + // add timestamp into key to avoid key duplication + Date date = new Date(); + SimpleDateFormat formatter = new SimpleDateFormat("HHmmss"); + String timestamp = formatter.format(date); + + HgOwnerKey key = HgStoreTestUtil.toOwnerKey("FOR-PUT-KEY-" + timestamp); + //HgOwnerKey key = toAllNodeKey("FOR-PUT-KEY"); + byte[] value = HgStoreTestUtil.toBytes("FOR-PUT-VALUE"); + + Assert.assertTrue(session.put(HgStoreTestUtil.TABLE_NAME, key, value)); + + Assert.assertEquals(HgStoreTestUtil.toStr(value), HgStoreTestUtil.toStr(session.get( + HgStoreTestUtil.TABLE_NAME, key))); + } + + + @Test + public void testBatchPutExt() throws IOException { + System.out.println("--- test batchPut ---"); + HgStoreSession session = getStoreSession(); + String keyPrefix = "BATCH-GET-UNIT"; + + Map map = HgStoreTestUtil.batchPut(session, keyPrefix, 1000); + + File outputFile = new File("tmp/batch_put_list"); + if (outputFile.exists()) { + FileUtils.forceDelete(outputFile); + } + FileUtils.forceMkdir(new File("tmp/")); + + ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(outputFile)); + oo.writeObject(map); + oo.close(); + System.out.printf("%d entries have be put into graph %s\n", map.size(), + HgStoreTestUtil.GRAPH_NAME); + + + int count = 0; + HgKvIterator iterator = null; + iterator = session.scanIterator(HgStoreTestUtil.TABLE_NAME); + while (iterator.hasNext()) { + iterator.next(); + count++; + } + Assert.assertEquals(1000, count); + + } + + + // @Test + public void testBatchGetExt() throws IOException, ClassNotFoundException { + File outputFile = new File("tmp/batch_put_list"); + ObjectInputStream ois = new ObjectInputStream(new FileInputStream(outputFile)); + Map map = (Map) ois.readObject(); + ois.close(); + System.out.printf("%d entries get from %s\n", map.size(), outputFile.getPath()); + + HgStoreSession session = getStoreSession(); + List keyList = + map.entrySet().stream().map(e -> e.getKey()).collect(Collectors.toList()); + List resList = session.batchGetOwner(HgStoreTestUtil.TABLE_NAME, keyList); + + Assert.assertTrue((resList.stream() + .map(e -> map.containsKey(HgStoreTestUtil.toOwnerKey(e.key()))) + .allMatch(Boolean::booleanValue)) + ); + } + + @Test + public void testBatchPutUniqueExt() throws IOException { + System.out.println("--- test batchPut ---"); + HgStoreSession session = getStoreSession(); + + // add timestamp into key to avoid key duplication + Date date = new Date(); + SimpleDateFormat formatter = new SimpleDateFormat("HHmmss"); + String timestamp = formatter.format(date); + + String keyPrefix = "BATCH-GET-UNIT-" + timestamp; + + Map map = HgStoreTestUtil.batchPut(session, keyPrefix, 300000); + + File outputFile = new File("tmp/batch_put_list"); + if (outputFile.exists()) { + FileUtils.forceDelete(outputFile); + } + FileUtils.forceMkdir(new File("tmp/")); + + ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(outputFile)); + oo.writeObject(map); + oo.close(); + System.out.printf("%d entries have be put into graph %s\n", map.size(), + HgStoreTestUtil.GRAPH_NAME); + } + + // @Test + public void testBatchPutMultiGraph() throws IOException { + System.out.println("--- test testBatchPutMultiGraph ---"); + Date date = new Date(); + SimpleDateFormat formatter = new SimpleDateFormat("HHmmss"); + String timestamp = formatter.format(date); + HgStoreSession session1 = getStoreSession(GRAPH_NAME_X); + HgStoreSession session2 = getStoreSession(GRAPH_NAME_Y); + HgStoreSession session3 = getStoreSession(GRAPH_NAME_Z); + String keyPrefix1 = "BATCH-PUT-UNIT-X-" + timestamp; + String keyPrefix2 = "BATCH-PUT-UNIT-Y-" + timestamp; + String keyPrefix3 = "BATCH-PUT-UNIT-Z-" + timestamp; + HgStoreTestUtil.batchPut(session1, TABLE_NAME_1, keyPrefix1, 1000); + HgStoreTestUtil.batchPut(session1, TABLE_NAME_2, keyPrefix1, 1000); + HgStoreTestUtil.batchPut(session1, TABLE_NAME_3, keyPrefix1, 1000); + HgStoreTestUtil.batchPut(session2, TABLE_NAME_1, keyPrefix2, 1000); + HgStoreTestUtil.batchPut(session2, TABLE_NAME_2, keyPrefix2, 1000); + HgStoreTestUtil.batchPut(session2, TABLE_NAME_3, keyPrefix2, 1000); + HgStoreTestUtil.batchPut(session3, TABLE_NAME_1, keyPrefix3, 1000); + HgStoreTestUtil.batchPut(session3, TABLE_NAME_2, keyPrefix3, 1000); + HgStoreTestUtil.batchPut(session3, TABLE_NAME_3, keyPrefix3, 1000); + } + + // @Test + //CAUTION: ONLY FOR LONG! + //注意:目前只可以对long类型value进行Merge操作。 + public void merge() { + System.out.println("--- test merge (1+1=2)---"); + HgStoreSession session = getStoreSession(); + String mergeKey = "merge-key"; + HgOwnerKey key = HgStoreTestUtil.toOwnerKey(mergeKey); + byte[] value = HgStoreTestUtil.toBytes(1L); + + System.out.println("- put " + mergeKey + ":1 -"); + session.put(HgStoreTestUtil.TABLE_NAME, key, value); + System.out.println("- merge " + mergeKey + ":1 -"); + session.merge(HgStoreTestUtil.TABLE_NAME, key, value); + long res = HgStoreTestUtil.toLong(session.get(HgStoreTestUtil.TABLE_NAME, key)); + System.out.printf("after merge " + mergeKey + "=%s%n", res); + Assert.assertEquals(2L, res); + + String putStr = "19"; + session.put(HgStoreTestUtil.TABLE_NAME, key, HgStoreTestUtil.toBytes(putStr)); + byte[] b1 = session.get(HgStoreTestUtil.TABLE_NAME, key); + Assert.assertEquals(putStr, HgStoreTestUtil.toStr(b1)); + } + + // @Test + public void delete() { + System.out.println("--- test delete ---"); + HgStoreSession session = getStoreSession(); + + String delKey = "del-key"; + String delValue = "del-value"; + HgOwnerKey key = HgStoreTestUtil.toOwnerKey(delKey); + byte[] value = HgStoreTestUtil.toBytes(delValue); + + HgStoreTestUtil.println("- put " + delKey + ": " + delValue); + session.put(HgStoreTestUtil.TABLE_NAME, key, value); + HgStoreTestUtil.println("- delete " + delKey); + session.delete(HgStoreTestUtil.TABLE_NAME, key); + value = session.get(HgStoreTestUtil.TABLE_NAME, key); + HgStoreTestUtil.println("- get " + delKey + ": " + HgStoreTestUtil.toStr(value)); + Assert.assertEquals(EMPTY_BYTES, value); + } + + // @Test + public void deleteSingle() { + System.out.println("--- test deleteSingle ---"); + HgStoreSession session = getStoreSession(); + + String delKey = "del-single-key"; + String delValue = "del-single-value"; + HgOwnerKey key = HgStoreTestUtil.toOwnerKey(delKey); + byte[] value = HgStoreTestUtil.toBytes(delValue); + + HgStoreTestUtil.println("- put [" + delKey + "] = " + delValue); + session.put(HgStoreTestUtil.TABLE_NAME, key, value); + + value = session.get(HgStoreTestUtil.TABLE_NAME, key); + HgStoreTestUtil.println( + "- before del, get [" + delKey + "] = " + HgStoreTestUtil.toStr(value)); + Assert.assertEquals(delValue, HgStoreTestUtil.toStr(value)); + + HgStoreTestUtil.println("- delete-single : [" + delKey + "]"); + session.deleteSingle(HgStoreTestUtil.TABLE_NAME, key); + value = session.get(HgStoreTestUtil.TABLE_NAME, key); + HgStoreTestUtil.println( + "- after del, get [" + delKey + "] = " + HgStoreTestUtil.toStr(value)); + Assert.assertEquals("", HgStoreTestUtil.toStr(value)); + + } + + // @Test + public void deleteRange() { + HgStoreTestUtil.println("--- test deleteRange ---"); + HgStoreSession session = getStoreSession(); + + String rangePrefix = "DEL-RANGE-KEY"; + String owner = "batch-delete-owner"; + Map map = + HgStoreTestUtil.batchPut(session, HgStoreTestUtil.TABLE_NAME, rangePrefix, 10, + key -> { + return HgStoreTestUtil.toOwnerKey(owner, key); + }); + + HgOwnerKey startKey = HgStoreTestUtil.toOwnerKey(owner, rangePrefix + "-00"); + HgOwnerKey endKey = HgStoreTestUtil.toOwnerKey(owner, rangePrefix + "-05"); + + Assert.assertTrue(session.deleteRange(HgStoreTestUtil.TABLE_NAME, startKey, endKey)); + + HgStoreTestUtil.println("- after delete range from [" + + HgStoreTestUtil.toStr(startKey.getKey()) + + "] to [" + + HgStoreTestUtil.toStr(endKey.getKey()) + "]"); + + for (int i = 0; i < 10; i++) { + HgOwnerKey key = HgStoreTestUtil.toOwnerKey(owner, rangePrefix + "-" + + HgStoreTestUtil.padLeftZeros( + String.valueOf(i), 2)); + String value = HgStoreTestUtil.toStr(session.get(HgStoreTestUtil.TABLE_NAME, key)); + HgStoreTestUtil.println( + "- get [" + HgStoreTestUtil.toStr(key.getKey()) + "] = " + value); + + // TODO: [start,end)? + if (i < 5) { + Assert.assertEquals("", value); + } else { + //println(value); + Assert.assertNotEquals("", value); + } + } + + } + + // @Test + public void deletePrefix() { + System.out.println("--- test deletePrefix ---"); + HgStoreSession session = getStoreSession(); + + String prefixStr = "DEL-PREFIX-KEY"; + String owner = "batch-delete-owner"; + Map map = + HgStoreTestUtil.batchPut(session, HgStoreTestUtil.TABLE_NAME, prefixStr, 10, + key -> { + return HgStoreTestUtil.toOwnerKey(owner, key); + }); + + //printOwner(map.entrySet().stream().map(e -> e.getKey()).collect(Collectors.toList())); + + HgOwnerKey prefix = HgStoreTestUtil.toOwnerKey(owner, prefixStr); + + Assert.assertEquals(10, HgStoreTestUtil.amountOf(session.scanIterator( + HgStoreTestUtil.TABLE_NAME, prefix))); + session.deletePrefix(HgStoreTestUtil.TABLE_NAME, prefix); + Assert.assertEquals(0, HgStoreTestUtil.amountOf(session.scanIterator( + HgStoreTestUtil.TABLE_NAME, prefix))); + + HgStoreTestUtil.println("- after delete by prefix:[" + prefixStr + "]"); + + for (int i = 0; i < 10; i++) { + HgOwnerKey key = + HgStoreTestUtil.toOwnerKey(owner, prefixStr + HgStoreTestUtil.toSuffix(i, 2)); + String value = HgStoreTestUtil.toStr(session.get(HgStoreTestUtil.TABLE_NAME, key)); + System.out.println("- get [" + HgStoreTestUtil.toStr(key.getKey()) + "] = " + value); + Assert.assertEquals("", value); + } + + } +// // @Test +// public void batchDeleteOwner() { +// System.out.println("--- test batchDelete ---"); +// HgStoreSession session = getStoreSession(); +// String batchPrefix = "DEL-BATCH-KEY"; +// batchPut(session, batchPrefix, 10); +// +// for (int i = 0; i < 10; i++) { +// HgOwnerKey key = toOwnerKey(batchPrefix + toSuffix(i, 2)); +// String value = toStr(session.get(TABLE_NAME, key)); +// println("- get [" + toStr(key.getKey()) + "] = " + value); +// Assert.assertNotEquals("", value); +// } +// +// Set keys = new HashSet<>(); +// for (int i = 0; i < 5; i++) { +// keys.add(toOwnerKey(batchPrefix + toSuffix(i, 2))); +// } +// +// Map> map = new HashMap<>(1); +// map.put(TABLE_NAME, keys); +// session.batchDeleteOwner(map); +// +// for (int i = 0; i < 10; i++) { +// HgOwnerKey key = toOwnerKey(batchPrefix + toSuffix(i, 2)); +// String value = toStr(session.get(TABLE_NAME, key)); +// println("- get [" + toStr(key.getKey()) + "] = " + value); +// // TODO: [,)? +// if (i < 5) { +// Assert.assertEquals("", value); +// } else { +// Assert.assertNotEquals("", value); +// } +// +// } +// } + +// // @Test +// public void batchDeleteRangeOwner() { +// System.out.println("--- test batchDeleteRange ---"); +// HgStoreSession session = getStoreSession(); +// String rangePrefix = "DEL-RANGE-KEY"; +// String owner="batch-delete-owner"; +// +// batchPut(session, TABLE_NAME,rangePrefix, 10,key->{ +// return toOwnerKey(owner,key); +// }); +// batchPut(session, TABLE_NAME2, rangePrefix, 10,key->{ +// return toOwnerKey(owner,key); +// }); +// +// HgOwnerKey startKey1 = toOwnerKey(owner,rangePrefix + "-03"); +// HgOwnerKey endKey1 = toOwnerKey(owner,rangePrefix + "-05"); +// +// HgOwnerKey startKey2 = toOwnerKey(owner,rangePrefix + "-06"); +// HgOwnerKey endKey2 = toOwnerKey(owner,rangePrefix + "-09"); +// +// Map> map = new HashMap<>(); +// map.put(TABLE_NAME, new HgPair<>(startKey1, endKey1)); +// map.put(TABLE_NAME2, new HgPair<>(startKey2, endKey2)); +// +// session.batchDeleteRangeOwner(map); +// +// for (int i = 0; i < 10; i++) { +// HgOwnerKey key = toOwnerKey(owner,rangePrefix + toSuffix(i, 2)); +// String value = toStr(session.get(TABLE_NAME, key)); +// println("- get [" + toStr(key.getKey()) + "] = " + value); +// +// if (i >= 3 && i < 5) { +// Assert.assertEquals("", value); +// } else { +// Assert.assertNotEquals("", value); +// } +// +// } +// +// for (int i = 0; i < 10; i++) { +// HgOwnerKey key = toOwnerKey(owner,rangePrefix + toSuffix(i, 2)); +// String value = toStr(session.get(TABLE_NAME2, key)); +// println("- get [" + toStr(key.getKey()) + "] = " + value); +// if (i >= 6 && i < 9) { +// Assert.assertEquals("", value); +// } else { +// Assert.assertNotEquals("", value); +// } +// +// } +// } + +// // @Test +// public void batchDeletePrefix() { +// System.out.println("--- test batchDeletePrefix ---"); +// HgStoreSession session = getStoreSession(); +// String batchKey1 = "DEL-PREFIX-BATCH-1-KEY"; +// String batchKey2 = "DEL-PREFIX-BATCH-2-KEY"; +// String owner="batch-delete-owner"; +// +// batchPut(session, TABLE_NAME, batchKey1, 10,key->{ +// return toOwnerKey(owner,key); +// }); +// batchPut(session, TABLE_NAME2, batchKey2, 10,key->{ +// return toOwnerKey(owner,key); +// }); +// +// HgOwnerKey prefix1 = toOwnerKey(owner,batchKey1); +// HgOwnerKey prefix2 = toOwnerKey(owner,batchKey2); +// +// Set set = new HashSet<>(); +// +// set.add(prefix1); +// set.add(prefix2); +// +// Map> map = new HashMap<>(); +// map.put(TABLE_NAME, set); +// map.put(TABLE_NAME2, set); +// +// Assert.assertEquals(10, amountOf(session.scanIterator(TABLE_NAME, prefix1))); +// Assert.assertEquals(10, amountOf(session.scanIterator(TABLE_NAME2, prefix2))); +// +// session.batchDeletePrefixOwner(map); +// +// Assert.assertEquals(0, amountOf(session.scanIterator(TABLE_NAME, prefix1))); +// Assert.assertEquals(0, amountOf(session.scanIterator(TABLE_NAME2, prefix2))); +// +// } + + // @Test + public void scanIterator() { + HgStoreTestUtil.println("--- test scanIterator ---"); + String tableName = "UNIT_SCAN"; + String keyName = "SCAN-ITER"; + HgStoreSession session = getStoreSession(); + HgStoreTestUtil.batchPut(session, tableName, keyName, 10000); + int count = 0; + int limit = 0; + int max = 99999; + HgKvIterator iterator = null; + + HgStoreTestUtil.println("-- test 0 element --"); + iterator = session.scanIterator(tableName, HgStoreTestUtil.toAllPartitionKey("__SCAN-001"), + HgStoreTestUtil.toAllPartitionKey("__SCAN-100"), 0); + Assert.assertFalse(iterator.hasNext()); + try { + iterator.next(); + Assert.fail(); + } catch (Throwable t) { + HgStoreTestUtil.println("-- test NoSuchElementException --"); + Assert.assertTrue(t instanceof NoSuchElementException); + } + + HgStoreTestUtil.println("-- test limit 1 to 10 --"); + for (int i = 1; i <= 10; i++) { + HgStoreTestUtil.println("- limit " + i + " -"); + limit = i; + iterator = session.scanIterator(tableName, + HgStoreTestUtil.toAllPartitionKey(keyName + "-0"), + HgStoreTestUtil.toAllPartitionKey(keyName + "-1"), + limit); + count = 0; + while (iterator.hasNext()) { + count++; + HgKvEntry entry = iterator.next(); + HgStoreTestUtil.println(entry); + } + Assert.assertEquals(limit, count); + } + + HgStoreTestUtil.println("-- test limit 1 to 10 not enough --"); + for (int i = 1; i <= 10; i++) { + HgStoreTestUtil.println("- limit " + i + " -"); + limit = i; + iterator = session.scanIterator(tableName, + HgStoreTestUtil.toAllPartitionKey(keyName + "-00001"), + HgStoreTestUtil.toAllPartitionKey(keyName + "-00005"), + limit); + count = 0; + while (iterator.hasNext()) { + count++; + HgKvEntry entry = iterator.next(); + HgStoreTestUtil.println(entry); + } + if (i <= 5) { + Assert.assertEquals(limit, count); + } else { + Assert.assertEquals(5, count); + } + + } + + HgStoreTestUtil.println("-- test limit 0 (no limit) --"); + limit = 0; + iterator = + session.scanIterator(tableName, HgStoreTestUtil.toAllPartitionKey(keyName + "-0"), + HgStoreTestUtil.toAllPartitionKey(keyName + "-1"), limit); + + count = 0; + while (iterator.hasNext()) { + count++; + HgKvEntry entry = iterator.next(); + if (count % 1000 == 0) { + HgStoreTestUtil.println(entry); + } + if (count >= max) break; + } + Assert.assertEquals(10000, count); + + HgStoreTestUtil.println("-- test scan all --"); + iterator = session.scanIterator(tableName); + count = 0; + while (iterator.hasNext()) { + count++; + HgKvEntry entry = iterator.next(); + if (count % 1000 == 0) { + HgStoreTestUtil.println(entry); + } + if (count >= max) break; + } + Assert.assertEquals(10000, count); + + HgStoreTestUtil.println("-- test scan prefix --"); + iterator = + session.scanIterator(tableName, HgStoreTestUtil.toAllPartitionKey(keyName + "-01")); + count = 0; + while (iterator.hasNext()) { + count++; + HgKvEntry entry = iterator.next(); + if (count % 100 == 0) { + HgStoreTestUtil.println(entry); + } + if (count >= max) break; + } + Assert.assertEquals(1000, count); + } + + // @Test + public void paging() { + HgStoreTestUtil.println("--- test scanIterator_range ---"); + String graph = "UNIT/paging"; + String tableName = "UNIT_SCAN_PAGING"; + String keyName = "SCAN-PAGING"; + int keyAmt = 100; + HgStoreSession session = getStoreSession(graph); + HgStoreTestUtil.batchPut(session, tableName, keyName, keyAmt); + int count = 0; + int limit = 0; + int max = 99999; + HgKvIterator iterator = null; + + count = 0; + + iterator = session.scanIterator(tableName + , HgStoreTestUtil.toAllPartitionKey(keyName + "-000") + , HgStoreClientConst.EMPTY_OWNER_KEY + , 0, HgKvStore.SCAN_ANY, EMPTY_BYTES + ); + //HgStoreTestUtil.println(iterator); + // Assert.assertEquals(100, HgStoreTestUtil.amountOf(iterator)); + List positionList = new LinkedList<>(); + while (iterator.hasNext()) { + HgStoreTestUtil.print((count++) + " "); + HgKvEntry entry = iterator.next(); + HgStoreTestUtil.print(entry); + HgStoreTestUtil.print(" " + Arrays.toString(iterator.position()) + "\n"); + positionList.add(iterator.position()); + if (count >= max) break; + } + + +/* iterator = session.scanIterator(tableName + , toAllPartitionKey(keyName + "-000") + , HgStoreClientConst.EMPTY_OWNER_KEY + , 0, HgKvStore.SCAN_ANY, EMPTY_BYTES + ); + + byte[] position=positionList.get(50); + println("seek: "+Arrays.toString(position)); + iterator.seek(position); + //HgStoreTestUtil.println(iterator); + Assert.assertEquals(50, HgStoreTestUtil.amountOf(iterator));*/ + + iterator = session.scanIterator(tableName, 100); + + byte[] position = positionList.get(50); + HgStoreTestUtil.println("seek: " + Arrays.toString(position)); + iterator.seek(position); + HgStoreTestUtil.println(iterator); + //Assert.assertEquals(50, HgStoreTestUtil.amountOf(iterator)); + + } + + // @Test + public void truncate() { + HgStoreTestUtil.println("--- test truncate ---"); + String graph = "graph_truncate"; + String tableName = "UNIT_TRUNCATE_1"; + String keyName = "KEY_TRUNCATE"; + + HgStoreSession session = getStoreSession(graph); + HgStoreTestUtil.batchPut(session, tableName, keyName, 100); + Assert.assertEquals(100, HgStoreTestUtil.amountOf(session.scanIterator(tableName))); + + String tableName2 = "UNIT_TRUNCATE_2"; + HgStoreTestUtil.batchPut(session, tableName2, keyName, 100); + Assert.assertEquals(100, HgStoreTestUtil.amountOf(session.scanIterator(tableName2))); + + + session.truncate(); + Assert.assertEquals(0, HgStoreTestUtil.amountOf(session.scanIterator(tableName))); + Assert.assertEquals(0, HgStoreTestUtil.amountOf(session.scanIterator(tableName2))); + } + + //// @Test + public void scanIteratorHuge() { + + /*************** test no limit, with 10 millions **************/ + String tableName = "UNIT_HUGE"; + String keyName = "SCAN-HUGE"; + int amount = 10_000_000; + HgStoreSession session = getStoreSession(); + + if (HgStoreTestUtil.amountOf( + session.scanIterator(tableName, HgStoreTestUtil.toAllPartitionKey(keyName), 10)) < + 10) { + HgStoreTestUtil.batchPut(session, tableName, keyName, amount); + } + + int count = 0; + HgKvIterator iterator = session.scanIterator(tableName); + + while (iterator.hasNext()) { + count++; + HgKvEntry entry = iterator.next(); + if (count % (amount / 10) == 0) { + HgStoreTestUtil.println(entry); + } + } + + Assert.assertEquals(amount, count); + } + + + // @Test + public void scanTable() { + HgStoreSession session = getStoreSession("DEFAULT/hg1/g"); + HgStoreTestUtil.println(session.scanIterator("g+v", 10)); + + + } + + // @Test + public void testDelGraph() { + HgStoreSession session = getStoreSession(); + session.deleteGraph(HgStoreTestUtil.GRAPH_NAME); + } + + // @Test + public void benchmark_scanBatch() { + HgStoreTestUtil.println("--- Benchmark scanBatch ---"); + String tableName = "Benchmark_SCAN_BATCH"; + String keyName = "SCAN-BATCH"; + int keyAmt = 30001; + + HgStoreSession session = getStoreSession(); + + /*if (amountOf(session.scanIterator(tableName, toAllPartitionKey(keyName), 1)) < 1) { + batchPut(session, tableName, keyName, keyAmt); + }*/ + HgStoreTestUtil.println("-- Starting scan --"); + MetricX metrics = MetricX.ofStart(); + //HgStoreTestUtil.println(session.scanIterator(tableName)); + List> iterators = session.scanBatch(HgScanQuery.tableOf(tableName)); + Assert.assertEquals(keyAmt, HgStoreTestUtil.amountIn(iterators)); + metrics.end(); + log.info("*************************************************"); + log.info("*********** Batch Scan Completed **************"); + log.info("Total : {} (sec)", metrics.past() / 1000); + log.info(" Sum : {} (sec)", MetricX.getIteratorWait() / 1000); + log.info(" Amt : {} (scans).", MetricX.getIteratorCount()); + log.info(" Avg : {} (ms)", MetricX.getIteratorWaitAvg()); + log.info(" Max : {} (ms)", MetricX.getIteratorWaitMax()); + log.info(" Fail : {} (times)", metrics.getFailureCount()); + log.info(" Page : {} (KVs)", HgStoreClientConfig.of().getNetKvScannerPageSize()); + log.info("Iters : {}", iterators.size()); + log.info("*************************************************"); + + } + + @Test + public void benchmark_scanBatch2() throws IOException { + HgStoreTestUtil.println("--- Benchmark scanBatch2 ---"); + //String tableName = TABLE_NAME; + HgStoreSession session = getStoreSession(); + String keyName = "SCAN-BATCH"; + int keyAmt = 3000; + + Map data = + HgStoreTestUtil.batchPut(session, HgStoreTestUtil.TABLE_NAME, keyName, keyAmt, + //(key) -> toOwnerKey(key.substring(0, keyName.length() + // + 2), key) + (key) -> HgStoreTestUtil.toOwnerKey(0, key) + ); + + HgStoreTestUtil.batchPut(session, HgStoreTestUtil.TABLE_NAME2, keyName, keyAmt, + //(key) -> toOwnerKey(key.substring(0, keyName.length() + 2), key) + (key) -> HgStoreTestUtil.toOwnerKey(0, key) + ); + + MetricX metrics = MetricX.ofStart(); + long t = System.currentTimeMillis(); + int count = 0; + String queryTable = null; + KvCloseableIterator> iterators = null; + List queryKeys = null; + List keys = new ArrayList<>(); + data.forEach((k, v) -> keys.add(k)); + + List prefixKeys = new ArrayList<>(); + prefixKeys.add(HgStoreTestUtil.toOwnerKey(0, keyName + "-0")); + prefixKeys.add(HgStoreTestUtil.toOwnerKey(0, keyName + "-1")); + prefixKeys.add(HgStoreTestUtil.toOwnerKey(0, keyName + "-2")); + prefixKeys.add(HgStoreTestUtil.toOwnerKey(0, keyName + "-3")); + + HgStoreTestUtil.println("-- test every key, one table --"); + count = 0; + queryTable = HgStoreTestUtil.TABLE_NAME; + queryKeys = keys; + iterators = session.scanBatch2( + HgScanQuery.prefixIteratorOf(queryTable, queryKeys.iterator(), + ScanOrderType.ORDER_NONE) + .builder() + .setScanType(0x40) + .build()); + while (iterators.hasNext()) { + HgKvIterator iterator = iterators.next(); + count += HgStoreTestUtil.amountOf(iterator); + } + iterators.close(); + Assert.assertEquals(keyAmt, count); + log.info(" size is {}", count); + + HgStoreTestUtil.println("-- test prefix key, one table --"); + count = 0; + queryTable = HgStoreTestUtil.TABLE_NAME; + queryKeys = prefixKeys; + iterators = session.scanBatch2( + HgScanQuery.prefixIteratorOf(queryTable, queryKeys.iterator(), + ScanOrderType.ORDER_STRICT) + .builder() + .setScanType(0x40) + .build()); + while (iterators.hasNext()) { + HgKvIterator iterator = iterators.next(); + count += HgStoreTestUtil.amountOf(iterator); + } + iterators.close(); + Assert.assertEquals(keyAmt, count); + log.info(" size is {}", count); + + HgStoreTestUtil.println("-- test prefix key, two table --"); + count = 0; + queryTable = HgStoreTestUtil.TABLE_NAME + "," + HgStoreTestUtil.TABLE_NAME2; + queryKeys = prefixKeys; + iterators = session.scanBatch2( + HgScanQuery.prefixIteratorOf(queryTable, queryKeys.iterator(), + ScanOrderType.ORDER_NONE) + .builder() + .setScanType(0x40) + .build()); + while (iterators.hasNext()) { + HgKvIterator iterator = iterators.next(); + count += HgStoreTestUtil.amountOf(iterator); + } + iterators.close(); + Assert.assertEquals(keyAmt * 2, count); + log.info(" size is {}", count); + + HgStoreTestUtil.println("-- test prefix key, two table, perKeyMax --"); + count = 0; + queryTable = HgStoreTestUtil.TABLE_NAME + "," + HgStoreTestUtil.TABLE_NAME2; + queryKeys = prefixKeys; + queryKeys.remove(queryKeys.size() - 1);//remove the last one. + iterators = session.scanBatch2( + HgScanQuery.prefixIteratorOf(queryTable, queryKeys.iterator(), + ScanOrderType.ORDER_NONE) + .builder() + .setScanType(0x40) + .setPerKeyMax(10) + .build()); + while (iterators.hasNext()) { + HgKvIterator iterator = iterators.next(); + count += HgStoreTestUtil.amountOf(iterator); + } + iterators.close(); + Assert.assertEquals(queryKeys.size() * 10, count); + log.info(" size is {}", count); + + keys.clear(); + + log.info("time is {}", System.currentTimeMillis() - t); + metrics.end(); + log.info("*************************************************"); + log.info("*********** Batch Scan Completed **************"); + log.info("Total : {} (sec)", metrics.past() / 1000); + log.info(" Sum : {} (sec)", MetricX.getIteratorWait() / 1000); + log.info(" Amt : {} (scans).", MetricX.getIteratorCount()); + log.info(" Avg : {} (ms)", MetricX.getIteratorWaitAvg()); + log.info(" Max : {} (ms)", MetricX.getIteratorWaitMax()); + log.info(" Fail : {} (times)", metrics.getFailureCount()); + log.info(" Page : {} (KVs)", HgStoreClientConfig.of().getNetKvScannerPageSize()); + log.info(" size is {}", count); + log.info("*************************************************"); + + + } + + // @Test + public void benchmark_scanBatch_SkipDegree() throws IOException { + HgStoreTestUtil.println("--- Benchmark scanBatch2 1Owner---"); + String tableName = HgStoreTestUtil.TABLE_NAME; + HgStoreSession session = getStoreSession(); + String keyName = "SCAN-BATCH"; + int keyAmt = 300000; + byte[] owner = "Owner".getBytes(); + Map data = + HgStoreTestUtil.batchPut(session, tableName, keyName, keyAmt, key -> { + return HgStoreTestUtil.toOwnerKey(owner, key); + }); + HgStoreTestUtil.println("-- Starting scan --"); + MetricX metrics = MetricX.ofStart(); + //HgStoreTestUtil.println(session.scanIterator(tableName)); + long t = System.currentTimeMillis(); + int count = 0; + { + List keys = new ArrayList<>(); + keys.add(HgStoreTestUtil.toOwnerKey(owner, keyName)); + + KvCloseableIterator> iterators = + session.scanBatch2(HgScanQuery.prefixIteratorOf(tableName, keys.iterator(), + ScanOrderType.ORDER_NONE) + .builder().setScanType(0x40).setSkipDegree(1) + .build()); + //KvCloseableIterator> iterators = session.scanBatch2(HgScanQuery + // .tableOf(tableName)); + while (iterators.hasNext()) { + HgKvIterator iterator = iterators.next(); +// while (iterator.hasNext()){ +// System.out.println(new String(iterator.next().key())); +// } + count += HgStoreTestUtil.amountOf(iterator); + // log.info("{} - {}", new String(iterator.key()), iterator.value()); + } + keys.clear(); + iterators.close(); + iterators = null; + } + log.info("time is {}", System.currentTimeMillis() - t); + metrics.end(); + log.info("*************************************************"); + log.info("*********** Batch Scan Completed **************"); + log.info("Total : {} (sec)", metrics.past() / 1000); + log.info(" Sum : {} (sec)", MetricX.getIteratorWait() / 1000); + log.info(" Amt : {} (scans).", MetricX.getIteratorCount()); + log.info(" Avg : {} (ms)", MetricX.getIteratorWaitAvg()); + log.info(" Max : {} (ms)", MetricX.getIteratorWaitMax()); + log.info(" Fail : {} (times)", metrics.getFailureCount()); + log.info(" Page : {} (KVs)", HgStoreClientConfig.of().getNetKvScannerPageSize()); + log.info(" size is {}", count); + log.info("*************************************************"); + + + } +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-client/src/test/java/org/apache/hugegraph/store/HgSessionManagerTest.java b/hugegraph-store/hg-store-client/src/test/java/org/apache/hugegraph/store/HgSessionManagerTest.java new file mode 100644 index 000000000..1ba678ea7 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/test/java/org/apache/hugegraph/store/HgSessionManagerTest.java @@ -0,0 +1,1125 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store; + +import static org.apache.hugegraph.store.client.util.HgStoreClientConst.ALL_PARTITION_OWNER; +import static org.apache.hugegraph.store.client.util.HgStoreClientConst.EMPTY_BYTES; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.stream.Collectors; + +import org.apache.hugegraph.pd.common.PartitionUtils; +import org.apache.hugegraph.store.client.HgStoreNodeManager; +import org.apache.hugegraph.store.client.util.ExecutorPool; +import org.apache.hugegraph.store.client.util.HgStoreClientConfig; +import org.apache.hugegraph.store.client.util.HgStoreClientConst; +import org.apache.hugegraph.store.client.util.MetricX; +import org.apache.hugegraph.store.util.HgStoreTestUtil; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class HgSessionManagerTest { + private static final Map leaderMap = new ConcurrentHashMap<>(); + private static final Map storeMap = new ConcurrentHashMap<>(); + + private static final ExecutorService pool = Executors.newFixedThreadPool(100, + ExecutorPool.newThreadFactory( + "unit-test")); + + private static final int partitionCount = 10; + // 需要与store的application.yml的fake-pd.partition-count保持一致 + + //private static String[] storeAddress = {"127.0.0.1:8500"}; + private static final String[] storeAddress = + {"127.0.0.1:8501", "127.0.0.1:8502", "127.0.0.1:8503"}; + + private static final int PARTITION_LENGTH = getPartitionLength(); + + private static int getPartitionLength() { + return PartitionUtils.MAX_VALUE / + (partitionCount == 0 ? storeAddress.length : partitionCount) + 1; + } + + @BeforeClass + public static void init() { + for (String address : storeAddress) { + storeMap.put((long) address.hashCode(), address); + } + for (int i = 0; i < partitionCount; i++) { + leaderMap.put(i, storeMap.keySet().iterator().next()); + } + + HgStoreNodeManager nodeManager = HgStoreNodeManager.getInstance(); + nodeManager.setNodePartitioner((builder, graphName, startKey, endKey) -> { + int startCode = PartitionUtils.calcHashcode(startKey); + int endCode = PartitionUtils.calcHashcode(endKey); + if (ALL_PARTITION_OWNER == startKey) { + storeMap.forEach((k, v) -> { + builder.add(k, -1); + }); + } else if (endKey == HgStoreClientConst.EMPTY_BYTES || startKey == endKey || + Arrays.equals(startKey, endKey)) { + //log.info("leader-> {}",leaderMap.get(startCode / PARTITION_LENGTH)); + builder.add(leaderMap.get(startCode / PARTITION_LENGTH), startCode); + } else { + Assert.fail("OwnerKey转成HashCode后已经无序了, 按照OwnerKey范围查询没意义"); + builder.add(leaderMap.get(startCode / PARTITION_LENGTH), startCode); + builder.add(leaderMap.get(endCode / PARTITION_LENGTH), endCode); + } + return 0; + }); + nodeManager.setNodeProvider((graphName, nodeId) -> { + // System.out.println("HgStoreNodeProvider apply " + graphName + " " + nodeId + + // " " + storeMap.get(nodeId)); + return nodeManager.getNodeBuilder().setNodeId(nodeId) + .setAddress(storeMap.get(nodeId)).build(); + }); + nodeManager.setNodeNotifier((graphName, storeNotice) -> { + System.out.println("recv node notifier " + storeNotice); + if (storeNotice.getPartitionLeaders().size() > 0) { + leaderMap.putAll(storeNotice.getPartitionLeaders()); + System.out.println("leader changed "); + leaderMap.forEach((k, v) -> { + System.out.print(" " + k + " " + v + ","); + }); + System.out.println(); + } + return 0; + }); + } + + protected static HgStoreSession getStoreSession() { + return HgSessionManager.getInstance().openSession(HgStoreTestUtil.GRAPH_NAME); + } + + protected static HgStoreSession getStoreSession(String graph) { + return HgSessionManager.getInstance().openSession(graph); + } + + @Test + public void put_get() { + System.out.println("--- test put & get ---"); + + HgStoreSession session = getStoreSession(); + + HgOwnerKey key = HgStoreTestUtil.toOwnerKey("FOR-PUT-KEY"); + //HgOwnerKey key = toAllNodeKey("FOR-PUT-KEY"); + byte[] value = HgStoreTestUtil.toBytes("FOR-PUT-VALUE"); + + Assert.assertTrue(session.put(HgStoreTestUtil.TABLE_NAME, key, value)); + Assert.assertEquals(HgStoreTestUtil.toStr(value), HgStoreTestUtil.toStr(session.get( + HgStoreTestUtil.TABLE_NAME, key))); + } + + @Test + public void batchPrefix() { + System.out.println("--- test batchGet ---"); + HgStoreSession session = getStoreSession(); + String keyPrefix = "UNIT-BATCH-GET"; + + Map map = HgStoreTestUtil.batchPut(session, keyPrefix); + List keyList = + map.entrySet().stream().map(e -> e.getKey()).collect(Collectors.toList()); + + Map keyMap = new HashMap<>(); + map.entrySet().forEach(e -> { + keyMap.put(Arrays.toString(e.getKey().getKey()), e.getValue()); + }); + + //printOwner(keyList); + HgKvIterator iterator = session.batchPrefix(HgStoreTestUtil.TABLE_NAME, keyList); + int amount = HgStoreTestUtil.println(iterator); + Assert.assertEquals(amount, keyList.size()); +/* + + println("--- batch-get result ---"); + iterator = session.batchGet(TABLE_NAME, keyList); + while(iterator.hasNext()){ + HgKvEntry kv=iterator.next(); + Assert.assertEquals(true,keyMap.containsKey(Arrays.toString(kv.key()))); + } +*/ + + } + + @Test + //CAUTION: ONLY FOR LONG! + //注意:目前只可以对long类型value进行Merge操作。 + public void merge() { + System.out.println("--- test merge (1+1=2)---"); + HgStoreSession session = getStoreSession(); + String mergeKey = "merge-key"; + HgOwnerKey key = HgStoreTestUtil.toOwnerKey(mergeKey); + byte[] value = HgStoreTestUtil.toBytes(1L); + + System.out.println("- put " + mergeKey + ":1 -"); + session.put(HgStoreTestUtil.TABLE_NAME, key, value); + System.out.println("- merge " + mergeKey + ":1 -"); + session.merge(HgStoreTestUtil.TABLE_NAME, key, value); + long res = HgStoreTestUtil.toLong(session.get(HgStoreTestUtil.TABLE_NAME, key)); + System.out.printf("after merge " + mergeKey + "=%s%n", res); + Assert.assertEquals(2L, res); + + String putStr = "19"; + session.put(HgStoreTestUtil.TABLE_NAME, key, HgStoreTestUtil.toBytes(putStr)); + byte[] b1 = session.get(HgStoreTestUtil.TABLE_NAME, key); + Assert.assertEquals(putStr, HgStoreTestUtil.toStr(b1)); + } + + @Test + public void delete() { + System.out.println("--- test delete ---"); + HgStoreSession session = getStoreSession(); + + String delKey = "del-key"; + String delValue = "del-value"; + HgOwnerKey key = HgStoreTestUtil.toOwnerKey(delKey); + byte[] value = HgStoreTestUtil.toBytes(delValue); + + HgStoreTestUtil.println("- put " + delKey + ": " + delValue); + session.put(HgStoreTestUtil.TABLE_NAME, key, value); + HgStoreTestUtil.println("- delete " + delKey); + session.delete(HgStoreTestUtil.TABLE_NAME, key); + value = session.get(HgStoreTestUtil.TABLE_NAME, key); + HgStoreTestUtil.println("- get " + delKey + ": " + HgStoreTestUtil.toStr(value)); + Assert.assertEquals(EMPTY_BYTES, value); + } + + // @Test + public void deleteSingle() { + System.out.println("--- test deleteSingle ---"); + HgStoreSession session = getStoreSession(); + + String delKey = "del-single-key"; + String delValue = "del-single-value"; + HgOwnerKey key = HgStoreTestUtil.toOwnerKey(delKey); + byte[] value = HgStoreTestUtil.toBytes(delValue); + + HgStoreTestUtil.println("- put [" + delKey + "] = " + delValue); + session.put(HgStoreTestUtil.TABLE_NAME, key, value); + + value = session.get(HgStoreTestUtil.TABLE_NAME, key); + HgStoreTestUtil.println( + "- before del, get [" + delKey + "] = " + HgStoreTestUtil.toStr(value)); + Assert.assertEquals(delValue, HgStoreTestUtil.toStr(value)); + + HgStoreTestUtil.println("- delete-single : [" + delKey + "]"); + session.deleteSingle(HgStoreTestUtil.TABLE_NAME, key); + value = session.get(HgStoreTestUtil.TABLE_NAME, key); + HgStoreTestUtil.println( + "- after del, get [" + delKey + "] = " + HgStoreTestUtil.toStr(value)); + Assert.assertEquals("", HgStoreTestUtil.toStr(value)); + + } + + // @Test + public void deleteRange() { + HgStoreTestUtil.println("--- test deleteRange ---"); + HgStoreSession session = getStoreSession(); + + String rangePrefix = "DEL-RANGE-KEY"; + String owner = "batch-delete-owner"; + Map map = + HgStoreTestUtil.batchPut(session, HgStoreTestUtil.TABLE_NAME, rangePrefix, 10, + key -> HgStoreTestUtil.toOwnerKey(owner, key) + ); + + HgOwnerKey startKey = HgStoreTestUtil.toOwnerKey(owner, rangePrefix + "-00"); + HgOwnerKey endKey = HgStoreTestUtil.toOwnerKey(owner, rangePrefix + "-05"); + + Assert.assertTrue(session.deleteRange(HgStoreTestUtil.TABLE_NAME, startKey, endKey)); + + HgStoreTestUtil.println("- after delete range from [" + + HgStoreTestUtil.toStr(startKey.getKey()) + "] to [" + + HgStoreTestUtil.toStr(endKey.getKey()) + "]"); + + for (int i = 0; i < 10; i++) { + HgOwnerKey key = HgStoreTestUtil.toOwnerKey(owner, rangePrefix + "-" + + HgStoreTestUtil.padLeftZeros( + String.valueOf(i), 2)); + String value = HgStoreTestUtil.toStr(session.get(HgStoreTestUtil.TABLE_NAME, key)); + HgStoreTestUtil.println( + "- get [" + HgStoreTestUtil.toStr(key.getKey()) + "] = " + value); + + // TODO: [start,end)? + if (i < 5) { + Assert.assertEquals("", value); + } else { + //println(value); + Assert.assertNotEquals("", value); + } + } + + } + + // @Test + public void deletePrefix() { + System.out.println("--- test deletePrefix ---"); + HgStoreSession session = getStoreSession(); + + String prefixStr = "DEL-PREFIX-KEY"; + String owner = "batch-delete-owner"; + Map map = + HgStoreTestUtil.batchPut(session, HgStoreTestUtil.TABLE_NAME, prefixStr, 10, + key -> { + return HgStoreTestUtil.toOwnerKey(owner, key); + }); + + //printOwner(map.entrySet().stream().map(e -> e.getKey()).collect(Collectors.toList())); + + HgOwnerKey prefix = HgStoreTestUtil.toOwnerKey(owner, prefixStr); + + Assert.assertEquals(10, HgStoreTestUtil.amountOf(session.scanIterator( + HgStoreTestUtil.TABLE_NAME, prefix))); + session.deletePrefix(HgStoreTestUtil.TABLE_NAME, prefix); + Assert.assertEquals(0, HgStoreTestUtil.amountOf(session.scanIterator( + HgStoreTestUtil.TABLE_NAME, prefix))); + + HgStoreTestUtil.println("- after delete by prefix:[" + prefixStr + "]"); + + for (int i = 0; i < 10; i++) { + HgOwnerKey key = + HgStoreTestUtil.toOwnerKey(owner, prefixStr + HgStoreTestUtil.toSuffix(i, 2)); + String value = HgStoreTestUtil.toStr(session.get(HgStoreTestUtil.TABLE_NAME, key)); + System.out.println("- get [" + HgStoreTestUtil.toStr(key.getKey()) + "] = " + value); + Assert.assertEquals("", value); + } + + } + + // @Test + public void scan() { + HgStoreTestUtil.println("--- test scanIterator ---"); + String tableName = "UNIT_SCAN_ITERATOR"; + String keyName = "SCAN-ITER"; + int keyAmt = 100; + + HgStoreSession session = getStoreSession(); + if (HgStoreTestUtil.amountOf( + session.scanIterator(tableName, HgStoreTestUtil.toAllPartitionKey(keyName), 10)) < + 10) { + HgStoreTestUtil.batchPut(session, tableName, keyName, keyAmt); + } + int count = 0; + int limit = 0; + int max = 99999; + HgKvIterator iterator = null; + + // println("-- test scan all --"); + iterator = session.scanIterator(tableName); + count = 0; + while (iterator.hasNext()) { + count++; + HgKvEntry entry = iterator.next(); + if (count % 10 == 0) { + // println(entry); + } + if (count >= max) break; + } + iterator.close(); + Assert.assertEquals(keyAmt, count); + + // println("-- test 0 element --"); + iterator = session.scanIterator(tableName, HgStoreTestUtil.toAllPartitionKey("__SCAN-001"), + HgStoreTestUtil.toAllPartitionKey("__SCAN-100"), 0); + Assert.assertFalse(iterator.hasNext()); + try { + iterator.next(); + Assert.fail(); + } catch (Throwable t) { + // println("-- test NoSuchElementException --"); + Assert.assertTrue(t instanceof NoSuchElementException); + } + iterator.close(); + + // println("-- test limit 1 to 10 --"); + for (int i = 1; i <= 10; i++) { + // println("- limit " + i + " -"); + limit = i; + iterator = session.scanIterator(tableName, + HgStoreTestUtil.toAllPartitionKey(keyName + "-0"), + HgStoreTestUtil.toAllPartitionKey(keyName + "-1"), + limit); + count = 0; + while (iterator.hasNext()) { + count++; + HgKvEntry entry = iterator.next(); + // println(entry); + } + iterator.close(); + Assert.assertEquals(limit, count); + } + + // println("-- test limit 1 to 10 not enough --"); + for (int i = 1; i <= 10; i++) { + // println("- limit " + i + " -"); + limit = i; + iterator = session.scanIterator(tableName + , HgStoreTestUtil.toAllPartitionKey(keyName + "-001") + , HgStoreTestUtil.toAllPartitionKey(keyName + "-005"), limit); + count = 0; + while (iterator.hasNext()) { + count++; + HgKvEntry entry = iterator.next(); + // println(entry); + } + iterator.close(); + if (i <= 5) { + Assert.assertEquals(limit, count); + } else { + Assert.assertEquals(5, count); + } + + } + + // println("-- test limit 0 (no limit) --"); + limit = 0; + iterator = + session.scanIterator(tableName, HgStoreTestUtil.toAllPartitionKey(keyName + "-0"), + HgStoreTestUtil.toAllPartitionKey(keyName + "-2"), limit); + + count = 0; + while (iterator.hasNext()) { + count++; + HgKvEntry entry = iterator.next(); + if (count % 10 == 0) { + // println(entry); + } + if (count >= max) break; + } + iterator.close(); + Assert.assertEquals(keyAmt, count); + + // println("-- test scan prefix --"); + iterator = + session.scanIterator(tableName, HgStoreTestUtil.toAllPartitionKey(keyName + "-")); + count = 0; + while (iterator.hasNext()) { + count++; + HgKvEntry entry = iterator.next(); + if (count % 10 == 0) { + // println(entry); + } + if (count >= max) break; + } + iterator.close(); + Assert.assertEquals(keyAmt, count); + + // println("-- test scan via hash code --"); + iterator = session.scanIterator(tableName, 0, 65535, HgKvStore.SCAN_HASHCODE, EMPTY_BYTES); + count = 0; + + while (iterator.hasNext()) { + count++; + HgKvEntry entry = iterator.next(); + if (count % 10 == 0) { + // println(entry); + } + if (count >= max) break; + } + iterator.close(); + Assert.assertEquals(keyAmt, count); + + // println("-- test range limit scan type --"); + iterator = session.scanIterator(tableName + , HgStoreTestUtil.toAllPartitionKey("WWWWWWW") + , HgStoreClientConst.EMPTY_OWNER_KEY + , 0, HgKvStore.SCAN_ANY, EMPTY_BYTES + ); + iterator.close(); + // println("-- test range limit scan type -session.scanIterator over--"); + Assert.assertEquals(0, HgStoreTestUtil.amountOf(iterator)); + // println("-- test range limit scan type -session.assertEquals over--"); + + // println("-- test range limit scan type -100"); + iterator = session.scanIterator(tableName + , HgStoreTestUtil.toAllPartitionKey(keyName + "-100") + , HgStoreClientConst.EMPTY_OWNER_KEY + , 0, HgKvStore.SCAN_ANY, EMPTY_BYTES + ); + + // println("-- test range limit scan type -100 over"); + Assert.assertEquals(1, HgStoreTestUtil.amountOf(iterator)); + // println("-- test range limit scan type -100 assertEquals 1"); + + // println("-- test range limit scan type -51 "); + iterator = session.scanIterator(tableName + , HgStoreTestUtil.toAllPartitionKey(keyName + "-051") + , HgStoreClientConst.EMPTY_OWNER_KEY + ); + + // println("-- test range limit scan type -51 over"); + //HgStoreTestUtil.println(iterator); + Assert.assertEquals(50, HgStoreTestUtil.amountOf(iterator)); + // println("-- test range limit scan type -51 assertEquals"); + //TODO: add more... + HgStoreTestUtil.println("-- test scanIterator end"); + } + + // @Test + public void scan_close() { + /*************** test scan close **************/ + HgStoreTestUtil.println("--- test scan close ---"); + String tableName = "UNIT_ITER_CLOSE"; + String keyName = "UNIT_ITER_CLOSE"; + + int amount = 1_000_000; + HgStoreSession session = getStoreSession(); + if (HgStoreTestUtil.amountOf( + session.scanIterator(tableName, HgStoreTestUtil.toAllPartitionKey(keyName), 1)) < + 1) { + HgStoreTestUtil.batchPut(session, tableName, keyName, amount); + } + + HgKvIterator iterator = null; + + /* server over, all -> close */ + iterator = session.scanIterator(tableName); + Assert.assertEquals(amount, HgStoreTestUtil.amountOf(iterator)); + + /* server over, one page, all -> close */ + iterator = session.scanIterator(tableName, 100_000); + Assert.assertEquals(100_000, HgStoreTestUtil.amountOf(iterator)); + + /* server over, two page, all -> close */ + iterator = session.scanIterator(tableName, 200_000); + Assert.assertEquals(200_000, HgStoreTestUtil.amountOf(iterator)); + + /* server not over, enough -> close */ + iterator = session.scanIterator(tableName); + iterator.next(); + iterator.close(); + + /* server not over, one page, enough -> close */ + iterator = session.scanIterator(tableName, 100_000); + iterator.next(); + iterator.close(); + + /* server not over, two page, enough -> close */ + iterator = session.scanIterator(tableName, 200_000); + iterator.next(); + iterator.close(); + + /* server not over, enough -> close */ + iterator = session.scanIterator(tableName); + iterator.next(); + iterator.close(); + iterator = session.scanIterator(tableName); + for (int i = 0; iterator.hasNext() && i < 500_000; i++) { + iterator.next(); + } + iterator.close(); + + /* server timeout, enough -> close */ + iterator = session.scanIterator(tableName); + iterator.next(); + // HgStoreTestUtil.sleeping(11000); + + try { + HgStoreTestUtil.amountOf(iterator); + } catch (Throwable t) { + HgStoreTestUtil.println("-- passed server waiting timeout --"); + } + + iterator.close(); + + } + + // @Test + public void paging() { + HgStoreTestUtil.println("--- test scanIterator_range ---"); + String graph = "UNIT/paging"; + String tableName = "UNIT_SCAN_PAGING"; + String keyName = "SCAN-PAGING"; + int keyAmt = 100; + HgStoreSession session = getStoreSession(graph); + + if (HgStoreTestUtil.amountOf( + session.scanIterator(tableName, HgStoreTestUtil.toAllPartitionKey(keyName), 1)) < + 1) { + HgStoreTestUtil.batchPut(session, tableName, keyName, keyAmt); + } + + int count = 0; + int limit = 0; + int max = 99999; + HgKvIterator iterator = null; + + count = 0; + + iterator = session.scanIterator(tableName + , HgStoreTestUtil.toAllPartitionKey(keyName + "-000") + , HgStoreClientConst.EMPTY_OWNER_KEY + , 0, HgKvStore.SCAN_ANY, EMPTY_BYTES + ); + //HgStoreTestUtil.println(iterator); + // Assert.assertEquals(100, HgStoreTestUtil.amountOf(iterator)); + List positionList = new LinkedList<>(); + while (iterator.hasNext()) { + HgStoreTestUtil.print((count++) + " "); + HgKvEntry entry = iterator.next(); + HgStoreTestUtil.print(entry); + HgStoreTestUtil.print(" " + Arrays.toString(iterator.position()) + "\n"); + positionList.add(iterator.position()); + if (count >= max) break; + } + + + iterator = session.scanIterator(tableName + , HgStoreTestUtil.toAllPartitionKey(keyName + "-000") + , HgStoreClientConst.EMPTY_OWNER_KEY + , 0, HgKvStore.SCAN_ANY, EMPTY_BYTES + ); + + byte[] position = positionList.get(50); + HgStoreTestUtil.println("seek: " + Arrays.toString(position)); + iterator.seek(position); + //println("amt after seek: "+HgStoreTestUtil.println(iterator)); + Assert.assertEquals(50, HgStoreTestUtil.amountOf(iterator)); + + } + + @Test + public void scanBatch() { + HgStoreTestUtil.println("--- test scanBatch ---"); + String tableName = "UNIT_SCAN_BATCH_1"; + String keyName = "SCAN-BATCH"; + int keyAmt = 10_000; + + HgStoreSession session = getStoreSession(); + + if (HgStoreTestUtil.amountOf( + session.scanIterator(tableName, HgStoreTestUtil.toAllPartitionKey(keyName), + keyAmt)) < keyAmt) { + HgStoreTestUtil.batchPut(session, tableName, keyName, keyAmt); + } + + Assert.assertEquals(keyAmt, HgStoreTestUtil.amountOf(session.scanIterator(tableName))); + + List> iterators = null; + List prefixes = null; + + List startList = Arrays.asList( + HgStoreTestUtil.toAllPartitionKey(keyName + "-001") + , HgStoreTestUtil.toAllPartitionKey(keyName + "-003") + , HgStoreTestUtil.toAllPartitionKey(keyName + "-005") + , HgStoreTestUtil.toAllPartitionKey(keyName + "-007") + , HgStoreTestUtil.toAllPartitionKey(keyName + "-009") + ); + + List endList = Arrays.asList( + HgStoreTestUtil.toAllPartitionKey(keyName + "-002") + , HgStoreTestUtil.toAllPartitionKey(keyName + "-004") + , HgStoreTestUtil.toAllPartitionKey(keyName + "-006") + , HgStoreTestUtil.toAllPartitionKey(keyName + "-008") + , HgStoreTestUtil.toAllPartitionKey(keyName + "-010") + ); + + List prefixList = Arrays.asList( + HgStoreTestUtil.toAllPartitionKey(keyName + "-001") + , HgStoreTestUtil.toAllPartitionKey(keyName + "-002") + , HgStoreTestUtil.toAllPartitionKey(keyName + "-003") + , HgStoreTestUtil.toAllPartitionKey(keyName + "-004") + , HgStoreTestUtil.toAllPartitionKey(keyName + "-005") + , HgStoreTestUtil.toAllPartitionKey(keyName + "-006") + , HgStoreTestUtil.toAllPartitionKey(keyName + "-007") + , HgStoreTestUtil.toAllPartitionKey(keyName + "-008") + , HgStoreTestUtil.toAllPartitionKey(keyName + "-009") + ); + + HgStoreTestUtil.println("-- test scan-batch all --"); + + HgScanQuery scanQuery = HgScanQuery.tableOf(tableName); + iterators = session.scanBatch(scanQuery); + Assert.assertEquals(3, iterators.size()); + + Assert.assertEquals(keyAmt, HgStoreTestUtil.println(iterators)); + + HgStoreTestUtil.println("-- test scan-batch prefix --"); + + + iterators = session.scanBatch( + HgScanQuery.prefixOf(tableName, prefixList) + ); + Assert.assertEquals(3, iterators.size()); + Assert.assertEquals(900, + iterators.parallelStream().mapToInt(e -> HgStoreTestUtil.amountOf(e)) + .sum() + ); + + HgStoreTestUtil.println("-- test scan-batch range --"); + + iterators = session.scanBatch(HgScanQuery.rangeOf(tableName, startList, endList)); + Assert.assertEquals(3, iterators.size()); + Assert.assertEquals( + HgStoreTestUtil.amountOf( + session.scanIterator(tableName + , HgStoreTestUtil.toAllPartitionKey(keyName + "-001") + , HgStoreTestUtil.toAllPartitionKey(keyName + "-010") + ) + ) + , + iterators.parallelStream().mapToInt(e -> HgStoreTestUtil.amountOf(e)).sum() + ); + + HgStoreTestUtil.println("-- test scan-batch limit --"); + + int limit = 1; + iterators = session.scanBatch( + HgScanQuery.rangeOf(tableName, startList, endList) + .builder() + .setLimit(limit) + .build() + ); + + //HgStoreTestUtil.println(iterators); + Assert.assertEquals(iterators.size() * limit, + iterators.parallelStream().mapToInt(e -> HgStoreTestUtil.amountOf(e)) + .sum() + ); + + HgStoreTestUtil.println("-- test scan-batch multi-table --"); + if (HgStoreTestUtil.amountOf( + session.scanIterator("g+oe", HgStoreTestUtil.toAllPartitionKey(keyName), keyAmt)) < + keyAmt) { + HgStoreTestUtil.batchPut(session, "g+oe", keyName, keyAmt); + HgStoreTestUtil.batchPut(session, "g+ie", keyName, keyAmt); + } + + prefixes = Collections.singletonList(HgStoreTestUtil.toAllPartitionKey(keyName)); + + tableName = "g+oe,g+ie"; + iterators = session.scanBatch( + HgScanQuery.prefixOf(tableName, prefixes) + ); + + //HgStoreTestUtil.println(iterators); + Assert.assertEquals(keyAmt * 2, HgStoreTestUtil.amountIn(iterators)); + iterators = session.scanBatch( + HgScanQuery.prefixOf(tableName, prefixList) + ); + Assert.assertEquals(900 * 2, HgStoreTestUtil.amountIn(iterators)); + + HgStoreTestUtil.println("-- test scan-batch per-key-limit --"); + + tableName = "PER_KEY_LIMIT_TABLE"; + keyName = "PER_KEY_LIMIT"; + if (HgStoreTestUtil.amountOf( + session.scanIterator(tableName, HgStoreTestUtil.toAllPartitionKey(keyName), + keyAmt)) < keyAmt) { + HgStoreTestUtil.batchPut(session, tableName, keyName, keyAmt); + } + prefixes = Arrays.asList( + HgStoreTestUtil.toAllPartitionKey(keyName + "-01") + , HgStoreTestUtil.toAllPartitionKey(keyName + "-02") + , HgStoreTestUtil.toAllPartitionKey(keyName + "-03") + , HgStoreTestUtil.toAllPartitionKey(keyName + "-04") + , HgStoreTestUtil.toAllPartitionKey(keyName + "-05") + ); + + iterators = session.scanBatch( + HgScanQuery.prefixOf(tableName, prefixes).builder().setPerKeyLimit(1).build() + ); + //HgStoreTestUtil.println(iterators); + Assert.assertEquals(prefixes.size() * 3, HgStoreTestUtil.amountIn(iterators)); + + } + + // @Test + public void truncate() { + HgStoreTestUtil.println("--- test truncate ---"); + String graph = "graph_truncate"; + String tableName = "UNIT_TRUNCATE_1"; + String keyName = "KEY_TRUNCATE"; + + HgStoreSession session = getStoreSession(graph); + HgStoreTestUtil.batchPut(session, tableName, keyName, 100); + Assert.assertEquals(100, HgStoreTestUtil.amountOf(session.scanIterator(tableName))); + + String tableName2 = "UNIT_TRUNCATE_2"; + HgStoreTestUtil.batchPut(session, tableName2, keyName, 100); + Assert.assertEquals(100, HgStoreTestUtil.amountOf(session.scanIterator(tableName2))); + + session.truncate(); + Assert.assertEquals(0, HgStoreTestUtil.amountOf(session.scanIterator(tableName))); + Assert.assertEquals(0, HgStoreTestUtil.amountOf(session.scanIterator(tableName2))); + } + + // @Test + public void handle_table() { + HgStoreTestUtil.println("--- test table ---"); + + String tableName = "UNIT_TABLE_" + System.currentTimeMillis(); + HgOwnerKey key = HgStoreTestUtil.toOwnerKey("FOR-PUT-KEY"); + byte[] value = HgStoreTestUtil.toBytes("FOR-PUT-VALUE"); + HgStoreSession session = getStoreSession(); + + HgStoreTestUtil.println("-- test createTable --"); + session.createTable(tableName); + Assert.assertTrue(session.existsTable(tableName)); + + HgStoreTestUtil.println("-- test deleteTable --"); + session.put(tableName, key, value); + Assert.assertEquals(HgStoreTestUtil.toStr(value), + HgStoreTestUtil.toStr(session.get(tableName, key))); + session.deleteTable(tableName); + Assert.assertNotEquals(HgStoreTestUtil.toStr(value), + HgStoreTestUtil.toStr(session.get(tableName, key))); + Assert.assertTrue(session.existsTable(tableName)); + + + HgStoreTestUtil.println("-- test dropTable --"); + Assert.assertTrue(session.dropTable(tableName)); + Assert.assertFalse(session.existsTable(tableName)); + + + HgStoreTestUtil.println("-- test existsTable --"); + Assert.assertFalse(session.existsTable(tableName)); + + } + + // @Test + public void tx() { + HgStoreTestUtil.println("--- test tx ---"); + HgStoreSession session = getStoreSession(); + String tableName = "UNIT_TABLE_TX"; + String keyPrefix = "TX"; + int keyAmt = 100; + + HgStoreTestUtil.println("-- unsupported tx operation --"); + HgStoreTestUtil.println("- tx deleteTable -"); + HgStoreTestUtil.batchPut(session, tableName, keyPrefix, keyAmt); + Assert.assertEquals(keyAmt, HgStoreTestUtil.amountOf(session.scanIterator(tableName))); + session.beginTx(); + session.deleteTable(tableName); + session.commit(); + Assert.assertEquals(0, HgStoreTestUtil.amountOf(session.scanIterator(tableName))); + + HgStoreTestUtil.println("- tx dropTable -"); + HgStoreTestUtil.batchPut(session, tableName, keyPrefix, keyAmt); + Assert.assertEquals(keyAmt, HgStoreTestUtil.amountOf(session.scanIterator(tableName))); + session.beginTx(); + session.dropTable(tableName); + session.commit(); + Assert.assertFalse(session.existsTable(tableName)); + Assert.assertEquals(0, HgStoreTestUtil.amountOf(session.scanIterator(tableName))); + + HgStoreTestUtil.println("- tx truncate -"); + HgStoreTestUtil.batchPut(session, tableName, keyPrefix, keyAmt); + Assert.assertEquals(keyAmt, HgStoreTestUtil.amountOf(session.scanIterator(tableName))); + session.beginTx(); + session.truncate(); + session.commit(); + Assert.assertFalse(session.existsTable(tableName)); + Assert.assertEquals(0, HgStoreTestUtil.amountOf(session.scanIterator(tableName))); + + //TODO : add others + } + + //// @Test + public void scanIterator_WithNonePartition() { + HgStoreTestUtil.println("--- test scanIterator with none partition ---"); + int count = 0; + HgStoreSession session = getStoreSession(); + + for (int i = 0; i < 100; i++) { + HgKvIterator iterator = session.scanIterator("XXXXXXXX"); + while (iterator.hasNext()) { + count++; + HgKvEntry entry = iterator.next(); + HgStoreTestUtil.println(entry); + } + } + + Assert.assertEquals(0, count); + + } + + //// @Test + public void repeatedly_parallel_scan() { + MetricX metrics = MetricX.ofStart(); + HgStoreTestUtil.repeatedly_test(100, () -> parallel_scan()); + metrics.end(); + + log.info("*************************************************"); + log.info("************* Scanning Completed **************"); + log.info("Total: {} sec.", metrics.past() / 1000); + log.info("Sum: {} sec.", MetricX.getIteratorWait() / 1000); + log.info("Amt: {} scans.", MetricX.getIteratorCount()); + log.info("Avg: {} ms.", MetricX.getIteratorWaitAvg()); + log.info("Max: {} ms.", MetricX.getIteratorWaitMax()); + log.info("Fail: {} times.", metrics.getFailureCount()); + log.info("Page: {}", HgStoreClientConfig.of().getNetKvScannerPageSize()); + log.info("*************************************************"); + + // runWaiting(); + } + + // @Test + public void parallel_scan() { + HgStoreTestUtil.println("--- test scanIterator in parallel ---"); + HgStoreTestUtil.parallel_test(100, () -> { + this.scan(); + }, t -> t.printStackTrace()); + } + + //// @Test + public void put_Benchmark() { + /*************** Put Benchmark **************/ + String tableName = "UNIT_PUT_BENCHMARK"; + String keyPrefix = "PUT-BENCHMARK"; + int amount = 1_000_000; + HgStoreSession session = getStoreSession(); + + int length = String.valueOf(amount).length(); + + session.beginTx(); + + long start = System.currentTimeMillis(); + for (int i = 0; i < amount; i++) { + HgOwnerKey key = HgStoreTestUtil.toOwnerKey( + keyPrefix + "-" + HgStoreTestUtil.padLeftZeros(String.valueOf(i), length)); + byte[] value = HgStoreTestUtil.toBytes(keyPrefix + "-V-" + i); + + session.put(tableName, key, value); + + if ((i + 1) % 100_000 == 0) { + HgStoreTestUtil.println("---------- " + (i + 1) + " --------"); + HgStoreTestUtil.println( + "Preparing took: " + (System.currentTimeMillis() - start) + " ms."); + session.commit(); + HgStoreTestUtil.println( + "Committing took: " + (System.currentTimeMillis() - start) + " ms."); + start = System.currentTimeMillis(); + session.beginTx(); + } + } + + if (session.isTx()) { + session.commit(); + } + + Assert.assertEquals(amount, HgStoreTestUtil.amountOf(session.scanIterator(tableName))); + + + } + + //// @Test + public void put_Benchmark_Parallel() { + int threadsAmount = 100; + CountDownLatch countDownLatch = new CountDownLatch(threadsAmount); + + for (int j = 0; j < threadsAmount; j++) { + for (int i = 0; i < threadsAmount; i++) { + pool.submit(() -> { + this.put_Benchmark(); + // this.scanIterator_WithNonePartition(); + countDownLatch.countDown(); + }); + } + } + try { + countDownLatch.await(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + } + + //// @Test + public void parallel_scanBatch() { + int threadsAmount = 50; + CountDownLatch countDownLatch = new CountDownLatch(threadsAmount); + + for (int j = 0; j < threadsAmount; j++) { + for (int i = 0; i < threadsAmount; i++) { + pool.submit(() -> { + try { + this.scanBatch(); + } catch (Throwable t) { + t.printStackTrace(); + } finally { + countDownLatch.countDown(); + } + }); + } + } + try { + countDownLatch.await(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + } + + //// @Test + public void benchmark_scanBatch() { + HgStoreTestUtil.println("--- Benchmark scanBatch ---"); + String tableName = "Benchmark_SCAN_BATCH"; + String keyName = "SCAN-BATCH"; + int keyAmt = 10_000_000; + + HgStoreSession session = getStoreSession(); + + if (HgStoreTestUtil.amountOf( + session.scanIterator(tableName, HgStoreTestUtil.toAllPartitionKey(keyName), 1)) < + 1) { + HgStoreTestUtil.batchPut(session, tableName, keyName, keyAmt); + } + HgStoreTestUtil.println("-- Starting scan --"); + // while (true) { + + MetricX metrics = MetricX.ofStart(); + //HgStoreTestUtil.println(session.scanIterator(tableName)); + List> iterators = session.scanBatch(HgScanQuery.tableOf(tableName)); + //HgStoreTestUtil.println(iterators); + //Assert.assertEquals(keyAmt, HgStoreTestUtil.amountIn(iterators)); + HgStoreTestUtil.amountIn(iterators); + metrics.end(); + log.info("*************************************************"); + log.info("*********** Batch Scan Completed **************"); + log.info("Total : {} (sec)", metrics.past() / 1000); + log.info(" Sum : {} (sec)", MetricX.getIteratorWait() / 1000); + log.info(" Amt : {} (scans).", MetricX.getIteratorCount()); + log.info(" Avg : {} (ms)", MetricX.getIteratorWaitAvg()); + log.info(" Max : {} (ms)", MetricX.getIteratorWaitMax()); + log.info(" Fail : {} (times)", metrics.getFailureCount()); + log.info(" Page : {} (KVs)", HgStoreClientConfig.of().getNetKvScannerPageSize()); + log.info("Iters : {}", iterators.size()); + log.info("*************************************************"); + HgStoreTestUtil.sleeping(100); + + // } + } + + //// @Test + public void benchmark_scan() { + /*************** test no limit, with 10 millions **************/ + String tableName = "UNIT_HUGE"; + String keyName = "SCAN-HUGE"; + int amount = 10_000_000; + int max = 10_000_000; + HgStoreSession session = getStoreSession(); + + /*Initialization*/ + if (HgStoreTestUtil.amountOf( + session.scanIterator(tableName, HgStoreTestUtil.toAllPartitionKey(keyName), 1)) < + 1) { + HgStoreTestUtil.batchPut(session, tableName, keyName, amount); + } + + MetricX metricX = MetricX.ofStart(); + + int count = 0; + HgKvIterator iterator = session.scanIterator(tableName); + //HgStoreTestUtil.println(iterator, e -> (e % (amount / 100) == 0)); + while (iterator.hasNext()) { + count++; + HgKvEntry entry = iterator.next(); + if (count % (amount / 10) == 0) { + HgStoreTestUtil.println(entry); + } + if (count >= max) break; + } + + metricX.end(); + + log.info("*************************************************"); + log.info("************* Benchmark Completed *************"); + log.info("Keys: {}", count); + log.info("Waiting: {} ms.", MetricX.getIteratorWait()); + log.info("Total: {} ms.", metricX.past()); + log.info("Iterator: [{}]", iterator.getClass().getSimpleName()); + log.info("Page: {}", HgStoreClientConfig.of().getNetKvScannerPageSize()); + log.info("*************************************************"); + + Assert.assertEquals(amount, count); + } + + + //// @Test + public void extreme_scan_close() { + /*************** test close **************/ + String tableName = "UNIT_ITER_CLOSE_EXTREME"; + String keyName = "UNIT_ITER_CLOSE_EXTREME"; + int amount = 1_000_000; + HgStoreSession session = getStoreSession(); + + if (HgStoreTestUtil.amountOf( + session.scanIterator(tableName, HgStoreTestUtil.toAllPartitionKey(keyName), 1)) < + 1) { + HgStoreTestUtil.batchPut(session, tableName, keyName, amount); + } + HgKvIterator iterator = null; + /* server not over, enough, extreme -> close */ + for (int i = 0; i <= 10_000; i++) { + iterator = session.scanIterator(tableName); + iterator.next(); + iterator.close(); + HgStoreTestUtil.println("extreme loop: " + i); + } + HgStoreTestUtil.runWaiting(); + } + + //// @Test + public void parallel_scan_close() { + HgStoreTestUtil.parallel_test(10, () -> this.scan_close(), t -> t.printStackTrace()); + } + + //// @Test + public void repeat_parallel_scan_close() { + HgStoreTestUtil.repeatedly_test(1000, () -> this.parallel_scan_close()); + HgStoreTestUtil.runWaiting(); + } + + //// @Test + public void parallel_huge_scan() { + int threadsAmount = 3; + CountDownLatch countDownLatch = new CountDownLatch(threadsAmount); + ExecutorService poll = Executors.newFixedThreadPool(threadsAmount); + + for (int i = 0; i < threadsAmount; i++) { + poll.submit(() -> { + this.benchmark_scan(); + countDownLatch.countDown(); + }); + } + + try { + countDownLatch.await(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + } + +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-client/src/test/java/org/apache/hugegraph/store/client/HgKvStoreTest.java b/hugegraph-store/hg-store-client/src/test/java/org/apache/hugegraph/store/client/HgKvStoreTest.java new file mode 100644 index 000000000..4043be995 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/test/java/org/apache/hugegraph/store/client/HgKvStoreTest.java @@ -0,0 +1,225 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client; + +import static org.apache.hugegraph.store.client.util.HgStoreClientUtil.toBytes; +import static org.apache.hugegraph.store.client.util.HgStoreClientUtil.toOwnerKey; +import static org.apache.hugegraph.store.client.util.HgStoreClientUtil.toStr; +import static org.apache.hugegraph.store.util.HgStoreTestUtil.TABLE_NAME; + +import org.apache.hugegraph.pd.client.PDClient; +import org.apache.hugegraph.pd.client.PDConfig; +import org.apache.hugegraph.store.HgKvEntry; +import org.apache.hugegraph.store.HgKvIterator; +import org.apache.hugegraph.store.HgOwnerKey; +import org.apache.hugegraph.store.HgStoreClient; +import org.apache.hugegraph.store.HgStoreSession; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import lombok.extern.slf4j.Slf4j; + + +@Slf4j +public class HgKvStoreTest { +// @Autowired +// private AppConfig appConfig; + + private final String tableName0 = "cli-table0"; + private HgStoreClient storeClient; + private PDClient pdClient; + + @Before + public void init() { + storeClient = HgStoreClient.create(PDConfig.of("127.0.0.1:8686") + .setEnableCache(true)); + pdClient = storeClient.getPdClient(); + } + + @Test + public void truncateTest() { + log.info("--- test truncateTest ---"); + HgStoreSession graph0 = storeClient.openSession("hugegraph0"); + HgStoreSession graph1 = storeClient.openSession("hugegraph1"); + + graph0.truncate(); + graph1.truncate(); + + for (int i = 0; i < 100; i++) { + HgOwnerKey key = toOwnerKey("owner-" + i, "ownerKey-" + i); + byte[] value0 = toBytes("g0 owner-" + i + ";ownerKey-" + i); + graph0.put(TABLE_NAME, key, value0); + + byte[] value1 = toBytes("g1 owner-" + i + ";ownerKey-" + i); + graph1.put(TABLE_NAME, key, value1); + } + + HgKvIterator iterator = graph0.scanIterator(TABLE_NAME); + Assert.assertTrue(iterator.hasNext()); + while (iterator.hasNext()) { + HgKvEntry entry = iterator.next(); +// log.info("key:{} value:{}", toStr(entry.key()), toStr(entry.value())); + Assert.assertEquals(0, toStr(entry.value()).indexOf("g0")); + } + iterator = graph1.scanIterator(TABLE_NAME); + Assert.assertTrue(iterator.hasNext()); + while (iterator.hasNext()) { + HgKvEntry entry = iterator.next(); +// log.info("key:{} value:{}", toStr(entry.key()), toStr(entry.value())); + Assert.assertEquals(0, toStr(entry.value()).indexOf("g1")); + } + + graph0.truncate(); + iterator = graph0.scanIterator(TABLE_NAME); + Assert.assertFalse(iterator.hasNext()); + + iterator = graph1.scanIterator(TABLE_NAME); + Assert.assertTrue(iterator.hasNext()); + while (iterator.hasNext()) { + HgKvEntry entry = iterator.next(); + log.info("key:{} value:{}", toStr(entry.key()), toStr(entry.value())); + Assert.assertEquals(0, toStr(entry.value()).indexOf("g1")); + } + } + + @Test + public void deleteTableTest() { + log.info("--- test deleteTableTest ---"); + HgStoreSession graph0 = storeClient.openSession("hugegraph0"); + HgStoreSession graph1 = storeClient.openSession("hugegraph1"); + graph0.truncate(); + graph1.truncate(); + + for (int i = 0; i < 10; i++) { + HgOwnerKey key = toOwnerKey("owner-" + i, "ownerKey-" + i); + byte[] value0 = toBytes("g0 owner-" + i + ";ownerKey-" + i); + graph0.put(TABLE_NAME, key, value0); + + byte[] value1 = toBytes("g1 owner-" + i + ";ownerKey-" + i); + graph1.put(TABLE_NAME, key, value1); + } + + HgKvIterator iterator = graph0.scanIterator(TABLE_NAME); + Assert.assertTrue(iterator.hasNext()); + while (iterator.hasNext()) { + HgKvEntry entry = iterator.next(); +// log.info("key:{} value:{}", toStr(entry.key()), toStr(entry.value())); + Assert.assertEquals(0, toStr(entry.value()).indexOf("g0")); + } + iterator = graph1.scanIterator(TABLE_NAME); + Assert.assertTrue(iterator.hasNext()); + while (iterator.hasNext()) { + HgKvEntry entry = iterator.next(); +// log.info("key:{} value:{}", toStr(entry.key()), toStr(entry.value())); + Assert.assertEquals(0, toStr(entry.value()).indexOf("g1")); + } + + graph0.deleteTable(TABLE_NAME); + Assert.assertTrue(graph0.existsTable(TABLE_NAME)); + iterator = graph0.scanIterator(TABLE_NAME); + Assert.assertFalse(iterator.hasNext()); + while (iterator.hasNext()) { + HgKvEntry entry = iterator.next(); + log.info("key:{} value:{}", toStr(entry.key()), toStr(entry.value())); +// Assert.assertEquals(0, toStr(entry.value()).indexOf("g1")); + } + + iterator = graph1.scanIterator(TABLE_NAME); + Assert.assertTrue(iterator.hasNext()); + while (iterator.hasNext()) { + HgKvEntry entry = iterator.next(); +// log.info("key:{} value:{}", toStr(entry.key()), toStr(entry.value())); + Assert.assertEquals(0, toStr(entry.value()).indexOf("g1")); + } + } + + @Test + public void dropTableTest() { + log.info("--- test dropTableTest ---"); + HgStoreSession graph0 = storeClient.openSession("hugegraph0"); + HgStoreSession graph1 = storeClient.openSession("hugegraph1"); + graph0.truncate(); + graph1.truncate(); + + for (int i = 0; i < 10; i++) { + HgOwnerKey key = toOwnerKey("owner-" + i, "ownerKey-" + i); + byte[] value0 = toBytes("g0 owner-" + i + ";ownerKey-" + i); + graph0.put(TABLE_NAME, key, value0); + + byte[] value1 = toBytes("g1 owner-" + i + ";ownerKey-" + i); + graph1.put(TABLE_NAME, key, value1); + } + + HgKvIterator iterator = graph0.scanIterator(TABLE_NAME); + Assert.assertTrue(iterator.hasNext()); + while (iterator.hasNext()) { + HgKvEntry entry = iterator.next(); +// log.info("key:{} value:{}", toStr(entry.key()), toStr(entry.value())); + Assert.assertEquals(0, toStr(entry.value()).indexOf("g0")); + } + iterator = graph1.scanIterator(TABLE_NAME); + Assert.assertTrue(iterator.hasNext()); + while (iterator.hasNext()) { + HgKvEntry entry = iterator.next(); +// log.info("key:{} value:{}", toStr(entry.key()), toStr(entry.value())); + Assert.assertEquals(0, toStr(entry.value()).indexOf("g1")); + } + + graph0.dropTable(TABLE_NAME); + Assert.assertTrue(graph0.existsTable(TABLE_NAME)); + iterator = graph0.scanIterator(TABLE_NAME); + Assert.assertFalse(iterator.hasNext()); + while (iterator.hasNext()) { + HgKvEntry entry = iterator.next(); + log.info("key:{} value:{}", toStr(entry.key()), toStr(entry.value())); +// Assert.assertEquals(0, toStr(entry.value()).indexOf("g1")); + } + + iterator = graph1.scanIterator(TABLE_NAME); + Assert.assertTrue(iterator.hasNext()); + while (iterator.hasNext()) { + HgKvEntry entry = iterator.next(); +// log.info("key:{} value:{}", toStr(entry.key()), toStr(entry.value())); + Assert.assertEquals(0, toStr(entry.value()).indexOf("g1")); + } + } + + @Test + public void moveDataPathTest() { + log.info("--- test moveDataPathTest ---"); +// log.info("data path=", appConfig.getDataPath()); + HgStoreSession graph0 = storeClient.openSession("hugegraph0"); + graph0.truncate(); + + for (int i = 0; i < 10; i++) { + HgOwnerKey key = toOwnerKey("owner-" + i, "ownerKey-" + i); + byte[] value0 = toBytes("g0 owner-" + i + ";ownerKey-" + i); + graph0.put(TABLE_NAME, key, value0); + } + + HgKvIterator iterator = graph0.scanIterator(TABLE_NAME); + Assert.assertTrue(iterator.hasNext()); + while (iterator.hasNext()) { + HgKvEntry entry = iterator.next(); + log.info("key:{} value:{}", toStr(entry.key()), toStr(entry.value())); + Assert.assertEquals(0, toStr(entry.value()).indexOf("g0")); + } + + } +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-client/src/test/java/org/apache/hugegraph/store/client/HgStoreClientBase.java b/hugegraph-store/hg-store-client/src/test/java/org/apache/hugegraph/store/client/HgStoreClientBase.java new file mode 100644 index 000000000..0d3f3c9cc --- /dev/null +++ b/hugegraph-store/hg-store-client/src/test/java/org/apache/hugegraph/store/client/HgStoreClientBase.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client; + +import org.apache.hugegraph.pd.client.PDClient; +import org.apache.hugegraph.pd.client.PDConfig; +import org.apache.hugegraph.store.HgStoreClient; +import org.apache.hugegraph.store.HgStoreSession; +import org.junit.Before; + +public class HgStoreClientBase { + + protected static String Graph_Name = "testGraphName"; + protected static String Table_Name = "testTableName"; + protected HgStoreClient storeClient; + protected PDClient pdClient; + + @Before + public void init() { + storeClient = HgStoreClient.create(PDConfig.of("127.0.0.1:8686") + .setEnableCache(true)); + pdClient = storeClient.getPdClient(); + + HgStoreSession session = storeClient.openSession(Graph_Name); + session.dropTable(Table_Name); + } +} diff --git a/hugegraph-store/hg-store-client/src/test/java/org/apache/hugegraph/store/client/HgStoreClientTest.java b/hugegraph-store/hg-store-client/src/test/java/org/apache/hugegraph/store/client/HgStoreClientTest.java new file mode 100644 index 000000000..2dd1166ec --- /dev/null +++ b/hugegraph-store/hg-store-client/src/test/java/org/apache/hugegraph/store/client/HgStoreClientTest.java @@ -0,0 +1,210 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client; + +import static org.apache.hugegraph.store.client.util.HgStoreClientConst.ALL_PARTITION_OWNER; + +import java.util.List; + +import org.apache.hugegraph.pd.client.PDClient; +import org.apache.hugegraph.pd.client.PDConfig; +import org.apache.hugegraph.pd.common.PDException; +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.store.HgKvEntry; +import org.apache.hugegraph.store.HgKvIterator; +import org.apache.hugegraph.store.HgKvStore; +import org.apache.hugegraph.store.HgOwnerKey; +import org.apache.hugegraph.store.HgStoreClient; +import org.apache.hugegraph.store.HgStoreSession; +import org.apache.hugegraph.store.util.HgStoreTestUtil; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class HgStoreClientTest { + + public final static byte[] EMPTY_BYTES = new byte[0]; + private static final String Graph_Name = "testGraphName"; + private static String Table_Name = "testTableName"; + private HgStoreClient storeClient; + private PDClient pdClient; + + @Before + public void init() { + storeClient = HgStoreClient.create(PDConfig.of("127.0.0.1:8686") + .setEnableCache(true)); + pdClient = storeClient.getPdClient(); + } + + + @Test + public void testPutData() { + HgStoreSession session = storeClient.openSession(Graph_Name); + long start = System.currentTimeMillis(); + HgStoreTestUtil.batchPut(session, Table_Name, "testKey", 100000); + + System.out.println("Time is " + (System.currentTimeMillis() - start)); + try (HgKvIterator iterator = session.scanIterator(Table_Name)) { + Assert.assertEquals(100000, HgStoreTestUtil.amountOf(iterator)); + } + } + + @Test + public void testScan() throws PDException { + + HgStoreSession session = storeClient.openSession(Graph_Name); + HgStoreTestUtil.batchPut(session, Table_Name, "testKey", 12); + + int count = 0; + byte[] position = null; + try (HgKvIterator iterator = session.scanIterator(Table_Name)) { + while (iterator.hasNext()) { + iterator.next(); + position = iterator.position(); + dumpPosition(position); + if (++count > 5) { + break; + } + } + } + + + System.out.println("--------------------------------"); + try (HgKvIterator iterator = session.scanIterator(Table_Name)) { + iterator.seek(position); + while (iterator.hasNext()) { + iterator.next(); + dumpPosition(iterator.position()); + } + } + + System.out.println("--------------------------------"); + + + byte[] start = new byte[]{0x0}; + byte[] end = new byte[]{-1}; + try (HgKvIterator iterator = session.scanIterator(Table_Name, + HgOwnerKey.of( + ALL_PARTITION_OWNER, + start), + HgOwnerKey.of( + ALL_PARTITION_OWNER, + end))) { + iterator.seek(position); + while (iterator.hasNext()) { + iterator.next(); + dumpPosition(iterator.position()); + } + } + } + + public void dumpPosition(byte[] b) { + byte[] buf = new byte[Long.BYTES]; + System.arraycopy(b, 0, buf, 0, Long.BYTES); + long storeId = HgStoreTestUtil.toLong(buf); + buf = new byte[Integer.BYTES]; + System.arraycopy(b, Long.BYTES, buf, 0, Integer.BYTES); + int partId = HgStoreTestUtil.toInt(buf); + String key = new String(b); + + System.out.println(" " + storeId + ", " + partId + ", " + key); + } + + // @Test + public void testDeleteData() { + Table_Name = "deleteData5"; + HgStoreSession session = storeClient.openSession(Graph_Name); + int ownerCode = 1; + HgStoreTestUtil.batchPut(session, Table_Name, "T", 10, (key) -> { + return HgStoreTestUtil.toOwnerKey(ownerCode, key); + } + ); + try (HgKvIterator iterators = session.scanIterator(Table_Name)) { +// while (iterators.hasNext()){ +// System.out.println(new String(iterators.next().key())); +// } + Assert.assertEquals(10, HgStoreTestUtil.amountOf(iterators)); + } + session.beginTx(); + session.deletePrefix(Table_Name, HgStoreTestUtil.toOwnerKey(ownerCode, "T")); + session.commit(); + + System.out.println("================================="); + try (HgKvIterator iterators = session.scanIterator(Table_Name)) { + Assert.assertEquals(0, HgStoreTestUtil.amountOf(iterators)); +// while (iterators.hasNext()){ +// System.out.println(new String(iterators.next().key())); +// } + } + } + + // @Test + public void testDropTable() throws PDException { + HgStoreSession session = storeClient.openSession(Graph_Name); + + String table1 = "Table1"; + session.createTable(table1); + HgStoreTestUtil.batchPut(session, table1, "testKey", 1000); + + try (HgKvIterator iterators = session.scanIterator(table1)) { + Assert.assertEquals(1000, HgStoreTestUtil.amountOf(iterators)); + } + + session.dropTable(table1); + try (HgKvIterator iterators = session.scanIterator(table1)) { + Assert.assertEquals(0, HgStoreTestUtil.amountOf(iterators)); + } + + deleteGraph(Graph_Name); + } + + + public void deleteGraph(String graphName) throws PDException { + HgStoreSession session = storeClient.openSession(graphName); + session.deleteGraph(graphName); + pdClient.delGraph(graphName); + + Metapb.Graph graph = null; + try { + graph = pdClient.getGraph(graphName); + } catch (PDException e) { + Assert.assertEquals(103, e.getErrorCode()); + } + Assert.assertNull(graph); + } + + // @Test + public void testScanPartition() throws PDException { + // testPutData(); + List partitions = pdClient.getPartitions(0, "DEFAULT/hugegraph/g"); + HgStoreSession session = storeClient.openSession("DEFAULT/hugegraph/g"); + for (Metapb.Partition partition : partitions) { + try (HgKvIterator iterators = session.scanIterator("g+v", + (int) (partition.getStartKey()), + (int) (partition.getEndKey()), + HgKvStore.SCAN_HASHCODE, + EMPTY_BYTES)) { + + System.out.println( + " " + partition.getId() + " " + HgStoreTestUtil.amountOf(iterators)); + } + } + } + + +} diff --git a/hugegraph-store/hg-store-client/src/test/java/org/apache/hugegraph/store/client/HgStoreNodeSessionTest.java b/hugegraph-store/hg-store-client/src/test/java/org/apache/hugegraph/store/client/HgStoreNodeSessionTest.java new file mode 100644 index 000000000..2edbec574 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/test/java/org/apache/hugegraph/store/client/HgStoreNodeSessionTest.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client; + +import static org.apache.hugegraph.store.util.HgStoreTestUtil.GRAPH_NAME; +import static org.apache.hugegraph.store.util.HgStoreTestUtil.amountOf; +import static org.apache.hugegraph.store.util.HgStoreTestUtil.batchPut; +import static org.apache.hugegraph.store.util.HgStoreTestUtil.println; + +import org.apache.hugegraph.store.HgStoreSession; +import org.junit.Assert; + +/** + * created on 2021/10/12 + */ +public class HgStoreNodeSessionTest { + private static final HgStoreNodeManager nodeManager = HgStoreNodeManager.getInstance(); + private static HgStoreNode node; + + // @BeforeClass + public static void init() { + node = nodeManager.addNode(GRAPH_NAME, + nodeManager.getNodeBuilder().setAddress("localhost:9080") + .build()); + } + + private static HgStoreSession getStoreSession() { + return node.openSession(GRAPH_NAME); + } + + private HgStoreNode getOneNode() { + return node; + } + + // @Test + public void truncate() { + println("--- test truncate ---"); + String tableName = "UNIT_TRUNCATE_1"; + String keyName = "KEY_TRUNCATE"; + + HgStoreSession session = getStoreSession(); + batchPut(session, tableName, keyName, 100); + Assert.assertEquals(100, amountOf(session.scanIterator(tableName))); + + String tableName2 = "UNIT_TRUNCATE_2"; + batchPut(session, tableName2, keyName, 100); + Assert.assertEquals(100, amountOf(session.scanIterator(tableName2))); + + + session.truncate(); + Assert.assertEquals(0, amountOf(session.scanIterator(tableName))); + Assert.assertEquals(0, amountOf(session.scanIterator(tableName2))); + } + +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-client/src/test/java/org/apache/hugegraph/store/client/HgStoreNodeStateTest.java b/hugegraph-store/hg-store-client/src/test/java/org/apache/hugegraph/store/client/HgStoreNodeStateTest.java new file mode 100644 index 000000000..62dd972dd --- /dev/null +++ b/hugegraph-store/hg-store-client/src/test/java/org/apache/hugegraph/store/client/HgStoreNodeStateTest.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client; + +// import org.junit.Test; + +import static org.apache.hugegraph.store.util.HgStoreTestUtil.GRAPH_NAME; +import static org.apache.hugegraph.store.util.HgStoreTestUtil.println; + +import java.util.concurrent.atomic.AtomicInteger; + + +public class HgStoreNodeStateTest { + private final static HgStoreNodeManager nodeManager = HgStoreNodeManager.getInstance(); + static int nodeNumber = 0; + + static { + registerNode(GRAPH_NAME, Long.valueOf(nodeNumber++), "localhost:9180"); + registerNode(GRAPH_NAME, Long.valueOf(nodeNumber++), "localhost:9280"); + registerNode(GRAPH_NAME, Long.valueOf(nodeNumber++), "localhost:9380"); + } + + private static void registerNode(String graphName, Long nodeId, String address) { + nodeManager.addNode(graphName, + nodeManager.getNodeBuilder().setNodeId(nodeId).setAddress(address) + .build()); + } + + + // @Test + public void isNodeHealthy() { + AtomicInteger count = new AtomicInteger(0); + + for (int i = 0; i < Integer.MAX_VALUE; i++) { + nodeManager.getStoreNodes(GRAPH_NAME) + .stream().map( + node -> { + println(node.getNodeId() + " " + node.getAddress() + "is " + + "healthy: " + + node.isHealthy()); + return node.isHealthy(); + } + ).count(); + + Thread.yield(); + + try { + Thread.sleep(10000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-client/src/test/java/org/apache/hugegraph/store/client/HgStoreNodeStreamTest.java b/hugegraph-store/hg-store-client/src/test/java/org/apache/hugegraph/store/client/HgStoreNodeStreamTest.java new file mode 100644 index 000000000..25fc59282 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/test/java/org/apache/hugegraph/store/client/HgStoreNodeStreamTest.java @@ -0,0 +1,185 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.client; + +import static org.apache.hugegraph.store.util.HgStoreTestUtil.GRAPH_NAME; +import static org.apache.hugegraph.store.util.HgStoreTestUtil.batchPut; +import static org.apache.hugegraph.store.util.HgStoreTestUtil.println; +import static org.apache.hugegraph.store.util.HgStoreTestUtil.toOwnerKey; + +import java.util.NoSuchElementException; + +import org.apache.hugegraph.store.HgKvEntry; +import org.apache.hugegraph.store.HgKvIterator; +import org.apache.hugegraph.store.HgStoreSession; +import org.junit.Assert; + +/** + * created on 2021/10/12 + */ +public class HgStoreNodeStreamTest { + private static final HgStoreNodeManager nodeManager = HgStoreNodeManager.getInstance(); + private static HgStoreNode node; + + // @BeforeClass + public static void init() { + node = nodeManager.addNode(GRAPH_NAME, + nodeManager.getNodeBuilder().setAddress("localhost:9080") + .build()); + } + + private static HgStoreSession getStoreSession() { + return node.openSession(GRAPH_NAME); + } + + private HgStoreNode getOneNode() { + return node; + } + + // @Test + public void scanIterator() { + + println("--- test scanIterator ---"); + String tableName = "UNIT_SCAN"; + String keyName = "SCAN-ITER"; + HgStoreSession session = getStoreSession(); + batchPut(session, tableName, keyName, 10000); + int count = 0; + int limit = 0; + int max = 99999; + HgKvIterator iterator = null; + + println("-- test 0 element --"); + iterator = + session.scanIterator(tableName, toOwnerKey("__SCAN-001"), toOwnerKey("__SCAN-100"), + 0); + Assert.assertFalse(iterator.hasNext()); + try { + iterator.next(); + Assert.fail(); + } catch (Throwable t) { + println("-- test NoSuchElementException --"); + Assert.assertTrue(t instanceof NoSuchElementException); + } + + println("-- test limit 1 to 10 --"); + for (int i = 1; i <= 10; i++) { + println("- limit " + i + " -"); + limit = i; + iterator = session.scanIterator(tableName, toOwnerKey(keyName + "-0"), + toOwnerKey(keyName + "-1"), limit); + count = 0; + while (iterator.hasNext()) { + count++; + HgKvEntry entry = iterator.next(); + println(entry); + } + Assert.assertEquals(limit, count); + } + + println("-- test limit 1 to 10 not enough --"); + for (int i = 1; i <= 10; i++) { + println("- limit " + i + " -"); + limit = i; + iterator = session.scanIterator(tableName, + toOwnerKey(keyName + "-00001"), + toOwnerKey(keyName + "-00005"), limit); + count = 0; + while (iterator.hasNext()) { + count++; + HgKvEntry entry = iterator.next(); + println(entry); + } + if (i <= 5) { + Assert.assertEquals(limit, count); + } else { + Assert.assertEquals(5, count); + } + + } + + println("-- test limit 0 (no limit) --"); + limit = 0; + iterator = session.scanIterator(tableName, toOwnerKey(keyName + "-0"), + toOwnerKey(keyName + "-1"), limit); + + count = 0; + while (iterator.hasNext()) { + count++; + HgKvEntry entry = iterator.next(); + if (count % 1000 == 0) { + println(entry); + } + if (count >= max) break; + } + Assert.assertEquals(10000, count); + + println("-- test scan all --"); + iterator = session.scanIterator(tableName); + count = 0; + while (iterator.hasNext()) { + count++; + HgKvEntry entry = iterator.next(); + if (count % 100 == 0) { + println(entry); + } + if (count >= max) break; + } + Assert.assertEquals(10000, count); + + println("-- test scan prefix --"); + iterator = session.scanIterator(tableName, toOwnerKey(keyName + "-01")); + count = 0; + while (iterator.hasNext()) { + count++; + HgKvEntry entry = iterator.next(); + if (count % 100 == 0) { + println(entry); + } + if (count >= max) break; + } + Assert.assertEquals(1000, count); + + } + + //// @Test + public void scanIteratorBenchmark() { + /*************** test no limit, with 10 millions **************/ + String tableName = "UNIT_HUGE"; + String keyName = "SCAN-HUGE"; + HgStoreSession session = getStoreSession(); + //batchPut(session,tableName, keyName, 10000000); + int count = 0; + int limit = 0; + HgKvIterator iterator = null; + + limit = 0; + iterator = session.scanIterator(tableName); + count = 0; + while (iterator.hasNext()) { + count++; + HgKvEntry entry = iterator.next(); + if (count % 100000 == 0) { + println(entry); + } + } + + Assert.assertEquals(10000000, count); + + } +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-client/src/test/java/org/apache/hugegraph/store/common/BitsTest.java b/hugegraph-store/hg-store-client/src/test/java/org/apache/hugegraph/store/common/BitsTest.java new file mode 100644 index 000000000..ff15db659 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/test/java/org/apache/hugegraph/store/common/BitsTest.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.common; + +import org.apache.hugegraph.store.term.Bits; +import org.junit.Assert; +// import org.junit.Test; + +public class BitsTest { + // @Test + public void test() { + for (int i = 0; i < Integer.MAX_VALUE; i = i + 10) { + byte[] val = new byte[4]; + Bits.putInt(val, 0, i); + int n = Bits.getInt(val, 0); + Assert.assertEquals(i, n); + } + } +} diff --git a/hugegraph-store/hg-store-client/src/test/java/org/apache/hugegraph/store/test/ChangeShardNumTest.java b/hugegraph-store/hg-store-client/src/test/java/org/apache/hugegraph/store/test/ChangeShardNumTest.java new file mode 100644 index 000000000..7c3d6a96c --- /dev/null +++ b/hugegraph-store/hg-store-client/src/test/java/org/apache/hugegraph/store/test/ChangeShardNumTest.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.test; + +import org.apache.hugegraph.pd.common.PDException; +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.store.HgKvEntry; +import org.apache.hugegraph.store.HgKvIterator; +import org.apache.hugegraph.store.HgStoreSession; +import org.apache.hugegraph.store.client.HgStoreClientBase; +import org.apache.hugegraph.store.util.HgStoreTestUtil; +import org.junit.Assert; +// import org.junit.Test; + +/** + * 测试修改副本数 + */ +public class ChangeShardNumTest extends HgStoreClientBase { + + // @Test + public void test3To1() throws PDException { + int number = 10000; + HgStoreSession session = storeClient.openSession(Graph_Name); + HgStoreTestUtil.batchPut(session, Table_Name, "testKey", number); + + try (HgKvIterator iterators = session.scanIterator(Table_Name)) { + Assert.assertEquals(number, HgStoreTestUtil.amountOf(iterators)); + } + + Metapb.PDConfig pdConfig = pdClient.getPDConfig(); + pdConfig = pdConfig.toBuilder().setShardCount(1).build(); + + pdClient.setPDConfig(pdConfig); + pdClient.balancePartition(); + } + + // @Test + public void test1To3() throws PDException { + int number = 10000; + HgStoreSession session = storeClient.openSession(Graph_Name); + HgStoreTestUtil.batchPut(session, Table_Name, "testKey", number); + + try (HgKvIterator iterators = session.scanIterator(Table_Name)) { + Assert.assertEquals(number, HgStoreTestUtil.amountOf(iterators)); + } + + Metapb.PDConfig pdConfig = pdClient.getPDConfig(); + pdConfig = pdConfig.toBuilder().setShardCount(3).build(); + + pdClient.setPDConfig(pdConfig); + pdClient.balancePartition(); + } + +} diff --git a/hugegraph-store/hg-store-client/src/test/java/org/apache/hugegraph/store/util/HgStoreTestUtil.java b/hugegraph-store/hg-store-client/src/test/java/org/apache/hugegraph/store/util/HgStoreTestUtil.java new file mode 100644 index 000000000..d935dc369 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/test/java/org/apache/hugegraph/store/util/HgStoreTestUtil.java @@ -0,0 +1,462 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.util; + +import java.io.Closeable; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; + +import org.apache.hugegraph.store.HgKvEntry; +import org.apache.hugegraph.store.HgKvIterator; +import org.apache.hugegraph.store.HgOwnerKey; +import org.apache.hugegraph.store.HgStoreSession; +import org.apache.hugegraph.store.client.util.HgStoreClientConst; +import org.apache.hugegraph.store.client.util.MetricX; + +import lombok.extern.slf4j.Slf4j; + +/** + * created on 2021/10/12 + */ +@Slf4j +public class HgStoreTestUtil { + + public final static String GRAPH_NAME = "default/hugegraph/g"; + public final static String GRAPH_NAME2 = "default/hugegraph2/g"; + public final static String TABLE_NAME = "unit-table"; + public final static String TABLE_NAME2 = "unit-table-2"; + + + public static Map batchPut(HgStoreSession session, String keyPrefix) { + return batchPut(session, keyPrefix, 100); + } + + public static Map batchPut(HgStoreSession session, String keyPrefix, + int loop) { + return batchPut(session, TABLE_NAME, keyPrefix, loop); + } + + public static Map batchPut(HgStoreSession session, String tableName + , String keyPrefix, int loop) { + return batchPut(session, tableName, keyPrefix, loop, 1, key -> toOwnerKey(key)); + } + + public static Map batchPut(HgStoreSession session, String tableName + , byte[] keyPrefix, int loop) { + return batchPut(session, tableName, keyPrefix, loop, 1, + (prefix, key) -> toOwnerKey(prefix, key)); + } + + public static Map batchPut(HgStoreSession session, String tableName + , String keyPrefix, int loop, int start) { + return batchPut(session, tableName, keyPrefix, loop, start, key -> toOwnerKey(key)); + } + + public static Map batchPut(HgStoreSession session, String tableName + , String keyPrefix, int loop, Function f) { + return batchPut(session, tableName, keyPrefix, loop, 1, f); + } + + public static Map batchPut(HgStoreSession session, String tableName + , String keyPrefix, int loop, int start, Function f) { + + Map res = new LinkedHashMap<>(); + + int length = String.valueOf(loop).length(); + + session.beginTx(); + for (int i = start; i <= loop; i++) { + + HgOwnerKey key = f.apply(keyPrefix + "-" + padLeftZeros(String.valueOf(i), length)); + + byte[] value = toBytes(keyPrefix + "-V-" + i); + res.put(key, value); + session.put(tableName, key, value); + + if ((i + 1) % 10000 == 0) { + println("commit: " + (i + 1)); + session.commit(); + session.beginTx(); + } + } + if (session.isTx()) { + session.commit(); + } + + return res; + } + + public static Map batchPut(HgStoreSession session, String tableName + , byte[] keyPrefix, int loop, int start, BiFunction f) { + + Map res = new LinkedHashMap<>(); + + int length = String.valueOf(loop).length(); + + session.beginTx(); + for (int i = start; i <= loop; i++) { + + HgOwnerKey key = f.apply(keyPrefix, padLeftZeros(String.valueOf(i), length)); + + byte[] value = toBytes(keyPrefix + "-V-" + i); + res.put(key, value); + session.put(tableName, key, value); + + if ((i + 1) % 10000 == 0) { + println("commit: " + (i + 1)); + session.commit(); + session.beginTx(); + } + } + if (session.isTx()) { + session.commit(); + } + + return res; + } + + /*---- common -----*/ + public static void printNum(List list, String title) { + if (list == null) return; + + println(title + " size: " + list.size()); + } + + public static int println(Iterable> iterators) { + AtomicInteger counter = new AtomicInteger(); + iterators.forEach(e -> counter.addAndGet(HgStoreTestUtil.println(e))); + return counter.get(); + } + + public static int println(HgKvIterator iterator) { + if (iterator == null) return 0; + + AtomicInteger counter = new AtomicInteger(); + + while (iterator.hasNext()) { + counter.incrementAndGet(); + print(iterator.next()); + println(" " + Arrays.toString(iterator.position())); + } + + iterator.close(); + + return counter.get(); + } + + public static void println(HgKvIterator iterator, Function mod) { + if (iterator == null) return; + int count = 0; + + while (iterator.hasNext()) { + count++; + if (mod.apply(count)) { + print(iterator.next()); + println(" " + Arrays.toString(iterator.position())); + } + } + + iterator.close(); + + } + + public static void printOwner(List list) { + if (list == null) return; + + for (HgOwnerKey entry : list) { + println(entry); + } + } + + public static void println(List list) { + if (list == null) return; + + for (HgKvEntry entry : list) { + println(entry); + } + } + + public static void println(List list, int mod) { + if (list == null) return; + + for (int i = 0; i < list.size(); i++) { + if (i % mod == 0) { + println(list.get(i)); + } + } + } + + public static void println(HgKvEntry kv) { + if (kv == null) { + System.out.println("null"); + return; + } + println("[ " + kv.code() + " " + toStr(kv.key()) + " : " + toStr(kv.value()) + " ]"); + } + + + public static void print(HgKvEntry kv) { + if (kv == null) { + System.out.println("null"); + return; + } + print("[ " + kv.code() + " " + toStr(kv.key()) + " : " + toStr(kv.value()) + " ]"); + } + + public static void println(HgOwnerKey key) { + if (key == null) { + System.out.println("null"); + return; + } + println("[ " + toInt(key.getOwner()) + " : " + toStr(key.getKey()) + " ]"); + } + + public static void println(String str) { + System.out.println(str); + } + + public static void println(Number num) { + System.out.println(num); + } + + public static void print(String str) { + System.out.print(str); + } + + public static HgOwnerKey toOwnerKey(byte[] prefix, String key) { + byte[] newKey = toBytes(prefix, key); + return new HgOwnerKey(getOwner(newKey), newKey); + } + + public static HgOwnerKey toOwnerKey(String key) { + return new HgOwnerKey(getOwner(key), toBytes(key)); + } + + public static HgOwnerKey toOwnerKey(byte[] key) { + return new HgOwnerKey(getOwner(key), key); + } + + private static byte[] getOwner(String key) { + return getOwner(toBytes(key)); + } + + private static byte[] getOwner(byte[] key) { + return toBytes(Arrays.hashCode(key)); + } + + public static HgOwnerKey toAllPartitionKey(String key) { + return HgOwnerKey.of(HgStoreClientConst.ALL_PARTITION_OWNER, toBytes(key)); + } + + public static HgOwnerKey toAllPartitionKey(byte[] prefix, String key) { + return HgOwnerKey.of(HgStoreClientConst.ALL_PARTITION_OWNER, toBytes(prefix, key)); + } + + public static HgOwnerKey toAllPartitionKey(byte[] prefix) { + return HgOwnerKey.of(HgStoreClientConst.ALL_PARTITION_OWNER, prefix); + } + + public static HgOwnerKey toOwnerKey(String owner, String key) { + return HgOwnerKey.of(toBytes(owner), toBytes(key)); + } + + public static HgOwnerKey toOwnerKey(int code, String key) { + return HgOwnerKey.of(code, toBytes(key)); + } + + public static String toStr(byte[] b) { + if (b == null) return ""; + if (b.length == 0) return ""; + return new String(b, StandardCharsets.UTF_8); + } + + public static byte[] toBytes(byte[] prefix, String str) { + if (str == null) return null; + byte[] buf = str.getBytes(StandardCharsets.UTF_8); + byte[] res = new byte[buf.length + prefix.length]; + System.arraycopy(prefix, 0, res, 0, prefix.length); + System.arraycopy(buf, 0, res, prefix.length, buf.length); + return res; + } + + public static byte[] toBytes(String str) { + if (str == null) return null; + return str.getBytes(StandardCharsets.UTF_8); + } + + public static byte[] toBytes(long l) { + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.putLong(l); + return buffer.array(); + } + + private static byte[] toBytes(final int i) { + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.putInt(i); + return buffer.array(); + } + + public static long toLong(byte[] bytes) { + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.put(bytes); + buffer.flip();//need flip + return buffer.getLong(); + } + + public static int toInt(byte[] bytes) { + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.put(bytes); + buffer.flip();//need flip + return buffer.getInt(); + } + + public static String padLeftZeros(String str, int n) { + return String.format("%1$" + n + "s", str).replace(' ', '0'); + } + + public static String toSuffix(int num, int length) { + return "-" + padLeftZeros(String.valueOf(num), length); + } + + public static int amountOf(List list) { + if (list == null) { + return 0; + } + return list.size(); + } + + public static int amountOf(Iterator iterator) { + if (iterator == null) return 0; + int count = 0; + while (iterator.hasNext()) { + Object obj = iterator.next(); + ++count; + } + return count; + } + + public static int OOMAmountOf(Iterator iterator) { + if (iterator == null) return 0; + int count = 0; + while (iterator.hasNext()) { + Object obj = iterator.next(); + ++count; + if (count % 10000 == 0) { + println(count); + sleeping(10); + } + if (count % 100000 == 0) { + break; + } + } + if (iterator instanceof Closeable) { + try { + ((Closeable) iterator).close(); + } catch (Exception e) { + + } + } + + return count; + } + + public static int amountIn(List iterators) { + return iterators.parallelStream().map(e -> HgStoreTestUtil.amountOf(e)) + .reduce(0, Integer::sum); + } + + + public static void sleeping(long time) { + try { + Thread.sleep(time); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + public static void runWaiting() { + while (true) { + sleeping(1000); + } + } + + public static void parallel_test(int threads, Runnable runner, + Consumer throwableConsumer) { + int threadsAmount = threads; + CountDownLatch countDownLatch = new CountDownLatch(threadsAmount); + ExecutorService pool = Executors.newFixedThreadPool(threadsAmount); + for (int i = 0; i < threadsAmount; i++) { + pool.submit(() -> { + try { + runner.run(); + } catch (Throwable t) { + throwableConsumer.accept(t); + } + countDownLatch.countDown(); + }); + } + + try { + countDownLatch.await(); + pool.shutdown(); + + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + + public static void repeatedly_test(int times, Runnable runner) { + MetricX metrics = MetricX.ofStart(); + + for (int j = 0; j < times; j++) { + try { + runner.run(); + Thread.sleep(1); + } catch (InterruptedException e) { + e.printStackTrace(); + break; + } catch (Throwable t) { + metrics.countFail(); + } + } + metrics.end(); + log.info("*************************************************"); + log.info("************* Repeatedly Test Completed **************"); + log.info("Total: {} sec.", metrics.past() / 1000); + log.info("Repeated: {} times.", times); + log.info("Fail: {} times.", metrics.getFailureCount()); + log.info("*************************************************"); + } + + +} + + diff --git a/hugegraph-store/hg-store-client/src/test/resources/log4j2.xml b/hugegraph-store/hg-store-client/src/test/resources/log4j2.xml new file mode 100644 index 000000000..0c37f08ea --- /dev/null +++ b/hugegraph-store/hg-store-client/src/test/resources/log4j2.xml @@ -0,0 +1,102 @@ + + + + + + + + logs + hg-store-client + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hugegraph-store/hg-store-client/src/test/resources/pd-server.yml b/hugegraph-store/hg-store-client/src/test/resources/pd-server.yml new file mode 100644 index 000000000..15b7b2322 --- /dev/null +++ b/hugegraph-store/hg-store-client/src/test/resources/pd-server.yml @@ -0,0 +1,54 @@ +spring: + application: + name: hugegraph-pd + +management: + metrics: + export: + prometheus: + enabled: true + endpoints: + web: + exposure: + include: "*" + +grpc: + port: 8686 + netty-server: + max-inbound-message-size: 100MB + +server: + port: 8620 + +pd: + + patrol-interval: 3000000 + data-path: tmp/8686 + # 最少节点数,少于该数字,集群停止入库 + initial-store-count: 1 + # 初始store列表,在列表内的store自动激活 + initial-store-list: 127.0.0.1:8501,127.0.0.1:8502,127.0.0.1:8503 + #initial-store-list: 127.0.0.1:8501 +raft: + address: 127.0.0.1:8610 + # raft集群 + peers-list: 127.0.0.1:8610 + # raft rpc读写超时时间,单位毫秒 + rpc-timeout: 10000 + # 快照生成时间间隔,单位秒 + snapshotInterval: 30000 + metrics: true +store: + # store心跳超时时间,超过该时间,认为store临时不可用,转移Leader到其他副本,单位秒 + keepAlive-timeout: 300 + # store下线时间。超过该时间,认为store永久不可用,分配副本到其他机器,单位秒 + max-down-time: 180000 +partition: + # 默认每个分区副本数 + default-shard-count: 3 + # 默认每机器最大副本数,初始分区数= store-max-shard-count * store-number / default-shard-count + store-max-shard-count: 1 + +discovery: + #客户端注册后,无心跳最长次数,超过后,之前的注册信息会被删除 + heartbeat-try-count: 3 \ No newline at end of file diff --git a/hugegraph-store/hg-store-common/.mvn/wrapper/MavenWrapperDownloader.java b/hugegraph-store/hg-store-common/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 000000000..574feef6b --- /dev/null +++ b/hugegraph-store/hg-store-common/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,121 @@ +/* + * Copyright 2007-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.6"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = + "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the + * wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if (mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if (mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if (!outputFile.getParentFile().exists()) { + if (!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/hugegraph-store/hg-store-common/.mvn/wrapper/maven-wrapper.properties b/hugegraph-store/hg-store-common/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 000000000..a0c0a3761 --- /dev/null +++ b/hugegraph-store/hg-store-common/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,18 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to You under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/hugegraph-store/hg-store-common/mvnw b/hugegraph-store/hg-store-common/mvnw new file mode 100644 index 000000000..41c0f0c23 --- /dev/null +++ b/hugegraph-store/hg-store-common/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/hugegraph-store/hg-store-common/mvnw.cmd b/hugegraph-store/hg-store-common/mvnw.cmd new file mode 100644 index 000000000..86115719e --- /dev/null +++ b/hugegraph-store/hg-store-common/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/hugegraph-store/hg-store-common/pom.xml b/hugegraph-store/hg-store-common/pom.xml new file mode 100644 index 000000000..3044a202b --- /dev/null +++ b/hugegraph-store/hg-store-common/pom.xml @@ -0,0 +1,33 @@ + + + + + + 4.0.0 + + + org.apache.hugegraph + hugegraph-store-root + ${revision} + ../pom.xml + + + hg-store-common + \ No newline at end of file diff --git a/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/buffer/ByteBufferAllocator.java b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/buffer/ByteBufferAllocator.java new file mode 100644 index 000000000..6e766bad2 --- /dev/null +++ b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/buffer/ByteBufferAllocator.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.buffer; + +import java.nio.ByteBuffer; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + + +public class ByteBufferAllocator { + // 每个bytebuffer大小 + final int capacity; + // buffer 最大个数 + final int maxCount; + // 空闲队列 + final BlockingQueue freeQueue = new LinkedBlockingQueue<>(); + // 当前数量 + AtomicInteger totalCount; + + public ByteBufferAllocator(int cap, int count) { + this.capacity = cap; + this.maxCount = count; + this.totalCount = new AtomicInteger(0); + } + + public ByteBuffer get() throws InterruptedException { + ByteBuffer buffer = null; + while (buffer == null) { + if (freeQueue.size() > 0) { + buffer = freeQueue.poll(); + } else if (totalCount.get() < maxCount) { + buffer = ByteBuffer.allocate(capacity); + totalCount.incrementAndGet(); + } else { + buffer = freeQueue.poll(1, TimeUnit.SECONDS); + } + } + return buffer; + } + + public void release(ByteBuffer buffer) { + if (freeQueue.size() < maxCount) { + buffer.clear(); + freeQueue.add(buffer); + } + } +} diff --git a/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/buffer/KVByteBuffer.java b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/buffer/KVByteBuffer.java new file mode 100644 index 000000000..da9025e3c --- /dev/null +++ b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/buffer/KVByteBuffer.java @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.buffer; + +import java.nio.ByteBuffer; + +public class KVByteBuffer { + ByteBuffer buffer; + + public KVByteBuffer(int capacity) { + buffer = ByteBuffer.allocate(capacity); + } + + public KVByteBuffer(byte[] buffer) { + this.buffer = ByteBuffer.wrap(buffer); + } + + public KVByteBuffer(ByteBuffer buffer) { + this.buffer = buffer; + } + + public void clear() { + this.buffer.clear(); + } + + public KVByteBuffer flip() { + buffer.flip(); + return this; + } + + public ByteBuffer getBuffer() { + return buffer; + } + + public ByteBuffer copyBuffer() { + byte[] buf = new byte[buffer.position()]; + System.arraycopy(buffer.array(), 0, buf, 0, buffer.position()); + return ByteBuffer.wrap(buf); + } + + public void put(byte data) { + buffer.put(data); + } + + public void put(byte[] data) { + if (data != null) { + buffer.putInt(data.length); + buffer.put(data); + } + } + + public byte[] getBytes() { + int len = buffer.getInt(); + byte[] data = new byte[len]; + buffer.get(data); + return data; + } + + public byte get() { + return buffer.get(); + } + + public void putInt(int data) { + buffer.putInt(data); + } + + public int getInt() { + return buffer.getInt(); + } + + public byte[] array() { + return this.buffer.array(); + } + + public int position() { + return this.buffer.position(); + } + + public final boolean hasRemaining() { + return this.buffer.hasRemaining(); + } +} diff --git a/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/term/Bits.java b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/term/Bits.java new file mode 100644 index 000000000..c38d4b679 --- /dev/null +++ b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/term/Bits.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.term; + +import java.nio.ByteBuffer; + +public class Bits { + /** + * 大头字节序写入short + */ + public static void putShort(byte[] buf, int offSet, int x) { + buf[offSet] = (byte) (x >> 8); + buf[offSet + 1] = (byte) (x); + } + + public static void putInt(byte[] buf, int offSet, int x) { + buf[offSet] = (byte) (x >> 24); + buf[offSet + 1] = (byte) (x >> 16); + buf[offSet + 2] = (byte) (x >> 8); + buf[offSet + 3] = (byte) (x); + } + + /** + * 大头字节序读取short + */ + public static int getShort(byte[] buf, int offSet) { + int x = buf[offSet] & 0xff; + x = (x << 8) + (buf[offSet + 1] & 0xff); + return x; + } + + public static int getInt(byte[] buf, int offSet) { + int x = (buf[offSet] << 24) + + ((buf[offSet + 1] & 0xff) << 16) + + ((buf[offSet + 2] & 0xff) << 8) + + (buf[offSet + 3] & 0xff); + return x; + } + + public static void put(byte[] buf, int offSet, byte[] srcBuf) { + System.arraycopy(srcBuf, 0, buf, offSet, srcBuf.length); + } + + public static int toInt(byte[] bytes) { + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.put(bytes); + buffer.flip();//need flip + return buffer.getInt(); + } +} diff --git a/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/term/HgPair.java b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/term/HgPair.java new file mode 100644 index 000000000..cb9bad27d --- /dev/null +++ b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/term/HgPair.java @@ -0,0 +1,142 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.term; + +import java.io.Serializable; +import java.util.Objects; + +/** + * Copy from javafx.util:Pair + * TODO: refer license later, ?% match, maybe refer to avafx.util:Pair + *

+ * created on 2021/10/15 + */ +public class HgPair implements Serializable { + + /** + * Key of this Pair. + */ + private K key; + /** + * Value of this Pair. + */ + private V value; + + public HgPair() { + + } + + /** + * Creates a new pair + * + * @param key The key for this pair + * @param value The value to use for this pair + */ + public HgPair(K key, V value) { + this.key = key; + this.value = value; + } + + /** + * Gets the key for this pair. + * + * @return key for this pair + */ + public K getKey() { + return key; + } + + public void setKey(K key) { + this.key = key; + } + + /** + * Gets the value for this pair. + * + * @return value for this pair + */ + public V getValue() { + return value; + } + + public void setValue(V value) { + this.value = value; + } + + /** + *

String representation of this + * Pair.

+ * + *

The default name/value delimiter '=' is always used.

+ * + * @return String representation of this Pair + */ + @Override + public String toString() { + return key + "=" + value; + } + + /** + *

Generate a hash code for this Pair.

+ * + *

The hash code is calculated using both the name and + * the value of the Pair.

+ * + * @return hash code for this Pair + */ + @Override + public int hashCode() { + // name's hashCode is multiplied by an arbitrary prime number (13) + // in order to make sure there is a difference in the hashCode between + // these two parameters: + // name: a value: aa + // name: aa value: a + return key.hashCode() * 13 + (value == null ? 0 : value.hashCode()); + } + + /** + *

Test this Pair for equality with another + * Object.

+ * + *

If the Object to be tested is not a + * Pair or is null, then this method + * returns false.

+ * + *

Two Pairs are considered equal if and only if + * both the names and values are equal.

+ * + * @param o the Object to test for + * equality with this Pair + * @return true if the given Object is + * equal to this Pair else false + */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o instanceof HgPair) { + HgPair pair = (HgPair) o; + if (!Objects.equals(key, pair.key)) { + return false; + } + return Objects.equals(value, pair.value); + } + return false; + } +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/term/HgTriple.java b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/term/HgTriple.java new file mode 100644 index 000000000..04cd05359 --- /dev/null +++ b/hugegraph-store/hg-store-common/src/main/java/org/apache/hugegraph/store/term/HgTriple.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.term; + +import java.util.Objects; + +/** + * created on 2021/10/19 + */ +public class HgTriple { + private final X x; + private final Y y; + private final Z z; + private int hash = -1; + + public HgTriple(X x, Y y, Z z) { + this.x = x; + this.y = y; + this.z = z; + } + + public X getX() { + return x; + } + + public Y getY() { + return y; + } + + public Z getZ() { + return z; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + HgTriple hgTriple = (HgTriple) o; + return Objects.equals(x, hgTriple.x) && Objects.equals(y, hgTriple.y) && + Objects.equals(z, hgTriple.z); + } + + @Override + public int hashCode() { + if (hash == -1) { + hash = Objects.hash(x, y, z); + } + return this.hash; + } + + @Override + public String toString() { + return "HgTriple{" + + "x=" + x + + ", y=" + y + + ", z=" + z + + '}'; + } +} diff --git a/hugegraph-store/hg-store-core/build.sh b/hugegraph-store/hg-store-core/build.sh new file mode 100755 index 000000000..4265ff844 --- /dev/null +++ b/hugegraph-store/hg-store-core/build.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to You under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +set -e + + +export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64 +export MAVEN_HOME=/usr/share/maven +export PATH=$JAVA_HOME/bin:$MAVEN_HOME/bin:$PATH + +mvn clean compile + +rm -rf ~/tmp + + + diff --git a/hugegraph-store/hg-store-core/pom.xml b/hugegraph-store/hg-store-core/pom.xml new file mode 100644 index 000000000..ee71b0f63 --- /dev/null +++ b/hugegraph-store/hg-store-core/pom.xml @@ -0,0 +1,133 @@ + + + + + 4.0.0 + + org.apache.hugegraph + hugegraph-store-root + ${revision} + ../pom.xml + + + hg-store-core + + + + + org.projectlombok + lombok + + + org.apache.logging.log4j + log4j-slf4j-impl + + + org.json + json + 20210307 + + + + com.alipay.sofa + jraft-core + 1.3.13 + + + org.rocksdb + rocksdbjni + + + + + + + + net.java.dev.jna + jna + 5.7.0 + + + + io.protostuff + protostuff-core + 1.6.0 + + + io.protostuff + protostuff-runtime + 1.6.0 + + + com.alibaba + fastjson + 1.2.83 + + + + + org.apache.hugegraph + hg-store-rocksdb + ${revision} + + + org.apache.logging.log4j + log4j-slf4j-impl + + + + + org.apache.hugegraph + hg-pd-client + ${revision} + compile + + + + org.apache.hugegraph + hg-store-common + ${revision} + + + org.apache.hugegraph + hugegraph-core + ${hugegraph.server.version} + + + com.google.code.gson + gson + 2.8.9 + + + org.apache.hugegraph + hg-store-grpc + + + org.apache.hugegraph + hg-store-client + test + + + + \ No newline at end of file diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/HeartbeatService.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/HeartbeatService.java new file mode 100644 index 000000000..280a9b0bf --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/HeartbeatService.java @@ -0,0 +1,386 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.apache.hugegraph.pd.common.PDException; +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.pd.grpc.Pdpb; +import org.apache.hugegraph.store.meta.Partition; +import org.apache.hugegraph.store.meta.PartitionRole; +import org.apache.hugegraph.store.meta.Store; +import org.apache.hugegraph.store.meta.StoreMetadata; +import org.apache.hugegraph.store.options.HgStoreEngineOptions; +import org.apache.hugegraph.store.options.RaftRocksdbOptions; +import org.apache.hugegraph.store.pd.PdProvider; +import org.apache.hugegraph.store.util.IpUtil; +import org.apache.hugegraph.store.util.Lifecycle; +import org.rocksdb.MemoryUsageType; + +import com.alipay.sofa.jraft.entity.PeerId; +import com.alipay.sofa.jraft.util.Utils; + +import lombok.extern.slf4j.Slf4j; + +/** + * Register and heartbeat, Keep the system online + */ +@Slf4j +public class HeartbeatService implements Lifecycle, PartitionStateListener { + + private static final int MAX_HEARTBEAT_RETRY_COUNT = 5; // 心跳重试次数 + private static final int REGISTER_RETRY_INTERVAL = 1; //注册重试时间间隔,单位秒 + private final HgStoreEngine storeEngine; + private final List stateListeners; + private final Object partitionThreadLock = new Object(); + private final Object storeThreadLock = new Object(); + private HgStoreEngineOptions options; + private PdProvider pdProvider; + private Store storeInfo; + private Metapb.ClusterStats clusterStats; + private StoreMetadata storeMetadata; + // 心跳失败次数 + private int heartbeatFailCount = 0; + private int reportErrCount = 0; + // 线程休眠时间 + private volatile int timerNextDelay = 1000; + private boolean terminated = false; + + public HeartbeatService(HgStoreEngine storeEngine) { + this.storeEngine = storeEngine; + stateListeners = Collections.synchronizedList(new ArrayList()); + } + + @Override + public boolean init(HgStoreEngineOptions opts) { + this.options = opts; + storeInfo = storeMetadata.getStore(); + if (storeInfo == null) { + storeInfo = new Store(); + } + storeInfo.setStoreAddress(options.getGrpcAddress()); + storeInfo.setPdAddress(options.getPdAddress()); + storeInfo.setRaftAddress(options.getRaftAddress()); + storeInfo.setState(Metapb.StoreState.Unknown); + storeInfo.setLabels(options.getLabels()); + storeInfo.setCores(Runtime.getRuntime().availableProcessors()); + storeInfo.setDeployPath(HeartbeatService.class.getResource("/").getPath()); + storeInfo.setDataPath(options.getDataPath()); + this.pdProvider = options.getPdProvider(); + + new Thread(new Runnable() { + @Override + public void run() { + doStoreHeartbeat(); + } + }, "heartbeat").start(); + + new Thread(new Runnable() { + @Override + public void run() { + doPartitionHeartbeat(); + } + }, " partition-hb").start(); + return true; + } + + public HeartbeatService addStateListener(HgStoreStateListener stateListener) { + stateListeners.add(stateListener); + return this; + } + + + public Store getStoreInfo() { + return storeInfo; + } + + public void setStoreMetadata(StoreMetadata storeMetadata) { + this.storeMetadata = storeMetadata; + } + + // 集群是否准备就绪 + public boolean isClusterReady() { + return clusterStats.getState() == Metapb.ClusterState.Cluster_OK; + } + + /** + * 服务状态有四种 + * 就绪,在线、离线、死亡(从集群排除) + */ + protected void doStoreHeartbeat() { + while (!terminated) { + try { + switch (storeInfo.getState()) { + case Unknown: + case Offline: + registerStore(); + break; + case Up: + storeHeartbeat(); + monitorMemory(); + break; + case Tombstone: + break; + + } + synchronized (storeThreadLock) { + storeThreadLock.wait(timerNextDelay); + } + } catch (Throwable e) { + log.error("heartbeat error: ", e); + } + } + } + + protected void doPartitionHeartbeat() { + while (!terminated) { + try { + partitionHeartbeat(); + + } catch (Exception e) { + log.error("doPartitionHeartbeat error: ", e); + } + try { + synchronized (partitionThreadLock) { + partitionThreadLock.wait(options.getPartitionHBInterval() * 1000L); + } + } catch (InterruptedException e) { + log.error("doPartitionHeartbeat error: ", e); + } + } + } + + protected void registerStore() { + try { + // 注册 store,初次注册 PD 产生 id,自动给 storeinfo 赋值 + this.storeInfo.setStoreAddress(IpUtil.getNearestAddress(options.getGrpcAddress())); + this.storeInfo.setRaftAddress(IpUtil.getNearestAddress(options.getRaftAddress())); + + long storeId = pdProvider.registerStore(this.storeInfo); + if (storeId != 0) { + storeInfo.setId(storeId); + storeMetadata.save(storeInfo); + this.clusterStats = pdProvider.getClusterStats(); + if (clusterStats.getState() == Metapb.ClusterState.Cluster_OK) { + timerNextDelay = options.getStoreHBInterval() * 1000; + } else { + timerNextDelay = REGISTER_RETRY_INTERVAL * 1000; + } + log.info("Register Store id= {} successfully. store = {}, clusterStats {}", + storeInfo.getId(), storeInfo, this.clusterStats); + // 监听 partition 消息 + pdProvider.startHeartbeatStream(error -> { + onStateChanged(Metapb.StoreState.Offline); + timerNextDelay = REGISTER_RETRY_INTERVAL * 1000; + wakeupHeartbeatThread(); + log.error("Connection closed. The store state changes to {}", + Metapb.StoreState.Offline); + }); + onStateChanged(Metapb.StoreState.Up); + } else { + timerNextDelay = REGISTER_RETRY_INTERVAL * 1000 / 2; + } + } catch (PDException e) { + int exceptCode = e.getErrorCode(); + if (exceptCode == Pdpb.ErrorType.STORE_ID_NOT_EXIST_VALUE) { + log.error( + "The store ID {} does not match the PD. Check that the correct PD is " + + "connected, " + + "and then delete the store ID!!!", + storeInfo.getId()); + System.exit(-1); + } else if (exceptCode == Pdpb.ErrorType.STORE_HAS_BEEN_REMOVED_VALUE) { + log.error("The store ID {} has been removed, please delete all data and restart!", + storeInfo.getId()); + System.exit(-1); + } else if (exceptCode == Pdpb.ErrorType.STORE_PROHIBIT_DUPLICATE_VALUE) { + log.error( + "The store ID {} maybe duplicated, please check out store raft address " + + "and restart later!", + storeInfo.getId()); + System.exit(-1); + } + } + } + + protected void storeHeartbeat() { + if (log.isDebugEnabled()) { + log.debug("storeHeartbeat ... "); + } + Metapb.ClusterStats clusterStats = null; + try { + clusterStats = pdProvider.storeHeartbeat(this.storeInfo); + } catch (PDException e) { + int exceptCode = e.getErrorCode(); + if (exceptCode == Pdpb.ErrorType.STORE_ID_NOT_EXIST_VALUE) { + log.error("The store ID {} does not match the PD. Check that the correct PD is " + + "connected, and then delete the store ID!!!", storeInfo.getId()); + System.exit(-1); + } else if (exceptCode == Pdpb.ErrorType.STORE_HAS_BEEN_REMOVED_VALUE) { + log.error("The store ID {} has been removed, please delete all data and restart!", + storeInfo.getId()); + System.exit(-1); + } + } + if (clusterStats.getState().getNumber() >= Metapb.ClusterState.Cluster_Fault.getNumber()) { + if (reportErrCount == 0) { + log.info("The cluster is abnormal, {}", clusterStats); + } + reportErrCount = (++reportErrCount) % 30; + } + + if (clusterStats.getState() == Metapb.ClusterState.Cluster_OK) { + timerNextDelay = options.getStoreHBInterval() * 1000; + } else { + timerNextDelay = REGISTER_RETRY_INTERVAL * 1000; + } + + if (clusterStats.getState() == Metapb.ClusterState.Cluster_Fault) { + heartbeatFailCount++; + } else { + heartbeatFailCount = 0; + this.clusterStats = clusterStats; + } + if (heartbeatFailCount > MAX_HEARTBEAT_RETRY_COUNT) { + onStateChanged(Metapb.StoreState.Offline); + timerNextDelay = REGISTER_RETRY_INTERVAL * 1000; + this.clusterStats = clusterStats; + log.error("Store heart beat failure. The store state changes to {}", + Metapb.StoreState.Offline); + } + } + + protected synchronized void onStateChanged(Metapb.StoreState newState) { + Utils.runInThread(() -> { + Metapb.StoreState oldState = this.storeInfo.getState(); + this.storeInfo.setState(newState); + stateListeners.forEach((e) -> + e.stateChanged(this.storeInfo, oldState, newState)); + }); + } + + + protected void partitionHeartbeat() { + if (storeEngine == null) { + return; + } + + List partitions = storeEngine.getLeaderPartition(); + final List statsList = new ArrayList<>(partitions.size()); + + Metapb.Shard localLeader = Metapb.Shard.newBuilder() + .setStoreId( + storeEngine.getPartitionManager().getStore() + .getId()) + .setRole(Metapb.ShardRole.Leader) + .build(); + // 获取各个 shard 信息。 + for (PartitionEngine partition : partitions) { + Metapb.PartitionStats.Builder stats = Metapb.PartitionStats.newBuilder(); + stats.setId(partition.getGroupId()); + stats.addAllGraphName(partition.getPartitions().keySet()); + stats.setLeaderTerm(partition.getLeaderTerm()); + stats.setConfVer(partition.getShardGroup().getConfVersion()); + stats.setLeader(localLeader); + + stats.addAllShard(partition.getShardGroup().getMetaPbShard()); + + // shard 状态 + List shardStats = new ArrayList<>(); + Map aliveShards = partition.getAlivePeers(); + // 统计 shard 状态 + partition.getShardGroup().getShards().forEach(shard -> { + Metapb.ShardState state = Metapb.ShardState.SState_Normal; + if (! aliveShards.containsKey(shard.getStoreId())) { + state = Metapb.ShardState.SState_Offline; + } + + shardStats.add(Metapb.ShardStats.newBuilder() + .setStoreId(shard.getStoreId()) + .setRole(shard.getRole()) + .setState(state).build()); + }); + stats.addAllShardStats(shardStats); + stats.setTimestamp(System.currentTimeMillis()); + + statsList.add(stats.build()); + } + // 发送心跳 + if (statsList.size() > 0) { + pdProvider.partitionHeartbeat(statsList); + } + + } + + public void monitorMemory() { + + try { + Map mems = + storeEngine.getBusinessHandler().getApproximateMemoryUsageByType(null); + + if (mems.get(MemoryUsageType.kCacheTotal) > + RaftRocksdbOptions.getWriteCacheCapacity() * 0.9 && + mems.get(MemoryUsageType.kMemTableUnFlushed) > + RaftRocksdbOptions.getWriteCacheCapacity() * 0.1) { + // storeEngine.getBusinessHandler().flushAll(); + log.warn("Less memory, start flush dbs, {}", mems); + } + } catch (Exception e) { + log.error("MonitorMemory exception {}", e); + } + } + + @Override + public void shutdown() { + log.info("HeartbeatService shutdown"); + terminated = true; + synchronized (partitionThreadLock) { + partitionThreadLock.notify(); + } + } + + @Override + public void partitionRoleChanged(Partition partition, PartitionRole newRole) { + if (newRole == PartitionRole.LEADER) { + // leader 发生改变,激活心跳 + synchronized (partitionThreadLock) { + partitionThreadLock.notifyAll(); + } + } + } + + @Override + public void partitionShardChanged(Partition partition, List oldShards, + List newShards) { + if (partition.isLeader()) { + synchronized (partitionThreadLock) { + partitionThreadLock.notifyAll(); + } + } + } + + private void wakeupHeartbeatThread() { + synchronized (storeThreadLock) { + storeThreadLock.notifyAll(); + } + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/HgStoreEngine.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/HgStoreEngine.java new file mode 100644 index 000000000..ff1a3e9ad --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/HgStoreEngine.java @@ -0,0 +1,729 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.hugegraph.pd.common.PDException; +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.rocksdb.access.RocksDBFactory; +import org.apache.hugegraph.store.business.BusinessHandler; +import org.apache.hugegraph.store.business.BusinessHandlerImpl; +import org.apache.hugegraph.store.business.DataMover; +import org.apache.hugegraph.store.cmd.HgCmdClient; +import org.apache.hugegraph.store.cmd.HgCmdProcessor; +import org.apache.hugegraph.store.cmd.UpdatePartitionRequest; +import org.apache.hugegraph.store.cmd.UpdatePartitionResponse; +import org.apache.hugegraph.store.meta.Partition; +import org.apache.hugegraph.store.meta.PartitionManager; +import org.apache.hugegraph.store.meta.ShardGroup; +import org.apache.hugegraph.store.meta.Store; +import org.apache.hugegraph.store.metric.HgMetricService; +import org.apache.hugegraph.store.options.HgStoreEngineOptions; +import org.apache.hugegraph.store.options.PartitionEngineOptions; +import org.apache.hugegraph.store.pd.DefaultPdProvider; +import org.apache.hugegraph.store.pd.FakePdServiceProvider; +import org.apache.hugegraph.store.pd.PdProvider; +import org.apache.hugegraph.store.raft.RaftClosure; +import org.apache.hugegraph.store.raft.RaftOperation; +import org.apache.hugegraph.store.util.HgRaftError; +import org.apache.hugegraph.store.util.Lifecycle; + +import com.alipay.sofa.jraft.JRaftUtils; +import com.alipay.sofa.jraft.Status; +import com.alipay.sofa.jraft.conf.Configuration; +import com.alipay.sofa.jraft.core.NodeMetrics; +import com.alipay.sofa.jraft.entity.PeerId; +import com.alipay.sofa.jraft.option.RpcOptions; +import com.alipay.sofa.jraft.rpc.RaftRpcServerFactory; +import com.alipay.sofa.jraft.rpc.RpcServer; +import com.alipay.sofa.jraft.util.Endpoint; + +import lombok.extern.slf4j.Slf4j; + +/** + * The core class of the storage engine, initializing PD client and raft client + */ +@Slf4j +public class HgStoreEngine implements Lifecycle, HgStoreStateListener { + private final static HgStoreEngine instance = new HgStoreEngine(); + // 分区raft引擎,key为GraphName_PartitionID + private final Map partitionEngines = new ConcurrentHashMap<>(); + private RpcServer rpcServer; + private HgStoreEngineOptions options; + private PdProvider pdProvider; + private HgCmdClient hgCmdClient; + private PartitionManager partitionManager; + private HeartbeatService heartbeatService; + private BusinessHandler businessHandler; + private HgMetricService metricService; + private DataMover dataMover; + + private static ConcurrentHashMap engineLocks = new ConcurrentHashMap<>(); + + public static HgStoreEngine getInstance() { + return instance; + } + + /** + * 1、读取StoreId,向pd注册,初次注册由PD生成StoreId,存储到本地 + * 2、注册成功,启动raft服务 + * 3、定时发送Store心跳和Partition心跳,与PD保持联系 + * + * @param opts + * @return + */ + @Override + public synchronized boolean init(final HgStoreEngineOptions opts) { + if (rpcServer != null) { + log.info("HgStoreEngine already started."); + return true; + } + + this.options = opts; + + BusinessHandlerImpl.initRocksdb(opts.getRocksdbConfig(), getRocksdbListener()); + + if (opts.isFakePD()) { + pdProvider = new FakePdServiceProvider(opts.getFakePdOptions()); + } else { + pdProvider = new DefaultPdProvider(opts.getPdAddress()); + pdProvider.addPartitionInstructionListener(new PartitionInstructionProcessor(this)); + } + options.setPdProvider(pdProvider); + + partitionManager = new PartitionManager(pdProvider, opts); + + partitionManager.addPartitionChangedListener(new PartitionChangedListener()); + + businessHandler = new BusinessHandlerImpl(partitionManager); + // 需要businessHandler 初始化后 + partitionManager.load(); + + rpcServer = createRaftRpcServer(opts.getRaftAddress()); + + hgCmdClient = new HgCmdClient(); + hgCmdClient.init(new RpcOptions(), (graphName, ptId) -> { + // 分裂的时候,还未及时的上报pd + if (getPartitionEngine(ptId) != null) { + return getPartitionEngine(ptId).waitForLeader( + options.getWaitLeaderTimeout() * 1000); + } else { + // 可能出现跨分区的迁移 + Metapb.Shard shard = pdProvider.getPartitionLeader(graphName, ptId); + return JRaftUtils.getEndPoint( + pdProvider.getStoreByID(shard.getStoreId()).getRaftAddress()); + } + }); + + heartbeatService = new HeartbeatService(this); + heartbeatService.setStoreMetadata(partitionManager.getStoreMetadata()); + heartbeatService.addStateListener(this).init(options); + + metricService = HgMetricService.getInstance(); + metricService.setHgStoreEngine(this).init(null); + + dataMover = opts.getDataTransfer(); + if (dataMover != null) { + this.dataMover.setBusinessHandler(this.businessHandler); + this.dataMover.setCmdClient(hgCmdClient); + } + return true; + } + + /** + * 创建raft rpc server,用于store之间通讯 + */ + private RpcServer createRaftRpcServer(String raftAddr) { + Endpoint endpoint = JRaftUtils.getEndPoint(raftAddr); + RpcServer rpcServer = RaftRpcServerFactory.createRaftRpcServer(endpoint, + JRaftUtils.createExecutor( + "RAFT-RPC-", + options.getRaftRpcThreadPoolSize()), + null); + HgCmdProcessor.registerProcessor(rpcServer, this); + rpcServer.init(null); + return rpcServer; + } + + @Override + public void shutdown() { + if (rpcServer == null) { + return; + } + partitionEngines.forEach((k, v) -> { + v.shutdown(); + }); + partitionEngines.clear(); + rpcServer.shutdown(); + // HgStoreEngine.init function check rpcServer whether is null, skipped if the instance + // exists even shut down. + rpcServer = null; + heartbeatService.shutdown(); + metricService.shutdown(); + // close all db session + RocksDBFactory.getInstance().releaseAllGraphDB(); + } + + + public void snapshotForTest() { + partitionEngines.forEach((k, v) -> { + v.snapshot(); + }); + } + + /** + * Store注册状态发生改变 + */ + @Override + public void stateChanged(Store store, Metapb.StoreState oldState, Metapb.StoreState newState) { + log.info("stateChanged, oldState {}, newState {}", oldState, newState); + if (newState == Metapb.StoreState.Up) { + // 状态变为上线,记录store信息 + partitionManager.setStore(store); + partitionManager.loadPartition(); + restoreLocalPartitionEngine(); + } + } + + + /** + * 恢复本地的PartitionEngine,恢复PD返回的分区信息 + * 1、需要检查本次保存的分区,删除作废的分区 + */ + public void restoreLocalPartitionEngine() { + try { + if (!options.isFakePD()) { // FakePD模式不需要同步 + partitionManager.syncPartitionsFromPD(partition -> { + log.warn( + "The local partition information is inconsistent with the PD server. " + + "Please delete the redundant data manually, {}", partition); + }); + } + partitionManager.getPartitions().forEach((k, g) -> { + g.forEach((id, p) -> { + try { + createPartitionEngine(p); + } catch (Exception e) { + log.error("Partition {}-{} restore exception {}", p.getGraphName(), + p.getId(), e); + } + }); + }); + } catch (PDException e) { + log.error("HgStoreEngine restoreLocalPartitionEngine error {}", e); + } + } + + /** + * 收到store raft addr 变更,需要重新创建raft group + * + * @param storeId 变更的store id + */ + public void rebuildRaftGroup(long storeId) { + partitionEngines.forEach((partId, engine) -> { + try { + var partitions = pdProvider.getPartitionsByStore(storeId); + if (partitions.size() > 0) { + var shards = pdProvider.getShardGroup(partId).getShardsList(); + if (shards.stream().anyMatch(s -> s.getStoreId() == storeId)) { + var peers = partitionManager.shards2Peers(shards); + Configuration initConf = engine.getOptions().getConf(); + if (initConf == null) { + engine.getOptions().setPeerList(peers); + } else { + peers.stream() + .forEach(peer -> initConf.addPeer(JRaftUtils.getPeerId(peer))); + } + + // engine.getOptions().getConf().setPeers(); + engine.restartRaftNode(); + } + } + } catch (PDException e) { + log.error("rebuild raft group error: {}", e.getMessage()); + } + }); + } + + /** + * 创建 raft Node + * + * @param partition + * @return + */ + public PartitionEngine createPartitionEngine(Partition partition) { + return createPartitionEngine(partition, null); + } + + public PartitionEngine createPartitionEngine(Partition partition, Configuration conf) { + partitionManager.updatePartition(partition, false); + + var shardGroup = partitionManager.getShardGroup(partition.getId()); + return createPartitionEngine(partition.getId(), shardGroup, conf); + } + + private PartitionEngine createPartitionEngine(int groupId, ShardGroup shardGroup, + Configuration conf) { + PartitionEngine engine; + if ((engine = partitionEngines.get(groupId)) == null) { + engineLocks.computeIfAbsent(groupId, k -> new Object()); + synchronized (engineLocks.get(groupId)) { + // 分区分裂时特殊情况(集群中图分区数量不一样),会导致分裂的分区,可能不在本机器上. + if (conf != null) { + var list = conf.listPeers(); + list.addAll(conf.listLearners()); + if (!list.stream().anyMatch( + p -> p.getEndpoint().toString().equals(options.getRaftAddress()))) { + log.info( + "raft {}, conf {} does not contains raft address:{}, skipped " + + "create partition engine", + groupId, conf, options.getRaftAddress()); + return null; + } + } else { + var storeId = partitionManager.getStore().getId(); + if (!shardGroup.getShards().stream().anyMatch(s -> s.getStoreId() == storeId)) { + log.info("raft {}, shard group {} does not contains current storeId {}, " + + "skipped create partition engine", groupId, shardGroup, storeId); + return null; + } + } + + if ((engine = partitionEngines.get(groupId)) == null) { + log.info("createPartitionEngine {}, with shards: {}", groupId, shardGroup); + + engine = new PartitionEngine(this, shardGroup); + PartitionEngineOptions ptOpts = new PartitionEngineOptions(); + if (conf != null) { + ptOpts.setConf(conf); + } else { + ptOpts.setPeerList(partitionManager.getPartitionPeers(shardGroup)); + } + ptOpts.setGroupId(groupId); + + ptOpts.setRaftAddress(options.getRaftAddress()); + ptOpts.setRaftDataPath(partitionManager.getRaftDataPath(groupId)); + ptOpts.setRaftSnapShotPath(partitionManager.getRaftSnapShotPath(groupId)); + ptOpts.setRaftOptions(options.getRaftOptions()); + // raft任务处理器 + ptOpts.setTaskHandler(options.getTaskHandler()); + + // 分区状态监听 + engine.addStateListener(this.heartbeatService); + engine.init(ptOpts); + partitionEngines.put(ptOpts.getGroupId(), engine); + } + } + } + // 检查是否活跃,如果不活跃,则重新创建 + engine.checkActivity(); + return engine; + } + + /** + * 创建 raft分组,除了创建本地raft node,还要通知其他peer创建raft node + * 1、遍历partition.shards + * 2、根据storeId获取Store信息 + * 3、建立向其他store的raft rpc,发送StartRaft消息 + * + * @param partition + * @return + */ + public PartitionEngine createPartitionGroups(Partition partition) { + PartitionEngine engine = partitionEngines.get(partition.getId()); + if (engine == null) { + engine = createPartitionEngine(partition); + if (engine == null) { + return null; + } + + var shardGroup = partitionManager.getShardGroup(partition.getId()); + if (shardGroup != null) { + // raft不存在,通知follower创建raft + shardGroup.getShards().forEach((shard) -> { + Store store = partitionManager.getStore(shard.getStoreId()); + if (store == null || partitionManager.isLocalStore(store)) { + return; + } + // 向其他peer发消息,创建raft 分组。此处是异步发送 + hgCmdClient.createRaftNode(store.getRaftAddress(), List.of(partition), + status -> { + log.info( + "send to {} createRaftNode rpc call " + + "result {} partitionId {}", + store.getRaftAddress(), status, + partition.getId()); + }); + }); + } + } else { + // raft存在,修改分区列表,通过raft同步给follower + engine = createPartitionEngine(partition); + } + return engine; + } + + public void destroyPartitionGroups(Partition partition) { + var shardGroup = partitionManager.getShardGroup(partition.getId()); + if (shardGroup != null) { + shardGroup.getShards().forEach((shard) -> { + Store store = partitionManager.getStore(shard.getStoreId()); + if (store == null) { + return; + } + // 向其他peer发消息,创建raft 分组。此处是异步发送 + hgCmdClient.destroyRaftNode(store.getRaftAddress(), + Arrays.asList(new Partition[]{partition}), + status -> { + log.info( + "send to {} - {} DestroyRaftNode rpc call" + + " result {}", + store.getRaftAddress(), partition.getId(), + status); + }); + }); + } + } + + /** + * 停止分区,并销毁数据 + */ + public synchronized void destroyPartitionEngine(Integer groupId, List graphNames) { + log.info("Partition {} start to be destroyed", groupId); + if (!partitionEngines.containsKey(groupId)) { + return; + } + PartitionEngine ptEngine = partitionEngines.get(groupId); + graphNames.forEach(graphName -> { + ptEngine.removePartition(graphName); + // 删除数据 + businessHandler.deletePartition(graphName, groupId); + }); + + if (ptEngine.getPartitions().size() == 0) { + ptEngine.destroy(); + partitionEngines.remove(groupId); + // 删除对应的db文件夹 + businessHandler.destroyGraphDB(graphNames.get(0), groupId); + } else { + graphNames.forEach(graphName -> { + businessHandler.dbCompaction(graphName, groupId); + }); + } + log.info("Partition {} has been destroyed", groupId); + } + + /** + * 删除图数据,删除本地数据,并删除PD上的分区信息 + */ + public void deletePartition(Integer groupId, String graphName) { + log.info("Partition {}-{} deletePartition", graphName, groupId); + if (!partitionEngines.containsKey(groupId)) { + return; + } + PartitionEngine ptEngine = partitionEngines.get(groupId); + ptEngine.removePartition(graphName); + // 删除数据 + businessHandler.deletePartition(graphName, groupId); + //通知PD删除分区数据 + if (ptEngine.isLeader()) { + synchronized (this) { + partitionManager.deletePartition(graphName, groupId); + } + } + } + + /** + * 获取所有的leader分区 + * + * @return + */ + public List getLeaderPartition() { + List partitions = new ArrayList<>(); + this.partitionEngines.forEach((k, v) -> { + if (v.isLeader()) { + partitions.add(v); + } + }); + return partitions; + } + + /** + * 获取分区所有活跃的peer + * + * @return + */ + public Map getAlivePeers(int groupId) { + PartitionEngine engine = this.partitionEngines.get(groupId); + try { + if (engine != null) { + return engine.getAlivePeers(); + } + } catch (Exception e) { + log.error("getAlivePeers {}", e); + } + return new HashMap<>(); + } + + /** + * 获取分区的最后提交的日志id + * + * @param groupId + * @return + */ + public long getLeaderTerm(int groupId) { + PartitionEngine engine = this.partitionEngines.get(groupId); + return engine.getLeaderTerm(); + } + + public long getCommittedIndex(int groupId) { + PartitionEngine engine = this.partitionEngines.get(groupId); + if (engine != null) { + return engine.getCommittedIndex(); + } + return 0; + } + + + public RpcServer getRaftRpcServer() { + return rpcServer; + } + + public PartitionManager getPartitionManager() { + return partitionManager; + } + + // For test + public void setPartitionManager(PartitionManager ptm) { + this.partitionManager = ptm; + } + + public DataMover getDataMover() { + return dataMover; + } + + public PdProvider getPdProvider() { + return pdProvider; + } + + public BusinessHandler getBusinessHandler() { + return businessHandler; + } + + public HgCmdClient getHgCmdClient() { + return hgCmdClient; + } + + public HeartbeatService getHeartbeatService() { + return heartbeatService; + } + + public boolean isClusterReady() { + return heartbeatService.isClusterReady(); + } + + public List getDataLocations() { + return partitionManager.getStoreMetadata().getDataLocations(); + } + + /** + * 添加raft任务 + * 1、检查partition是否存在 + * 1.1、如果不存在,则向PD查询分区是否属于本地 + * 1.1.1 如果分区属于本地,则创建raft分组,并通知其他Store + * 1.1.2 如果分区不属于本地,则抛出异常 + * 1.2 检查Partition是否是leader + * 1.2.1 如果是leader,则提交任务 + * 1.2.2 否则,返回错误 + * + * @param partId + * @param operation + */ + public void addRaftTask(String graphName, Integer partId, RaftOperation operation, + RaftClosure closure) { + PartitionEngine engine = getPartitionEngine(graphName, partId); + if (engine == null) { + engineLocks.computeIfAbsent(partId, k -> new Object()); + synchronized (engineLocks.get(partId)) { + engine = getPartitionEngine(graphName, partId); + if (engine == null) { + Partition partition = partitionManager.findPartition(graphName, partId); + if (partition != null) { + engine = this.createPartitionGroups(partition); + // 可能迁移,不应该创建, 放到 synchronize体中,避免后面的 + if (engine != null) { + engine.waitForLeader(options.getWaitLeaderTimeout() * 1000); + } + } + } + } + } + + if (engine != null) { + // 等待Leader + Endpoint leader = engine.waitForLeader(options.getWaitLeaderTimeout() * 1000); + if (engine.isLeader()) { + engine.addRaftTask(operation, closure); + } else if (leader != null) { + // 当前不是leader,返回leader所在的storeId + Store store = partitionManager.getStoreByRaftEndpoint(engine.getShardGroup(), + leader.toString()); + if (store.getId() == 0) { + // 本地未找到Leader的Store信息,可能Partition还未同步过来,重新向Leader获取。 + Store leaderStore = hgCmdClient.getStoreInfo(leader.toString()); + store = leaderStore != null ? leaderStore : store; + log.error("getStoreByRaftEndpoint error store:{}, shard: {}, leader is {}", + store, engine.getShardGroup().toString(), leader); + } + // Leader 不是本机,通知客户端 + closure.onLeaderChanged(partId, store.getId()); + closure.run(new Status(HgRaftError.NOT_LEADER.getNumber(), + String.format("Partition %s-%d leader changed to %x", + graphName, partId, store.getId()))); + log.error("Raft Partition {}-{} not leader, redirectTo leader {}.", graphName, + partId, leader); + } else { + closure.run(new Status(HgRaftError.WAIT_LEADER_TIMEOUT.getNumber(), + HgRaftError.WAIT_LEADER_TIMEOUT.getMsg())); + log.error("Partition {}-{} waiting for leader timeout.", graphName, partId); + } + } else { + closure.run( + new Status(HgRaftError.NOT_LOCAL.getNumber(), HgRaftError.NOT_LOCAL.getMsg())); + log.error("Partition {}-{} does not belong to local store.", graphName, partId); + } + } + + public PartitionEngine getPartitionEngine(Integer partitionId) { + PartitionEngine engine = partitionEngines.get(partitionId); + return engine; + } + + public PartitionEngine getPartitionEngine(String graphName, Integer partitionId) { + PartitionEngine engine = partitionEngines.get(partitionId); + if (engine != null && engine.hasPartition(graphName)) { + return engine; + } + return null; + } + + public Map getPartitionEngines() { + return partitionEngines; + } + + public Map getNodeMetrics() { + Map metrics = new HashMap(); + partitionEngines.forEach((k, v) -> { + metrics.put(Integer.toString(k), v.getNodeMetrics()); + }); + return metrics; + } + + /** + * Number of raft-group. + * + * @return + */ + public int getRaftGroupCount() { + return partitionEngines.size(); + } + + /** + * 监听rocksdb事件 + * + * @return + */ + private RocksDBFactory.RocksdbChangedListener getRocksdbListener() { + return new RocksDBFactory.RocksdbChangedListener() { + @Override + public void onCompacted(String dbName) { + String sid = dbName.substring(dbName.lastIndexOf("/") + 1); + try { + Integer groupId = Integer.parseInt(sid); + PartitionEngine engine = getPartitionEngine(groupId); + if (engine != null) { + engine.addBlankRaftTask(); + } + } catch (Exception e) { + + } + } + }; + } + + class PartitionChangedListener implements PartitionManager.PartitionChangedListener { + + /** + * Partition对象发生改变,leader通知到其他的follower + */ + @Override + public void onChanged(Partition partition) { + PartitionEngine engine = getPartitionEngine(partition.getId()); + + if (engine != null && engine.isLeader()) { + try { + engine.addRaftTask(RaftOperation.create(RaftOperation.SYNC_PARTITION, + partition.getProtoObj()), + new RaftClosure() { + @Override + public void run(Status status) { + log.info( + "Partition {}-{}-{} sync partition status " + + "is {}", + partition.getGraphName(), partition.getId(), + partition.getWorkState(), + status); + } + }); + } catch (IOException e) { + log.error("Partition {}-{} sync partition exception {}", + partition.getGraphName(), partition.getId(), e); + } + } + } + + /** + * Partition对象key范围、状态发生改变,通过主动寻找leader再通知到其他的follower + */ + @Override + public UpdatePartitionResponse rangeOrStateChanged(UpdatePartitionRequest request) { + UpdatePartitionResponse response = null; + try { + response = hgCmdClient.raftUpdatePartition(request); + + log.info("not leader request threadId:{} pId:{} range:{}-{} state:{} response:{}", + Thread.currentThread().getId(), request.getPartitionId(), + request.getStartKey(), + request.getEndKey(), request.getWorkState(), response.getStatus()); + + } catch (Exception e) { + e.printStackTrace(); + } + + return response; + } + + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/HgStoreStateListener.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/HgStoreStateListener.java new file mode 100644 index 000000000..33f5aead1 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/HgStoreStateListener.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store; + +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.store.meta.Store; + +public interface HgStoreStateListener { + void stateChanged(Store store, Metapb.StoreState oldState, Metapb.StoreState newState); +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/PartitionEngine.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/PartitionEngine.java new file mode 100644 index 000000000..04f4ff722 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/PartitionEngine.java @@ -0,0 +1,1267 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store; + +import static org.apache.hugegraph.pd.grpc.MetaTask.TaskType.Clean_Partition; + +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.apache.commons.collections.ListUtils; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.hugegraph.pd.common.PDException; +import org.apache.hugegraph.pd.grpc.MetaTask; +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.store.cmd.BatchPutRequest; +import org.apache.hugegraph.store.cmd.CleanDataRequest; +import org.apache.hugegraph.store.cmd.DbCompactionRequest; +import org.apache.hugegraph.store.cmd.HgCmdClient; +import org.apache.hugegraph.store.cmd.UpdatePartitionRequest; +import org.apache.hugegraph.store.meta.Partition; +import org.apache.hugegraph.store.meta.PartitionManager; +import org.apache.hugegraph.store.meta.Shard; +import org.apache.hugegraph.store.meta.ShardGroup; +import org.apache.hugegraph.store.meta.Store; +import org.apache.hugegraph.store.meta.TaskManager; +import org.apache.hugegraph.store.options.PartitionEngineOptions; +import org.apache.hugegraph.store.raft.HgStoreStateMachine; +import org.apache.hugegraph.store.raft.RaftClosure; +import org.apache.hugegraph.store.raft.RaftOperation; +import org.apache.hugegraph.store.raft.RaftStateListener; +import org.apache.hugegraph.store.raft.RaftTaskHandler; +import org.apache.hugegraph.store.raft.util.RaftUtils; +import org.apache.hugegraph.store.snapshot.HgSnapshotHandler; +import org.apache.hugegraph.store.util.FutureClosure; +import org.apache.hugegraph.store.util.HgRaftError; +import org.apache.hugegraph.store.util.HgStoreException; +import org.apache.hugegraph.store.util.Lifecycle; + +import com.alipay.sofa.jraft.Closure; +import com.alipay.sofa.jraft.JRaftUtils; +import com.alipay.sofa.jraft.Node; +import com.alipay.sofa.jraft.RaftGroupService; +import com.alipay.sofa.jraft.ReplicatorGroup; +import com.alipay.sofa.jraft.Status; +import com.alipay.sofa.jraft.conf.Configuration; +import com.alipay.sofa.jraft.core.DefaultJRaftServiceFactory; +import com.alipay.sofa.jraft.core.NodeMetrics; +import com.alipay.sofa.jraft.core.Replicator; +import com.alipay.sofa.jraft.entity.PeerId; +import com.alipay.sofa.jraft.entity.Task; +import com.alipay.sofa.jraft.error.RaftException; +import com.alipay.sofa.jraft.option.NodeOptions; +import com.alipay.sofa.jraft.option.RaftOptions; +import com.alipay.sofa.jraft.storage.LogStorage; +import com.alipay.sofa.jraft.storage.impl.RocksDBLogStorage; +import com.alipay.sofa.jraft.storage.log.RocksDBSegmentLogStorage; +import com.alipay.sofa.jraft.util.Endpoint; +import com.alipay.sofa.jraft.util.SystemPropertyUtil; +import com.alipay.sofa.jraft.util.ThreadId; +import com.alipay.sofa.jraft.util.Utils; +import com.alipay.sofa.jraft.util.internal.ThrowUtil; +import com.google.protobuf.CodedInputStream; + +import lombok.extern.slf4j.Slf4j; + + +/** + * Partition processing engine + */ +@Slf4j +public class PartitionEngine implements Lifecycle, RaftStateListener { + + private static final ThreadPoolExecutor raftLogWriteExecutor = null; + public final String raftPrefix = "hg_"; + + private final HgStoreEngine storeEngine; + private final PartitionManager partitionManager; + private final List stateListeners; + private final ShardGroup shardGroup; + private final AtomicBoolean changingPeer; + private final AtomicBoolean snapshotFlag; + private final Object leaderChangedEvent = "leaderChangedEvent"; + /** + * Default value size threshold to decide whether it will be stored in segments or rocksdb, + * default is 4K. + * When the value size is less than 4K, it will be stored in rocksdb directly. + */ + private final int DEFAULT_VALUE_SIZE_THRESHOLD = SystemPropertyUtil.getInt( + "jraft.log_storage.segment.value.threshold.bytes", 4 * 1024); + /** + * Default checkpoint interval in milliseconds. + */ + private final int DEFAULT_CHECKPOINT_INTERVAL_MS = SystemPropertyUtil.getInt( + "jraft.log_storage.segment.checkpoint.interval.ms", 5000); + + private PartitionEngineOptions options; + private HgStoreStateMachine stateMachine; + private RaftGroupService raftGroupService; + private TaskManager taskManager; + private Node raftNode; + private boolean started; + + public PartitionEngine(HgStoreEngine storeEngine, ShardGroup shardGroup) { + this.storeEngine = storeEngine; + this.shardGroup = shardGroup; + this.changingPeer = new AtomicBoolean(false); + this.snapshotFlag = new AtomicBoolean(false); + partitionManager = storeEngine.getPartitionManager(); + stateListeners = Collections.synchronizedList(new ArrayList()); + } +// public static ThreadPoolExecutor getRaftLogWriteExecutor() { +// if (raftLogWriteExecutor == null) { +// synchronized (PartitionEngine.class) { +// if (raftLogWriteExecutor == null) +// raftLogWriteExecutor = RocksDBSegmentLogStorage.createDefaultWriteExecutor(); +// } +// } +// return raftLogWriteExecutor; +// } + + + /** + * 记录使用本raft的分区信息 + */ + + public synchronized void loadPartitionFromSnapshot(Partition partition) { + partitionManager.loadPartitionFromSnapshot(partition); + } + + public List loadPartitionsFromLocalDb() { + return partitionManager.loadPartitionsFromDb(getGroupId()); + } + + public void removePartition(String graphName) { + partitionManager.removePartition(graphName, options.getGroupId()); + } + + public boolean hasPartition(String graphName) { + return partitionManager.hasPartition(graphName, getGroupId()); + } + + public Integer getGroupId() { + return options.getGroupId(); + } + + /** + * Initialize the raft engine + * + * @return + */ + @Override + public synchronized boolean init(PartitionEngineOptions opts) { + this.options = opts; + if (this.started) { + log.info("PartitionEngine: {} already started.", this.options.getGroupId()); + return true; + } + + log.info("PartitionEngine starting: {}", this); + this.taskManager = new TaskManager(storeEngine.getBusinessHandler(), opts.getGroupId()); + HgSnapshotHandler snapshotHandler = new HgSnapshotHandler(this); + this.stateMachine = new HgStoreStateMachine(opts.getGroupId(), snapshotHandler); + // probably null in test case + if (opts.getTaskHandler() != null) { + this.stateMachine.addTaskHandler(opts.getTaskHandler()); + } + this.stateMachine.addTaskHandler(new TaskHandler()); + + // 监听分组leader是否发生改变 + this.stateMachine.addStateListener(this); + + new File(options.getRaftDataPath()).mkdirs(); + + Configuration initConf = opts.getConf(); + if (initConf == null) { + String peersList = StringUtils.join(options.getPeerList(), ","); + initConf = new Configuration(); + initConf.parse(peersList); + } + + // 设置Node参数,包括日志存储路径和状态机实例 + NodeOptions nodeOptions = new NodeOptions(); + nodeOptions.setFsm(stateMachine); + nodeOptions.setEnableMetrics(true); + // 日志路径 + nodeOptions.setLogUri(options.getRaftDataPath() + "/log"); + // raft元数据路径 + nodeOptions.setRaftMetaUri(options.getRaftDataPath() + "/meta"); + // 快照路径 + nodeOptions.setSnapshotUri(options.getRaftSnapShotPath() + "/snapshot"); + nodeOptions.setSharedTimerPool(true); + nodeOptions.setSharedElectionTimer(true); + nodeOptions.setSharedSnapshotTimer(true); + nodeOptions.setSharedStepDownTimer(true); + nodeOptions.setSharedVoteTimer(true); + nodeOptions.setFilterBeforeCopyRemote(true); + + nodeOptions.setServiceFactory(new DefaultJRaftServiceFactory() { + @Override + public LogStorage createLogStorage(final String uri, final RaftOptions raftOptions) { + if (options.getRaftOptions().isUseRocksDBSegmentLogStorage()) { + return new RocksDBSegmentLogStorage(uri, raftOptions); + } else { + return new RocksDBLogStorage(uri, raftOptions); + } + } + }); + // 初始集群 + nodeOptions.setInitialConf(initConf); + // 快照时间间隔 + nodeOptions.setSnapshotIntervalSecs(options.getRaftOptions().getSnapshotIntervalSecs()); + + //nodeOptions.setSnapshotLogIndexMargin(options.getRaftOptions() + // .getSnapshotLogIndexMargin()); + + nodeOptions.setRpcConnectTimeoutMs(options.getRaftOptions().getRpcConnectTimeoutMs()); + nodeOptions.setRpcDefaultTimeout(options.getRaftOptions().getRpcDefaultTimeout()); + nodeOptions.setRpcInstallSnapshotTimeout( + options.getRaftOptions().getRpcInstallSnapshotTimeout()); + nodeOptions.setElectionTimeoutMs(options.getRaftOptions().getElectionTimeoutMs()); + // 设置raft配置 + RaftOptions raftOptions = nodeOptions.getRaftOptions(); + raftOptions.setDisruptorBufferSize(options.getRaftOptions().getDisruptorBufferSize()); + raftOptions.setMaxEntriesSize(options.getRaftOptions().getMaxEntriesSize()); + raftOptions.setMaxReplicatorInflightMsgs( + options.getRaftOptions().getMaxReplicatorInflightMsgs()); + raftOptions.setMaxByteCountPerRpc(1024 * 1024); + nodeOptions.setEnableMetrics(true); + + final PeerId serverId = JRaftUtils.getPeerId(options.getRaftAddress()); + + // 构建raft组并启动raft + this.raftGroupService = new RaftGroupService(raftPrefix + options.getGroupId(), + serverId, nodeOptions, + storeEngine.getRaftRpcServer(), true); + this.raftNode = raftGroupService.start(false); + this.raftNode.addReplicatorStateListener(new ReplicatorStateListener()); + + // 检查pd返回的peers是否和本地一致,如果不一致,重置peerlist + if (this.raftNode != null) { + //TODO 检查peer列表,如果peer发生改变,进行重置 + started = true; + } + + log.info("PartitionEngine start successfully: id = {}, peers list = {}", + options.getGroupId(), nodeOptions.getInitialConf().getPeers()); + return this.started; + } + + public HgStoreEngine getStoreEngine() { + return this.storeEngine; + } + + public ShardGroup getShardGroup() { + return shardGroup; + } + + /** + * 1、收到PD发送的分区迁移指令,向状态机添加迁移任务,状态为新建 + * 2、执行状态机消息,添加到任务队列,并执行任务 + * 3、比较新旧peer,查找出新增和删除的peer + * 4、如果存在新增的peer + * 4.1、对于新增的peer,通知peer创建raft状态机 + * 4.2、以learner方式加入raft group + * 4.3、监听snapshot同步事件,重复执行步骤3 + * 5、不存在新增的peer + * 5.1、移除learner,并等待返回 + * 5.2、修改learner为peer,加入raft group + * 6、存在被删除的peer + * 6.1、通知peer,删除状态机并删除数据 + * + * @param peers + * @param done + * @return true表示完成,false表示未完成 + */ + public Status changePeers(List peers, final Closure done) { + if (ListUtils.isEqualList(peers, RaftUtils.getPeerEndpoints(raftNode))) { + return Status.OK(); + } + + Status result = HgRaftError.TASK_CONTINUE.toStatus(); + List oldPeers = RaftUtils.getAllEndpoints(raftNode); + log.info("Raft {} changePeers start, old peer is {}, new peer is {}", + getGroupId(), oldPeers, peers); + // 检查需要新增的peer。 + List addPeers = ListUtils.removeAll(peers, oldPeers); + // 需要删除的learner。可能peer发生改变 + List removedPeers = ListUtils.removeAll(RaftUtils.getLearnerEndpoints(raftNode), + peers); + + HgCmdClient rpcClient = storeEngine.getHgCmdClient(); + // 生成新的Configuration对象 + Configuration oldConf = getCurrentConf(); + Configuration conf = oldConf.copy(); + if (!addPeers.isEmpty()) { + addPeers.forEach(peer -> { + conf.addLearner(JRaftUtils.getPeerId(peer)); + }); + + doSnapshot((RaftClosure) status -> { + log.info("Raft {} snapshot before add learner, result:{}", getGroupId(), status); + }); + + FutureClosure closure = new FutureClosure(addPeers.size()); + addPeers.forEach(peer -> Utils.runInThread(() -> { + // 1. 创建新peer的raft对象 + rpcClient.createRaftNode(peer, partitionManager.getPartitionList(getGroupId()), + conf, status -> { + closure.run(status); + if (!status.isOk()) { + log.error("Raft {} add node {} error {}", + options.getGroupId(), peer, status); + } + }); + })); + closure.get(); + } else { + // 3. 检查learner是否完成快照同步 + boolean snapshotOk = true; + for (PeerId peerId : raftNode.listLearners()) { + Replicator.State state = getReplicatorState(peerId); + if (state == null || state != Replicator.State.Replicate) { + snapshotOk = false; + break; + } + log.info("Raft {} {} getReplicatorState {}", getGroupId(), peerId, state); + } + if (snapshotOk && !conf.listLearners().isEmpty()) { + // 4. 删除learner,以peer重新加入 + FutureClosure closure = new FutureClosure(); + raftNode.removeLearners(conf.listLearners(), closure); + if (closure.get().isOk()) { + conf.listLearners().forEach(peerId -> { + conf.addPeer(peerId); + conf.removeLearner(peerId); + }); + result = Status.OK(); + } else { + // 失败了重试 + result = HgRaftError.TASK_ERROR.toStatus(); + } + } else if (snapshotOk) { + result = Status.OK(); // 没有learner,说明只做删除操作 + } + } + if (result.isOk()) { + // 同步完成,删除旧peer + removedPeers.addAll(ListUtils.removeAll(oldPeers, peers)); + // 检查leader是否被删除,如果是,先进行leader迁移 + if (removedPeers.contains( + this.getRaftNode().getNodeId().getPeerId().getEndpoint().toString())) { + + log.info("Raft {} leader is removed, needs to transfer leader {}, conf: {}", + getGroupId(), peers, conf); + // only one (that's leader self), should add peer first + if (raftNode.listPeers().size() == 1) { + FutureClosure closure = new FutureClosure(); + raftNode.changePeers(conf, closure); + log.info("Raft {} change peer result:{}", getGroupId(), closure.get()); + } + + var status = this.raftNode.transferLeadershipTo(PeerId.ANY_PEER); + log.info("Raft {} transfer leader status : {}", getGroupId(), status); + // 需要重新发送指令给新leader + return HgRaftError.TASK_ERROR.toStatus(); + } + } + + if (!removedPeers.isEmpty()) { + removedPeers.forEach(peer -> { + conf.removeLearner(JRaftUtils.getPeerId(peer)); + conf.removePeer(JRaftUtils.getPeerId(peer)); + }); + } + + if (!RaftUtils.configurationEquals(oldConf, conf)) { + // 2. 新的peer以learner身份加入 + // 5. peer切换,增加新的peer,删除旧的peer + FutureClosure closure = new FutureClosure(); + raftNode.changePeers(conf, closure); + if (closure.get().isOk()) { + if (!removedPeers.isEmpty()) { + removedPeers.forEach(peer -> Utils.runInThread(() -> { + // 6. 停止已被删除的peer + rpcClient.destroyRaftNode(peer, + partitionManager.getPartitionList(getGroupId()), + status -> { + if (!status.isOk()) { + // TODO 失败了怎么办? + log.error("Raft {} destroy node {}" + + " error {}", + options.getGroupId(), peer, + status); + } + }); + })); + } + } else { + // 失败了重试 + result = HgRaftError.TASK_ERROR.toStatus(); + } + log.info("Raft {} changePeers result {}, conf is {}", + getRaftNode().getGroupId(), closure.get(), conf); + } + log.info("Raft {} changePeers end. {}, result is {}", getGroupId(), peers, result); + return result; + } + + public void addRaftTask(RaftOperation operation, RaftClosure closure) { + if (!isLeader()) { + closure.run(new Status(HgRaftError.NOT_LEADER.getNumber(), "Not leader")); + return; + } + final Task task = new Task(); + task.setData(ByteBuffer.wrap(operation.getValues())); + task.setDone(new HgStoreStateMachine.RaftClosureAdapter(operation, closure)); + this.raftNode.apply(task); + } + + @Override + public void shutdown() { + if (!this.started) { + return; + } + + partitionManager.updateShardGroup(shardGroup); + + if (this.raftGroupService != null) { + this.raftGroupService.shutdown(); + try { + this.raftGroupService.join(); + } catch (final InterruptedException e) { + ThrowUtil.throwException(e); + } + } + this.started = false; + log.info("PartitionEngine shutdown successfully: {}.", this); + } + + /** + * 重启raft引擎 + */ + public void restartRaftNode() { + shutdown(); + log.error("Raft {} is restarting !!!", getGroupId()); + this.init(this.options); + } + + /** + * 检查是否活跃,如果不活跃,则重启 + */ + public void checkActivity() { + Utils.runInThread(() -> { + if (!this.raftNode.getNodeState().isActive()) { + log.error("Raft {} is not activity state is {} ", + this.getGroupId(), raftNode.getNodeState()); + restartRaftNode(); + } + }); + } + + /** + * raft peer被销毁,删除日志和数据 + */ + public void destroy() { + shutdown(); + try { + FileUtils.deleteDirectory(new File(this.options.getRaftDataPath())); + if (!Objects.equals(this.options.getRaftDataPath(), + this.options.getRaftSnapShotPath())) { + FileUtils.deleteDirectory(new File(this.options.getRaftSnapShotPath())); + } + } catch (IOException e) { + log.error("Raft {} destroy exception {}", this.options.getGroupId(), e); + } + } + + public boolean isLeader() { + return this.raftNode != null && this.raftNode.isLeader(false); + } + + public Endpoint getLeader() { + PeerId peerId = this.raftNode.getLeaderId(); + return peerId != null ? peerId.getEndpoint() : null; + } + + public void addStateListener(PartitionStateListener listener) { + this.stateListeners.add(listener); + } + + /** + * 返回所有活跃的peer + * + * @return + */ + public Map getAlivePeers() { + Map peers = new HashMap<>(); + raftNode.listAlivePeers().forEach(peerId -> { + Shard shard = partitionManager.getShardByRaftEndpoint(shardGroup, + peerId.getEndpoint().toString()); + if (shard != null) { + peers.put(shard.getStoreId(), peerId); + } + }); + return peers; + } + + public Node getRaftNode() { + return raftNode; + } + + /** + * 等待Leader被选举 + * + * @param timeOut + * @return + */ + public Endpoint waitForLeader(long timeOut) { + Endpoint leader = getLeader(); + if (leader != null) { + return leader; + } + + synchronized (leaderChangedEvent) { + leader = getLeader(); + if (leader != null) { + return leader; + } + + long start = System.currentTimeMillis(); + while ((System.currentTimeMillis() - start < timeOut) && (leader == null)) { + try { + leaderChangedEvent.wait(timeOut); + } catch (InterruptedException e) { + log.error("Raft {} wait for leader exception", this.options.getGroupId(), e); + } + leader = getLeader(); + if (leader == null) { + if (partitionManager.isLocalPartition(this.options.getGroupId())) { + log.error("Raft {} leader not found, try to repair!", + this.options.getGroupId()); + // TODO 判断raft是否本机,如果是,尝试修复Leader,包括检查配置是否正确 + storeEngine.createPartitionGroups( + partitionManager.getPartitionList(getGroupId()).get(0)); + } + } else { + log.info("Raft {} wait for leader success, latency time {}", + this.options.getGroupId(), System.currentTimeMillis() - start); + } + } + return leader; + } + } + + public Map getPartitions() { + return partitionManager.getPartitions(getGroupId()); + } + + public Partition getPartition(String graphName) { + return partitionManager.getPartition(graphName, getGroupId()); + } + + public PartitionEngineOptions getOptions() { + return options; + } + + @Override + public String toString() { + return "PartitionEngine{" + "groupId =" + this.options.getGroupId() + ", isLeader=" + + isLeader() + + ", options=" + this.options + '}'; + } + + + /** + * update partition leader + * + * @param newTerm the new term + */ + @Override + public void onLeaderStart(long newTerm) { + log.info("Raft {} onLeaderStart newTerm is {}", getGroupId(), newTerm); + // 更新shard group对象 + shardGroup.changeLeader(partitionManager.getStore().getId()); + + onConfigurationCommitted(getCurrentConf()); + synchronized (leaderChangedEvent) { + leaderChangedEvent.notifyAll(); + } + + } + + @Override + public void onStartFollowing(final PeerId newLeaderId, final long newTerm) { + onConfigurationCommitted(getCurrentConf()); + synchronized (leaderChangedEvent) { + leaderChangedEvent.notifyAll(); + } + } + + /** + * update partition shardList + * + * @param conf committed configuration + */ + @Override + public void onConfigurationCommitted(Configuration conf) { + + try { + //更新shardlist + log.info("Raft {} onConfigurationCommitted, conf is {}", getGroupId(), conf.toString()); + // 根据raft endpoint查找storeId + List peerIds = new ArrayList<>(); + for (String peer : RaftUtils.getPeerEndpoints(conf)) { + Store store = getStoreByEndpoint(peer); + if (store != null) { + peerIds.add(store.getId()); + } else { + log.error("Raft {} GetStoreInfo failure, {}", getGroupId(), peer); + } + } + List learners = new ArrayList<>(); + for (String learner : RaftUtils.getLearnerEndpoints(conf)) { + Store store = getStoreByEndpoint(learner); + if (store != null) { + learners.add(store.getId()); + } else { + log.error("Raft {} GetStoreInfo failure, {}", getGroupId(), learner); + } + } + + shardGroup.changeShardList(peerIds, learners, partitionManager.getStore().getId()); + partitionManager.updateShardGroup(shardGroup); + + if (isLeader()) { + // partitionManager.getPartitionList(getGroupId()).forEach(partition -> { + // partitionManager.changeShards(partition, shardGroup.getMetaPbShard()); + // }); + try { + var pdGroup = storeEngine.getPdProvider().getShardGroup(getGroupId()); + List peers = partitionManager.shards2Peers(pdGroup.getShardsList()); + + if (!ListUtils.isEqualList(peers, RaftUtils.getPeerEndpoints(raftNode))) { + partitionManager.getPdProvider().updateShardGroup(shardGroup.getProtoObj()); + } + + } catch (PDException e) { + throw new RuntimeException(e); + } + } + log.info("Raft {} onConfigurationCommitted, shardGroup {}", getGroupId(), + shardGroup.getMetaPbShard()); + + } catch (Exception e) { + log.error("Raft {} onConfigurationCommitted exception {}", getGroupId(), e); + } + + } + + private Store getStoreByEndpoint(String endpoint) { + Store store = partitionManager.getStoreByRaftEndpoint(getShardGroup(), endpoint); + if (store == null || store.getId() == 0) { + store = this.storeEngine.getHgCmdClient().getStoreInfo(endpoint); + } + return store; + } + + @Override + public void onDataCommitted(long index) { + + } + + @Override + public void onError(RaftException e) { + this.restartRaftNode(); + } + + public NodeMetrics getNodeMetrics() { + return this.raftNode.getNodeMetrics(); + } + + public long getCommittedIndex() { + return stateMachine.getCommittedIndex(); + } + + public long getLeaderTerm() { + return stateMachine.getLeaderTerm(); + } + + public TaskManager getTaskManager() { + return this.taskManager; + } + + /** + * 收到PD发送的leader转移指令 + * + * @param graphName + * @param shard + * @return + */ + public Status transferLeader(String graphName, Metapb.Shard shard) { + if (!isLeader()) { + return new Status(HgRaftError.NOT_LEADER.getNumber(), "Not leader"); + } + String address = partitionManager.getStore(shard.getStoreId()).getRaftAddress(); + return raftNode.transferLeadershipTo(JRaftUtils.getPeerId(address)); + } + + + /** + * 接收到pd发来的修改副本指令 + * 1. 比较新旧peer,查找出新增和删除的peer + * 2. 对于新增的peer,以learner方式加入 + * 3. 监听快照同步事件 + * 4. 快照同步完成后,调用changePeers,修改learner为follower,删除旧的peer + */ + public void doChangeShard(final MetaTask.Task task, Closure done) { + if (!isLeader()) { + return; + } + + log.info("Raft {} doChangeShard task is {}", getGroupId(), task); + // 如果相同的分区有相同的任务在执行,忽略任务执行 + if (taskManager.partitionTaskRepeat(task.getPartition().getId(), + task.getPartition().getGraphName(), + task.getType().name())) { + log.error("Raft {} doChangeShard task repeat, type:{}", getGroupId(), task.getType()); + return; + } + // 任务未完成,重复执行 + if (task.getState().getNumber() < MetaTask.TaskState.Task_Stop_VALUE && isLeader()) { + Utils.runInThread(() -> { + try { + // 不能在状态机中changePeers + List peers = + partitionManager.shards2Peers(task.getChangeShard().getShardList()); + HashSet hashSet = new HashSet<>(peers); + // 任务中有相同的peers,说明任务本身有错误,任务忽略 + if (peers.size() != hashSet.size()) { + log.info("Raft {} doChangeShard peer is repeat, peers:{}", getGroupId(), + peers); + } + Status result; + if (changingPeer.compareAndSet(false, true)) { + result = this.changePeers(peers, done); + } else { + result = HgRaftError.TASK_ERROR.toStatus(); + } + + if (result.getCode() != HgRaftError.TASK_CONTINUE.getNumber()) { + log.info("Raft {} doChangeShard is finished, status is {}", getGroupId(), + result); + // 任务完成,同步任务状态 + MetaTask.Task newTask; + if (result.isOk()) { + newTask = task.toBuilder().setState(MetaTask.TaskState.Task_Success) + .build(); + } else { + log.warn( + "Raft {} doChangeShard is failure, need to retry, status is {}", + getGroupId(), result); + try { + // 减少发送次数 + Thread.sleep(1000); + } catch (Exception e) { + log.error("wait 1s to resend retry task. got error:{}", + e.getMessage()); + } + newTask = task.toBuilder().setState(MetaTask.TaskState.Task_Ready) + .build(); + } + try { + // 等待过程中,可能已经shut down了 + if (isLeader()) { + storeEngine.addRaftTask(newTask.getPartition().getGraphName(), + newTask.getPartition().getId(), + RaftOperation.create( + RaftOperation.SYNC_PARTITION_TASK, + newTask), + status -> { + if (!status.isOk()) { + log.error( + "Raft {} addRaftTask " + + "error, status is {}", + newTask.getPartition() + .getId(), status); + } + } + ); + } + } catch (Exception e) { + log.error("Partition {}-{} update task state exception {}", + task.getPartition().getGraphName(), + task.getPartition().getId(), e); + } + // db 可能被销毁了,就不要更新了 + if (this.started) { + taskManager.updateTask(newTask); + } + } else { + log.info("Raft {} doChangeShard not finished", getGroupId()); + } + } catch (Exception e) { + log.error("Raft {} doChangeShard exception {}", getGroupId(), e); + } finally { + changingPeer.set(false); + } + }); + } else { + // 返回消息是否被处理 + if (done != null) { + done.run(Status.OK()); + } + } + } + + /** + * 收到PD发送的分区之间转移数据 + * 1. 通知目标机器,创建raft + * 2. 从源机器拷贝数据到目标机器 + * 3. 迁移成功后,通知PD修改分区信息 + * 4. 删除源分区 + * + * @return + */ + public Status moveData(MetaTask.Task task) { + + synchronized (this) { + if (taskManager.taskExists(task.getPartition().getId(), + task.getPartition().getGraphName(), + task.getType().name())) { + log.info("task : {}-{}-{} repeat", task.getPartition().getGraphName(), + task.getPartition().getId(), + task.getType().name()); + return Status.OK(); + } + taskManager.updateTask( + task.toBuilder().setState(MetaTask.TaskState.Task_Doing).build()); + } + + Status status = Status.OK(); + + switch (task.getType()) { + case Split_Partition: + status = handleSplitTask(task); + break; + case Move_Partition: + status = handleMoveTask(task); + break; + default: + break; + } + log.info("moveData {}-{}-{} result:{}", task.getPartition().getGraphName(), + task.getPartition().getId(), + task.getType().name(), status); + + if (status.isOk()) { + // 向PD汇报任务执行结果 + partitionManager.reportTask( + task.toBuilder().setState(MetaTask.TaskState.Task_Success).build()); + // 更新本地任务状态 + taskManager.updateTask( + task.toBuilder().setState(MetaTask.TaskState.Task_Success).build()); + } else { + partitionManager.reportTask(task.toBuilder() + .setState(MetaTask.TaskState.Task_Failure) + .setMessage(status.getErrorMsg()).build()); + // 更新本地任务状态 + taskManager.updateTask(task.toBuilder() + .setState(MetaTask.TaskState.Task_Failure) + .setMessage(status.getErrorMsg()).build()); + } + + return status; + } + + /** + * 对应分区分裂的任务 + * + * @param task split partition task + * @return task execution result + */ + private Status handleSplitTask(MetaTask.Task task) { + + log.info("Partition {}-{} moveData {}", task.getPartition().getGraphName(), + task.getPartition().getId(), task); + + Status status; + + List targets = task.getSplitPartition().getNewPartitionList(); + List newPartitions = targets.subList(1, targets.size()); + try { + for (int i = 0; i < newPartitions.size(); i++) { + storeEngine.createPartitionGroups(new Partition(newPartitions.get(i))); + } + // 从源机器拷贝数据到目标机器 + status = storeEngine.getDataMover().moveData(task.getPartition(), newPartitions); + + if (status.isOk()) { + var source = Metapb.Partition.newBuilder(targets.get(0)) + .setState(Metapb.PartitionState.PState_Normal) + .build(); + // 更新本地key range,并同步follower + partitionManager.updatePartition(source, true); + storeEngine.getDataMover().updatePartitionRange(source, + (int) source.getStartKey(), + (int) source.getEndKey()); + } + + if (!status.isOk()) { + throw new Exception(status.getErrorMsg()); + } + } catch (Exception e) { + log.error("Partition {}-{} moveData exception {}", + task.getPartition().getGraphName(), task.getPartition().getId(), e); + status = new Status(-1, e.getMessage()); + } + + return status; + } + + /** + * 对应分区数据移动的task + * + * @param task move partition task + * @return task execution result + */ + private Status handleMoveTask(MetaTask.Task task) { + Status status; + try { + log.info("handleMoveTask: start to copy {} data from {} to {}", + task.getPartition().getGraphName(), + task.getPartition().getId(), + task.getMovePartition().getTargetPartition().getId()); + status = storeEngine.getDataMover().moveData(task.getPartition(), + task.getMovePartition() + .getTargetPartition()); + } catch (Exception e) { + log.error("handleMoveTask got exception: ", e); + status = new Status(-1, e.getMessage()); + } + return status; + } + + /** + * 对于整个图删除的清空,删除分区, 如果没有其他的图,销毁raft group. + * 需要放到调用move data 之后 + * + * @param graphName graph name + * @param partitionId partition id + * @param keyStart key start 用于验证 + * @param keyEnd key end 用于验证 + * @param isLeader 是否leader,避免leader漂移,采取move data时候的leader状态 + */ + private synchronized void destroyPartitionIfGraphsNull(String graphName, int partitionId, + long keyStart, long keyEnd, + boolean isLeader) { + Partition partition = partitionManager.getPartition(graphName, partitionId); + + // key range 校验 + if (partition != null && partition.getEndKey() == keyEnd && + partition.getStartKey() == keyStart) { + log.info("remove partition id :{}, graph:{}", partition.getId(), + partition.getGraphName()); + storeEngine.deletePartition(partitionId, graphName); + } + + // 没有partition engine的情况 + if (isLeader && partition == null) { + partitionManager.deletePartition(graphName, partitionId); + } + + if (isLeader && partition != null) { + if (partitionManager.getPartitions(partitionId).size() == 0) { + log.info("destroyPartitionIfGraphsNull, destroy raft group id:{}", + partition.getId()); + storeEngine.destroyPartitionGroups(partition); + try { + // delete shard group from pd + partitionManager.getPdProvider().deleteShardGroup(partitionId); + } catch (PDException e) { + log.error("delete shard group failed, status:{}", e.getMessage()); + } + } + } + } + + + public void snapshot() { + log.info("Raft {} send snapshot command. ", this.getGroupId()); + // 空指令,占位 + this.addRaftTask( + RaftOperation.create(RaftOperation.BLANK_TASK), status -> { + // 生成快照指令 + this.addRaftTask( + RaftOperation.create(RaftOperation.DO_SNAPSHOT), status2 -> { + }); + }); + + } + + protected void doSnapshot(Closure done) { + log.info("Raft {} doSnapshot. ", this.getGroupId()); + + if (this.snapshotFlag.compareAndSet(false, true)) { + + raftNode.snapshot(status -> { + log.info("Raft {} snapshot OK. ", this.getGroupId()); + if (done != null) { + done.run(status); + } + }); + } + + this.snapshotFlag.set(false); + } + + public void addBlankRaftTask() { + // 空指令,占位 + this.addRaftTask( + RaftOperation.create(RaftOperation.BLANK_TASK), status -> { + }); + } + + private void handleCleanOp(CleanDataRequest request) { + // 避免清理数据过程中的leader漂移 + boolean isLeader = isLeader(); + var partition = + partitionManager.getPartition(request.getGraphName(), request.getPartitionId()); + + if (partition != null) { + storeEngine.getDataMover().doCleanData(request); + storeEngine.getBusinessHandler() + .dbCompaction(partition.getGraphName(), partition.getId()); + + if (request.isDeletePartition()) { + destroyPartitionIfGraphsNull(request.getGraphName(), request.getPartitionId(), + request.getKeyStart(), request.getKeyEnd(), isLeader); + } + } else { + log.info("handleCleanOp, partition is null. {}-{}", request.getGraphName(), + request.getPartitionId()); + partition = new Partition() {{ + setId(request.getPartitionId()); + setGraphName(request.getGraphName()); + setVersion(0); + setStartKey(0); + setEndKey(0); + setWorkState(Metapb.PartitionState.PState_Normal); + }}; + } + + // report task + if (isLeader) { + MetaTask.Task task = MetaTask.Task.newBuilder() + .setType(Clean_Partition) + .setId(request.getTaskId()) + .setPartition(partition.getProtoObj()) + .setCleanPartition( + CleanDataRequest.toCleanPartitionTask( + request)) + .setState(MetaTask.TaskState.Task_Success) + .build(); + partitionManager.reportTask(task); + } + } + + + class ReplicatorStateListener implements Replicator.ReplicatorStateListener { + + @Override + public void onCreated(PeerId peer) { + log.info("Raft {} Replicator onCreated {}", getGroupId(), peer); + } + + @Override + public void onError(PeerId peer, Status status) { + // log.info("Raft {} Replicator onError {} {}", getGroupId(), peer, status); + } + + @Override + public void onDestroyed(PeerId peer) { + + } + + /** + * 监听replicator状态改变,判断快照是否同步完成 + * 检查是否存在changeShard任务,如果存在,调用changeShard + */ + @Override + public void stateChanged(final PeerId peer, final ReplicatorState newState) { + log.info("Raft {} Replicator stateChanged {} {}", getGroupId(), peer, newState); + if (newState == ReplicatorState.ONLINE) { + MetaTask.Task task = taskManager.getOneTask(getGroupId(), + MetaTask.TaskType.Change_Shard.name()); + if (task != null) { + doChangeShard(task, status -> { + log.info("Raft {} (replicator state changed) changeShard task {}, " + + "status is {}", getGroupId(), task, status); + }); + } + } + } + } + + class TaskHandler implements RaftTaskHandler { + @Override + public boolean invoke(final int groupId, byte[] request, + RaftClosure response) throws HgStoreException { + try { + CodedInputStream input = CodedInputStream.newInstance(request); + byte methodId = input.readRawByte(); + switch (methodId) { + case RaftOperation.SYNC_PARTITION_TASK: + invoke(groupId, methodId, MetaTask.Task.parseFrom(input), response); + break; + case RaftOperation.SYNC_PARTITION: + invoke(groupId, methodId, Metapb.Partition.parseFrom(input), response); + break; + case RaftOperation.DO_SNAPSHOT: + case RaftOperation.BLANK_TASK: + invoke(groupId, methodId, null, response); + break; + case RaftOperation.IN_WRITE_OP: + case RaftOperation.RAFT_UPDATE_PARTITION: + case RaftOperation.IN_CLEAN_OP: + case RaftOperation.DB_COMPACTION: + invoke(groupId, methodId, RaftOperation.toObject(request, 0), response); + break; + default: + return false; + } + } catch (IOException e) { + log.error("raft task exception ", e); + } + return true; + } + + @Override + public boolean invoke(final int groupId, byte methodId, Object req, + RaftClosure response) throws HgStoreException { + switch (methodId) { + case RaftOperation.SYNC_PARTITION_TASK: { + MetaTask.Task task = (MetaTask.Task) req; + taskManager.updateTask(task); + if (task.getType() == MetaTask.TaskType.Change_Shard) { + log.info("change shard task: id {}, change shard:{} ", + task.getId(), task.getChangeShard()); + doChangeShard(task, response); + } + } + break; + case RaftOperation.SYNC_PARTITION: + log.info("receive sync partition {}", req); + if (!isLeader()) { + partitionManager.updatePartition((Metapb.Partition) req, true); + } + break; + case RaftOperation.BLANK_TASK: + break; + case RaftOperation.DO_SNAPSHOT: + doSnapshot(response); + break; + case RaftOperation.IN_WRITE_OP: + storeEngine.getDataMover().doWriteData((BatchPutRequest) (req)); + break; + case RaftOperation.IN_CLEAN_OP: + handleCleanOp((CleanDataRequest) req); + break; + case RaftOperation.RAFT_UPDATE_PARTITION: + log.info("Raft {}, receive raft updatePartitionRangeOrState {}", + getGroupId(), req); + partitionManager.updatePartitionRangeOrState((UpdatePartitionRequest) (req)); + break; + case RaftOperation.DB_COMPACTION: + DbCompactionRequest dbCompactionRequest = (DbCompactionRequest) (req); + storeEngine.getBusinessHandler() + .dbCompaction(dbCompactionRequest.getGraphName(), + dbCompactionRequest.getPartitionId(), + dbCompactionRequest.getTableName()); + break; + default: + return false; + } + return true; + } + } + + public Configuration getCurrentConf() { + return new Configuration(this.raftNode.listPeers(), this.raftNode.listLearners()); + } + + private Replicator.State getReplicatorState(PeerId peerId) { + var replicateGroup = getReplicatorGroup(); + if (replicateGroup == null) { + return null; + } + + ThreadId threadId = replicateGroup.getReplicator(peerId); + if (threadId == null) { + return null; + } else { + Replicator r = (Replicator) threadId.lock(); + if (r == null) { + return Replicator.State.Probe; + } + Replicator.State result = getState(r); + threadId.unlock(); + return result; + } + } + + private ReplicatorGroup getReplicatorGroup() { + var clz = this.raftNode.getClass(); + try { + var f = clz.getDeclaredField("replicatorGroup"); + f.setAccessible(true); + var group = (ReplicatorGroup) f.get(this.raftNode); + f.setAccessible(false); + return group; + } catch (NoSuchFieldException | IllegalAccessException e) { + log.info("getReplicatorGroup: error {}", e.getMessage()); + return null; + } + } + + private Replicator.State getState(Replicator r) { + var clz = r.getClass(); + try { + var f = clz.getDeclaredField("state"); + f.setAccessible(true); + var state = (Replicator.State) f.get(this.raftNode); + f.setAccessible(false); + return state; + } catch (NoSuchFieldException | IllegalAccessException e) { + log.info("getReplicatorGroup: error {}", e.getMessage()); + return null; + } + } + +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/PartitionInstructionProcessor.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/PartitionInstructionProcessor.java new file mode 100644 index 000000000..5bdbd847f --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/PartitionInstructionProcessor.java @@ -0,0 +1,344 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +import org.apache.hugegraph.pd.common.PDException; +import org.apache.hugegraph.pd.grpc.MetaTask; +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.pd.grpc.pulse.ChangeShard; +import org.apache.hugegraph.pd.grpc.pulse.CleanPartition; +import org.apache.hugegraph.pd.grpc.pulse.DbCompaction; +import org.apache.hugegraph.pd.grpc.pulse.MovePartition; +import org.apache.hugegraph.pd.grpc.pulse.PartitionKeyRange; +import org.apache.hugegraph.pd.grpc.pulse.SplitPartition; +import org.apache.hugegraph.pd.grpc.pulse.TransferLeader; +import org.apache.hugegraph.store.cmd.CleanDataRequest; +import org.apache.hugegraph.store.cmd.DbCompactionRequest; +import org.apache.hugegraph.store.meta.MetadataKeyHelper; +import org.apache.hugegraph.store.meta.Partition; +import org.apache.hugegraph.store.pd.PartitionInstructionListener; +import org.apache.hugegraph.store.raft.RaftClosure; +import org.apache.hugegraph.store.raft.RaftOperation; +import org.apache.hugegraph.util.Log; +import org.slf4j.Logger; + +import com.alipay.sofa.jraft.Status; +import com.alipay.sofa.jraft.util.Utils; +import com.google.common.util.concurrent.ThreadFactoryBuilder; + +/** + * PD发给Store的分区指令处理器 + */ +public class PartitionInstructionProcessor implements PartitionInstructionListener { + private static final Logger LOG = Log.logger(PartitionInstructionProcessor.class); + private final HgStoreEngine storeEngine; + private final ExecutorService threadPool; + + public PartitionInstructionProcessor(HgStoreEngine storeEngine) { + this.storeEngine = storeEngine; + ThreadFactory namedThreadFactory = + new ThreadFactoryBuilder().setNameFormat("instruct-process-pool-%d").build(); + threadPool = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(), + 1000000, + 180L, + TimeUnit.SECONDS, + new LinkedBlockingQueue<>(1000000), + namedThreadFactory, + new ThreadPoolExecutor.AbortPolicy()); + } + + @Override + public void onChangeShard(long taskId, Partition partition, ChangeShard changeShard, + Consumer consumer) { + PartitionEngine engine = storeEngine.getPartitionEngine(partition.getId()); + + if (engine != null) { + // 清理所有的任务,有失败的情况 + engine.getTaskManager() + .deleteTask(partition.getId(), MetaTask.TaskType.Change_Shard.name()); + } + + if (engine != null && engine.isLeader()) { + LOG.info("Partition {}-{} Receive change shard message, {}", partition.getGraphName(), + partition.getId(), changeShard); + String graphName = partition.getGraphName(); + int partitionId = partition.getId(); + MetaTask.Task task = MetaTask.Task.newBuilder() + .setId(taskId) + .setPartition(partition.getProtoObj()) + .setType(MetaTask.TaskType.Change_Shard) + .setState(MetaTask.TaskState.Task_Ready) + .setChangeShard(changeShard) + .build(); + try { + storeEngine.addRaftTask(graphName, partitionId, + RaftOperation.create(RaftOperation.SYNC_PARTITION_TASK, + task), + new RaftClosure() { + @Override + public void run(Status status) { + LOG.info( + "Partition {}-{} onChangeShard complete, " + + "status is {}", + graphName, partitionId, status); + consumer.accept(0); + } + }); + } catch (Exception e) { + LOG.error("Partition {}-{} onSplitPartition exception {}", + graphName, partitionId, e); + } + } + } + + @Override + public void onTransferLeader(long taskId, Partition partition, TransferLeader transferLeader, + Consumer consumer) { + PartitionEngine engine = storeEngine.getPartitionEngine(partition.getId()); + if (engine != null && engine.isLeader()) { + consumer.accept(0); + Utils.runInThread(() -> { + LOG.info("Partition {}-{} receive TransferLeader instruction, new leader is {}" + , partition.getGraphName(), partition.getId(), transferLeader.getShard()); + engine.transferLeader(partition.getGraphName(), transferLeader.getShard()); + }); + } + } + + /** + * Leader接收到PD发送的分区分裂任务 + * 添加到raft任务队列,由raft进行任务分发。 + */ + @Override + public void onSplitPartition(long taskId, Partition partition, SplitPartition splitPartition, + Consumer consumer) { + PartitionEngine engine = storeEngine.getPartitionEngine(partition.getId()); + + if (preCheckTaskId(taskId, partition.getId())) { + return; + } + + if (engine != null && engine.isLeader()) { + // 先应答,避免超时造成pd重复发送 + consumer.accept(0); + + String graphName = partition.getGraphName(); + int partitionId = partition.getId(); + MetaTask.Task task = MetaTask.Task.newBuilder() + .setId(taskId) + .setPartition(partition.getProtoObj()) + .setType(MetaTask.TaskType.Split_Partition) + .setState(MetaTask.TaskState.Task_Ready) + .setSplitPartition(splitPartition) + .build(); + try { + threadPool.submit(() -> { + engine.moveData(task); + }); + } catch (Exception e) { + LOG.error("Partition {}-{} onSplitPartition exception {}", + graphName, partitionId, e); + } + } + } + + /** + * Leader接收到PD发送的rocksdb compaction任务 + * 添加到raft任务队列,由raft进行任务分发。 + */ + @Override + public void onDbCompaction(long taskId, Partition partition, DbCompaction dbCompaction, + Consumer consumer) { + PartitionEngine engine = storeEngine.getPartitionEngine(partition.getId()); + if (engine != null && engine.isLeader()) { + try { + DbCompactionRequest dbCompactionRequest = new DbCompactionRequest(); + dbCompactionRequest.setPartitionId(partition.getId()); + dbCompactionRequest.setTableName(dbCompaction.getTableName()); + dbCompactionRequest.setGraphName(partition.getGraphName()); + engine.addRaftTask(RaftOperation.create(RaftOperation.DB_COMPACTION, + dbCompactionRequest), + new RaftClosure() { + @Override + public void run(Status status) { + LOG.info( + "onRocksdbCompaction {}-{} sync partition " + + "status is {}", + partition.getGraphName(), partition.getId(), + status); + } + } + ); + } finally { + consumer.accept(0); + } + } + } + + @Override + public void onMovePartition(long taskId, Partition partition, MovePartition movePartition, + Consumer consumer) { + PartitionEngine engine = storeEngine.getPartitionEngine(partition.getId()); + + if (preCheckTaskId(taskId, partition.getId())) { + return; + } + + if (engine != null && engine.isLeader()) { + // 先应答,避免超时造成pd重复发送 + consumer.accept(0); + + String graphName = partition.getGraphName(); + int partitionId = partition.getId(); + MetaTask.Task task = MetaTask.Task.newBuilder() + .setId(taskId) + .setPartition(partition.getProtoObj()) + .setType(MetaTask.TaskType.Move_Partition) + .setState(MetaTask.TaskState.Task_Ready) + .setMovePartition(movePartition) + .build(); + try { + threadPool.submit(() -> { + engine.moveData(task); + }); + } catch (Exception e) { + LOG.error("Partition {}-{} onMovePartition exception {}", + graphName, partitionId, e); + } + } + } + + @Override + public void onCleanPartition(long taskId, Partition partition, CleanPartition cleanPartition, + Consumer consumer) { + + if (preCheckTaskId(taskId, partition.getId())) { + return; + } + + PartitionEngine engine = storeEngine.getPartitionEngine(partition.getId()); + if (engine != null && engine.isLeader()) { + consumer.accept(0); + + CleanDataRequest + request = + CleanDataRequest.fromCleanPartitionTask(cleanPartition, partition, taskId); + + storeEngine.addRaftTask(partition.getGraphName(), partition.getId(), + RaftOperation.create(RaftOperation.IN_CLEAN_OP, request), + status -> { + LOG.info( + "onCleanPartition {}-{}, cleanType: {}, " + + "range:{}-{}, status:{}", + partition.getGraphName(), + partition.getId(), + cleanPartition.getCleanType(), + cleanPartition.getKeyStart(), + cleanPartition.getKeyEnd(), + status); + }); + } + + } + + @Override + public void onPartitionKeyRangeChanged(long taskId, Partition partition, + PartitionKeyRange partitionKeyRange, + Consumer consumer) { + PartitionEngine engine = storeEngine.getPartitionEngine(partition.getId()); + if (engine != null && engine.isLeader()) { + consumer.accept(0); + var partitionManager = storeEngine.getPartitionManager(); + var localPartition = + partitionManager.getPartition(partition.getGraphName(), partition.getId()); + + if (localPartition == null) { + // 如果分区数据为空,本地不会存储 + localPartition = partitionManager.getPartitionFromPD(partition.getGraphName(), + partition.getId()); + LOG.info("onPartitionKeyRangeChanged, get from pd:{}-{} -> {}", + partition.getGraphName(), partition.getId(), localPartition); + if (localPartition == null) { + return; + } + } + + var newPartition = localPartition.getProtoObj().toBuilder() + .setStartKey(partitionKeyRange.getKeyStart()) + .setEndKey(partitionKeyRange.getKeyEnd()) + .setState(Metapb.PartitionState.PState_Normal) + .build(); + partitionManager.updatePartition(newPartition, true); + + try { + engine.addRaftTask(RaftOperation.create(RaftOperation.SYNC_PARTITION, newPartition), + status -> { + LOG.info( + "onPartitionKeyRangeChanged, {}-{},key range: " + + "{}-{} status{}", + newPartition.getGraphName(), + newPartition.getId(), + partitionKeyRange.getKeyStart(), + partitionKeyRange.getKeyEnd(), + status); + }); + LOG.info("onPartitionKeyRangeChanged: {}, update to pd", newPartition); + partitionManager.updatePartitionToPD(List.of(newPartition)); + } catch (IOException e) { + LOG.error("Partition {}-{} onPartitionKeyRangeChanged exception {}", + newPartition.getGraphName(), newPartition.getId(), e); + } catch (PDException e) { + throw new RuntimeException(e); + } + } + } + + /** + * is the task exists + * + * @param taskId task id + * @param partId partition id + * @return true if exists, false otherwise + */ + private boolean preCheckTaskId(long taskId, int partId) { + + if (storeEngine.getPartitionEngine(partId) == null) { + return false; + } + + byte[] key = MetadataKeyHelper.getInstructionIdKey(taskId); + var wrapper = storeEngine.getPartitionManager().getWrapper(); + byte[] value = wrapper.get(partId, key); + + if (value != null) { + return true; + } + + wrapper.put(partId, key, new byte[0]); + return false; + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/PartitionStateListener.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/PartitionStateListener.java new file mode 100644 index 000000000..90b30f955 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/PartitionStateListener.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store; + +import java.util.List; + +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.store.meta.Partition; +import org.apache.hugegraph.store.meta.PartitionRole; + +public interface PartitionStateListener { + // 分区角色发生改变 + void partitionRoleChanged(Partition partition, PartitionRole newRole); + + // 分区发生改变 + void partitionShardChanged(Partition partition, List oldShards, + List newShards); +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/AbstractSelectIterator.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/AbstractSelectIterator.java new file mode 100644 index 000000000..da9bb5f8e --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/AbstractSelectIterator.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.business; + +import org.apache.hugegraph.backend.serializer.AbstractSerializer; +import org.apache.hugegraph.backend.serializer.BinarySerializer; +import org.apache.hugegraph.backend.store.BackendEntry; +import org.apache.hugegraph.iterator.CIter; +import org.apache.hugegraph.rocksdb.access.ScanIterator; +import org.apache.hugegraph.structure.HugeElement; +import org.apache.hugegraph.util.Bytes; +import org.apache.tinkerpop.gremlin.structure.Edge; + +import lombok.extern.slf4j.Slf4j; + +/** + * @date 2022/10/26 + **/ +@Slf4j +public abstract class AbstractSelectIterator implements ScanIterator { + protected ScanIterator iterator; + protected AbstractSerializer serializer; + + public AbstractSelectIterator() { + this.serializer = new BinarySerializer(); + } + + public boolean belongToMe(BackendEntry entry, + BackendEntry.BackendColumn column) { + return Bytes.prefixWith(column.name, entry.id().asBytes()); + } + + public HugeElement parseEntry(BackendEntry entry, boolean isVertex) { + try { + if (isVertex) { + return this.serializer.readVertex(null, entry); + } else { + CIter itr = + this.serializer.readEdges(null, entry, true, false); + + // Iterator itr = this.serializer.readEdges( + // null, entry, true, false).iterator(); + HugeElement el = null; + if (itr.hasNext()) { + el = (HugeElement) itr.next(); + } + return el; + } + } catch (Exception e) { + log.error("Failed to parse entry: {}", entry, e); + throw e; + } + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/BusinessHandler.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/BusinessHandler.java new file mode 100644 index 000000000..717e8140a --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/BusinessHandler.java @@ -0,0 +1,225 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.business; + +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import javax.annotation.concurrent.NotThreadSafe; + +import org.apache.hugegraph.pd.grpc.pulse.CleanType; +import org.apache.hugegraph.rocksdb.access.ScanIterator; +import org.apache.hugegraph.store.grpc.Graphpb; +import org.apache.hugegraph.store.grpc.common.Key; +import org.apache.hugegraph.store.grpc.common.OpType; +import org.apache.hugegraph.store.grpc.session.BatchEntry; +import org.apache.hugegraph.store.meta.base.DBSessionBuilder; +import org.apache.hugegraph.store.metric.HgStoreMetric; +import org.apache.hugegraph.store.raft.HgStoreStateMachine; +import org.apache.hugegraph.store.term.HgPair; +import org.apache.hugegraph.store.util.HgStoreException; +import org.rocksdb.Cache; +import org.rocksdb.MemoryUsageType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @projectName: hugegraph-store + * @package: org.apache.hugegraph.store.business + * @className: HgBusinessHandler + * @description: + * @date: 2021/10/19 19:07 + * @version: 1.0 + */ + +public interface BusinessHandler extends DBSessionBuilder { + + Logger log = LoggerFactory.getLogger(HgStoreStateMachine.class); + String tableUnknown = "unknown"; + String tableVertex = "g+v"; + String tableOutEdge = "g+oe"; + String tableInEdge = "g+ie"; + String tableIndex = "g+index"; + String tableTask = "g+task"; + String tableOlap = "g+olap"; + + String[] tables = new String[]{tableUnknown, tableVertex, tableOutEdge, tableInEdge, tableIndex, + tableTask, tableOlap}; + + void doPut(String graph, int code, String table, byte[] key, byte[] value) throws + HgStoreException; + + byte[] doGet(String graph, int code, String table, byte[] key) throws HgStoreException; + + ScanIterator scanAll(String graph, String table) throws HgStoreException; + + ScanIterator scanAll(String graph, String table, byte[] query) throws HgStoreException; + + ScanIterator scan(String graph, String table, int codeFrom, int codeTo) throws HgStoreException; + + ScanIterator scan(String graph, int code, String table, byte[] start, byte[] end, + int scanType) throws HgStoreException; + + ScanIterator scan(String graph, int code, String table, byte[] start, byte[] end, int scanType, + byte[] conditionQuery) throws HgStoreException; + + GraphStoreIterator scan(Graphpb.ScanPartitionRequest request); + + ScanIterator scanOriginal(Graphpb.ScanPartitionRequest request); + + ScanIterator scanPrefix(String graph, int code, String table, byte[] prefix, + int scanType) throws HgStoreException; + + ScanIterator scanPrefix(String graph, int code, String table, byte[] prefix) throws + HgStoreException; + + HgStoreMetric.Partition getPartitionMetric(String graph, int partId, + boolean accurateCount) throws HgStoreException; + + void batchGet(String graph, String table, Supplier> s, + Consumer> c) throws HgStoreException; + + void truncate(String graph, int partId) throws HgStoreException; + + void flushAll(); + + void closeAll(); + + // + Map getApproximateMemoryUsageByType(List caches); + + List getLeaderPartitionIds(String graph); + + HgStoreMetric.Graph getGraphMetric(String graph, int partId); + + void saveSnapshot(String snapshotPath, String graph, int partId) throws HgStoreException; + + void loadSnapshot(String snapshotPath, String graph, int partId, long version) throws + HgStoreException; + + long getLatestSequenceNumber(String graph, int partId); + + + // 扫描分区从 seqnum 开始的 kv + ScanIterator scanRaw(String graph, int partId, long seqNum) throws HgStoreException; + + void ingestSstFile(String graph, int partId, Map> sstFiles) throws + HgStoreException; + + //提交分区分裂,删除旧数据 + // 删除分区数据 + boolean deletePartition(String graph, int partId); + + //清理分区,删除多余的数据 + boolean cleanPartition(String graph, int partId); + + boolean cleanPartition(String graph, int partId, long startKey, long endKey, + CleanType cleanType); + + //所有指定分区图的所有 table 名 + List getTableNames(String graph, int partId); + + TxBuilder txBuilder(String graph, int partId); + + default void doBatch(String graph, int partId, List entryList) { + BusinessHandler.TxBuilder builder = txBuilder(graph, partId); + try { + for (BatchEntry b : entryList) { + Key start = b.getStartKey(); + String table = tables[b.getTable()]; + byte[] startKey = start.getKey().toByteArray(); + int number = b.getOpType().getNumber(); + if (number == OpType.OP_TYPE_PUT_VALUE) { + builder.put(start.getCode(), table, startKey, b.getValue().toByteArray()); + } else { + switch (number) { + case OpType.OP_TYPE_DEL_VALUE: + builder.del(start.getCode(), table, startKey); + continue; + case OpType.OP_TYPE_DEL_PREFIX_VALUE: + builder.delPrefix(start.getCode(), table, startKey); + continue; + case OpType.OP_TYPE_DEL_RANGE_VALUE: + builder.delRange(start.getCode(), table, startKey, + b.getEndKey().getKey().toByteArray()); + continue; + case OpType.OP_TYPE_DEL_SINGLE_VALUE: + builder.delSingle(start.getCode(), table, startKey); + continue; + case OpType.OP_TYPE_MERGE_VALUE: + builder.merge(start.getCode(), table, startKey, + b.getValue().toByteArray()); + continue; + default: + throw new IllegalArgumentException( + "unsupported batch-op-type: " + b.getOpType().name()); + } + } + } + builder.build().commit(); + } catch (Throwable e) { + String msg = + String.format("graph data %s-%s do batch insert with error:", graph, partId); + log.error(msg, e); + builder.build().rollback(); + throw e; + } + } + + boolean existsTable(String graph, int partId, String table); + + void createTable(String graph, int partId, String table); + + void deleteTable(String graph, int partId, String table); + + void dropTable(String graph, int partId, String table); + + boolean dbCompaction(String graphName, int partitionId); + + boolean dbCompaction(String graphName, int partitionId, String tableName); + + void destroyGraphDB(String graphName, int partId) throws HgStoreException; + + long count(String graphName, String table); + + @NotThreadSafe + interface TxBuilder { + TxBuilder put(int code, String table, byte[] key, byte[] value) throws HgStoreException; + + TxBuilder del(int code, String table, byte[] key) throws HgStoreException; + + TxBuilder delSingle(int code, String table, byte[] key) throws HgStoreException; + + TxBuilder delPrefix(int code, String table, byte[] prefix) throws HgStoreException; + + TxBuilder delRange(int code, String table, byte[] start, byte[] end) throws + HgStoreException; + + TxBuilder merge(int code, String table, byte[] key, byte[] value) throws HgStoreException; + + Tx build(); + } + + interface Tx { + void commit() throws HgStoreException; + + void rollback() throws HgStoreException; + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/BusinessHandlerImpl.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/BusinessHandlerImpl.java new file mode 100644 index 000000000..b406a35de --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/BusinessHandlerImpl.java @@ -0,0 +1,933 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.business; + + +import static org.apache.hugegraph.store.util.HgStoreConst.EMPTY_BYTES; +import static org.apache.hugegraph.store.util.HgStoreConst.SCAN_ALL_PARTITIONS_ID; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import javax.annotation.concurrent.NotThreadSafe; + +import org.apache.commons.configuration2.MapConfiguration; +import org.apache.commons.lang.ArrayUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.hugegraph.config.HugeConfig; +import org.apache.hugegraph.config.OptionSpace; +import org.apache.hugegraph.pd.grpc.pulse.CleanType; +import org.apache.hugegraph.rocksdb.access.DBStoreException; +import org.apache.hugegraph.rocksdb.access.RocksDBFactory; +import org.apache.hugegraph.rocksdb.access.RocksDBFactory.RocksdbChangedListener; +import org.apache.hugegraph.rocksdb.access.RocksDBOptions; +import org.apache.hugegraph.rocksdb.access.RocksDBSession; +import org.apache.hugegraph.rocksdb.access.ScanIterator; +import org.apache.hugegraph.rocksdb.access.SessionOperator; +import org.apache.hugegraph.store.HgStoreEngine; +import org.apache.hugegraph.store.cmd.CleanDataRequest; +import org.apache.hugegraph.store.grpc.Graphpb.ScanPartitionRequest; +import org.apache.hugegraph.store.grpc.Graphpb.ScanPartitionRequest.Request; +import org.apache.hugegraph.store.grpc.Graphpb.ScanPartitionRequest.ScanType; +import org.apache.hugegraph.store.meta.Partition; +import org.apache.hugegraph.store.meta.PartitionManager; +import org.apache.hugegraph.store.meta.asynctask.AsyncTaskState; +import org.apache.hugegraph.store.meta.asynctask.CleanTask; +import org.apache.hugegraph.store.metric.HgStoreMetric; +import org.apache.hugegraph.store.pd.PdProvider; +import org.apache.hugegraph.store.term.Bits; +import org.apache.hugegraph.store.term.HgPair; +import org.apache.hugegraph.store.util.HgStoreException; +import org.rocksdb.Cache; +import org.rocksdb.MemoryUsageType; + +import com.alipay.sofa.jraft.util.Utils; + +import lombok.extern.slf4j.Slf4j; + +/** + * @projectName: hugegraph-store + * @package: org.apache.hugegraph.store.business + * @className: HgBusinessImpl + * @description: TODO + * @date: 2021/10/19 19:17 + * @version: 1.0 + */ +@Slf4j +public class BusinessHandlerImpl implements BusinessHandler { + + private static final int batchSize = 10000; + private static final RocksDBFactory factory = RocksDBFactory.getInstance(); + private static final HashMap tableMapping = new HashMap<>() {{ + put(ScanType.SCAN_VERTEX, tableVertex); + put(ScanType.SCAN_EDGE, tableOutEdge); + }}; + private static final Map dbNames = new ConcurrentHashMap<>(); + + static { + int code = tableUnknown.hashCode(); + code = tableVertex.hashCode(); + code = tableOutEdge.hashCode(); + code = tableInEdge.hashCode(); + code = tableIndex.hashCode(); + code = tableTask.hashCode(); + code = tableTask.hashCode(); + log.debug("init table code:{}", code); + } + + private final PartitionManager partitionManager; + private final PdProvider provider; + private final InnerKeyCreator keyCreator; + + + public BusinessHandlerImpl(PartitionManager partitionManager) { + this.partitionManager = partitionManager; + this.provider = partitionManager.getPdProvider(); + this.keyCreator = new InnerKeyCreator(this); + + factory.addRocksdbChangedListener(new RocksdbChangedListener() { + @Override + public void onDBDeleteBegin(String dbName, String filePath) { + partitionManager.getDeletedFileManager().addDeletedFile(filePath); + } + + @Override + public void onDBDeleted(String dbName, String filePath) { + partitionManager.getDeletedFileManager().removeDeletedFile(filePath); + } + + @Override + public void onDBSessionReleased(RocksDBSession dbSession) { + + } + }); + } + + public static HugeConfig initRocksdb(Map rocksdbConfig, + RocksdbChangedListener listener) { + // 注册 rocksdb 配置 + OptionSpace.register("rocksdb", "org.apache.hugegraph.rocksdb.access.RocksDBOptions"); + RocksDBOptions.instance(); + HugeConfig hConfig = new HugeConfig(new MapConfiguration(rocksdbConfig)); + factory.setHugeConfig(hConfig); + if (listener != null) { + factory.addRocksdbChangedListener(listener); + } + return hConfig; + } + + public static String getDbName(int partId) { + String dbName = dbNames.get(partId); + if (dbName == null) { + dbName = String.format("%05d", partId); + dbNames.put(partId, dbName); + } + // 每个分区对应一个 rocksdb 实例,因此 rocksdb 实例名为 partId + return dbName; + } + + @Override + public void doPut(String graph, int code, String table, byte[] key, byte[] value) throws + HgStoreException { + + int partId = provider.getPartitionByCode(graph, code).getId(); + try (RocksDBSession dbSession = getSession(graph, table, partId)) { + SessionOperator op = dbSession.sessionOp(); + try { + op.prepare(); + byte[] targetKey = keyCreator.getKey(partId, graph, code, key); + op.put(table, targetKey, value); + op.commit(); + } catch (Exception e) { + log.error("Graph " + graph + " doPut exception", e); + op.rollback(); + throw new HgStoreException(HgStoreException.EC_RKDB_DOPUT_FAIL, e.toString()); + } + } + } + + @Override + public byte[] doGet(String graph, int code, String table, byte[] key) throws HgStoreException { + int partId = provider.getPartitionByCode(graph, code).getId(); + + try (RocksDBSession dbSession = getSession(graph, table, partId)) { + byte[] targetKey = keyCreator.getKey(partId, graph, code, key); + return dbSession.sessionOp().get(table, targetKey); + } catch (Exception e) { + log.error("Graph " + graph + " doGet exception", e); + throw new HgStoreException(HgStoreException.EC_RKDB_DOGET_FAIL, e.toString()); + } + } + + @Override + public ScanIterator scanAll(String graph, String table) throws HgStoreException { + List ids = this.getLeaderPartitionIds(graph); + + BiFunction function = (id, position) -> { + try (RocksDBSession dbSession = getSession(graph, table, id)) { + return new InnerKeyFilter(dbSession.sessionOp().scan(table, position == null ? + keyCreator.getStartKey( + id, graph) : + keyCreator.getStartKey( + id, graph, + position), + keyCreator.getEndKey(id, + graph), + ScanIterator.Trait.SCAN_LT_END)); + } + }; + return MultiPartitionIterator.of(ids, function); + } + + @Override + public ScanIterator scanAll(String graph, String table, byte[] query) throws HgStoreException { + return scanAll(graph, table); + } + + @Override + public ScanIterator scan(String graph, int code, String table, byte[] start, byte[] end, + int scanType) throws HgStoreException { + List ids; + if (code == SCAN_ALL_PARTITIONS_ID) { + ids = this.getLeaderPartitionIds(graph); + } else { + ids = new ArrayList<>(); + ids.add(partitionManager.getPartitionIdByCode(graph, code)); + } + BiFunction function = (id, position) -> { + byte[] endKey; + int type; + if (ArrayUtils.isEmpty(end)) { + endKey = keyCreator.getEndKey(id, graph); + type = ScanIterator.Trait.SCAN_LT_END; + } else { + endKey = keyCreator.getEndKey(id, graph, end); + type = scanType; + } + try (RocksDBSession dbSession = getSession(graph, table, id)) { + return new InnerKeyFilter(dbSession.sessionOp().scan(table, + keyCreator.getStartKey(id, + graph, + toPosition( + start, + position)), + endKey, type)); + } + }; + return MultiPartitionIterator.of(ids, function); + } + + /** + * 根据 keyCode 范围返回数据,左闭右开 + * + * @param graph + * @param table + * @param codeFrom 起始 code,包含该值 + * @param codeTo 结束 code,不包含该值 + * @return + * @throws HgStoreException + */ + @Override + public ScanIterator scan(String graph, String table, int codeFrom, int codeTo) throws + HgStoreException { + + List ids = new ArrayList<>(); + ids.add(partitionManager.getPartitionIdByCode(graph, codeFrom)); + BiFunction function = (id, position) -> { + try (RocksDBSession dbSession = getSession(graph, table, id)) { + byte[] startKey; + if (position != null) { + startKey = keyCreator.getStartKey(id, graph, position); + } else { + startKey = keyCreator.getStartKey(id, graph); + } + byte[] endKey = keyCreator.getEndKey(id, graph); + ScanIterator iterator = dbSession.sessionOp().scan(table, startKey, endKey, + ScanIterator.Trait.SCAN_LT_END); + return new InnerKeyFilter(iterator, codeFrom, codeTo); + } + }; + return MultiPartitionIterator.of(ids, function); + } + + @Override + public ScanIterator scan(String graph, int code, String table, byte[] start, byte[] end, + int scanType, byte[] conditionQuery) throws HgStoreException { + ScanIterator it = null; + if ((scanType & ScanIterator.Trait.SCAN_HASHCODE) == ScanIterator.Trait.SCAN_HASHCODE) { + int codeFrom = Bits.toInt(start); + int codeTo = Bits.toInt(end); + it = scan(graph, table, codeFrom, codeTo); + } else { + it = scan(graph, code, table, start, end, scanType); + } + return it; + } + + @Override + public GraphStoreIterator scan(ScanPartitionRequest spr) throws HgStoreException { + return new GraphStoreIterator(scanOriginal(spr), spr); + } + + @Override + public ScanIterator scanOriginal(ScanPartitionRequest spr) throws HgStoreException { + Request request = spr.getScanRequest(); + String graph = request.getGraphName(); + List ids; + int partitionId = request.getPartitionId(); + int startCode = request.getStartCode(); + int endCode = request.getEndCode(); + if (partitionId == SCAN_ALL_PARTITIONS_ID) { + ids = this.getLeaderPartitionIds(graph); + } else { + ids = new ArrayList<>(); + if (startCode != 0 || endCode != 0) { + ids.add(partitionManager.getPartitionIdByCode(graph, startCode)); + } else { + ids.add(partitionId); + } + } + String table = request.getTable(); + if (StringUtils.isEmpty(table)) { + table = tableMapping.get(request.getScanType()); + } + int scanType = request.getBoundary(); + if (scanType == 0) { + scanType = ScanIterator.Trait.SCAN_LT_END; + } + String tab = table; + int st = scanType; + BiFunction func = (id, position) -> { + try (RocksDBSession dbSession = getSession(graph, tab, id)) { + byte[] startPos = toPosition(EMPTY_BYTES, position); + byte[] startKey = keyCreator.getStartKey(id, graph, startPos); + byte[] endKey = keyCreator.getEndKey(id, graph); + ScanIterator iter = dbSession.sessionOp().scan(tab, startKey, endKey, st); + return new InnerKeyFilter(iter); + } + }; + return MultiPartitionIterator.of(ids, func); + } + + @Override + public ScanIterator scanPrefix(String graph, int code, String table, byte[] prefix, + int scanType) throws HgStoreException { + List ids; + if (code == SCAN_ALL_PARTITIONS_ID) { + ids = this.getLeaderPartitionIds(graph); + } else { + ids = new ArrayList<>(); + ids.add(partitionManager.getPartitionIdByCode(graph, code)); + } + BiFunction function = (id, position) -> { + try (RocksDBSession dbSession = getSession(graph, table, id)) { + return new InnerKeyFilter(dbSession.sessionOp().scan(table, + keyCreator.getPrefixKey(id, + graph, + toPosition( + prefix, + position)), + scanType)); + } + }; + return MultiPartitionIterator.of(ids, function); + } + + @Override + public ScanIterator scanPrefix(String graph, int code, String table, byte[] prefix) throws + HgStoreException { + + return scanPrefix(graph, code, table, prefix, 0); + } + + private byte[] toPosition(byte[] start, byte[] position) { + if (position == null || position.length == 0) { + return start; + } + return position; + } + + @Override + public HgStoreMetric.Partition getPartitionMetric(String graph, int partId, + boolean accurateCount) throws + HgStoreException { + // get key count + Map countMap = null; + Map sizeMap = null; + + try (RocksDBSession dbSession = getSession(graph, partId)) { + countMap = dbSession.getKeyCountPerCF(keyCreator.getStartKey(partId, graph), + keyCreator.getEndKey(partId, graph), + accurateCount); + sizeMap = dbSession.getApproximateCFDataSize(keyCreator.getStartKey(partId, graph), + keyCreator.getEndKey(partId, graph)); + + + HgStoreMetric.Partition partMetric = new HgStoreMetric.Partition(); + partMetric.setPartitionId(partId); + + List tables = new ArrayList<>(sizeMap.size()); + for (String tableName : sizeMap.keySet()) { + HgStoreMetric.Table table = new HgStoreMetric.Table(); + table.setTableName(tableName); + table.setKeyCount(countMap.get(tableName)); + table.setDataSize(sizeMap.get(tableName)); + tables.add(table); + } + + partMetric.setTables(tables); + return partMetric; + } + } + + @Override + public HgStoreMetric.Graph getGraphMetric(String graph, int partId) { + HgStoreMetric.Graph graphMetric = new HgStoreMetric.Graph(); + try (RocksDBSession dbSession = getSession(graph, partId)) { + graphMetric.setApproxDataSize( + dbSession.getApproximateDataSize(keyCreator.getStartKey(partId, graph), + keyCreator.getEndKey(partId, graph))); + graphMetric.setApproxKeyCount(dbSession.getEstimateNumKeys()); + + return graphMetric; + } + } + + @Override + public void batchGet(String graph, String table, Supplier> s, + Consumer> c) throws HgStoreException { + + int count = 0; + while (true) { + // Prevent dead loops + if (count++ == Integer.MAX_VALUE) { + break; + } + HgPair duality = s.get(); + if (duality == null) { + break; + } + int code = duality.getKey(); + byte[] key = duality.getValue(); + + int partId = provider.getPartitionByCode(graph, code).getId(); + + try (RocksDBSession dbSession = getSession(graph, table, partId)) { + byte[] targetKey = keyCreator.getKey(partId, graph, code, key); + byte[] value = dbSession.sessionOp().get(table, targetKey); + c.accept(new HgPair<>(key, value)); + } + + } + } + + /** + * 清空图数据 + */ + @Override + public void truncate(String graphName, int partId) throws HgStoreException { + // 每个分区对应一个 rocksdb 实例,因此 rocksdb 实例名为 rocksdb + partId + try (RocksDBSession dbSession = getSession(graphName, partId)) { + dbSession.sessionOp().deleteRange(keyCreator.getStartKey(partId, graphName), + keyCreator.getEndKey(partId, graphName)); + // 释放图 ID + keyCreator.delGraphId(partId, graphName); + } + } + + @Override + public void flushAll() { + log.warn("Flush all!!! "); + factory.getGraphNames().forEach(dbName -> { + try (RocksDBSession dbSession = factory.queryGraphDB(dbName)) { + if (dbSession != null) { + dbSession.flush(false); + } + } + }); + } + + @Override + public void closeAll() { + log.warn("close all db!!! "); + factory.getGraphNames().forEach(dbName -> { + factory.releaseGraphDB(dbName); + }); + } + + @Override + public Map getApproximateMemoryUsageByType(List caches) { + try { + return factory.getApproximateMemoryUsageByType(null, caches); + } catch (Exception e) { + return new HashMap<>(); + } + } + + @Override + public List getLeaderPartitionIds(String graph) { + return partitionManager.getLeaderPartitionIds(graph); + } + + @Override + public void saveSnapshot(String snapshotPath, String graph, int partId) throws + HgStoreException { + try (RocksDBSession dbSession = getSession(graph, partId)) { + dbSession.saveSnapshot(snapshotPath); + } catch (DBStoreException e) { + throw new HgStoreException(HgStoreException.EC_RKDB_EXPORT_SNAPSHOT_FAIL, e.toString()); + } + } + + @Override + public void loadSnapshot(String snapshotPath, String graph, int partId, long v1) throws + HgStoreException { + try (RocksDBSession dbSession = getSession(graph, partId)) { + dbSession.loadSnapshot(snapshotPath, v1); + keyCreator.clearCache(partId); + factory.destroyGraphDB(dbSession.getGraphName()); + } catch (DBStoreException e) { + throw new HgStoreException(HgStoreException.EC_RKDB_IMPORT_SNAPSHOT_FAIL, e.toString()); + } + } + + @Override + public long getLatestSequenceNumber(String graph, int partId) { + try (RocksDBSession dbSession = getSession(graph, partId)) { + return dbSession.getLatestSequenceNumber(); + } + } + + @Override + public ScanIterator scanRaw(String graph, int partId, long seqNum) throws HgStoreException { + try (RocksDBSession dbSession = getSession(graph, partId)) { + return dbSession.sessionOp().scanRaw(null, null, seqNum); + } catch (DBStoreException e) { + throw new HgStoreException(HgStoreException.EC_RKDB_EXPORT_SNAPSHOT_FAIL, e.toString()); + } + } + + @Override + public void ingestSstFile(String graph, int partId, Map> sstFiles) throws + HgStoreException { + try (RocksDBSession dbSession = getSession(graph, partId)) { + dbSession.ingestSstFile(sstFiles); + } + } + + @Override + public boolean cleanPartition(String graph, int partId) { + Partition partition = partitionManager.getPartitionFromPD(graph, partId); + cleanPartition(graph, partId, partition.getStartKey(), partition.getEndKey(), + CleanType.CLEAN_TYPE_KEEP_RANGE); + return true; + } + + @Override + public boolean cleanPartition(String graph, int partId, long startKey, long endKey, + CleanType cleanType) { + Partition partition = partitionManager.getPartition(graph, partId); + if (partition == null) { + return true; + } + + log.info("cleanPartition: graph {}, part id: {}, {} -> {}, cleanType:{}", graph, partId, + startKey, endKey, cleanType); + + var taskManager = HgStoreEngine.getInstance().getPartitionEngine(partId).getTaskManager(); + CleanDataRequest request = new CleanDataRequest(); + request.setPartitionId(partId); + request.setGraphName(graph); + request.setKeyStart(startKey); + request.setKeyEnd(endKey); + request.setCleanType(cleanType); + var cleanTask = new CleanTask(partId, graph, AsyncTaskState.START, request); + taskManager.putAsyncTask(cleanTask); + + Utils.runInThread(() -> { + cleanPartition(partition, code -> { + // in range + boolean flag = code >= startKey && code < endKey; + return (cleanType == CleanType.CLEAN_TYPE_KEEP_RANGE) == flag; + }); + // 可能被 destroy 了 + if (HgStoreEngine.getInstance().getPartitionEngine(partId) != null) { + taskManager.updateAsyncTaskState(partId, graph, cleanTask.getId(), + AsyncTaskState.SUCCESS); + } + }); + return true; + } + + /** + * 清理分区数据,删除非本分区的数据 + * 遍历 partId 的所有 key,读取 code,if code >= splitKey 生成新的 key,写入 newPartId + */ + private boolean cleanPartition(Partition partition, + Function belongsFunction) { + log.info("Partition {}-{} cleanPartition begin... {}", partition.getGraphName(), + partition.getId(), partition); + int counter = 0; + + SessionOperator op = getSession(partition.getGraphName(), partition.getId()).sessionOp(); + try { + ScanIterator cfIterator = + op.scanRaw(keyCreator.getStartKey(partition.getId(), partition.getGraphName()), + keyCreator.getEndKey(partition.getId(), partition.getGraphName()), + 0); + while (cfIterator.hasNext()) { + ScanIterator iterator = cfIterator.next(); + String table = new String(cfIterator.position()); + long deleted = 0; + long total = 0; + while (iterator.hasNext()) { + total += 1; + RocksDBSession.BackendColumn col = iterator.next(); + int keyCode = keyCreator.parseKeyCode(col.name); + // if (keyCode < partition.getStartKey() || keyCode >= partition.getEndKey()) { + if (!belongsFunction.apply(keyCode)) { + if (counter == 0) { + op.prepare(); + } + op.delete(table, col.name); // 删除旧数据 + if (++counter > batchSize) { + op.commit(); + counter = 0; + } + deleted += 1; + } + } + iterator.close(); + log.info("partition {}-{}, table:{}, delete keys {}, total:{}", + partition.getGraphName(), partition.getId(), table, deleted, total); + } + cfIterator.close(); + } catch (Exception e) { + log.error("Partition {}-{} cleanPartition exception {}", partition.getGraphName(), + partition.getId(), e); + op.rollback(); + throw e; + } finally { + if (counter > 0) { + try { + op.commit(); + } catch (Exception e) { + op.rollback(); + throw e; + } + } + op.getDBSession().close(); + } + op.compactRange(); + log.info("Partition {}-{} cleanPartition end", partition.getGraphName(), partition.getId()); + return true; + } + + @Override + public boolean deletePartition(String graph, int partId) { + try { + deleteGraphDatabase(graph, partId); + } catch (Exception e) { + log.error("Partition {}-{} deletePartition exception {}", graph, partId, e); + } + return true; + } + + @Override + public List getTableNames(String graph, int partId) { + try (RocksDBSession dbSession = getSession(graph, partId)) { + List tables = null; + tables = dbSession.getTables().keySet().stream().collect(Collectors.toList()); + return tables; + } + } + + private RocksDBSession getSession(String graph, String table, int partId) throws + HgStoreException { + RocksDBSession dbSession = getSession(partId); + dbSession.checkTable(table); + return dbSession; + } + + private RocksDBSession getSession(String graphName, int partId) throws HgStoreException { + return getSession(partId); + } + + /** + * 获取 dbsession,不更新 dbsession 活跃时间 + */ + @Override + public RocksDBSession getSession(int partId) throws HgStoreException { + // 每个分区对应一个 rocksdb 实例,因此 rocksdb 实例名为 rocksdb + partId + String dbName = getDbName(partId); + RocksDBSession dbSession = factory.queryGraphDB(dbName); + if (dbSession == null) { + long version = HgStoreEngine.getInstance().getCommittedIndex(partId); + dbSession = + factory.createGraphDB(partitionManager.getDbDataPath(partId, dbName), dbName, + version); + if (dbSession == null) { + log.info("failed to create a new graph db: {}", dbName); + throw new HgStoreException(HgStoreException.EC_RKDB_CREATE_FAIL, + "failed to create a new graph db: {}", dbName); + } + } + dbSession.setDisableWAL(true); //raft 模式,关闭 rocksdb 日志 + return dbSession; + } + + private void deleteGraphDatabase(String graph, int partId) throws IOException { + truncate(graph, partId); + + } + + private PartitionManager getPartManager() { + return this.partitionManager; + } + + @Override + public TxBuilder txBuilder(String graph, int partId) throws HgStoreException { + return new TxBuilderImpl(graph, partId, getSession(graph, partId)); + } + + @Override + public boolean existsTable(String graph, int partId, String table) { + try (RocksDBSession session = getSession(graph, partId)) { + return session.tableIsExist(table); + } + } + + @Override + public void createTable(String graph, int partId, String table) { + try (RocksDBSession session = getSession(graph, partId)) { + session.checkTable(table); + } + } + + @Override + public void deleteTable(String graph, int partId, String table) { + dropTable(graph, partId, table); + // todo 检查表是否为空,为空则真实删除表 +// try (RocksDBSession session = getOrCreateGraphDB(graph, partId)) { +// session.deleteTables(table); +// } + } + + @Override + public void dropTable(String graph, int partId, String table) { + try (RocksDBSession session = getSession(graph, partId)) { +// session.dropTables(table); + session.sessionOp().deleteRange(table, keyCreator.getStartKey(partId, graph), + keyCreator.getEndKey(partId, graph)); + } + } + + /** + * 对 rocksdb 进行 compaction + */ + @Override + public boolean dbCompaction(String graphName, int partitionId) { + return this.dbCompaction(graphName, partitionId, ""); + } + + /** + * 对 rocksdb 进行 compaction + */ + @Override + public boolean dbCompaction(String graphName, int partitionId, String tableName) { + try (RocksDBSession session = getSession(graphName, partitionId)) { + SessionOperator op = session.sessionOp(); + if (tableName.isEmpty()) { + op.compactRange(); + } else { + op.compactRange(tableName); + } + } + + log.info("Partition {}-{} dbCompaction end", graphName, partitionId); + return true; + } + + /** + * 销毁图,并删除数据文件 + * + * @param graphName + * @param partId + */ + @Override + public void destroyGraphDB(String graphName, int partId) throws HgStoreException { + // 每个图每个分区对应一个 rocksdb 实例,因此 rocksdb 实例名为 rocksdb + partId + String dbName = getDbName(partId); + + factory.destroyGraphDB(dbName); + keyCreator.clearCache(partId); + } + + @Override + public long count(String graph, String table) { + List ids = this.getLeaderPartitionIds(graph); + Long all = ids.parallelStream().map((id) -> { + InnerKeyFilter it = null; + try (RocksDBSession dbSession = getSession(graph, table, id)) { + long count = 0; + SessionOperator op = dbSession.sessionOp(); + it = new InnerKeyFilter(op.scan(table, keyCreator.getStartKey(id, graph), + keyCreator.getEndKey(id, graph), + ScanIterator.Trait.SCAN_LT_END)); + while (it.hasNext()) { + it.next(); + count++; + } + return count; + } catch (Exception e) { + throw e; + } finally { + if (it != null) { + try { + it.close(); + } catch (Exception e) { + + } + } + } + }).collect(Collectors.summingLong(l -> l)); + return all; + } + + @NotThreadSafe + private class TxBuilderImpl implements TxBuilder { + private final String graph; + private final int partId; + private final RocksDBSession dbSession; + private final SessionOperator op; + + private TxBuilderImpl(String graph, int partId, RocksDBSession dbSession) { + this.graph = graph; + this.partId = partId; + this.dbSession = dbSession; + this.op = this.dbSession.sessionOp(); + this.op.prepare(); + } + + @Override + public TxBuilder put(int code, String table, byte[] key, byte[] value) throws + HgStoreException { + try { + byte[] targetKey = keyCreator.getKey(this.partId, graph, code, key); + this.op.put(table, targetKey, value); + } catch (DBStoreException e) { + throw new HgStoreException(HgStoreException.EC_RKDB_DOPUT_FAIL, e.toString()); + } + return this; + } + + @Override + public TxBuilder del(int code, String table, byte[] key) throws HgStoreException { + try { + byte[] targetKey = keyCreator.getKey(this.partId, graph, code, key); + this.op.delete(table, targetKey); + } catch (DBStoreException e) { + throw new HgStoreException(HgStoreException.EC_RKDB_DODEL_FAIL, e.toString()); + } + + return this; + } + + @Override + public TxBuilder delSingle(int code, String table, byte[] key) throws HgStoreException { + + try { + byte[] targetKey = keyCreator.getKey(this.partId, graph, code, key); + op.deleteSingle(table, targetKey); + } catch (DBStoreException e) { + throw new HgStoreException(HgStoreException.EC_RDKDB_DOSINGLEDEL_FAIL, + e.toString()); + } + + return this; + } + + @Override + public TxBuilder delPrefix(int code, String table, byte[] prefix) throws HgStoreException { + + try { + this.op.deletePrefix(table, keyCreator.getPrefixKey(this.partId, graph, prefix)); + } catch (DBStoreException e) { + throw new HgStoreException(HgStoreException.EC_RKDB_DODELPREFIX_FAIL, e.toString()); + } + + return this; + } + + @Override + public TxBuilder delRange(int code, String table, byte[] start, byte[] end) throws + HgStoreException { + + try { + this.op.deleteRange(table, keyCreator.getStartKey(this.partId, graph, start), + keyCreator.getEndKey(this.partId, graph, end)); + } catch (DBStoreException e) { + throw new HgStoreException(HgStoreException.EC_RKDB_DODELRANGE_FAIL, e.toString()); + } + + return this; + } + + @Override + public TxBuilder merge(int code, String table, byte[] key, byte[] value) throws + HgStoreException { + + try { + byte[] targetKey = keyCreator.getKey(this.partId, graph, code, key); + op.merge(table, targetKey, value); + } catch (DBStoreException e) { + throw new HgStoreException(HgStoreException.EC_RKDB_DOMERGE_FAIL, e.toString()); + } + return this; + } + + @Override + public Tx build() { + return new Tx() { + @Override + public void commit() throws HgStoreException { + op.commit(); // commit发生异常后,必须调用rollback,否则造成锁未释放 + dbSession.close(); + } + + @Override + public void rollback() throws HgStoreException { + try { + op.rollback(); + } finally { + dbSession.close(); + } + } + }; + } + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/DataMover.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/DataMover.java new file mode 100644 index 000000000..63eee071e --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/DataMover.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.business; + +import java.util.List; + +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.store.cmd.BatchPutRequest; +import org.apache.hugegraph.store.cmd.CleanDataRequest; +import org.apache.hugegraph.store.cmd.HgCmdClient; +import org.apache.hugegraph.store.cmd.UpdatePartitionResponse; + +import com.alipay.sofa.jraft.Status; + +/** + * 数据转移接口,实现分区分裂和合并,支持跨机器转移数据 + */ +public interface DataMover { + void setBusinessHandler(BusinessHandler handler); + + void setCmdClient(HgCmdClient client); + + + /** + * 拷贝分区source内的数据到其他分区targets + * 一个分区,迁移到多个分区 + * + * @param source source partition + * @param targets target partitions + * @return execution status + * @throws Exception execution exception + */ + Status moveData(Metapb.Partition source, List targets) throws Exception; + + /** + * 将source target的数据全部拷贝到target上 + * 从一个分区迁移到另外一个分区 + * + * @param source source partition + * @param target target partition + * @return execution result + * @throws Exception execution exception + */ + Status moveData(Metapb.Partition source, Metapb.Partition target) throws Exception; + + // 同步副本之间的分区状态 + UpdatePartitionResponse updatePartitionState(Metapb.Partition partition, + Metapb.PartitionState state); + + // 同步副本之间分区的范围 + UpdatePartitionResponse updatePartitionRange(Metapb.Partition partition, int startKey, + int endKey); + + // 清理分区partition内的无效数据 + void cleanData(Metapb.Partition partition); + + // 写入数据 + void doWriteData(BatchPutRequest request); + + void doCleanData(CleanDataRequest request); +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/DefaultDataMover.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/DefaultDataMover.java new file mode 100644 index 000000000..352b884e6 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/DefaultDataMover.java @@ -0,0 +1,278 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.business; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.function.BiFunction; + +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.pd.grpc.pulse.CleanType; +import org.apache.hugegraph.rocksdb.access.RocksDBSession; +import org.apache.hugegraph.rocksdb.access.ScanIterator; +import org.apache.hugegraph.store.cmd.BatchPutRequest; +import org.apache.hugegraph.store.cmd.BatchPutResponse; +import org.apache.hugegraph.store.cmd.CleanDataRequest; +import org.apache.hugegraph.store.cmd.HgCmdClient; +import org.apache.hugegraph.store.cmd.UpdatePartitionRequest; +import org.apache.hugegraph.store.cmd.UpdatePartitionResponse; +import org.apache.hugegraph.store.term.Bits; + +import com.alipay.sofa.jraft.Status; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class DefaultDataMover implements DataMover { + public static int Batch_Put_Size = 2000; + private BusinessHandler businessHandler; + private HgCmdClient client; + + private static Metapb.Partition findPartition(List partitions, int code) { + for (Metapb.Partition partition : partitions) { + if (code >= partition.getStartKey() && code < partition.getEndKey()) { + return partition; + } + } + return null; + } + + @Override + public void setBusinessHandler(BusinessHandler handler) { + this.businessHandler = handler; + } + + @Override + public void setCmdClient(HgCmdClient client) { + this.client = client; + } + + @Override + public Status moveData(Metapb.Partition source, List targets) throws + Exception { + Status status = Status.OK(); + // 开始移动数据之前,先把分区下线 + UpdatePartitionResponse response = + updatePartitionState(source, Metapb.PartitionState.PState_Offline); + if (response.getStatus().isOK()) { + status = moveData(source, targets, DefaultDataMover::findPartition); + + // 数据迁移成功后,设置新分区范围和上线新分区 + for (var target : targets) { + if (status.isOk()) { + if (!(updatePartitionRange(target, (int) target.getStartKey(), + (int) target.getEndKey()) + .getStatus().isOK() + && updatePartitionState(target, + Metapb.PartitionState.PState_Normal).getStatus() + .isOK())) { + status.setError(-3, "new partition online fail"); + } + } + } + } else { + status.setError(-1, "source partition offline fail"); + } + + updatePartitionState(source, Metapb.PartitionState.PState_Normal); + + return status; + } + + @Override + public Status moveData(Metapb.Partition source, Metapb.Partition target) throws Exception { + // 只写入 target + return moveData(source, Collections.singletonList(target), (partitions, integer) -> target); + } + + /** + * move data from partition to targets + * + * @param source source partition + * @param targets target partitions + * @param partitionSelector the key of source partition belongs which target + * @return execution result + * @throws Exception exception when put data + */ + + private Status moveData(Metapb.Partition source, List targets, + BiFunction, Integer, Metapb.Partition> partitionSelector) + throws Exception { + + Status status = Status.OK(); + String graphName = source.getGraphName(); + List tables = businessHandler.getTableNames(graphName, source.getId()); + + log.info("moveData, graph:{}, partition id:{} tables:{}, {}-{}", source.getGraphName(), + source.getId(), tables, + source.getStartKey(), source.getEndKey()); + WriteBatch batch = new WriteBatch(graphName); + // target partition : count + Map moveCount = new HashMap<>(); + + for (String table : tables) { + int total = 0; + moveCount.clear(); + + try (ScanIterator iterator = + businessHandler.scan(graphName, table, (int) source.getStartKey(), + (int) source.getEndKey())) { + int count = 0; + while (iterator.hasNext() && status.isOk()) { + total += 1; + RocksDBSession.BackendColumn entry = iterator.next(); + byte[] innerKey = entry.name; + byte[] key = Arrays.copyOfRange(innerKey, 0, innerKey.length - Short.BYTES); + int code = Bits.getShort(innerKey, innerKey.length - Short.BYTES); + Metapb.Partition partition = partitionSelector.apply(targets, code); + if (partition != null) { + moveCount.put(partition.getId(), + moveCount.getOrDefault(partition.getId(), 0L) + 1); + batch.add(partition.getId(), + BatchPutRequest.KV.of(table, code, key, entry.value)); + if (++count >= Batch_Put_Size) { + if (!batch.sync()) { + status.setError(-2, "move data fail"); + } + count = 0; + } + } + } + if (count > 0) { + if (!batch.sync()) { + status.setError(-2, "move data fail"); + } + } + + for (var pair : moveCount.entrySet()) { + log.info("{}-{}, table: {}, move to partition id {}, count:{}, total:{}", + source.getGraphName(), source.getId(), table, pair.getKey(), + pair.getValue(), total); + } + } + } + + return status; + } + + @Override + public UpdatePartitionResponse updatePartitionState(Metapb.Partition partition, + Metapb.PartitionState state) { + // 分区分裂时,主动需要查找 leader 进行同步信息 + UpdatePartitionRequest request = new UpdatePartitionRequest(); + request.setWorkState(state); + request.setPartitionId(partition.getId()); + request.setGraphName(partition.getGraphName()); + return client.raftUpdatePartition(request); + } + + @Override + public UpdatePartitionResponse updatePartitionRange(Metapb.Partition partition, int startKey, + int endKey) { + // 分区分裂时,主动需要查找 leader 进行同步信息 + UpdatePartitionRequest request = new UpdatePartitionRequest(); + request.setStartKey(startKey); + request.setEndKey(endKey); + request.setPartitionId(partition.getId()); + request.setGraphName(partition.getGraphName()); + return client.raftUpdatePartition(request); + } + + @Override + public void cleanData(Metapb.Partition partition) { + String graphName = partition.getGraphName(); + CleanDataRequest request = new CleanDataRequest(); + request.setGraphName(graphName); + request.setPartitionId(partition.getId()); + request.setCleanType(CleanType.CLEAN_TYPE_KEEP_RANGE); + request.setKeyStart(partition.getStartKey()); + request.setKeyEnd(partition.getEndKey()); + request.setDeletePartition(false); + + try { + client.cleanData(request); + } catch (Exception e) { + log.error("exception ", e); + } + } + + @Override + public void doWriteData(BatchPutRequest request) { + BusinessHandler.TxBuilder tx = + businessHandler.txBuilder(request.getGraphName(), request.getPartitionId()); + for (BatchPutRequest.KV kv : request.getEntries()) { + tx.put(kv.getCode(), kv.getTable(), kv.getKey(), kv.getValue()); + } + tx.build().commit(); + } + + @Override + public void doCleanData(CleanDataRequest request) { + // raft 执行真实数据的清理 + businessHandler.cleanPartition(request.getGraphName(), request.getPartitionId(), + request.getKeyStart(), request.getKeyEnd(), + request.getCleanType()); + } + + class WriteBatch { + private final Map> data = new HashMap<>(); + private final String graphName; + + public WriteBatch(String graphName) { + this.graphName = graphName; + } + + public WriteBatch add(int partition, BatchPutRequest.KV kv) { + if (!data.containsKey(partition)) { + data.put(partition, new LinkedList<>()); + } + data.get(partition).add(kv); + return this; + } + + public Boolean sync() throws Exception { + Boolean ret = true; + for (Map.Entry> entry : data.entrySet()) { + ret = ret && sendData(entry.getKey(), entry.getValue()); + } + for (List list : data.values()) { + list.clear(); + } + + return ret; + } + + public Boolean sendData(Integer partId, List kvs) throws Exception { + BatchPutRequest request = new BatchPutRequest(); + request.setGraphName(graphName); + request.setPartitionId(partId); + request.setEntries(kvs); + BatchPutResponse response = client.batchPut(request); + if (response == null || !response.getStatus().isOK()) { + log.error("sendData moveData error, pId:{} status:{}", partId, + response != null ? response.getStatus() : "EMPTY_RESPONSE"); + return false; + } + return true; + } + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/FilterIterator.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/FilterIterator.java new file mode 100644 index 000000000..cf4e7502e --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/FilterIterator.java @@ -0,0 +1,126 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.business; + +import java.util.Arrays; + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.hugegraph.backend.query.ConditionQuery; +import org.apache.hugegraph.backend.serializer.BinaryBackendEntry; +import org.apache.hugegraph.backend.store.BackendEntry; +import org.apache.hugegraph.rocksdb.access.RocksDBSession.BackendColumn; +import org.apache.hugegraph.rocksdb.access.ScanIterator; +import org.apache.hugegraph.structure.HugeElement; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class FilterIterator extends + AbstractSelectIterator + implements ScanIterator { + + private final ConditionQuery query; + T current = null; + + public FilterIterator(ScanIterator iterator, ConditionQuery query) { + super(); + this.iterator = iterator; + this.query = query; + // log.info("operator sinking is used to filter data:{}", + // query.toString()); + } + + public static ScanIterator of(ScanIterator it, byte[] conditionQuery) { + if (ArrayUtils.isEmpty(conditionQuery)) { + return it; + } + ConditionQuery query = ConditionQuery.fromBytes(conditionQuery); + return new FilterIterator(it, query); + } + + @Override + public boolean hasNext() { + boolean match = false; + if (this.query.resultType().isVertex() || + this.query.resultType().isEdge()) { + BackendEntry entry = null; + while (iterator.hasNext()) { + current = iterator.next(); + BackendEntry.BackendColumn column = + BackendEntry.BackendColumn.of( + current.name, current.value); + BackendEntry.BackendColumn[] columns = + new BackendEntry.BackendColumn[]{column}; + if (entry == null || !belongToMe(entry, column) || + this.query.resultType().isEdge()) { + entry = new BinaryBackendEntry(query.resultType(), + current.name); + entry.columns(Arrays.asList(columns)); + } else { + // 有可能存在包含多个 column 的情况 + entry.columns(Arrays.asList(columns)); + continue; + } + HugeElement element = this.parseEntry(entry, + this.query.resultType() + .isVertex()); + match = query.test(element); + if (match) { + break; + } + } + } else { + boolean has = iterator.hasNext(); + if (has) { + current = iterator.next(); + } + return has; + } + return match; + } + + @Override + public boolean isValid() { + return false; + } + + @Override + public T next() { + return (T) current; + } + + @Override + public long count() { + return iterator.count(); + } + + @Override + public byte[] position() { + return iterator.position(); + } + + @Override + public void seek(byte[] position) { + this.iterator.seek(position); + } + + @Override + public void close() { + iterator.close(); + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/GraphStoreIterator.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/GraphStoreIterator.java new file mode 100644 index 000000000..36ec1562b --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/GraphStoreIterator.java @@ -0,0 +1,354 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.business; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import javax.script.Bindings; +import javax.script.CompiledScript; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; + +import org.apache.commons.lang.StringUtils; +import org.apache.hugegraph.backend.id.Id; +import org.apache.hugegraph.backend.serializer.BinaryBackendEntry; +import org.apache.hugegraph.backend.store.BackendEntry; +import org.apache.hugegraph.rocksdb.access.RocksDBSession.BackendColumn; +import org.apache.hugegraph.rocksdb.access.ScanIterator; +import org.apache.hugegraph.schema.EdgeLabel; +import org.apache.hugegraph.schema.PropertyKey; +import org.apache.hugegraph.schema.VertexLabel; +import org.apache.hugegraph.store.grpc.Graphpb; +import org.apache.hugegraph.store.grpc.Graphpb.Edge; +import org.apache.hugegraph.store.grpc.Graphpb.ScanPartitionRequest; +import org.apache.hugegraph.store.grpc.Graphpb.ScanPartitionRequest.Request; +import org.apache.hugegraph.store.grpc.Graphpb.ScanPartitionRequest.ScanType; +import org.apache.hugegraph.store.grpc.Graphpb.Variant.Builder; +import org.apache.hugegraph.store.grpc.Graphpb.VariantType; +import org.apache.hugegraph.store.grpc.Graphpb.Vertex; +import org.apache.hugegraph.structure.HugeEdge; +import org.apache.hugegraph.structure.HugeElement; +import org.apache.hugegraph.structure.HugeProperty; +import org.apache.hugegraph.structure.HugeVertex; +import org.apache.hugegraph.type.HugeType; +import org.apache.hugegraph.util.Blob; +import org.apache.tinkerpop.gremlin.structure.Property; +import org.apache.tinkerpop.gremlin.structure.VertexProperty; +import org.codehaus.groovy.jsr223.GroovyScriptEngineImpl; + +import com.google.protobuf.ByteString; +import com.google.protobuf.Descriptors; + +import lombok.extern.slf4j.Slf4j; + +/** + * @date 2022/10/26 + **/ +@Slf4j +public class GraphStoreIterator extends AbstractSelectIterator + implements ScanIterator { + private static final Descriptors.FieldDescriptor propertiesDesEdge = + Edge.getDescriptor().findFieldByNumber(6); + private static final Descriptors.FieldDescriptor propertiesDesVertex = + Vertex.getDescriptor().findFieldByNumber(3); + private final ScanPartitionRequest scanRequest; + private final ScanIterator iter; + private final Request request; + private final boolean isVertex; + private final HugeType type; + private final Set properties; + private Vertex.Builder vertex; + private Edge.Builder edge; + private ArrayList data; + private GroovyScriptEngineImpl engine; + private CompiledScript script; + private HugeElement current; + + public GraphStoreIterator(ScanIterator iterator, + ScanPartitionRequest scanRequest) { + super(); + this.iter = iterator; + this.scanRequest = scanRequest; + this.request = this.scanRequest.getScanRequest(); + ScanType scanType = this.request.getScanType(); + isVertex = scanType.equals(ScanType.SCAN_VERTEX); + if (isVertex) { + vertex = Vertex.newBuilder(); + type = HugeType.VERTEX; + } else { + edge = Edge.newBuilder(); + type = HugeType.EDGE; + } + properties = new HashSet(); + List pl = request.getPropertiesList(); + if (pl != null) { + for (Long i : pl) { + properties.add(i); + } + } + String condition = request.getCondition(); + if (!StringUtils.isEmpty(condition)) { + ScriptEngineManager factory = new ScriptEngineManager(); + engine = (GroovyScriptEngineImpl) factory.getEngineByName("groovy"); + try { + script = engine.compile(condition); + } catch (ScriptException e) { + log.error("create script with error:", e); + } + } + } + + private HugeElement getElement(BackendColumn next) { + BackendEntry entry = null; + BackendEntry.BackendColumn column = BackendEntry.BackendColumn.of( + next.name, next.value); + if (entry == null || !belongToMe(entry, column) || !isVertex) { + try { + entry = new BinaryBackendEntry(type, next.name); + } catch (Exception e) { + log.error("using core to new entry with error:", e); + } + } + BackendEntry.BackendColumn[] columns = + new BackendEntry.BackendColumn[]{column}; + entry.columns(Arrays.asList(columns)); + return this.parseEntry(entry, isVertex); + } + + @Override + public boolean hasNext() { + if (current == null) { + while (iter.hasNext()) { + BackendColumn next = this.iter.next(); + HugeElement element = getElement(next); + try { + boolean evalResult = true; + if (isVertex) { + HugeVertex el = (HugeVertex) element; + if (engine != null) { + Bindings bindings = engine.createBindings(); + bindings.put("element", el); + evalResult = (boolean) script.eval(bindings); + } + } else { + HugeEdge el = (HugeEdge) element; + if (engine != null) { + Bindings bindings = engine.createBindings(); + bindings.put("element", el); + evalResult = (boolean) script.eval(bindings); + } + } + if (!evalResult) { + continue; + } + current = element; + return true; + } catch (Exception e) { + log.error("get next with error:", e); + } + } + } else { + return true; + } + return false; + } + + @Override + public boolean isValid() { + return false; + } + + @Override + public T next() { + T next; + if (isVertex) { + next = (T) parseVertex(current); + } else { + next = (T) parseEdge(current); + } + current = null; + return next; + } + + public T select(BackendColumn current) { + HugeElement element = getElement(current); + if (isVertex) { + return (T) parseVertex(element); + } else { + return (T) parseEdge(element); + } + } + + public ArrayList convert() { + ArrayList result = new ArrayList(data.size()); + for (int i = 0; i < data.size(); i++) { + result.add(select(data.get(i))); + } + return result; + } + + private

> List buildProperties( + Builder variant, + int size, + Iterator

eps) { + int pSize = properties.size(); + List props = new ArrayList<>(pSize > 0 ? + pSize : size); + Graphpb.Property.Builder pb = Graphpb.Property.newBuilder(); + while (eps.hasNext()) { + HugeProperty property = (HugeProperty) eps.next(); + PropertyKey key = property.propertyKey(); + long pkId = key.id().asLong(); + if (pSize > 0 && !properties.contains(pkId)) { + continue; + } + pb.clear(); + variant.clear(); + pb.setLabel(pkId); + Object v = property.value(); + switch (key.dataType()) { + case UUID: + variant.setType(VariantType.VT_STRING) + .setValueString(v.toString()); + break; + case LONG: + variant.setType(VariantType.VT_LONG) + .setValueInt64((Long) v); + break; + case INT: + variant.setType(VariantType.VT_INT) + .setValueInt32((Integer) v); + break; + case BLOB: + byte[] bytes = v instanceof byte[] ? + (byte[]) v : ((Blob) v).bytes(); + variant.setType(VariantType.VT_BYTES) + .setValueBytes(ByteString.copyFrom(bytes)); + break; + case BYTE: + variant.setType(VariantType.VT_BYTES) + .setValueBytes( + ByteString.copyFrom(new byte[]{(Byte) v})); + break; + case DATE: + Date date = (Date) v; + variant.setType(VariantType.VT_DATETIME) + .setValueDatetime(date.toString()); + break; + case FLOAT: + variant.setType(VariantType.VT_FLOAT) + .setValueFloat((Float) v); + break; + case TEXT: + variant.setType(VariantType.VT_STRING) + .setValueString((String) v); + break; + case DOUBLE: + variant.setType(VariantType.VT_DOUBLE) + .setValueDouble((Double) v); + break; + case OBJECT: + case UNKNOWN: + variant.setType(VariantType.VT_UNKNOWN) + .setValueString(v.toString()); + break; + case BOOLEAN: + variant.setType(VariantType.VT_BOOLEAN) + .setValueBoolean((Boolean) v); + break; + default: + break; + } + pb.setValue(variant.build()); + props.add(pb.build()); + } + return props; + } + + private void buildId(Builder variant, Id id) { + switch (id.type()) { + case STRING: + case UUID: + variant.setType(VariantType.VT_STRING) + .setValueString(id.asString()); + break; + case LONG: + variant.setType(VariantType.VT_LONG) + .setValueInt64(id.asLong()); + break; + case EDGE: + // TODO + break; + case UNKNOWN: + variant.setType(VariantType.VT_UNKNOWN) + .setValueBytes(ByteString.copyFrom(id.asBytes())); + break; + + default: + break; + + } + } + + private Edge parseEdge(HugeElement element) { + HugeEdge e = (HugeEdge) element; + edge.clear(); + EdgeLabel label = e.schemaLabel(); + edge.setLabel(label.longId()); + edge.setSourceLabel(e.sourceVertex().schemaLabel().id().asLong()); + edge.setTargetLabel(e.targetVertex().schemaLabel().id().asLong()); + Builder variant = Graphpb.Variant.newBuilder(); + buildId(variant, e.sourceVertex().id()); + edge.setSourceId(variant.build()); + variant.clear(); + buildId(variant, e.targetVertex().id()); + edge.setTargetId(variant.build()); + int size = e.sizeOfProperties(); + Iterator> eps = e.properties(); + List props = buildProperties(variant, size, eps); + edge.setField(propertiesDesEdge, props); + return edge.build(); + } + + private Vertex parseVertex(HugeElement element) { + HugeVertex v = (HugeVertex) element; + vertex.clear(); + VertexLabel label = v.schemaLabel(); + vertex.setLabel(label.longId()); + Builder variant = Graphpb.Variant.newBuilder(); + buildId(variant, v.id()); + vertex.setId(variant.build()); + int size = v.sizeOfProperties(); + Iterator> vps = v.properties(); + List props = buildProperties(variant, size, vps); + vertex.setField(propertiesDesVertex, props); + return vertex.build(); + } + + + @Override + public void close() { + iter.close(); + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/InnerKeyCreator.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/InnerKeyCreator.java new file mode 100644 index 000000000..1108d3bf3 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/InnerKeyCreator.java @@ -0,0 +1,119 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.business; + + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.hugegraph.store.meta.GraphIdManager; +import org.apache.hugegraph.store.term.Bits; +import org.apache.hugegraph.store.util.HgStoreException; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class InnerKeyCreator { + + final BusinessHandler businessHandler; + private final Map graphIdCache = new ConcurrentHashMap<>(); + + public InnerKeyCreator(BusinessHandler businessHandler) { + this.businessHandler = businessHandler; + } + + public int getGraphId(Integer partId, String graphName) throws HgStoreException { + try { + GraphIdManager manager; + if ((manager = graphIdCache.get(partId)) == null) { + manager = new GraphIdManager(businessHandler, partId); + graphIdCache.put(partId, manager); + } + return (int) manager.getGraphId(graphName); + } catch (Exception e) { + throw new HgStoreException(HgStoreException.EC_RKDB_PD_FAIL, e.getMessage()); + } + } + + public void delGraphId(Integer partId, String graphName) { + if (graphIdCache.containsKey(partId)) { + graphIdCache.get(partId).releaseGraphId(graphName); + } else { + new GraphIdManager(businessHandler, partId).releaseGraphId(graphName); + } + } + + public void clearCache(Integer partId) { + graphIdCache.remove(partId); + } + + /** + * 从key中解析出keyCode + */ + public int parseKeyCode(byte[] innerKey) { + return Bits.getShort(innerKey, innerKey.length - Short.BYTES); + } + + public byte[] getKey(Integer partId, String graph, int code, byte[] key) { + int graphId = getGraphId(partId, graph); + byte[] buf = new byte[Short.BYTES + key.length + Short.BYTES]; + Bits.putShort(buf, 0, graphId); + Bits.put(buf, Short.BYTES, key); + Bits.putShort(buf, key.length + Short.BYTES, code); + return buf; + } + + public byte[] getStartKey(Integer partId, String graph) { + int graphId = getGraphId(partId, graph); + byte[] buf = new byte[Short.BYTES]; + Bits.putShort(buf, 0, graphId); + return buf; + } + + public byte[] getStartKey(Integer partId, String graph, byte[] key) { + int graphId = getGraphId(partId, graph); + byte[] buf = new byte[Short.BYTES + key.length]; + Bits.putShort(buf, 0, graphId); + Bits.put(buf, Short.BYTES, key); + return buf; + } + + public byte[] getEndKey(Integer partId, String graph) { + int graphId = getGraphId(partId, graph); + byte[] buf = new byte[Short.BYTES]; + Bits.putShort(buf, 0, graphId + 1); + return buf; + } + + public byte[] getEndKey(Integer partId, String graph, byte[] key) { + int graphId = getGraphId(partId, graph); + byte[] buf = new byte[Short.BYTES + key.length]; + Bits.putShort(buf, 0, graphId); + Bits.put(buf, Short.BYTES, key); + return buf; + } + + + public byte[] getPrefixKey(Integer partId, String graph, byte[] prefix) { + int graphId = getGraphId(partId, graph); + byte[] buf = new byte[Short.BYTES + prefix.length]; + Bits.putShort(buf, 0, graphId); + Bits.put(buf, Short.BYTES, prefix); + return buf; + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/InnerKeyFilter.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/InnerKeyFilter.java new file mode 100644 index 000000000..201c36f3a --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/InnerKeyFilter.java @@ -0,0 +1,104 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.business; + +import java.util.Arrays; + +import org.apache.hugegraph.rocksdb.access.RocksDBSession.BackendColumn; +import org.apache.hugegraph.rocksdb.access.ScanIterator; +import org.apache.hugegraph.store.term.Bits; + +public class InnerKeyFilter implements ScanIterator { + final int codeFrom; + final int codeTo; + //是否进行code过滤,启动该选项,返回key的尾部包含code + final boolean codeFilter; + ScanIterator iterator; + T current = null; + + public InnerKeyFilter(ScanIterator iterator) { + this.iterator = iterator; + this.codeFrom = Integer.MIN_VALUE; + this.codeTo = Integer.MAX_VALUE; + this.codeFilter = false; + moveNext(); + } + + public InnerKeyFilter(ScanIterator iterator, int codeFrom, int codeTo) { + this.iterator = iterator; + this.codeFrom = codeFrom; + this.codeTo = codeTo; + this.codeFilter = true; + moveNext(); + } + + private void moveNext() { + current = null; + if (codeFilter) { + while (iterator.hasNext()) { + T t = iterator.next(); + int code = Bits.getShort(t.name, t.name.length - Short.BYTES); + if (code >= codeFrom && code < codeTo) { + current = t; + break; + } + } + } else { + if (iterator.hasNext()) { + current = iterator.next(); + } + } + } + + @Override + public boolean hasNext() { + return current != null; + } + + @Override + public boolean isValid() { + return iterator.isValid(); + } + + @Override + public T next() { + T column = current; + if (!codeFilter) + // 去掉图ID和hash后缀 + { + column.name = Arrays.copyOfRange(column.name, Short.BYTES, + column.name.length - Short.BYTES); + } else// 去掉图ID + { + column.name = Arrays.copyOfRange(column.name, Short.BYTES, + column.name.length); + } + moveNext(); + return column; + } + + @Override + public void close() { + iterator.close(); + } + + @Override + public long count() { + return iterator.count(); + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/MultiPartitionIterator.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/MultiPartitionIterator.java new file mode 100644 index 000000000..a573df370 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/MultiPartitionIterator.java @@ -0,0 +1,200 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.business; + +import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Queue; +import java.util.function.BiFunction; + +import org.apache.hugegraph.rocksdb.access.ScanIterator; + +import lombok.extern.slf4j.Slf4j; + +/** + * created on 2021/11/2 + * + * @version 1.1.0 implements position method to pass partition-id on 2022/03/10 + */ +@Slf4j +public class MultiPartitionIterator implements ScanIterator { + public final static byte[] EMPTY_BYTES = new byte[0]; + private final Queue partitions; + private final BiFunction supplier; + private ScanIterator iterator; + private Integer curPartitionId; + private Integer positionPartitionId; + private byte[] positionKey; + + private MultiPartitionIterator(List partitionIds, + BiFunction supplier) { + /***************************************************************************** + ** CAUTION: MAKE SURE IT SORTED IN A FIXED ORDER! TO DO THIS IS FOR PAGING. ** + *****************************************************************************/ + Collections.sort(partitionIds); + this.partitions = new LinkedList<>(partitionIds); + this.supplier = supplier; + } + + public static MultiPartitionIterator of(List partitionIdList, + BiFunction supplier) { + return new MultiPartitionIterator(partitionIdList, supplier); + } + + private static byte[] toBytes(final int i) { + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.putInt(i); + return buffer.array(); + } + + public static int toInt(byte[] bytes) { + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.put(bytes); + buffer.flip();//need flip + return buffer.getInt(); + } + + private ScanIterator getIterator() { + if (this.partitions.isEmpty()) { + return null; + } + ScanIterator buf = null; + while (!partitions.isEmpty()) { + this.curPartitionId = partitions.poll(); + if (!this.inPosition(this.curPartitionId)) { + continue; + } + buf = supplier.apply(this.curPartitionId, getPositionKey(this.curPartitionId)); + if (buf == null) { + continue; + } + if (buf.hasNext()) { + break; + } + } + if (buf == null) { + return null; + } + if (!buf.hasNext()) { + buf.close(); + buf = null; + } + return buf; + } + + private void init() { + if (this.iterator == null) { + this.iterator = this.getIterator(); + } + } + + @Override + public boolean hasNext() { + this.init(); + return this.iterator != null; + } + + @Override + public boolean isValid() { + this.init(); + return this.iterator != null; + } + + @Override + public T next() { + this.init(); + if (this.iterator == null) { + throw new NoSuchElementException(); + } + T t = this.iterator.next(); + if (!this.iterator.hasNext()) { + this.iterator.close(); + this.iterator = null; + } + return t; + } + + @Override + public long count() { + long count = 0; + this.iterator = this.getIterator(); + while (this.iterator != null) { + count += this.iterator.count(); + // this.iterator.close(); + this.iterator = this.getIterator(); + } + return count; + } + + /** + * @return the current partition-id in bytes form. + */ + @Override + public byte[] position() { + if (this.curPartitionId == null) { + return EMPTY_BYTES; + } + return toBytes(this.curPartitionId.shortValue()); + } + + @Override + public void seek(byte[] position) { + if (position == null || position.length < Integer.BYTES) { + return; + } + byte[] buf = new byte[Integer.BYTES]; + System.arraycopy(position, 0, buf, 0, Integer.BYTES); + this.positionPartitionId = toInt(buf); + this.positionKey = new byte[position.length - Integer.BYTES]; + System.arraycopy(position, Integer.BYTES, this.positionKey, 0, this.positionKey.length); + + } + + @Override + public void close() { + if (this.iterator != null) { + this.iterator.close(); + } + } + + private boolean inPosition(int partitionId) { + if (this.positionPartitionId == null) { + return true; + } + return partitionId >= this.positionPartitionId; + } + + private byte[] getPositionKey(int partitionId) { + if (this.positionKey == null || this.positionKey.length == 0) { + return null; + } + if (this.positionPartitionId == null) { + return null; + } + if (this.positionPartitionId.intValue() == partitionId) { + return this.positionKey; + } else { + return null; + } + + } + +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/SelectIterator.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/SelectIterator.java new file mode 100644 index 000000000..56425b7fd --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/business/SelectIterator.java @@ -0,0 +1,110 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.business; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.hugegraph.backend.id.Id; +import org.apache.hugegraph.backend.serializer.BytesBuffer; +import org.apache.hugegraph.rocksdb.access.RocksDBSession.BackendColumn; +import org.apache.hugegraph.rocksdb.access.ScanIterator; +import org.apache.hugegraph.type.define.DataType; +import org.apache.hugegraph.type.define.SerialEnum; + +/** + * @date 2022/9/23 + **/ +public class SelectIterator implements ScanIterator { + + ScanIterator iter; + Set properties; + + public SelectIterator(ScanIterator iterator, List properties) { + this.iter = iterator; + this.properties = new HashSet<>(properties); + } + + public BackendColumn select(BackendColumn column) { + int size; + if (properties == null || (size = properties.size()) == 0) { + return column; + } + byte[] name = column.name; + byte[] value = column.value; + BytesBuffer buffer = BytesBuffer.wrap(value); + Id labelId = buffer.readId(); // label + int bpSize = buffer.readVInt(); // property + if (size == bpSize) { + return column; + } + BytesBuffer allocate = BytesBuffer.allocate(8 + 16 * size); + allocate.writeId(labelId); + allocate.writeVInt(size); + for (int i = 0; i < bpSize; i++) { + int propertyId = buffer.readVInt(); + byte cat = buffer.read(); // cardinality and type + byte code = BytesBuffer.getType(cat); + DataType dataType = SerialEnum.fromCode(DataType.class, code); + Object bpValue = buffer.readProperty(dataType); + if (properties.contains(propertyId)) { + allocate.writeVInt(propertyId); + allocate.write(cat); + allocate.writeProperty(dataType, bpValue); + } + } + return BackendColumn.of(name, allocate.bytes()); + } + + @Override + public boolean hasNext() { + return this.iter.hasNext(); + } + + @Override + public boolean isValid() { + return this.iter.isValid(); + } + + @Override + public BackendColumn next() { + BackendColumn value = this.iter.next(); + return select(value); + } + + @Override + public long count() { + return this.iter.count(); + } + + @Override + public byte[] position() { + return this.iter.position(); + } + + @Override + public void seek(byte[] position) { + this.iter.seek(position); + } + + @Override + public void close() { + this.iter.close(); + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/BatchPutRequest.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/BatchPutRequest.java new file mode 100644 index 000000000..53c3b9188 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/BatchPutRequest.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.cmd; + +import java.io.Serializable; +import java.util.List; + +import lombok.Data; + +@Data +public class BatchPutRequest extends HgCmdBase.BaseRequest { + private List entries; + + @Override + public byte magic() { + return HgCmdBase.BATCH_PUT; + } + + @Data + public static class KV implements Serializable { + private String table; + private int code; + private byte[] key; + private byte[] value; + + public static KV of(String table, int code, byte[] key, byte[] value) { + KV kv = new KV(); + kv.table = table; + kv.code = code; + kv.key = key; + kv.value = value; + return kv; + } + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/BatchPutResponse.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/BatchPutResponse.java new file mode 100644 index 000000000..a197c261c --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/BatchPutResponse.java @@ -0,0 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.cmd; + +public class BatchPutResponse extends HgCmdBase.BaseResponse { +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/CleanDataRequest.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/CleanDataRequest.java new file mode 100644 index 000000000..053452573 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/CleanDataRequest.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.cmd; + +import org.apache.hugegraph.pd.grpc.pulse.CleanPartition; +import org.apache.hugegraph.pd.grpc.pulse.CleanType; +import org.apache.hugegraph.store.meta.Partition; + +import lombok.Data; + + +@Data +public class CleanDataRequest extends HgCmdBase.BaseRequest { + + private long keyStart; + private long keyEnd; + + private CleanType cleanType; + + private boolean deletePartition; + + private long taskId; + + public static CleanDataRequest fromCleanPartitionTask(CleanPartition task, Partition partition, + long taskId) { + return fromCleanPartitionTask(partition.getGraphName(), partition.getId(), taskId, task); + } + + public static CleanDataRequest fromCleanPartitionTask(String graphName, int partitionId, + long taskId, + CleanPartition task) { + CleanDataRequest request = new CleanDataRequest(); + request.setGraphName(graphName); + request.setPartitionId(partitionId); + request.setCleanType(task.getCleanType()); + request.setKeyStart(task.getKeyStart()); + request.setKeyEnd(task.getKeyEnd()); + request.setDeletePartition(task.getDeletePartition()); + request.setTaskId(taskId); + return request; + } + + public static CleanPartition toCleanPartitionTask(CleanDataRequest request) { + return CleanPartition.newBuilder() + .setKeyStart(request.keyStart) + .setKeyEnd(request.keyEnd) + .setDeletePartition(request.deletePartition) + .setCleanType(request.cleanType) + .build(); + } + + @Override + public byte magic() { + return HgCmdBase.CLEAN_DATA; + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/CleanDataResponse.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/CleanDataResponse.java new file mode 100644 index 000000000..e280494dd --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/CleanDataResponse.java @@ -0,0 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.cmd; + +public class CleanDataResponse extends HgCmdBase.BaseResponse { +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/CreateRaftRequest.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/CreateRaftRequest.java new file mode 100644 index 000000000..d77cae7a3 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/CreateRaftRequest.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.cmd; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.hugegraph.pd.grpc.Metapb; + +import com.alipay.sofa.jraft.conf.Configuration; +import com.google.protobuf.InvalidProtocolBufferException; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class CreateRaftRequest extends HgCmdBase.BaseRequest { + List values = new ArrayList<>(); + String peers; + + public List getPartitions() { + try { + List partitions = new ArrayList<>(); + for (byte[] partition : values) { + partitions.add(Metapb.Partition.parseFrom(partition)); + } + return partitions; + } catch (InvalidProtocolBufferException e) { + log.error("CreateRaftNodeProcessor parse partition exception }", e); + } + return new ArrayList<>(); + } + + public void addPartition(Metapb.Partition partition) { + values.add(partition.toByteArray()); + } + + public Configuration getConf() { + Configuration conf = null; + if (peers != null) { + conf = new Configuration(); + conf.parse(this.peers); + } + return conf; + } + + public void setConf(Configuration conf) { + if (conf != null) { + this.peers = conf.toString(); + } + } + + @Override + public byte magic() { + return HgCmdBase.CREATE_RAFT; + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/CreateRaftResponse.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/CreateRaftResponse.java new file mode 100644 index 000000000..77990e282 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/CreateRaftResponse.java @@ -0,0 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.cmd; + +public class CreateRaftResponse extends HgCmdBase.BaseResponse { +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/DbCompactionRequest.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/DbCompactionRequest.java new file mode 100644 index 000000000..0cc32721b --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/DbCompactionRequest.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.cmd; + +import lombok.Data; + +@Data +public class DbCompactionRequest extends HgCmdBase.BaseRequest { + private String tableName; + + @Override + public byte magic() { + return HgCmdBase.ROCKSDB_COMPACTION; + } +} + diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/DbCompactionResponse.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/DbCompactionResponse.java new file mode 100644 index 000000000..d5c3418b9 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/DbCompactionResponse.java @@ -0,0 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.cmd; + +public class DbCompactionResponse extends HgCmdBase.BaseResponse { +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/DestroyRaftRequest.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/DestroyRaftRequest.java new file mode 100644 index 000000000..5e17f27fa --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/DestroyRaftRequest.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.cmd; + +import java.util.ArrayList; +import java.util.List; + +import lombok.Data; + +@Data +public class DestroyRaftRequest extends HgCmdBase.BaseRequest { + private final List graphNames = new ArrayList<>(); + + public void addGraphName(String graphName) { + graphNames.add(graphName); + } + + @Override + public byte magic() { + return HgCmdBase.DESTROY_RAFT; + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/DestroyRaftResponse.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/DestroyRaftResponse.java new file mode 100644 index 000000000..4e6633af5 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/DestroyRaftResponse.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.cmd; + +public class DestroyRaftResponse extends HgCmdBase.BaseResponse { + +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/FutureClosureAdapter.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/FutureClosureAdapter.java new file mode 100644 index 000000000..ce0627d44 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/FutureClosureAdapter.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.cmd; + +import java.util.concurrent.CompletableFuture; + +import com.alipay.sofa.jraft.Closure; +import com.alipay.sofa.jraft.Status; + +public class FutureClosureAdapter implements Closure { + public final CompletableFuture future = new CompletableFuture<>(); + private T resp; + + public T getResponse() { + return this.resp; + } + + public void setResponse(T resp) { + this.resp = resp; + future.complete(resp); + run(Status.OK()); + } + + public void failure(Throwable t) { + future.completeExceptionally(t); + run(new Status(-1, t.getMessage())); + } + + @Override + public void run(Status status) { + + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/GetStoreInfoRequest.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/GetStoreInfoRequest.java new file mode 100644 index 000000000..accbc2890 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/GetStoreInfoRequest.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.cmd; + +public class GetStoreInfoRequest extends HgCmdBase.BaseRequest { + @Override + public byte magic() { + return HgCmdBase.GET_STORE_INFO; + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/GetStoreInfoResponse.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/GetStoreInfoResponse.java new file mode 100644 index 000000000..eb47bd410 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/GetStoreInfoResponse.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.cmd; + +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.store.meta.Store; + +import com.google.protobuf.InvalidProtocolBufferException; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class GetStoreInfoResponse extends HgCmdBase.BaseResponse { + private byte[] store; + + public Store getStore() { + try { + return new Store(Metapb.Store.parseFrom(this.store)); + } catch (InvalidProtocolBufferException e) { + log.error("GetStoreResponse parse exception {}", e); + } + return null; + } + + public void setStore(Store store) { + this.store = store.getProtoObj().toByteArray(); + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/HgCmdBase.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/HgCmdBase.java new file mode 100644 index 000000000..ba247450c --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/HgCmdBase.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.cmd; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import lombok.Data; + +public class HgCmdBase { + + public static final byte GET_STORE_INFO = 0x01; + public static final byte BATCH_PUT = 0x02; + public static final byte CLEAN_DATA = 0x03; + public static final byte RAFT_UPDATE_PARTITION = 0x04; + public static final byte ROCKSDB_COMPACTION = 0x05; + public static final byte CREATE_RAFT = 0x06; + public static final byte DESTROY_RAFT = 0x07; + + @Data + public abstract static class BaseRequest implements Serializable { + private String graphName; + private int partitionId; + + public abstract byte magic(); + } + + @Data + public abstract static class BaseResponse implements Serializable { + List partitionLeaders; + private HgCmdProcessor.Status status; + + public synchronized BaseResponse addPartitionLeader(PartitionLeader ptLeader) { + if (partitionLeaders == null) { + partitionLeaders = new ArrayList<>(); + } + partitionLeaders.add(ptLeader); + return this; + } + + public static class PartitionLeader implements Serializable { + private final Integer partId; + private final Long storeId; + + public PartitionLeader(Integer partId, Long storeId) { + this.partId = partId; + this.storeId = storeId; + } + + public Long getStoreId() { + return storeId; + } + + public Integer getPartId() { + return partId; + } + } + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/HgCmdClient.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/HgCmdClient.java new file mode 100644 index 000000000..e4cab740b --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/HgCmdClient.java @@ -0,0 +1,260 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.cmd; + +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.apache.hugegraph.store.meta.Partition; +import org.apache.hugegraph.store.meta.Store; + +import com.alipay.sofa.jraft.Closure; +import com.alipay.sofa.jraft.JRaftUtils; +import com.alipay.sofa.jraft.Status; +import com.alipay.sofa.jraft.conf.Configuration; +import com.alipay.sofa.jraft.option.RpcOptions; +import com.alipay.sofa.jraft.rpc.InvokeCallback; +import com.alipay.sofa.jraft.rpc.InvokeContext; +import com.alipay.sofa.jraft.rpc.RaftRpcFactory; +import com.alipay.sofa.jraft.rpc.RpcClient; +import com.alipay.sofa.jraft.util.Endpoint; +import com.alipay.sofa.jraft.util.RpcFactoryHelper; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class HgCmdClient { + private static final int MAX_RETRY_TIMES = 5; + protected volatile RpcClient rpcClient; + private RpcOptions rpcOptions; + private PartitionAgent ptAgent; + + public synchronized boolean init(final RpcOptions rpcOptions, PartitionAgent ptAgent) { + this.ptAgent = ptAgent; + this.rpcOptions = rpcOptions; + final RaftRpcFactory factory = RpcFactoryHelper.rpcFactory(); + this.rpcClient = + factory.createRpcClient(factory.defaultJRaftClientConfigHelper(this.rpcOptions)); + return this.rpcClient.init(rpcOptions); + } + + public Future createRaftNode(final String address, final List partitions, + final Closure done) { + CreateRaftRequest request = new CreateRaftRequest(); + partitions.forEach(partition -> { + request.addPartition(partition.getProtoObj()); + }); + + log.info("Send to {} CreateRaftNode rpc call {} ", address, request.getPartitions().get(0)); + return internalCallAsyncWithRpc(JRaftUtils.getEndPoint(address), request, done); + } + + public Future createRaftNode(final String address, final List partitions, + Configuration conf, final Closure done) { + CreateRaftRequest request = new CreateRaftRequest(); + partitions.forEach(partition -> { + request.addPartition(partition.getProtoObj()); + }); + request.setConf(conf); + + log.info("Send to {} CreateRaftNode rpc call {} ", address, request.getPartitions().get(0)); + return internalCallAsyncWithRpc(JRaftUtils.getEndPoint(address), request, done); + } + + public Future destroyRaftNode(final String peer, final List partitions, + final Closure done) { + + DestroyRaftRequest request = new DestroyRaftRequest(); + partitions.forEach(partition -> { + request.setPartitionId(partition.getId()); + request.addGraphName(partition.getGraphName()); + }); + + log.info("Send to {} DestroyRaftNode rpc call partitionId={} ", peer, + request.getPartitionId()); + return internalCallAsyncWithRpc(JRaftUtils.getEndPoint(peer), request, done); + } + + public Store getStoreInfo(final String address) { + GetStoreInfoRequest request = new GetStoreInfoRequest(); + request.setGraphName(""); + request.setPartitionId(0); + GetStoreInfoResponse response = null; + try { + response = internalCallSyncWithRpc(JRaftUtils.getEndPoint(address), request); + } catch (Exception e) { + return null; + } + return response != null ? response.getStore() : null; + } + + /** + * 批量插入数据 + * + * @param request + * @return + */ + public BatchPutResponse batchPut(BatchPutRequest request) { + return (BatchPutResponse) tryInternalCallSyncWithRpc(request); + } + + /** + * 清理无效数据 + * + * @param request + * @return + */ + public CleanDataResponse cleanData(CleanDataRequest request) { + return (CleanDataResponse) tryInternalCallSyncWithRpc(request); + } + + /** + * 通过raft更新本地分区信息 + * + * @param request + * @return + */ + public UpdatePartitionResponse raftUpdatePartition(UpdatePartitionRequest request) { + return (UpdatePartitionResponse) tryInternalCallSyncWithRpc(request); + } + + /** + * 查找Leader,错误重试,处理Leader重定向 + * + * @param request + * @return + */ + public HgCmdBase.BaseResponse tryInternalCallSyncWithRpc(HgCmdBase.BaseRequest request) { + HgCmdBase.BaseResponse response = null; + + for (int i = 0; i < MAX_RETRY_TIMES; i++) { + try { + Endpoint leader = ptAgent.getPartitionLeader(request.getGraphName(), + request.getPartitionId()); + if (leader == null) { + log.error("get leader of graph {} - {} is null", request.getGraphName(), + request.getPartitionId()); + Thread.sleep(i * 1000); + continue; + } + + response = internalCallSyncWithRpc(leader, request); + if (response != null) { + if (response.getStatus().isOK()) { + break; + } else if (HgCmdProcessor.Status.LEADER_REDIRECT == response.getStatus() + && response.partitionLeaders != null + ) { + // 当返回leader 漂移,并且partitionLeaders 不为空时,需要重新设置leader + } else { + log.error( + "HgCmdClient tryInternalCallSyncWithRpc error msg {} leaders is {}", + response.getStatus().getMsg(), response.getPartitionLeaders()); + } + } +// break; + } catch (Exception e) { + if (i + 1 >= MAX_RETRY_TIMES) { + log.error("tryInternalCallSyncWithRpc Exception {}", e); + } + } + } + return response; + } + + private V internalCallSyncWithRpc(final Endpoint endpoint, + final HgCmdBase.BaseRequest request) + throws ExecutionException, InterruptedException, TimeoutException { + FutureClosureAdapter response = new FutureClosureAdapter<>(); + internalCallAsyncWithRpc(endpoint, request, response); + try { + return response.future.get(5000, TimeUnit.MILLISECONDS); + } catch (Exception e) { + throw e; + } + } + + private Future internalCallAsyncWithRpc(final Endpoint endpoint, + final HgCmdBase.BaseRequest request, + final Closure done) { + final InvokeContext invokeCtx = null; + int[] retryCount = new int[]{0}; + FutureClosureAdapter response = new FutureClosureAdapter<>() { + @Override + public void run(Status status) { + done.run(status); + } + }; + tryWithTimes(endpoint, request, response, invokeCtx, retryCount); + return response.future; + } + + private void internalCallAsyncWithRpc(final Endpoint endpoint, + final HgCmdBase.BaseRequest request, + final FutureClosureAdapter closure) { + final InvokeContext invokeCtx = null; + int[] retryCount = new int[]{0}; + tryWithTimes(endpoint, request, closure, invokeCtx, retryCount); + } + + private void tryWithTimes(Endpoint endpoint, HgCmdBase.BaseRequest request, + FutureClosureAdapter closure, + InvokeContext invokeCtx, + int[] retryCount) { + InvokeCallback invokeCallback = (result, err) -> { + if (err == null) { + final HgCmdBase.BaseResponse response = (HgCmdBase.BaseResponse) result; + closure.setResponse((V) response); + } else { + tryWithThrowable(endpoint, request, closure, invokeCtx, retryCount, err); + } + }; + try { + this.rpcClient.invokeAsync(endpoint, request, invokeCtx, invokeCallback, + this.rpcOptions.getRpcDefaultTimeout()); + } catch (final Throwable err) { + tryWithThrowable(endpoint, request, closure, invokeCtx, retryCount, err); + } + } + + private void tryWithThrowable(Endpoint endpoint, + HgCmdBase.BaseRequest request, + FutureClosureAdapter closure, + InvokeContext invokeCtx, + int[] retryCount, Throwable err) { + if (retryCount[0] >= MAX_RETRY_TIMES) { + closure.failure(err); + closure.run(new Status(-1, err.getMessage())); + } else { + retryCount[0]++; + try { + Thread.sleep(100L * retryCount[0]); + } catch (InterruptedException e) { + closure.run(new Status(-1, e.getMessage())); + } + tryWithTimes(endpoint, request, closure, invokeCtx, retryCount); + } + } + + public interface PartitionAgent { + Endpoint getPartitionLeader(String graph, int partitionId); + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/HgCmdProcessor.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/HgCmdProcessor.java new file mode 100644 index 000000000..5c799f50a --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/HgCmdProcessor.java @@ -0,0 +1,227 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.cmd; + +import java.io.Serializable; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.apache.hugegraph.store.HgStoreEngine; +import org.apache.hugegraph.store.meta.Partition; +import org.apache.hugegraph.store.raft.RaftClosure; +import org.apache.hugegraph.store.raft.RaftOperation; +import org.apache.hugegraph.store.util.HgRaftError; + +import com.alipay.sofa.jraft.rpc.RpcContext; +import com.alipay.sofa.jraft.rpc.RpcProcessor; +import com.alipay.sofa.jraft.rpc.RpcServer; + +import lombok.extern.slf4j.Slf4j; + +/** + * 快照同步rpc处理器,leader批量入库完成后,基于seqnum读取新增的kv,批量发送给follower. + * + * @param + */ +@Slf4j +public class HgCmdProcessor implements RpcProcessor { + private final Class requestClass; + private final HgStoreEngine engine; + public HgCmdProcessor(Class requestClass, HgStoreEngine engine) { + this.requestClass = requestClass; + this.engine = engine; + } + + public static void registerProcessor(final RpcServer rpcServer, final HgStoreEngine engine) { + rpcServer.registerProcessor(new HgCmdProcessor<>(GetStoreInfoRequest.class, engine)); + rpcServer.registerProcessor(new HgCmdProcessor<>(BatchPutRequest.class, engine)); + rpcServer.registerProcessor(new HgCmdProcessor<>(CleanDataRequest.class, engine)); + rpcServer.registerProcessor(new HgCmdProcessor<>(UpdatePartitionRequest.class, engine)); + rpcServer.registerProcessor(new HgCmdProcessor<>(CreateRaftRequest.class, engine)); + rpcServer.registerProcessor(new HgCmdProcessor<>(DestroyRaftRequest.class, engine)); + } + + @Override + public void handleRequest(RpcContext rpcCtx, T request) { + HgCmdBase.BaseResponse response = null; + switch (request.magic()) { + case HgCmdBase.GET_STORE_INFO: { + response = new GetStoreInfoResponse(); + handleGetStoreInfo((GetStoreInfoRequest) request, (GetStoreInfoResponse) response); + break; + } + case HgCmdBase.BATCH_PUT: { + response = new BatchPutResponse(); + handleBatchPut((BatchPutRequest) request, (BatchPutResponse) response); + break; + } + case HgCmdBase.CLEAN_DATA: { + response = new CleanDataResponse(); + handleCleanData((CleanDataRequest) request, (CleanDataResponse) response); + break; + } + case HgCmdBase.RAFT_UPDATE_PARTITION: { + response = new UpdatePartitionResponse(); + handleUpdatePartition((UpdatePartitionRequest) request, + (UpdatePartitionResponse) response); + break; + } + case HgCmdBase.CREATE_RAFT: { + response = new CreateRaftResponse(); + handleCreateRaft((CreateRaftRequest) request, (CreateRaftResponse) response); + break; + } + case HgCmdBase.DESTROY_RAFT: { + response = new DestroyRaftResponse(); + handleDestroyRaft((DestroyRaftRequest) request, (DestroyRaftResponse) response); + break; + } + default: { + log.warn("HgCmdProcessor magic {} is not recognized ", request.magic()); + } + } + rpcCtx.sendResponse(response); + } + + @Override + public String interest() { + return this.requestClass.getName(); + } + + public void handleGetStoreInfo(GetStoreInfoRequest request, GetStoreInfoResponse response) { + response.setStore(engine.getPartitionManager().getStore()); + response.setStatus(Status.OK); + } + + public void handleUpdatePartition(UpdatePartitionRequest request, + UpdatePartitionResponse response) { + raftSyncTask(request, response, RaftOperation.RAFT_UPDATE_PARTITION); + } + + public void handleBatchPut(BatchPutRequest request, BatchPutResponse response) { + raftSyncTask(request, response, RaftOperation.IN_WRITE_OP); + } + + public void handleCleanData(CleanDataRequest request, CleanDataResponse response) { + raftSyncTask(request, response, RaftOperation.IN_CLEAN_OP); + } + + public void handleCreateRaft(CreateRaftRequest request, CreateRaftResponse response) { + log.info("CreateRaftNode rpc call received, {}, {}", request.getPartitions(), + request.getConf()); + request.getPartitions().forEach(partition -> { + engine.createPartitionEngine(new Partition(partition), request.getConf()); + }); + response.setStatus(Status.OK); + } + + public void handleDestroyRaft(DestroyRaftRequest request, DestroyRaftResponse response) { + log.info("DestroyRaftNode rpc call received, partitionId={}", request.getPartitionId()); + engine.destroyPartitionEngine(request.getPartitionId(), request.getGraphNames()); + response.setStatus(Status.OK); + } + + /** + * raft 通知副本同步执行 + * + * @param request + * @param response + * @param op + */ + private void raftSyncTask(HgCmdBase.BaseRequest request, HgCmdBase.BaseResponse response, + final byte op) { + CountDownLatch latch = new CountDownLatch(1); + engine.addRaftTask(request.getGraphName(), request.getPartitionId(), + RaftOperation.create(op, request), new RaftClosure() { + @Override + public void run(com.alipay.sofa.jraft.Status status) { + Status responseStatus = Status.UNKNOWN; + switch (HgRaftError.forNumber(status.getCode())) { + case OK: + responseStatus = Status.OK; + break; + case NOT_LEADER: + responseStatus = Status.LEADER_REDIRECT; + break; + case NOT_LOCAL: + responseStatus = Status.NO_PARTITION; + break; + case WAIT_LEADER_TIMEOUT: + responseStatus = Status.WAIT_LEADER_TIMEOUT; + break; + default: + responseStatus.setMsg(status.getErrorMsg()); + } + response.setStatus(responseStatus); + latch.countDown(); + } + + @Override + public void onLeaderChanged(Integer partId, Long storeId) { + RaftClosure.super.onLeaderChanged(partId, storeId); + response.addPartitionLeader( + new HgCmdBase.BaseResponse.PartitionLeader(partId, storeId)); + } + }); + try { + latch.await(1, TimeUnit.MINUTES); + } catch (InterruptedException e) { + log.info("handleBatchPut InterruptedException {}", e); + } + } + + public enum Status implements Serializable { + UNKNOWN(-1, "unknown"), + OK(0, "ok"), + COMPLETE(0, "Transmission completed"), + INCOMPLETE(1, "Incomplete transmission"), + NO_PARTITION(10, "Partition not found"), + IO_ERROR(11, "io error"), + EXCEPTION(12, "exception"), + DOWN_SNAPSHOT_ERROR(13, "download snapshot error"), + LEADER_REDIRECT(14, "leader redirect"), + WAIT_LEADER_TIMEOUT(15, "Waiting for leader timeout"), + ABORT(100, "Transmission aborted"); + + + private final int code; + private String msg; + + Status(int code, String msg) { + this.code = code; + this.msg = msg; + } + + public int getCode() { + return this.code; + } + + public String getMsg() { + return this.msg; + } + + public Status setMsg(String msg) { + this.msg = msg; + return this; + } + + public boolean isOK() { + return this.code == 0; + } + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/UpdatePartitionRequest.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/UpdatePartitionRequest.java new file mode 100644 index 000000000..bfed81d7d --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/UpdatePartitionRequest.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.cmd; + +import org.apache.hugegraph.pd.grpc.Metapb; + +import lombok.Data; + + +@Data +public class UpdatePartitionRequest extends HgCmdBase.BaseRequest { + private int startKey; + private int endKey; + + private Metapb.PartitionState workState; + + @Override + public byte magic() { + return HgCmdBase.RAFT_UPDATE_PARTITION; + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/UpdatePartitionResponse.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/UpdatePartitionResponse.java new file mode 100644 index 000000000..98bc247b2 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/cmd/UpdatePartitionResponse.java @@ -0,0 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.cmd; + +public class UpdatePartitionResponse extends HgCmdBase.BaseResponse { +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/DeletedFileManager.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/DeletedFileManager.java new file mode 100644 index 000000000..27a099a2d --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/DeletedFileManager.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.meta; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import org.apache.commons.io.FileUtils; +import org.apache.hugegraph.rocksdb.access.RocksDBSession; +import org.apache.hugegraph.store.meta.base.GlobalMetaStore; +import org.apache.hugegraph.store.options.MetadataOptions; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class DeletedFileManager extends GlobalMetaStore { + public DeletedFileManager(MetadataOptions options) { + super(options); + } + + public void load() { + byte[] key = MetadataKeyHelper.getDeletedFilePrefix(); + List columns = scan(key); + for (RocksDBSession.BackendColumn column : columns) { + String filePath = new String(column.value, StandardCharsets.UTF_8); + try { + if (new File(filePath).exists()) { + FileUtils.deleteDirectory(new File(filePath)); + log.warn("Delete legacy files {}", filePath); + removeDeletedFile(filePath); + } + } catch (IOException e) { + log.error("Delete legacy files {} exception", filePath, e); + } + } + } + + public void addDeletedFile(String path) { + byte[] key = MetadataKeyHelper.getDeletedFileKey(path); + put(key, path.getBytes(StandardCharsets.UTF_8)); + } + + public void removeDeletedFile(String path) { + byte[] key = MetadataKeyHelper.getDeletedFileKey(path); + delete(key); + } + + +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/Graph.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/Graph.java new file mode 100644 index 000000000..c7b80e7d8 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/Graph.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.meta; + +import org.apache.hugegraph.pd.grpc.Metapb; + +import lombok.Data; + +@Data +public class Graph implements Cloneable { + private String graphName; + private Metapb.Graph graph; + + public Graph() { + + } + + public Graph(Metapb.Graph protoObj) { + graphName = protoObj.getGraphName(); + this.graph = protoObj; + } + + public Metapb.Graph getProtoObj() { + // return Metapb.Graph.newBuilder() + // .setGraphName(graphName) + // .build(); + return this.graph; + } + + public void setProtoObj(Metapb.Graph protoObj) { + // return Metapb.Graph.newBuilder() + // .setGraphName(graphName) + // .build(); + this.graph = protoObj; + } + + @Override + public Graph clone() { + try { + return (Graph) super.clone(); + } catch (CloneNotSupportedException e) { + e.printStackTrace(); + } + return null; + } + +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/GraphIdManager.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/GraphIdManager.java new file mode 100644 index 000000000..693bdc2d1 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/GraphIdManager.java @@ -0,0 +1,181 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.meta; + +import java.nio.ByteBuffer; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.hugegraph.store.meta.base.DBSessionBuilder; +import org.apache.hugegraph.store.meta.base.PartitionMetaStore; +import org.apache.hugegraph.store.util.HgStoreException; + +import com.google.protobuf.Int64Value; + +/** + * GraphId管理器,维护一个自增循环ID,负责管理GraphName和GraphId的映射 + */ +public class GraphIdManager extends PartitionMetaStore { + + protected static final String GRAPH_ID_PREFIX = "@GRAPH_ID@"; + protected static int maxGraphID = 65535; + static Object graphIdLock = new Object(); + static Object cidLock = new Object(); + final DBSessionBuilder sessionBuilder; + final int partitionId; + // public long getGraphId(String graphName) { + // if (!graphIdCache.containsKey(graphName)) { + // synchronized (graphIdLock) { + // if (!graphIdCache.containsKey(graphName)) { + // byte[] key = MetadataKeyHelper.getGraphIDKey(graphName); + // Int64Value id = get(Int64Value.parser(), key); + // if (id == null) { + // id = Int64Value.of(getCId(GRAPH_ID_PREFIX, maxGraphID)); + // if (id.getValue() == -1) { + // throw new HgStoreException(HgStoreException.EC_FAIL, + // "The number of graphs exceeds the maximum 65535"); + // } + // put(key, id); + // flush(); + // } + // graphIdCache.put(graphName, id.getValue()); + // } + // } + // } + // return graphIdCache.get(graphName); + // } + private final Map graphIdCache = new ConcurrentHashMap<>(); + + public GraphIdManager(DBSessionBuilder sessionBuilder, int partitionId) { + super(sessionBuilder, partitionId); + this.sessionBuilder = sessionBuilder; + this.partitionId = partitionId; + } + + /** + * 获取一个图的id + */ + public long getGraphId(String graphName) { + Long l = graphIdCache.get(graphName); + if (l == null) { + synchronized (graphIdLock) { + if ((l = graphIdCache.get(graphName)) == null) { + byte[] key = MetadataKeyHelper.getGraphIDKey(graphName); + Int64Value id = get(Int64Value.parser(), key); + if (id == null) { + id = Int64Value.of(getCId(GRAPH_ID_PREFIX, maxGraphID)); + if (id.getValue() == -1) { + throw new HgStoreException(HgStoreException.EC_FAIL, + "The number of graphs exceeds the maximum " + + "65535"); + } + put(key, id); + flush(); + } + l = id.getValue(); + graphIdCache.put(graphName, l); + } + } + } + return l; + } + + /** + * 释放一个图id + */ + public long releaseGraphId(String graphName) { + long gid = getGraphId(graphName); + synchronized (graphIdLock) { + graphIdCache.remove(graphName); + byte[] key = MetadataKeyHelper.getGraphIDKey(graphName); + delete(key); + delCId(GRAPH_ID_PREFIX, gid); + flush(); + } + return gid; + } + + /** + * 获取自增循环不重复id, 达到上限后从0开始自增 + * + * @param key key + * @param max id上限,达到该值后,重新从0开始自增 + * @return id + */ + protected long getCId(String key, long max) { + byte[] cidNextKey = MetadataKeyHelper.getCidKey(key); + synchronized (cidLock) { + Int64Value value = get(Int64Value.parser(), cidNextKey); + long current = value != null ? value.getValue() : 0L; + long last = current == 0 ? max - 1 : current - 1; + // 查找一个未使用的cid + List ids = + scan(Int64Value.parser(), genCIDSlotKey(key, current), genCIDSlotKey(key, max)); + for (Int64Value id : ids) { + if (current == id.getValue()) { + current++; + } else { + break; + } + } + + if (current == max) { + current = 0; + ids = scan(Int64Value.parser(), genCIDSlotKey(key, current), + genCIDSlotKey(key, last)); + for (Int64Value id : ids) { + if (current == id.getValue()) { + current++; + } else { + break; + } + } + } + + if (current == last) { + return -1; + } + // 保存当前id,标记已被使用 + put(genCIDSlotKey(key, current), Int64Value.of(current)); + // 保存下一次遍历的id + put(cidNextKey, Int64Value.of(current + 1)); + return current; + } + } + + /** + * 返回已使用Cid的key + */ + private byte[] genCIDSlotKey(String key, long value) { + byte[] keySlot = MetadataKeyHelper.getCidSlotKeyPrefix(key); + ByteBuffer buf = ByteBuffer.allocate(keySlot.length + Long.SIZE); + buf.put(keySlot); + buf.putLong(value); + return buf.array(); + } + + /** + * 删除一个循环id,释放id值 + */ + protected void delCId(String key, long cid) { + delete(genCIDSlotKey(key, cid)); + } + + +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/GraphManager.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/GraphManager.java new file mode 100644 index 000000000..555260bd5 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/GraphManager.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.meta; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.store.meta.base.GlobalMetaStore; +import org.apache.hugegraph.store.options.MetadataOptions; +import org.apache.hugegraph.store.pd.PdProvider; + +public class GraphManager extends GlobalMetaStore { + private final PdProvider pdProvider; + + private final Map graphs; + + public GraphManager(MetadataOptions options, PdProvider pdProvider) { + super(options); + this.graphs = new ConcurrentHashMap<>(); + this.pdProvider = pdProvider; + this.pdProvider.setGraphManager(this); + } + + /** + * 修改图 + * 此处不加锁,要求graph是被克隆的,进制修改原始对象 + * + * @param graph + * @return + */ + public Graph updateGraph(Graph graph) { + this.graphs.put(graph.getGraphName(), graph); + byte[] key = MetadataKeyHelper.getGraphKey(graph.getGraphName()); + if (graph.getProtoObj() != null) { + put(key, graph.getProtoObj().toByteArray()); + } + return graph; + } + + public void load() { + byte[] key = MetadataKeyHelper.getGraphKeyPrefix(); + List values = scan(Metapb.Graph.parser(), key); + values.forEach(graph -> { + graphs.put(graph.getGraphName(), new Graph(graph)); + }); + } + + public Map getGraphs() { + return graphs; + } + + public Graph getGraph(String graphName) { + return graphs.get(graphName); + } + + public Graph getCloneGraph(String graphName) { + if (graphs.containsKey(graphName)) { + return graphs.get(graphName).clone(); + } + return new Graph(); + } + + public Graph removeGraph(String graphName) { + byte[] key = MetadataKeyHelper.getGraphKey(graphName); + delete(key); + return graphs.remove(graphName); + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/MetadataKeyHelper.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/MetadataKeyHelper.java new file mode 100644 index 000000000..c7b3dce65 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/MetadataKeyHelper.java @@ -0,0 +1,315 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.meta; + +import java.nio.charset.StandardCharsets; + +public class MetadataKeyHelper { + private static final char DELIMITER = '/'; + private static final String HUGEGRAPH = "HUGEGRAPH"; + private static final String STORE = "STORE"; + private static final String PARTITION = "PARTITION"; + private static final String TASK = "TASK"; + private static final String ASYNC_TASK = "A_TASK"; + private static final String INSTRUCTION_TASK = "INS_TASK"; + private static final String TASK_DONE = "TASK_DONE"; + private static final String GRAPH = "GRAPH"; + private static final String PARTITION_STORE = "PARTITION_STORE"; + private static final String PARTITION_RAFT = "PARTITION_Raft"; + private static final String DELETED_FILE = "DELETED_FILE"; + private static final String SHARD_GROUP = "SHARDGROUP"; + + private static final String CID_PREFIX = "CID"; + private static final String CID_SLOT_PREFIX = "CID_SLOT"; + private static final String GRAPH_ID_PREFIX = "GRAPH_ID"; + + public static byte[] getPartitionKey(String graph, Integer partId) { + // HUGEGRAPH/Partition/{graph}/partId + String key = StringBuilderHelper.get() + .append(HUGEGRAPH).append(DELIMITER) + .append(PARTITION).append(DELIMITER) + .append(graph).append(DELIMITER) + .append(partId) + .toString(); + return key.getBytes(StandardCharsets.UTF_8); + } + + public static byte[] getPartitionPrefixKey(String graph) { + // HUGEGRAPH/Partition/{graph}/ + String key = StringBuilderHelper.get() + .append(HUGEGRAPH).append(DELIMITER) + .append(PARTITION).append(DELIMITER) + .append(graph).append(DELIMITER) + .toString(); + return key.getBytes(StandardCharsets.UTF_8); + } + + /** + * 查询分区内的所有partition prefix, 不包含 graph name + * + * @return + */ + public static byte[] getPartitionPrefixKey() { + // HUGEGRAPH/Partition/ + String key = StringBuilderHelper.get() + .append(HUGEGRAPH).append(DELIMITER) + .append(PARTITION).append(DELIMITER) + .toString(); + return key.getBytes(StandardCharsets.UTF_8); + } + + public static byte[] getShardGroupKey(int partitionId) { + // HUGEGRAPH/SHARDGROUP/{partition_id} + String key = StringBuilderHelper.get() + .append(HUGEGRAPH).append(DELIMITER) + .append(SHARD_GROUP).append(DELIMITER) + .append(partitionId) + .toString(); + return key.getBytes(StandardCharsets.UTF_8); + } + + public static byte[] getGraphKey(String graph) { + // HUGEGRAPH/Graph/{graph} + String key = StringBuilderHelper.get() + .append(HUGEGRAPH).append(DELIMITER) + .append(GRAPH).append(DELIMITER) + .append(graph) + .toString(); + return key.getBytes(StandardCharsets.UTF_8); + } + + public static byte[] getGraphKeyPrefix() { + // HUGEGRAPH/Graph/{graph} + String key = StringBuilderHelper.get() + .append(HUGEGRAPH).append(DELIMITER) + .append(GRAPH).append(DELIMITER) + .toString(); + return key.getBytes(StandardCharsets.UTF_8); + } + + public static byte[] getStoreKey() { + // HUGEGRAPH/STORE/ + String key = StringBuilderHelper.get() + .append(HUGEGRAPH).append(DELIMITER) + .append(STORE) + .toString(); + return key.getBytes(StandardCharsets.UTF_8); + } + + public static byte[] getTaskKey(int partId, String type, long taskId) { + // HUGEGRAPH/TASK/ + String key = StringBuilderHelper.get() + .append(HUGEGRAPH).append(DELIMITER) + .append(TASK).append(DELIMITER) + .append(partId).append(DELIMITER) + .append(type).append(DELIMITER) + .append(String.format("%016x", taskId)) + .toString(); + return key.getBytes(StandardCharsets.UTF_8); + } + + public static byte[] getInstructionIdKey(long taskId) { + String key = StringBuilderHelper.get() + .append(HUGEGRAPH).append(DELIMITER) + .append(INSTRUCTION_TASK).append(DELIMITER) + .append(taskId).append(DELIMITER) + .toString(); + return key.getBytes(StandardCharsets.UTF_8); + } + + public static byte[] getAsyncTaskKey(int partId, String graphName, String taskId) { + // HUGEGRAPH/A_TASK/ part id / graphName / task id + String key = StringBuilderHelper.get() + .append(HUGEGRAPH).append(DELIMITER) + .append(ASYNC_TASK).append(DELIMITER) + .append(partId).append(DELIMITER) + .append(graphName).append(DELIMITER) + .append(taskId) + .toString(); + return key.getBytes(StandardCharsets.UTF_8); + } + + public static byte[] getAsyncTaskPrefix(int partId, String graphName) { + // HUGEGRAPH/A_TASK/ part id / graphName / task id + String key = StringBuilderHelper.get() + .append(HUGEGRAPH).append(DELIMITER) + .append(ASYNC_TASK).append(DELIMITER) + .append(partId).append(DELIMITER) + .append(graphName).append(DELIMITER) + .toString(); + return key.getBytes(StandardCharsets.UTF_8); + } + + public static byte[] getTaskPrefix(int partId, String type) { + // HUGEGRAPH/TASK/ + String key = StringBuilderHelper.get() + .append(HUGEGRAPH).append(DELIMITER) + .append(TASK).append(DELIMITER) + .append(partId).append(DELIMITER) + .append(type).append(DELIMITER) + .toString(); + return key.getBytes(StandardCharsets.UTF_8); + } + + public static byte[] getTaskPrefix(int partId) { + // HUGEGRAPH/TASK/ + String key = StringBuilderHelper.get() + .append(HUGEGRAPH).append(DELIMITER) + .append(TASK).append(DELIMITER) + .append(partId).append(DELIMITER) + .toString(); + return key.getBytes(StandardCharsets.UTF_8); + } + + public static byte[] getTaskPrefix() { + // HUGEGRAPH/TASK/ + String key = StringBuilderHelper.get() + .append(HUGEGRAPH).append(DELIMITER) + .append(TASK).append(DELIMITER) + .toString(); + return key.getBytes(StandardCharsets.UTF_8); + } + + public static byte[] getDoneTaskKey(long taskId) { + // HUGEGRAPH/TASK/ + String key = StringBuilderHelper.get() + .append(HUGEGRAPH).append(DELIMITER) + .append(TASK_DONE).append(DELIMITER) + .append(String.format("%016x", taskId)) + .toString(); + return key.getBytes(StandardCharsets.UTF_8); + } + + public static byte[] getPartitionStoreKey(int partId) { + // HUGEGRAPH/TASK/ + String key = StringBuilderHelper.get() + .append(HUGEGRAPH).append(DELIMITER) + .append(PARTITION_STORE).append(DELIMITER) + .append(partId) + .toString(); + return key.getBytes(StandardCharsets.UTF_8); + } + + public static byte[] getPartitionStorePrefix() { + // HUGEGRAPH/TASK/ + String key = StringBuilderHelper.get() + .append(HUGEGRAPH).append(DELIMITER) + .append(PARTITION_STORE).append(DELIMITER) + .toString(); + return key.getBytes(StandardCharsets.UTF_8); + } + + public static byte[] getPartitionRaftKey(int partId) { + // HUGEGRAPH/TASK/ + String key = StringBuilderHelper.get() + .append(HUGEGRAPH).append(DELIMITER) + .append(PARTITION_RAFT).append(DELIMITER) + .append(partId) + .toString(); + return key.getBytes(StandardCharsets.UTF_8); + } + + public static byte[] getPartitionRaftPrefix() { + // HUGEGRAPH/TASK/ + String key = StringBuilderHelper.get() + .append(HUGEGRAPH).append(DELIMITER) + .append(PARTITION_RAFT).append(DELIMITER) + .toString(); + return key.getBytes(StandardCharsets.UTF_8); + } + + public static byte[] getDeletedFileKey(String filePath) { + // HUGEGRAPH/TASK/ + String key = StringBuilderHelper.get() + .append(HUGEGRAPH).append(DELIMITER) + .append(DELETED_FILE).append(DELIMITER) + .append(filePath) + .toString(); + return key.getBytes(StandardCharsets.UTF_8); + } + + public static byte[] getDeletedFilePrefix() { + // HUGEGRAPH/TASK/ + String key = StringBuilderHelper.get() + .append(HUGEGRAPH).append(DELIMITER) + .append(DELETED_FILE).append(DELIMITER) + .toString(); + return key.getBytes(StandardCharsets.UTF_8); + } + + public static byte[] getCidKey(String name) { + // HUGEGRAPH/CID/ + String key = StringBuilderHelper.get() + .append(HUGEGRAPH).append(DELIMITER) + .append(CID_PREFIX).append(DELIMITER) + .append(name) + .toString(); + return key.getBytes(StandardCharsets.UTF_8); + } + + public static byte[] getCidSlotKeyPrefix(String name) { + // HUGEGRAPH/CID_SLOT/ + String key = StringBuilderHelper.get() + .append(HUGEGRAPH).append(DELIMITER) + .append(CID_SLOT_PREFIX).append(DELIMITER) + .append(name).append(DELIMITER) + .toString(); + return key.getBytes(StandardCharsets.UTF_8); + } + + public static byte[] getGraphIDKey(String graph) { + // HUGEGRAPH/Graph/{graph} + String key = StringBuilderHelper.get() + .append(HUGEGRAPH).append(DELIMITER) + .append(GRAPH_ID_PREFIX).append(DELIMITER) + .append(graph) + .toString(); + return key.getBytes(StandardCharsets.UTF_8); + } + + static class StringBuilderHelper { + private static final int DISCARD_LIMIT = 1024 << 3; // 8k + + private static final ThreadLocal holderThreadLocal = ThreadLocal + .withInitial(StringBuilderHolder::new); + + public static StringBuilder get() { + final StringBuilderHolder holder = holderThreadLocal.get(); + return holder.getStringBuilder(); + } + + public static void truncate() { + final StringBuilderHolder holder = holderThreadLocal.get(); + holder.truncate(); + } + + private static class StringBuilderHolder { + + private final StringBuilder buf = new StringBuilder(); + + private StringBuilder getStringBuilder() { + truncate(); + return buf; + } + + private void truncate() { + buf.setLength(0); + } + } + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/Partition.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/Partition.java new file mode 100644 index 000000000..8aca2b021 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/Partition.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.meta; + +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.store.HgStoreEngine; +import org.apache.hugegraph.store.PartitionEngine; + +import lombok.Data; + +/** + * @projectName: hugegraph-store + * @package: org.apache.hugegraph.store.partitions + * @className: Partition + * @description: TODO + * @date: 2021/10/19 18:16 + * @version: 1.0 + */ +@Data +public class Partition implements Cloneable { + private int id; // region id + private String graphName; + // Region key range [startKey, endKey) + private long startKey; + private long endKey; + private long version; + // shardlist版本,shardlist每次改变加1 + // private long confVer; + + private Metapb.PartitionState workState; + // private PartitionRole role; + // private List shardsList; + // exclusive + + public Partition() { + workState = Metapb.PartitionState.PState_Normal; + } + + public Partition(Metapb.Partition protoObj) { + id = protoObj.getId(); + graphName = protoObj.getGraphName(); + startKey = protoObj.getStartKey(); + endKey = protoObj.getEndKey(); + // shardsList = protoObj.getShardsList(); + workState = protoObj.getState(); + version = protoObj.getVersion(); + // confVer = protoObj.getConfVer(); + if (workState == Metapb.PartitionState.UNRECOGNIZED || + workState == Metapb.PartitionState.PState_None) { + workState = Metapb.PartitionState.PState_Normal; + } + } + + public boolean isLeader() { + PartitionEngine engine = HgStoreEngine.getInstance().getPartitionEngine(id); + return engine != null && engine.isLeader(); + } + + public Metapb.Partition getProtoObj() { + return Metapb.Partition.newBuilder() + .setId(id) + .setVersion(version) + // .setConfVer(confVer) + .setGraphName(graphName) + .setStartKey(startKey) + .setEndKey(endKey) + .setState(workState) + // .addAllShards(shardsList) + .build(); + } + + @Override + public Partition clone() { + Partition obj = null; + try { + obj = (Partition) super.clone(); + } catch (CloneNotSupportedException e) { + e.printStackTrace(); + } + return obj; + } + + @Override + public String toString() { + return getProtoObj().toString(); + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/PartitionManager.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/PartitionManager.java new file mode 100644 index 000000000..f25f2b53a --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/PartitionManager.java @@ -0,0 +1,925 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.meta; + +import java.io.File; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Consumer; + +import org.apache.hugegraph.pd.common.PDException; +import org.apache.hugegraph.pd.grpc.MetaTask; +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.store.HgStoreEngine; +import org.apache.hugegraph.store.business.BusinessHandlerImpl; +import org.apache.hugegraph.store.cmd.UpdatePartitionRequest; +import org.apache.hugegraph.store.cmd.UpdatePartitionResponse; +import org.apache.hugegraph.store.meta.base.GlobalMetaStore; +import org.apache.hugegraph.store.options.HgStoreEngineOptions; +import org.apache.hugegraph.store.options.MetadataOptions; +import org.apache.hugegraph.store.pd.PdProvider; +import org.apache.hugegraph.store.util.PartitionMetaStoreWrapper; +import org.apache.hugegraph.util.Log; +import org.slf4j.Logger; + +import com.alipay.sofa.jraft.core.ElectionPriority; + +import lombok.extern.slf4j.Slf4j; + + +/** + * Partition对象管理策略,每次修改需要克隆一份,并且版本号递增 + */ +@Slf4j +public class PartitionManager extends GlobalMetaStore { + private static final Logger LOG = Log.logger(PartitionManager.class); + private final PdProvider pdProvider; + private final GraphManager graphManager; + private final StoreMetadata storeMetadata; + private final DeletedFileManager deletedFileManager; + private final boolean useRaft; + private final HgStoreEngineOptions options; + private final List partitionChangedListeners; + // 读写锁对象 + private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); + + private final PartitionMetaStoreWrapper wrapper = new PartitionMetaStoreWrapper(); + + + // 记录本机所有的分区信息,与rocksdb存储保持一致 + private Map> partitions; + + public PartitionManager(PdProvider pdProvider, HgStoreEngineOptions options) { + super(new MetadataOptions() {{ + setDataPath(options.getDataPath()); + setRaftPath(options.getRaftPath()); + }}); + this.options = options; + this.pdProvider = pdProvider; + partitions = new ConcurrentHashMap<>(); + + storeMetadata = new StoreMetadata(getOptions()); + graphManager = new GraphManager(getOptions(), pdProvider); + deletedFileManager = new DeletedFileManager(getOptions()); + this.useRaft = true; + partitionChangedListeners = new ArrayList<>(); + } + + public void load() { + storeMetadata.load(); + graphManager.load(); + deletedFileManager.load(); + } + + public void loadPartition() { + loadPartitions(); + } + + + public DeletedFileManager getDeletedFileManager() { + return deletedFileManager; + } + + public PdProvider getPdProvider() { + return pdProvider; + } + + public StoreMetadata getStoreMetadata() { + return storeMetadata; + } + + public void addPartitionChangedListener(PartitionChangedListener listener) { + partitionChangedListeners.add(listener); + } + + /** + * 判断存储路径为分区id或者分区id_开头 + * + * @param detections dir list + * @param partitionId partition id + * @param checkLogDir : 是否包含子目录 log ( raft snapshot 和log 分离,需要进一步检查) + * @return true if contains partition id, otherwise false + */ + private Boolean checkPathContains(File[] detections, int partitionId, boolean checkLogDir) { + String partitionDirectory = String.format("%05d", partitionId); + for (int x = 0; x < detections.length; x++) { + // 一定是以分区id命名的文件夹下 + if (detections[x].isDirectory()) { + String tmp = detections[x].getName(); + if (tmp.equals(partitionDirectory) || tmp.startsWith(partitionDirectory + "_")) { + if (checkLogDir) { + String logDir = detections[x].getAbsolutePath() + "/log"; + if (new File(logDir).exists()) { + return true; + } + } else { + return true; + } + } + } + } + return false; + } + + /** + * 根据配置文件根目录,循环查找分区的存储路径 + * 根据约定db数据在dataPath/db/分区id目录,raft数据在dataPath/raft/分区id目录 + * 检测分区存储文件夹是否存在 + */ + private Boolean resetPartitionPath(int partitionId) { + List dataPaths = Arrays.asList(this.options.getDataPath().split(",")); + List raftPaths = Arrays.asList(this.options.getRaftPath().split(",")); + + boolean isDataOk = false; + boolean isRaftOk = false; + + // 检查 db 目录 + for (int i = 0; i < dataPaths.size(); i++) { + String dbPath = Paths.get(dataPaths.get(i), + HgStoreEngineOptions.DB_Path_Prefix).toAbsolutePath() + .toString(); + File dbFile = new File(dbPath); + if (dbFile.exists()) { + File[] dbFiles = dbFile.listFiles(); + + if (this.checkPathContains(dbFiles, partitionId, false)) { + Metapb.PartitionStore location = storeMetadata.getPartitionStore(partitionId); + if (!location.getStoreLocation().equals(dataPaths.get(i))) { + Metapb.PartitionStore newLocation = location.toBuilder() + .setStoreLocation( + dataPaths.get(i)) + .build(); + storeMetadata.savePartitionStore(newLocation); + } + isDataOk = true; + break; + } + } + } + + // 检查 raft目录 + for (int i = 0; i < raftPaths.size(); i++) { + String raftPath = Paths.get(raftPaths.get(i), + HgStoreEngineOptions.Raft_Path_Prefix).toAbsolutePath() + .toString(); + + File raftFile = new File(raftPath); + + if (raftFile.exists()) { + File[] raftFiles = raftFile.listFiles(); + if (this.checkPathContains(raftFiles, partitionId, true)) { + Metapb.PartitionRaft location = storeMetadata.getPartitionRaft(partitionId); + // 兼容版本升级 + if (location == null || + !Objects.equals(location.getRaftLocation(), raftPaths.get(i))) { + Metapb.PartitionRaft newLocation = Metapb.PartitionRaft.newBuilder() + .setPartitionId( + partitionId) + .setRaftLocation( + raftPaths.get( + i)) + .build(); + storeMetadata.savePartitionRaft(newLocation); + } + isRaftOk = true; + break; + } + } + } + + return isDataOk && isRaftOk; + } + + /** + * 从本地storage中读取分区 + */ + private void loadPartitions() { + byte[] key = MetadataKeyHelper.getPartitionPrefixKey(); + long storeId = getStore().getId(); + + // 从data path中读取 partition + // 记录有哪些分区 + var partIds = new HashSet(); + for (String path : this.options.getDataPath().split(",")) { + File[] dirs = new File(path + "/" + HgStoreEngineOptions.DB_Path_Prefix).listFiles(); + if (dirs == null) { + continue; + } + + for (File f : dirs) { + if (f.isDirectory()) { + try { + partIds.add(Integer.parseInt(f.getName().split("_")[0])); + } catch (Exception e) { + log.error("find illegal dir {} in data path, error:{}", f.getName(), + e.getMessage()); + } + } + } + } + + // 一次按照分区读取 + for (int partId : partIds) { + if (!resetPartitionPath(partId)) { + log.error("partition " + partId + " Directory not exists,options " + + this.options.getDataPath()); + continue; + } + + for (var metaPart : wrapper.scan(partId, Metapb.Partition.parser(), key)) { + var graph = metaPart.getGraphName(); + var pdPartition = pdProvider.getPartitionByID(graph, metaPart.getId()); + boolean isLegeal = false; + + var shards = pdProvider.getShardGroup(metaPart.getId()).getShardsList(); + + if (pdPartition != null) { + // 判断是否包含本store id + if (shards.stream().anyMatch(s -> s.getStoreId() == storeId)) { + isLegeal = true; + } + } + + if (isLegeal) { + if (!partitions.containsKey(graph)) { + partitions.put(graph, new ConcurrentHashMap<>()); + } + + Partition partition = new Partition(metaPart); + partition.setWorkState(Metapb.PartitionState.PState_Normal); // 启动恢复工作状态 + partitions.get(graph).put(partition.getId(), partition); + log.info("load partition : {} -{}", partition.getGraphName(), + partition.getId()); + } else { + // 无效 + // removePartitionFromLocalDb(graph, partId); + // var businessHandler = HgStoreEngine.getInstance().getBusinessHandler(); + // businessHandler.truncate(graph, partId); + // businessHandler.dbCompaction(graph, partId); + log.error("partition {}-{} is illegal. store id {} not in valid shard group:{}", + graph, partId, getStore().getId(), shards2Peers(shards)); + System.exit(0); + } + } + } + } + + public List loadPartitionsFromDb(int partitionId) { + byte[] key = MetadataKeyHelper.getPartitionPrefixKey(); + return wrapper.scan(partitionId, Metapb.Partition.parser(), key); + } + + /** + * 从PD同步分区,并删除本地多余的分区 + * 同步过程中,新增分区需要保存到本地,已有的分区信息与本地进行合并 + */ + public void syncPartitionsFromPD(Consumer delCallback) throws PDException { + Lock writeLock = readWriteLock.writeLock(); + writeLock.lock(); + try { + + List partListFrPD = + pdProvider.getPartitionsByStore(storeMetadata.getStore().getId()); + + Map> graphPtFrpd = new HashMap<>(); + partListFrPD.forEach(partition -> { + if (!graphPtFrpd.containsKey(partition.getGraphName())) { + graphPtFrpd.put(partition.getGraphName(), new HashMap<>()); + } + if (isLocalPartition(partition)) { + graphPtFrpd.get(partition.getGraphName()).put(partition.getId(), partition); + } + }); + + // 遍历本地图,删除本地多余,追加新的。 + partitions.forEach((graphName, v) -> { + Map partitionsFrpd = graphPtFrpd.get(graphName); + v.forEach((id, pt) -> { + if (partitionsFrpd == null || !partitionsFrpd.containsKey(id)) { + // 本地的分区,pd已不存在,需要删除 + delCallback.accept(pt); + removePartition(pt.getGraphName(), pt.getId()); + } else { + // 修改shard信息 + // Partition ptFrpd = partitionsFrpd.get(id); + // pt.setShardsList(ptFrpd.getShardsList()); + savePartition(pt, true, true); + + } + }); + if (partitionsFrpd != null) { + partitionsFrpd.forEach((id, pt) -> { + if (!v.containsKey(id)) { + // 新增的分区 + savePartition(pt, true); + } + }); + } + }); + partitions = graphPtFrpd; + } finally { + writeLock.unlock(); + } + } + + public Partition changeState(Partition partition, Metapb.PartitionState state) { + Lock writeLock = readWriteLock.writeLock(); + writeLock.lock(); + try { + partition = findPartition(partition.getGraphName(), partition.getId()); + partition.setWorkState(state); + savePartition(partition, false); + } finally { + writeLock.unlock(); + } + return partition; + } + + public Partition changeKeyRange(Partition partition, int startKey, int endKey) { + Lock writeLock = readWriteLock.writeLock(); + writeLock.lock(); + try { + partition = findPartition(partition.getGraphName(), partition.getId()); + partition.setStartKey(startKey); + partition.setEndKey(endKey); + savePartition(partition, false, true); + } finally { + writeLock.unlock(); + } + return partition; + } + + public Partition updatePartition(Metapb.Partition partition, boolean updateRange) { + return updatePartition(new Partition(partition), updateRange); + } + + /** + * 增加partition对象 + * + * @param partition + * @return + */ + public Partition updatePartition(Partition partition, boolean updateRange) { + Lock writeLock = readWriteLock.writeLock(); + writeLock.lock(); + try { + savePartition(partition, true, updateRange); + } finally { + writeLock.unlock(); + } + return partition; + } + + public void updatePartitionRangeOrState(UpdatePartitionRequest req) { + Lock writeLock = readWriteLock.writeLock(); + writeLock.lock(); + try { + Partition partition = findPartition(req.getGraphName(), req.getPartitionId()); + if (req.getStartKey() >= 0 && req.getEndKey() > 0 + && partition.getStartKey() != req.getStartKey() && + partition.getEndKey() != req.getEndKey()) { + changeKeyRange(partition, req.getStartKey(), req.getEndKey()); + } + if (req.getWorkState() != null) { + changeState(partition, req.getWorkState()); + } + } finally { + writeLock.unlock(); + } + } + + /** + * 强制更新 partition,不校验 version + * + * @param partition + * @return + */ + public Partition loadPartitionFromSnapshot(Partition partition) { + Lock writeLock = readWriteLock.writeLock(); + writeLock.lock(); + try { + savePartition(partition, true, true); + } finally { + writeLock.unlock(); + } + return partition; + } + + /** + * 查找属于本机的Partiton,优先从本地查找,本地未找到,询问pd。 + * + * @param graph + * @param partId + * @return + */ + public Partition findPartition(String graph, Integer partId) { + Partition partition = null; + if (partitions.containsKey(graph)) { + partition = partitions.get(graph).get(partId); + } + + if (partition == null) { + partition = pdProvider.getPartitionByID(graph, partId); + if (partition != null) { + if (isLocalPartition(partition)) { + + // 属于本机的partion,保存partition + Lock writeLock = readWriteLock.writeLock(); + writeLock.lock(); + try { + savePartition(partition, true); + } finally { + writeLock.unlock(); + } + } else { + LOG.error("Partition {}-{} does not belong to local store! store id{} \n {}", + graph, partId, + storeMetadata.getStore().getId(), partition.getProtoObj()); + return null; + } + } else { + LOG.error("Partition {}-{} is not Found! ", graph, partId); + return null; + } + } + return partitions.get(graph).get(partId); + } + + public int getPartitionIdByCode(String graph, int code) { + return pdProvider.getPartitionByCode(graph, code).getId(); + } + + /** + * 从pd获取拉取分区信息,并和本地的分区信息进行合并。leader和shardList取自本地 + */ + public Partition getPartitionFromPD(String graph, int partId) { + pdProvider.invalidPartitionCache(graph, partId); + Partition partition = pdProvider.getPartitionByID(graph, partId); + Lock writeLock = readWriteLock.writeLock(); + writeLock.lock(); + try { + if (partitions.containsKey(graph)) { + Partition local = partitions.get(graph).get(partId); + if (local != null) { + //更新本地的key范围,保证pd和本地分区信息的一致性 + local.setStartKey(partition.getStartKey()); + local.setEndKey(partition.getEndKey()); + savePartition(local, true, true); + } + partition = local; + } + } finally { + writeLock.unlock(); + } + return partition; + } + + /** + * 是否是本地的分区 + * 对于批处理入库,只有leader才属于本地 + * + * @param partition + * @return + */ + public boolean isLocalPartition(Partition partition) { + boolean isLocal = false; + var shardGroup = getShardGroup(partition.getId()); + if (shardGroup != null) { + for (Shard shard : shardGroup.getShards()) { + if (shard.getStoreId() == storeMetadata.getStore().getId()) { + isLocal = true; + break; + } + } + } + return isLocal; + } + + /** + * 是否是本地的分区 + * 对于批处理入库,只有leader才属于本地 + * + * @return + */ + public boolean isLocalPartition(int partId) { + return pdProvider.isLocalPartition(storeMetadata.getStore().getId(), partId); + } + + /** + * 存储partition信息,同步保存到内存和rocksdb + * 不更新key range + */ + + private void savePartition(Partition partition, Boolean changeLeader) { + savePartition(partition, changeLeader, false); + } + + /** + * 保存partition 信息 + * + * @param partition partition + * @param changeLeader is change leader + * @param changeRange update start and end key if yes. + * using key range in local if no and partition key exists + */ + + private void savePartition(Partition partition, Boolean changeLeader, Boolean changeRange) { + String graphName = partition.getGraphName(); + Integer partId = partition.getId(); + byte[] key = MetadataKeyHelper.getPartitionKey(graphName, partId); + + if (!changeRange) { + var local = wrapper.get(partId, key, Metapb.Partition.parser()); + if (local != null) { + partition.setStartKey(local.getStartKey()); + partition.setEndKey(local.getEndKey()); + } + } + + if (!partitions.containsKey(graphName)) { + partitions.put(graphName, new ConcurrentHashMap<>()); + } + partitions.get(graphName).put(partition.getId(), partition); + + // put(key, partition.getProtoObj().toByteArray()); + wrapper.put(partId, key, partition.getProtoObj().toByteArray()); + + Graph graph = new Graph(); + graph.setGraphName(partition.getGraphName()); + + graphManager.updateGraph(graph); + // 更新PD cache,后序优化,store不依赖pdclient cache + pdProvider.updatePartitionCache(partition, changeLeader); + + partitionChangedListeners.forEach(listener -> { + listener.onChanged(partition); // 通知raft,进行同步分区信息同步 + }); + } + + /** + * 更新shard group到db, 同时更新shardGroups对象 + * + * @param shardGroup + */ + public void updateShardGroup(ShardGroup shardGroup) { + Lock writeLock = readWriteLock.writeLock(); + writeLock.lock(); + wrapper.put(shardGroup.getId(), + MetadataKeyHelper.getShardGroupKey(shardGroup.getId()), + shardGroup.getProtoObj().toByteArray()); + writeLock.unlock(); + } + + /** + * 查找 partition id对应的shard group。 + * 依次从 raft node/local db/ pd 读取。 + * + * @param partitionId + * @return + */ + public ShardGroup getShardGroup(int partitionId) { + var partitionEngine = HgStoreEngine.getInstance().getPartitionEngine(partitionId); + if (partitionEngine != null) { + return partitionEngine.getShardGroup(); + } + + Metapb.ShardGroup shardGroup = + wrapper.get(partitionId, MetadataKeyHelper.getShardGroupKey(partitionId), + Metapb.ShardGroup.parser()); + + if (shardGroup == null) { + shardGroup = pdProvider.getShardGroup(partitionId); + + if (shardGroup != null) { + // local not found, write back to db from pd + wrapper.put(partitionId, MetadataKeyHelper.getShardGroupKey(partitionId), + shardGroup.toByteArray()); + } else { + log.error("get shard group {} from pd failed", partitionId); + } + } + return ShardGroup.from(shardGroup); + } + + public Partition removePartition(String graphName, Integer partId) { + log.info("partition manager: remove partition : {}-{}", graphName, partId); + if (partitions.containsKey(graphName)) { + pdProvider.invalidPartitionCache(graphName, partId); + removePartitionFromLocalDb(graphName, partId); + Partition partition = partitions.get(graphName).remove(partId); + log.info("partition manager: remove partition, partition: {}", partition); + if (partitions.get(graphName).size() == 0) { + log.info("remove graph {}", graphName); + graphManager.removeGraph(graphName); + } + return partition; + } + return null; + } + + private void removePartitionFromLocalDb(String graphName, Integer partId) { + byte[] key = MetadataKeyHelper.getPartitionKey(graphName, partId); + // delete(key); + wrapper.delete(partId, key); + } + + /** + * 删除图数据,删除本地数据,并删除PD上的分区信息 + */ + public Partition deletePartition(String graphName, Integer partId) { + removePartition(graphName, partId); + return pdProvider.delPartition(graphName, partId); + } + + // 获取本地Store信息 + public Store getStore() { + return storeMetadata.getStore(); + } + + // 注册会修改StoreId,需要重置 + public void setStore(Store store) { + Lock writeLock = readWriteLock.writeLock(); + writeLock.lock(); + try { + storeMetadata.save(store); + } finally { + writeLock.unlock(); + } + } + + public Store getStore(Long storeId) { + return pdProvider.getStoreByID(storeId); + } + + public Map> getPartitions() { + return partitions; + } + + public Map getPartitions(int partitionId) { + Map result = new HashMap<>(); + this.partitions.forEach((k, v) -> { + v.forEach((k1, v1) -> { + if (k1 == partitionId) { + result.put(k, v1); + } + }); + } + ); + return result; + } + + public Partition getPartition(String graphName, int partitionId) { + return this.partitions.getOrDefault(graphName, new HashMap<>()) + .getOrDefault(partitionId, null); + } + + public List getPartitionList(int partitionId) { + List pts = new ArrayList<>(); + getPartitions(partitionId).forEach((k, v) -> { + pts.add(findPartition(k, v.getId())); + }); + return pts; + } + + public boolean hasPartition(String graphName, int partitionId) { + return this.partitions.getOrDefault(graphName, new HashMap<>()).containsKey(partitionId); + } + + /** + * 获取图在本机中所有Leader 分区 + * + * @param graph + * @return + */ + public List getLeaderPartitionIds(String graph) { + List ids = new ArrayList<>(); + if (partitions.containsKey(graph)) { + partitions.get(graph).forEach((k, v) -> { + if (!useRaft || v.isLeader()) { + ids.add(k); + } + }); + } + return ids; + } + + + /** + * 生成分区peer字符串,包含优先级信息 * + * + * @param shardGroup + * @return + */ + public List getPartitionPeers(ShardGroup shardGroup) { + List peers = new ArrayList<>(); + final int decayPriorityGap = 10; + int priority = 100; + if (shardGroup != null) { + for (Shard shard : shardGroup.getShards()) { + Store store = getStore(shard.getStoreId()); + if (store != null && !store.getRaftAddress().isEmpty()) { + peers.add(store.getRaftAddress() + "::" + priority); + final int gap = Math.max(decayPriorityGap, (priority / 5)); + priority = Math.max(ElectionPriority.MinValue, (priority - gap)); + } + } + } + + return peers; + } + + + public List shards2Peers(List shards) { + List peers = new ArrayList<>(); + shards.forEach(s -> { + peers.add(getStore(s.getStoreId()).getRaftAddress()); + }); + return peers; + } + + /** + * 是否是本地store + * + * @param store + * @return + */ + public boolean isLocalStore(Store store) { + return storeMetadata.getStore().getId() == store.getId(); + } + + public PartitionRole getLocalRoleFromShard(Partition partition) { + return partition.isLeader() ? PartitionRole.LEADER : PartitionRole.FOLLOWER; + } + + /** + * 修改分区角色 + */ + public Partition changeLeader(Partition pt, List shards, long term) { + Lock writeLock = readWriteLock.writeLock(); + writeLock.lock(); + try { + Partition partition = findPartition(pt.getGraphName(), pt.getId()); + if (partition != null) { + // partition.setShardsList(shards); + partition.setVersion(term); + savePartition(partition, true); + } + return partition; + } finally { + writeLock.unlock(); + } + } + + /** + * 根据raft peers清单,重建shardList + */ + public Partition changeShards(Partition pt, List shards) { + Lock writeLock = readWriteLock.writeLock(); + writeLock.lock(); + try { + Partition partition = findPartition(pt.getGraphName(), pt.getId()); + if (partition != null) { + // partition.setShardsList(shards); + // partition.setConfVer(partition.getConfVer() + 1); + savePartition(partition, true); + } + return partition; + } finally { + writeLock.unlock(); + } + } + + /** + * 拆分partition对象 + */ + public List updatePartitionToPD(List partitions) throws + PDException { + // 更新本地分区信息,以及cache信息 + return pdProvider.updatePartition(partitions); + } + + /** + * 根据raft address查找Store + */ + public Store getStoreByRaftEndpoint(ShardGroup group, String endpoint) { + final Store[] result = {new Store()}; + group.getShards().forEach((shard) -> { + Store store = getStore(shard.getStoreId()); + if (store != null && store.getRaftAddress().equalsIgnoreCase(endpoint)) { + result[0] = store; + } + }); + return result[0]; + } + + public Shard getShardByRaftEndpoint(ShardGroup group, String endpoint) { + final Shard[] result = {new Shard()}; + group.getShards().forEach((shard) -> { + Store store = getStore(shard.getStoreId()); + if (store != null && store.getRaftAddress().equalsIgnoreCase(endpoint)) { + result[0] = shard; + } + }); + return result[0]; + } + + /** + * raft存储路径 + * + * @param groupId + * @return location/raft/groupId/ + */ + public String getRaftDataPath(int groupId) { + String location = storeMetadata.getPartitionRaftLocation(groupId); + location = Paths.get(location, + HgStoreEngineOptions.Raft_Path_Prefix, + String.format("%05d", groupId)).toAbsolutePath().toString(); + return location; + } + + /** + * raft snapshot 的路径,要和 db同一个盘上,便于hard link + * + * @param groupId raft group id + * @return location/snapshot/0000x/ + */ + public String getRaftSnapShotPath(int groupId) { + String dbName = BusinessHandlerImpl.getDbName(groupId); + String location = storeMetadata.getPartitionStoreLocation(groupId, dbName); + location = Paths.get(location, + HgStoreEngineOptions.Raft_Path_Prefix, + dbName).toAbsolutePath().toString(); + return location; + } + + /** + * db存储路径 + * + * @return location/db + */ + public String getDbDataPath(int partitionId, String dbName) { + String location = storeMetadata.getPartitionStoreLocation(partitionId, dbName); + location = Paths.get(location, + HgStoreEngineOptions.DB_Path_Prefix).toAbsolutePath().toString(); + return location; + } + + + public void reportTask(MetaTask.Task task) { + try { + pdProvider.reportTask(task); + } catch (Exception e) { + LOG.error("reportTask exception {}, {}", e, task); + } + } + + /** + * 修改partion的state状态 + */ + public List changePartitionToOnLine(List partitions) { + List newPartitions = new ArrayList<>(); + partitions.forEach(e -> { + newPartitions.add(e.toBuilder().setState(Metapb.PartitionState.PState_Normal).build()); + }); + return newPartitions; + } + + public PartitionMetaStoreWrapper getWrapper() { + return wrapper; + } + + /** + * Partition对象被修改消息 + */ + public interface PartitionChangedListener { + void onChanged(Partition partition); + + UpdatePartitionResponse rangeOrStateChanged(UpdatePartitionRequest request); + } + +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/PartitionRole.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/PartitionRole.java new file mode 100644 index 000000000..b001b5536 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/PartitionRole.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.meta; + +import org.apache.hugegraph.pd.grpc.Metapb; + +/** + * @projectName: hugegraph-store + * @package: org.apache.hugegraph.store.partitions + * @className: PartitionRole + * @description: TODO + * @date: 2021/10/19 18:18 + * @version: 1.0 + */ +public enum PartitionRole { + UNKNOWN(0, "unknown"), + LEADER(1, "leader"), + FOLLOWER(2, "follower"), + LEARNER(3, "learner"), + CANDIDATE(4, "candidate"); + private final int role; + private final String name; + + PartitionRole(int role, String name) { + this.role = role; + this.name = name; + } + + public static PartitionRole fromShardRole(Metapb.ShardRole shard) { + PartitionRole role = PartitionRole.FOLLOWER; + switch (shard) { + case Leader: + role = PartitionRole.LEADER; + break; + case Follower: + role = PartitionRole.FOLLOWER; + break; + case Learner: + role = PartitionRole.LEARNER; + break; + } + return role; + + } + + @Override + public String toString() { + return this.ordinal() + "_" + this.name; + } + + public String getName() { + return this.name; + } + + public Metapb.ShardRole toShardRole() { + Metapb.ShardRole shardRole = Metapb.ShardRole.None; + switch (this) { + case LEADER: + shardRole = Metapb.ShardRole.Leader; + break; + case FOLLOWER: + shardRole = Metapb.ShardRole.Follower; + break; + case LEARNER: + shardRole = Metapb.ShardRole.Learner; + break; + } + return shardRole; + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/PartitionStats.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/PartitionStats.java new file mode 100644 index 000000000..485c365c3 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/PartitionStats.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.meta; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.hugegraph.pd.grpc.Metapb; + +import lombok.Data; + +@Data +public class PartitionStats { + // 分区leader所在shard + Metapb.Shard leader; + // 分区离线的shard + List offlineShards = new ArrayList<>(); + long committedIndex; + long leaderTerm; + long approximateSize; + long approximateKeys; + // 分区ID + private int id; + private String namespace; + private String graphName; + + public PartitionStats addOfflineShard(Metapb.Shard shard) { + offlineShards.add(shard); + return this; + } + + public Metapb.PartitionStats getProtoObj() { + return Metapb.PartitionStats.newBuilder() + .setId(id) + .addGraphName(graphName) + .setLeader(leader) + .addAllShard(offlineShards) + .setApproximateKeys(approximateKeys) + .setApproximateSize(approximateSize) + .setLeaderTerm(leaderTerm) + .build(); + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/Shard.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/Shard.java new file mode 100644 index 000000000..b8305f09c --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/Shard.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.meta; + +import org.apache.hugegraph.pd.grpc.Metapb; + +import lombok.Data; + +/** + * 一个分片 + */ +@Data +public class Shard { + private long storeId; + private Metapb.ShardRole role; + + public static Shard fromMetaPbShard(Metapb.Shard shard) { + Shard s = new Shard(); + s.setRole(shard.getRole()); + s.setStoreId(shard.getStoreId()); + return s; + } + + public Metapb.Shard toMetaPbShard() { + return Metapb.Shard.newBuilder().setStoreId(storeId).setRole(role).build(); + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/ShardGroup.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/ShardGroup.java new file mode 100644 index 000000000..7581bc29b --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/ShardGroup.java @@ -0,0 +1,114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.meta; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.stream.Collectors; + +import org.apache.hugegraph.pd.grpc.Metapb; + +import lombok.Data; + +/** + * 分片副本组 + */ +@Data +public class ShardGroup { + private List shards = new CopyOnWriteArrayList<>(); + private int id; + /** + * Leader 任期,leader 切换后递增 = raftNode.leader_term + * 无实际用处 + */ + private long version; + /** + * shards 版本号,每次改变后递增 + */ + private long confVersion; + + public static ShardGroup from(Metapb.ShardGroup meta) { + if (meta == null) { + return null; + } + ShardGroup shardGroup = new ShardGroup(); + shardGroup.setId(meta.getId()); + shardGroup.setVersion(meta.getVersion()); + shardGroup.setConfVersion(meta.getConfVer()); + shardGroup.setShards(meta.getShardsList().stream().map(Shard::fromMetaPbShard) + .collect(Collectors.toList())); + return shardGroup; + } + + public synchronized ShardGroup changeLeader(long storeId) { + shards.forEach(shard -> { + shard.setRole(shard.getStoreId() == storeId ? Metapb.ShardRole.Leader : + Metapb.ShardRole.Follower); + }); + return this; + } + + public synchronized ShardGroup changeShardList(List peerIds, List learners, + long leaderId) { + if (!peerIds.isEmpty()) { + shards.clear(); + peerIds.forEach(id -> { + shards.add(new Shard() {{ + setStoreId(id); + setRole(id == leaderId ? Metapb.ShardRole.Leader : Metapb.ShardRole.Follower); + }}); + }); + + learners.forEach(id -> { + shards.add(new Shard() {{ + setStoreId(id); + setRole(Metapb.ShardRole.Learner); + }}); + }); + confVersion = confVersion + 1; + } + return this; + } + + public synchronized List getMetaPbShard() { + List shardList = new ArrayList<>(); + shards.forEach(shard -> { + shardList.add(shard.toMetaPbShard()); + }); + return shardList; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + shards.forEach(e -> { + builder.append(String.format("{ id:%s,role:%s },", e.getStoreId(), e.getRole())); + }); + return builder.length() > 0 ? builder.substring(0, builder.length() - 1) : ""; + } + + public Metapb.ShardGroup getProtoObj() { + return Metapb.ShardGroup.newBuilder() + .setId(this.id) + .setVersion(this.version) + .setConfVer(this.confVersion) + .addAllShards(getMetaPbShard()) + .build(); + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/Store.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/Store.java new file mode 100644 index 000000000..23df46796 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/Store.java @@ -0,0 +1,104 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.meta; + +import java.util.Map; + +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.store.util.Asserts; +import org.apache.hugegraph.store.util.Version; + +import lombok.Data; + +/** + * @projectName: hugegraph-store + * @package: org.apache.hugegraph.store.core + * @className: HgStoreNode + * @description: TODO + * @date: 2021/10/19 19:44 + * @version: 1.0 + */ +@Data +public class Store { + private final String version; + private long id = 0; + private String storeAddress; + private String pdAddress; + private String raftAddress; + private String deployPath; + private String dataPath; // 数据存储路径 + private int dataVersion; + private int partitionCount; + private int startTime; + private int usedSize; //rocksdb存储大小 + private int pdHeartbeatInterval; + private Metapb.StoreState state; + private Map labels; + private int cores; + + public Store() { + this.id = 0; + this.version = Version.getVersion(); + } + + public Store(int dataVersion) { + this.id = 0; + this.dataVersion = dataVersion; + this.version = Version.getVersion(); + } + + public Store(Metapb.Store protoObj) { + if (protoObj != null) { + this.id = protoObj.getId(); + this.raftAddress = protoObj.getRaftAddress(); + this.storeAddress = protoObj.getAddress(); + this.dataVersion = protoObj.getDataVersion(); + } else { + this.id = 0; + } + this.version = Version.getVersion(); + } + + + public Metapb.Store getProtoObj() { + Asserts.isNonNull(storeAddress); + Asserts.isNonNull(raftAddress); + Metapb.Store.Builder builder = Metapb.Store.newBuilder() + .setId(id).setVersion(version) + .setDataVersion(dataVersion) + .setAddress(storeAddress) + .setRaftAddress(raftAddress) + .setState(Metapb.StoreState.Up) + .setCores(cores) + .setDeployPath(deployPath) + .setDataPath(dataPath); + + if (labels != null) { + labels.forEach((k, v) -> { + builder.addLabels(Metapb.StoreLabel.newBuilder().setKey(k).setValue(v).build()); + }); + } + + return builder.build(); + + } + + public boolean checkState(Metapb.StoreState state) { + return this.state == state; + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/StoreMetadata.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/StoreMetadata.java new file mode 100644 index 000000000..367e2e388 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/StoreMetadata.java @@ -0,0 +1,230 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.meta; + +import java.io.File; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.store.meta.base.GlobalMetaStore; +import org.apache.hugegraph.store.options.MetadataOptions; +import org.apache.hugegraph.store.util.HgStoreException; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class StoreMetadata extends GlobalMetaStore { + protected final static int Store_DataFmt_Version = 1; + private final List dataLocations; + private final List raftLocations; + private Store store = null; + + public StoreMetadata(MetadataOptions options) { + super(options); + dataLocations = Arrays.asList(options.getDataPath().split(",")); + raftLocations = Arrays.asList(options.getRaftPath().split(",")); + } + + public List getDataLocations() { + return dataLocations; + } + + public List getRaftLocations() { + return raftLocations; + } + + public Store load() { + // 针对多目录存储的情况下,预先创建文件夹,方便pd端统计文件存储 + dataLocations.forEach(path -> { + String strPath = Paths.get(path).toAbsolutePath().toString(); + File dbFile = new File(strPath); + if (!dbFile.exists()) { + dbFile.mkdir(); + } + }); + + raftLocations.forEach(path -> { + String strPath = Paths.get(path).toAbsolutePath().toString(); + File dbFile = new File(strPath); + if (!dbFile.exists()) { + dbFile.mkdir(); + } + }); + + byte[] key = MetadataKeyHelper.getStoreKey(); + byte[] value = get(key); + if (value != null) { + try { + Metapb.Store protoObj = Metapb.Store.parseFrom(value); + if (protoObj != null) { + store = new Store(protoObj); + } + } catch (Exception e) { + throw new HgStoreException(HgStoreException.EC_FAIL, e); + } + } + if (store == null) { + store = new Store(Store_DataFmt_Version); + } + checkDataFmtCompatible(); + return store; + } + + public Store getStore() { + return store; + } + + public void save(Store store) { + byte[] key = MetadataKeyHelper.getStoreKey(); + put(key, store.getProtoObj().toByteArray()); + this.store = store; + } + + public void checkDataFmtCompatible() { + if (store == null || store.getDataVersion() != Store_DataFmt_Version) { + throw new HgStoreException(HgStoreException.EC_DATAFMT_NOT_SUPPORTED, + String.format( + "Incompatible data format, data format version is " + + "%d, supported version is %d", + store.getDataVersion(), Store_DataFmt_Version)); + } + } + + public Metapb.PartitionStore getPartitionStore(int partitionId) { + byte[] key = MetadataKeyHelper.getPartitionStoreKey(partitionId); + return get(Metapb.PartitionStore.parser(), key); + } + + public List getPartitionStores() { + byte[] key = MetadataKeyHelper.getPartitionStorePrefix(); + return scan(Metapb.PartitionStore.parser(), key); + } + + public void savePartitionStore(Metapb.PartitionStore partitionStore) { + byte[] key = MetadataKeyHelper.getPartitionStoreKey(partitionStore.getPartitionId()); + put(key, partitionStore.toByteArray()); + } + + public Metapb.PartitionRaft getPartitionRaft(int partitionId) { + byte[] key = MetadataKeyHelper.getPartitionRaftKey(partitionId); + return get(Metapb.PartitionRaft.parser(), key); + } + + public List getPartitionRafts() { + byte[] key = MetadataKeyHelper.getPartitionRaftPrefix(); + return scan(Metapb.PartitionRaft.parser(), key); + } + + public void savePartitionRaft(Metapb.PartitionRaft partitionRaft) { + byte[] key = MetadataKeyHelper.getPartitionRaftKey(partitionRaft.getPartitionId()); + put(key, partitionRaft.toByteArray()); + } + + private String getMinDataLocation() { + Map counter = new HashMap<>(); + dataLocations.forEach(l -> { + counter.put(l, Integer.valueOf(0)); + }); + getPartitionStores().forEach(ptStore -> { + if (counter.containsKey(ptStore.getStoreLocation())) { + counter.put(ptStore.getStoreLocation(), + counter.get(ptStore.getStoreLocation()) + 1); + } + }); + int min = Integer.MAX_VALUE; + String location = ""; + for (String k : counter.keySet()) { + if (counter.get(k) < min) { + min = counter.get(k); + location = k; + } + } + return location; + } + + private String getMinRaftLocation() { + Map counter = new HashMap<>(); + raftLocations.forEach(l -> { + counter.put(l, Integer.valueOf(0)); + }); + + getPartitionRafts().forEach(ptRaft -> { + if (counter.containsKey(ptRaft.getRaftLocation())) { + counter.put(ptRaft.getRaftLocation(), counter.get(ptRaft.getRaftLocation()) + 1); + } + }); + + int min = Integer.MAX_VALUE; + String location = ""; + for (String k : counter.keySet()) { + if (counter.get(k) < min) { + min = counter.get(k); + location = k; + } + } + return location; + } + + /** + * 获取分区数据存储的位置,如果分布数据不存在,自动创建新的位置 + * + * @param partitionId + * @return + */ + public String getPartitionStoreLocation(int partitionId, String dbName) { + Metapb.PartitionStore location = getPartitionStore(partitionId); + if (location == null) { + synchronized (this) { + location = getPartitionStore(partitionId); + if (location == null) { + // 查找分区数最少的存储 + location = Metapb.PartitionStore.newBuilder() + .setPartitionId(partitionId) + .setStoreLocation(getMinDataLocation()) + .build(); + // TODO 选择分区数最小的路径 + savePartitionStore(location); + } + } + } + return location.getStoreLocation(); + } + + public String getPartitionRaftLocation(int partitionId) { + Metapb.PartitionRaft location = getPartitionRaft(partitionId); + if (location == null) { + synchronized (this) { + location = getPartitionRaft(partitionId); + if (location == null) { + // 查找分区数最少的存储 + location = Metapb.PartitionRaft.newBuilder() + .setPartitionId(partitionId) + .setRaftLocation(getMinRaftLocation()) + .build(); + // TODO 选择分区数最小的路径 + savePartitionRaft(location); + } + } + } + return location.getRaftLocation(); + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/TaskManager.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/TaskManager.java new file mode 100644 index 000000000..d66673a4a --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/TaskManager.java @@ -0,0 +1,147 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.meta; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.hugegraph.pd.grpc.MetaTask; +import org.apache.hugegraph.store.meta.asynctask.AbstractAsyncTask; +import org.apache.hugegraph.store.meta.asynctask.AsyncTask; +import org.apache.hugegraph.store.meta.asynctask.AsyncTaskState; +import org.apache.hugegraph.store.meta.base.DBSessionBuilder; +import org.apache.hugegraph.store.meta.base.PartitionMetaStore; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class TaskManager extends PartitionMetaStore { + + public TaskManager(DBSessionBuilder sessionBuilder, int partId) { + super(sessionBuilder, partId); + } + + public void updateTask(int partId, String type, long taskId, byte[] task) { + byte[] key = MetadataKeyHelper.getTaskKey(partId, type, taskId); + put(key, task); + } + + public void updateTask(MetaTask.Task task) { + if (task.getState().compareTo(MetaTask.TaskState.Task_Stop) < 0) { + updateTask(task.getPartition().getId(), + task.getType().name(), task.getId(), task.toByteArray()); + } else { + deleteTask(task); + } + } + + public MetaTask.Task getOneTask(int partId, String type) { + byte[] key = MetadataKeyHelper.getTaskPrefix(partId, type); + List tasks = scan(MetaTask.Task.parser(), key); + if (tasks.size() > 0) { + return tasks.get(tasks.size() - 1); + } + return get(MetaTask.Task.parser(), key); + } + + public MetaTask.Task getOneTask(int partId, MetaTask.TaskType taskType) { + return getOneTask(partId, taskType.name()); + } + + public MetaTask.Task getOneTask(MetaTask.Task task) { + return getOneTask(task.getPartition().getId(), task.getType()); + } + + public void deleteTask(MetaTask.Task task) { + byte[] key = MetadataKeyHelper.getTaskKey(task.getPartition().getId(), + task.getType().name(), task.getId()); + delete(key); + } + + public void deleteTask(int partId, String type) { + deletePrefix(MetadataKeyHelper.getTaskPrefix(partId, type)); + } + + public boolean taskExists(int partId, String graphName, String taskTypeName) { + return partitionTaskRepeat(partId, graphName, taskTypeName, 0); + } + + public boolean taskExists(MetaTask.Task task) { + return null != getOneTask(task); + } + + /* + * 判断相同分区下相同任务是否重复 + * partId 分区id + * TaskTypeName 任务类型名称 + * graphName + */ + public boolean partitionTaskRepeat(int partId, String graphName, String taskTypeName) { + return partitionTaskRepeat(partId, graphName, taskTypeName, 1); + } + + private boolean partitionTaskRepeat(int partId, String graphName, String taskTypeName, + int checkCount) { + byte[] key = MetadataKeyHelper.getTaskPrefix(partId, taskTypeName); + List tasks = scan(MetaTask.Task.parser(), key); + if (tasks.size() > 1) { + int graphCount = 0; + for (MetaTask.Task task : tasks) { + if (task.getPartition().getGraphName().equals(graphName) && + task.getState().getNumber() < MetaTask.TaskState.Task_Stop_VALUE) { + graphCount++; + } + } + + return graphCount > checkCount; + } + return false; + } + + public void putAsyncTask(AsyncTask task) { + put(MetadataKeyHelper.getAsyncTaskKey(task.getPartitionId(), + task.getGraphName(), task.getId()), task.toBytes()); + } + + public AsyncTask getOneAsyncTask(int partId, String graphName, String taskId) { + var bytes = get(MetadataKeyHelper.getAsyncTaskKey(partId, graphName, taskId)); + if (bytes != null) { + return AbstractAsyncTask.fromBytes(bytes); + } + return null; + } + + public void updateAsyncTaskState(int partId, String graphName, String taskId, + AsyncTaskState state) { + var task = getOneAsyncTask(partId, graphName, taskId); + if (task != null) { + task.setState(state); + putAsyncTask(task); + } + } + + public List scanAsyncTasks(int partitionId, String graphName) { + var list = new ArrayList(); + for (var task : scan(MetadataKeyHelper.getAsyncTaskPrefix(partitionId, graphName))) { + list.add(AbstractAsyncTask.fromBytes(task.value)); + } + return list; + } + + +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/asynctask/AbstractAsyncTask.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/asynctask/AbstractAsyncTask.java new file mode 100644 index 000000000..233702c6d --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/asynctask/AbstractAsyncTask.java @@ -0,0 +1,145 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.meta.asynctask; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.UUID; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public abstract class AbstractAsyncTask implements AsyncTask, Serializable { + private final String id; + private final int partitionId; + + private final String graphName; + private final String type; + /** + * 任务额外需要的参数 + */ + private final Object extra; + private AsyncTaskState state; + + public AbstractAsyncTask(int partitionId, String graphName, AsyncTaskState state, + Object extra) { + this.id = getNextId(); + this.partitionId = partitionId; + this.graphName = graphName; + this.state = state; + this.type = getType(); + this.extra = extra; + } + + private static String getNextId() { + return UUID.randomUUID().toString().replace("-", ""); + } + + public static AsyncTask fromBytes(byte[] bytes) { + AsyncTask obj = null; + try { + ByteArrayInputStream bis = new ByteArrayInputStream(bytes); + ObjectInputStream ois = new ObjectInputStream(bis); + obj = (AsyncTask) ois.readObject(); + ois.close(); + bis.close(); + } catch (IOException e) { + log.error("AsyncTask deserialized failed,{}", e.getMessage()); + } catch (ClassNotFoundException e) { + log.error("AsyncTask deserialized failed,{}", e.getMessage()); + } + return obj; + } + + @Override + public String getId() { + return this.id; + } + + @Override + public int getPartitionId() { + return this.partitionId; + } + + public AsyncTaskState getState() { + return this.state; + } + + @Override + public void setState(AsyncTaskState newState) { + this.state = newState; + } + + @Override + public String getGraphName() { + return this.graphName; + } + + public Object getExtra() { + return this.extra; + } + + public abstract String getType(); + + @Override + public byte[] toBytes() { + byte[] bytes = null; + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + try { + ObjectOutputStream oos = new ObjectOutputStream(bos); + oos.writeObject(this); + oos.flush(); + bytes = bos.toByteArray(); + oos.close(); + bos.close(); + } catch (IOException e) { + log.error("AsyncTask serialized failed, {}", e.getMessage()); + e.printStackTrace(); + } + return bytes; + } + + @Override + public void handleTask() { + if (this.getState() == AsyncTaskState.FAILED) { + onError(); + } else if (this.getState() == AsyncTaskState.START) { + onNotFinished(); + } + } + + protected abstract void onError(); + + protected abstract void onNotFinished(); + + @Override + public String toString() { + return "AbstractAsyncTask{" + + "id='" + id + '\'' + + ", partitionId=" + partitionId + + ", graphName='" + graphName + '\'' + + ", state=" + state + + ", type='" + type + '\'' + + ", extra=" + getExtra() + + '}'; + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/asynctask/AsyncTask.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/asynctask/AsyncTask.java new file mode 100644 index 000000000..d758899c9 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/asynctask/AsyncTask.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.meta.asynctask; + +public interface AsyncTask { + + /** + * 需要检查异步任务时候,检查当前的状态,根据状态去做对应的处理 + */ + void handleTask(); + + /** + * 任务ID + */ + String getId(); + + /** + * 针对哪个图的 + */ + String getGraphName(); + + /** + * 针对哪个分区的 + */ + int getPartitionId(); + + /** + * 用来进行序列化 + * + * @return + */ + byte[] toBytes(); + + /** + * 设置执行状态 + * + * @param newState + */ + void setState(AsyncTaskState newState); +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/asynctask/AsyncTaskState.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/asynctask/AsyncTaskState.java new file mode 100644 index 000000000..c89497487 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/asynctask/AsyncTaskState.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.meta.asynctask; + +public enum AsyncTaskState { + START, + SUCCESS, + FAILED +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/asynctask/CleanTask.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/asynctask/CleanTask.java new file mode 100644 index 000000000..a2749bf57 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/asynctask/CleanTask.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.meta.asynctask; + +import org.apache.hugegraph.pd.grpc.pulse.CleanType; +import org.apache.hugegraph.store.HgStoreEngine; +import org.apache.hugegraph.store.cmd.CleanDataRequest; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class CleanTask extends AbstractAsyncTask { + + public CleanTask(int partitionId, String graphName, AsyncTaskState state, Object attach) { + super(partitionId, graphName, state, attach); + } + + @Override + public String getType() { + return "CLEAN_TYPE"; + } + + @Override + protected void onError() { + cleanTask(); + } + + @Override + protected void onNotFinished() { + cleanTask(); + } + + private void cleanTask() { + log.info("CleanTask begin to run:{}", this); + var storeEngine = HgStoreEngine.getInstance(); + if (storeEngine != null) { + if (getExtra() != null) { + CleanDataRequest request = (CleanDataRequest) getExtra(); + var partition = storeEngine.getPartitionManager() + .getPartition(getGraphName(), getPartitionId()); + // 只允许清理本分区之外的数据。 缩容等任务会造成干扰, 而且不能删除分区 + if (request.getKeyEnd() == partition.getStartKey() && + request.getKeyEnd() == partition.getEndKey() && + request.getCleanType() == CleanType.CLEAN_TYPE_EXCLUDE_RANGE && + !request.isDeletePartition()) { + storeEngine.getBusinessHandler() + .cleanPartition(getGraphName(), getPartitionId(), + request.getKeyStart(), request.getKeyEnd(), + request.getCleanType()); + } + } else { + storeEngine.getBusinessHandler().cleanPartition(getGraphName(), getPartitionId()); + } + + storeEngine.getPartitionEngine(getPartitionId()).getTaskManager() + .updateAsyncTaskState(getPartitionId(), getGraphName(), getId(), + AsyncTaskState.SUCCESS); + } + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/base/DBSessionBuilder.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/base/DBSessionBuilder.java new file mode 100644 index 000000000..fed6b1f61 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/base/DBSessionBuilder.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.meta.base; + +import org.apache.hugegraph.rocksdb.access.RocksDBSession; +import org.apache.hugegraph.store.util.HgStoreException; + +public interface DBSessionBuilder { + RocksDBSession getSession(int partId) throws HgStoreException; +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/base/GlobalMetaStore.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/base/GlobalMetaStore.java new file mode 100644 index 000000000..230f26c8a --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/base/GlobalMetaStore.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.meta.base; + +import java.util.Arrays; + +import org.apache.hugegraph.rocksdb.access.RocksDBFactory; +import org.apache.hugegraph.rocksdb.access.RocksDBSession; +import org.apache.hugegraph.store.options.MetadataOptions; + +public class GlobalMetaStore extends MetaStoreBase { + public static final String HSTORE_METADATA_GRAPH_NAME = "hgstore-metadata"; + public static final String HSTORE_CF_NAME = "default"; + + private final MetadataOptions options; + + private final String dataPath; + + public GlobalMetaStore(MetadataOptions options) { + this.options = options; + dataPath = Arrays.asList(options.getDataPath().split(",")).get(0); + } + + public MetadataOptions getOptions() { + return options; + } + + @Override + protected RocksDBSession getRocksDBSession() { + RocksDBFactory rocksDBFactory = RocksDBFactory.getInstance(); + RocksDBSession dbSession = rocksDBFactory.queryGraphDB(HSTORE_METADATA_GRAPH_NAME); + if (dbSession == null) { + dbSession = rocksDBFactory.createGraphDB(dataPath, HSTORE_METADATA_GRAPH_NAME); + } + return dbSession; + } + + @Override + protected String getCFName() { + return HSTORE_CF_NAME; + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/base/MetaStoreBase.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/base/MetaStoreBase.java new file mode 100644 index 000000000..4cec12eb8 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/base/MetaStoreBase.java @@ -0,0 +1,173 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.meta.base; + +import java.io.Closeable; +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; + +import org.apache.hugegraph.rocksdb.access.RocksDBSession; +import org.apache.hugegraph.rocksdb.access.ScanIterator; +import org.apache.hugegraph.rocksdb.access.SessionOperator; +import org.apache.hugegraph.store.util.Asserts; +import org.apache.hugegraph.store.util.HgStoreException; + +import com.google.protobuf.GeneratedMessageV3; +import com.google.protobuf.InvalidProtocolBufferException; + +/** + * Store、Partition等元数据存储到hgstore-metadata图下 + */ +public abstract class MetaStoreBase implements Closeable { + protected abstract RocksDBSession getRocksDBSession(); + + protected abstract String getCFName(); + + @Override + public void close() throws IOException { + } + + public void put(byte[] key, byte[] value) { + try (RocksDBSession dbSession = getRocksDBSession()) { + Asserts.isTrue(dbSession != null, "DB session is null."); + SessionOperator op = dbSession.sessionOp(); + try { + op.prepare(); + op.put(getCFName(), key, value); + op.commit(); + } catch (Exception e) { + op.rollback(); + throw e; + } + } + } + + public void put(byte[] key, GeneratedMessageV3 value) { + put(key, value.toByteArray()); + } + + public byte[] get(byte[] key) { + try (RocksDBSession dbSession = getRocksDBSession()) { + SessionOperator op = dbSession.sessionOp(); + return op.get(getCFName(), key); + } + } + + public E get(com.google.protobuf.Parser parser, byte[] key) { + byte[] value = get(key); + try { + if (value != null) { + return parser.parseFrom(value); + } + } catch (Exception e) { + throw new HgStoreException(HgStoreException.EC_FAIL, e); + } + return null; + } + + public List scan(byte[] prefix) { + List values = new LinkedList<>(); + try (RocksDBSession dbSession = getRocksDBSession()) { + SessionOperator op = dbSession.sessionOp(); + ScanIterator iterator = op.scan(getCFName(), prefix); + while (iterator.hasNext()) { + values.add(iterator.next()); + } + } + return values; + } + + public List scan(com.google.protobuf.Parser parser, byte[] prefix) { + try (RocksDBSession dbSession = getRocksDBSession()) { + SessionOperator op = dbSession.sessionOp(); + ScanIterator iterator = op.scan(getCFName(), prefix); + List values = new LinkedList<>(); + try { + while (iterator.hasNext()) { + RocksDBSession.BackendColumn col = iterator.next(); + values.add(parser.parseFrom(col.value)); + } + } catch (InvalidProtocolBufferException e) { + throw new HgStoreException(HgStoreException.EC_FAIL, e); + } + return values; + } + } + + public List scan(byte[] start, byte[] end) { + List values = new LinkedList<>(); + try (RocksDBSession dbSession = getRocksDBSession()) { + SessionOperator op = dbSession.sessionOp(); + ScanIterator iterator = op.scan(getCFName(), start, end, + ScanIterator.Trait.SCAN_GTE_BEGIN | + ScanIterator.Trait.SCAN_LT_END); + while (iterator.hasNext()) { + values.add(iterator.next()); + } + } + return values; + } + + public List scan(com.google.protobuf.Parser parser, byte[] start, byte[] end) { + try (RocksDBSession dbSession = getRocksDBSession()) { + SessionOperator op = dbSession.sessionOp(); + ScanIterator iterator = op.scan(getCFName(), start, end, + ScanIterator.Trait.SCAN_GTE_BEGIN | + ScanIterator.Trait.SCAN_LT_END); + List values = new LinkedList<>(); + try { + while (iterator.hasNext()) { + RocksDBSession.BackendColumn col = iterator.next(); + values.add(parser.parseFrom(col.value)); + } + } catch (InvalidProtocolBufferException e) { + throw new HgStoreException(HgStoreException.EC_FAIL, e); + } + return values; + } + } + + public void delete(byte[] key) { + try (RocksDBSession dbSession = getRocksDBSession()) { + SessionOperator op = dbSession.sessionOp(); + try { + op.prepare(); + op.delete(getCFName(), key); + op.commit(); + } catch (Exception e) { + op.rollback(); + throw e; + } + } + } + + public void deletePrefix(byte[] key) { + try (RocksDBSession dbSession = getRocksDBSession()) { + SessionOperator op = dbSession.sessionOp(); + try { + op.prepare(); + op.deletePrefix(getCFName(), key); + op.commit(); + } catch (Exception e) { + op.rollback(); + throw e; + } + } + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/base/PartitionMetaStore.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/base/PartitionMetaStore.java new file mode 100644 index 000000000..8996bd1b0 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/meta/base/PartitionMetaStore.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.meta.base; + +import org.apache.hugegraph.rocksdb.access.RocksDBSession; + +/** + * 元数据存储在分区的default cf中 + */ +public class PartitionMetaStore extends MetaStoreBase { + public static final String DEFAULT_CF_NAME = "default"; + + private final DBSessionBuilder sessionBuilder; + private final Integer partitionId; + + public PartitionMetaStore(DBSessionBuilder sessionBuilder, int partId) { + this.sessionBuilder = sessionBuilder; + this.partitionId = partId; + } + + @Override + protected RocksDBSession getRocksDBSession() { + return sessionBuilder.getSession(this.partitionId); + } + + @Override + protected String getCFName() { + return DEFAULT_CF_NAME; + } + + protected void flush() { + try (RocksDBSession dbSession = getRocksDBSession()) { + dbSession.flush(true); + } + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/metric/HgMetricService.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/metric/HgMetricService.java new file mode 100644 index 000000000..fb5f59476 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/metric/HgMetricService.java @@ -0,0 +1,198 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.metric; + +import java.io.File; +import java.nio.file.FileStore; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.time.Instant; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.commons.io.FileUtils; +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.store.HgStoreEngine; +import org.apache.hugegraph.store.PartitionEngine; +import org.apache.hugegraph.store.util.Lifecycle; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class HgMetricService implements Lifecycle { + private final static HgMetricService instance = new HgMetricService(); + private final static AtomicLong bytesWritten = new AtomicLong(); + private final static AtomicLong bytesRead = new AtomicLong(); + private final static AtomicLong keysWritten = new AtomicLong(); + private final static AtomicLong keysRead = new AtomicLong(); + private final static long startTime = Instant.now().getEpochSecond(); + private static long lastQueryTime = 0; + private final SystemMetricService systemMetricService = new SystemMetricService(); + private HgStoreEngine storeEngine; + private Map systemMetrics = new HashMap<>(); + private HgMetricService() { + } + + public static HgMetricService getInstance() { + return instance; + } + + @Override + public boolean init(final Void v) { + resetMetrics(); + return true; + } + + @Override + public void shutdown() { + + } + + public HgMetricService setHgStoreEngine(HgStoreEngine storeEngine) { + this.storeEngine = storeEngine; + this.systemMetricService.setStoreEngine(storeEngine); + return this; + } + + public Metapb.StoreStats.Builder getMetrics() { + Metapb.StoreStats.Builder builder = Metapb.StoreStats.newBuilder(); + try { + getStoreMetrics(builder); + getRaftMetrics(builder); + getDiskMetrics(builder); + getSystemMetrics(builder); + } catch (Exception e) { + log.error("HgMetricService getMetrics {}", e); + } + return builder; + } + + private Metapb.StoreStats.Builder getDiskMetrics(Metapb.StoreStats.Builder builder) { + try { + long capacity = 0L; + long available = 0L; + long used = 0L; + HashSet fileStoreSet = new HashSet<>(); + for (String dbPath : this.storeEngine.getDataLocations()) { + FileStore fs = Files.getFileStore(Paths.get(dbPath)); + if (fileStoreSet.contains(fs.name())) { + continue; + } + fileStoreSet.add(fs.name()); + capacity += fs.getTotalSpace(); + available += fs.getUsableSpace(); + used += FileUtils.sizeOfDirectory(new File(dbPath)); + } + builder.setCapacity(capacity); + builder.setAvailable(available); + builder.setUsedSize(used); + } catch (Exception e) { + log.error("Failed to get disk metrics. {}", e.toString()); + } + return builder; + } + + private Metapb.StoreStats.Builder getRaftMetrics(Metapb.StoreStats.Builder builder) { + Map partitionEngines = this.storeEngine.getPartitionEngines(); + builder.setPartitionCount(partitionEngines.size()); + partitionEngines.forEach((partId, engine) -> { + builder.addRaftStats(Metapb.RaftStats.newBuilder() + .setPartitionId(partId) + .setCommittedIndex(engine.getCommittedIndex()) + .build()); + }); + return builder; + } + + private Metapb.StoreStats.Builder getStoreMetrics(Metapb.StoreStats.Builder builder) { + builder.setStoreId(this.storeEngine.getHeartbeatService().getStoreInfo().getId()); + + builder.setStartTime((int) startTime); + this.storeEngine.getPartitionManager().getPartitions().forEach((graphName, partitions) -> { + partitions.forEach((partId, partition) -> { + HgStoreMetric.Graph graphMetric = + this.storeEngine.getBusinessHandler().getGraphMetric(graphName, partId); + if ((graphMetric != null) && + (storeEngine.getPartitionManager().getLocalRoleFromShard(partition) != null)) { + builder.addGraphStats(Metapb.GraphStats.newBuilder() + .setGraphName(graphName) + .setPartitionId(partId) + .setApproximateKeys( + graphMetric.getApproxKeyCount()) + .setApproximateSize( + graphMetric.getApproxDataSize()) + .setRole( + storeEngine.getPartitionManager() + .getLocalRoleFromShard( + partition) + .toShardRole()) + .setWorkState(partition.getWorkState()) + + .build()); + } + + }); + }); + + return builder; + } + + /** + * get system metrics each 1 minute + * + * @param builder + * @return + */ + private Metapb.StoreStats.Builder getSystemMetrics(Metapb.StoreStats.Builder builder) { + // load each 1 minute + if (systemMetrics.isEmpty() || System.currentTimeMillis() - lastQueryTime >= 60000) { + systemMetrics = systemMetricService.getSystemMetrics(); + lastQueryTime = System.currentTimeMillis(); + } + + for (Map.Entry entry : systemMetrics.entrySet()) { + if (entry.getValue() != null) { + builder.addSystemMetrics(Metapb.RecordPair.newBuilder() + .setKey(entry.getKey()) + .setValue(entry.getValue()) + .build()); + } + } + + return builder; + } + + private void resetMetrics() { + bytesWritten.set(0); + bytesRead.set(0); + keysWritten.set(0); + keysRead.set(0); + } + + public void increaseWriteCount(long keys, long bytes) { + keysWritten.addAndGet(keys); + bytesWritten.addAndGet(bytes); + } + + public void increaseReadCount(long keys, long bytes) { + keysRead.addAndGet(keys); + bytesRead.addAndGet(bytes); + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/metric/HgStoreMetric.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/metric/HgStoreMetric.java new file mode 100644 index 000000000..9115400ac --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/metric/HgStoreMetric.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.metric; + +import java.util.List; + +import lombok.Data; + +public class HgStoreMetric { + + @Data + public static class Table { + private String tableName; + private long keyCount; + private String dataSize; + } + + @Data + public static class Partition { + private int partitionId; + private List tables; + } + + @Data + public static class Graph { + private long approxDataSize; + private long approxKeyCount; + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/metric/SystemMetricService.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/metric/SystemMetricService.java new file mode 100644 index 000000000..48967cb18 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/metric/SystemMetricService.java @@ -0,0 +1,448 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.metric; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryUsage; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Deque; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import org.apache.hugegraph.rocksdb.access.RocksDBFactory; +import org.apache.hugegraph.rocksdb.access.RocksDBSession; +import org.apache.hugegraph.store.HgStoreEngine; +import org.rocksdb.MemoryUsageType; +import org.rocksdb.Statistics; +import org.rocksdb.TickerType; + +import com.sun.management.OperatingSystemMXBean; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class SystemMetricService { + + private static final long MIB = 1024 * 1024; + private final Deque>> deque = new LinkedList<>(); + HgStoreEngine storeEngine; + + public void setStoreEngine(HgStoreEngine hgStoreEngine) { + this.storeEngine = hgStoreEngine; + } + + public HgStoreEngine getStorageEngine() { + return this.storeEngine; + } + + public Map getSystemMetrics() { + Map systemMetrics = new HashMap<>(); + try { + // cpu + loadCpuInfo(systemMetrics); + + // memory + loadMemInfo(systemMetrics); + + // disk + loadDiskInfo(systemMetrics); + + // disk io + loadDiskIo(systemMetrics); + + // network + loadNetFlowInfo(systemMetrics); + + // rocksdb + loadRocksDbInfo(systemMetrics); + } catch (Exception e) { + log.error("get system metric failed, {}", e.toString()); + } + + return systemMetrics; + } + + private void loadCpuInfo(Map map) { + OperatingSystemMXBean osBean = + (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean(); + Double cpuLoad = osBean.getSystemLoadAverage(); + map.put("cpu.load", cpuLoad.longValue()); + } + + private void loadMemInfo(Map map) { + OperatingSystemMXBean osBean = + (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean(); + map.put("mem.physical_total", osBean.getTotalPhysicalMemorySize()); + map.put("mem.physical_free", osBean.getFreePhysicalMemorySize()); + map.put("mem.swap_total", osBean.getTotalSwapSpaceSize()); + map.put("mem.swap_free", osBean.getFreeSwapSpaceSize()); + + Runtime runtime = Runtime.getRuntime(); + map.put("mem.heap_total", runtime.totalMemory()); + map.put("mem.heap_used", runtime.totalMemory() - runtime.freeMemory()); + + MemoryUsage memoryUsage = ManagementFactory.getMemoryMXBean().getNonHeapMemoryUsage(); + map.put("mem.non_heap_total", memoryUsage.getCommitted()); + map.put("mem.non_heap_used", memoryUsage.getUsed()); + } + + private void loadDiskInfo(Map map) { + // sum up all disk space + File[] rootDrive = File.listRoots(); + long total = 0; + long free = 0; + long usable = 0; + if (rootDrive != null) { + for (File d : rootDrive) { + total += d.getTotalSpace(); + free += d.getFreeSpace(); + usable += d.getUsableSpace(); + } + } + map.put("disk.total_size", total / MIB); + map.put("disk.free_size", free / MIB); + map.put("disk.usable_size", usable / MIB); + } + + private void loadDiskIo(Map map) { + for (Map.Entry entry : getDiskIoData().entrySet()) { + map.put(entry.getKey(), entry.getValue().longValue()); + } + } + + private void loadNetFlowInfo(Map map) { + for (Map.Entry> entry : getTraffic().entrySet()) { + // exclude none-functional network interface + map.put("network." + entry.getKey() + ".sent_bytes", + entry.getValue().get(0) / 1024 / 1024); + map.put("network." + entry.getKey() + ".recv_bytes", + entry.getValue().get(1) / 1024 / 1024); + map.put("network." + entry.getKey() + ".sent_rates", + entry.getValue().get(2) / 1024 / 1024); + map.put("network." + entry.getKey() + ".recv_rates", + entry.getValue().get(3) / 1024 / 1024); + } + } + + private void loadRocksDbInfo(Map map) { + Map dbMem = + storeEngine.getBusinessHandler().getApproximateMemoryUsageByType(null); + map.put("rocksdb.table.reader.total", dbMem.get(MemoryUsageType.kTableReadersTotal)); + map.put("rocksdb.mem.table.total", dbMem.get(MemoryUsageType.kMemTableTotal)); + map.put("rocksdb.cache.total", dbMem.get(MemoryUsageType.kCacheTotal)); + map.put("rocksdb.mem.table.un_flushed", dbMem.get(MemoryUsageType.kMemTableUnFlushed)); + + RocksDBFactory dbFactory = RocksDBFactory.getInstance(); + Set names = dbFactory.getGraphNames(); + if (names != null) { + for (String name : names) { + try { + RocksDBSession session = dbFactory.queryGraphDB(name); + Statistics statistics = session.getRocksDbStats(); + map.put( + "rocksdb.graph." + name + "." + + TickerType.NUMBER_KEYS_WRITTEN.name().toLowerCase(), + statistics.getTickerCount(TickerType.NUMBER_KEYS_WRITTEN)); + map.put( + "rocksdb.graph." + name + "." + + TickerType.NUMBER_KEYS_READ.name().toLowerCase(), + statistics.getTickerCount(TickerType.NUMBER_KEYS_READ)); + map.put( + "rocksdb.graph." + name + "." + + TickerType.NUMBER_KEYS_UPDATED.name().toLowerCase(), + statistics.getTickerCount(TickerType.NUMBER_KEYS_UPDATED)); + map.put( + "rocksdb.graph." + name + "." + + TickerType.BYTES_WRITTEN.name().toLowerCase(), + statistics.getTickerCount(TickerType.BYTES_WRITTEN)); + map.put( + "rocksdb.graph." + name + "." + + TickerType.BYTES_READ.name().toLowerCase(), + statistics.getTickerCount(TickerType.BYTES_READ)); + } catch (Exception e) { + + } + } + } + } + + /** + * get all network interface traffic(delta from last invoke). + * -sent bytes + * -receive bytes + * -in rates + * -out rates + * + * @return + */ + private Map> getTraffic() { + deque.add(loadTrafficData()); + + if (deque.size() < 2) { + return new HashMap<>(); + } + // keep 2 copies + while (deque.size() > 2) { + deque.removeFirst(); + } + + // compare + Map> result = new HashMap<>(); + Map> currentFlows = deque.getLast(); + Map> preFlows = deque.getFirst(); + + for (Map.Entry> entry : currentFlows.entrySet()) { + if (preFlows.containsKey(entry.getKey())) { + List prev = preFlows.get(entry.getKey()); + List now = preFlows.get(entry.getKey()); + // no traffic + if (now.get(0) == 0) { + continue; + } + long diff = now.get(2) - prev.get(2); + diff = diff > 0 ? diff : 1L; + result.put( + entry.getKey(), + Arrays.asList( + now.get(0) - prev.get(0), + now.get(1) - prev.get(1), + // rate rate + (now.get(0) - prev.get(0)) / diff, + // recv rate + (now.get(1) - prev.get(1)) / diff)); + } + } + return result; + } + + /** + * load traffic according to os, now only support mac os and linux + * + * @return + */ + private Map> loadTrafficData() { + String osName = System.getProperty("os.name").toLowerCase(); + if (osName.startsWith("linux")) { + return loadLinuxTrafficData(); + } else if (osName.startsWith("mac")) { + return loadMacOsTrafficData(); + } + return new HashMap<>(); + } + + /** + * read the result of "netstat -ib". (lo is ignored) + * + * @return + */ + private Map> loadMacOsTrafficData() { + Map> flows = new HashMap<>(); + Long current = System.currentTimeMillis() / 1000; + for (String line : executeCmd("netstat -ib")) { + if (line.startsWith("Name") || line.startsWith("lo")) { + // first table header line + continue; + } + + List arr = Arrays.stream(line.split(" ")).filter(x -> x.length() > 0) + .collect(Collectors.toList()); + + long sentBytes = Long.parseLong(arr.get(arr.size() - 2)); + long recvBytes = Long.parseLong(arr.get(arr.size() - 5)); + String name = arr.get(0); + // log.debug("mac: {}, -> {},{},{}", line, sentBytes, recvBytes, name); + if (sentBytes > 0 && recvBytes > 0) { + flows.put(name, Arrays.asList(sentBytes, recvBytes, current)); + } + } + + return flows; + } + + /** + * read the statistics file for network interface + * cat /sys/class/net/NETWORK_INTERFACE_NAME/statistics/tx_bytes + * cat /sys/class/net/NETWORK_INTERFACE_NAME/statistics/rx_bytes + * + * @return + */ + private Map> loadLinuxTrafficData() { + Long current = System.currentTimeMillis() / 1000; + Map> flows = new HashMap<>(); + try { + for (String name : getAllNetworkInterfaces()) { + long sentBytes = getUnsignedLongFromFile( + String.format("/sys/class/net/%s/statistics/tx_bytes", name)); + long recvBytes = getUnsignedLongFromFile( + String.format("/sys/class/net/%s/statistics/rx_bytes", name)); + flows.put(name, Arrays.asList(sentBytes, recvBytes, current)); + } + } catch (Exception e) { + + } + return flows; + } + + /** + * read file and parse to long + * + * @param filename + * @return + */ + private long getUnsignedLongFromFile(String filename) throws IOException { + List lines = Files.readAllLines(Paths.get(filename), StandardCharsets.UTF_8); + if (!lines.isEmpty()) { + return Long.parseLong(lines.get(0)); + } + return 0L; + } + + /** + * get all network interface names. (lo is ignored) + * + * @return + * @throws SocketException + */ + private List getAllNetworkInterfaces() throws SocketException { + Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); + List names = new ArrayList<>(); + while (interfaces.hasMoreElements()) { + String name = interfaces.nextElement().getName(); + if (!"lo".equals(name)) { + names.add(name); + } + } + return names; + } + + private Map getDiskIoData() { + String osName = System.getProperty("os.name").toLowerCase(); + if (osName.startsWith("linux")) { + return loadLinuxDiskIoData(); + } else if (osName.startsWith("mac")) { + return loadMacDiskIoData(); + } + return new HashMap<>(); + } + + /** + * get io data using iostat -d -x -k + * + * @return + */ + private Map loadLinuxDiskIoData() { + Map result = new HashMap<>(); + boolean contentFlag = false; + for (String line : executeCmd("iostat -d -x -k")) { + // header + if (line.startsWith("Device")) { + contentFlag = true; + continue; + } + + if (contentFlag) { + List arr = + Arrays.stream(line.split(" ")).filter(x -> x.length() > 0) + .collect(Collectors.toList()); + try { + // util% + result.put("disk.io." + arr.get(0) + ".util", + Float.valueOf(arr.get(arr.size() - 1)) * 100); + // wait + result.put("disk.io." + arr.get(0) + ".wait", + Float.valueOf(arr.get(arr.size() - 5)) * 100); + } catch (Exception e) { + log.debug("error get disk io data {}", line); + } + } + } + return result; + } + + /** + * get io data using iostat + * + * @return + */ + private Map loadMacDiskIoData() { + + Map result = new HashMap<>(); + List lines = executeCmd("iostat -oK"); + // disks + List disks = + Arrays.stream(lines.get(0).split(" ")) + .filter(x -> x.length() > 0 && x.startsWith("disk")) + .collect(Collectors.toList()); + // datas + List data = + Arrays.stream(lines.get(2).split(" ")).filter(x -> x.length() > 0) + .collect(Collectors.toList()); + // zip data + for (int i = 0; i < disks.size(); i++) { + try { + // msps + result.put("disk.io." + disks.get(i) + ".wait", + Float.valueOf(data.get(i * 3 + 2)) * 100); + // no such value + result.put("disk.io." + disks.get(i) + ".util", 0.0F); + } catch (Exception e) { + log.debug("error get io data {}", data.get(i)); + } + } + return result; + } + + /** + * execute cmd and get the output + * + * @param cmd + * @return + */ + private List executeCmd(String cmd) { + List result = new ArrayList<>(); + try { + Process pr = Runtime.getRuntime().exec(cmd); + BufferedReader in = new BufferedReader(new InputStreamReader(pr.getInputStream())); + String line; + while ((line = in.readLine()) != null) { + if (line.length() > 0) { + result.add(line); + } + } + pr.waitFor(); + in.close(); + } catch (IOException | InterruptedException e) { + } + return result; + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/options/HgStoreEngineOptions.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/options/HgStoreEngineOptions.java new file mode 100644 index 000000000..2a51b342b --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/options/HgStoreEngineOptions.java @@ -0,0 +1,146 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.options; + +import java.util.Map; + +import org.apache.hugegraph.store.business.DataMover; +import org.apache.hugegraph.store.pd.PdProvider; +import org.apache.hugegraph.store.raft.RaftTaskHandler; + +import com.alipay.sofa.jraft.util.Utils; + +import lombok.Data; + +/** + * Storage engine configuration + */ +@Data +public class HgStoreEngineOptions { + public static String Raft_Path_Prefix = "raft"; + public static String DB_Path_Prefix = "db"; + public static String Snapshot_Path_Prefix = "snapshot"; + // store心跳间隔,单位秒 + private final int storeHBInterval = 30; + // 分区心跳间隔,单位秒 + private final int partitionHBInterval = 5; + // 等待leader超时时间,单位秒 + private final int waitLeaderTimeout = 30; + private final int raftRpcThreadPoolSize = Utils.cpus() * 6; + // 没有PD模式,用于开发调试使用 + private boolean fakePD = false; + // fakePd配置项 + private FakePdOptions fakePdOptions = new FakePdOptions(); + private RaftOptions raftOptions = new RaftOptions(); + // pd 服务器地址 + private String pdAddress; + // 对外服务地址 + private String grpcAddress; + // Raft 对外服务地址 + private String raftAddress; + // 存储路径,支持多个位置,逗号分割 + private String dataPath; + private String raftPath; + private Map rocksdbConfig; + // 自定义的标签,传给pd + private Map labels; + // Raft任务处理器 + private RaftTaskHandler taskHandler; + + private PdProvider pdProvider; + + // 数据迁移服务 + private DataMover dataTransfer; + + @Data + public static class FakePdOptions { + private int partitionCount = 0; + private int shardCount = 0; + private String storeList; + private String peersList; + } + + @Data + public static class RaftOptions { + + /* + * Rpc connect timeout in milliseconds + * The time should be less than electionTimeoutMs, otherwise the election will timeout + */ + private final int rpcConnectTimeoutMs = 1000; + private final int electionTimeoutMs = 3000; + + // A follower would become a candidate if it doesn't receive any message + // from the leader in |election_timeout_ms| milliseconds + /** + * Install snapshot RPC request default timeout in milliseconds + */ + private final int rpcInstallSnapshotTimeout = 60 * 60 * 1000; + // 等待leader超时时间,单位秒 + private final int waitLeaderTimeout = 30; + /** + * The maximum number of entries in AppendEntriesRequest + */ + private final int maxEntriesSize = 256; + /** + * Raft集群发生数据积压后,限速等待时间 单位毫秒 + **/ + private final int overloadRateLimit = 100; + private final int keepInMemorySegmentCount = 2; + private final int preAllocateSegmentCount = 1; + private final int splitPartitionLogIndexMargin = 10; + /** + * RPC request default timeout in milliseconds + */ + private int rpcDefaultTimeout = 5000; + // A snapshot saving would be triggered every |snapshot_interval_s| seconds + // if this was reset as a positive number + // If |snapshot_interval_s| <= 0, the time based snapshot would be disabled. + // + // Default: 3600 (1 hour) + private int snapshotIntervalSecs = 3600; + // A snapshot saving would be triggered every |snapshot_interval_s| seconds, + // and at this moment when state machine's lastAppliedIndex value + // minus lastSnapshotId value is greater than snapshotLogIndexMargin value, + // the snapshot action will be done really. + // If |snapshotLogIndexMargin| <= 0, the distance based snapshot would be disable. + // + // Default: 0 + private int snapshotLogIndexMargin = 1024; + private boolean metrics = true; + /** + * Internal disruptor buffers size for Node/FSMCaller/LogManager etc. + */ + private int disruptorBufferSize = 4096; + /** + * The maximum replicator pipeline in-flight requests/responses, only valid when enable + * replicator pipeline. + */ + private int maxReplicatorInflightMsgs = 256; + /** + * The maximum byte size of log allowed by user. + */ + private long maxLogSize = 100 * 1024 * 1024; + /** + * The ratio of exponential approximation for average size of log entry. + */ + private double aveLogEntrySizeRatio = 0.95; + private boolean useRocksDBSegmentLogStorage = true; + private int maxSegmentFileSize = 64 * 1024 * 1024; + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/options/MetadataOptions.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/options/MetadataOptions.java new file mode 100644 index 000000000..1bfd2f844 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/options/MetadataOptions.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.options; + +import lombok.Data; + +@Data +public class MetadataOptions { + private String dataPath; + private String raftPath; +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/options/PartitionEngineOptions.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/options/PartitionEngineOptions.java new file mode 100644 index 000000000..49ddd6c17 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/options/PartitionEngineOptions.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.options; + +import java.util.List; + +import org.apache.hugegraph.store.raft.RaftTaskHandler; + +import com.alipay.sofa.jraft.conf.Configuration; + +import lombok.Data; + +/** + * Partition engine configuration + */ +@Data +public class PartitionEngineOptions { + // 异步任务执行时间间隔, 单位秒 + private final int taskScheduleTime = 60; + // 分裂过程,等待数据对齐超时时间 + private final long splitPartitionTimeout = 30 * 60 * 1000; + HgStoreEngineOptions.RaftOptions raftOptions; + // raft存储路径 + private String raftDataPath; + private String raftSnapShotPath; + private Integer groupId; + private String raftAddress; + private List peerList; + private Configuration conf; + // raft 任务处理器 + private RaftTaskHandler taskHandler; +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/options/RaftRocksdbOptions.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/options/RaftRocksdbOptions.java new file mode 100644 index 000000000..1db3260ab --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/options/RaftRocksdbOptions.java @@ -0,0 +1,207 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.options; + +import java.util.Map; + +import org.apache.hugegraph.config.HugeConfig; +import org.apache.hugegraph.rocksdb.access.RocksDBOptions; +import org.apache.hugegraph.store.business.BusinessHandlerImpl; +import org.rocksdb.BlockBasedTableConfig; +import org.rocksdb.BloomFilter; +import org.rocksdb.Cache; +import org.rocksdb.ColumnFamilyOptions; +import org.rocksdb.CompressionType; +import org.rocksdb.DBOptions; +import org.rocksdb.Env; +import org.rocksdb.IndexType; +import org.rocksdb.LRUCache; +import org.rocksdb.RocksDB; +import org.rocksdb.WriteBufferManager; +import org.rocksdb.util.SizeUnit; + +import com.alipay.sofa.jraft.storage.impl.RocksDBLogStorage; +import com.alipay.sofa.jraft.util.StorageOptionsFactory; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class RaftRocksdbOptions { + private static RocksdbConfig rocksdbConfig = null; + + private static RocksdbConfig getRocksdbConfig(HugeConfig options) { + if (rocksdbConfig == null) { + synchronized (RocksdbConfig.class) { + rocksdbConfig = new RocksdbConfig(options); + } + } + return rocksdbConfig; + } + + private static void registerRaftRocksdbConfig(HugeConfig options) { + Cache blockCache = new LRUCache(SizeUnit.GB); + BlockBasedTableConfig tableConfig = new BlockBasedTableConfig() + .setIndexType(IndexType.kTwoLevelIndexSearch) + .setPartitionFilters(true) // + .setMetadataBlockSize(8 * SizeUnit.KB) // + .setCacheIndexAndFilterBlocks( + options.get(RocksDBOptions.PUT_FILTER_AND_INDEX_IN_CACHE)) + .setCacheIndexAndFilterBlocksWithHighPriority(true) + .setPinL0FilterAndIndexBlocksInCache( + options.get(RocksDBOptions.PIN_L0_FILTER_AND_INDEX_IN_CACHE)) + .setBlockSize(4 * SizeUnit.KB) + .setBlockCache(blockCache); + + StorageOptionsFactory.registerRocksDBTableFormatConfig(RocksDBLogStorage.class, + tableConfig); + + DBOptions dbOptions = StorageOptionsFactory.getDefaultRocksDBOptions(); + dbOptions.setEnv(rocksdbConfig.getEnv()); + + // raft rocksdb数量固定,通过max_write_buffer_number可以控制 + //dbOptions.setWriteBufferManager(rocksdbConfig.getBufferManager()); + dbOptions.setUnorderedWrite(true); + StorageOptionsFactory.registerRocksDBOptions(RocksDBLogStorage.class, + dbOptions); + + ColumnFamilyOptions cfOptions = + StorageOptionsFactory.getDefaultRocksDBColumnFamilyOptions(); + cfOptions.setTargetFileSizeBase(256 * SizeUnit.MB); + cfOptions.setWriteBufferSize(8 * SizeUnit.MB); + cfOptions.setNumLevels(3); + cfOptions.setMaxWriteBufferNumber(3); + cfOptions.setCompressionType(CompressionType.NO_COMPRESSION); + cfOptions.setMaxBytesForLevelBase(2048 * SizeUnit.GB); + + StorageOptionsFactory.registerRocksDBColumnFamilyOptions(RocksDBLogStorage.class, + cfOptions); + } + + public static void initRocksdbGlobalConfig(Map config) { + HugeConfig hugeConfig = BusinessHandlerImpl.initRocksdb(config, null); + RocksdbConfig rocksdbConfig = getRocksdbConfig(hugeConfig); + registerRaftRocksdbConfig(hugeConfig); + config.put(RocksDBOptions.ENV, rocksdbConfig.getEnv()); + config.put(RocksDBOptions.WRITE_BUFFER_MANAGER, rocksdbConfig.getBufferManager()); + config.put(RocksDBOptions.BLOCK_TABLE_CONFIG, rocksdbConfig.getTableConfig()); + config.put(RocksDBOptions.BLOCK_CACHE, rocksdbConfig.getBlockCache()); + config.put(RocksDBOptions.WRITE_CACHE, rocksdbConfig.getWriteCache()); + } + + public static WriteBufferManager getWriteBufferManager() { + return rocksdbConfig.getBufferManager(); + } + + public static Env getEnv() { + return rocksdbConfig.getEnv(); + } + + public static Cache getWriteCache() { + return rocksdbConfig.getWriteCache(); + } + + public static Cache getBlockCache() { + return rocksdbConfig.getBlockCache(); + } + + public static long getWriteCacheCapacity() { + return rocksdbConfig.getWriteCacheCapacity(); + } + + public static long getBlockCacheCapacity() { + return rocksdbConfig.getBlockCacheCapacity(); + } + + static class RocksdbConfig { + private final Env env; + private final LRUCache blockCache; + private final LRUCache writeCache; + private final WriteBufferManager bufferManager; + private final BlockBasedTableConfig tableConfig; + private final long blockCacheCapacity; + private final long writeCacheCapacity; + + public RocksdbConfig(HugeConfig options) { + RocksDB.loadLibrary(); + this.env = Env.getDefault(); + double writeBufferRatio = options.get(RocksDBOptions.WRITE_BUFFER_RATIO); + this.writeCacheCapacity = + (long) (options.get(RocksDBOptions.TOTAL_MEMORY_SIZE) * writeBufferRatio); + this.blockCacheCapacity = + options.get(RocksDBOptions.TOTAL_MEMORY_SIZE) - writeCacheCapacity; + this.writeCache = new LRUCache(writeCacheCapacity); + this.blockCache = new LRUCache(blockCacheCapacity); + this.bufferManager = new WriteBufferManager(writeCacheCapacity, writeCache, + options.get( + RocksDBOptions.WRITE_BUFFER_ALLOW_STALL)); + this.tableConfig = new BlockBasedTableConfig() // + .setIndexType( + IndexType.kTwoLevelIndexSearch) // + .setPartitionFilters(true) // + .setMetadataBlockSize(8 * SizeUnit.KB) // + .setCacheIndexAndFilterBlocks( + options.get( + RocksDBOptions.PUT_FILTER_AND_INDEX_IN_CACHE)) // + .setCacheIndexAndFilterBlocksWithHighPriority( + true) // + .setPinL0FilterAndIndexBlocksInCache( + options.get( + RocksDBOptions.PIN_L0_FILTER_AND_INDEX_IN_CACHE)) // + .setBlockSize(4 * SizeUnit.KB)// + .setBlockCache(blockCache); + + int bitsPerKey = options.get(RocksDBOptions.BLOOM_FILTER_BITS_PER_KEY); + if (bitsPerKey >= 0) { + tableConfig.setFilterPolicy(new BloomFilter(bitsPerKey, + options.get( + RocksDBOptions.BLOOM_FILTER_MODE))); + } + tableConfig.setWholeKeyFiltering( + options.get(RocksDBOptions.BLOOM_FILTER_WHOLE_KEY)); + log.info("RocksdbConfig {}", options.get(RocksDBOptions.BLOOM_FILTER_BITS_PER_KEY)); + } + + public Env getEnv() { + return env; + } + + public LRUCache getBlockCache() { + return blockCache; + } + + public LRUCache getWriteCache() { + return writeCache; + } + + public WriteBufferManager getBufferManager() { + return bufferManager; + } + + public BlockBasedTableConfig getTableConfig() { + return tableConfig; + } + + public long getBlockCacheCapacity() { + return blockCacheCapacity; + } + + public long getWriteCacheCapacity() { + return writeCacheCapacity; + } + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/pd/DefaultPdProvider.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/pd/DefaultPdProvider.java new file mode 100644 index 000000000..1f868e9a7 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/pd/DefaultPdProvider.java @@ -0,0 +1,474 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.pd; + + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; + +import org.apache.hugegraph.pd.client.PDClient; +import org.apache.hugegraph.pd.client.PDConfig; +import org.apache.hugegraph.pd.client.PDPulse; +import org.apache.hugegraph.pd.client.PDPulseImpl; +import org.apache.hugegraph.pd.common.KVPair; +import org.apache.hugegraph.pd.common.PDException; +import org.apache.hugegraph.pd.grpc.MetaTask; +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.pd.grpc.pulse.PartitionHeartbeatRequest; +import org.apache.hugegraph.pd.grpc.pulse.PartitionHeartbeatResponse; +import org.apache.hugegraph.pd.grpc.pulse.PdInstructionType; +import org.apache.hugegraph.pd.grpc.pulse.PulseResponse; +import org.apache.hugegraph.pd.grpc.watch.WatchGraphResponse; +import org.apache.hugegraph.pd.grpc.watch.WatchResponse; +import org.apache.hugegraph.pd.pulse.PulseServerNotice; +import org.apache.hugegraph.pd.watch.NodeEvent; +import org.apache.hugegraph.pd.watch.PartitionEvent; +import org.apache.hugegraph.store.HgStoreEngine; +import org.apache.hugegraph.store.meta.Graph; +import org.apache.hugegraph.store.meta.GraphManager; +import org.apache.hugegraph.store.meta.Partition; +import org.apache.hugegraph.store.meta.Store; +import org.apache.hugegraph.store.metric.HgMetricService; +import org.apache.hugegraph.store.util.Asserts; +import org.apache.hugegraph.util.Log; +import org.slf4j.Logger; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class DefaultPdProvider implements PdProvider { + private static final Logger LOG = Log.logger(DefaultPdProvider.class); + private final PDClient pdClient; + private final String pdServerAddress; + private final PDPulse pulseClient; + private Consumer hbOnError = null; + private List partitionCommandListeners; + private PDPulse.Notifier pdPulse; + private GraphManager graphManager = null; + PDClient.PDEventListener listener = new PDClient.PDEventListener() { + // 监听pd变更信息的listener + @Override + public void onStoreChanged(NodeEvent event) { + if (event.getEventType() == NodeEvent.EventType.NODE_RAFT_CHANGE) { + log.info("store raft group changed!, {}", event); + pdClient.invalidStoreCache(event.getNodeId()); + HgStoreEngine.getInstance().rebuildRaftGroup(event.getNodeId()); + } else if (event.getEventType() == NodeEvent.EventType.NODE_PD_LEADER_CHANGE) { + log.info("pd leader changed!, {}. restart heart beat", event); + if (pulseClient.resetStub(event.getGraph(), pdPulse)) { + startHeartbeatStream(hbOnError); + } + } + } + + @Override + public void onPartitionChanged(PartitionEvent event) { + + } + + @Override + public void onGraphChanged(WatchResponse event) { + WatchGraphResponse graphResponse = event.getGraphResponse(); + Metapb.Graph graph = graphResponse.getGraph(); + if (graphManager != null) { + graphManager.updateGraph(new Graph(graph)); + } + + } + }; + + public DefaultPdProvider(String pdAddress) { + this.pdClient = PDClient.create(PDConfig.of(pdAddress).setEnableCache(true)); + this.pdClient.addEventListener(listener); + this.pdServerAddress = pdAddress; + partitionCommandListeners = Collections.synchronizedList(new ArrayList()); + log.info("pulse client connect to {}", pdClient.getLeaderIp()); + this.pulseClient = new PDPulseImpl(pdClient.getLeaderIp()); + } + + @Override + public long registerStore(Store store) throws PDException { + Asserts.isTrue(this.pdClient != null, "pd client is null"); + LOG.info("registerStore pd={} storeId={}, store={}", this.pdServerAddress, store.getId(), + store); + + long storeId = 0; + Metapb.Store protoObj = store.getProtoObj(); + try { + storeId = pdClient.registerStore(protoObj); + store.setId(storeId); + if (pdClient.getStore(storeId).getState() != Metapb.StoreState.Up) { + LOG.warn("Store {} is not activated, state is {}", storeId, + pdClient.getStore(storeId).getState()); + } + } catch (PDException e) { + LOG.error( + "Exception in storage registration, StoreID= {} pd= {} exceptCode= {} except=" + + " {}.", + protoObj.getId(), this.pdServerAddress, e.getErrorCode(), e.getMessage()); + storeId = 0; + throw e; + } catch (Exception e) { + LOG.error( + "Exception in storage registration, StoreID= {} pd= {} except= {}, Please " + + "check your network settings.", + protoObj.getId(), this.pdServerAddress, e.getMessage()); + handleCommonException(e); + storeId = 0; + } + return storeId; + } + + @Override + public Partition getPartitionByID(String graph, int partId) { + try { + KVPair pair = pdClient.getPartitionById( + graph, partId); + if (null != pair) { + return new Partition(pair.getKey()); + } + } catch (PDException e) { + log.error("Partition {}-{} getPartitionByID exception {}", graph, partId, e); + } + return null; + } + + @Override + public Metapb.Shard getPartitionLeader(String graph, int partId) { + try { + KVPair pair = pdClient.getPartitionById( + graph, partId); + if (null != pair) { + return pair.getValue(); + } + } catch (PDException e) { + log.error("Partition {}-{} getPartitionByID exception {}", graph, partId, e); + } + return null; + } + + @Override + public Metapb.Partition getPartitionByCode(String graph, int code) { + try { + KVPair pair = pdClient.getPartitionByCode( + graph, code); + if (null != pair) { + return pair.getKey(); + } + } catch (PDException e) { + log.error("Partition {} getPartitionByCode {} exception {}", graph, code, e); + } + return null; + } + + @Override + public Partition delPartition(String graph, int partId) { + log.info("Partition {}-{} send delPartition to PD", graph, partId); + try { + Metapb.Partition partition = pdClient.delPartition(graph, partId); + if (null != partition) { + return new Partition(partition); + } + } catch (PDException e) { + log.error("Partition {}-{} remove exception {}", graph, partId, e); + } + return null; + } + + @Override + public List updatePartition(List partitions) throws + PDException { + + try { + List results = pdClient.updatePartition(partitions); + return results; + } catch (PDException e) { + throw e; + } + } + + @Override + public List getPartitionsByStore(long storeId) throws PDException { + List partitions = new ArrayList<>(); + List parts = pdClient.getPartitionsByStore(storeId); + parts.forEach(e -> { + partitions.add(new Partition(e)); + }); + return partitions; + } + + @Override + public void updatePartitionCache(Partition partition, Boolean changeLeader) { + Metapb.Shard leader = null; + + var shardGroup = getShardGroup(partition.getId()); + if (shardGroup != null) { + for (Metapb.Shard shard : shardGroup.getShardsList()) { + if (shard.getRole() == Metapb.ShardRole.Leader) { + leader = shard; + } + } + } + if (!changeLeader) { + try { + leader = pdClient.getPartitionById(partition.getGraphName(), partition.getId()) + .getValue(); + } catch (PDException e) { + log.error("find leader error,leader changed to storeId:{}", leader.getStoreId()); + } catch (Exception e1) { + log.error("exception ", e1); + } + } + pdClient.updatePartitionCache(partition.getProtoObj(), leader); + } + + @Override + public void invalidPartitionCache(String graph, int partId) { + pdClient.invalidPartitionCache(graph, partId); + } + + /** + * 启动partition心跳流式传输 + * + * @return + */ + @Override + public boolean startHeartbeatStream(Consumer onError) { + this.hbOnError = onError; + pdPulse = pulseClient.connectPartition(new PDPulse.Listener<>() { + + @Override + public void onNotice(PulseServerNotice response) { + PulseResponse content = response.getContent(); + + // 消息消费应答,能够正确消费消息,调用accept返回状态码,否则不要调用accept + Consumer consumer = integer -> { + LOG.debug("Partition heartbeat accept instruction: {}", content); + // LOG.info("accept notice id : {}, ts:{}", response.getNoticeId(), System + // .currentTimeMillis()); + // http2 并发问题,需要加锁 + // synchronized (pdPulse) { + response.ack(); + // } + }; + + if (content.hasInstructionResponse()) { + var pdInstruction = content.getInstructionResponse(); + consumer.accept(0); + // 当前的链接变成了follower,重新链接 + if (pdInstruction.getInstructionType() == + PdInstructionType.CHANGE_TO_FOLLOWER) { + onCompleted(); + log.info("got pulse instruction, change leader to {}", + pdInstruction.getLeaderIp()); + if (pulseClient.resetStub(pdInstruction.getLeaderIp(), pdPulse)) { + startHeartbeatStream(hbOnError); + } + } + return; + } + + PartitionHeartbeatResponse instruct = content.getPartitionHeartbeatResponse(); + LOG.debug("Partition heartbeat receive instruction: {}", instruct); + + Partition partition = new Partition(instruct.getPartition()); + + for (PartitionInstructionListener event : partitionCommandListeners) { + if (instruct.hasChangeShard()) { + event.onChangeShard(instruct.getId(), partition, instruct + .getChangeShard(), + consumer); + } + if (instruct.hasSplitPartition()) { + event.onSplitPartition(instruct.getId(), partition, + instruct.getSplitPartition(), consumer); + } + if (instruct.hasTransferLeader()) { + event.onTransferLeader(instruct.getId(), partition, + instruct.getTransferLeader(), consumer); + } + if (instruct.hasDbCompaction()) { + event.onDbCompaction(instruct.getId(), partition, + instruct.getDbCompaction(), consumer); + } + + if (instruct.hasMovePartition()) { + event.onMovePartition(instruct.getId(), partition, + instruct.getMovePartition(), consumer); + } + + if (instruct.hasCleanPartition()) { + event.onCleanPartition(instruct.getId(), partition, + instruct.getCleanPartition(), + consumer); + } + + if (instruct.hasKeyRange()) { + event.onPartitionKeyRangeChanged(instruct.getId(), partition, + instruct.getKeyRange(), + consumer); + } + } + } + + @Override + public void onError(Throwable throwable) { + LOG.error("Partition heartbeat stream error. {}", throwable); + pulseClient.resetStub(pdClient.getLeaderIp(), pdPulse); + onError.accept(throwable); + } + + @Override + public void onCompleted() { + LOG.info("Partition heartbeat stream complete"); + } + }); + return true; + } + + + /** + * 添加服务端消息监听 + * + * @param listener + * @return + */ + @Override + public boolean addPartitionInstructionListener(PartitionInstructionListener listener) { + partitionCommandListeners.add(listener); + return true; + } + + @Override + public boolean partitionHeartbeat(List statsList) { + for (Metapb.PartitionStats stats : statsList) { + PartitionHeartbeatRequest.Builder request = PartitionHeartbeatRequest.newBuilder() + .setStates(stats); + pdPulse.notifyServer(request); + } + return false; + } + + @Override + public boolean isLocalPartition(long storeId, int partitionId) { + try { + return !pdClient.queryPartitions(storeId, partitionId).isEmpty(); + } catch (PDException e) { + log.error("isLocalPartition exception ", e); + } + return false; + } + + @Override + public Metapb.Graph getGraph(String graphName) throws PDException { + return pdClient.getGraph(graphName); + } + + @Override + public void reportTask(MetaTask.Task task) throws PDException { + pdClient.reportTask(task); + } + + @Override + public PDClient getPDClient() { + return this.pdClient; + } + + @Override + public boolean updatePartitionLeader(String graphName, int partId, long leaderStoreId) { + this.pdClient.updatePartitionLeader(graphName, partId, leaderStoreId); + return true; + } + + @Override + public Store getStoreByID(Long storeId) { + try { + return new Store(pdClient.getStore(storeId)); + } catch (PDException e) { + log.error("getStoreByID exception {}", e); + } + return null; + } + + @Override + public Metapb.ClusterStats getClusterStats() { + try { + return pdClient.getClusterStats(); + } catch (PDException e) { + log.error("getClusterStats exception {}", e); + return Metapb.ClusterStats.newBuilder() + .setState(Metapb.ClusterState.Cluster_Fault).build(); + } + } + + @Override + public Metapb.ClusterStats storeHeartbeat(Store node) throws PDException { + LOG.debug("storeHeartbeat node id: {}", node.getId()); + + try { + Metapb.StoreStats.Builder stats = HgMetricService.getInstance().getMetrics(); + LOG.debug("storeHeartbeat StoreStats: {}", stats); + stats.setCores(node.getCores()); + return pdClient.storeHeartbeat(stats.build()); + + } catch (PDException e) { + throw e; + } catch (Exception e) { + LOG.warn("Store {} report heartbeat exception: {}", node.getId(), e.toString()); + } + + return Metapb.ClusterStats.newBuilder() + .setState(Metapb.ClusterState.Cluster_Fault).build(); + } + + + private void handleCommonException(Exception e) { + } + + @Override + public GraphManager getGraphManager() { + return graphManager; + } + + @Override + public void setGraphManager(GraphManager graphManager) { + this.graphManager = graphManager; + } + + @Override + public void deleteShardGroup(int groupId) throws PDException { + pdClient.deleteShardGroup(groupId); + } + + @Override + public Metapb.ShardGroup getShardGroup(int partitionId) { + try { + return pdClient.getShardGroup(partitionId); + } catch (PDException e) { + log.error("get shard group :{} from pd failed: {}", partitionId, e.getMessage()); + } + return null; + } + + @Override + public void updateShardGroup(Metapb.ShardGroup shardGroup) throws PDException { + pdClient.updateShardGroup(shardGroup); + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/pd/FakePdServiceProvider.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/pd/FakePdServiceProvider.java new file mode 100644 index 000000000..43ccc6e39 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/pd/FakePdServiceProvider.java @@ -0,0 +1,265 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.pd; + + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; + +import org.apache.hugegraph.pd.client.PDClient; +import org.apache.hugegraph.pd.common.PDException; +import org.apache.hugegraph.pd.common.PartitionUtils; +import org.apache.hugegraph.pd.grpc.MetaTask; +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.pd.grpc.Pdpb; +import org.apache.hugegraph.store.meta.GraphManager; +import org.apache.hugegraph.store.meta.Partition; +import org.apache.hugegraph.store.meta.Store; +import org.apache.hugegraph.store.options.HgStoreEngineOptions; + +import lombok.extern.slf4j.Slf4j; + +/** + * 内置PD服务,用于单机部署或开发调试 + */ +@Slf4j +public class FakePdServiceProvider implements PdProvider { + private final Map stores; + private final int shardCount = 0; + private final Map partitions = new ConcurrentHashMap<>(); + private int partitionCount = 0; + private GraphManager graphManager = null; + + public FakePdServiceProvider(HgStoreEngineOptions.FakePdOptions options) { + stores = new LinkedHashMap<>(); + if (options != null) { + String[] storeList = options.getStoreList().split(","); + String[] peersList = options.getPeersList().split(","); + for (int i = 0; i < storeList.length; i++) { + if (!storeList[i].isEmpty()) { + addStore(storeList[i], peersList[i]); + } + } + } + this.partitionCount = options.getPartitionCount(); + } + + public static long makeStoreId(String storeAddress) { + return storeAddress.hashCode(); + } + + /** + * For unit test + * + * @return + */ + public static Store getDefaultStore() { + Store store = new Store(); + store.setId(1); + store.setStoreAddress("127.0.0.1:8501"); + store.setRaftAddress("127.0.0.1:8511"); + store.setPartitionCount(1); + return store; + } + + private void addStore(String storeAddr, String raftAddr) { + Store store = new Store() {{ + setId(makeStoreId(storeAddr)); + setRaftAddress(raftAddr); + setStoreAddress(storeAddr); + }}; + stores.put(store.getId(), store); + } + + public void addStore(Store store) { + stores.put(store.getId(), store); + } + + @Override + public long registerStore(Store store) throws PDException { + log.info("registerStore storeId:{}, storeAddress:{}", store.getId(), + store.getStoreAddress()); + + // id 不匹配,禁止登录 + if (store.getId() != 0 && store.getId() != makeStoreId(store.getStoreAddress())) { + throw new PDException(Pdpb.ErrorType.STORE_ID_NOT_EXIST_VALUE, + "Store id does not matched"); + } + + if (!stores.containsKey(makeStoreId(store.getStoreAddress()))) { + store.setId(makeStoreId(store.getStoreAddress())); + stores.put(store.getId(), store); + } + Store s = stores.get(makeStoreId(store.getStoreAddress())); + store.setId(s.getId()); + + return store.getId(); + } + + @Override + public Partition getPartitionByID(String graph, int partId) { + List storeList = new ArrayList(stores.values()); + int shardCount = this.shardCount; + if (shardCount == 0 || shardCount >= stores.size()) { + shardCount = stores.size(); + } + + + int storeIdx = partId % storeList.size(); + List shards = new ArrayList<>(); + for (int i = 0; i < shardCount; i++) { + Metapb.Shard shard = + Metapb.Shard.newBuilder().setStoreId(storeList.get(storeIdx).getId()) + .setRole(i == 0 ? Metapb.ShardRole.Leader : + Metapb.ShardRole.Follower) // + .build(); + shards.add(shard); + storeIdx = (storeIdx + 1) >= storeList.size() ? 0 : ++storeIdx; // 顺序选择 + } + + int partLength = getPartitionLength(); + Metapb.Partition partition = Metapb.Partition.newBuilder() + .setGraphName(graph) + .setId(partId) + .setStartKey(partLength * partId) + .setEndKey(partLength * (partId + 1)) + //.addAllShards(shards) + .build(); + return new Partition(partition); + } + + @Override + public Metapb.Shard getPartitionLeader(String graph, int partId) { + return null; + } + + private int getPartitionLength() { + return PartitionUtils.MAX_VALUE / (partitionCount == 0 ? stores.size() : partitionCount) + + 1; + } + + @Override + public Metapb.Partition getPartitionByCode(String graph, int code) { + int partId = code / getPartitionLength(); + return getPartitionByID(graph, partId).getProtoObj(); + } + + @Override + public Partition delPartition(String graph, int partId) { + return null; + } + + @Override + public List updatePartition(List partitions) { + return partitions; + } + + @Override + public List getPartitionsByStore(long storeId) throws PDException { + return new ArrayList<>(); + } + + @Override + public void updatePartitionCache(Partition partition, Boolean changeLeader) { + + } + + @Override + public void invalidPartitionCache(String graph, int partId) { + + } + + @Override + public boolean startHeartbeatStream(Consumer onError) { + return false; + } + + @Override + public boolean addPartitionInstructionListener(PartitionInstructionListener listener) { + return false; + } + + @Override + public boolean partitionHeartbeat(List statsList) { + return true; + } + + @Override + public boolean isLocalPartition(long storeId, int partitionId) { + return true; + } + + @Override + public Metapb.Graph getGraph(String graphName) { + return Metapb.Graph.newBuilder().setGraphName(graphName) + //.setId(PartitionUtils.calcHashcode(graphName.getBytes())) + .build(); + } + + @Override + public void reportTask(MetaTask.Task task) throws PDException { + + } + + @Override + public PDClient getPDClient() { + return null; + } + + @Override + public Store getStoreByID(Long storeId) { + return stores.get(storeId); + } + + @Override + public Metapb.ClusterStats getClusterStats() { + return Metapb.ClusterStats.newBuilder() + .setState(Metapb.ClusterState.Cluster_OK).build(); + } + + @Override + public Metapb.ClusterStats storeHeartbeat(Store node) { + + return getClusterStats(); + } + + @Override + public boolean updatePartitionLeader(String graphName, int partId, long leaderStoreId) { + return false; + + } + + @Override + public GraphManager getGraphManager() { + return graphManager; + } + + @Override + public void setGraphManager(GraphManager graphManager) { + this.graphManager = graphManager; + } + + @Override + public void deleteShardGroup(int groupId) { + + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/pd/PartitionInstructionListener.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/pd/PartitionInstructionListener.java new file mode 100644 index 000000000..51c6df11b --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/pd/PartitionInstructionListener.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.pd; + + +import java.util.function.Consumer; + +import org.apache.hugegraph.pd.grpc.pulse.ChangeShard; +import org.apache.hugegraph.pd.grpc.pulse.CleanPartition; +import org.apache.hugegraph.pd.grpc.pulse.DbCompaction; +import org.apache.hugegraph.pd.grpc.pulse.MovePartition; +import org.apache.hugegraph.pd.grpc.pulse.PartitionKeyRange; +import org.apache.hugegraph.pd.grpc.pulse.SplitPartition; +import org.apache.hugegraph.pd.grpc.pulse.TransferLeader; +import org.apache.hugegraph.store.meta.Partition; + +public interface PartitionInstructionListener { + void onChangeShard(long taskId, Partition partition, ChangeShard changeShard, + Consumer consumer); + + void onTransferLeader(long taskId, Partition partition, TransferLeader transferLeader, + Consumer consumer); + + void onSplitPartition(long taskId, Partition partition, SplitPartition splitPartition, + Consumer consumer); + + void onDbCompaction(long taskId, Partition partition, DbCompaction rocksdbCompaction, + Consumer consumer); + + void onMovePartition(long taskId, Partition partition, MovePartition movePartition, + Consumer consumer); + + void onCleanPartition(long taskId, Partition partition, CleanPartition cleanPartition, + Consumer consumer); + + void onPartitionKeyRangeChanged(long taskId, Partition partition, + PartitionKeyRange partitionKeyRange, + Consumer consumer); +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/pd/PdProvider.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/pd/PdProvider.java new file mode 100644 index 000000000..dc38ca019 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/pd/PdProvider.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.pd; + +import java.util.List; +import java.util.function.Consumer; + +import org.apache.hugegraph.pd.client.PDClient; +import org.apache.hugegraph.pd.common.PDException; +import org.apache.hugegraph.pd.grpc.MetaTask; +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.store.meta.GraphManager; +import org.apache.hugegraph.store.meta.Partition; +import org.apache.hugegraph.store.meta.Store; +import org.apache.hugegraph.store.util.HgStoreException; + +public interface PdProvider { + long registerStore(Store store) throws PDException; + + Store getStoreByID(Long storeId); + + Metapb.ClusterStats getClusterStats(); + + Metapb.ClusterStats storeHeartbeat(Store node) throws HgStoreException, PDException; + + Partition getPartitionByID(String graph, int partId); + + Metapb.Shard getPartitionLeader(String graph, int partId); + + Metapb.Partition getPartitionByCode(String graph, int code); + + Partition delPartition(String graph, int partId); + + List updatePartition(List partitions) throws PDException; + + List getPartitionsByStore(long storeId) throws PDException; + + void updatePartitionCache(Partition partition, Boolean changeLeader); + + void invalidPartitionCache(String graph, int partId); + + boolean startHeartbeatStream(Consumer onError); + + boolean addPartitionInstructionListener(PartitionInstructionListener listener); + + boolean partitionHeartbeat(List statsList); + + + boolean isLocalPartition(long storeId, int partitionId); + + Metapb.Graph getGraph(String graphName) throws PDException; + + void reportTask(MetaTask.Task task) throws PDException; + + PDClient getPDClient(); + + boolean updatePartitionLeader(String graphName, int partId, long leaderStoreId); + + GraphManager getGraphManager(); + + void setGraphManager(GraphManager graphManager); + + /** + * 删除分区 shard group + * + * @param groupId + */ + void deleteShardGroup(int groupId) throws PDException; + + default Metapb.ShardGroup getShardGroup(int partitionId) { + return null; + } + + default void updateShardGroup(Metapb.ShardGroup shardGroup) throws PDException { + } + +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/raft/HgStoreStateMachine.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/raft/HgStoreStateMachine.java new file mode 100644 index 000000000..3ba1fe3b1 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/raft/HgStoreStateMachine.java @@ -0,0 +1,269 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.raft; + +import java.util.Base64; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.hugegraph.store.snapshot.HgSnapshotHandler; +import org.apache.hugegraph.store.util.HgStoreException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alipay.sofa.jraft.Closure; +import com.alipay.sofa.jraft.Iterator; +import com.alipay.sofa.jraft.Status; +import com.alipay.sofa.jraft.conf.Configuration; +import com.alipay.sofa.jraft.core.StateMachineAdapter; +import com.alipay.sofa.jraft.entity.LeaderChangeContext; +import com.alipay.sofa.jraft.entity.RaftOutter; +import com.alipay.sofa.jraft.error.RaftError; +import com.alipay.sofa.jraft.error.RaftException; +import com.alipay.sofa.jraft.storage.snapshot.SnapshotReader; +import com.alipay.sofa.jraft.storage.snapshot.SnapshotWriter; +import com.alipay.sofa.jraft.util.Utils; + +/** + * Raft 状态机 + */ + +public class HgStoreStateMachine extends StateMachineAdapter { + private static final Logger LOG = LoggerFactory.getLogger(HgStoreStateMachine.class); + private final AtomicLong leaderTerm = new AtomicLong(-1); + private final HgSnapshotHandler snapshotHandler; + private final List taskHandlers; + private final List stateListeners; + private final Integer groupId; + private long committedIndex; + + public HgStoreStateMachine(Integer groupId, HgSnapshotHandler snapshotHandler) { + this.groupId = groupId; + this.snapshotHandler = snapshotHandler; + this.stateListeners = new CopyOnWriteArrayList<>(); + this.taskHandlers = new CopyOnWriteArrayList<>(); + } + + public void addTaskHandler(RaftTaskHandler handler) { + taskHandlers.add(handler); + } + + public void addStateListener(RaftStateListener listener) { + stateListeners.add(listener); + } + + public boolean isLeader() { + return this.leaderTerm.get() > 0; + } + + @Override + public void onApply(Iterator inter) { + + while (inter.hasNext()) { + final RaftClosureAdapter done = (RaftClosureAdapter) inter.done(); + try { + for (RaftTaskHandler taskHandler : taskHandlers) { + if (done != null) { + // Leader分支,本地调用 + if (taskHandler.invoke(groupId, done.op.getOp(), done.op.getReq(), + done.closure)) { + done.run(Status.OK()); + break; + } + } else { + if (taskHandler.invoke(groupId, inter.getData().array(), null)) { + break; + } + } + } + } catch (Throwable t) { + LOG.info("{}", Base64.getEncoder().encode(inter.getData().array())); + LOG.error("StateMachine{} meet critical error: .", groupId, t); + if (done != null) { + LOG.error("StateMachine meet critical error: op = {} {}.", done.op.getOp(), + done.op.getReq()); + // done.run(new Status(RaftError.EINTERNAL, t.getMessage())); + } + } + committedIndex = inter.getIndex(); + + stateListeners.forEach(listener -> { + listener.onDataCommitted(committedIndex); + }); + // 清理数据 + if (done != null) { + done.clear(); + } + // 遍历下一条 + inter.next(); + } + } + + public long getCommittedIndex() { + return committedIndex; + } + + public long getLeaderTerm() { + return leaderTerm.get(); + } + + @Override + public void onError(final RaftException e) { + LOG.error("Raft {} StateMachine on error {}", groupId, e); + Utils.runInThread(() -> { + stateListeners.forEach(listener -> { + listener.onError(e); + }); + }); + } + + @Override + public void onShutdown() { + super.onShutdown(); + } + + @Override + public void onLeaderStart(final long term) { + this.leaderTerm.set(term); + super.onLeaderStart(term); + Utils.runInThread(() -> { + stateListeners.forEach(listener -> { + listener.onLeaderStart(term); + }); + }); + LOG.info("Raft {} becomes leader ", groupId); + } + + @Override + public void onLeaderStop(final Status status) { + Utils.runInThread(() -> { + stateListeners.forEach(listener -> { + listener.onLeaderStop(this.leaderTerm.get()); + }); + }); + this.leaderTerm.set(-1); + super.onLeaderStop(status); + LOG.info("Raft {} lost leader ", groupId); + } + + @Override + public void onStartFollowing(final LeaderChangeContext ctx) { + super.onStartFollowing(ctx); + Utils.runInThread(() -> { + stateListeners.forEach(listener -> { + listener.onStartFollowing(ctx.getLeaderId(), ctx.getTerm()); + }); + }); + LOG.info("Raft {} start following: {}.", groupId, ctx); + } + + @Override + public void onStopFollowing(final LeaderChangeContext ctx) { + super.onStopFollowing(ctx); + Utils.runInThread(() -> { + stateListeners.forEach(listener -> { + listener.onStopFollowing(ctx.getLeaderId(), ctx.getTerm()); + }); + }); + LOG.info("Raft {} stop following: {}.", groupId, ctx); + } + + + @Override + public void onConfigurationCommitted(final Configuration conf) { + stateListeners.forEach(listener -> { + Utils.runInThread(() -> { + try { + listener.onConfigurationCommitted(conf); + } catch (Exception e) { + LOG.error("Raft {} onConfigurationCommitted {}", groupId, e); + } + }); + }); + LOG.info("Raft {} onConfigurationCommitted {}", groupId, conf); + } + + @Override + public void onSnapshotSave(final SnapshotWriter writer, final Closure done) { + Utils.runInThread(() -> { + try { + snapshotHandler.onSnapshotSave(writer); + LOG.info("Raft {} onSnapshotSave success", groupId); + done.run(Status.OK()); + } catch (HgStoreException e) { + LOG.error("Raft {} onSnapshotSave failed. {}", groupId, e.toString()); + done.run(new Status(RaftError.EIO, e.toString())); + } + }); + } + + @Override + public boolean onSnapshotLoad(final SnapshotReader reader) { + try { + RaftOutter.SnapshotMeta meta = reader.load(); + if (meta != null) { + this.committedIndex = meta.getLastIncludedIndex(); + LOG.info("onSnapshotLoad committedIndex = {}", this.committedIndex); + } else { + LOG.error("onSnapshotLoad failed to get SnapshotMeta"); + return false; + } + } catch (Exception e) { + LOG.error("onSnapshotLoad failed to get SnapshotMeta. {}", e.toString()); + return false; + } + + if (isLeader()) { + LOG.warn("Leader is not supposed to load snapshot"); + return false; + } + try { + snapshotHandler.onSnapshotLoad(reader, this.committedIndex); + LOG.info("Raft {} onSnapshotLoad success", groupId); + return true; + } catch (HgStoreException e) { + LOG.error("Raft {} onSnapshotLoad failed. {}", groupId, e.toString()); + return false; + } + } + + public static class RaftClosureAdapter implements RaftClosure { + private final RaftClosure closure; + private RaftOperation op; + + public RaftClosureAdapter(RaftOperation op, RaftClosure closure) { + this.op = op; + this.closure = closure; + } + + @Override + public void run(Status status) { + closure.run(status); + } + + public RaftClosure getClosure() { + return closure; + } + + public void clear() { + op = null; + } + } + +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/raft/RaftClosure.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/raft/RaftClosure.java new file mode 100644 index 000000000..50390bd4e --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/raft/RaftClosure.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.raft; + +import com.alipay.sofa.jraft.Closure; + +public interface RaftClosure extends Closure { + default void onLeaderChanged(Integer partId, Long storeId) { + } + +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/raft/RaftOperation.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/raft/RaftOperation.java new file mode 100644 index 000000000..222e44151 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/raft/RaftOperation.java @@ -0,0 +1,127 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.raft; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.caucho.hessian.io.Hessian2Input; +import com.caucho.hessian.io.Hessian2Output; +import com.google.protobuf.CodedOutputStream; + +import lombok.Data; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Data +public class RaftOperation { + + public static final byte SYNC_PARTITION_TASK = 0x60; + public static final byte SYNC_PARTITION = 0x61; + public static final byte BLANK_TASK = 0x62; + public static final byte DO_SNAPSHOT = 0x63; + // 集群内部数据迁移操作 + public static final byte IN_WRITE_OP = 0x64; + public static final byte IN_CLEAN_OP = 0x65; + public static final byte RAFT_UPDATE_PARTITION = 0x66; + public static final byte DB_COMPACTION = 0x67; + final static byte[] EMPTY_Bytes = new byte[0]; + private static final Logger LOG = LoggerFactory.getLogger(RaftOperation.class); + private byte[] values; // req序列化的结果,用于传输给其他raft node + private Object req; // 原始对象,用于本机处理,减少一次反序列化操作 + private byte op; // 操作类型 + + public static RaftOperation create(final byte op) { + try { + RaftOperation operation = new RaftOperation(); + operation.setOp(op); + operation.setReq(null); + operation.setValues(toByteArray(op)); + return operation; + } catch (Exception e) { + LOG.error("create error", e); + return null; + } + } + + public static RaftOperation create(final byte op, final byte[] values, final Object req) { + RaftOperation operation = new RaftOperation(); + operation.setOp(op); + operation.setReq(req); + operation.setValues(values); + return operation; + } + + public static RaftOperation create(final byte op, final Object req) { + try { + RaftOperation operation = new RaftOperation(); + operation.setOp(op); + operation.setReq(req); + operation.setValues(toByteArray(op, req)); + return operation; + } catch (Exception e) { + log.error("exception ", e); + } + return null; + } + + public static RaftOperation create(final byte op, + final com.google.protobuf.GeneratedMessageV3 req) throws + IOException { + // 序列化, + final byte[] buffer = new byte[req.getSerializedSize() + 1]; + final CodedOutputStream output = CodedOutputStream.newInstance(buffer); + output.write(op); + req.writeTo(output); + output.checkNoSpaceLeft(); + output.flush(); + return create(op, buffer, req); + } + + public static byte[] toByteArray(final byte op) throws IOException { + try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) { + bos.write(op); + bos.flush(); + return bos.toByteArray(); + } + } + + public static byte[] toByteArray(final byte op, final Object obj) throws IOException { + try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) { + bos.write(op); + Hessian2Output output = new Hessian2Output(bos); + output.writeObject(obj); + output.flush(); + return bos.toByteArray(); + } + } + + public static Object toObject(final byte[] bytes, int offset) throws IOException { + try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes, offset + 1, + bytes.length - offset)) { + Hessian2Input input = new Hessian2Input(bis); + Object obj = input.readObject(); + input.close(); + return obj; + } + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/raft/RaftStateListener.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/raft/RaftStateListener.java new file mode 100644 index 000000000..b4502789e --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/raft/RaftStateListener.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.raft; + +import com.alipay.sofa.jraft.conf.Configuration; +import com.alipay.sofa.jraft.entity.PeerId; +import com.alipay.sofa.jraft.error.RaftException; + +public interface RaftStateListener { + /** + * Called when current node becomes leader. + * + * @param newTerm the new term + */ + void onLeaderStart(final long newTerm); + + /** + * Called when current node loses leadership. + * + * @param oldTerm the old term + */ + default void onLeaderStop(final long oldTerm) { + } + + /** + * This method is called when a follower or candidate starts following a leader and its leaderId + * (should be NULL before the method is called) is set to the leader's id, situations including: + * 1. A candidate receives appendEntries request from a leader + * 2. A follower(without leader) receives appendEntries from a leader + *

+ * The parameters gives the information(leaderId and term) about the very + * leader whom the follower starts to follow. + * User can reset the node's information as it starts to follow some leader. + * + * @param newLeaderId the new leader id whom the follower starts to follow + * @param newTerm the new term + */ + default void onStartFollowing(final PeerId newLeaderId, final long newTerm) { + } + + /** + * This method is called when a follower stops following a leader and its leaderId becomes null, + * situations including: + * 1. Handle election timeout and start preVote + * 2. Receive requests with higher term such as VoteRequest from a candidate + * or appendEntries request from a new leader + * 3. Receive timeoutNow request from current leader and start request vote. + *

+ * The parameters gives the information(leaderId and term) about the very leader + * whom the follower followed before. + * User can reset the node's information as it stops following some leader. + * + * @param oldLeaderId the old leader id whom the follower followed before + * @param oldTerm the old term + */ + default void onStopFollowing(final PeerId oldLeaderId, final long oldTerm) { + } + + + /** + * Invoked when a configuration has been committed to the group. + * + * @param conf committed configuration + */ + default void onConfigurationCommitted(final Configuration conf) { + } + + default void onDataCommitted(long index) { + } + + void onError(final RaftException e); +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/raft/RaftTaskHandler.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/raft/RaftTaskHandler.java new file mode 100644 index 000000000..9aca94931 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/raft/RaftTaskHandler.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.raft; + +import org.apache.hugegraph.store.util.HgStoreException; + +/** + * 接收raft发送的数据 + */ +public interface RaftTaskHandler { + boolean invoke(final int groupId, final byte[] request, RaftClosure response) throws + HgStoreException; + + boolean invoke(final int groupId, final byte methodId, final Object req, + RaftClosure response) throws HgStoreException; +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/raft/util/RaftUtils.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/raft/util/RaftUtils.java new file mode 100644 index 000000000..f644dc1bd --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/raft/util/RaftUtils.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.raft.util; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.collections.ListUtils; + +import com.alipay.sofa.jraft.Node; +import com.alipay.sofa.jraft.conf.Configuration; + +public class RaftUtils { + public static List getAllEndpoints(Node node) { + List endPoints = new ArrayList<>(); + node.listPeers().forEach(peerId -> { + endPoints.add(peerId.getEndpoint().toString()); + }); + node.listLearners().forEach(peerId -> { + endPoints.add(peerId.getEndpoint().toString()); + }); + return endPoints; + } + + public static List getAllEndpoints(Configuration conf) { + List endPoints = new ArrayList<>(); + conf.listPeers().forEach(peerId -> { + endPoints.add(peerId.getEndpoint().toString()); + }); + conf.listLearners().forEach(peerId -> { + endPoints.add(peerId.getEndpoint().toString()); + }); + return endPoints; + } + + public static List getPeerEndpoints(Node node) { + List endPoints = new ArrayList<>(); + node.listPeers().forEach(peerId -> { + endPoints.add(peerId.getEndpoint().toString()); + }); + return endPoints; + } + + public static List getPeerEndpoints(Configuration conf) { + List endPoints = new ArrayList<>(); + conf.listPeers().forEach(peerId -> { + endPoints.add(peerId.getEndpoint().toString()); + }); + return endPoints; + } + + public static List getLearnerEndpoints(Node node) { + List endPoints = new ArrayList<>(); + node.listLearners().forEach(peerId -> { + endPoints.add(peerId.getEndpoint().toString()); + }); + return endPoints; + } + + public static List getLearnerEndpoints(Configuration conf) { + List endPoints = new ArrayList<>(); + conf.listLearners().forEach(peerId -> { + endPoints.add(peerId.getEndpoint().toString()); + }); + return endPoints; + } + + public static boolean configurationEquals(Configuration oldConf, Configuration newConf) { + return ListUtils.isEqualList(oldConf.listPeers(), newConf.listPeers()) && + ListUtils.isEqualList(oldConf.listLearners(), newConf.listLearners()); + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/snapshot/HgSnapshotHandler.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/snapshot/HgSnapshotHandler.java new file mode 100644 index 000000000..4631d4944 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/snapshot/HgSnapshotHandler.java @@ -0,0 +1,225 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.snapshot; + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.zip.Checksum; + +import org.apache.commons.io.FileUtils; +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.store.PartitionEngine; +import org.apache.hugegraph.store.business.BusinessHandler; +import org.apache.hugegraph.store.meta.Partition; +import org.apache.hugegraph.store.util.HgStoreException; + +import com.alipay.sofa.jraft.entity.LocalFileMetaOutter; +import com.alipay.sofa.jraft.storage.snapshot.Snapshot; +import com.alipay.sofa.jraft.storage.snapshot.SnapshotReader; +import com.alipay.sofa.jraft.storage.snapshot.SnapshotWriter; +import com.alipay.sofa.jraft.util.CRC64; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class HgSnapshotHandler { + + private static final String SHOULD_NOT_LOAD = "should_not_load"; + private static final String SNAPSHOT_DATA_PATH = "data"; + + private final PartitionEngine partitionEngine; + private final BusinessHandler businessHandler; + + public HgSnapshotHandler(PartitionEngine partitionEngine) { + this.partitionEngine = partitionEngine; + this.businessHandler = partitionEngine.getStoreEngine().getBusinessHandler(); + } + + public static String trimStartPath(String str, String prefix) { + if (!prefix.endsWith(File.separator)) { + prefix = prefix + File.separator; + } + if (str.startsWith(prefix)) { + return (str.substring(prefix.length())); + } + return str; + } + + public static void findFileList(File dir, File rootDir, List files) { + if (!dir.exists() || !dir.isDirectory()) { + return; + } + File[] fs = dir.listFiles(); + if (fs != null) { + for (File f : fs) { + if (f.isFile()) { + files.add(trimStartPath(dir.getPath(), rootDir.getPath()) + File.separator + + f.getName()); + } else { + findFileList(f, rootDir, files); + } + } + } + } + + public Map getPartitions() { + return partitionEngine.getPartitions(); + } + + /** + * create rocksdb checkpoint + */ + public void onSnapshotSave(final SnapshotWriter writer) throws HgStoreException { + final String snapshotDir = writer.getPath(); + + if (partitionEngine != null) { + // rocks db snapshot + final String graphSnapshotDir = snapshotDir + File.separator + SNAPSHOT_DATA_PATH; + businessHandler.saveSnapshot(graphSnapshotDir, "", partitionEngine.getGroupId()); + + List files = new ArrayList<>(); + File dir = new File(graphSnapshotDir); + File rootDirFile = new File(writer.getPath()); + // add all files in data dir + findFileList(dir, rootDirFile, files); + + // load snapshot by learner ?? + for (String file : files) { + String checksum = calculateChecksum(writer.getPath() + File.separator + file); + if (checksum.length() != 0) { + LocalFileMetaOutter.LocalFileMeta meta = + LocalFileMetaOutter.LocalFileMeta.newBuilder() + .setChecksum(checksum) + .build(); + writer.addFile(file, meta); + } else { + writer.addFile(file); + } + } + // should_not_load wound not sync to learner + markShouldNotLoad(writer, true); + } + } + + private String calculateChecksum(String path) { + // only calculate .sst and .log(wal file) file + final String emptyString = ""; + if (path.endsWith(".sst") || path.endsWith(".log")) { + final int maxFullCheckLength = 8192; + final int checkLength = 4096; + try { + File file = new File(path); + long length = file.length(); + Checksum checksum = new CRC64(); + try (final RandomAccessFile raf = new RandomAccessFile(file, "r")) { + byte[] buf = new byte[checkLength]; + if (length <= maxFullCheckLength) { + int totalReadLen = 0; + while (totalReadLen < length) { + int readLen = raf.read(buf); + checksum.update(buf, 0, readLen); + totalReadLen += readLen; + } + } else { + // head + int readLen = raf.read(buf); + checksum.update(buf, 0, readLen); + // tail + raf.seek(length - checkLength); + readLen = raf.read(buf); + checksum.update(buf, 0, readLen); + } + } + // final checksum = crc checksum + file length + return Long.toHexString(checksum.getValue()) + "_" + Long.toHexString(length); + } catch (IOException e) { + log.error("Failed to calculateChecksum for file {}. {}", path, e); + return emptyString; + } + } else { + return emptyString; + } + } + + public void onSnapshotLoad(final SnapshotReader reader, long committedIndex) throws + HgStoreException { + final String snapshotDir = reader.getPath(); + + // 本地保存的快照没必要加载 + if (shouldNotLoad(reader)) { + log.info("skip to load snapshot because of should_not_load flag"); + return; + } + + // 直接使用 snapshot + final String graphSnapshotDir = snapshotDir + File.separator + SNAPSHOT_DATA_PATH; + log.info("Raft {} begin loadSnapshot, {}", partitionEngine.getGroupId(), graphSnapshotDir); + businessHandler.loadSnapshot(graphSnapshotDir, "", partitionEngine.getGroupId(), + committedIndex); + log.info("Raft {} end loadSnapshot.", partitionEngine.getGroupId()); + + for (Metapb.Partition snapPartition : partitionEngine.loadPartitionsFromLocalDb()) { + log.info("onSnapshotLoad loaded partition from local db. Partition: {}", snapPartition); + partitionEngine.loadPartitionFromSnapshot(new Partition(snapPartition)); + + Partition partition = partitionEngine.getPartition(snapPartition.getGraphName()); + if (partition == null) { + log.warn("skip to load snapshot for {}-{}, it is not belong to this node", + snapPartition.getGraphName(), snapPartition.getId()); + continue; + } + + var taskManager = partitionEngine.getTaskManager(); + // async tasks + for (var task : taskManager.scanAsyncTasks(partitionEngine.getGroupId(), + snapPartition.getGraphName())) { + task.handleTask(); + } + } + + // mark snapshot has been loaded + markShouldNotLoad(reader, false); + } + + private boolean shouldNotLoad(final Snapshot snapshot) { + String shouldNotLoadPath = getShouldNotLoadPath(snapshot); + return new File(shouldNotLoadPath).exists(); + } + + private void markShouldNotLoad(final Snapshot snapshot, boolean saveSnapshot) { + String shouldNotLoadPath = getShouldNotLoadPath(snapshot); + try { + FileUtils.writeStringToFile(new File(shouldNotLoadPath), + saveSnapshot ? "saved snapshot" : "loaded snapshot", + Charset.defaultCharset()); + } catch (IOException e) { + log.error("Failed to create snapshot should not load flag file {}. {}", + shouldNotLoadPath, e); + } + } + + private String getShouldNotLoadPath(final Snapshot snapshot) { + return snapshot.getPath() + File.separator + SHOULD_NOT_LOAD; + } + +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/Asserts.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/Asserts.java new file mode 100644 index 000000000..816adc8e4 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/Asserts.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.util; + + +public final class Asserts { + public static void isTrue(boolean expression, String message) { + if (message == null) { + throw new IllegalArgumentException("message is null"); + } + if (!expression) { + throw new IllegalArgumentException(message); + } + } + + public static void isFalse(boolean expression, String message) { + isTrue(!expression, message); + } + + + public static boolean isInvalid(String... strs) { + if (strs == null || strs.length == 0) { + return true; + } + for (String item : strs) { + if (item == null || "".equals(item.trim())) { + return true; + } + } + return false; + } + + public static T isNonNull(T obj) { + if (obj == null) { + throw new NullPointerException(); + } + return obj; + } + + public static T isNonNull(T obj, String message) { + if (obj == null) { + throw new NullPointerException(message); + } + return obj; + } + + +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/CopyOnWriteCache.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/CopyOnWriteCache.java new file mode 100644 index 000000000..0606d08b3 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/CopyOnWriteCache.java @@ -0,0 +1,150 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.util; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +//TODO: refer license later, 74% match, maybe refer to kafka (0.8.1) +public class CopyOnWriteCache implements ConcurrentMap { + + ScheduledExecutorService scheduledExecutor; + private volatile Map map; + + public CopyOnWriteCache(long effectiveTime) { + this.map = Collections.emptyMap(); + scheduledExecutor = Executors.newScheduledThreadPool(1); + scheduledExecutor.scheduleWithFixedDelay(() -> { + this.clear(); + }, effectiveTime, effectiveTime, TimeUnit.MILLISECONDS); + } + + @Override + public boolean containsKey(Object k) { + return map.containsKey(k); + } + + @Override + public boolean containsValue(Object v) { + return map.containsValue(v); + } + + @Override + public Set> entrySet() { + return map.entrySet(); + } + + @Override + public V get(Object k) { + return map.get(k); + } + + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + @Override + public Set keySet() { + return map.keySet(); + } + + @Override + public int size() { + return map.size(); + } + + @Override + public Collection values() { + return map.values(); + } + + @Override + public synchronized void clear() { + this.map = Collections.emptyMap(); + } + + @Override + public synchronized V put(K k, V v) { + Map copy = new HashMap<>(this.map); + V prev = copy.put(k, v); + this.map = Collections.unmodifiableMap(copy); + return prev; + } + + @Override + public synchronized void putAll(Map entries) { + Map copy = new HashMap<>(this.map); + copy.putAll(entries); + this.map = Collections.unmodifiableMap(copy); + } + + @Override + public synchronized V remove(Object key) { + Map copy = new HashMap<>(this.map); + V prev = copy.remove(key); + this.map = Collections.unmodifiableMap(copy); + return prev; + } + + @Override + public synchronized V putIfAbsent(K k, V v) { + if (!containsKey(k)) { + return put(k, v); + } else { + return get(k); + } + } + + @Override + public synchronized boolean remove(Object k, Object v) { + if (containsKey(k) && get(k).equals(v)) { + remove(k); + return true; + } else { + return false; + } + } + + @Override + public synchronized boolean replace(K k, V original, V replacement) { + if (containsKey(k) && get(k).equals(original)) { + put(k, replacement); + return true; + } else { + return false; + } + } + + @Override + public synchronized V replace(K k, V v) { + if (containsKey(k)) { + return put(k, v); + } else { + return null; + } + } + +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/FutureClosure.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/FutureClosure.java new file mode 100644 index 000000000..c51900454 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/FutureClosure.java @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.util; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alipay.sofa.jraft.Closure; +import com.alipay.sofa.jraft.Status; +import com.alipay.sofa.jraft.error.RaftError; + +public class FutureClosure implements Closure, Future { + private static final Logger LOG = LoggerFactory.getLogger(FutureClosure.class); + + private final CountDownLatch latch; + private Status status; + + public FutureClosure() { + this(1); + } + + public FutureClosure(int count) { + this.latch = new CountDownLatch(count); + } + + public static void waitLatch(CountDownLatch latch) { + try { + latch.await(); + } catch (InterruptedException e) { + LOG.error("{}", e); + } + } + + @Override + public void run(Status status) { + this.status = status; + latch.countDown(); + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + return true; + } + + @Override + public boolean isCancelled() { + return false; + } + + @Override + public boolean isDone() { + return false; + } + + @Override + public Status get() { + try { + latch.await(); + } catch (InterruptedException e) { + status = new Status(RaftError.EINTR, e.getMessage()); + } + return status; + } + + @Override + public Status get(long timeout, TimeUnit unit) { + try { + latch.await(timeout, unit); + } catch (InterruptedException e) { + status = new Status(RaftError.EINTR, e.getMessage()); + } + return status; + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/HgRaftError.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/HgRaftError.java new file mode 100644 index 000000000..7a933a09c --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/HgRaftError.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.util; + +import java.util.HashMap; +import java.util.Map; + +import com.alipay.sofa.jraft.Status; + +public enum HgRaftError { + UNKNOWN(-1, "unknown"), + OK(0, "OK"), + NOT_LEADER(20000, "This partition is not leader"), + WAIT_LEADER_TIMEOUT(20001, "Waiting for leader timeout"), + NOT_LOCAL(20002, "This partition is not local"), + CLUSTER_NOT_READY(20003, "The cluster is not ready, please check active stores number!"), + + TASK_CONTINUE(21000, "Task is continue"), + TASK_ERROR(21001, "Task is error, need to retry"), + END(30000, "HgStore error is end"); + + private static final Map RAFT_ERROR_MAP = new HashMap<>(); + + static { + for (final HgRaftError error : HgRaftError.values()) { + RAFT_ERROR_MAP.put(error.getNumber(), error); + } + } + + private final int value; + + private final String msg; + + HgRaftError(final int value, final String msg) { + this.value = value; + this.msg = msg; + } + + public static HgRaftError forNumber(final int value) { + return RAFT_ERROR_MAP.getOrDefault(value, UNKNOWN); + } + + public final int getNumber() { + return this.value; + } + + public final String getMsg() { + return this.msg; + } + + public Status toStatus() { + return new Status(value, msg); + } +} + diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/HgStoreConst.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/HgStoreConst.java new file mode 100644 index 000000000..af285e3e0 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/HgStoreConst.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.util; + +import java.util.Collections; +import java.util.List; + +/** + * created on 2021/10/22 + */ +public final class HgStoreConst { + public final static byte[] EMPTY_BYTES = new byte[0]; + + public static final List EMPTY_LIST = Collections.EMPTY_LIST; + + public final static int SCAN_ALL_PARTITIONS_ID = -1; // means scan all partitions. + + private HgStoreConst() { + } + +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/HgStoreException.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/HgStoreException.java new file mode 100644 index 000000000..8ebe3053a --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/HgStoreException.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.util; + +/** + * @projectName: hugegraph-store + * @package: org.apache.hugegraph.store + * @className: HgCoreServiceException + * @description: TODO + * @date: 2021/10/22 9:44 + * @version: 1.0 + */ +public class HgStoreException extends RuntimeException { + public final static int EC_NOEXCEPT = 0; + public final static int EC_FAIL = 1000; + //存储的数据格式不支持 + public final static int EC_DATAFMT_NOT_SUPPORTED = 1001; + public final static int EC_RKDB_CREATE_FAIL = 1201; + public final static int EC_RKDB_DOPUT_FAIL = 1202; + public final static int EC_RKDB_DODEL_FAIL = 1203; + public final static int EC_RDKDB_DOSINGLEDEL_FAIL = 1204; + public final static int EC_RKDB_DODELPREFIX_FAIL = 1205; + public final static int EC_RKDB_DODELRANGE_FAIL = 1206; + public final static int EC_RKDB_DOMERGE_FAIL = 1207; + public final static int EC_RKDB_DOGET_FAIL = 1208; + public final static int EC_RKDB_PD_FAIL = 1209; + public final static int EC_RKDB_TRUNCATE_FAIL = 1212; + public final static int EC_RKDB_EXPORT_SNAPSHOT_FAIL = 1214; + public final static int EC_RKDB_IMPORT_SNAPSHOT_FAIL = 1215; + public final static int EC_RKDB_TRANSFER_SNAPSHOT_FAIL = 1216; + public final static int EC_METRIC_FAIL = 1401; + private static final long serialVersionUID = 5193624480997934335L; + private final int code; + + public HgStoreException() { + super(); + this.code = EC_NOEXCEPT; + } + + public HgStoreException(String message) { + super(message); + this.code = EC_FAIL; + } + + public HgStoreException(int exceptCode, String message) { + super(message); + this.code = exceptCode; + } + + public HgStoreException(int exceptCode, Throwable cause) { + super(codeToMsg(exceptCode), cause); + this.code = exceptCode; + } + + public HgStoreException(int exceptCode, String message, Object... args) { + super(String.format(message, args)); + this.code = exceptCode; + } + + public HgStoreException(String message, Throwable cause) { + super(message, cause); + this.code = EC_FAIL; + } + + public static String codeToMsg(int code) { + return "errorCode = " + code; + } + + public int getCode() { + return this.code; + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/IpUtil.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/IpUtil.java new file mode 100644 index 000000000..4040fa4ec --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/IpUtil.java @@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.util; + +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Enumeration; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Collectors; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class IpUtil { + /** + * 获取所有的ipv4 地址 + * + * @return all ipv4 addr + * @throws SocketException io error or no network interface + */ + private static List getIpAddress() throws SocketException { + List list = new LinkedList<>(); + Enumeration enumeration = NetworkInterface.getNetworkInterfaces(); + while (enumeration.hasMoreElements()) { + NetworkInterface network = (NetworkInterface) enumeration.nextElement(); + Enumeration addresses = network.getInetAddresses(); + while (addresses.hasMoreElements()) { + InetAddress address = (InetAddress) addresses.nextElement(); + if (address != null && (address instanceof Inet4Address)) { + list.add(address.getHostAddress()); + } + } + } + return list; + } + + /** + * 根据 option中的raft addr,根据本机的ip获取最相近的一个 + * + * @param raftAddress raft addr + * @return raft addr that have the nearest distance with given param + */ + public static String getNearestAddress(String raftAddress) { + try { + List ipv4s = getIpAddress(); + String[] tmp = raftAddress.split(":"); + if (ipv4s.size() == 0) { + throw new Exception("no available ipv4"); + } + + if (ipv4s.size() == 1) { + return ipv4s.get(0) + ":" + tmp[1]; + } + + var raftSeg = Arrays.stream(tmp[0].split("\\.")) + .map(s -> Integer.parseInt(s)) + .collect(Collectors.toList()); + + ipv4s.sort(Comparator.comparingLong(ip -> { + String[] ipSegments = ip.split("\\."); + long base = 256 * 256 * 256; + int i = 0; + long sum = 0; + for (String seg : ipSegments) { + sum += base * (Math.abs(raftSeg.get(i) - Integer.parseInt(seg))); + base = base / 256; + i += 1; + } + return sum; + })); + + return ipv4s.get(0) + ":" + tmp[1]; + } catch (SocketException e) { + log.error("getIpAddress, get ip failed, {}", e.getMessage()); + } catch (Exception e) { + log.error("getRaftAddress, got exception, {}", e.getMessage()); + } + return raftAddress; + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/Lifecycle.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/Lifecycle.java new file mode 100644 index 000000000..e7b646abf --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/Lifecycle.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.util; + +public interface Lifecycle { + + /** + * Initialize the service. + * + * @return true when successes. + */ + boolean init(final T opts); + + /** + * Dispose the resources for service. + */ + void shutdown(); +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/ManualResetEvent.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/ManualResetEvent.java new file mode 100644 index 000000000..1d19dcc71 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/ManualResetEvent.java @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.util; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Updated by RoshanF on 12/28/2015. + */ +public class ManualResetEvent { + + private static final Object mutex = new Object(); + private volatile CountDownLatch event; + + /** + * Initializes a new instance of the System.Threading.ManualResetEvent class + * with a Boolean value indicating whether to set the initial state to signaled. + * + * @param signalled true to set the initial state to signaled; false to set the initial state + * to nonsignaled. + */ + public ManualResetEvent(boolean signalled) { + if (signalled) { + event = new CountDownLatch(0); + } else { + event = new CountDownLatch(1); + } + } + + /** + * Sets the state of the event to signaled, allowing one or more waiting threads to proceed. + */ + public void set() { + event.countDown(); + } + + /** + * Sets the state of the event to nonsignaled, causing threads to block. + */ + public void reset() { + synchronized (mutex) { + if (event.getCount() == 0) { + event = new CountDownLatch(1); + } + } + } + + /** + * Blocks the current thread until the current wait handle receives a signal. + * + * @throws InterruptedException + */ + public void waitOne() throws InterruptedException { + event.await(); + } + + /** + * Blocks the current thread until the current wait handle receives a signal. + * + * @param timeout the maximum time to wait + * @param unit the time unit of the {@code timeout} argument + * @return {@code true} if the count reached zero and {@code false} + * if the waiting time elapsed before the count reached zero + * @throws InterruptedException if the current thread is interrupted + * while waiting + */ + public boolean waitOne(int timeout, TimeUnit unit) throws InterruptedException { + return event.await(timeout, unit); + } + + /** + * Check if the handle was signalled + * + * @return Boolean state + */ + public boolean isSignalled() { + return event.getCount() == 0; + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/PartitionMetaStoreWrapper.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/PartitionMetaStoreWrapper.java new file mode 100644 index 000000000..1d3e3debf --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/PartitionMetaStoreWrapper.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.util; + +import java.util.List; + +import org.apache.hugegraph.rocksdb.access.RocksDBSession; +import org.apache.hugegraph.store.HgStoreEngine; +import org.apache.hugegraph.store.meta.base.MetaStoreBase; + +public class PartitionMetaStoreWrapper { + + private static final InnerMetaStore store = new InnerMetaStore(); + + + public void put(int partitionId, byte[] key, byte[] value) { + store.setPartitionId(partitionId); + store.put(key, value); + } + + public T get(int partitionId, byte[] key, com.google.protobuf.Parser parser) { + store.setPartitionId(partitionId); + return store.get(parser, key); + } + + public byte[] get(int partitionId, byte[] key) { + store.setPartitionId(partitionId); + return store.get(key); + } + + public void delete(int partitionId, byte[] key) { + store.setPartitionId(partitionId); + store.delete(key); + } + + public List scan(int partitionId, com.google.protobuf.Parser parser, byte[] prefix) { + store.setPartitionId(partitionId); + return store.scan(parser, prefix); + } + + private static class InnerMetaStore extends MetaStoreBase { + + private int partitionId; + + private void setPartitionId(int partitionId) { + this.partitionId = partitionId; + } + + @Override + protected RocksDBSession getRocksDBSession() { + return HgStoreEngine.getInstance().getBusinessHandler().getSession(this.partitionId); + } + + @Override + protected String getCFName() { + return "default"; + } + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/UnsafeUtf8Util.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/UnsafeUtf8Util.java new file mode 100644 index 000000000..c2c8868a6 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/UnsafeUtf8Util.java @@ -0,0 +1,336 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.util; + +import static java.lang.Character.MAX_SURROGATE; +import static java.lang.Character.MIN_HIGH_SURROGATE; +import static java.lang.Character.MIN_LOW_SURROGATE; +import static java.lang.Character.MIN_SUPPLEMENTARY_CODE_POINT; +import static java.lang.Character.MIN_SURROGATE; +import static java.lang.Character.isSurrogatePair; +import static java.lang.Character.toCodePoint; + +import java.util.Arrays; + +/** + * @projectName: hugegraph-store + * @package: org.apache.hugegraph.store.util + * @className: UnsafeUtf8Util + * @description: + * @date: 2021/10/21 11:48 + * @version: 1.0 + */ +public class UnsafeUtf8Util { + + public static int encodedLength(CharSequence sequence) { + // Warning to maintainers: this implementation is highly optimized. + int utf16Length = sequence.length(); + int utf8Length = utf16Length; + int i = 0; + + // This loop optimizes for pure ASCII. + while (i < utf16Length && sequence.charAt(i) < 0x80) { + i++; + } + + // This loop optimizes for chars less than 0x800. + for (; i < utf16Length; i++) { + char c = sequence.charAt(i); + if (c < 0x800) { + utf8Length += ((0x7f - c) >>> 31); // branch free! + } else { + utf8Length += encodedLengthGeneral(sequence, i); + break; + } + } + + if (utf8Length < utf16Length) { + // Necessary and sufficient condition for overflow because of maximum 3x expansion + throw new IllegalArgumentException( + "UTF-8 length does not fit in int: " + (utf8Length + (1L << 32))); + } + return utf8Length; + } + + private static int encodedLengthGeneral(CharSequence sequence, int start) { + int utf16Length = sequence.length(); + int utf8Length = 0; + for (int i = start; i < utf16Length; i++) { + char c = sequence.charAt(i); + if (c < 0x800) { + utf8Length += (0x7f - c) >>> 31; // branch free! + } else { + utf8Length += 2; + // jdk7+: if (Character.isSurrogate(c)) { + if (Character.MIN_SURROGATE <= c && c <= Character.MAX_SURROGATE) { + // Check that we have a well-formed surrogate pair. + int cp = Character.codePointAt(sequence, i); + if (cp < MIN_SUPPLEMENTARY_CODE_POINT) { + throw new IllegalArgumentException( + "Unpaired surrogate at index " + i + " of " + utf16Length); + } + i++; + } + } + } + return utf8Length; + } + + public static int encodeUtf8(CharSequence in, byte[] out, int offset, int length) { + long outIx = offset; + final long outLimit = outIx + length; + final int inLimit = in.length(); + if (inLimit > length || out.length - length < offset) { + // Not even enough room for an ASCII-encoded string. + throw new ArrayIndexOutOfBoundsException( + "Failed writing " + in.charAt(inLimit - 1) + " at index " + + (offset + length)); + } + + // Designed to take advantage of + // https://wikis.oracle.com/display/HotSpotInternals/RangeCheckElimination + int inIx = 0; + for (char c; inIx < inLimit && (c = in.charAt(inIx)) < 0x80; ++inIx) { + UnsafeUtil.putByte(out, outIx++, (byte) c); + } + if (inIx == inLimit) { + // We're done, it was ASCII encoded. + return (int) outIx; + } + + for (char c; inIx < inLimit; ++inIx) { + c = in.charAt(inIx); + if (c < 0x80 && outIx < outLimit) { + UnsafeUtil.putByte(out, outIx++, (byte) c); + } else if (c < 0x800 && outIx <= outLimit - 2L) { // 11 bits, two UTF-8 bytes + UnsafeUtil.putByte(out, outIx++, (byte) ((0xF << 6) | (c >>> 6))); + UnsafeUtil.putByte(out, outIx++, (byte) (0x80 | (0x3F & c))); + } else if ((c < MIN_SURROGATE || MAX_SURROGATE < c) && outIx <= outLimit - 3L) { + // Maximum single-char code point is 0xFFFF, 16 bits, three UTF-8 bytes + UnsafeUtil.putByte(out, outIx++, (byte) ((0xF << 5) | (c >>> 12))); + UnsafeUtil.putByte(out, outIx++, (byte) (0x80 | (0x3F & (c >>> 6)))); + UnsafeUtil.putByte(out, outIx++, (byte) (0x80 | (0x3F & c))); + } else if (outIx <= outLimit - 4L) { + // Minimum code point represented by a surrogate pair is 0x10000, 17 bits, four + // UTF-8 + // bytes + final char low; + if (inIx + 1 == inLimit || !isSurrogatePair(c, (low = in.charAt(++inIx)))) { + throw new IllegalArgumentException( + "Unpaired surrogate at index " + (inIx - 1) + " of " + inLimit); + } + int codePoint = toCodePoint(c, low); + UnsafeUtil.putByte(out, outIx++, (byte) ((0xF << 4) | (codePoint >>> 18))); + UnsafeUtil.putByte(out, outIx++, (byte) (0x80 | (0x3F & (codePoint >>> 12)))); + UnsafeUtil.putByte(out, outIx++, (byte) (0x80 | (0x3F & (codePoint >>> 6)))); + UnsafeUtil.putByte(out, outIx++, (byte) (0x80 | (0x3F & codePoint))); + } else { + if ((MIN_SURROGATE <= c && c <= MAX_SURROGATE) + && (inIx + 1 == inLimit || !isSurrogatePair(c, in.charAt(inIx + 1)))) { + // We are surrogates and we're not a surrogate pair. + throw new IllegalArgumentException( + "Unpaired surrogate at index " + inIx + " of " + inLimit); + } + // Not enough space in the output buffer. + throw new ArrayIndexOutOfBoundsException( + "Failed writing " + c + " at index " + outIx); + } + } + + // All bytes have been encoded. + return (int) outIx; + } + + + public static String decodeUtf8(byte[] bytes, int index, int size) { + if ((index | size | bytes.length - index - size) < 0) { + throw new ArrayIndexOutOfBoundsException( + "buffer length=" + bytes.length + ", index=" + index + ", size=" + + size); + } + + int offset = index; + final int limit = offset + size; + + // The longest possible resulting String is the same as the number of input bytes, when + // it is + // all ASCII. For other cases, this over-allocates and we will truncate in the end. + char[] resultArr = new char[size]; + int resultPos = 0; + + // Optimize for 100% ASCII (Hotspot loves small simple top-level loops like this). + // This simple loop stops when we encounter a byte >= 0x80 (i.e. non-ASCII). + while (offset < limit) { + byte b = UnsafeUtil.getByte(bytes, offset); + if (!DecodeUtil.isOneByte(b)) { + break; + } + offset++; + DecodeUtil.handleOneByte(b, resultArr, resultPos++); + } + + while (offset < limit) { + byte byte1 = UnsafeUtil.getByte(bytes, offset++); + if (DecodeUtil.isOneByte(byte1)) { + DecodeUtil.handleOneByte(byte1, resultArr, resultPos++); + // It's common for there to be multiple ASCII characters in a run mixed in, so + // add an + // extra optimized loop to take care of these runs. + while (offset < limit) { + byte b = UnsafeUtil.getByte(bytes, offset); + if (!DecodeUtil.isOneByte(b)) { + break; + } + offset++; + DecodeUtil.handleOneByte(b, resultArr, resultPos++); + } + } else if (DecodeUtil.isTwoBytes(byte1)) { + if (offset >= limit) { + throw invalidUtf8(); + } + DecodeUtil.handleTwoBytes(byte1, /* byte2 */UnsafeUtil.getByte(bytes, offset++), + resultArr, + resultPos++); + } else if (DecodeUtil.isThreeBytes(byte1)) { + if (offset >= limit - 1) { + throw invalidUtf8(); + } + DecodeUtil.handleThreeBytes(byte1, + /* byte2 */UnsafeUtil.getByte(bytes, offset++), + /* byte3 */UnsafeUtil.getByte(bytes, offset++), resultArr, resultPos++); + } else { + if (offset >= limit - 2) { + throw invalidUtf8(); + } + DecodeUtil.handleFourBytes(byte1, + /* byte2 */UnsafeUtil.getByte(bytes, offset++), + /* byte3 */UnsafeUtil.getByte(bytes, offset++), + /* byte4 */UnsafeUtil.getByte(bytes, offset++), resultArr, resultPos++); + // 4-byte case requires two chars. + resultPos++; + } + } + + if (resultPos < resultArr.length) { + resultArr = Arrays.copyOf(resultArr, resultPos); + } + return UnsafeUtil.moveToString(resultArr); + } + + static IllegalStateException invalidUtf8() { + return new IllegalStateException("Message had invalid UTF-8."); + } + + private static class DecodeUtil { + + /** + * Returns whether this is a single-byte codepoint (i.e., ASCII) with the form '0XXXXXXX'. + */ + private static boolean isOneByte(byte b) { + return b >= 0; + } + + /** + * Returns whether this is a two-byte codepoint with the form '10XXXXXX'. + */ + private static boolean isTwoBytes(byte b) { + return b < (byte) 0xE0; + } + + /** + * Returns whether this is a three-byte codepoint with the form '110XXXXX'. + */ + private static boolean isThreeBytes(byte b) { + return b < (byte) 0xF0; + } + + private static void handleOneByte(byte byte1, char[] resultArr, int resultPos) { + resultArr[resultPos] = (char) byte1; + } + + private static void handleTwoBytes(byte byte1, byte byte2, char[] resultArr, + int resultPos) { + // Simultaneously checks for illegal trailing-byte in leading position (<= + // '11000000') and + // overlong 2-byte, '11000001'. + if (byte1 < (byte) 0xC2 || isNotTrailingByte(byte2)) { + throw invalidUtf8(); + } + resultArr[resultPos] = (char) (((byte1 & 0x1F) << 6) | trailingByteValue(byte2)); + } + + private static void handleThreeBytes(byte byte1, byte byte2, byte byte3, char[] resultArr, + int resultPos) { + if (isNotTrailingByte(byte2) + // overlong? 5 most significant bits must not all be zero + || (byte1 == (byte) 0xE0 && byte2 < (byte) 0xA0) + // check for illegal surrogate codepoints + || (byte1 == (byte) 0xED && byte2 >= (byte) 0xA0) || isNotTrailingByte(byte3)) { + throw invalidUtf8(); + } + resultArr[resultPos] = + (char) (((byte1 & 0x0F) << 12) | (trailingByteValue(byte2) << 6) | + trailingByteValue(byte3)); + } + + private static void handleFourBytes(byte byte1, byte byte2, byte byte3, byte byte4, + char[] resultArr, + int resultPos) { + if (isNotTrailingByte(byte2) + // Check that 1 <= plane <= 16. Tricky optimized form of: + // valid 4-byte leading byte? + // if (byte1 > (byte) 0xF4 || + // overlong? 4 most significant bits must not all be zero + // byte1 == (byte) 0xF0 && byte2 < (byte) 0x90 || + // codepoint larger than the highest code point (U+10FFFF)? + // byte1 == (byte) 0xF4 && byte2 > (byte) 0x8F) + || (((byte1 << 28) + (byte2 - (byte) 0x90)) >> 30) != 0 || isNotTrailingByte(byte3) + || isNotTrailingByte(byte4)) { + throw invalidUtf8(); + } + int codePoint = ((byte1 & 0x07) << 18) | (trailingByteValue(byte2) << 12) | + (trailingByteValue(byte3) << 6) + | trailingByteValue(byte4); + resultArr[resultPos] = DecodeUtil.highSurrogate(codePoint); + resultArr[resultPos + 1] = DecodeUtil.lowSurrogate(codePoint); + } + + /** + * Returns whether the byte is not a valid continuation of the form '10XXXXXX'. + */ + private static boolean isNotTrailingByte(byte b) { + return b > (byte) 0xBF; + } + + /** + * Returns the actual value of the trailing byte (removes the prefix '10') for composition. + */ + private static int trailingByteValue(byte b) { + return b & 0x3F; + } + + private static char highSurrogate(int codePoint) { + return (char) ((MIN_HIGH_SURROGATE - (MIN_SUPPLEMENTARY_CODE_POINT >>> 10)) + + (codePoint >>> 10)); + } + + private static char lowSurrogate(int codePoint) { + return (char) (MIN_LOW_SURROGATE + (codePoint & 0x3ff)); + } + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/UnsafeUtil.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/UnsafeUtil.java new file mode 100644 index 000000000..d58318531 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/UnsafeUtil.java @@ -0,0 +1,331 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.util; + +import java.lang.reflect.Field; + +import lombok.extern.slf4j.Slf4j; + +/** + * TODO: refer license later, 76% match, maybe refer to jraft-core (1.2.6) + */ +@Slf4j +public class UnsafeUtil { + private static final Object UNSAFE = getUnsafe0(); + private static final UnsafeAccessor UNSAFE_ACCESSOR = getUnsafeAccessor0(); + + private static final long BYTE_ARRAY_BASE_OFFSET = arrayBaseOffset(byte[].class); + private static final long STRING_VALUE_OFFSET = objectFieldOffset(stringValueField()); + + public static boolean hasUnsafe() { + return UNSAFE != null; + } + + public static Object getUnsafe0() { + Object unsafe; + try { + final Class unsafeClass = Class.forName("sun.misc.Unsafe"); + final Field unsafeField = unsafeClass.getDeclaredField("theUnsafe"); + unsafeField.setAccessible(true); + unsafe = unsafeField.get(null); + } catch (final Throwable t) { + if (log.isWarnEnabled()) { + log.warn("sun.misc.Unsafe.theUnsafe: unavailable.", t); + } + unsafe = null; + } + return unsafe; + } + + + public static void putByte(final byte[] target, final long index, final byte value) { + UNSAFE_ACCESSOR.putByte(target, BYTE_ARRAY_BASE_OFFSET + index, value); + } + + public static byte getByte(final byte[] target, final long index) { + return UNSAFE_ACCESSOR.getByte(target, BYTE_ARRAY_BASE_OFFSET + index); + } + + public static int arrayBaseOffset(final Class clazz) { + return hasUnsafe() ? UNSAFE_ACCESSOR.arrayBaseOffset(clazz) : -1; + } + + public static String moveToString(final char[] chars) { + if (STRING_VALUE_OFFSET == -1) { + // In the off-chance that this JDK does not implement String as we'd expect, just do + // a copy. + return new String(chars); + } + final String str; + try { + str = (String) UNSAFE_ACCESSOR.allocateInstance(String.class); + } catch (final InstantiationException e) { + // This should never happen, but return a copy as a fallback just in case. + return new String(chars); + } + UNSAFE_ACCESSOR.putObject(str, STRING_VALUE_OFFSET, chars); + return str; + } + + public static long objectFieldOffset(final Field field) { + return field == null || hasUnsafe() ? UNSAFE_ACCESSOR.objectFieldOffset(field) : -1; + } + + private static Field stringValueField() { + return field(String.class, "value", char[].class); + } + + private static Field field(final Class clazz, final String fieldName, + final Class expectedType) { + Field field; + try { + field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + if (!field.getType().equals(expectedType)) { + return null; + } + } catch (final Throwable t) { + // Failed to access the fields. + field = null; + } + return field; + } + + private static UnsafeAccessor getUnsafeAccessor0() { + return hasUnsafe() ? new UnsafeAccessor(UNSAFE) : null; + } + + public static class UnsafeAccessor { + + private final sun.misc.Unsafe unsafe; + + public UnsafeAccessor(Object unsafe) { + this.unsafe = (sun.misc.Unsafe) unsafe; + } + + /** + * Returns the {@link sun.misc.Unsafe}'s instance. + */ + public sun.misc.Unsafe getUnsafe() { + return unsafe; + } + + public byte getByte(final Object target, final long offset) { + return this.unsafe.getByte(target, offset); + } + + public void putByte(final Object target, final long offset, final byte value) { + this.unsafe.putByte(target, offset, value); + } + + public short getShort(final Object target, final long offset) { + return this.unsafe.getShort(target, offset); + } + + public void putShort(final Object target, final long offset, final short value) { + this.unsafe.putShort(target, offset, value); + } + + public int getInt(final Object target, final long offset) { + return this.unsafe.getInt(target, offset); + } + + public void putInt(final Object target, final long offset, final int value) { + this.unsafe.putInt(target, offset, value); + } + + public long getLong(final Object target, final long offset) { + return this.unsafe.getLong(target, offset); + } + + public void putLong(final Object target, final long offset, final long value) { + this.unsafe.putLong(target, offset, value); + } + + public boolean getBoolean(final Object target, final long offset) { + return this.unsafe.getBoolean(target, offset); + } + + public void putBoolean(final Object target, final long offset, final boolean value) { + this.unsafe.putBoolean(target, offset, value); + } + + public float getFloat(final Object target, final long offset) { + return this.unsafe.getFloat(target, offset); + } + + public void putFloat(final Object target, final long offset, final float value) { + this.unsafe.putFloat(target, offset, value); + } + + public double getDouble(final Object target, final long offset) { + return this.unsafe.getDouble(target, offset); + } + + public void putDouble(final Object target, final long offset, final double value) { + this.unsafe.putDouble(target, offset, value); + } + + public Object getObject(final Object target, final long offset) { + return this.unsafe.getObject(target, offset); + } + + public void putObject(final Object target, final long offset, final Object value) { + this.unsafe.putObject(target, offset, value); + } + + public byte getByte(final long address) { + return this.unsafe.getByte(address); + } + + public void putByte(final long address, final byte value) { + this.unsafe.putByte(address, value); + } + + public short getShort(final long address) { + return this.unsafe.getShort(address); + } + + public void putShort(final long address, final short value) { + this.unsafe.putShort(address, value); + } + + public int getInt(final long address) { + return this.unsafe.getInt(address); + } + + public void putInt(final long address, final int value) { + this.unsafe.putInt(address, value); + } + + public long getLong(final long address) { + return this.unsafe.getLong(address); + } + + public void putLong(final long address, final long value) { + this.unsafe.putLong(address, value); + } + + public void copyMemory(final Object srcBase, final long srcOffset, final Object dstBase, + final long dstOffset, + final long bytes) { + this.unsafe.copyMemory(srcBase, srcOffset, dstBase, dstOffset, bytes); + } + + public void copyMemory(final long srcAddress, final long dstAddress, final long bytes) { + this.unsafe.copyMemory(srcAddress, dstAddress, bytes); + } + + public byte getByteVolatile(final Object target, final long offset) { + return this.unsafe.getByteVolatile(target, offset); + } + + public void putByteVolatile(final Object target, final long offset, final byte value) { + this.unsafe.putByteVolatile(target, offset, value); + } + + public short getShortVolatile(final Object target, final long offset) { + return this.unsafe.getShortVolatile(target, offset); + } + + public void putShortVolatile(final Object target, final long offset, final short value) { + this.unsafe.putShortVolatile(target, offset, value); + } + + public int getIntVolatile(final Object target, final long offset) { + return this.unsafe.getIntVolatile(target, offset); + } + + public void putIntVolatile(final Object target, final long offset, final int value) { + this.unsafe.putIntVolatile(target, offset, value); + } + + public long getLongVolatile(final Object target, final long offset) { + return this.unsafe.getLongVolatile(target, offset); + } + + public void putLongVolatile(final Object target, final long offset, final long value) { + this.unsafe.putLongVolatile(target, offset, value); + } + + public boolean getBooleanVolatile(final Object target, final long offset) { + return this.unsafe.getBooleanVolatile(target, offset); + } + + public void putBooleanVolatile(final Object target, final long offset, + final boolean value) { + this.unsafe.putBooleanVolatile(target, offset, value); + } + + public float getFloatVolatile(final Object target, final long offset) { + return this.unsafe.getFloatVolatile(target, offset); + } + + public void putFloatVolatile(final Object target, final long offset, final float value) { + this.unsafe.putFloatVolatile(target, offset, value); + } + + public double getDoubleVolatile(final Object target, final long offset) { + return this.unsafe.getDoubleVolatile(target, offset); + } + + public void putDoubleVolatile(final Object target, final long offset, final double value) { + this.unsafe.putDoubleVolatile(target, offset, value); + } + + public Object getObjectVolatile(final Object target, final long offset) { + return this.unsafe.getObjectVolatile(target, offset); + } + + public void putObjectVolatile(final Object target, final long offset, final Object value) { + this.unsafe.putObjectVolatile(target, offset, value); + } + + /** + * Reports the offset of the first element in the storage allocation of a + * given array class. + */ + public int arrayBaseOffset(final Class clazz) { + return this.unsafe != null ? this.unsafe.arrayBaseOffset(clazz) : -1; + } + + /** + * Reports the scale factor for addressing elements in the storage + * allocation of a given array class. + */ + public int arrayIndexScale(final Class clazz) { + return this.unsafe != null ? this.unsafe.arrayIndexScale(clazz) : -1; + } + + /** + * Returns the offset of the provided field, or {@code -1} if {@code sun.misc.Unsafe} is not + * available. + */ + public long objectFieldOffset(final Field field) { + return field == null || this.unsafe == null ? -1 : this.unsafe.objectFieldOffset(field); + } + + public Object allocateInstance(final Class clazz) throws InstantiationException { + return this.unsafe.allocateInstance(clazz); + } + + public void throwException(final Throwable t) { + this.unsafe.throwException(t); + } + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/Version.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/Version.java new file mode 100644 index 000000000..7193e223a --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/Version.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.util; + +import java.io.InputStream; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class Version { + + private static String version = ""; + + /** + * 软件版本号 + */ + public static String getVersion() { + if (version.isEmpty()) { + try (InputStream is = Version.class.getResourceAsStream("/version.txt")) { + byte[] buf = new byte[64]; + int len = is.read(buf); + version = new String(buf, 0, len); + } catch (Exception e) { + log.error("Version.getVersion exception: ", e); + } + } + return version; + } + + /** + * 存储格式版本号 + */ + public static int getDataFmtVersion() { + return 1; + } +} diff --git a/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/ZipUtils.java b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/ZipUtils.java new file mode 100644 index 000000000..d226dc863 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/util/ZipUtils.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.util; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.zip.CheckedInputStream; +import java.util.zip.CheckedOutputStream; +import java.util.zip.Checksum; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.output.NullOutputStream; + +import lombok.extern.slf4j.Slf4j; + +//TODO: refer license later, 64% match, maybe refer to jraft-rheakv-core? ensure it later +@Slf4j +public final class ZipUtils { + + public static void compress(final String rootDir, final String sourceDir, + final String outputFile, final Checksum checksum) throws + IOException { + try (final FileOutputStream fos = new FileOutputStream(outputFile); + final CheckedOutputStream cos = new CheckedOutputStream(fos, checksum); + final ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(cos))) { + ZipUtils.compressDirectoryToZipFile(rootDir, sourceDir, zos); + zos.flush(); + fos.getFD().sync(); + } + } + + private static void compressDirectoryToZipFile(final String rootDir, final String sourceDir, + final ZipOutputStream zos) throws IOException { + final String dir = Paths.get(rootDir, sourceDir).toString(); + final File[] files = new File(dir).listFiles(); + for (final File file : files) { + final String child = Paths.get(sourceDir, file.getName()).toString(); + if (file.isDirectory()) { + compressDirectoryToZipFile(rootDir, child, zos); + } else { + zos.putNextEntry(new ZipEntry(child)); + try (final FileInputStream fis = new FileInputStream(file); + final BufferedInputStream bis = new BufferedInputStream(fis)) { + IOUtils.copy(bis, zos); + } + } + } + } + + public static void decompress(final String sourceFile, final String outputDir, + final Checksum checksum) throws IOException { + try (final FileInputStream fis = new FileInputStream(sourceFile); + final CheckedInputStream cis = new CheckedInputStream(fis, checksum); + final ZipInputStream zis = new ZipInputStream(new BufferedInputStream(cis))) { + ZipEntry entry; + while ((entry = zis.getNextEntry()) != null) { + final String fileName = entry.getName(); + final File entryFile = new File(Paths.get(outputDir, fileName).toString()); + FileUtils.forceMkdir(entryFile.getParentFile()); + try (final FileOutputStream fos = new FileOutputStream(entryFile); + final BufferedOutputStream bos = new BufferedOutputStream(fos)) { + IOUtils.copy(zis, bos); + bos.flush(); + fos.getFD().sync(); + } + } + IOUtils.copy(cis, NullOutputStream.NULL_OUTPUT_STREAM); + } + } +} diff --git a/hugegraph-store/hg-store-core/src/test/java/org/apache/hugegraph/store/HgBusinessImplTest.java b/hugegraph-store/hg-store-core/src/test/java/org/apache/hugegraph/store/HgBusinessImplTest.java new file mode 100644 index 000000000..6d1200a85 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/test/java/org/apache/hugegraph/store/HgBusinessImplTest.java @@ -0,0 +1,210 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CountDownLatch; + +import org.apache.hugegraph.rocksdb.access.RocksDBFactory; +import org.apache.hugegraph.rocksdb.access.RocksDBSession; +import org.apache.hugegraph.rocksdb.access.ScanIterator; +import org.apache.hugegraph.store.business.BusinessHandler; +import org.apache.hugegraph.store.business.BusinessHandlerImpl; +import org.apache.hugegraph.store.meta.PartitionManager; +import org.apache.hugegraph.store.options.HgStoreEngineOptions; +import org.apache.hugegraph.store.options.RaftRocksdbOptions; +import org.apache.hugegraph.store.pd.FakePdServiceProvider; +import org.apache.hugegraph.store.pd.PdProvider; +import org.apache.hugegraph.util.Bytes; +import org.junit.Assert; + +public class HgBusinessImplTest { + static String GRAPH_NAME = "graph_test"; + static String[] GRAPH_TABLE = {"table1", "table2"}; + + static String dbPath = "tmp/junit"; + + // @BeforeClass + public static void init() { + UnitTestBase.deleteDir(new File(dbPath)); + Map configMap = new HashMap<>(); + configMap.put("rocksdb.write_buffer_size", "1048576"); + + RaftRocksdbOptions.initRocksdbGlobalConfig(configMap); + BusinessHandlerImpl.initRocksdb(configMap, null); + } + + public BusinessHandler getBusinessHandler() { + + int partitionCount = 2; + HgStoreEngineOptions options = new HgStoreEngineOptions() {{ + setDataPath(dbPath); + setFakePdOptions(new HgStoreEngineOptions.FakePdOptions() {{ + setPartitionCount(partitionCount); + setPeersList("127.0.0.1"); + setStoreList("127.0.0.1"); + + }}); + }}; + + PdProvider pdProvider = new FakePdServiceProvider(options.getFakePdOptions()); + PartitionManager partitionManager = new PartitionManager(pdProvider, options) { + @Override + public List getLeaderPartitionIds(String graph) { + List ids = new ArrayList<>(); + for (int i = 0; i < partitionCount; i++) { + ids.add(i); + } + return ids; + } + }; + BusinessHandler handler = new BusinessHandlerImpl(partitionManager); + + return handler; + } + + // @Test + public void testPut() { + String graph1 = "test-graph11"; + String graph2 = "test-graph12"; + String table = "test"; + BusinessHandler handler = getBusinessHandler(); + handler.doPut(graph1, 0, table, "key1".getBytes(), "value1".getBytes()); + handler.doPut(graph1, 0xF, table, "key2".getBytes(), "value1".getBytes()); + handler.doPut(graph1, 0xFF, table, "key3".getBytes(), "value1".getBytes()); + handler.doPut(graph1, 0xFFF, table, "key4".getBytes(), "value1".getBytes()); + + handler.doPut(graph2, 0, table, "key21".getBytes(), "value1".getBytes()); + handler.doPut(graph2, 0xF, table, "key22".getBytes(), "value1".getBytes()); + handler.doPut(graph2, 0xFF, table, "key23".getBytes(), "value1".getBytes()); + handler.doPut(graph2, 0xFFF, table, "key24".getBytes(), "value1".getBytes()); + + System.out.println("--------------------dump all -------"); + dump(handler, graph1, 0); + System.out.println("--------------------dump scan 0 0xff -------"); + ScanIterator iterator = handler.scan(graph1, table, 0, 0xff); + int count = 0; + while (iterator.hasNext()) { + RocksDBSession.BackendColumn entry = iterator.next(); + System.out.println(new String(entry.name) + " -- " + Bytes.toHex(entry.name)); + count++; + } + + Assert.assertEquals(2, count); + + System.out.println("--------------------dump scan prefix -------"); + iterator = handler.scanPrefix(graph1, 0, table, "key".getBytes()); + + count = 0; + while (iterator.hasNext()) { + RocksDBSession.BackendColumn entry = iterator.next(); + System.out.println(new String(entry.name) + " -- " + Bytes.toHex(entry.name)); + count++; + } + + Assert.assertEquals(4, count); + System.out.println("--------------------dump scan range key1 key4 -------"); + iterator = handler.scan(graph1, 0, table, "key1".getBytes(), "key4".getBytes(), + ScanIterator.Trait.SCAN_LT_END); + count = 0; + while (iterator.hasNext()) { + RocksDBSession.BackendColumn entry = iterator.next(); + System.out.println(new String(entry.name) + " -- " + Bytes.toHex(entry.name)); + count++; + } + + Assert.assertEquals(3, count); + } + + // @Test + public void testLoadSnapshot() throws InterruptedException { + + BusinessHandler handler = getBusinessHandler(); + String graph1 = "test-graph1"; + String graph2 = "test-graph2"; + String table = "test"; + for (int i = 0; i < 256; i++) { + handler.doPut(graph1, i, table, ("key" + i).getBytes(), "value1".getBytes()); + } + ScanIterator iterator = handler.scanAll(graph1, table); + + System.out.println(iterator.count()); + + String snapshotPath; + try (RocksDBSession session = handler.getSession(0)) { + snapshotPath = session.getDbPath(); + } + + handler.closeAll(); + + System.out.println("start loadSnapshot"); + handler.loadSnapshot(snapshotPath, graph1, 0, 10); + iterator = handler.scanAll(graph1, table); + Assert.assertEquals(255, iterator.count()); + try (RocksDBSession session = handler.getSession(0)) { + System.out.println(session.getDbPath()); + } + + CountDownLatch latch = new CountDownLatch(1); + RocksDBFactory.getInstance() + .addRocksdbChangedListener(new RocksDBFactory.RocksdbChangedListener() { + @Override + public void onCompacted(String dbName) { + RocksDBFactory.RocksdbChangedListener.super.onCompacted(dbName); + } + + @Override + public void onDBDeleteBegin(String dbName, String filePath) { + RocksDBFactory.RocksdbChangedListener.super.onDBDeleteBegin(dbName, + filePath); + } + + @Override + public void onDBDeleted(String dbName, String filePath) { + latch.countDown(); + } + + @Override + public void onDBSessionReleased(RocksDBSession dbSession) { + RocksDBFactory.RocksdbChangedListener.super.onDBSessionReleased( + dbSession); + } + }); + latch.await(); + + } + + public void dump(BusinessHandler handler, String graph, int partId) { + ScanIterator cfIterator = handler.scanRaw(graph, partId, 0); + while (cfIterator.hasNext()) { + try (ScanIterator iterator = cfIterator.next()) { + byte[] cfName = cfIterator.position(); + System.out.println(graph + "-" + +partId + "-" + new String(cfName) + "--------"); + while (iterator.hasNext()) { + RocksDBSession.BackendColumn col = iterator.next(); + System.out.println(new String(col.name) + " -- " + Bytes.toHex(col.name)); + } + } + } + cfIterator.close(); + } +} diff --git a/hugegraph-store/hg-store-core/src/test/java/org/apache/hugegraph/store/PartitionEngineTest.java b/hugegraph-store/hg-store-core/src/test/java/org/apache/hugegraph/store/PartitionEngineTest.java new file mode 100644 index 000000000..69f5d8a19 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/test/java/org/apache/hugegraph/store/PartitionEngineTest.java @@ -0,0 +1,236 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; + +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.store.cmd.HgCmdClient; +import org.apache.hugegraph.store.meta.Partition; +import org.junit.Assert; + +import com.alipay.sofa.jraft.option.RpcOptions; + +public class PartitionEngineTest { + + // @Test + public void test() { + String[] peers = new String[]{"1", "2", "6"}; + String[] learners = new String[]{"3", "7"}; + List oldPeers1 = Arrays.asList("1", "2", "3"); + List oldLearner1 = Arrays.asList("4", "5"); + + + List oldPeers = + oldPeers1.stream().map(peer -> peer).collect(Collectors.toList()); + oldPeers.addAll( + oldLearner1.stream().map(peer -> peer).collect(Collectors.toList())); + + List addedNodes = Arrays.stream(peers).filter(peer -> + !oldPeers.stream() + .map(e -> e) + .collect( + Collectors.toSet()) + .contains(peer)) + .collect(Collectors.toList()); + addedNodes.addAll(Arrays.stream(learners).filter(peer -> + !oldPeers.stream() + .map(e -> e) + .collect( + Collectors.toSet()) + .contains(peer)) + .collect(Collectors.toList())); + + List removedNodes = oldPeers.stream().filter(peer -> + !Arrays.stream(peers) + .map(e -> e).collect( + Collectors.toSet()) + .contains(peer)) + .collect(Collectors.toList()); + removedNodes = removedNodes.stream().filter(peer -> + !Arrays.stream(learners).map(e -> e) + .collect(Collectors.toSet()) + .contains(peer)) + .collect(Collectors.toList()); + List mixedPeer = oldPeers1.stream().filter(peer -> + Arrays.stream(learners) + .map(e -> e).collect( + Collectors.toSet()) + .contains(peer)) + .collect(Collectors.toList()); + + // 新增 6、7 + Assert.assertEquals(2, addedNodes.size()); + addedNodes.clear(); + addedNodes.addAll(Arrays.asList(peers)); + addedNodes.addAll(Arrays.asList(learners)); + addedNodes.removeAll(oldPeers); + Assert.assertEquals(2, addedNodes.size()); + + addedNodes.forEach(s -> System.out.print(s + " ")); + System.out.println(); + // 删除 4,5 + Assert.assertEquals(2, removedNodes.size()); + + removedNodes.clear(); + removedNodes.addAll(oldPeers); + removedNodes.removeAll(Arrays.asList(peers)); + removedNodes.removeAll(Arrays.asList(learners)); + Assert.assertEquals(2, removedNodes.size()); + removedNodes.forEach(s -> System.out.print(s + " ")); + System.out.println(); + // 交集 5 + Assert.assertEquals(1, mixedPeer.size()); + oldPeers1.removeAll(Arrays.asList(learners)); + Assert.assertEquals(1, oldPeers1.size()); + mixedPeer.forEach(s -> System.out.print(s + " ")); + + } + + + // @Test + public void testPartition() { + Partition p = new Partition(); + p.setId(1); + List shards = new ArrayList<>(); + shards.add(Metapb.Shard.newBuilder().build()); + // p.setShardsList(shards); + + Partition p2 = p.clone(); + p.setId(2); + Assert.assertNotEquals(p2.getId(), p.getId()); + + } + + // @Test + public void testUpdateShardsList() { + List curShards = new ArrayList<>(); + curShards.add(Metapb.Shard.newBuilder().setStoreId(1001).setRole(Metapb.ShardRole.Follower) + .build()); + curShards.add(Metapb.Shard.newBuilder().setStoreId(1002).setRole(Metapb.ShardRole.Leader) + .build()); + curShards.add(Metapb.Shard.newBuilder().setStoreId(1003).setRole(Metapb.ShardRole.Follower) + .build()); + + List reqShards = new ArrayList<>(); + reqShards.add(Metapb.Shard.newBuilder().setStoreId(1001).setRole(Metapb.ShardRole.Leader) + .build()); + reqShards.add(Metapb.Shard.newBuilder().setStoreId(1002).setRole(Metapb.ShardRole.Leader) + .build()); + reqShards.add(Metapb.Shard.newBuilder().setStoreId(1004).setRole(Metapb.ShardRole.Leader) + .build()); + + + long leaderStoreId = 0; + for (Metapb.Shard shard : curShards) { + if (shard.getRole() == Metapb.ShardRole.Leader) { + leaderStoreId = shard.getStoreId(); + break; + } + } + + // remove + List shards = curShards.stream().filter(shard -> + reqShards.stream() + .map(Metapb.Shard::getStoreId) + .collect( + Collectors.toSet()) + .contains( + shard.getStoreId())) + .collect(Collectors.toList()); + + // add + List addShards = reqShards.stream().filter(shard -> + !curShards.stream() + .map(Metapb.Shard::getStoreId) + .collect( + Collectors.toSet()) + .contains( + shard.getStoreId())) + .collect(Collectors.toList()); + shards.addAll(addShards); + + // change leader + for (Metapb.Shard shard : shards) { + if (shard.getStoreId() == leaderStoreId) { + shard.toBuilder().setRole(Metapb.ShardRole.Leader).build(); + } else { + shard.toBuilder().setRole(Metapb.ShardRole.Follower).build(); + } + } + + } + + // @Test + public void testPriority() { + List oldPeers = new ArrayList<>(); + oldPeers.add("127.0.0.1:8001::100"); + oldPeers.add("127.0.0.1:8002::75"); + oldPeers.add("127.0.0.1:8003::50"); + + List peers = new ArrayList<>(); + peers.add("127.0.0.1:8001"); + peers.add("127.0.0.1:8003::60"); + peers.add("127.0.0.1:8004"); + + List priPeers = new ArrayList<>(); + for (String peer : peers) { + if (peer.contains("::")) { + priPeers.add(peer); + System.out.println(peer); + } else { + boolean find = false; + for (String oldPeer : oldPeers) { + if (oldPeer.contains(peer + "::")) { + find = true; + priPeers.add(oldPeer); + System.out.println(oldPeer); + break; + } + } + if (!find) { + priPeers.add(peer); + System.out.println(peer); + } + } + } + + + } + + // @Test + public void testRpcCall() throws ExecutionException, InterruptedException { + HgCmdClient client = new HgCmdClient(); + client.init(new RpcOptions(), null); + ArrayList ps = new ArrayList<>(); + Metapb.Partition p = Metapb.Partition.newBuilder() + .setGraphName("OK") + .setId(1) + // .addShards(Metapb.Shard.newBuilder().setStoreId + // (1).build()) + .build(); + ps.add(new Partition(p)); + client.createRaftNode("127.0.0.1:8511", ps, status -> { + System.out.println(status); + }).get(); + } +} diff --git a/hugegraph-store/hg-store-core/src/test/java/org/apache/hugegraph/store/UnitTestBase.java b/hugegraph-store/hg-store-core/src/test/java/org/apache/hugegraph/store/UnitTestBase.java new file mode 100644 index 000000000..890516ab2 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/test/java/org/apache/hugegraph/store/UnitTestBase.java @@ -0,0 +1,99 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +import org.apache.hugegraph.rocksdb.access.RocksDBFactory; +import org.apache.hugegraph.rocksdb.access.RocksDBSession; +import org.apache.hugegraph.store.business.BusinessHandler; +import org.apache.hugegraph.store.business.BusinessHandlerImpl; +import org.apache.hugegraph.store.meta.PartitionManager; +import org.apache.hugegraph.store.options.HgStoreEngineOptions; +import org.apache.hugegraph.store.options.RaftRocksdbOptions; +import org.apache.hugegraph.store.pd.FakePdServiceProvider; +import org.apache.hugegraph.store.pd.PdProvider; + +public class UnitTestBase { + + public final static RocksDBFactory factory = RocksDBFactory.getInstance(); + private String dbPath; + + private BusinessHandler handler; + + public static boolean deleteDir(File dir) { + if (dir.isDirectory()) { + for (File file : dir.listFiles()) { + deleteDir(file); + } + } + return dir.delete(); + } + + public void initDB(String dbPath) { + this.dbPath = dbPath; + UnitTestBase.deleteDir(new File(dbPath)); + Map configMap = new HashMap<>(); + configMap.put("rocksdb.write_buffer_size", "1048576"); + configMap.put("rocksdb.bloom_filter_bits_per_key", "10"); + + RaftRocksdbOptions.initRocksdbGlobalConfig(configMap); + BusinessHandlerImpl.initRocksdb(configMap, null); + + + } + + protected BusinessHandler getBusinessHandler() { + if (handler == null) { + synchronized (this) { + if (handler == null) { + int partitionCount = 2; + HgStoreEngineOptions options = new HgStoreEngineOptions() {{ + setDataPath(dbPath); + setFakePdOptions(new HgStoreEngineOptions.FakePdOptions() {{ + setPartitionCount(partitionCount); + setPeersList("127.0.0.1"); + setStoreList("127.0.0.1"); + + }}); + }}; + + PdProvider pdProvider = new FakePdServiceProvider(options.getFakePdOptions()); + PartitionManager partitionManager = new PartitionManager(pdProvider, options); + BusinessHandler handler = new BusinessHandlerImpl(partitionManager); + } + } + } + + return handler; + } + + public RocksDBSession getDBSession(String dbName) { + RocksDBSession session = factory.queryGraphDB(dbName); + if (session == null) { + session = factory.createGraphDB(dbPath, dbName); + } + return session; + } + + public void close() { + handler.closeAll(); + } +} diff --git a/hugegraph-store/hg-store-core/src/test/java/org/apache/hugegraph/store/meta/GraphIDManagerTest.java b/hugegraph-store/hg-store-core/src/test/java/org/apache/hugegraph/store/meta/GraphIDManagerTest.java new file mode 100644 index 000000000..e20273907 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/test/java/org/apache/hugegraph/store/meta/GraphIDManagerTest.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.meta; + +import java.io.File; + +import org.apache.hugegraph.pd.common.PDException; +import org.apache.hugegraph.rocksdb.access.RocksDBSession; +import org.apache.hugegraph.store.UnitTestBase; +import org.apache.hugegraph.store.meta.base.DBSessionBuilder; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class GraphIDManagerTest extends UnitTestBase { + @Before + public void init() { + String dbPath = "/tmp/junit"; + deleteDir(new File(dbPath)); + super.initDB(dbPath); + } + + @Test + public void test() throws PDException { + GraphIdManager.maxGraphID = 64; + int max = GraphIdManager.maxGraphID; + try (RocksDBSession session = getDBSession("test")) { + GraphIdManager gid = new GraphIdManager(new DBSessionBuilder() { + @Override + public RocksDBSession getSession(int partId) { + return session.clone(); + } + }, 0); + for (int i = 0; i < max; i++) { + Assert.assertEquals(i, gid.getCId("Test", max)); + } + + Assert.assertEquals(-1, gid.getCId("Test", max)); + + gid.delCId("Test", 3); + Assert.assertEquals(3, gid.getCId("Test", max)); + Assert.assertEquals(-1, gid.getCId("Test", max)); + + long start = System.currentTimeMillis(); + for (int i = 0; i < GraphIdManager.maxGraphID; i++) { + long id = gid.getGraphId("g" + i); + Assert.assertEquals(i, id); + } + System.out.println("time is " + (System.currentTimeMillis() - start)); + { + gid.releaseGraphId("g" + 10); + long id = gid.getGraphId("g" + 10); + Assert.assertEquals(10, id); + } + start = System.currentTimeMillis(); + for (int i = 0; i < GraphIdManager.maxGraphID; i++) { + long id = gid.releaseGraphId("g" + i); + Assert.assertEquals(i, id); + } + System.out.println("time is " + (System.currentTimeMillis() - start)); + start = System.currentTimeMillis(); + for (int i = 0; i < GraphIdManager.maxGraphID; i++) { + long id = gid.getCId(GraphIdManager.GRAPH_ID_PREFIX, GraphIdManager.maxGraphID); + // long id = gid.getGraphId("g" + i); + Assert.assertTrue(id >= 0); + } + System.out.println("time is " + (System.currentTimeMillis() - start)); + } + } +} diff --git a/hugegraph-store/hg-store-core/src/test/java/org/apache/hugegraph/store/meta/GraphManagerTest.java b/hugegraph-store/hg-store-core/src/test/java/org/apache/hugegraph/store/meta/GraphManagerTest.java new file mode 100644 index 000000000..04888a21d --- /dev/null +++ b/hugegraph-store/hg-store-core/src/test/java/org/apache/hugegraph/store/meta/GraphManagerTest.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.meta; + + +import org.junit.Assert; +// import org.junit.Test; + +public class GraphManagerTest { + // @Test + public void testCloneGraph() { + Graph graph = new Graph(); + graph.setGraphName("test1"); + + + Graph graph1 = graph.clone(); + + Assert.assertNotSame(graph, graph1); + + Assert.assertEquals(graph.getGraphName(), graph1.getGraphName()); + graph1.setGraphName("test4"); + + + Assert.assertNotEquals(graph.getGraphName(), graph1.getGraphName()); + + Assert.assertEquals(graph.getGraphName(), "test1"); + + } + +} diff --git a/hugegraph-store/hg-store-core/src/test/java/org/apache/hugegraph/store/util/CopyOnWriteCacheTest.java b/hugegraph-store/hg-store-core/src/test/java/org/apache/hugegraph/store/util/CopyOnWriteCacheTest.java new file mode 100644 index 000000000..29483cbd6 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/test/java/org/apache/hugegraph/store/util/CopyOnWriteCacheTest.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.util; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Map; + +import com.caucho.hessian.io.Hessian2Input; +import com.caucho.hessian.io.Hessian2Output; + +public class CopyOnWriteCacheTest { + + // @Test + public void testCache() throws InterruptedException { + Map cache = new CopyOnWriteCache<>(1000); + cache.put("1", "1"); + Thread.sleep(2000); + Asserts.isTrue(!cache.containsKey("1"), "cache do not clear"); + } + + // @Test + public void test() { + + byte[] bytes; + try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) { + long[] l = new long[]{1, 2}; + Hessian2Output output = new Hessian2Output(bos); + output.writeObject(l); + output.flush(); + bytes = bos.toByteArray(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes)) { + Hessian2Input input = new Hessian2Input(bis); + long[] obj = (long[]) input.readObject(); + input.close(); + + for (long l : obj) { + System.out.println(l); + } + + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/hugegraph-store/hg-store-core/src/test/java/org/apache/hugegraph/store/util/FutureClosureTest.java b/hugegraph-store/hg-store-core/src/test/java/org/apache/hugegraph/store/util/FutureClosureTest.java new file mode 100644 index 000000000..d29683967 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/test/java/org/apache/hugegraph/store/util/FutureClosureTest.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.util; + +import org.junit.Assert; +import org.junit.Test; + +import com.alipay.sofa.jraft.Status; + +public class FutureClosureTest { + @Test + public void test() { + FutureClosure closure = new FutureClosure(); + new Thread(() -> { + try { + Thread.sleep(1000); + closure.run(Status.OK()); + } catch (InterruptedException e) { + closure.run(new Status(-1, e.getMessage())); + } + + }).start(); + + Assert.assertEquals(closure.get().getCode(), Status.OK().getCode()); + Assert.assertEquals(closure.get().getCode(), Status.OK().getCode()); + Assert.assertEquals(closure.get().getCode(), Status.OK().getCode()); + } +} diff --git a/hugegraph-store/hg-store-core/src/test/resources/log4j2.xml b/hugegraph-store/hg-store-core/src/test/resources/log4j2.xml new file mode 100644 index 000000000..8c7912b45 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/test/resources/log4j2.xml @@ -0,0 +1,101 @@ + + + + + + + + logs + hg-store-node + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hugegraph-store/hg-store-core/src/test/resources/version.txt b/hugegraph-store/hg-store-core/src/test/resources/version.txt new file mode 100644 index 000000000..b55f10804 --- /dev/null +++ b/hugegraph-store/hg-store-core/src/test/resources/version.txt @@ -0,0 +1 @@ +3.6.5 \ No newline at end of file diff --git a/hugegraph-store/hg-store-dist/pom.xml b/hugegraph-store/hg-store-dist/pom.xml new file mode 100644 index 000000000..44c6aabb5 --- /dev/null +++ b/hugegraph-store/hg-store-dist/pom.xml @@ -0,0 +1,152 @@ + + + + + + + org.apache.hugegraph + hugegraph-store-root + ${revision} + ../pom.xml + + 4.0.0 + + hg-store-dist + + + + ${project.basedir}/../dist + bash + ${project.basedir}/src/assembly + ${assembly.dir}/descriptor + ${assembly.dir}/static + hugegraph-store-${revision} + + + + org.apache.hugegraph + hugegraph-store + ${revision} + + + + + + maven-assembly-plugin + 2.4 + + + assembly-hugegraph-store + package + + single + + + false + false + ${dist.dir} + + + ${assembly.descriptor.dir}/server-assembly.xml + + + ${final.name} + + + + + + maven-clean-plugin + + + + ${dist.dir} + + + + + + initialize + + clean + + + + + + maven-antrun-plugin + 1.3 + + + package + + run + + + + + + + + + tar zcvf \ + ${dist.dir}/${final.name}.tar.gz \ + ${final.name} || exit 1 + + rm -f ${dist.dir}/dist.sh + echo + echo "HugeGraph dist tar.gz available at: + ${dist.dir}/${final.name}.tar.gz" + echo + + + + + + + + + + + + + + + + + + ant-contrib + ant-contrib + 1.0b3 + + + ant + ant + + + + + + + + + \ No newline at end of file diff --git a/hugegraph-store/hg-store-dist/release-docs/LICENSE b/hugegraph-store/hg-store-dist/release-docs/LICENSE new file mode 100644 index 000000000..58d78a52c --- /dev/null +++ b/hugegraph-store/hg-store-dist/release-docs/LICENSE @@ -0,0 +1,216 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +============================================================================ + APACHE HUGEGRAPH (Incubating) SUBCOMPONENTS: + + The Apache HugeGraph(Incubating) project contains subcomponents with separate copyright + notices and license terms. Your use of the source code for the these + subcomponents is subject to the terms and conditions of the following + licenses. + +======================================================================== +Apache 2.0 licenses +======================================================================== + +The following file are provided under the Apache 2.0 License. + hugegraph-xxxx \ No newline at end of file diff --git a/hugegraph-store/hg-store-dist/release-docs/NOTICE b/hugegraph-store/hg-store-dist/release-docs/NOTICE new file mode 100644 index 000000000..67f995276 --- /dev/null +++ b/hugegraph-store/hg-store-dist/release-docs/NOTICE @@ -0,0 +1,7 @@ +Apache HugeGraph(incubating) +Copyright 2022-2023 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). + +The initial codebase was donated to the ASF by HugeGraph Authors, copyright 2017-2021. \ No newline at end of file diff --git a/hugegraph-store/hg-store-dist/src/assembly/descriptor/server-assembly.xml b/hugegraph-store/hg-store-dist/src/assembly/descriptor/server-assembly.xml new file mode 100644 index 000000000..44001cffb --- /dev/null +++ b/hugegraph-store/hg-store-dist/src/assembly/descriptor/server-assembly.xml @@ -0,0 +1,59 @@ + + + + distribution + false + + + dir + + + + + + + ${assembly.static.dir}/bin + bin + + * + + 755 + + + ${assembly.static.dir}/conf + conf + + * + + + + + + + + /lib + false + runtime + false + + org.apache.hugegraph:hugegraph-store:jar:* + + + + + \ No newline at end of file diff --git a/hugegraph-store/hg-store-dist/src/assembly/static/bin/libjemalloc.so b/hugegraph-store/hg-store-dist/src/assembly/static/bin/libjemalloc.so new file mode 100644 index 000000000..768688dbf Binary files /dev/null and b/hugegraph-store/hg-store-dist/src/assembly/static/bin/libjemalloc.so differ diff --git a/hugegraph-store/hg-store-dist/src/assembly/static/bin/libjemalloc_aarch64.so b/hugegraph-store/hg-store-dist/src/assembly/static/bin/libjemalloc_aarch64.so new file mode 100644 index 000000000..f6fc60642 Binary files /dev/null and b/hugegraph-store/hg-store-dist/src/assembly/static/bin/libjemalloc_aarch64.so differ diff --git a/hugegraph-store/hg-store-dist/src/assembly/static/bin/restart-hugegraph-store.sh b/hugegraph-store/hg-store-dist/src/assembly/static/bin/restart-hugegraph-store.sh new file mode 100644 index 000000000..085ddad43 --- /dev/null +++ b/hugegraph-store/hg-store-dist/src/assembly/static/bin/restart-hugegraph-store.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to You under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +function abs_path() { + SOURCE="${BASH_SOURCE[0]}" + while [ -h "$SOURCE" ]; do + DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" + SOURCE="$(readlink "$SOURCE")" + [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" + done + echo "$( cd -P "$( dirname "$SOURCE" )" && pwd )" +} + +BIN=$(abs_path) +. "$BIN"/stop-hugegraph-store.sh +. "$BIN"/start-hugegraph-store.sh \ No newline at end of file diff --git a/hugegraph-store/hg-store-dist/src/assembly/static/bin/start-hugegraph-store.sh b/hugegraph-store/hg-store-dist/src/assembly/static/bin/start-hugegraph-store.sh new file mode 100644 index 000000000..242c41791 --- /dev/null +++ b/hugegraph-store/hg-store-dist/src/assembly/static/bin/start-hugegraph-store.sh @@ -0,0 +1,166 @@ +#!/bin/bash + +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to You under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +function abs_path() { + SOURCE="${BASH_SOURCE[0]}" + while [ -h "$SOURCE" ]; do + DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" + SOURCE="$(readlink "$SOURCE")" + [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" + done + echo "$( cd -P "$( dirname "$SOURCE" )" && pwd )" +} + +BIN=$(abs_path) +TOP="$(cd "$BIN"/../ && pwd)" +CONF="$TOP/conf" +LIB="$TOP/lib" +LOGS="$TOP/logs" +OUTPUT=${LOGS}/hugegraph-store-server.log +PID_FILE="$BIN/pid" +arch=$(arch) + +# TODO: repalce it with uname -a? +echo "Current arch: ", ${arch} +#if [[ $arch =~ "aarch64" ]];then +# export LD_PRELOAD="$TOP/bin/libjemalloc_aarch64.so" +#else +export LD_PRELOAD="$TOP/bin/libjemalloc.so" +#fi + +##pd/store max user processes, ulimit -u +# Reduce the maximum number of processes that can be opened by a normal dev/user +export PROC_LIMITN=1024 +#export PROC_LIMITN=20480 +##pd/store open files, ulimit -n +export FILE_LIMITN=1024 +#export FILE_LIMITN=1024000 + +function check_evn_limit() { + local limit_check=$(ulimit -n) + if [ ${limit_check} -lt ${FILE_LIMITN} ]; then + echo -e "${BASH_SOURCE[0]##*/}:${LINENO}:\E[1;32m ulimit -n 可以打开的最大文件描述符数太少,需要(${FILE_LIMITN})!! \E[0m" + return 1 + fi + limit_check=$(ulimit -u) + if [ ${limit_check} -lt ${PROC_LIMITN} ]; then + echo -e "${BASH_SOURCE[0]##*/}:${LINENO}:\E[1;32m ulimit -u 用户最大可用的进程数太少,需要(${PROC_LIMITN})!! \E[0m" + return 2 + fi + return 0 +} + +check_evn_limit +if [ $? != 0 ]; then + exit 8 +fi + +if [ -z "$GC_OPTION" ];then + GC_OPTION="" +fi +if [ -z "$USER_OPTION" ];then + USER_OPTION="" +fi + +while getopts "g:j:v" arg; do + case ${arg} in + g) GC_OPTION="$OPTARG" ;; + j) USER_OPTION="$OPTARG" ;; + v) VERBOSE="verbose" ;; + ?) echo "USAGE: $0 [-g g1] [-j xxx] [-v]" && exit 1 ;; + esac +done + + + + +. "$BIN"/util.sh + +mkdir -p "${LOGS}" + +# The maximum and minimum heap memory that service can use (for production env set it 36GB) +MAX_MEM=$((2 * 1024)) +MIN_MEM=$((1 * 512)) +EXPECT_JDK_VERSION=11 + +# Change to $BIN's parent +cd ${TOP} || exit + +# Find Java +if [ "$JAVA_HOME" = "" ]; then + JAVA="java" +else + JAVA="$JAVA_HOME/bin/java" +fi + +# check jdk version +JAVA_VERSION=$($JAVA -version 2>&1 | awk 'NR==1{gsub(/"/,""); print $3}' | awk -F'_' '{print $1}') +if [[ $? -ne 0 || $JAVA_VERSION < $EXPECT_JDK_VERSION ]]; then + echo "Please make sure that the JDK is installed and the version >= $EXPECT_JDK_VERSION" >> ${OUTPUT} + exit 1 +fi + +# Set Java options +if [ "$JAVA_OPTIONS" = "" ]; then + XMX=$(calc_xmx $MIN_MEM $MAX_MEM) + if [ $? -ne 0 ]; then + echo "Failed to start HugeGraphStoreServer, requires at least ${MIN_MEM}m free memory" \ + >> ${OUTPUT} + exit 1 + fi + JAVA_OPTIONS="-Xms${MIN_MEM}m -Xmx${XMX}m -XX:MetaspaceSize=256M -XX:+UseG1GC -XX:+ParallelRefProcEnabled -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${LOGS} ${USER_OPTION} " + # JAVA_OPTIONS="-Xms${MIN_MEM}m -Xmx${XMX}m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${LOGS} ${USER_OPTION}" + + # Rolling out detailed GC logs + JAVA_OPTIONS="${JAVA_OPTIONS} -Xlog:gc=info:file=./logs/gc.log:tags,uptime,level:filecount=3,filesize=100m " +fi + +# Using G1GC as the default garbage collector (Recommended for large memory machines) +case "$GC_OPTION" in + g1) + echo "Using G1GC as the default garbage collector" + JAVA_OPTIONS="${JAVA_OPTIONS} -XX:+UseG1GC -XX:+ParallelRefProcEnabled \ + -XX:InitiatingHeapOccupancyPercent=50 -XX:G1RSetUpdatingPauseTimePercent=5" + ;; + "") ;; + *) + echo "Unrecognized gc option: '$GC_OPTION', only support 'g1' now" >> ${OUTPUT} + exit 1 +esac + +JVM_OPTIONS="-Dlog4j.configurationFile=${CONF}/log4j2.xml -Dfastjson.parser.safeMode=true" +#if [ "${JMX_EXPORT_PORT}" != "" ] && [ ${JMX_EXPORT_PORT} -ne 0 ] ; then +# JAVA_OPTIONS="${JAVA_OPTIONS} -javaagent:${LIB}/jmx_prometheus_javaagent-0.16.1.jar=${JMX_EXPORT_PORT}:${CONF}/jmx_exporter.yml" +#fi + +if [ $(ps -ef|grep -v grep| grep java|grep -cE ${CONF}) -ne 0 ]; then + echo "HugeGraphStoreServer is already running..." + exit 0 +fi + +echo "Starting HG-StoreServer..." + +exec ${JAVA} -Dname="HugeGraphStore" ${JVM_OPTIONS} ${JAVA_OPTIONS} -jar \ + -Dspring.config.location=${CONF}/application.yml \ + ${LIB}/hugegraph-store-*.jar >> ${OUTPUT} 2>&1 & + +PID="$!" +# Write pid to file +echo "$PID" > "$PID_FILE" +echo "[+pid] $PID" diff --git a/hugegraph-store/hg-store-dist/src/assembly/static/bin/stop-hugegraph-store.sh b/hugegraph-store/hg-store-dist/src/assembly/static/bin/stop-hugegraph-store.sh new file mode 100644 index 000000000..50d6ec9ec --- /dev/null +++ b/hugegraph-store/hg-store-dist/src/assembly/static/bin/stop-hugegraph-store.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to You under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +abs_path() { + SOURCE="${BASH_SOURCE[0]}" + while [ -h "$SOURCE" ]; do + DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" + SOURCE="$(readlink "$SOURCE")" + [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" + done + echo "$( cd -P "$( dirname "$SOURCE" )" && pwd )" +} + +BIN=$(abs_path) +TOP="$(cd $BIN/../ && pwd)" + +. "$BIN"/util.sh + +PID_FILE=$BIN/pid +SERVER_SHUTDOWN_TIMEOUT_S=30 + +if [ ! -f ${PID_FILE} ]; then + echo "The pid file $PID_FILE doesn't exist" + exit 0 +fi + +PID=`cat $PID_FILE` +kill_process_and_wait "HugeGraphStoreServer" "$PID" "$SERVER_SHUTDOWN_TIMEOUT_S" + +if [ $? -eq 0 ]; then + rm "$PID_FILE" +fi + diff --git a/hugegraph-store/hg-store-dist/src/assembly/static/bin/util.sh b/hugegraph-store/hg-store-dist/src/assembly/static/bin/util.sh new file mode 100644 index 000000000..ef09ed515 --- /dev/null +++ b/hugegraph-store/hg-store-dist/src/assembly/static/bin/util.sh @@ -0,0 +1,372 @@ +#!/bin/bash + +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to You under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +function command_available() { + local cmd=$1 + if [ `command -v $cmd >/dev/null 2>&1` ]; then + return 1 + else + return 0 + fi +} + +# read a property from .properties file +function read_property() { + # file path + file_name=$1 + # replace "." to "\." + property_name=`echo $2 | sed 's/\./\\\./g'` + cat $file_name | sed -n -e "s/^[ ]*//g;/^#/d;s/^$property_name=//p" | tail -1 +} + +function write_property() { + local file=$1 + local key=$2 + local value=$3 + + local os=`uname` + case $os in + # Note: in mac os should use sed -i '' "xxx" to replace string, + # otherwise prompt 'command c expects \ followed by text'. + # See http://www.cnblogs.com/greedy-day/p/5952899.html + Darwin) sed -i '' "s!$key=.*!$key=$value!g" "$file" ;; + *) sed -i "s!$key=.*!$key=$value!g" "$file" ;; + esac +} + +function parse_yaml() { + local file=$1 + local version=$2 + local module=$3 + + cat $file | tr -d '\n {}'| awk -F',+|:' '''{ + pre=""; + for(i=1; i<=NF; ) { + if(match($i, /version/)) { + pre=$i; + i+=1 + } else { + result[pre"-"$i] = $(i+1); + i+=2 + } + } + } END {for(e in result) {print e": "result[e]}}''' \ + | grep "$version-$module" | awk -F':' '{print $2}' | tr -d ' ' && echo +} + +function process_num() { + num=`ps -ef | grep $1 | grep -v grep | wc -l` + return $num +} + +function process_id() { + pid=`ps -ef | grep $1 | grep -v grep | awk '{print $2}'` + return $pid +} + +# check the port of rest server is occupied +function check_port() { + local port=`echo $1 | awk -F':' '{print $3}'` + if ! command_available "lsof"; then + echo "Required lsof but it is unavailable" + exit 1 + fi + lsof -i :$port >/dev/null + if [ $? -eq 0 ]; then + echo "The port $port has already been used" + exit 1 + fi +} + +function crontab_append() { + local job="$1" + crontab -l | grep -F "$job" >/dev/null 2>&1 + if [ $? -eq 0 ]; then + return 1 + fi + (crontab -l ; echo "$job") | crontab - +} + +function crontab_remove() { + local job="$1" + # check exist before remove + crontab -l | grep -F "$job" >/dev/null 2>&1 + if [ $? -eq 1 ]; then + return 0 + fi + + crontab -l | grep -Fv "$job" | crontab - + + # Check exist after remove + crontab -l | grep -F "$job" >/dev/null 2>&1 + if [ $? -eq 0 ]; then + return 1 + else + return 0 + fi +} + +# wait_for_startup friendly_name host port timeout_s +function wait_for_startup() { + local pid="$1" + local server_name="$2" + local server_url="$3" + local timeout_s="$4" + + local now_s=`date '+%s'` + local stop_s=$(( $now_s + $timeout_s )) + + local status + + echo -n "Connecting to $server_name ($server_url)" + while [ $now_s -le $stop_s ]; do + echo -n . + process_status "$server_name" "$pid" >/dev/null + if [ $? -eq 1 ]; then + echo "Starting $server_name failed" + return 1 + fi + + status=`curl -o /dev/null -s -k -w %{http_code} $server_url` + if [[ $status -eq 200 || $status -eq 401 ]]; then + echo "OK" + echo "Started [pid $pid]" + return 0 + fi + sleep 2 + now_s=`date '+%s'` + done + + echo "The operation timed out when attempting to connect to $server_url" >&2 + return 1 +} + +function free_memory() { + local free="" + local os=`uname` + if [ "$os" == "Linux" ]; then + local mem_free=`cat /proc/meminfo | grep -w "MemFree" | awk '{print $2}'` + local mem_buffer=`cat /proc/meminfo | grep -w "Buffers" | awk '{print $2}'` + local mem_cached=`cat /proc/meminfo | grep -w "Cached" | awk '{print $2}'` + if [[ "$mem_free" == "" || "$mem_buffer" == "" || "$mem_cached" == "" ]]; then + echo "Failed to get free memory" + exit 1 + fi + free=`expr $mem_free + $mem_buffer + $mem_cached` + free=`expr $free / 1024` + elif [ "$os" == "Darwin" ]; then + local pages_free=`vm_stat | awk '/Pages free/{print $0}' | awk -F'[:.]+' '{print $2}' | tr -d " "` + local pages_inactive=`vm_stat | awk '/Pages inactive/{print $0}' | awk -F'[:.]+' '{print $2}' | tr -d " "` + local pages_available=`expr $pages_free + $pages_inactive` + free=`expr $pages_available \* 4096 / 1024 / 1024` + else + echo "Unsupported operating system $os" + exit 1 + fi + echo $free +} + +function calc_xmx() { + local min_mem=$1 + local max_mem=$2 + # Get machine available memory + local free=`free_memory` + local half_free=$[free/2] + + local xmx=$min_mem + if [[ "$free" -lt "$min_mem" ]]; then + exit 1 + elif [[ "$half_free" -ge "$max_mem" ]]; then + xmx=$max_mem + elif [[ "$half_free" -lt "$min_mem" ]]; then + xmx=$min_mem + else + xmx=$half_free + fi + echo $xmx +} + +function remove_with_prompt() { + local path=$1 + local tips="" + + if [ -d "$path" ]; then + tips="Remove directory '$path' and all sub files [y/n]?" + elif [ -f "$path" ]; then + tips="Remove file '$path' [y/n]?" + else + return 0 + fi + + read -p "$tips " yn + case $yn in + [Yy]* ) rm -rf "$path";; + * ) ;; + esac +} + +function ensure_path_writable() { + local path=$1 + # Ensure input path exist + if [ ! -d "${path}" ]; then + mkdir -p ${path} + fi + # Check for write permission + if [ ! -w "${path}" ]; then + echo "No write permission on directory ${path}" + exit 1 + fi +} + +function get_ip() { + local os=`uname` + local loopback="127.0.0.1" + local ip="" + case $os in + Linux) + if command_available "ifconfig"; then + ip=`ifconfig | grep 'inet addr:' | grep -v "$loopback" | cut -d: -f2 | awk '{ print $1}'` + elif command_available "ip"; then + ip=`ip addr | grep 'state UP' -A2 | tail -n1 | awk '{print $2}' | awk -F"/" '{print $1}'` + else + ip=$loopback + fi + ;; + FreeBSD|OpenBSD|Darwin) + if command_available "ifconfig"; then + ip=`ifconfig | grep -E 'inet.[0-9]' | grep -v "$loopback" | awk '{ print $2}'` + else + ip=$loopback + fi + ;; + SunOS) + if command_available "ifconfig"; then + ip=`ifconfig -a | grep inet | grep -v "$loopback" | awk '{ print $2} '` + else + ip=$loopback + fi + ;; + *) ip=$loopback;; + esac + echo $ip +} + +function download() { + local path=$1 + local link_url=$2 + + if command_available "wget"; then + wget --help | grep -q '\--show-progress' && progress_opt="-q --show-progress" || progress_opt="" + wget ${link_url} -P ${path} $progress_opt + elif command_available "curl"; then + curl ${link_url} -o ${path}/${link_url} + else + echo "Required wget or curl but they are unavailable" + exit 1 + fi +} + +function ensure_package_exist() { + local path=$1 + local dir=$2 + local tar=$3 + local link=$4 + + if [ ! -d ${path}/${dir} ]; then + if [ ! -f ${path}/${tar} ]; then + echo "Downloading the compressed package '${tar}'" + download ${path} ${link} + if [ $? -ne 0 ]; then + echo "Failed to download, please ensure the network is available and link is valid" + exit 1 + fi + echo "[OK] Finished download" + fi + echo "Unzip the compressed package '$tar'" + tar -zxvf ${path}/${tar} -C ${path} >/dev/null 2>&1 + if [ $? -ne 0 ]; then + echo "Failed to unzip, please check the compressed package" + exit 1 + fi + echo "[OK] Finished unzip" + fi +} + +########################################################################### + +function wait_for_shutdown() { + local process_name="$1" + local pid="$2" + local timeout_s="$3" + + local now_s=`date '+%s'` + local stop_s=$(( $now_s + $timeout_s )) + + echo -n "Killing $process_name(pid $pid)" >&2 + while [ $now_s -le $stop_s ]; do + echo -n . + process_status "$process_name" "$pid" >/dev/null + if [ $? -eq 1 ]; then + echo "OK" + return 0 + fi + sleep 2 + now_s=`date '+%s'` + done + echo "$process_name shutdown timeout(exceeded $timeout_s seconds)" >&2 + return 1 +} + +function process_status() { + local process_name="$1" + local pid="$2" + + ps -p "$pid" + if [ $? -eq 0 ]; then + echo "$process_name is running with pid $pid" + return 0 + else + echo "The process $process_name does not exist" + return 1 + fi +} + +function kill_process() { + local process_name="$1" + local pid="$2" + + if [ -z "$pid" ]; then + echo "The process $pid does not exist" + return 0 + fi + + case "`uname`" in + CYGWIN*) taskkill /F /PID "$pid" ;; + *) kill "$pid" ;; + esac +} + +function kill_process_and_wait() { + local process_name="$1" + local pid="$2" + local timeout_s="$3" + + kill_process "$process_name" "$pid" + wait_for_shutdown "$process_name" "$pid" "$timeout_s" +} diff --git a/hugegraph-store/hg-store-dist/src/assembly/static/conf/application-pd.yml b/hugegraph-store/hg-store-dist/src/assembly/static/conf/application-pd.yml new file mode 100644 index 000000000..59c677d92 --- /dev/null +++ b/hugegraph-store/hg-store-dist/src/assembly/static/conf/application-pd.yml @@ -0,0 +1,17 @@ +management: + metrics: + export: + prometheus: + enabled: true + endpoints: + web: + exposure: + include: "*" + +rocksdb: + # rocksdb使用的总内存大小,达到该值强制写盘 + total_memory_size: 32000000000 + # rocksdb使用的memtable大小 + write_buffer_size: 32000000 + # 对于每个rocksdb来说,memtable个数达到该值进行写盘 + min_write_buffer_number_to_merge: 16 diff --git a/hugegraph-store/hg-store-dist/src/assembly/static/conf/application.yml b/hugegraph-store/hg-store-dist/src/assembly/static/conf/application.yml new file mode 100644 index 000000000..de9bd5df4 --- /dev/null +++ b/hugegraph-store/hg-store-dist/src/assembly/static/conf/application.yml @@ -0,0 +1,47 @@ +pdserver: + # pd 服务地址,多个 pd 地址用逗号分割 + address: localhost:8686 + +management: + metrics: + export: + prometheus: + enabled: true + endpoints: + web: + exposure: + include: "*" + +grpc: + # grpc 的服务地址 + host: 127.0.0.1 + port: 8500 + netty-server: + max-inbound-message-size: 1000MB +raft: + # raft 缓存队列大小 + disruptorBufferSize: 1024 + address: 127.0.0.1:8510 + max-log-file-size: 600000000000 + # 快照生成时间间隔,单位秒 + snapshotInterval: 1800 +server: + # rest 服务地址 + port: 8520 + +app: + # 存储路径,支持多个路径,逗号分割 + data-path: ./storage + #raft-path: ./storage + +spring: + application: + name: store-node-grpc-server + profiles: + active: default + include: pd + +logging: + config: 'file:./conf/log4j2.xml' + level: + root: info \ No newline at end of file diff --git a/hugegraph-store/hg-store-dist/src/assembly/static/conf/log4j2.xml b/hugegraph-store/hg-store-dist/src/assembly/static/conf/log4j2.xml new file mode 100644 index 000000000..0636d063d --- /dev/null +++ b/hugegraph-store/hg-store-dist/src/assembly/static/conf/log4j2.xml @@ -0,0 +1,136 @@ + + + + + + + + logs + hugegraph-store + raft-hugegraph-store + audit-hugegraph-store + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hugegraph-store/hg-store-grpc/.gitignore b/hugegraph-store/hg-store-grpc/.gitignore new file mode 100644 index 000000000..7e970ee27 --- /dev/null +++ b/hugegraph-store/hg-store-grpc/.gitignore @@ -0,0 +1 @@ +/src/main/java/ diff --git a/hugegraph-store/hg-store-grpc/build.sh b/hugegraph-store/hg-store-grpc/build.sh new file mode 100644 index 000000000..42aafffc7 --- /dev/null +++ b/hugegraph-store/hg-store-grpc/build.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to You under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +mkdir output +touch output/1 +export JAVA_HOME=$ORACLEJDK_1_8_0_HOME + +#$MAVEN_3_5_3_BIN/mvn -U deploy + +mvn -U install + diff --git a/hugegraph-store/hg-store-grpc/pom.xml b/hugegraph-store/hg-store-grpc/pom.xml new file mode 100644 index 000000000..54980831c --- /dev/null +++ b/hugegraph-store/hg-store-grpc/pom.xml @@ -0,0 +1,162 @@ + + + + + 4.0.0 + + + org.apache.hugegraph + hugegraph-store-root + ${revision} + ../pom.xml + + + hg-store-grpc + + + 11 + 11 + + 1.6.2 + 1.39.0 + 3.17.2 + 0.6.1 + + + + + + io.grpc + grpc-netty-shaded + ${grpc.version} + + + io.grpc + grpc-protobuf + ${grpc.version} + + + io.grpc + grpc-stub + ${grpc.version} + + + javax.annotation + javax.annotation-api + 1.3.2 + + + + + ${basedir}/src/main/java + + + src/main/resources + + + src/main/proto + + + + + kr.motd.maven + os-maven-plugin + ${os.plugin.version} + + + + + org.xolstice.maven.plugins + protobuf-maven-plugin + ${protobuf.plugin.version} + true + + + com.google.protobuf:protoc:${protoc.version}:exe:${os.detected.classifier} + + grpc-java + + io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier} + + + ${project.basedir}/src/main/proto + + + ${project.basedir}/src/main/java + + false + + + + + + generate-sources + + + compile + + compile-custom + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.3.0 + + + add-source + generate-sources + + add-source + + + + ${basedir}/src/main/java + ${basedir}/src/main/dev + + + + + + + maven-clean-plugin + 3.1.0 + + + + src/main/java + + + + + + + initialize + + clean + + + + + + + \ No newline at end of file diff --git a/hugegraph-store/hg-store-grpc/src/main/dev/org/apache/hugegraph/store/grpc/stream/HgStoreStreamGrpc.java b/hugegraph-store/hg-store-grpc/src/main/dev/org/apache/hugegraph/store/grpc/stream/HgStoreStreamGrpc.java new file mode 100644 index 000000000..aabed077b --- /dev/null +++ b/hugegraph-store/hg-store-grpc/src/main/dev/org/apache/hugegraph/store/grpc/stream/HgStoreStreamGrpc.java @@ -0,0 +1,560 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.grpc.stream; + +import static io.grpc.MethodDescriptor.generateFullMethodName; + +/** + *

+ **
+ *为了提升性能,复用内存,减少gc回收,需要重写KvStream.writeTo方法
+ * 
+ */ +@javax.annotation.Generated( + value = "by gRPC proto compiler (version 1.39.0)", + comments = "Source: store_stream.proto") +public final class HgStoreStreamGrpc { + + private HgStoreStreamGrpc() {} + + public static final String SERVICE_NAME = "HgStoreStream"; + + // Static method descriptors that strictly reflect the proto. + private static volatile io.grpc.MethodDescriptor getScanMethod; + + @io.grpc.stub.annotations.RpcMethod( + fullMethodName = SERVICE_NAME + '/' + "Scan", + requestType = org.apache.hugegraph.store.grpc.stream.ScanStreamReq.class, + responseType = org.apache.hugegraph.store.grpc.stream.KvPageRes.class, + methodType = io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING) + public static io.grpc.MethodDescriptor getScanMethod() { + io.grpc.MethodDescriptor getScanMethod; + if ((getScanMethod = HgStoreStreamGrpc.getScanMethod) == null) { + synchronized (HgStoreStreamGrpc.class) { + if ((getScanMethod = HgStoreStreamGrpc.getScanMethod) == null) { + HgStoreStreamGrpc.getScanMethod = getScanMethod = + io.grpc.MethodDescriptor.newBuilder() + .setType(io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING) + .setFullMethodName(generateFullMethodName(SERVICE_NAME, "Scan")) + .setSampledToLocalTracing(true) + .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + org.apache.hugegraph.store.grpc.stream.ScanStreamReq.getDefaultInstance())) + .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + org.apache.hugegraph.store.grpc.stream.KvPageRes.getDefaultInstance())) + .setSchemaDescriptor(new HgStoreStreamMethodDescriptorSupplier("Scan")) + .build(); + } + } + } + return getScanMethod; + } + + private static volatile io.grpc.MethodDescriptor getScanOneShotMethod; + + @io.grpc.stub.annotations.RpcMethod( + fullMethodName = SERVICE_NAME + '/' + "ScanOneShot", + requestType = org.apache.hugegraph.store.grpc.stream.ScanStreamReq.class, + responseType = org.apache.hugegraph.store.grpc.stream.KvPageRes.class, + methodType = io.grpc.MethodDescriptor.MethodType.UNARY) + public static io.grpc.MethodDescriptor getScanOneShotMethod() { + io.grpc.MethodDescriptor getScanOneShotMethod; + if ((getScanOneShotMethod = HgStoreStreamGrpc.getScanOneShotMethod) == null) { + synchronized (HgStoreStreamGrpc.class) { + if ((getScanOneShotMethod = HgStoreStreamGrpc.getScanOneShotMethod) == null) { + HgStoreStreamGrpc.getScanOneShotMethod = getScanOneShotMethod = + io.grpc.MethodDescriptor.newBuilder() + .setType(io.grpc.MethodDescriptor.MethodType.UNARY) + .setFullMethodName(generateFullMethodName(SERVICE_NAME, "ScanOneShot")) + .setSampledToLocalTracing(true) + .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + org.apache.hugegraph.store.grpc.stream.ScanStreamReq.getDefaultInstance())) + .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + org.apache.hugegraph.store.grpc.stream.KvPageRes.getDefaultInstance())) + .setSchemaDescriptor(new HgStoreStreamMethodDescriptorSupplier("ScanOneShot")) + .build(); + } + } + } + return getScanOneShotMethod; + } + + private static volatile io.grpc.MethodDescriptor getScanBatchMethod; + + @io.grpc.stub.annotations.RpcMethod( + fullMethodName = SERVICE_NAME + '/' + "ScanBatch", + requestType = org.apache.hugegraph.store.grpc.stream.ScanStreamBatchReq.class, + responseType = org.apache.hugegraph.store.grpc.stream.KvPageRes.class, + methodType = io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING) + public static io.grpc.MethodDescriptor getScanBatchMethod() { + io.grpc.MethodDescriptor getScanBatchMethod; + if ((getScanBatchMethod = HgStoreStreamGrpc.getScanBatchMethod) == null) { + synchronized (HgStoreStreamGrpc.class) { + if ((getScanBatchMethod = HgStoreStreamGrpc.getScanBatchMethod) == null) { + HgStoreStreamGrpc.getScanBatchMethod = getScanBatchMethod = + io.grpc.MethodDescriptor.newBuilder() + .setType(io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING) + .setFullMethodName(generateFullMethodName(SERVICE_NAME, "ScanBatch")) + .setSampledToLocalTracing(true) + .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + org.apache.hugegraph.store.grpc.stream.ScanStreamBatchReq.getDefaultInstance())) + .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + org.apache.hugegraph.store.grpc.stream.KvPageRes.getDefaultInstance())) + .setSchemaDescriptor(new HgStoreStreamMethodDescriptorSupplier("ScanBatch")) + .build(); + } + } + } + return getScanBatchMethod; + } + + private static volatile io.grpc.MethodDescriptor getScanBatch2Method; + + @io.grpc.stub.annotations.RpcMethod( + fullMethodName = SERVICE_NAME + '/' + "ScanBatch2", + requestType = org.apache.hugegraph.store.grpc.stream.ScanStreamBatchReq.class, + responseType = KvStream.class, + methodType = io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING) + public static io.grpc.MethodDescriptor getScanBatch2Method() { + io.grpc.MethodDescriptor getScanBatch2Method; + if ((getScanBatch2Method = HgStoreStreamGrpc.getScanBatch2Method) == null) { + synchronized (HgStoreStreamGrpc.class) { + if ((getScanBatch2Method = HgStoreStreamGrpc.getScanBatch2Method) == null) { + HgStoreStreamGrpc.getScanBatch2Method = getScanBatch2Method = + io.grpc.MethodDescriptor.newBuilder() + .setType(io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING) + .setFullMethodName(generateFullMethodName(SERVICE_NAME, "ScanBatch2")) + .setSampledToLocalTracing(true) + .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + org.apache.hugegraph.store.grpc.stream.ScanStreamBatchReq.getDefaultInstance())) + .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + KvStream.getDefaultInstance())) + .setSchemaDescriptor(new HgStoreStreamMethodDescriptorSupplier("ScanBatch2")) + .build(); + } + } + } + return getScanBatch2Method; + } + + private static volatile io.grpc.MethodDescriptor getScanBatchOneShotMethod; + + @io.grpc.stub.annotations.RpcMethod( + fullMethodName = SERVICE_NAME + '/' + "ScanBatchOneShot", + requestType = org.apache.hugegraph.store.grpc.stream.ScanStreamBatchReq.class, + responseType = org.apache.hugegraph.store.grpc.stream.KvPageRes.class, + methodType = io.grpc.MethodDescriptor.MethodType.UNARY) + public static io.grpc.MethodDescriptor getScanBatchOneShotMethod() { + io.grpc.MethodDescriptor getScanBatchOneShotMethod; + if ((getScanBatchOneShotMethod = HgStoreStreamGrpc.getScanBatchOneShotMethod) == null) { + synchronized (HgStoreStreamGrpc.class) { + if ((getScanBatchOneShotMethod = HgStoreStreamGrpc.getScanBatchOneShotMethod) == null) { + HgStoreStreamGrpc.getScanBatchOneShotMethod = getScanBatchOneShotMethod = + io.grpc.MethodDescriptor.newBuilder() + .setType(io.grpc.MethodDescriptor.MethodType.UNARY) + .setFullMethodName(generateFullMethodName(SERVICE_NAME, "ScanBatchOneShot")) + .setSampledToLocalTracing(true) + .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + org.apache.hugegraph.store.grpc.stream.ScanStreamBatchReq.getDefaultInstance())) + .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller( + org.apache.hugegraph.store.grpc.stream.KvPageRes.getDefaultInstance())) + .setSchemaDescriptor(new HgStoreStreamMethodDescriptorSupplier("ScanBatchOneShot")) + .build(); + } + } + } + return getScanBatchOneShotMethod; + } + + /** + * Creates a new async stub that supports all call types for the service + */ + public static HgStoreStreamStub newStub(io.grpc.Channel channel) { + io.grpc.stub.AbstractStub.StubFactory factory = + new io.grpc.stub.AbstractStub.StubFactory() { + @java.lang.Override + public HgStoreStreamStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new HgStoreStreamStub(channel, callOptions); + } + }; + return HgStoreStreamStub.newStub(factory, channel); + } + + /** + * Creates a new blocking-style stub that supports unary and streaming output calls on the service + */ + public static HgStoreStreamBlockingStub newBlockingStub( + io.grpc.Channel channel) { + io.grpc.stub.AbstractStub.StubFactory factory = + new io.grpc.stub.AbstractStub.StubFactory() { + @java.lang.Override + public HgStoreStreamBlockingStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new HgStoreStreamBlockingStub(channel, callOptions); + } + }; + return HgStoreStreamBlockingStub.newStub(factory, channel); + } + + /** + * Creates a new ListenableFuture-style stub that supports unary calls on the service + */ + public static HgStoreStreamFutureStub newFutureStub( + io.grpc.Channel channel) { + io.grpc.stub.AbstractStub.StubFactory factory = + new io.grpc.stub.AbstractStub.StubFactory() { + @java.lang.Override + public HgStoreStreamFutureStub newStub(io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new HgStoreStreamFutureStub(channel, callOptions); + } + }; + return HgStoreStreamFutureStub.newStub(factory, channel); + } + + /** + *
+   **
+   *为了提升性能,复用内存,减少gc回收,需要重写KvStream.writeTo方法
+   * 
+ */ + public static abstract class HgStoreStreamImplBase implements io.grpc.BindableService { + + /** + */ + public io.grpc.stub.StreamObserver scan( + io.grpc.stub.StreamObserver responseObserver) { + return io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall(getScanMethod(), responseObserver); + } + + /** + */ + public void scanOneShot(org.apache.hugegraph.store.grpc.stream.ScanStreamReq request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getScanOneShotMethod(), responseObserver); + } + + /** + */ + public io.grpc.stub.StreamObserver scanBatch( + io.grpc.stub.StreamObserver responseObserver) { + return io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall(getScanBatchMethod(), responseObserver); + } + + /** + */ + public io.grpc.stub.StreamObserver scanBatch2( + io.grpc.stub.StreamObserver responseObserver) { + return io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall(getScanBatch2Method(), responseObserver); + } + + /** + */ + public void scanBatchOneShot(org.apache.hugegraph.store.grpc.stream.ScanStreamBatchReq request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getScanBatchOneShotMethod(), responseObserver); + } + + @java.lang.Override public final io.grpc.ServerServiceDefinition bindService() { + return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor()) + .addMethod( + getScanMethod(), + io.grpc.stub.ServerCalls.asyncBidiStreamingCall( + new MethodHandlers< + org.apache.hugegraph.store.grpc.stream.ScanStreamReq, + org.apache.hugegraph.store.grpc.stream.KvPageRes>( + this, METHODID_SCAN))) + .addMethod( + getScanOneShotMethod(), + io.grpc.stub.ServerCalls.asyncUnaryCall( + new MethodHandlers< + org.apache.hugegraph.store.grpc.stream.ScanStreamReq, + org.apache.hugegraph.store.grpc.stream.KvPageRes>( + this, METHODID_SCAN_ONE_SHOT))) + .addMethod( + getScanBatchMethod(), + io.grpc.stub.ServerCalls.asyncBidiStreamingCall( + new MethodHandlers< + org.apache.hugegraph.store.grpc.stream.ScanStreamBatchReq, + org.apache.hugegraph.store.grpc.stream.KvPageRes>( + this, METHODID_SCAN_BATCH))) + .addMethod( + getScanBatch2Method(), + io.grpc.stub.ServerCalls.asyncBidiStreamingCall( + new MethodHandlers< + org.apache.hugegraph.store.grpc.stream.ScanStreamBatchReq, + KvStream>( + this, METHODID_SCAN_BATCH2))) + .addMethod( + getScanBatchOneShotMethod(), + io.grpc.stub.ServerCalls.asyncUnaryCall( + new MethodHandlers< + org.apache.hugegraph.store.grpc.stream.ScanStreamBatchReq, + org.apache.hugegraph.store.grpc.stream.KvPageRes>( + this, METHODID_SCAN_BATCH_ONE_SHOT))) + .build(); + } + } + + /** + *
+   **
+   *为了提升性能,复用内存,减少gc回收,需要重写KvStream.writeTo方法
+   * 
+ */ + public static final class HgStoreStreamStub extends io.grpc.stub.AbstractAsyncStub { + private HgStoreStreamStub( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + super(channel, callOptions); + } + + @java.lang.Override + protected HgStoreStreamStub build( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new HgStoreStreamStub(channel, callOptions); + } + + /** + */ + public io.grpc.stub.StreamObserver scan( + io.grpc.stub.StreamObserver responseObserver) { + return io.grpc.stub.ClientCalls.asyncBidiStreamingCall( + getChannel().newCall(getScanMethod(), getCallOptions()), responseObserver); + } + + /** + */ + public void scanOneShot(org.apache.hugegraph.store.grpc.stream.ScanStreamReq request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ClientCalls.asyncUnaryCall( + getChannel().newCall(getScanOneShotMethod(), getCallOptions()), request, responseObserver); + } + + /** + */ + public io.grpc.stub.StreamObserver scanBatch( + io.grpc.stub.StreamObserver responseObserver) { + return io.grpc.stub.ClientCalls.asyncBidiStreamingCall( + getChannel().newCall(getScanBatchMethod(), getCallOptions()), responseObserver); + } + + /** + */ + public io.grpc.stub.StreamObserver scanBatch2( + io.grpc.stub.StreamObserver responseObserver) { + return io.grpc.stub.ClientCalls.asyncBidiStreamingCall( + getChannel().newCall(getScanBatch2Method(), getCallOptions()), responseObserver); + } + + /** + */ + public void scanBatchOneShot(org.apache.hugegraph.store.grpc.stream.ScanStreamBatchReq request, + io.grpc.stub.StreamObserver responseObserver) { + io.grpc.stub.ClientCalls.asyncUnaryCall( + getChannel().newCall(getScanBatchOneShotMethod(), getCallOptions()), request, responseObserver); + } + } + + /** + *
+   **
+   *为了提升性能,复用内存,减少gc回收,需要重写KvStream.writeTo方法
+   * 
+ */ + public static final class HgStoreStreamBlockingStub extends io.grpc.stub.AbstractBlockingStub { + private HgStoreStreamBlockingStub( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + super(channel, callOptions); + } + + @java.lang.Override + protected HgStoreStreamBlockingStub build( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new HgStoreStreamBlockingStub(channel, callOptions); + } + + /** + */ + public org.apache.hugegraph.store.grpc.stream.KvPageRes scanOneShot(org.apache.hugegraph.store.grpc.stream.ScanStreamReq request) { + return io.grpc.stub.ClientCalls.blockingUnaryCall( + getChannel(), getScanOneShotMethod(), getCallOptions(), request); + } + + /** + */ + public org.apache.hugegraph.store.grpc.stream.KvPageRes scanBatchOneShot(org.apache.hugegraph.store.grpc.stream.ScanStreamBatchReq request) { + return io.grpc.stub.ClientCalls.blockingUnaryCall( + getChannel(), getScanBatchOneShotMethod(), getCallOptions(), request); + } + } + + /** + *
+   **
+   *为了提升性能,复用内存,减少gc回收,需要重写KvStream.writeTo方法
+   * 
+ */ + public static final class HgStoreStreamFutureStub extends io.grpc.stub.AbstractFutureStub { + private HgStoreStreamFutureStub( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + super(channel, callOptions); + } + + @java.lang.Override + protected HgStoreStreamFutureStub build( + io.grpc.Channel channel, io.grpc.CallOptions callOptions) { + return new HgStoreStreamFutureStub(channel, callOptions); + } + + /** + */ + public com.google.common.util.concurrent.ListenableFuture scanOneShot( + org.apache.hugegraph.store.grpc.stream.ScanStreamReq request) { + return io.grpc.stub.ClientCalls.futureUnaryCall( + getChannel().newCall(getScanOneShotMethod(), getCallOptions()), request); + } + + /** + */ + public com.google.common.util.concurrent.ListenableFuture scanBatchOneShot( + org.apache.hugegraph.store.grpc.stream.ScanStreamBatchReq request) { + return io.grpc.stub.ClientCalls.futureUnaryCall( + getChannel().newCall(getScanBatchOneShotMethod(), getCallOptions()), request); + } + } + + private static final int METHODID_SCAN_ONE_SHOT = 0; + private static final int METHODID_SCAN_BATCH_ONE_SHOT = 1; + private static final int METHODID_SCAN = 2; + private static final int METHODID_SCAN_BATCH = 3; + private static final int METHODID_SCAN_BATCH2 = 4; + + private static final class MethodHandlers implements + io.grpc.stub.ServerCalls.UnaryMethod, + io.grpc.stub.ServerCalls.ServerStreamingMethod, + io.grpc.stub.ServerCalls.ClientStreamingMethod, + io.grpc.stub.ServerCalls.BidiStreamingMethod { + private final HgStoreStreamImplBase serviceImpl; + private final int methodId; + + MethodHandlers(HgStoreStreamImplBase serviceImpl, int methodId) { + this.serviceImpl = serviceImpl; + this.methodId = methodId; + } + + @java.lang.Override + @java.lang.SuppressWarnings("unchecked") + public void invoke(Req request, io.grpc.stub.StreamObserver responseObserver) { + switch (methodId) { + case METHODID_SCAN_ONE_SHOT: + serviceImpl.scanOneShot((org.apache.hugegraph.store.grpc.stream.ScanStreamReq) request, + (io.grpc.stub.StreamObserver) responseObserver); + break; + case METHODID_SCAN_BATCH_ONE_SHOT: + serviceImpl.scanBatchOneShot((org.apache.hugegraph.store.grpc.stream.ScanStreamBatchReq) request, + (io.grpc.stub.StreamObserver) responseObserver); + break; + default: + throw new AssertionError(); + } + } + + @java.lang.Override + @java.lang.SuppressWarnings("unchecked") + public io.grpc.stub.StreamObserver invoke( + io.grpc.stub.StreamObserver responseObserver) { + switch (methodId) { + case METHODID_SCAN: + return (io.grpc.stub.StreamObserver) serviceImpl.scan( + (io.grpc.stub.StreamObserver) responseObserver); + case METHODID_SCAN_BATCH: + return (io.grpc.stub.StreamObserver) serviceImpl.scanBatch( + (io.grpc.stub.StreamObserver) responseObserver); + case METHODID_SCAN_BATCH2: + return (io.grpc.stub.StreamObserver) serviceImpl.scanBatch2( + (io.grpc.stub.StreamObserver) responseObserver); + default: + throw new AssertionError(); + } + } + } + + private static abstract class HgStoreStreamBaseDescriptorSupplier + implements io.grpc.protobuf.ProtoFileDescriptorSupplier, io.grpc.protobuf.ProtoServiceDescriptorSupplier { + HgStoreStreamBaseDescriptorSupplier() {} + + @java.lang.Override + public com.google.protobuf.Descriptors.FileDescriptor getFileDescriptor() { + return HgStoreStreamProto.getDescriptor(); + } + + @java.lang.Override + public com.google.protobuf.Descriptors.ServiceDescriptor getServiceDescriptor() { + return getFileDescriptor().findServiceByName("HgStoreStream"); + } + } + + private static final class HgStoreStreamFileDescriptorSupplier + extends HgStoreStreamBaseDescriptorSupplier { + HgStoreStreamFileDescriptorSupplier() {} + } + + private static final class HgStoreStreamMethodDescriptorSupplier + extends HgStoreStreamBaseDescriptorSupplier + implements io.grpc.protobuf.ProtoMethodDescriptorSupplier { + private final String methodName; + + HgStoreStreamMethodDescriptorSupplier(String methodName) { + this.methodName = methodName; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.MethodDescriptor getMethodDescriptor() { + return getServiceDescriptor().findMethodByName(methodName); + } + } + + private static volatile io.grpc.ServiceDescriptor serviceDescriptor; + + public static io.grpc.ServiceDescriptor getServiceDescriptor() { + io.grpc.ServiceDescriptor result = serviceDescriptor; + if (result == null) { + synchronized (HgStoreStreamGrpc.class) { + result = serviceDescriptor; + if (result == null) { + serviceDescriptor = result = io.grpc.ServiceDescriptor.newBuilder(SERVICE_NAME) + .setSchemaDescriptor(new HgStoreStreamFileDescriptorSupplier()) + .addMethod(getScanMethod()) + .addMethod(getScanOneShotMethod()) + .addMethod(getScanBatchMethod()) + .addMethod(getScanBatch2Method()) + .addMethod(getScanBatchOneShotMethod()) + .build(); + } + } + } + return result; + } +} diff --git a/hugegraph-store/hg-store-grpc/src/main/dev/org/apache/hugegraph/store/grpc/stream/HgStoreStreamProto.java b/hugegraph-store/hg-store-grpc/src/main/dev/org/apache/hugegraph/store/grpc/stream/HgStoreStreamProto.java new file mode 100644 index 000000000..940ddab7f --- /dev/null +++ b/hugegraph-store/hg-store-grpc/src/main/dev/org/apache/hugegraph/store/grpc/stream/HgStoreStreamProto.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: store_stream.proto + +package org.apache.hugegraph.store.grpc.stream; + +public final class HgStoreStreamProto { + private HgStoreStreamProto() {} + public static void registerAllExtensions( + com.google.protobuf.ExtensionRegistryLite registry) { + } + + public static void registerAllExtensions( + com.google.protobuf.ExtensionRegistry registry) { + registerAllExtensions( + (com.google.protobuf.ExtensionRegistryLite) registry); + } + static final com.google.protobuf.Descriptors.Descriptor + internal_static_KvStream_descriptor; + static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_KvStream_fieldAccessorTable; + + public static com.google.protobuf.Descriptors.FileDescriptor + getDescriptor() { + return descriptor; + } + private static com.google.protobuf.Descriptors.FileDescriptor + descriptor; + static { + java.lang.String[] descriptorData = { + "\n\022store_stream.proto\032\022store_common.proto" + + "\032\027store_stream_meta.proto\"I\n\010KvStream\022\016\n" + + "\006seq_no\030\001 \001(\005\022\014\n\004over\030\002 \001(\010\022\017\n\007version\030\004" + + " \001(\r\022\016\n\006stream\030\005 \001(\0142\205\002\n\rHgStoreStream\022(" + + "\n\004Scan\022\016.ScanStreamReq\032\n.KvPageRes\"\000(\0010\001" + + "\022+\n\013ScanOneShot\022\016.ScanStreamReq\032\n.KvPage" + + "Res\"\000\0222\n\tScanBatch\022\023.ScanStreamBatchReq\032" + + "\n.KvPageRes\"\000(\0010\001\0222\n\nScanBatch2\022\023.ScanSt" + + "reamBatchReq\032\t.KvStream\"\000(\0010\001\0225\n\020ScanBat" + + "chOneShot\022\023.ScanStreamBatchReq\032\n.KvPageR" + + "es\"\000B=\n%org.apache.hugegraph.store.grpc.s" + + "treamB\022HgStoreStreamProtoP\001b\006proto3" + }; + descriptor = com.google.protobuf.Descriptors.FileDescriptor + .internalBuildGeneratedFileFrom(descriptorData, + new com.google.protobuf.Descriptors.FileDescriptor[] { + org.apache.hugegraph.store.grpc.common.HgStoreCommonProto.getDescriptor(), + org.apache.hugegraph.store.grpc.stream.HgStoreStreamMetaProto.getDescriptor(), + }); + internal_static_KvStream_descriptor = + getDescriptor().getMessageTypes().get(0); + internal_static_KvStream_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_KvStream_descriptor, + new java.lang.String[] { "SeqNo", "Over", "Version", "Stream", }); + org.apache.hugegraph.store.grpc.common.HgStoreCommonProto.getDescriptor(); + org.apache.hugegraph.store.grpc.stream.HgStoreStreamMetaProto.getDescriptor(); + } + + // @@protoc_insertion_point(outer_class_scope) +} diff --git a/hugegraph-store/hg-store-grpc/src/main/dev/org/apache/hugegraph/store/grpc/stream/KvStream.java b/hugegraph-store/hg-store-grpc/src/main/dev/org/apache/hugegraph/store/grpc/stream/KvStream.java new file mode 100644 index 000000000..8026ee74d --- /dev/null +++ b/hugegraph-store/hg-store-grpc/src/main/dev/org/apache/hugegraph/store/grpc/stream/KvStream.java @@ -0,0 +1,910 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: store_stream.proto + +package org.apache.hugegraph.store.grpc.stream; + +import java.nio.ByteBuffer; +import java.util.function.Consumer; + +/** + * 为了提升性能,复用内存,减少gc回收,需要重写KvStream.writeTo方法 + */ +public final class KvStream extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point + // (message_implements + // :KvStream) + KvStreamOrBuilder { + public static final int SEQ_NO_FIELD_NUMBER = 1; + public static final int OVER_FIELD_NUMBER = 2; + public static final int VERSION_FIELD_NUMBER = 4; + public static final int STREAM_FIELD_NUMBER = 5; + public static final int TYPE_FIELD_NUMBER = 6; + private static final long serialVersionUID = 0L; + // @@protoc_insertion_point(class_scope:KvStream) + private static final KvStream DEFAULT_INSTANCE; + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser<>() { + @java.lang.Override + public KvStream parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new KvStream(input, extensionRegistry); + } + }; + + static { + DEFAULT_INSTANCE = new KvStream(); + } + + private int seqNo_; + private boolean over_; + private int version_; + private ByteBuffer stream_; + private Consumer complete_; + private int type_; + private byte memoizedIsInitialized = -1; + + // Use KvStream.newBuilder() to construct. + private KvStream(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + + private KvStream() { + stream_ = ByteBuffer.allocate(0); + } + private KvStream( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 8: { + + seqNo_ = input.readInt32(); + break; + } + case 16: { + + over_ = input.readBool(); + break; + } + case 32: { + + version_ = input.readUInt32(); + break; + } + case 42: { + + stream_ = input.readByteBuffer(); + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + + public static com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return HgStoreStreamProto.internal_static_KvStream_descriptor; + } + + public static KvStream parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static KvStream parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static KvStream parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static KvStream parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static KvStream parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static KvStream parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static KvStream parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + + public static KvStream parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + public static KvStream parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + + public static KvStream parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + + public static KvStream parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + + public static KvStream parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + + public static Builder newBuilder(KvStream prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + + public static KvStream getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new KvStream(); + } + + @java.lang.Override + public com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return HgStoreStreamProto.internal_static_KvStream_fieldAccessorTable + .ensureFieldAccessorsInitialized( + KvStream.class, KvStream.Builder.class); + } + + /** + *
+     * query times.
+     * 
+ * + * int32 seq_no = 1; + * + * @return The seqNo. + */ + @java.lang.Override + public int getSeqNo() { + return seqNo_; + } + + /** + *
+     * true=no more data
+     * 
+ * + * bool over = 2; + * + * @return The over. + */ + @java.lang.Override + public boolean getOver() { + return over_; + } + + /** + * uint32 version = 4; + * + * @return The version. + */ + @java.lang.Override + public int getVersion() { + return version_; + } + + /** + * bytes stream = 5; + * + * @return The stream. + */ + @java.lang.Override + public ByteBuffer getStream() { + return stream_; + } + + /** + * .KvStreamType type = 6; + * + * @return The enum numeric value on the wire for type. + */ + @java.lang.Override + public int getTypeValue() { + return type_; + } + + /** + * .KvStreamType type = 6; + * + * @return The type. + */ + @java.lang.Override + public org.apache.hugegraph.store.grpc.stream.KvStreamType getType() { + @SuppressWarnings("deprecation") + org.apache.hugegraph.store.grpc.stream.KvStreamType result = + org.apache.hugegraph.store.grpc.stream.KvStreamType.valueOf(type_); + return result == null ? org.apache.hugegraph.store.grpc.stream.KvStreamType.UNRECOGNIZED : + result; + } + + @java.lang.Override + public boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (seqNo_ != 0) { + output.writeInt32(1, seqNo_); + } + if (over_) { + output.writeBool(2, over_); + } + if (version_ != 0) { + output.writeUInt32(4, version_); + } + if (stream_.limit() > 0) { + output.writeByteArray(5, stream_.array(), 0, stream_.limit()); + } + if (type_ != + org.apache.hugegraph.store.grpc.stream.KvStreamType.STREAM_TYPE_NONE.getNumber()) { + output.writeEnum(6, type_); + } + unknownFields.writeTo(output); + if (complete_ != null) { + complete_.accept(this); + } + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (seqNo_ != 0) { + size += com.google.protobuf.CodedOutputStream + .computeInt32Size(1, seqNo_); + } + if (over_) { + size += com.google.protobuf.CodedOutputStream + .computeBoolSize(2, over_); + } + if (version_ != 0) { + size += com.google.protobuf.CodedOutputStream + .computeUInt32Size(4, version_); + } + if (stream_.limit() > 0) { + size += com.google.protobuf.CodedOutputStream + .computeTagSize(5) + + com.google.protobuf.CodedOutputStream + .computeUInt32SizeNoTag(stream_.limit()) + + stream_.limit(); + } + if (type_ != + org.apache.hugegraph.store.grpc.stream.KvStreamType.STREAM_TYPE_NONE.getNumber()) { + size += com.google.protobuf.CodedOutputStream + .computeEnumSize(6, type_); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof KvStream)) { + return super.equals(obj); + } + KvStream other = (KvStream) obj; + + if (getSeqNo() + != other.getSeqNo()) { + return false; + } + if (getOver() + != other.getOver()) { + return false; + } + if (getVersion() + != other.getVersion()) { + return false; + } + if (!getStream() + .equals(other.getStream())) { + return false; + } + return unknownFields.equals(other.unknownFields); + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + SEQ_NO_FIELD_NUMBER; + hash = (53 * hash) + getSeqNo(); + hash = (37 * hash) + OVER_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean( + getOver()); + hash = (37 * hash) + VERSION_FIELD_NUMBER; + hash = (53 * hash) + getVersion(); + hash = (37 * hash) + STREAM_FIELD_NUMBER; + hash = (53 * hash) + getStream().hashCode(); + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + @java.lang.Override + public Builder newBuilderForType() { + return newBuilder(); + } + + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public KvStream getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + /** + * Protobuf type {@code KvStream} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:KvStream) + KvStreamOrBuilder { + private int seqNo_; + private boolean over_; + private int version_; + private ByteBuffer stream_ = ByteBuffer.allocate(0); + private int type_ = 0; + private Consumer complete_; + + // Construct using org.apache.hugegraph.store.grpc.stream.KvStream.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + + public static com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return HgStoreStreamProto.internal_static_KvStream_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return HgStoreStreamProto.internal_static_KvStream_fieldAccessorTable + .ensureFieldAccessorsInitialized( + KvStream.class, KvStream.Builder.class); + } + + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + } + } + + @java.lang.Override + public Builder clear() { + super.clear(); + seqNo_ = 0; + + over_ = false; + + version_ = 0; + + stream_ = ByteBuffer.allocate(0); + + complete_ = null; + + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return HgStoreStreamProto.internal_static_KvStream_descriptor; + } + + @java.lang.Override + public KvStream getDefaultInstanceForType() { + return KvStream.getDefaultInstance(); + } + + @java.lang.Override + public KvStream build() { + KvStream result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public KvStream buildPartial() { + KvStream result = new KvStream(this); + result.seqNo_ = seqNo_; + result.over_ = over_; + result.version_ = version_; + result.stream_ = stream_; + result.complete_ = complete_; + onBuilt(); + // d��� + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof KvStream) { + return mergeFrom((KvStream) other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(KvStream other) { + if (other == KvStream.getDefaultInstance()) return this; + if (other.getSeqNo() != 0) { + setSeqNo(other.getSeqNo()); + } + if (other.getOver()) { + setOver(other.getOver()); + } + if (other.getVersion() != 0) { + setVersion(other.getVersion()); + } + if (other.getStream() != ByteBuffer.allocate(0)) { + setStream(other.getStream()); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + KvStream parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (KvStream) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + + /** + *
+         * query times.
+         * 
+ * + * int32 seq_no = 1; + * + * @return The seqNo. + */ + @java.lang.Override + public int getSeqNo() { + return seqNo_; + } + + /** + *
+         * query times.
+         * 
+ * + * int32 seq_no = 1; + * + * @param value The seqNo to set. + * @return This builder for chaining. + */ + public Builder setSeqNo(int value) { + + seqNo_ = value; + onChanged(); + return this; + } + + /** + *
+         * query times.
+         * 
+ * + * int32 seq_no = 1; + * + * @return This builder for chaining. + */ + public Builder clearSeqNo() { + + seqNo_ = 0; + onChanged(); + return this; + } + + /** + *
+         * true=no more data
+         * 
+ * + * bool over = 2; + * + * @return The over. + */ + @java.lang.Override + public boolean getOver() { + return over_; + } + + /** + *
+         * true=no more data
+         * 
+ * + * bool over = 2; + * + * @param value The over to set. + * @return This builder for chaining. + */ + public Builder setOver(boolean value) { + + over_ = value; + onChanged(); + return this; + } + + /** + *
+         * true=no more data
+         * 
+ * + * bool over = 2; + * + * @return This builder for chaining. + */ + public Builder clearOver() { + + over_ = false; + onChanged(); + return this; + } + + /** + * uint32 version = 4; + * + * @return The version. + */ + @java.lang.Override + public int getVersion() { + return version_; + } + + /** + * uint32 version = 4; + * + * @param value The version to set. + * @return This builder for chaining. + */ + public Builder setVersion(int value) { + + version_ = value; + onChanged(); + return this; + } + + /** + * uint32 version = 4; + * + * @return This builder for chaining. + */ + public Builder clearVersion() { + + version_ = 0; + onChanged(); + return this; + } + + /** + * bytes stream = 5; + * + * @return The stream. + */ + @java.lang.Override + public ByteBuffer getStream() { + return stream_; + } + + /** + * bytes stream = 5; + * + * @param value The stream to set. + * @return This builder for chaining. + */ + public Builder setStream(ByteBuffer value) { + if (value == null) { + throw new NullPointerException(); + } + + stream_ = value; + onChanged(); + return this; + } + + /** + * bytes stream = 5; + * + * @return This builder for chaining. + */ + public Builder clearStream() { + + stream_ = getDefaultInstance().getStream(); + onChanged(); + return this; + } + + /** + * .KvStreamType type = 6; + * + * @return The enum numeric value on the wire for type. + */ + @java.lang.Override + public int getTypeValue() { + return type_; + } + + /** + * .KvStreamType type = 6; + * + * @param value The enum numeric value on the wire for type to set. + * @return This builder for chaining. + */ + public Builder setTypeValue(int value) { + + type_ = value; + onChanged(); + return this; + } + + /** + * .KvStreamType type = 6; + * + * @return The type. + */ + @java.lang.Override + public org.apache.hugegraph.store.grpc.stream.KvStreamType getType() { + @SuppressWarnings("deprecation") + org.apache.hugegraph.store.grpc.stream.KvStreamType result = + org.apache.hugegraph.store.grpc.stream.KvStreamType.valueOf(type_); + return result == null ? + org.apache.hugegraph.store.grpc.stream.KvStreamType.UNRECOGNIZED : result; + } + + /** + * .KvStreamType type = 6; + * + * @param value The type to set. + * @return This builder for chaining. + */ + public Builder setType(org.apache.hugegraph.store.grpc.stream.KvStreamType value) { + if (value == null) { + throw new NullPointerException(); + } + + type_ = value.getNumber(); + onChanged(); + return this; + } + + /** + * .KvStreamType type = 6; + * + * @return This builder for chaining. + */ + public Builder clearType() { + + type_ = 0; + onChanged(); + return this; + } + + @java.lang.Override + public Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + public void complete(Consumer consumer) { + this.complete_ = consumer; + } + + // @@protoc_insertion_point(builder_scope:KvStream) + } + +} diff --git a/hugegraph-store/hg-store-grpc/src/main/dev/org/apache/hugegraph/store/grpc/stream/KvStreamOrBuilder.java b/hugegraph-store/hg-store-grpc/src/main/dev/org/apache/hugegraph/store/grpc/stream/KvStreamOrBuilder.java new file mode 100644 index 000000000..418241dfd --- /dev/null +++ b/hugegraph-store/hg-store-grpc/src/main/dev/org/apache/hugegraph/store/grpc/stream/KvStreamOrBuilder.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: store_stream.proto + +package org.apache.hugegraph.store.grpc.stream; + +import java.nio.ByteBuffer; + +public interface KvStreamOrBuilder extends + // @@protoc_insertion_point(interface_extends:KvStream) + com.google.protobuf.MessageOrBuilder { + + /** + *
+   *query times.
+   * 
+ * + * int32 seq_no = 1; + * @return The seqNo. + */ + int getSeqNo(); + + /** + *
+   *true=no more data
+   * 
+ * + * bool over = 2; + * @return The over. + */ + boolean getOver(); + + /** + * uint32 version = 4; + * @return The version. + */ + int getVersion(); + + /** + * bytes stream = 5; + * @return The stream. + */ + ByteBuffer getStream(); + /** + * .KvStreamType type = 6; + * @return The enum numeric value on the wire for type. + */ + int getTypeValue(); + /** + * .KvStreamType type = 6; + * @return The type. + */ + org.apache.hugegraph.store.grpc.stream.KvStreamType getType(); +} diff --git a/hugegraph-store/hg-store-grpc/src/main/dev/org/apache/hugegraph/store/grpc/stream/store_stream.proto b/hugegraph-store/hg-store-grpc/src/main/dev/org/apache/hugegraph/store/grpc/stream/store_stream.proto new file mode 100644 index 000000000..92a2c16af --- /dev/null +++ b/hugegraph-store/hg-store-grpc/src/main/dev/org/apache/hugegraph/store/grpc/stream/store_stream.proto @@ -0,0 +1,33 @@ +syntax = "proto3"; + +import "store_common.proto"; +import "store_stream_meta.proto"; +option java_multiple_files = true; +option java_package = "org.apache.hugegraph.store.grpc.stream"; +option java_outer_classname = "HgStoreStreamProto"; + +/** + 为了提升性能,复用内存,减少gc回收,需要重写KvStream.writeTo方法 + + */ +service HgStoreStream { + + rpc Scan(stream ScanStreamReq) returns (stream KvPageRes) {} + rpc ScanOneShot(ScanStreamReq) returns (KvPageRes) {} + rpc ScanBatch(stream ScanStreamBatchReq) returns (stream KvPageRes) {} + rpc ScanBatch2(stream ScanStreamBatchReq) returns (stream KvStream) {} + rpc ScanBatchOneShot(ScanStreamBatchReq) returns (KvPageRes) {} +} + + +message KvStream { + int32 seq_no = 1; //query times. + bool over = 2; //true=no more data + uint32 version = 4; + bytes stream = 5; + KvStreamType type = 6; +} + + + + diff --git a/hugegraph-store/hg-store-grpc/src/main/proto/graphpb.proto b/hugegraph-store/hg-store-grpc/src/main/proto/graphpb.proto new file mode 100644 index 000000000..b61940714 --- /dev/null +++ b/hugegraph-store/hg-store-grpc/src/main/proto/graphpb.proto @@ -0,0 +1,121 @@ +syntax = "proto3"; +package graph_pb; + +option java_package = "org.apache.hugegraph.store.grpc"; + +service GraphStore { + rpc ScanPartition(stream ScanPartitionRequest) returns (stream ScanResponse){} +} + +message ScanPartitionRequest{ + enum ScanType{ + SCAN_UNKNOWN = 0; + SCAN_VERTEX = 1; + SCAN_EDGE = 2; + } + // 请求参数 + message Request{ + ScanType scan_type = 1; + string graph_name = 2; + uint32 partition_id = 3; + uint32 start_code = 4; + uint32 end_code = 5; + // 过滤条件 + string condition = 6; + string table = 7; + int64 limit = 8; + int32 boundary = 9; + bytes position = 10; + // 返回条件 + repeated int64 properties = 11; + } + + + message Reply{ + int32 seq_no = 1; + } + RequestHeader header = 1; + oneof request { + Request scan_request = 2; + // 每消费一个数据包,通知服务端一次,返回消息序号 + Reply reply_request = 4; + } +} + +message ScanResponse{ + ResponseHeader header = 1; + // 消息序号 + int32 seq_no = 2; + repeated Vertex vertex = 3; + repeated Edge edge = 4; +} + + +message Property{ + uint64 label = 1; + Variant value = 2; +} + +message Vertex{ + int64 label = 1; // 点类型 + Variant id = 2; // 点ID + repeated Property properties = 3; //点属性 +} + +message Edge{ + int64 label = 1; // 边类型 + int64 sourceLabel = 2; + int64 targetLabel = 3; + Variant source_id = 4; // 源点ID + Variant target_id = 5; // 目标点ID + + repeated Property properties = 6; //边属性 +} + +message Variant { + optional VariantType type = 1; + optional int32 value_int32 = 2; + optional int64 value_int64 = 3; + optional float value_float = 4; + optional double value_double = 5; + optional string value_string = 6; + optional bytes value_bytes = 7; + optional string value_datetime = 8; + optional bool value_boolean = 9; +} + +enum VariantType { + VT_UNKNOWN = 0; + VT_BOOLEAN = 1; + VT_INT = 2; + VT_LONG = 3; + VT_FLOAT = 4; + VT_DOUBLE = 7; + VT_STRING = 8; + VT_BYTES = 9; + VT_DATETIME = 10; +} + + + +message RequestHeader { + // 发送者 ID. + uint64 sender_id = 2; +} + +message ResponseHeader { + uint64 sender_id = 1; + Error error = 2; +} + + +enum ErrorType { + OK = 0; + UNKNOWN = 1; +} + +message Error { + ErrorType type = 1; + string message = 2; +} + diff --git a/hugegraph-store/hg-store-grpc/src/main/proto/healthy.proto b/hugegraph-store/hg-store-grpc/src/main/proto/healthy.proto new file mode 100644 index 000000000..fc24acab0 --- /dev/null +++ b/hugegraph-store/hg-store-grpc/src/main/proto/healthy.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +option java_package = "org.apache.hugegraph.store.grpc"; + +import "google/protobuf/empty.proto"; + +service Healthy { + rpc IsOk(google.protobuf.Empty) returns (StringReply) {} +} + +message StringReply { + string message = 1; +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-grpc/src/main/proto/store_common.proto b/hugegraph-store/hg-store-grpc/src/main/proto/store_common.proto new file mode 100644 index 000000000..afc25634f --- /dev/null +++ b/hugegraph-store/hg-store-grpc/src/main/proto/store_common.proto @@ -0,0 +1,96 @@ +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = "org.apache.hugegraph.store.grpc.common"; +option java_outer_classname = "HgStoreCommonProto"; + +message Header { + string graph = 1; +} +message Tkv { + string table = 1; + bytes key = 2; + bytes value = 3; + int32 code = 9; + +} + +message Tk { + string table = 1; + bytes key = 2; + int32 code = 9; +} + +message Tp { + string table = 1; + bytes prefix = 2; + int32 code = 9; +} + +message Tse { + string table = 1; + Key start = 2; + Key end = 3; +} + +message Key { + bytes key = 1; + int32 code = 9; +} + +message Kv { + bytes key = 1; + bytes value = 2; + int32 code = 9; +} + +message ResStatus { + ResCode code = 1; + string msg = 2; +} + +/*--- enum ---*/ +enum ResCode { + RES_CODE_OK = 0; + RES_CODE_FAIL = 1; + RES_CODE_NOT_EXIST = 2; + RES_CODE_EXCESS = 3; +} + +enum ScanMethod { + UNKNOWN_SCAN_TYPE = 0; + ALL = 1; + PREFIX = 2; + RANGE = 3; +} + +enum ScanOrderType{ + // 批量接口下,返回顺序的要求 + ORDER_NONE = 0; // 允许无序 + ORDER_WITHIN_VERTEX = 1; // 一个点内的边不会被打断,单不同点之间为无序 + ORDER_STRICT = 2; // 保证原始的输入点顺序 +} + +enum OpType { + OP_TYPE_UNKNOWN = 0; + OP_TYPE_PUT = 1; + OP_TYPE_DEL = 2; + OP_TYPE_DEL_SINGLE = 3; + OP_TYPE_DEL_PREFIX = 4; + OP_TYPE_DEL_RANGE = 5; + OP_TYPE_MERGE = 6; +} + +enum TableMethod{ + TABLE_METHOD_UNKNOWN = 0; + TABLE_METHOD_EXISTS = 1; + TABLE_METHOD_CREATE = 2; + TABLE_METHOD_DELETE = 3; + TABLE_METHOD_DROP = 4; + TABLE_METHOD_TRUNCATE = 5; +} + +enum GraphMethod{ + GRAPH_METHOD_UNKNOWN = 0; + GRAPH_METHOD_DELETE = 3; +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-grpc/src/main/proto/store_session.proto b/hugegraph-store/hg-store-grpc/src/main/proto/store_session.proto new file mode 100644 index 000000000..3110a9b4b --- /dev/null +++ b/hugegraph-store/hg-store-grpc/src/main/proto/store_session.proto @@ -0,0 +1,121 @@ +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = "org.apache.hugegraph.store.grpc.session"; +option java_outer_classname = "HgStoreSessionProto"; + +import "store_common.proto"; +import "store_stream_meta.proto"; + +service HgStoreSession { + rpc Get2(GetReq) returns (FeedbackRes) {} + rpc BatchGet2(BatchGetReq) returns (FeedbackRes) {} + rpc Batch(BatchReq) returns (FeedbackRes){} + rpc Table(TableReq) returns (FeedbackRes){}; + rpc Graph(GraphReq) returns (FeedbackRes){}; + rpc Clean(CleanReq) returns (FeedbackRes) {} + rpc Count(ScanStreamReq) returns (Agg) {} +} + +message TableReq{ + Header header = 1; + TableMethod method = 2; + string table_name = 3; +} + +message GraphReq{ + Header header = 1; + GraphMethod method = 2; + string graph_name = 3; +} + +message BatchReq{ + Header header = 1; + string batch_id = 2; + oneof requests{ + BatchWriteReq write_req = 10; + BatchCommitReq commit_req = 11; + BatchRollbackReq rollback_req = 12; + } +} + +message BatchWriteReq{ + repeated BatchEntry entry = 1; +} + +message BatchCommitReq{} + +message BatchRollbackReq{} + +message BatchEntry{ + OpType op_type = 1; + int32 table = 2; + Key start_key = 3; + Key end_key = 4; + bytes value = 5; +} + +message BatchGetReq { + Header header = 1; + string table = 2; + repeated Key key = 3; + int32 partition = 9; +} + +message GetReq { + Header header = 1; + Tk tk = 2; +} + +message CleanReq{ + Header header = 1; + int32 partition = 2; +} + + +message FeedbackRes { + ResStatus status = 1; + + oneof responses{ + PartitionFaultResponse partition_fault_response = 10; + ValueResponse value_response = 11; + KeyValueResponse key_value_response = 12; + + } + +} + +message ValueResponse { + bytes value = 1; +} + +message KeyValueResponse { + repeated Kv kv = 1; +} + +message PartitionFaultResponse{ + PartitionFaultType fault_type = 1; + repeated PartitionLeader partition_leaders = 2; + repeated int32 partition_ids = 3; +} + +message PartitionLeader { + int32 partitionId = 2; + int64 leaderId = 3; +} + +enum PartitionFaultType{ + PARTITION_FAULT_TYPE_UNKNOWN = 0; + // 当前不是Leader,返回Leader所在store + PARTITION_FAULT_TYPE_NOT_LEADER = 1; + // 等待Leader超时,可能raft group创建失败 + PARTITION_FAULT_TYPE_WAIT_LEADER_TIMEOUT = 2; + // 分区不属于本机 + PARTITION_FAULT_TYPE_NOT_LOCAL = 3; + +} +message Agg { + Header header = 1; + int64 count = 2; +} + diff --git a/hugegraph-store/hg-store-grpc/src/main/proto/store_state.proto b/hugegraph-store/hg-store-grpc/src/main/proto/store_state.proto new file mode 100644 index 000000000..d8ef112c9 --- /dev/null +++ b/hugegraph-store/hg-store-grpc/src/main/proto/store_state.proto @@ -0,0 +1,56 @@ +syntax = "proto3"; + +import "google/protobuf/empty.proto"; +import "store_common.proto"; + +option java_multiple_files = true; +option java_package = "org.apache.hugegraph.store.grpc.state"; +option java_outer_classname = "HgStoreStateProto"; + +service HgStoreState { + + // Subscribe Store Node state publishing. + rpc SubState(SubStateReq) returns (stream NodeStateRes) {} + + // Unsubscribe Store Node state publishing. + rpc UnsubState(SubStateReq) returns (google.protobuf.Empty){} + rpc getScanState(SubStateReq) returns (ScanState){} + +} + +message SubStateReq{ + string subId = 1; +} + +message NodeStateRes { + NodeStateType state = 1; +} + +message ScanState{ + string address = 1; + uint64 taskCount = 2; + uint64 completedTaskCount = 3; + uint32 activeCount = 4; + uint32 largestPoolSize = 5; + uint32 poolSize = 6; + int32 maximumPoolSize = 7; + int32 queueSize = 8; + int32 queueRemainingCapacity = 9; +} + +enum NodeStateType { + UNKNOWN_STATE_TYPE = 0; + STARTING = 10; + STANDBY = 20; + ONLINE = 30; + PAUSE = 40; + PENDING = 50; + STOPPING = 60; + HALTED = 70; + ERROR = 90; + ZOMBIE = 99; +} + +message QuotaRequest { + map limits = 1; +} diff --git a/hugegraph-store/hg-store-grpc/src/main/proto/store_stream_meta.proto b/hugegraph-store/hg-store-grpc/src/main/proto/store_stream_meta.proto new file mode 100644 index 000000000..4b4350cd6 --- /dev/null +++ b/hugegraph-store/hg-store-grpc/src/main/proto/store_stream_meta.proto @@ -0,0 +1,92 @@ +syntax = "proto3"; + +import "store_common.proto"; + +option java_multiple_files = true; +option java_package = "org.apache.hugegraph.store.grpc.stream"; +option java_outer_classname = "HgStoreStreamMetaProto"; + + +message ScanStreamBatchReq { + Header header = 1; + oneof query { + ScanQueryRequest query_request = 10; + ScanPagingRequest paging_request = 11; + ScanPauseRequest pause_request = 12; + ScanCancelRequest cancel_request = 13; + ScanReceiptRequest receipt_request = 14; + } + int64 logId = 15; +} + +message ScanQueryRequest { + ScanMethod method = 2; + string table = 3; + int64 limit = 4; + int64 pageSize = 5; + int32 scanType = 6; + bytes query = 7; + bytes position = 8; + repeated ScanCondition condition = 9; + int64 perKeyLimit = 10; + int64 skipDegree = 11; + ScanOrderType orderType = 12; + int64 perKeyMax = 13; +} + +message ScanPagingRequest { + int64 pageSize = 1; +} +message ScanPauseRequest {} +message ScanCancelRequest {} +message ScanReceiptRequest { + uint32 times = 1; +} + +message ScanCondition { + int32 code = 1; // owner key hashcode + bytes prefix = 2; // key prefix + bytes start = 3; // start key + bytes end = 4; // end key + int32 serialNo = 5; // serial no +} + +message ScanStreamReq { + Header header = 1; + ScanMethod method = 2; + string table = 3; + int32 code = 4; // partitionId + bytes prefix = 5; // key prefix + bytes start = 6; //start key + bytes end = 7; //end key + int64 limit = 8; + int32 scanType = 9; + bytes query = 10; + int32 pageSize = 11; + bytes position = 12; + uint32 closeFlag = 13; + SelectParam selects = 14; +} + +message SelectParam { + bytes filter = 1; + bool withNoProperties = 2; + repeated int32 properties = 3; +} + +message KvPageRes { + int32 times = 1; //query times. + bool over = 2; //true=no more data + repeated Kv data = 3; + uint32 version = 4; + bytes stream = 5; +} + +enum KvStreamType { + STREAM_TYPE_NONE = 0; + STREAM_TYPE_KV = 1; + STREAM_TYPE_K = 2; + STREAM_TYPE_SKV = 3; + STREAM_TYPE_SK = 4; +} + diff --git a/hugegraph-store/hg-store-node/banner.txt b/hugegraph-store/hg-store-node/banner.txt new file mode 100644 index 000000000..13f0501ce --- /dev/null +++ b/hugegraph-store/hg-store-node/banner.txt @@ -0,0 +1,5 @@ + _ _ ____ ____ _____ ___ ____ _____ _ _ ___ ____ _____ + | | | |/ ___| / ___|_ _/ _ \| _ \| ____| | \ | |/ _ \| _ \| ____| + | |_| | | _ ____\___ \ | || | | | |_) | _| _____| \| | | | | | | | _| + | _ | |_| |_____|__) || || |_| | _ <| |__|_____| |\ | |_| | |_| | |___ + |_| |_|\____| |____/ |_| \___/|_| \_\_____| |_| \_|\___/|____/|_____| \ No newline at end of file diff --git a/hugegraph-store/hg-store-node/pom.xml b/hugegraph-store/hg-store-node/pom.xml new file mode 100644 index 000000000..b0af90b60 --- /dev/null +++ b/hugegraph-store/hg-store-node/pom.xml @@ -0,0 +1,171 @@ + + + + + 4.0.0 + + + org.apache.hugegraph + hugegraph-store-root + ${revision} + ../pom.xml + + + hugegraph-store + + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-logging + + + + + org.springframework.boot + spring-boot-starter-log4j2 + + + org.springframework.boot + spring-boot-starter-actuator + + + io.micrometer + micrometer-registry-prometheus + + + io.dropwizard.metrics + metrics-core + 4.2.4 + + + org.springframework.boot + spring-boot-starter-web + 2.5.14 + + + + org.springframework.boot + spring-boot-starter-tomcat + + + + + org.springframework.boot + spring-boot-starter-jetty + + + io.github.lognet + grpc-spring-boot-starter + 4.5.5 + + + org.projectlombok + lombok + + + org.springframework.boot + spring-boot-starter-test + test + + + + + org.apache.hugegraph + hg-store-common + + + com.google.protobuf + protobuf-java-util + 3.17.2 + + + org.apache.hugegraph + hg-store-grpc + + + org.apache.hugegraph + hg-pd-client + ${revision} + + + io.grpc + * + + + + + org.apache.hugegraph + hg-store-core + + + org.apache.hugegraph + hugegraph-core + ${hugegraph.server.version} + + + io.etcd + jetcd-core + + + + + + + com.taobao.arthas + arthas-agent-attach + 3.6.4 + + + com.taobao.arthas + arthas-packaging + 3.6.4 + + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + org.apache.hugegraph.store.node.StoreNodeApplication + + + + + + + + + \ No newline at end of file diff --git a/hugegraph-store/hg-store-node/src/assembly/static/bin/app.sh b/hugegraph-store/hg-store-node/src/assembly/static/bin/app.sh new file mode 100755 index 000000000..dab64ed99 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/assembly/static/bin/app.sh @@ -0,0 +1,72 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to You under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +#ver 0.1.0 liyan75 on 2021/10/08 +#readonly CUR_SHELL=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) +readonly CUR_SHELL_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) +readonly CUR_SHELL_NAME=$(basename ${BASH_SOURCE}) + +readonly LOG_PATH=${CUR_SHELL_DIR}/stdout.log +readonly APP_JAR_PATH=./hg-store-node-1.0-SNAPSHOT-spring-boot.jar + +{ + #command parameters + CMD=$1 +} + +start(){ + nohup java -jar ${APP_JAR_PATH} >> ${LOG_PATH} 2>&1 & + pid=$! + echo $pid>>${CUR_SHELL_DIR}/app.pid +} + +stop(){ + while read pid; do + kill ${pid} + done <${CUR_SHELL_DIR}/app.pid + echo >${CUR_SHELL_DIR}/app.pid +} + +help(){ + _log "syntax: /bin/bash $CUR_SHELL_NAME [ start | stop ]" + exit 1 +} +function _log(){ + echo -e "$(_getTime) : $1" 2>&1 +} +function _err(){ + echo -e "$(_getTime) : ERROR: $1" 1>&2 + return $2; +} +function _getTime(){ + local res=`date +"%Y/%m/%d %H:%M:%S"` + echo $res +} + +#entrance +case ${CMD} in + "start") + #init + start;; + "stop") + stop;; + "alive") + alive;; + *) + help;; +esac \ No newline at end of file diff --git a/hugegraph-store/hg-store-node/src/assembly/static/bin/hg-get-release.sh b/hugegraph-store/hg-store-node/src/assembly/static/bin/hg-get-release.sh new file mode 100644 index 000000000..b083c6d55 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/assembly/static/bin/hg-get-release.sh @@ -0,0 +1,72 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to You under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +# TODO: remove thie repo +readonly REPO_SNAPSHOT_PATH=http://10.14.139.8:8082/artifactory/star +readonly REPO_FILE_PATH=http://10.14.139.8:8082/artifactory/star-file +readonly REPO_BAIDU_LOCAL_PATH=http://maven.baidu-int.com/nexus/content/repositories/Baidu_Local +readonly PACKAGE_PATH=${REPO_SNAPSHOT_PATH}/com/baidu/hugegraph + +readonly VER=3.6.3 +{ + #command parameters + cmd=$1 + ver=$2 +} + +case ${cmd} in + "server") + curl ${REPO_FILE_PATH}/hugegraph/hugegraph-${ver}.tar.gz -o hugegraph-${ver}.tar.gz + ;; + "client") + dest=store-client-${VER} + mkdir ${dest} + + curl ${PACKAGE_PATH}/hg-store-client/${VER}/hg-store-client-${VER}.jar -o ./${dest}/hg-store-client-${VER}.jar + curl ${PACKAGE_PATH}/hg-store-client/${VER}/hg-store-client-${VER}.pom -o ./${dest}/hg-store-client-pom.xml + + curl ${PACKAGE_PATH}/hg-store-grpc/${VER}/hg-store-grpc-${VER}.jar -o ./${dest}/hg-store-grpc-${VER}.jar + curl ${PACKAGE_PATH}/hg-store-grpc/${VER}/hg-store-grpc-${VER}.pom -o ./${dest}/hg-store-grpc-pom.xml + + curl ${PACKAGE_PATH}/hg-store-common/${VER}/hg-store-common-${VER}.jar -o ./${dest}/hg-store-common-${VER}.jar + curl ${PACKAGE_PATH}/hg-store-common/${VER}/hg-store-common-${VER}.pom -o ./${dest}/hg-store-common-pom.xml + + curl ${PACKAGE_PATH}/hg-pd-client/${VER}/hg-pd-client-${VER}.jar -o ./${dest}/hg-pd-client-${VER}.jar + curl ${PACKAGE_PATH}/hg-pd-client/${VER}/hg-pd-client-${VER}.pom -o ./${dest}/hg-pd-client-pom.xml + + curl ${PACKAGE_PATH}/hg-pd-grpc/${VER}/hg-pd-grpc-${VER}.jar -o ./${dest}/hg-pd-grpc-${VER}.jar + curl ${PACKAGE_PATH}/hg-pd-grpc/${VER}/hg-pd-grpc-${VER}.pom -o ./${dest}/hg-pd-grpc-pom.xml + + curl ${PACKAGE_PATH}/hg-pd-common/${VER}/hg-pd-common-${VER}.jar -o ./${dest}/hg-pd-common-${VER}.jar + curl ${PACKAGE_PATH}/hg-pd-common/${VER}/hg-pd-common-${VER}.pom -o ./${dest}/hg-pd-common-pom.xml + + ;; + "store") + dest=hugegraph-store-${VER} + mkdir ${dest} + curl ${PACKAGE_PATH}/hugegraph-store/${VER}/hugegraph-store-${VER}.jar -o ./${dest}/hugegraph-store-${VER}.jar -o hugegraph-store-${VER}.jar + ;; + "pd") + dest=hugegraph-pd-${VER} + mkdir ${dest} + curl ${PACKAGE_PATH}/hugegraph-pd/${VER}/hugegraph-pd-${VER}.jar -o ./${dest}/hugegraph-pd-${VER}.jar -o hugegraph-pd-${VER}.jar + ;; + *) + echo "./hg-get-release.sh [client|store|server|pd ]" + ;; +esac diff --git a/hugegraph-store/hg-store-node/src/assembly/static/bin/hg-update.sh b/hugegraph-store/hg-store-node/src/assembly/static/bin/hg-update.sh new file mode 100644 index 000000000..fa8abd787 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/assembly/static/bin/hg-update.sh @@ -0,0 +1,48 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to You under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +#ver 0.1.0 liyan75 on 2021/12/17 +readonly REPO_SNAPSHOT_PATH=http://10.14.139.8:8082/artifactory/star-snapshot +readonly REPO_FILE_PATH=http://10.14.139.8:8082/artifactory/star-file +readonly REPO_BAIDU_LOCAL_PATH=http://maven.baidu-int.com/nexus/content/repositories/Baidu_Local +{ + #command parameters + cmd=$1 + ver=$2 +} + +case ${cmd} in + "server") + if [ -z "${ver}" ]; then + echo "The argument is empty. e.g. 0.13.0" + exit 1; + fi + curl ${REPO_FILE_PATH}/hugegraph/hugegraph-${ver}.tar.gz -o hugegraph-${ver}.tar.gz + ;; + "client") + curl ${REPO_SNAPSHOT_PATH}/com/baidu/hugegraph/hg-store-client/1.0-SNAPSHOT/hg-store-client-1.0-SNAPSHOT.jar -o hg-store-client-1.0-SNAPSHOT.jar + curl ${REPO_SNAPSHOT_PATH}/com/baidu/hugegraph/hg-store-grpc/1.0-SNAPSHOT/hg-store-grpc-1.0-SNAPSHOT.jar -o hg-store-grpc-1.0-SNAPSHOT.jar + curl ${REPO_BAIDU_LOCAL_PATH}/com/baidu/hugegraph/hg-store-common/1.0.1/hg-store-common-1.0.1.jar -o hg-store-term-1.0.1.jar + ;; + "node") + curl ${REPO_SNAPSHOT_PATH}/com/baidu/hugegraph/hg-store-node/1.0-SNAPSHOT/hg-store-node-1.0-SNAPSHOT-spring-boot.jar -o hg-store-node-1.0-SNAPSHOT-spring-boot.jar + ;; + *) + echo "./hg-update.sh [client|node|server ]" + ;; +esac diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/AppConfig.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/AppConfig.java new file mode 100644 index 000000000..473a8a3b3 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/AppConfig.java @@ -0,0 +1,228 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node; + +import java.util.HashMap; +import java.util.Map; + +import javax.annotation.PostConstruct; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.stereotype.Component; + +import lombok.Data; + +@Data +@Component +public class AppConfig { + @Value("${pdserver.address}") + private String pdServerAddress; + + @Value("${grpc.host}") + private String host; + + @Value("${grpc.port}") + private int grpcPort; + + @Value("${grpc.server.wait-time: 3600}") + private int serverWaitTime; + + @Value("${server.port}") + private int restPort; + + + //内置pd模式,用于单机部署 + @Value("${app.data-path: store}") + private String dataPath; + + @Value("${app.raft-path:}") + private String raftPath; + + //内置pd模式,用于单机部署 + @Value("${app.fake-pd: false}") + private boolean fakePd; + @Autowired + private Raft raft; + @Autowired + private ArthasConfig arthasConfig; + @Autowired + private FakePdConfig fakePdConfig; + @Autowired + private LabelConfig labelConfig; + @Autowired + private RocksdbConfig rocksdbConfig; + @Autowired + private ThreadPoolGrpc threadPoolGrpc; + @Autowired + private ThreadPoolScan threadPoolScan; + + public String getRaftPath() { + if (raftPath == null || raftPath.length() == 0) { + return dataPath; + } + return raftPath; + } + + @PostConstruct + public void init() { + Runtime rt = Runtime.getRuntime(); + if (threadPoolScan.core == 0) { + threadPoolScan.core = rt.availableProcessors() * 4; + } + + Map rocksdb = rocksdbConfig.rocksdb; + if (!rocksdb.containsKey("total_memory_size") + || "0".equals(rocksdb.get("total_memory_size"))) { + rocksdb.put("total_memory_size", Long.toString(rt.maxMemory())); + } + long totalMemory = Long.parseLong(rocksdbConfig.rocksdb.get("total_memory_size")); + if (raft.getDisruptorBufferSize() == 0) { + int size = (int) (totalMemory / 1000 / 1000 / 1000); + size = (int) Math.pow(2, Math.round(Math.log(size) / Math.log(2))) * 32; + raft.setDisruptorBufferSize(size); // 每32M增加一个buffer + } + + if (!rocksdb.containsKey("write_buffer_size") || + "0".equals(rocksdb.get("write_buffer_size"))) { + rocksdb.put("write_buffer_size", Long.toString(totalMemory / 1000)); + } + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("AppConfig \n") + .append("rocksdb:\n"); + rocksdbConfig.rocksdb.forEach((k, v) -> builder.append("\t" + k + ":") + .append(v) + .append("\n")); + builder.append("raft:\n"); + builder.append("\tdisruptorBufferSize: " + raft.disruptorBufferSize); + return builder.toString(); + } + + public String getStoreServerAddress() { + return String.format("%s:%d", host, grpcPort); + } + + public Map getRocksdbConfig() { + Map config = new HashMap<>(); + rocksdbConfig.rocksdb.forEach((k, v) -> { + config.put("rocksdb." + k, v); + }); + return config; + } + + @Data + @Configuration + public class ThreadPoolGrpc { + @Value("${thread.pool.grpc.core:600}") + private int core; + @Value("${thread.pool.grpc.max:1000}") + private int max; + @Value("${thread.pool.grpc.queue:" + Integer.MAX_VALUE + "}") + private int queue; + } + + @Data + @Configuration + public class ThreadPoolScan { + @Value("${thread.pool.scan.core: 128}") + private int core; + @Value("${thread.pool.scan.max: 1000}") + private int max; + @Value("${thread.pool.scan.queue: 0}") + private int queue; + } + + @Data + @Configuration + public class Raft { + @Value("${raft.address}") + private String address; + + @Value("${raft.rpc-timeout:10000}") + private int rpcTimeOut; + @Value("${raft.metrics:true}") + private boolean metrics; + @Value("${raft.snapshotLogIndexMargin:0}") + private int snapshotLogIndexMargin; + @Value("${raft.snapshotInterval:300}") + private int snapshotInterval; + @Value("${raft.disruptorBufferSize:0}") + private int disruptorBufferSize; + @Value("${raft.max-log-file-size: 50000000000}") + private long maxLogFileSize; + @Value("${ave-logEntry-size-ratio : 0.95}") + private double aveLogEntrySizeRation; + @Value("${raft.useRocksDBSegmentLogStorage: true}") + private boolean useRocksDBSegmentLogStorage; + @Value("${raft.maxSegmentFileSize:67108864}") + private int maxSegmentFileSize; + @Value("${raft.maxReplicatorInflightMsgs:256}") + private int maxReplicatorInflightMsgs; + + } + + @Data + @Configuration + public class ArthasConfig { + @Value("${arthas.telnetPort:8566}") + private String telnetPort; + + @Value("${arthas.httpPort:8565}") + private String httpPort; + + @Value("${arthas.ip:0.0.0.0}") + private String arthasip; + + @Value("${arthas.disabledCommands:jad}") + private String disCmd; + } + + @Data + @Configuration + public class FakePdConfig { + @Value("${fake-pd.store-list:''}") + private String storeList; + @Value("${fake-pd.peers-list:''}") + private String peersList; //fakePd模式下,raft集群初始配置 + @Value("${fake-pd.partition-count:3}") + private int partitionCount; + @Value("${fake-pd.shard-count:3}") + private int shardCount; + } + + @Data + @Configuration + @ConfigurationProperties(prefix = "app") + public class LabelConfig { + private final Map label = new HashMap<>(); + } + + @Data + @Configuration + @ConfigurationProperties(prefix = "") + public class RocksdbConfig { + private final Map rocksdb = new HashMap<>(); + } + +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/AppShutdownHook.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/AppShutdownHook.java new file mode 100644 index 000000000..5a08b7964 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/AppShutdownHook.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node; + +import org.apache.hugegraph.rocksdb.access.RocksDBFactory; + +/** + * copy from web + */ +public class AppShutdownHook extends Thread { + + private final Thread mainThread; + private boolean shutDownSignalReceived; + + public AppShutdownHook(Thread mainThread) { + super(); + this.mainThread = mainThread; + this.shutDownSignalReceived = false; + Runtime.getRuntime().addShutdownHook(this); + } + + @Override + public void run() { + System.out.println("Shut down signal received."); + this.shutDownSignalReceived = true; + mainThread.interrupt(); + + doSomethingForShutdown(); + + try { + mainThread.join(); //当收到停止信号时,等待mainThread的执行完成 + } catch (InterruptedException ignored) { + } + System.out.println("Shut down complete."); + } + + public boolean shouldShutDown() { + return shutDownSignalReceived; + } + + private void doSomethingForShutdown() { + RocksDBFactory.getInstance().releaseAllGraphDB(); + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/StoreNodeApplication.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/StoreNodeApplication.java new file mode 100644 index 000000000..1f16c5ac1 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/StoreNodeApplication.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node; + +import org.apache.hugegraph.store.node.listener.ContextClosedListener; +import org.apache.hugegraph.store.node.listener.PdConfigureListener; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ConfigurableApplicationContext; + +import com.alipay.remoting.util.StringUtils; + +/** + * + */ +@SpringBootApplication +public class StoreNodeApplication { + + //TODO Is this OK? + private final AppShutdownHook shutdownHook = new AppShutdownHook(Thread.currentThread()); + + public static void main(String[] args) { + start(); + } + + public static void start() { + // 设置solt用到的日志位置 + String logPath = System.getProperty("logging.path"); + if (StringUtils.isBlank(logPath)) { + System.setProperty("logging.path", "logs"); + } + System.setProperty("com.alipay.remoting.client.log.level", "WARN"); + if (System.getProperty("bolt.channel_write_buf_low_water_mark") == null) { + System.setProperty("bolt.channel_write_buf_low_water_mark", + Integer.toString(4 * 1024 * 1024)); + } + if (System.getProperty("bolt.channel_write_buf_high_water_mark") == null) { + System.setProperty("bolt.channel_write_buf_high_water_mark", + Integer.toString(8 * 1024 * 1024)); + } + SpringApplication application = new SpringApplication(StoreNodeApplication.class); + PdConfigureListener listener = new PdConfigureListener(); + ContextClosedListener closedListener = new ContextClosedListener(); + application.addListeners(listener); + application.addListeners(closedListener); + ConfigurableApplicationContext context = application.run(); + listener.setContext(context); + System.out.println("StoreNodeApplication started."); + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/HgStoreMetricsController.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/HgStoreMetricsController.java new file mode 100644 index 000000000..99ff83954 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/HgStoreMetricsController.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.controller; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.hugegraph.store.node.grpc.HgStoreNodeService; +import org.apache.hugegraph.store.node.metrics.DriveMetrics; +import org.apache.hugegraph.store.node.metrics.SystemMetrics; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import com.alipay.sofa.jraft.core.NodeMetrics; + +/** + * 2021/11/23 + */ +@RestController +@RequestMapping(value = "/metrics", method = RequestMethod.GET) +public class HgStoreMetricsController { + private final SystemMetrics systemMetrics = new SystemMetrics(); + private final DriveMetrics driveMetrics = new DriveMetrics(); + @Autowired + HgStoreNodeService nodeService; + + @GetMapping + public Map index() { + return new HashMap<>(); + } + + @GetMapping("system") + public Map> system() { + return this.systemMetrics.metrics(); + } + + @GetMapping("drive") + public Map> drive() { + return this.driveMetrics.metrics(); + } + + @GetMapping("raft") + public Map getRaftMetrics() { + return nodeService.getNodeMetrics(); + } + + +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/HgStoreStatusController.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/HgStoreStatusController.java new file mode 100644 index 000000000..bc2a8483c --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/HgStoreStatusController.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.controller; + +import java.io.Serializable; + +import org.apache.hugegraph.store.grpc.state.ScanState; +import org.apache.hugegraph.store.node.entry.RestResult; +import org.apache.hugegraph.store.node.grpc.HgStoreNodeState; +import org.apache.hugegraph.store.node.grpc.HgStoreStreamImpl; +import org.apache.hugegraph.store.node.model.HgNodeStatus; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +import com.google.protobuf.util.JsonFormat; + +/** + * created on 2021/11/1 + */ +@RestController +public class HgStoreStatusController { + + @Autowired + HgStoreStreamImpl streamImpl; + + @GetMapping("/-/echo") + public HgNodeStatus greeting( + @RequestParam(value = "name", defaultValue = "World") String name) { + return new HgNodeStatus(0, name + " is ok."); + } + + @GetMapping("/-/state") + public HgNodeStatus getState() { + return new HgNodeStatus(0, HgStoreNodeState.getState().name()); + } + + @PutMapping("/-/state") + public HgNodeStatus setState(@RequestParam(value = "name") String name) { + + switch (name) { + case "starting": + HgStoreNodeState.goStarting(); + break; + case "online": + HgStoreNodeState.goOnline(); + break; + case "stopping": + HgStoreNodeState.goStopping(); + break; + default: + return new HgNodeStatus(1000, "invalid parameter: " + name); + } + + return new HgNodeStatus(0, name); + } + + @GetMapping(value = "/-/scan", + produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + public Serializable getScanState() { + RestResult result = new RestResult(); + try { + ScanState state = streamImpl.getState(); + JsonFormat.Printer printer = JsonFormat.printer(); + printer = printer.includingDefaultValueFields().preservingProtoFieldNames(); + return printer.print(state); + } catch (Exception e) { + result.setState(RestResult.ERR); + result.setMessage(e.getMessage()); + return result; + } + } + +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/HgTestController.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/HgTestController.java new file mode 100644 index 000000000..23203ab2f --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/HgTestController.java @@ -0,0 +1,116 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.controller; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.hugegraph.store.PartitionEngine; +import org.apache.hugegraph.store.meta.Partition; +import org.apache.hugegraph.store.meta.Store; +import org.apache.hugegraph.store.node.grpc.HgStoreNodeService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import lombok.extern.slf4j.Slf4j; + +/** + * 仅用于测试 + */ +@RestController +@Slf4j +@RequestMapping("/test") +public class HgTestController { + @Autowired + HgStoreNodeService nodeService; + + @GetMapping(value = "/leaderStore", produces = MediaType.APPLICATION_JSON_VALUE) + public Store testGetStoreInfo() { + + Store store = null; + PartitionEngine engine = nodeService.getStoreEngine().getPartitionEngine(0); + + for (Partition partition : engine.getPartitions().values()) { + store = nodeService.getStoreEngine().getHgCmdClient() + .getStoreInfo(engine.getLeader().toString()); + } + return store; + } + + @GetMapping(value = "/raftRestart/{groupId}", produces = MediaType.APPLICATION_JSON_VALUE) + public String restartRaftNode(@PathVariable(value = "groupId") int groupId) { + PartitionEngine engine = nodeService.getStoreEngine().getPartitionEngine(groupId); + engine.restartRaftNode(); + return "OK"; + } + + @GetMapping(value = "/raftDelete/{groupId}", produces = MediaType.APPLICATION_JSON_VALUE) + public String deleteRaftNode(@PathVariable(value = "groupId") int groupId) { + List graphs = new ArrayList<>(); + PartitionEngine engine = nodeService.getStoreEngine().getPartitionEngine(groupId); + if (engine != null) { + engine.getPartitions().forEach((k, v) -> { + graphs.add(v.getGraphName()); + }); + nodeService.getStoreEngine().destroyPartitionEngine(groupId, graphs); + return "OK"; + } else { + return "未找到分区"; + } + + + } + + @GetMapping(value = "/gc", produces = MediaType.APPLICATION_JSON_VALUE) + public String doGc() { + System.gc(); + return "gc OK!"; + } + + @GetMapping(value = "/flush", produces = MediaType.APPLICATION_JSON_VALUE) + public String doFlush() { + nodeService.getStoreEngine().getBusinessHandler().flushAll(); + return "flush all!"; + } + + @GetMapping(value = "/close", produces = MediaType.APPLICATION_JSON_VALUE) + public String doCloseAll() { + nodeService.getStoreEngine().getBusinessHandler().closeAll(); + return "close all!"; + } + + @GetMapping(value = "/snapshot", produces = MediaType.APPLICATION_JSON_VALUE) + public String doSnapshot() { + nodeService.getStoreEngine().getPartitionEngines().forEach((k, v) -> { + v.snapshot(); + }); + return "snapshot OK!"; + } + + @GetMapping(value = "/compact", produces = MediaType.APPLICATION_JSON_VALUE) + public String dbCompaction() { + nodeService.getStoreEngine().getPartitionEngines().forEach((k, v) -> { + nodeService.getStoreEngine().getBusinessHandler().dbCompaction("", k); + }); + return "snapshot OK!"; + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/IndexAPI.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/IndexAPI.java new file mode 100644 index 000000000..ede36b7f5 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/IndexAPI.java @@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.controller; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.store.meta.Partition; +import org.apache.hugegraph.store.metric.HgStoreMetric; +import org.apache.hugegraph.store.node.grpc.HgStoreNodeService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.alipay.sofa.jraft.entity.PeerId; +import com.alipay.sofa.jraft.util.Endpoint; + +import lombok.Data; +import lombok.extern.slf4j.Slf4j; + +@RestController +@Slf4j +@RequestMapping("/") +public class IndexAPI { + @Autowired + HgStoreNodeService nodeService; + + @GetMapping(value = "/", produces = "application/json") + public StoreInfo index() { + StoreInfo info = new StoreInfo(); + info.leaderCount = nodeService.getStoreEngine().getLeaderPartition().size(); + info.partitionCount = nodeService.getStoreEngine().getPartitionEngines().size(); + return info; + } + + public Map okMap(String k, Object v) { + Map map = new HashMap<>(); + map.put("status", 0); + map.put(k, v); + return map; + } + + @Data + class StoreInfo { + private int leaderCount; + private int partitionCount; + } + + @Data + public class Raft { + private final List partitions = new ArrayList<>(); + private int groupId; + private String role; + private String conf; + private Endpoint leader; + private long logIndex; + private List peers; + private List learners; + } + + @Data + public class PartitionInfo { + private final int id; // region id + private final String graphName; + // Region key range [startKey, endKey) + private final long startKey; + private final long endKey; + private final String version; + private final Metapb.PartitionState workState; + private HgStoreMetric.Partition metric; + private String leader; + + + public PartitionInfo(Partition pt) { + id = pt.getId(); + graphName = pt.getGraphName(); + startKey = pt.getStartKey(); + endKey = pt.getEndKey(); + + workState = pt.getWorkState(); + version = String.valueOf(pt.getVersion()); + + } + } +} + diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/PartitionAPI.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/PartitionAPI.java new file mode 100644 index 000000000..43118d627 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/controller/PartitionAPI.java @@ -0,0 +1,248 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.controller; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.hugegraph.pd.common.PDException; +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.rocksdb.access.RocksDBSession; +import org.apache.hugegraph.rocksdb.access.ScanIterator; +import org.apache.hugegraph.store.HgStoreEngine; +import org.apache.hugegraph.store.PartitionEngine; +import org.apache.hugegraph.store.business.BusinessHandler; +import org.apache.hugegraph.store.business.InnerKeyCreator; +import org.apache.hugegraph.store.meta.Partition; +import org.apache.hugegraph.store.metric.HgStoreMetric; +import org.apache.hugegraph.store.node.AppConfig; +import org.apache.hugegraph.store.node.grpc.HgStoreNodeService; +import org.apache.hugegraph.util.Bytes; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import com.alipay.sofa.jraft.entity.PeerId; +import com.alipay.sofa.jraft.util.Endpoint; +import com.taobao.arthas.agent.attach.ArthasAgent; + +import lombok.Data; +import lombok.extern.slf4j.Slf4j; + +@RestController +@Slf4j +@RequestMapping("/v1") +public class PartitionAPI { + @Autowired + HgStoreNodeService nodeService; + + @Autowired + AppConfig appConfig; + + @GetMapping(value = "/partitions", produces = "application/json") + public Map getPartitions( + @RequestParam(required = false, defaultValue = "") String flags) { + + boolean accurate = false; + if (!flags.isEmpty()) { + List flagList = Arrays.asList(flags.split(",")); + if (flagList.contains("accurate")) { + accurate = true; + } + } + + List rafts = new ArrayList<>(); + HgStoreEngine storeEngine = nodeService.getStoreEngine(); + + BusinessHandler businessHandler = storeEngine.getBusinessHandler(); + Map partitionEngines = storeEngine.getPartitionEngines(); + + for (Map.Entry engineEntry : partitionEngines.entrySet()) { + PartitionEngine engine = engineEntry.getValue(); + Raft raft = new Raft(); + raft.setGroupId(engine.getGroupId()); + raft.setLeader(engine.getLeader()); + raft.setRole(engine.getRaftNode().getNodeState().name()); + raft.setConf(engine.getCurrentConf().toString()); + if (engine.isLeader()) { + raft.setPeers(engine.getRaftNode().listPeers()); + raft.setLearners(engine.getRaftNode().listLearners()); + } + raft.setTerm(engine.getLeaderTerm()); + raft.setLogIndex(engine.getCommittedIndex()); + raft.setPartitionCount(engine.getPartitions().size()); + for (Map.Entry partitionEntry : engine.getPartitions().entrySet()) { + String graphName = partitionEntry.getKey(); + Partition pt = partitionEntry.getValue(); + PartitionInfo partition = new PartitionInfo(pt); + // 此处为了打开所有的图,metric只返回已打开的图 + businessHandler.getLatestSequenceNumber(graphName, pt.getId()); + partition.setMetric( + businessHandler.getPartitionMetric(graphName, pt.getId(), accurate)); + partition.setLeader(pt.isLeader() == engine.isLeader() ? "OK" : "Error"); + raft.getPartitions().add(partition); + } + rafts.add(raft); + } + + return okMap("partitions", rafts); + } + + @GetMapping(value = "/partition/{id}", produces = "application/json") + public Raft getPartition(@PathVariable(value = "id") int id) { + + HgStoreEngine storeEngine = nodeService.getStoreEngine(); + + BusinessHandler businessHandler = storeEngine.getBusinessHandler(); + PartitionEngine engine = storeEngine.getPartitionEngine(id); + + Raft raft = new Raft(); + raft.setGroupId(engine.getGroupId()); + raft.setLeader(engine.getLeader()); + raft.setRole(engine.getRaftNode().getNodeState().name()); + if (engine.isLeader()) { + raft.setPeers(engine.getRaftNode().listPeers()); + raft.setLearners(engine.getRaftNode().listLearners()); + } + raft.setLogIndex(engine.getCommittedIndex()); + for (Map.Entry partitionEntry : engine.getPartitions().entrySet()) { + String graphName = partitionEntry.getKey(); + Partition pt = partitionEntry.getValue(); + PartitionInfo partition = new PartitionInfo(pt); + partition.setMetric(businessHandler.getPartitionMetric(graphName, pt.getId(), false)); + + raft.getPartitions().add(partition); + } + + return raft; + //return okMap("partition", rafts); + } + + /** + * 打印分区的所有key + */ + @GetMapping(value = "/partition/dump/{id}", produces = MediaType.APPLICATION_JSON_VALUE) + public Map dumpPartition(@PathVariable(value = "id") int id) throws + PDException { + HgStoreEngine storeEngine = nodeService.getStoreEngine(); + BusinessHandler handler = storeEngine.getBusinessHandler(); + InnerKeyCreator innerKeyCreator = new InnerKeyCreator(handler); + storeEngine.getPartitionEngine(id).getPartitions().forEach((graph, partition) -> { + log.info("{}----------------------------", graph); + ScanIterator cfIterator = handler.scanRaw(graph, partition.getId(), 0); + while (cfIterator.hasNext()) { + try (ScanIterator iterator = cfIterator.next()) { + byte[] cfName = cfIterator.position(); + log.info("\t{}", new String(cfName)); + while (iterator.hasNext()) { + RocksDBSession.BackendColumn col = iterator.next(); + int keyCode = innerKeyCreator.parseKeyCode(col.name); + log.info("\t\t{} --key={}, code={} ", new String(col.name), + Bytes.toHex(col.name), keyCode); + } + } + } + cfIterator.close(); + }); + return okMap("ok", null); + } + + /** + * 打印分区的所有key + */ + @GetMapping(value = "/partition/clean/{id}", produces = MediaType.APPLICATION_JSON_VALUE) + public Map cleanPartition(@PathVariable(value = "id") int id) throws + PDException { + HgStoreEngine storeEngine = nodeService.getStoreEngine(); + BusinessHandler handler = storeEngine.getBusinessHandler(); + + storeEngine.getPartitionEngine(id).getPartitions().forEach((graph, partition) -> { + handler.cleanPartition(graph, id); + }); + return okMap("ok", null); + } + + @GetMapping(value = "/arthasstart", produces = "application/json") + public Map arthasstart( + @RequestParam(required = false, defaultValue = "") String flags) { + HashMap configMap = new HashMap<>(); + configMap.put("arthas.telnetPort", appConfig.getArthasConfig().getTelnetPort()); + configMap.put("arthas.httpPort", appConfig.getArthasConfig().getHttpPort()); + configMap.put("arthas.ip", appConfig.getArthasConfig().getArthasip()); + configMap.put("arthas.disabledCommands", appConfig.getArthasConfig().getDisCmd()); + ArthasAgent.attach(configMap); +// DashResponse retPose = new DashResponse(); + List ret = new ArrayList<>(); + ret.add("Arthas 启动成功"); + return okMap("arthasstart", ret); + } + + public Map okMap(String k, Object v) { + Map map = new HashMap<>(); + map.put("status", 0); + map.put(k, v); + return map; + } + + @Data + public class Raft { + private final List partitions = new ArrayList<>(); + private int groupId; + private String role; + private String conf; + private Endpoint leader; + private long term; + private long logIndex; + private List peers; + private List learners; + private int partitionCount; + } + + @Data + public class PartitionInfo { + private final int id; // region id + private final String graphName; + // Region key range [startKey, endKey) + private final long startKey; + private final long endKey; + private final String version; + private final Metapb.PartitionState workState; + private HgStoreMetric.Partition metric; + private String leader; + + + public PartitionInfo(Partition pt) { + id = pt.getId(); + graphName = pt.getGraphName(); + startKey = pt.getStartKey(); + endKey = pt.getEndKey(); + + workState = pt.getWorkState(); + version = String.valueOf(pt.getVersion()); + + } + } +} + diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/entry/RestResult.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/entry/RestResult.java new file mode 100644 index 000000000..5d65cd271 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/entry/RestResult.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.entry; + +import java.io.Serializable; + +import lombok.Data; + +@Data +public class RestResult implements Serializable { + public static final String OK = "OK"; + public static final String ERR = "ERR"; + String state; + String message; + Serializable data; +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/BatchGrpcClosure.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/BatchGrpcClosure.java new file mode 100644 index 000000000..80ea6bdcf --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/BatchGrpcClosure.java @@ -0,0 +1,212 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.grpc; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import java.util.function.Function; + +import org.apache.commons.collections.CollectionUtils; +import org.apache.hugegraph.store.grpc.common.ResCode; +import org.apache.hugegraph.store.grpc.common.ResStatus; +import org.apache.hugegraph.store.grpc.session.FeedbackRes; +import org.apache.hugegraph.store.grpc.session.PartitionFaultResponse; +import org.apache.hugegraph.store.grpc.session.PartitionFaultType; +import org.apache.hugegraph.store.grpc.session.PartitionLeader; +import org.apache.hugegraph.store.raft.RaftClosure; +import org.apache.hugegraph.store.util.HgRaftError; + +import com.alipay.sofa.jraft.Status; + +import io.grpc.stub.StreamObserver; +import lombok.extern.slf4j.Slf4j; + +/** + * 批量处理的grpc回调封装类 + * + * @param + */ +@Slf4j +class BatchGrpcClosure { + private final CountDownLatch countDownLatch; + private final List errorStatus; + private final List results; + private final Map leaderMap; + + public BatchGrpcClosure(int count) { + countDownLatch = new CountDownLatch(count); + errorStatus = Collections.synchronizedList(new ArrayList<>()); + results = Collections.synchronizedList(new ArrayList<>()); + leaderMap = new ConcurrentHashMap<>(); + } + + public RaftClosure newRaftClosure() { + return new GrpcClosure() { + @Override + public void run(Status status) { + countDownLatch.countDown(); + if (status.isOk()) { + results.add(this.getResult()); + } else { + leaderMap.putAll(this.getLeaderMap()); + errorStatus.add(status); + } + } + }; + } + + public RaftClosure newRaftClosure(Consumer ok) { + return new GrpcClosure() { + @Override + public void run(Status status) { + countDownLatch.countDown(); + if (status.isOk()) { + results.add(this.getResult()); + } else { + leaderMap.putAll(this.getLeaderMap()); + errorStatus.add(status); + } + ok.accept(status); + } + }; + } + + /** + * 不使用计数器latch + * + * @return + */ + public RaftClosure newClosureNoLatch() { + return new GrpcClosure() { + @Override + public void run(Status status) { + if (status.isOk()) { + results.add(this.getResult()); + } else { + leaderMap.putAll(this.getLeaderMap()); + errorStatus.add(status); + } + } + }; + } + + + public PartitionFaultResponse getErrorResponse() { + PartitionFaultResponse errorResponse; + + if (leaderMap.size() > 0) { + PartitionFaultResponse.Builder partitionFault = + PartitionFaultResponse.newBuilder().setFaultType( + PartitionFaultType.PARTITION_FAULT_TYPE_NOT_LEADER); + leaderMap.forEach((k, v) -> { + partitionFault.addPartitionLeaders(PartitionLeader.newBuilder() + .setPartitionId(k) + .setLeaderId(v).build()); + }); + errorResponse = partitionFault.build(); + } else { + PartitionFaultType faultType = PartitionFaultType.PARTITION_FAULT_TYPE_UNKNOWN; + switch (HgRaftError.forNumber(errorStatus.get(0).getCode())) { + case NOT_LEADER: + faultType = PartitionFaultType.PARTITION_FAULT_TYPE_NOT_LEADER; + break; + case WAIT_LEADER_TIMEOUT: + faultType = PartitionFaultType.PARTITION_FAULT_TYPE_WAIT_LEADER_TIMEOUT; + break; + case NOT_LOCAL: + faultType = PartitionFaultType.PARTITION_FAULT_TYPE_NOT_LOCAL; + break; + default: + log.error("Unmatchable errorStatus: " + errorStatus); + } + errorResponse = PartitionFaultResponse.newBuilder().setFaultType(faultType).build(); + } + return errorResponse; + } + + public String getErrorMsg() { + StringBuilder builder = new StringBuilder(); + errorStatus.forEach(status -> { + if (!status.isOk()) { + builder.append(status.getErrorMsg()); + builder.append("\n"); + } + }); + return builder.toString(); + } + + /** + * 等待raft执行结束,返回结果给grpc + */ + public void waitFinish(StreamObserver observer, Function, V> ok, long timeout) { + try { + countDownLatch.await(timeout, TimeUnit.MILLISECONDS); + + if (errorStatus.isEmpty()) { // 没有错误时,合并结果 + observer.onNext(ok.apply(results)); + } else { + observer.onNext((V) FeedbackRes.newBuilder() + .setStatus(ResStatus.newBuilder() + .setCode(ResCode.RES_CODE_FAIL) + .setMsg(getErrorMsg())) + .setPartitionFaultResponse(this.getErrorResponse()) + .build()); + } + } catch (InterruptedException e) { + log.error("waitFinish exception: ", e); + observer.onNext((V) FeedbackRes.newBuilder() + .setStatus(ResStatus.newBuilder() + .setCode(ResCode.RES_CODE_FAIL) + .setMsg(e.getLocalizedMessage()) + .build()).build()); + } + observer.onCompleted(); + } + + /** + * 从多个结果中选择一个错误的结果返回,如果没有错误,返回第一个 + */ + public FeedbackRes selectError(List results) { + if (!CollectionUtils.isEmpty(results)) { + AtomicReference res = new AtomicReference<>(results.get(0)); + results.forEach(e -> { + try { + if (e.getStatus().getCode() != ResCode.RES_CODE_OK) { + res.set(e); + } + } catch (Exception ex) { + log.error("{}", ex); + } + }); + return res.get(); + } else { + return FeedbackRes.newBuilder() + .setStatus(ResStatus.newBuilder() + .setCode(ResCode.RES_CODE_OK).build()) + .build(); + } + + } +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/BatchScanIterator.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/BatchScanIterator.java new file mode 100644 index 000000000..dbbb7a670 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/BatchScanIterator.java @@ -0,0 +1,160 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.grpc; + +import java.util.NoSuchElementException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; + +import javax.annotation.concurrent.NotThreadSafe; + +import org.apache.hugegraph.pd.common.KVPair; +import org.apache.hugegraph.rocksdb.access.ScanIterator; +import org.apache.hugegraph.store.node.util.HgAssert; +import org.apache.hugegraph.store.node.util.HgStoreConst; + +/** + * 2022/2/28 + * + * @version 0.3.0 added limit support + */ +@NotThreadSafe +public final class BatchScanIterator implements ScanIterator { + private final Supplier> batchSupplier; + private final Supplier limitSupplier; + private final AtomicBoolean closed = new AtomicBoolean(); + private ScanIterator iterator; + private boolean hasNext = false; + private long curCount; + private long curLimit; + + private BatchScanIterator(Supplier> iteratorSupplier, + Supplier limitSupplier) { + this.batchSupplier = iteratorSupplier; + this.limitSupplier = limitSupplier; + } + + public static BatchScanIterator of( + Supplier> iteratorSupplier, + Supplier limitSupplier) { + HgAssert.isArgumentNotNull(iteratorSupplier, "iteratorSupplier"); + HgAssert.isArgumentNotNull(limitSupplier, "limitSupplier"); + return new BatchScanIterator(iteratorSupplier, limitSupplier); + } + + private ScanIterator getIterator() { + ScanIterator buf; + int count = 0; + this.curCount = 0L; + + do { + buf = this.batchSupplier.get().getValue(); + + if (buf == null) { + break; + } + + if (!buf.hasNext()) { + buf.close(); + buf = null; + } + + if (++count == Integer.MAX_VALUE) { + throw new RuntimeException("Do loop times more than Integer.MAX_VALUE"); + } + + } while (buf == null); + + if (buf != null) { + Long limit = this.limitSupplier.get(); + + if (limit == null || limit <= 0) { + this.curLimit = Integer.MAX_VALUE; + } else { + this.curLimit = limit; + } + + } + + return buf; + + } + + @Override + public boolean hasNext() { + + if (this.iterator == null) { + this.iterator = this.getIterator(); + } else if (!this.iterator.hasNext()) { + this.iterator.close(); + this.iterator = this.getIterator(); + } else if (this.curCount == this.curLimit) { + this.iterator.close(); + this.iterator = this.getIterator(); + } + + if (this.iterator == null) { + return false; + } else { + this.hasNext = true; + return true; + } + } + + @Override + public T next() { + if (this.hasNext) { + this.hasNext = false; + } else { + if (!this.hasNext()) { + throw new NoSuchElementException(); + } + } + + this.curCount++; + + return this.iterator.next(); + } + + @Override + public void close() { + if (!this.closed.getAndSet(true)) { + if (this.iterator != null) { + this.iterator.close(); + } + } + } + + @Override + public boolean isValid() { + throw new UnsupportedOperationException(); + } + + @Override + public long count() { + throw new UnsupportedOperationException(); + } + + @Override + public byte[] position() { + if (this.iterator != null) { + return this.iterator.position(); + } + return HgStoreConst.EMPTY_BYTES; + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/EmptyIterator.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/EmptyIterator.java new file mode 100644 index 000000000..6df461fc0 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/EmptyIterator.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.grpc; + +import org.apache.hugegraph.rocksdb.access.ScanIterator; + +/** + * 2021/11/29 + */ +final class EmptyIterator implements ScanIterator { + + @Override + public boolean hasNext() { + return false; + } + + @Override + public boolean isValid() { + return false; + } + + @Override + public T next() { + return null; + } + + @Override + public long count() { + return 0; + } + + @Override + public byte[] position() { + return new byte[0]; + } + + @Override + public void close() { + + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/FusingScanIterator.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/FusingScanIterator.java new file mode 100644 index 000000000..0e31a9c2c --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/FusingScanIterator.java @@ -0,0 +1,120 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.grpc; + +import java.util.NoSuchElementException; +import java.util.function.Supplier; + +import org.apache.hugegraph.rocksdb.access.ScanIterator; + +/** + * This is a wrapper of the ScanIterator that provides a mechanism + * to set a threshold value in order to abort the iterating operation. + *

+ * 2023/2/8 + */ +final class FusingScanIterator implements ScanIterator { + public static final byte[] EMPTY_BYTES = new byte[0]; + private long max; + private long accumulator; + private Supplier supplier; + private ScanIterator iterator; + private byte[] position = EMPTY_BYTES; + + private FusingScanIterator() { + } + + public static FusingScanIterator maxOf(long maxThreshold, + Supplier iteratorSupplier) { + FusingScanIterator res = new FusingScanIterator(); + res.max = maxThreshold; + res.supplier = iteratorSupplier; + return res; + } + + private ScanIterator getIterator() { + ScanIterator buf = this.supplier.get(); + if (buf == null) { + return null; + } + if (!buf.hasNext()) { + buf = null; + } + return buf; + } + + private void init() { + if (this.iterator == null) { + this.iterator = this.getIterator(); + } + } + + @Override + public boolean hasNext() { + if (this.isThresholdExceeded()) { + return false; + } + if (this.iterator == null) { + this.iterator = this.getIterator(); + } + return this.iterator != null; + } + + @Override + public boolean isValid() { + return hasNext(); + } + + @Override + public byte[] position() { + return this.position; + } + + /** + * @return true, when the threshold is exceeded. + */ + private boolean isThresholdExceeded() { + return this.accumulator >= this.max; + } + + @Override + public T next() { + if (this.isThresholdExceeded()) { + throw new NoSuchElementException(); + } + this.init(); + if (this.iterator == null) { + throw new NoSuchElementException(); + } + T t = this.iterator.next(); + position = this.iterator.position(); + this.accumulator++; + if (!this.iterator.hasNext() || this.isThresholdExceeded()) { + this.iterator.close(); + this.iterator = null; + } + return t; + } + + @Override + public void close() { + if (this.iterator != null) { + this.iterator.close(); + } + } +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/GRpcServerConfig.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/GRpcServerConfig.java new file mode 100644 index 000000000..68b1809f7 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/GRpcServerConfig.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.grpc; + +import org.apache.hugegraph.store.node.AppConfig; +import org.apache.hugegraph.store.node.util.HgExecutorUtil; +import org.lognet.springboot.grpc.GRpcServerBuilderConfigurer; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import io.grpc.ServerBuilder; + +/** + * 2022/3/4 + */ +@Component +public class GRpcServerConfig extends GRpcServerBuilderConfigurer { + public final static String EXECUTOR_NAME = "hg-grpc"; + @Autowired + private AppConfig appConfig; + + @Override + public void configure(ServerBuilder serverBuilder) { + AppConfig.ThreadPoolGrpc grpc = appConfig.getThreadPoolGrpc(); + serverBuilder.executor( + HgExecutorUtil.createExecutor(EXECUTOR_NAME, grpc.getCore(), grpc.getMax(), + grpc.getQueue()) + ); + } + +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/GrpcClosure.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/GrpcClosure.java new file mode 100644 index 000000000..14f39b3f5 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/GrpcClosure.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.grpc; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.hugegraph.store.grpc.session.FeedbackRes; +import org.apache.hugegraph.store.raft.RaftClosure; + +import io.grpc.stub.StreamObserver; + +/** + * 2022/1/27 + */ + +abstract class GrpcClosure implements RaftClosure { + private final Map leaderMap = new HashMap<>(); + private V result; + + /** + * 设置输出结果给raftClosure,对于Follower来说,raftClosure为空 + */ + public static void setResult(RaftClosure raftClosure, V result) { + GrpcClosure closure = (GrpcClosure) raftClosure; + if (closure != null) { + closure.setResult(result); + } + } + + public static RaftClosure newRaftClosure(StreamObserver observer) { + BatchGrpcClosure wrap = new BatchGrpcClosure<>(0); + return wrap.newRaftClosure(s -> { + wrap.waitFinish(observer, r -> { + return (V) wrap.selectError((List) r); + }, 0); + }); + } + + public V getResult() { + return result; + } + + public void setResult(V result) { + this.result = result; + } + + public Map getLeaderMap() { + return leaderMap; + } + + @Override + public void onLeaderChanged(Integer partId, Long storeId) { + leaderMap.put(partId, storeId); + } +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreNodeService.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreNodeService.java new file mode 100644 index 000000000..c0e19df41 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreNodeService.java @@ -0,0 +1,237 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.grpc; + +import static org.apache.hugegraph.store.grpc.common.GraphMethod.GRAPH_METHOD_DELETE; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; + +import org.apache.hugegraph.store.HgStoreEngine; +import org.apache.hugegraph.store.business.DefaultDataMover; +import org.apache.hugegraph.store.grpc.session.BatchReq; +import org.apache.hugegraph.store.grpc.session.CleanReq; +import org.apache.hugegraph.store.grpc.session.GraphReq; +import org.apache.hugegraph.store.grpc.session.TableReq; +import org.apache.hugegraph.store.node.AppConfig; +import org.apache.hugegraph.store.options.HgStoreEngineOptions; +import org.apache.hugegraph.store.options.RaftRocksdbOptions; +import org.apache.hugegraph.store.raft.RaftClosure; +import org.apache.hugegraph.store.raft.RaftOperation; +import org.apache.hugegraph.store.raft.RaftTaskHandler; +import org.apache.hugegraph.store.util.HgRaftError; +import org.apache.hugegraph.store.util.HgStoreException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.alipay.sofa.jraft.Status; +import com.alipay.sofa.jraft.core.NodeMetrics; +import com.google.protobuf.CodedInputStream; +import com.google.protobuf.CodedOutputStream; + +import lombok.extern.slf4j.Slf4j; + +/** + * @projectName: raft task executor + */ +@Slf4j +@Service +public class HgStoreNodeService implements RaftTaskHandler { + public static final byte BATCH_OP = 0x12; + public static final byte TABLE_OP = 0x13; + public static final byte GRAPH_OP = 0x14; + public static final byte CLEAN_OP = 0x15; + + + public static final byte MAX_OP = 0x59; + private final AppConfig appConfig; + @Autowired + HgStoreSessionImpl hgStoreSession; + private HgStoreEngine storeEngine; + + public HgStoreNodeService(@Autowired AppConfig appConfig) { + this.appConfig = appConfig; + } + + public HgStoreEngine getStoreEngine() { + return this.storeEngine; + } + + @PostConstruct + public void init() { + log.info("{}", appConfig.toString()); + HgStoreEngineOptions options = new HgStoreEngineOptions() {{ + setRaftAddress(appConfig.getRaft().getAddress()); + setDataPath(appConfig.getDataPath()); + setRaftPath(appConfig.getRaftPath()); + setPdAddress(appConfig.getPdServerAddress()); + setFakePD(appConfig.isFakePd()); + setRocksdbConfig(appConfig.getRocksdbConfig()); + setGrpcAddress(appConfig.getStoreServerAddress()); + setLabels(appConfig.getLabelConfig().getLabel()); + setRaftOptions(new RaftOptions() {{ + setMetrics(appConfig.getRaft().isMetrics()); + setRpcDefaultTimeout(appConfig.getRaft().getRpcTimeOut()); + setSnapshotLogIndexMargin(appConfig.getRaft().getSnapshotLogIndexMargin()); + setSnapshotIntervalSecs(appConfig.getRaft().getSnapshotInterval()); + setDisruptorBufferSize(appConfig.getRaft().getDisruptorBufferSize()); + setMaxLogSize(appConfig.getRaft().getMaxLogFileSize()); + setAveLogEntrySizeRatio(appConfig.getRaft().getAveLogEntrySizeRation()); + setUseRocksDBSegmentLogStorage(appConfig.getRaft() + .isUseRocksDBSegmentLogStorage()); + setMaxSegmentFileSize(appConfig.getRaft().getMaxSegmentFileSize()); + setMaxReplicatorInflightMsgs(appConfig.getRaft().getMaxReplicatorInflightMsgs()); + }}); + setFakePdOptions(new FakePdOptions() {{ + setStoreList(appConfig.getFakePdConfig().getStoreList()); + setPeersList(appConfig.getFakePdConfig().getPeersList()); + setPartitionCount(appConfig.getFakePdConfig().getPartitionCount()); + setShardCount(appConfig.getFakePdConfig().getShardCount()); + }}); + }}; + + RaftRocksdbOptions.initRocksdbGlobalConfig(options.getRocksdbConfig()); + + options.getLabels().put("rest.port", Integer.toString(appConfig.getRestPort())); + log.info("HgStoreEngine init {}", options); + options.setTaskHandler(this); + options.setDataTransfer(new DefaultDataMover()); + storeEngine = HgStoreEngine.getInstance(); + storeEngine.init(options); + + } + + public List getGraphLeaderPartitionIds(String graphName) { + return storeEngine.getPartitionManager().getLeaderPartitionIds(graphName); + } + + /** + * 添加raft 任务,转发数据给raft + * + * @return true 表示数据已被提交,false表示未提交,用于单副本入库减少批次拆分 + */ + public + void addRaftTask(byte methodId, String graphName, Integer partitionId, Req req, + RaftClosure closure) { + if (!storeEngine.isClusterReady()) { + closure.run(new Status(HgRaftError.CLUSTER_NOT_READY.getNumber(), + "The cluster is not ready, please check active stores number!")); + log.error("The cluster is not ready, please check active stores number!"); + return; + } + // + try { + // 序列化, + final byte[] buffer = new byte[req.getSerializedSize() + 1]; + final CodedOutputStream output = CodedOutputStream.newInstance(buffer); + output.write(methodId); + req.writeTo(output); + output.checkNoSpaceLeft(); + output.flush(); + // 传送给raft + storeEngine.addRaftTask(graphName, partitionId, + RaftOperation.create(methodId, buffer, req), closure); + + } catch (Exception e) { + closure.run(new Status(HgRaftError.UNKNOWN.getNumber(), e.getMessage())); + log.error("addRaftTask {}", e); + } + + } + + /** + * 来自日志的任务,一般是follower 或者 日志回滚的任务 + */ + @Override + public boolean invoke(int partId, byte[] request, RaftClosure response) throws + HgStoreException { + try { + CodedInputStream input = CodedInputStream.newInstance(request); + byte methodId = input.readRawByte(); + switch (methodId) { + case HgStoreNodeService.BATCH_OP: + invoke(partId, methodId, BatchReq.parseFrom(input), response); + break; + case HgStoreNodeService.TABLE_OP: + invoke(partId, methodId, TableReq.parseFrom(input), response); + break; + case HgStoreNodeService.GRAPH_OP: + invoke(partId, methodId, GraphReq.parseFrom(input), response); + break; + case HgStoreNodeService.CLEAN_OP: + invoke(partId, methodId, CleanReq.parseFrom(input), response); + break; + default: + return false; // 未处理 + } + } catch (IOException e) { + throw new HgStoreException(e.getMessage(), e); + } + return true; + } + + /** + * 处理raft传送过来的数据 + */ + @Override + public boolean invoke(int partId, byte methodId, Object req, RaftClosure response) throws + HgStoreException { + switch (methodId) { + case HgStoreNodeService.BATCH_OP: + hgStoreSession.doBatch(partId, (BatchReq) req, response); + break; + case HgStoreNodeService.TABLE_OP: + hgStoreSession.doTable(partId, (TableReq) req, response); + break; + case HgStoreNodeService.GRAPH_OP: + if (((GraphReq) req).getMethod() == GRAPH_METHOD_DELETE) { + storeEngine.deletePartition(partId, ((GraphReq) req).getGraphName()); + } + hgStoreSession.doGraph(partId, (GraphReq) req, response); + break; + case HgStoreNodeService.CLEAN_OP: + hgStoreSession.doClean(partId, (CleanReq) req, response); + break; + default: + return false; // 未处理 + } + return true; + } + + @PreDestroy + public void destroy() { + storeEngine.shutdown(); + } + + + private String getSerializingExceptionMessage(String target) { + return "Serializing " + + getClass().getName() + + " to a " + + target + + " threw an IOException (should never happen)."; + } + + public Map getNodeMetrics() { + return storeEngine.getNodeMetrics(); + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreNodeState.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreNodeState.java new file mode 100644 index 000000000..823ee4f94 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreNodeState.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.grpc; + +import javax.annotation.concurrent.ThreadSafe; + +import org.apache.hugegraph.store.grpc.state.NodeStateType; + +/** + * created on 2021/11/3 + */ +@ThreadSafe +public final class HgStoreNodeState { + + private static NodeStateType curState = NodeStateType.STARTING; + + public static NodeStateType getState() { + return curState; + } + + private static void setState(NodeStateType state) { + curState = state; + change(); + } + + private static void change() { + HgStoreStateSubject.notifyAll(curState); + } + + public static void goOnline() { + setState(NodeStateType.ONLINE); + } + + public static void goStarting() { + setState(NodeStateType.STARTING); + } + + public static void goStopping() { + setState(NodeStateType.STOPPING); + } + +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreSessionImpl.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreSessionImpl.java new file mode 100644 index 000000000..981715ef0 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreSessionImpl.java @@ -0,0 +1,551 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.grpc; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.hugegraph.pd.common.PDException; +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.pd.grpc.Metapb.GraphMode; +import org.apache.hugegraph.rocksdb.access.ScanIterator; +import org.apache.hugegraph.store.business.BusinessHandler; +import org.apache.hugegraph.store.grpc.common.Key; +import org.apache.hugegraph.store.grpc.common.Kv; +import org.apache.hugegraph.store.grpc.common.ResCode; +import org.apache.hugegraph.store.grpc.common.ResStatus; +import org.apache.hugegraph.store.grpc.session.Agg; +import org.apache.hugegraph.store.grpc.session.BatchEntry; +import org.apache.hugegraph.store.grpc.session.BatchGetReq; +import org.apache.hugegraph.store.grpc.session.BatchReq; +import org.apache.hugegraph.store.grpc.session.BatchWriteReq; +import org.apache.hugegraph.store.grpc.session.CleanReq; +import org.apache.hugegraph.store.grpc.session.FeedbackRes; +import org.apache.hugegraph.store.grpc.session.GetReq; +import org.apache.hugegraph.store.grpc.session.GraphReq; +import org.apache.hugegraph.store.grpc.session.HgStoreSessionGrpc; +import org.apache.hugegraph.store.grpc.session.KeyValueResponse; +import org.apache.hugegraph.store.grpc.session.TableReq; +import org.apache.hugegraph.store.grpc.session.ValueResponse; +import org.apache.hugegraph.store.grpc.stream.ScanStreamReq; +import org.apache.hugegraph.store.meta.Graph; +import org.apache.hugegraph.store.meta.GraphManager; +import org.apache.hugegraph.store.node.AppConfig; +import org.apache.hugegraph.store.node.util.HgGrpc; +import org.apache.hugegraph.store.node.util.HgStoreNodeUtil; +import org.apache.hugegraph.store.pd.PdProvider; +import org.apache.hugegraph.store.raft.RaftClosure; +import org.apache.hugegraph.store.util.HgStoreConst; +import org.lognet.springboot.grpc.GRpcService; +import org.springframework.beans.factory.annotation.Autowired; + +import com.google.protobuf.ByteString; + +import io.grpc.stub.StreamObserver; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@GRpcService +public class HgStoreSessionImpl extends HgStoreSessionGrpc.HgStoreSessionImplBase { + @Autowired() + private AppConfig appConfig; + @Autowired + private HgStoreNodeService storeService; + private HgStoreWrapperEx wrapper; + private PdProvider pdProvider; + + private HgStoreWrapperEx getWrapper() { + if (this.wrapper == null) { + synchronized (this) { + if (this.wrapper == null) { + this.wrapper = new HgStoreWrapperEx( + storeService.getStoreEngine().getBusinessHandler()); + } + } + } + return this.wrapper; + } + + private PdProvider getPD() { + if (pdProvider == null) { + synchronized (this) { + if (pdProvider == null) { + pdProvider = storeService.getStoreEngine().getPdProvider(); + } + } + } + return pdProvider; + } + + @Override + public void get2(GetReq request, StreamObserver responseObserver) { + String graph = request.getHeader().getGraph(); + String table = request.getTk().getTable(); + byte[] key = request.getTk().getKey().toByteArray(); + int code = request.getTk().getCode(); + byte[] value = getWrapper().doGet(graph, code, table, key); + + FeedbackRes.Builder builder = FeedbackRes.newBuilder(); + + FeedbackRes res = null; + if (value != null) { + res = builder.setStatus(HgGrpc.success()) + .setValueResponse( + ValueResponse.newBuilder() + .setValue(ByteString.copyFrom(value)) + ).build(); + + } else { + res = builder.setStatus(HgGrpc.success()) + .setStatus(HgGrpc.not()) + .build(); + } + + responseObserver.onNext(res); + responseObserver.onCompleted(); + } + + @Override + public void clean(CleanReq request, + StreamObserver responseObserver) { + + String graph = request.getHeader().getGraph(); + int partition = request.getPartition(); + // 发给不同的raft执行 + BatchGrpcClosure closure = new BatchGrpcClosure<>(1); + storeService.addRaftTask(HgStoreNodeService.CLEAN_OP, graph, partition, + request, + closure.newRaftClosure()); + // 等待返回结果 + closure.waitFinish(responseObserver, r -> closure.selectError(r), + appConfig.getRaft().getRpcTimeOut()); + } + + public void doClean(int partId, CleanReq request, RaftClosure response) { + String graph = request.getHeader().getGraph(); + FeedbackRes.Builder builder = FeedbackRes.newBuilder(); + try { + if (getWrapper().doClean(graph, partId)) { + builder.setStatus(HgGrpc.success()); + } else { + builder.setStatus(HgGrpc.not()); + } + } catch (Throwable t) { + String msg = "Failed to doClean, graph: " + graph + "; partitionId = " + partId; + log.error(msg, t); + builder.setStatus(HgGrpc.fail(msg)); + } + GrpcClosure.setResult(response, builder.build()); + } + + @Override + public void batchGet2(BatchGetReq request, StreamObserver responseObserver) { + String graph = request.getHeader().getGraph(); + String table = request.getTable(); + FeedbackRes.Builder builder = FeedbackRes.newBuilder(); + + List keyList = request.getKeyList(); + if (keyList == null || keyList.isEmpty()) { + builder.setStatus(HgGrpc.fail("keys is empty")); + responseObserver.onNext(builder.build()); + responseObserver.onCompleted(); + return; + } + + KeyValueResponse.Builder keyValueBuilder = KeyValueResponse.newBuilder(); + + int max = keyList.size() - 1; + AtomicInteger count = new AtomicInteger(-1); + Kv.Builder kvBuilder = Kv.newBuilder(); + getWrapper().batchGet(graph, table, + () -> { + if (count.getAndAdd(1) == max) { + return null; + } + + Key key = keyList.get(count.get()); + if (log.isDebugEnabled()) { + log.debug("batch-get: " + + HgStoreNodeUtil.toStr( + key.getKey() + .toByteArray())); + } + return HgGrpc.toHgPair(key); + }, + ( + pair -> { + if (pair.getValue() == null || pair.getKey() == null) { + return; + } + keyValueBuilder.addKv(HgGrpc.toKv(pair, kvBuilder)); + } + ) + + ); + + builder.setKeyValueResponse(keyValueBuilder.build()); + responseObserver.onNext(builder.build()); + responseObserver.onCompleted(); + + } + + @Override + public void batch(BatchReq request, StreamObserver observer) { + String graph = request.getHeader().getGraph(); + List list = request.getWriteReq().getEntryList(); + PdProvider pd = getPD(); + try { + GraphManager graphManager = pd.getGraphManager(); + Graph managerGraph = graphManager.getGraph(graph); + if (managerGraph != null && graph.endsWith("/g")) { + Metapb.Graph g = managerGraph.getProtoObj(); + if (g == null || g.getGraphState() == null) { + g = pd.getPDClient().getGraphWithOutException(graph); + managerGraph.setGraph(g); + } + if (g != null) { + Metapb.GraphState graphState = g.getGraphState(); + if (graphState != null) { + GraphMode graphMode = graphState.getMode(); + if (graphMode != null && + graphMode.getNumber() == GraphMode.ReadOnly_VALUE) { + // 状态为只读时从pd获取最新的图状态,图只读状态会在pd的通知中更新 + Metapb.Graph pdGraph = + pd.getPDClient().getGraph(graph); + Metapb.GraphState pdGraphState = + pdGraph.getGraphState(); + if (pdGraphState != null && + pdGraphState.getMode() != null && + pdGraphState.getMode().getNumber() == + GraphMode.ReadOnly_VALUE) { + // 确认pd中存储的当前状态也是只读,则不允许插入数据 + throw new PDException(-1, + "the graph space size " + + "has " + + "reached the threshold"); + } + // pd状态与本地缓存不一致,本地缓存更新为pd中的状态 + managerGraph.setProtoObj(pdGraph); + } + } + } + } + } catch (PDException e) { + ResStatus status = ResStatus.newBuilder() + .setCode(ResCode.RES_CODE_EXCESS) + .setMsg(e.getMessage()) + .build(); + FeedbackRes feedbackRes = FeedbackRes.newBuilder() + .setStatus(status) + .build(); + observer.onNext(feedbackRes); + observer.onCompleted(); + return; + } + + // 按分区拆分数据 + Map> groups = new HashMap<>(); + list.forEach((entry) -> { + Key startKey = entry.getStartKey(); + if (startKey.getCode() == HgStoreConst.SCAN_ALL_PARTITIONS_ID) { + // 所有Leader分区 + List ids = + storeService.getGraphLeaderPartitionIds(graph); + ids.forEach(id -> { + if (!groups.containsKey(id)) { + groups.put(id, new LinkedList<>()); + } + groups.get(id).add(entry); + }); + } else { + // 根据keyCode查询所属分区ID,按分区ID分组 + Integer partitionId = + pd.getPartitionByCode(graph, startKey.getCode()) + .getId(); + if (!groups.containsKey(partitionId)) { + groups.put(partitionId, new LinkedList<>()); + } + groups.get(partitionId).add(entry); + } + }); + + // 发给不同的raft执行 + BatchGrpcClosure closure = + new BatchGrpcClosure<>(groups.size()); + groups.forEach((partition, entries) -> { + storeService.addRaftTask(HgStoreNodeService.BATCH_OP, graph, + partition, + BatchReq.newBuilder() + .setHeader(request.getHeader()) + .setWriteReq( + BatchWriteReq.newBuilder() + .addAllEntry( + entries)) + .build(), + closure.newRaftClosure()); + }); + + if (!graph.isEmpty()) { + log.debug(" batch: waiting raft..."); + // 等待返回结果 + closure.waitFinish(observer, r -> closure.selectError(r), + appConfig.getRaft().getRpcTimeOut()); + log.debug(" batch: ended waiting"); + } else { + log.info(" batch: there is none of raft leader, graph = {}.", + request.getHeader().getGraph()); + observer.onNext( + FeedbackRes.newBuilder().setStatus(HgGrpc.success()) + .build()); + observer.onCompleted(); + } + } + + public void doBatch(int partId, BatchReq request, RaftClosure response) { + String graph = request.getHeader().getGraph(); + String batchId = request.getBatchId(); + FeedbackRes.Builder builder = FeedbackRes.newBuilder(); + List entries = request.getWriteReq().getEntryList(); + try { + getWrapper().doBatch(graph, partId, entries); + builder.setStatus(HgGrpc.success()); + } catch (Throwable t) { + String msg = "Failed to doBatch, graph: " + graph + "; batchId= " + batchId; + log.error(msg, t); + builder.setStatus(HgGrpc.fail(msg)); + } + GrpcClosure.setResult(response, builder.build()); + } + + // private static HgBusinessHandler.Batch toBatch(BatchEntry entry) { + // return new HgBusinessHandler.Batch() { + // @Override + // public BatchOpType getOp() { + // return BatchOpType.of(entry.getOpType().getNumber()); + // } + // + // @Override + // public int getKeyCode() { + // return entry.getStartKey().getCode(); + // } + // + // @Override + // public String getTable() { + // return entry.getTable(); + // } + // + // @Override + // public byte[] getStartKey() { + // return entry.getStartKey().getKey().toByteArray(); + // } + // + // @Override + // public byte[] getEndKey() { + // return entry.getEndKey().getKey().toByteArray(); + // } + // + // @Override + // public byte[] getValue() { + // return entry.getValue().toByteArray(); + // } + // }; + // + //} + + @Override + public void table(TableReq request, StreamObserver observer) { + if (log.isDebugEnabled()) { + log.debug("table: method = {}, graph = {}, table = {}" + , request.getMethod().name() + , request.getHeader().getGraph() + , request.getTableName() + ); + } + + String graph = request.getHeader().getGraph(); + // 所有Leader分区 + List ids = storeService.getGraphLeaderPartitionIds(graph); + // 按分区拆分数据 + Map groups = new HashMap<>(); + // 按分区拆分数据 + ids.forEach(id -> { + groups.put(id, request); + }); + + // 发给不同的raft执行 + BatchGrpcClosure closure = new BatchGrpcClosure<>(groups.size()); + groups.forEach((partition, entries) -> { + storeService.addRaftTask(HgStoreNodeService.TABLE_OP, graph, partition, + TableReq.newBuilder(request).build(), + closure.newRaftClosure()); + }); + + if (!groups.isEmpty()) { + // log.info(" table waiting raft..."); + // 等待返回结果 + closure.waitFinish(observer, r -> closure.selectError(r), + appConfig.getRaft().getRpcTimeOut()); + // log.info(" table ended waiting raft"); + } else { + // log.info(" table none leader logic"); + ResStatus status = null; + + switch (request.getMethod()) { + case TABLE_METHOD_EXISTS: + status = HgGrpc.not(); + break; + default: + status = HgGrpc.success(); + } + + // log.info(" table none leader status: {}", status.getCode()); + observer.onNext(FeedbackRes.newBuilder().setStatus(status).build()); + observer.onCompleted(); + } + + } + + public void doTable(int partId, TableReq request, RaftClosure response) { + if (log.isDebugEnabled()) { + log.debug(" - doTable[{}]: graph = {}, table = {}" + , request.getMethod().name() + , request.getHeader().getGraph() + , request.getTableName() + ); + } + + FeedbackRes.Builder builder = FeedbackRes.newBuilder(); + + try { + log.debug(" - starting wrapper:doTable "); + if (getWrapper().doTable(partId, + request.getMethod(), + request.getHeader().getGraph(), + request.getTableName())) { + builder.setStatus(HgGrpc.success()); + } else { + builder.setStatus(HgGrpc.not()); + } + log.debug(" - ended wrapper:doTable "); + } catch (Throwable t) { + String msg = "Failed to invoke doTable[ " + + request.getMethod().name() + " ], graph=" + + request.getHeader().getGraph() + " , table=" + + request.getTableName(); + log.error(msg, t); + builder.setStatus(HgGrpc.fail(msg)); + } + log.debug(" - starting GrpcClosure:setResult "); + GrpcClosure.setResult(response, builder.build()); + log.debug(" - ended GrpcClosure:setResult "); + } + + + @Override + public void graph(GraphReq request, StreamObserver observer) { + if (log.isDebugEnabled()) { + log.debug("graph: method = {}, graph = {}, table = {}" + , request.getMethod().name() + , request.getHeader().getGraph() + , request.getGraphName() + ); + } + + String graph = request.getHeader().getGraph(); + // 所有Leader分区 + List ids = storeService.getGraphLeaderPartitionIds(graph); + // 按分区拆分数据 + Map groups = new HashMap<>(); + // 按分区拆分数据 + ids.forEach(id -> { + groups.put(id, request); + }); + + // 发给不同的raft执行 + BatchGrpcClosure closure = new BatchGrpcClosure<>(groups.size()); + groups.forEach((partition, entries) -> { + storeService.addRaftTask(HgStoreNodeService.GRAPH_OP, graph, partition, + GraphReq.newBuilder(request).build(), + closure.newRaftClosure()); + }); + + if (!groups.isEmpty()) { + // 等待返回结果 + closure.waitFinish(observer, r -> closure.selectError(r), + appConfig.getRaft().getRpcTimeOut()); + + } else { + observer.onNext(FeedbackRes.newBuilder().setStatus(HgGrpc.success()).build()); + observer.onCompleted(); + } + + } + + public void doGraph(int partId, GraphReq request, RaftClosure response) { + if (log.isDebugEnabled()) { + log.debug(" - doGraph[{}]: graph = {}, table = {}" + , request.getMethod().name() + , request.getHeader().getGraph() + , request.getGraphName() + ); + } + + FeedbackRes.Builder builder = FeedbackRes.newBuilder(); + + try { + if (getWrapper().doGraph(partId, + request.getMethod(), + request.getHeader().getGraph())) { + builder.setStatus(HgGrpc.success()); + } else { + builder.setStatus(HgGrpc.not()); + } + } catch (Throwable t) { + String msg = "Failed to invoke doGraph[ " + + request.getMethod().name() + " ], graph=" + + request.getHeader().getGraph(); + log.error(msg, t); + builder.setStatus(HgGrpc.fail(msg)); + } + GrpcClosure.setResult(response, builder.build()); + } + + @Override + public void count(ScanStreamReq request, StreamObserver observer) { + ScanIterator it = null; + try { + BusinessHandler handler = storeService.getStoreEngine().getBusinessHandler(); + long count = handler.count(request.getHeader().getGraph(), request.getTable()); + observer.onNext(Agg.newBuilder().setCount(count).build()); + observer.onCompleted(); + } catch (Exception e) { + observer.onError(e); + } finally { + if (it != null) { + try { + it.close(); + } catch (Exception e) { + + } + } + } + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreStateService.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreStateService.java new file mode 100644 index 000000000..760748c11 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreStateService.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.grpc; + + +import org.apache.hugegraph.store.grpc.state.HgStoreStateGrpc; +import org.apache.hugegraph.store.grpc.state.NodeStateRes; +import org.apache.hugegraph.store.grpc.state.ScanState; +import org.apache.hugegraph.store.grpc.state.SubStateReq; +import org.lognet.springboot.grpc.GRpcService; +import org.springframework.beans.factory.annotation.Autowired; + +import com.google.protobuf.Empty; + +import io.grpc.stub.StreamObserver; +import lombok.extern.slf4j.Slf4j; + +/** + * created on 2021/11/3 + */ +@Slf4j +@GRpcService +public class HgStoreStateService extends HgStoreStateGrpc.HgStoreStateImplBase { + + @Autowired + HgStoreStreamImpl impl; + + @Override + public void subState(SubStateReq request, StreamObserver observer) { + HgStoreStateSubject.addObserver(request.getSubId(), observer); + } + + @Override + public void unsubState(SubStateReq request, StreamObserver observer) { + HgStoreStateSubject.removeObserver(request.getSubId()); + } + + @Override + public void getScanState(SubStateReq request, StreamObserver observer) { + ScanState state = impl.getState(); + observer.onNext(state); + observer.onCompleted(); + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreStateSubject.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreStateSubject.java new file mode 100644 index 000000000..76fe769ed --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreStateSubject.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.grpc; + +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.hugegraph.store.grpc.state.NodeStateRes; +import org.apache.hugegraph.store.grpc.state.NodeStateType; +import org.apache.hugegraph.store.node.util.HgAssert; + +import io.grpc.stub.StreamObserver; +import lombok.extern.slf4j.Slf4j; + +/** + * created on 2021/11/3 + */ +@Slf4j +public final class HgStoreStateSubject { + public final static Map> subObserverHolder = + new ConcurrentHashMap<>(); + + + public static void addObserver(String subId, StreamObserver observer) { + HgAssert.isArgumentValid(subId, "subId"); + HgAssert.isArgumentNotNull(observer == null, "observer"); + + subObserverHolder.put(subId, observer); + } + + public static void removeObserver(String subId) { + HgAssert.isArgumentValid(subId, "subId"); + subObserverHolder.remove(subId); + } + + public static void notifyAll(NodeStateType nodeState) { + + HgAssert.isArgumentNotNull(nodeState == null, "nodeState"); + NodeStateRes res = NodeStateRes.newBuilder().setState(nodeState).build(); + Iterator>> iter = + subObserverHolder.entrySet().iterator(); + + while (iter.hasNext()) { + Map.Entry> entry = iter.next(); + + try { + entry.getValue().onNext(res); + } catch (Throwable e) { + log.error("Failed to send node-state[" + nodeState + "] to subscriber[" + + entry.getKey() + "].", e); + iter.remove(); + log.error("Removed the subscriber[" + entry.getKey() + "].", e); + } + + } + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreStreamImpl.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreStreamImpl.java new file mode 100644 index 000000000..3b688eb04 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreStreamImpl.java @@ -0,0 +1,121 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.grpc; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; + +import org.apache.hugegraph.store.grpc.state.ScanState; +import org.apache.hugegraph.store.grpc.stream.HgStoreStreamGrpc; +import org.apache.hugegraph.store.grpc.stream.KvPageRes; +import org.apache.hugegraph.store.grpc.stream.KvStream; +import org.apache.hugegraph.store.grpc.stream.ScanStreamBatchReq; +import org.apache.hugegraph.store.grpc.stream.ScanStreamReq; +import org.apache.hugegraph.store.node.AppConfig; +import org.apache.hugegraph.store.node.util.HgExecutorUtil; +import org.lognet.springboot.grpc.GRpcService; +import org.springframework.beans.factory.annotation.Autowired; + +import io.grpc.stub.StreamObserver; +import lombok.extern.slf4j.Slf4j; + +/** + * created on 2021/10/19 + */ +@Slf4j +@GRpcService +public class HgStoreStreamImpl extends HgStoreStreamGrpc.HgStoreStreamImplBase { + + @Autowired + private HgStoreNodeService storeService; + @Autowired + private AppConfig appConfig; + private HgStoreWrapperEx wrapper; + private ThreadPoolExecutor executor; + + private HgStoreWrapperEx getWrapper() { + if (this.wrapper == null) { + synchronized (this) { + if (this.wrapper == null) { + this.wrapper = new HgStoreWrapperEx( + storeService.getStoreEngine().getBusinessHandler()); + } + } + } + return this.wrapper; + } + + public ThreadPoolExecutor getRealExecutor() { + return executor; + } + + public ThreadPoolExecutor getExecutor() { + if (this.executor == null) { + synchronized (this) { + if (this.executor == null) { + AppConfig.ThreadPoolScan scan = this.appConfig.getThreadPoolScan(); + this.executor = + HgExecutorUtil.createExecutor("hg-scan", scan.getCore(), scan.getMax(), + scan.getQueue()); + } + } + } + return this.executor; + } + + public ScanState getState() { + ThreadPoolExecutor ex = getExecutor(); + ScanState.Builder builder = ScanState.newBuilder(); + BlockingQueue queue = ex.getQueue(); + ScanState state = + builder.setActiveCount(ex.getActiveCount()).setTaskCount(ex.getTaskCount()) + .setCompletedTaskCount(ex.getCompletedTaskCount()) + .setMaximumPoolSize(ex.getMaximumPoolSize()) + .setLargestPoolSize(ex.getLargestPoolSize()).setPoolSize(ex.getPoolSize()) + .setAddress(appConfig.getStoreServerAddress()) + .setQueueSize(queue.size()) + .setQueueRemainingCapacity(queue.remainingCapacity()) + .build(); + return state; + } + + @Override + public StreamObserver scan(StreamObserver response) { + return ScanStreamResponse.of(response, getWrapper(), getExecutor(), appConfig); + } + + @Override + public void scanOneShot(ScanStreamReq request, StreamObserver response) { + ScanOneShotResponse.scanOneShot(request, response, getWrapper()); + } + + @Override + public StreamObserver scanBatch(StreamObserver response) { + return ScanBatchResponse3.of(response, getWrapper(), getExecutor()); + } + + @Override + public StreamObserver scanBatch2(StreamObserver response) { + return ScanBatchResponseFactory.of(response, getWrapper(), getExecutor()); + } + + @Override + public void scanBatchOneShot(ScanStreamBatchReq request, StreamObserver response) { + ScanBatchOneShotResponse.scanOneShot(request, response, getWrapper()); + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreWrapperEx.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreWrapperEx.java new file mode 100644 index 000000000..e54fc678f --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/HgStoreWrapperEx.java @@ -0,0 +1,131 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.grpc; + +import static org.apache.hugegraph.store.grpc.common.GraphMethod.GRAPH_METHOD_DELETE; + +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import org.apache.hugegraph.rocksdb.access.ScanIterator; +import org.apache.hugegraph.store.business.BusinessHandler; +import org.apache.hugegraph.store.business.FilterIterator; +import org.apache.hugegraph.store.grpc.common.GraphMethod; +import org.apache.hugegraph.store.grpc.common.TableMethod; +import org.apache.hugegraph.store.grpc.session.BatchEntry; +import org.apache.hugegraph.store.term.HgPair; + +import lombok.extern.slf4j.Slf4j; + +/** + * @projectName: hugegraph-store + * @package: org.apache.hugegraph.store.grpc.boot.service + * @className: HgStoreWrapperEx + * @description: TODO + * @date: 2021/10/25 18:35 + * @version: 1.0 + */ +@Slf4j +public class HgStoreWrapperEx { + + private final BusinessHandler handler; + + public HgStoreWrapperEx(BusinessHandler handler) { + this.handler = handler; + } + + public byte[] doGet(String graph, int code, String table, byte[] key) { + return this.handler.doGet(graph, code, table, key); + } + + public boolean doClean(String graph, int partId) { + return this.handler.cleanPartition(graph, partId); + } + + public ScanIterator scanAll(String graph, String table, byte[] query) { + ScanIterator scanIterator = this.handler.scanAll(graph, table, query); + return FilterIterator.of(scanIterator, query); + } + + public ScanIterator scan(String graph, int partId, String table, byte[] start, byte[] end, + int scanType, + byte[] query) { + ScanIterator scanIterator = + this.handler.scan(graph, partId, table, start, end, scanType, query); + return FilterIterator.of(scanIterator, query); + } + + public void batchGet(String graph, String table, Supplier> s, + Consumer> c) { + this.handler.batchGet(graph, table, s, (pair -> { + c.accept(new HgPair<>(pair.getKey(), pair.getValue())); + })); + } + + public ScanIterator scanPrefix(String graph, int partition, String table, byte[] prefix, + int scanType, + byte[] query) { + ScanIterator scanIterator = + this.handler.scanPrefix(graph, partition, table, prefix, scanType); + return FilterIterator.of(scanIterator, query); + } + + public void doBatch(String graph, int partId, List entryList) { + this.handler.doBatch(graph, partId, entryList); + } + + public boolean doTable(int partId, TableMethod method, String graph, String table) { + boolean flag; + switch (method) { + case TABLE_METHOD_EXISTS: + flag = this.handler.existsTable(graph, partId, table); + break; + case TABLE_METHOD_CREATE: + this.handler.createTable(graph, partId, table); + flag = true; + break; + case TABLE_METHOD_DELETE: + this.handler.deleteTable(graph, partId, table); + flag = true; + break; + case TABLE_METHOD_DROP: + this.handler.dropTable(graph, partId, table); + flag = true; + break; + case TABLE_METHOD_TRUNCATE: + this.handler.truncate(graph, partId); + flag = true; + break; + default: + throw new UnsupportedOperationException("TableMethod: " + method.name()); + } + + return flag; + } + + public boolean doGraph(int partId, GraphMethod method, String graph) { + boolean flag = true; + if (method == GRAPH_METHOD_DELETE) {// 交给 raft 执行,此处不处理 + flag = true; + } else { + throw new UnsupportedOperationException("GraphMethod: " + method.name()); + } + return flag; + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ParallelScanIterator.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ParallelScanIterator.java new file mode 100644 index 000000000..7b3598d5a --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ParallelScanIterator.java @@ -0,0 +1,386 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.grpc; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Supplier; + +import org.apache.hugegraph.pd.common.KVPair; +import org.apache.hugegraph.rocksdb.access.RocksDBSession; +import org.apache.hugegraph.rocksdb.access.ScanIterator; +import org.apache.hugegraph.store.buffer.KVByteBuffer; +import org.apache.hugegraph.store.grpc.common.ScanOrderType; +import org.apache.hugegraph.store.grpc.stream.ScanQueryRequest; +import org.apache.hugegraph.store.node.util.HgAssert; +import org.apache.hugegraph.store.node.util.PropertyUtil; +import org.apache.hugegraph.store.term.Bits; + +import com.alipay.sofa.jraft.util.Utils; + +import lombok.extern.slf4j.Slf4j; + +/** + * 支持平行读取的批量查询迭代器 + */ +@Slf4j +public class ParallelScanIterator implements ScanIterator { + + private static final int waitDataMaxTryTimes = 600; + protected static int maxBodySize = + PropertyUtil.getInt("app.scan.stream.body.size", 1024 * 1024); + private final int batchSize = PropertyUtil.getInt("app.scan.stream.entries.size", 20000); + private final Supplier> batchSupplier; + private final Supplier limitSupplier; + private final BlockingQueue> queue; + private final ReentrantLock queueLock = new ReentrantLock(); + final private ThreadPoolExecutor executor; + private final ScanQueryRequest query; + private final Queue scanners = new LinkedList<>(); + private final Queue pauseScanners = new LinkedList<>(); + final private List NO_DATA = new ArrayList<>(); + private final boolean orderVertex; + private final boolean orderEdge; + private int maxWorkThreads = Utils.cpus() / 8; + private int maxInQueue = maxWorkThreads * 2; + private volatile boolean finished; + private List current = null; + + private ParallelScanIterator(Supplier> iteratorSupplier, + Supplier limitSupplier, + ScanQueryRequest query, + ThreadPoolExecutor executor) { + this.executor = executor; + this.batchSupplier = iteratorSupplier; + this.limitSupplier = limitSupplier; + this.finished = false; + this.query = query; + orderVertex = query.getOrderType() == ScanOrderType.ORDER_STRICT; + orderEdge = query.getOrderType() == ScanOrderType.ORDER_WITHIN_VERTEX; + if (orderVertex) { + this.maxWorkThreads = 1; + } else { + this.maxWorkThreads = + Math.max(1, Math.min(query.getConditionCount() / 16, maxWorkThreads)); + } + this.maxInQueue = maxWorkThreads * 2; + // 边有序需要更大的队列 + queue = new LinkedBlockingQueue<>(maxInQueue * 2); + createScanner(); + } + + public static ParallelScanIterator of( + Supplier> iteratorSupplier, + Supplier limitSupplier, + ScanQueryRequest query, + ThreadPoolExecutor executor) { + HgAssert.isArgumentNotNull(iteratorSupplier, "iteratorSupplier"); + HgAssert.isArgumentNotNull(limitSupplier, "limitSupplier"); + return new ParallelScanIterator(iteratorSupplier, limitSupplier, query, executor); + } + + @Override + public boolean hasNext() { + int tryTimes = 0; + while (current == null && tryTimes < waitDataMaxTryTimes) { + try { + if (queue.size() != 0 || !finished) { + current = queue.poll(100, TimeUnit.MILLISECONDS); //定期检查client是否被关闭了 + if (current == null && !finished) { + wakeUpScanner(); + } + } else { + break; + } + } catch (InterruptedException e) { + log.error("hasNext interrupted {}", e); + break; + } + tryTimes++; + } + if (current == null && tryTimes >= waitDataMaxTryTimes) { + log.error("Wait data timeout!!!, scanner is {}/{}", scanners.size(), + pauseScanners.size()); + } + return current != null && current != NO_DATA; + } + + @Override + public boolean isValid() { + throw new UnsupportedOperationException(); + } + + @Override + public List next() { + List t = current; + current = null; + if (queue.size() < maxWorkThreads) { + wakeUpScanner(); + } + return t; + } + + @Override + public void close() { + finished = true; + synchronized (scanners) { + scanners.forEach(scanner -> { + scanner.close(); + }); + } + synchronized (pauseScanners) { + pauseScanners.forEach(s -> { + s.close(); + }); + } + queue.clear(); + } + + /** + * 创建扫描器 + */ + private void createScanner() { + synchronized (scanners) { + for (int i = 0; i < maxWorkThreads; i++) { + scanners.add(new KVScanner()); + } + scanners.forEach(scanner -> { + executor.execute(() -> scanner.scanKV()); + }); + } + } + + /** + * 唤醒扫描器 + */ + private void wakeUpScanner() { + synchronized (pauseScanners) { + if (!pauseScanners.isEmpty()) { + KVScanner scanner = pauseScanners.poll(); + if (scanner != null) { + executor.execute(() -> scanner.scanKV()); + } + } + } + } + + /** + * 休眠扫描器 + * + * @param scanner + */ + private void suspendScanner(KVScanner scanner) { + synchronized (pauseScanners) { + pauseScanners.add(scanner); + } + } + + private void quitScanner(KVScanner scanner) { + synchronized (scanners) { + scanner.close(); + scanners.remove(scanner); + if (scanners.size() == 0) { + putData(NO_DATA); + this.finished = true; + } + } + } + + /** + * 添加到队列,返回队列是否已满 + * + * @param data + * @return false: 队列已满 + */ + private boolean putData(List data) { + try { + this.queue.put(data); + } catch (InterruptedException e) { + log.error("exception ", e); + this.finished = true; + return false; + } + return this.queue.size() < maxInQueue; + } + + private boolean putData(List data, boolean hasNext) { + try { + queueLock.lock(); + this.queue.put(data); + } catch (InterruptedException e) { + log.error("exception ", e); + this.finished = true; + return false; + } finally { + if (!hasNext) { + queueLock.unlock(); + } + } + // 数据未结束,线程继续执行 + return hasNext || this.queue.size() < maxInQueue; + } + + private synchronized KVPair getIterator() { + return this.batchSupplier.get(); + } + + private long getLimit() { + Long limit = this.limitSupplier.get(); + if (limit == null || limit <= 0) { + limit = Long.valueOf(Integer.MAX_VALUE); + } + return limit; + } + + static class KV { + public int sn; + public byte[] key; + public byte[] value; + + public boolean hasSN = false; + + public static KV of(RocksDBSession.BackendColumn col) { + KV kv = new KV(); + kv.key = col.name; + kv.value = col.value; + return kv; + } + + public static KV ofSeparator(int value) { + KV kv = new KV(); + kv.key = new byte[4]; + Bits.putInt(kv.key, 0, value); + return kv; + } + + public KV setNo(int sn) { + this.sn = sn; + hasSN = true; + return this; + } + + public void write(KVByteBuffer buffer) { + if (hasSN) { + buffer.putInt(sn); + } + buffer.put(key); + buffer.put(value); + } + + public int size() { + return this.key.length + this.value.length + 1; + } + } + + class KVScanner { + + private final ReentrantLock iteratorLock = new ReentrantLock(); + private ScanIterator iterator = null; + private QueryCondition query = null; + private long limit; + private long counter; + private volatile boolean closed = false; + + private ScanIterator getIterator() { + // 迭代器没有数据,或该点以达到limit,切换新的迭代器 + if (iterator == null || !iterator.hasNext() || counter >= limit) { + if (iterator != null) { + iterator.close(); + } + KVPair pair = ParallelScanIterator.this.getIterator(); + query = pair.getKey(); + iterator = pair.getValue(); + limit = getLimit(); + counter = 0; + } + return iterator; + } + + public void scanKV() { + boolean canNext = true; + ArrayList dataList = new ArrayList<>(batchSize); + dataList.ensureCapacity(batchSize); + iteratorLock.lock(); + try { + long entriesSize = 0, bodySize = 0; + while (canNext && !closed) { + iterator = this.getIterator(); + if (iterator == null) { + break; + } + while (iterator.hasNext() && entriesSize < batchSize && + bodySize < maxBodySize && + counter < limit && !closed) { + KV kv = KV.of(iterator.next()); + dataList.add(orderVertex ? kv.setNo(query.getSerialNo()) : kv); + bodySize += kv.size(); + entriesSize++; + counter++; + } + if ((entriesSize >= batchSize || bodySize >= maxBodySize) || + (orderEdge && bodySize >= maxBodySize / 2)) { + if (orderEdge) { + //边排序,保证一个点的所有边连续,阻止其他点插入 + canNext = putData(dataList, iterator != null && iterator.hasNext()); + } else { + canNext = putData(dataList); + } + dataList = new ArrayList<>(batchSize); + dataList.ensureCapacity(batchSize); + entriesSize = bodySize = 0; + } + } + if (!dataList.isEmpty()) { + if (orderEdge) { + putData(dataList, false); + } else { + putData(dataList); + } + } + } catch (Exception e) { + log.error("exception {}", e); + } finally { + iteratorLock.unlock(); + if (iterator != null && counter < limit && !closed) { + suspendScanner(this); + } else { + quitScanner(this); + } + } + } + + public void close() { + closed = true; + iteratorLock.lock(); + try { + if (iterator != null) { + iterator.close(); + } + } finally { + iteratorLock.unlock(); + } + } + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/QueryCondition.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/QueryCondition.java new file mode 100644 index 000000000..edc6e3602 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/QueryCondition.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.grpc; + +/** + * 2023/2/8 + */ +public interface QueryCondition { + byte[] getStart(); + + byte[] getEnd(); + + byte[] getPrefix(); + + int getKeyCode(); + + int getScanType(); + + byte[] getQuery(); + + byte[] getPosition(); + + int getSerialNo(); +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ScanBatchOneShotResponse.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ScanBatchOneShotResponse.java new file mode 100644 index 000000000..685f77fac --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ScanBatchOneShotResponse.java @@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.grpc; + +import static org.apache.hugegraph.store.node.grpc.ScanUtil.getIterator; + +import org.apache.hugegraph.rocksdb.access.RocksDBSession; +import org.apache.hugegraph.rocksdb.access.ScanIterator; +import org.apache.hugegraph.store.grpc.common.Kv; +import org.apache.hugegraph.store.grpc.stream.KvPageRes; +import org.apache.hugegraph.store.grpc.stream.ScanQueryRequest; +import org.apache.hugegraph.store.grpc.stream.ScanStreamBatchReq; +import org.apache.hugegraph.store.node.util.HgGrpc; +import org.apache.hugegraph.store.node.util.HgStoreNodeUtil; + +import com.google.protobuf.ByteString; + +import io.grpc.Status; +import io.grpc.stub.StreamObserver; +import lombok.extern.slf4j.Slf4j; + + +/** + * created on 2022/04/08 + * + * @version 0.1.0 + */ +@Slf4j +public class ScanBatchOneShotResponse { + + /** + * Handle one-shot batch scan + * + * @param request + * @param responseObserver + */ + public static void scanOneShot(ScanStreamBatchReq request, + StreamObserver responseObserver, + HgStoreWrapperEx wrapper) { + + String graph = request.getHeader().getGraph(); + ScanQueryRequest queryRequest = request.getQueryRequest(); + ScanIterator iterator = getIterator(graph, queryRequest, wrapper); + + KvPageRes.Builder resBuilder = KvPageRes.newBuilder(); + Kv.Builder kvBuilder = Kv.newBuilder(); + + long limit = queryRequest.getLimit(); + + if (limit <= 0) { + limit = Integer.MAX_VALUE; + log.warn("As limit is less than or equals 0, default limit was effective:[ {} ]", + Integer.MAX_VALUE); + } + + int count = 0; + + try { + while (iterator.hasNext()) { + + if (++count > limit) { + break; + } + + RocksDBSession.BackendColumn col = iterator.next(); + + resBuilder.addData(kvBuilder + .setKey(ByteString.copyFrom(col.name)) + .setValue(ByteString.copyFrom(col.value)) + .setCode(HgStoreNodeUtil.toInt(iterator.position())) +//position == partition-id. + ); + + } + + responseObserver.onNext(resBuilder.build()); + responseObserver.onCompleted(); + + } catch (Throwable t) { + String msg = "Failed to do oneshot batch scan, scanning was interrupted, cause by:"; + responseObserver.onError( + HgGrpc.toErr(Status.Code.INTERNAL, msg, t)); + } finally { + iterator.close(); + } + + } + + +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ScanBatchResponse.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ScanBatchResponse.java new file mode 100644 index 000000000..d6a8eef45 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ScanBatchResponse.java @@ -0,0 +1,278 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.grpc; + + +import static org.apache.hugegraph.store.node.grpc.ScanUtil.getParallelIterator; + +import java.util.List; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import org.apache.hugegraph.rocksdb.access.ScanIterator; +import org.apache.hugegraph.store.buffer.ByteBufferAllocator; +import org.apache.hugegraph.store.buffer.KVByteBuffer; +import org.apache.hugegraph.store.grpc.stream.KvStream; +import org.apache.hugegraph.store.grpc.stream.ScanQueryRequest; +import org.apache.hugegraph.store.grpc.stream.ScanStreamBatchReq; +import org.apache.hugegraph.store.node.util.HgGrpc; +import org.apache.hugegraph.store.node.util.PropertyUtil; + +import io.grpc.stub.StreamObserver; +import lombok.extern.slf4j.Slf4j; + +/** + * 批量查询处理器,批量查询数据,流式返回数据。 + * 1、服务端流式发送数据给客户端 + * 2、客户端每消费一批次数据,返回批次号给服务端 + * 3、服务端根据批次号决定发送多少数据,保证传送数据的不间断, + */ +@Slf4j +public class ScanBatchResponse implements StreamObserver { + static ByteBufferAllocator bfAllocator = + new ByteBufferAllocator(ParallelScanIterator.maxBodySize * 3 / 2, 1000); + static ByteBufferAllocator alloc = + new ByteBufferAllocator(ParallelScanIterator.maxBodySize * 3 / 2, 1000); + private final int maxInFlightCount = PropertyUtil.getInt("app.scan.stream.inflight", 16); + private final int activeTimeout = PropertyUtil.getInt("app.scan.stream.timeout", 60); //单位秒 + private final StreamObserver sender; + private final HgStoreWrapperEx wrapper; + private final ThreadPoolExecutor executor; + private final Object stateLock = new Object(); + private final Lock iteratorLock = new ReentrantLock(); + // 当前正在遍历的迭代器 + private ScanIterator iterator; + // 下一次发送的序号 + private volatile int seqNo; + // Client已消费的序号 + private volatile int clientSeqNo; + // 已经发送的条目数 + private volatile long count; + // 客户端要求返回的最大条目数 + private volatile long limit; + private ScanQueryRequest query; + // 上次读取数据时间 + private long activeTime; + private volatile State state; + + + public ScanBatchResponse(StreamObserver response, HgStoreWrapperEx wrapper, + ThreadPoolExecutor executor) { + this.sender = response; + this.wrapper = wrapper; + this.executor = executor; + this.iterator = null; + this.seqNo = 1; + this.state = State.IDLE; + this.activeTime = System.currentTimeMillis(); + } + + /** + * 接收客户端发送的消息 + * 服务端另起线程处理消息,不阻塞网络 + * + * @param request + */ + @Override + public void onNext(ScanStreamBatchReq request) { + switch (request.getQueryCase()) { + case QUERY_REQUEST: // 查询条件 + executor.execute(() -> { + startQuery(request.getHeader().getGraph(), request.getQueryRequest()); + }); + break; + case RECEIPT_REQUEST: // 消息异步应答 + this.clientSeqNo = request.getReceiptRequest().getTimes(); + if (seqNo - clientSeqNo < maxInFlightCount) { + synchronized (stateLock) { + if (state == State.IDLE) { + state = State.DOING; + executor.execute(() -> { + sendEntries(); + }); + } else if (state == State.DONE) { + sendNoDataEntries(); + } + } + } + break; + case CANCEL_REQUEST: // 关闭流 + closeQuery(); + break; + default: + sender.onError( + HgGrpc.toErr("Unsupported sub-request: [ " + request + " ]")); + } + } + + @Override + public void onError(Throwable t) { + log.error("onError ", t); + closeQuery(); + } + + @Override + public void onCompleted() { + closeQuery(); + } + + + /** + * 生成迭代器 + * + * @param request + */ + private void startQuery(String graphName, ScanQueryRequest request) { + this.query = request; + this.limit = request.getLimit(); + this.count = 0; + this.iterator = getParallelIterator(graphName, request, this.wrapper, executor); + synchronized (stateLock) { + if (state == State.IDLE) { + state = State.DOING; + executor.execute(() -> { + sendEntries(); + }); + } + } + } + + /** + * 生成迭代器 + */ + private void closeQuery() { + setStateDone(); + try { + closeIter(); + this.sender.onCompleted(); + } catch (Exception e) { + log.error("exception ", e); + } + int active = ScanBatchResponseFactory.getInstance().removeStreamObserver(this); + log.info("ScanBatchResponse closeQuery, active count is {}", active); + } + + private void closeIter() { + try { + if (this.iterator != null) { + this.iterator.close(); + this.iterator = null; + } + } catch (Exception e) { + + } + } + + /** + * 发送数据 + */ + private void sendEntries() { + if (state == State.DONE || iterator == null) { + setStateIdle(); + return; + } + iteratorLock.lock(); + try { + if (state == State.DONE || iterator == null) { + setStateIdle(); + return; + } + KvStream.Builder dataBuilder = KvStream.newBuilder().setVersion(1); + while (state != State.DONE && iterator.hasNext() + && (seqNo - clientSeqNo < maxInFlightCount) + && this.count < limit) { + KVByteBuffer buffer = new KVByteBuffer(alloc.get()); + List dataList = iterator.next(); + dataList.forEach(kv -> { + kv.write(buffer); + this.count++; + }); + dataBuilder.setStream(buffer.flip().getBuffer()); + dataBuilder.setSeqNo(seqNo++); + dataBuilder.complete(e -> alloc.release(buffer.getBuffer())); + this.sender.onNext(dataBuilder.build()); + this.activeTime = System.currentTimeMillis(); + } + if (!iterator.hasNext() || this.count >= limit || state == State.DONE) { + closeIter(); + this.sender.onNext(KvStream.newBuilder().setOver(true).build()); + setStateDone(); + } else { + setStateIdle(); + } + } catch (Throwable e) { + if (this.state != State.DONE) { + log.error(" send data exception: ", e); + setStateIdle(); + if (this.sender != null) { + try { + this.sender.onError(e); + } catch (Exception ex) { + + } + } + } + } finally { + iteratorLock.unlock(); + } + } + + private void sendNoDataEntries() { + try { + this.sender.onNext(KvStream.newBuilder().setOver(true).build()); + } catch (Exception e) { + } + } + + private State setStateDone() { + synchronized (this.stateLock) { + this.state = State.DONE; + } + return state; + } + + private State setStateIdle() { + synchronized (this.stateLock) { + if (this.state != State.DONE) { + this.state = State.IDLE; + } + } + return state; + } + + /** + * 检查是否活跃,超过一定时间客户端没有请求数据,认为已经不活跃,关闭连接释放资源 + */ + public void checkActiveTimeout() { + if ((System.currentTimeMillis() - activeTime) > activeTimeout * 1000L) { + log.warn("The stream is not closed, and the timeout is forced to close"); + closeQuery(); + } + } + + /** + * 任务状态 + */ + private enum State { + IDLE, + DOING, + DONE, + ERROR + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ScanBatchResponse3.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ScanBatchResponse3.java new file mode 100644 index 000000000..6da57804d --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ScanBatchResponse3.java @@ -0,0 +1,417 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.grpc; + +import java.util.List; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.ReentrantLock; + +import javax.annotation.concurrent.NotThreadSafe; + +import org.apache.hugegraph.rocksdb.access.RocksDBSession; +import org.apache.hugegraph.rocksdb.access.ScanIterator; +import org.apache.hugegraph.store.grpc.common.Kv; +import org.apache.hugegraph.store.grpc.stream.KvPageRes; +import org.apache.hugegraph.store.grpc.stream.ScanCondition; +import org.apache.hugegraph.store.grpc.stream.ScanQueryRequest; +import org.apache.hugegraph.store.grpc.stream.ScanStreamBatchReq; +import org.apache.hugegraph.store.node.util.Base58; +import org.apache.hugegraph.store.node.util.HgAssert; +import org.apache.hugegraph.store.node.util.HgGrpc; +import org.apache.hugegraph.store.node.util.HgStoreConst; +import org.apache.hugegraph.store.node.util.HgStoreNodeUtil; + +import com.google.protobuf.ByteString; + +import io.grpc.Status; +import io.grpc.stub.StreamObserver; +import lombok.extern.slf4j.Slf4j; + +/** + * created on 2022/03/27 + * + * @version 3.6.0 + */ +@Slf4j +public class ScanBatchResponse3 { + private final static long DEFAULT_PACKAGE_SIZE = 10_000; + private final static int MAX_NOT_RECEIPT = 10; + + public static StreamObserver of(StreamObserver responseObserver, + HgStoreWrapperEx wrapper, ThreadPoolExecutor executor) { + HgAssert.isArgumentNotNull(responseObserver, "responseObserver"); + HgAssert.isArgumentNotNull(wrapper, "wrapper"); + return new Broker(responseObserver, wrapper, executor); + } + + private enum OrderState { + NEW(0), + WORKING(1);//, PAUSE(2), COMPLETE(10); + int value; + + OrderState(int value) { + this.value = value; + } + } + + /*** Broker ***/ + private static class Broker implements StreamObserver { + private final StreamObserver responseObserver; + private final HgStoreWrapperEx wrapper; + private final ThreadPoolExecutor executor; + private final OrderManager manager = new OrderManager(); + private String graph; + + Broker(StreamObserver responseObserver, HgStoreWrapperEx wrapper, + ThreadPoolExecutor executor) { + this.responseObserver = responseObserver; + this.wrapper = wrapper; + this.executor = executor; + } + + @Override + public void onNext(ScanStreamBatchReq request) { + this.handleHeader(request); + switch (request.getQueryCase()) { + case QUERY_REQUEST: + this.makeADeal(request.getQueryRequest()); + break; + case RECEIPT_REQUEST: + this.manager.receipt(request.getReceiptRequest().getTimes()); + break; + case CANCEL_REQUEST: + this.manager.finished(); + break; + default: + responseObserver.onError( + HgGrpc.toErr("Unsupported sub-request: [ " + request + " ]")); + } + } + + @Override + public void onError(Throwable t) { + log.warn(t.getMessage()); + this.manager.breakdown(); + } + + @Override + public void onCompleted() { + this.manager.finished(); + } + + private void handleHeader(ScanStreamBatchReq request) { + if (this.graph == null) { + this.graph = request.getHeader().getGraph(); + } + } + + private void makeADeal(ScanQueryRequest request) { + String deliverId = ""; + if (log.isDebugEnabled()) { + List conditions = request.getConditionList(); + if (conditions.size() > 0) { + ScanCondition c = conditions.get(0); + if (c.getPrefix() != null && c.getPrefix().size() > 0) { + deliverId = Base58.encode(c.getPrefix().toByteArray()); + log.info("[ANALYSIS DEAL] [{}] prefixLength: {}", deliverId, + conditions.size()); + } + + } + } + + OrderDeliverer deliverer = new OrderDeliverer(deliverId, this.responseObserver); + OrderWorker worker = new OrderWorker( + request.getLimit(), + request.getPageSize(), + ScanUtil.getIterator(this.graph, request, this.wrapper), + deliverer, + this.executor); + + this.manager.deal(worker, deliverer); + } + + } + + @NotThreadSafe + private static class OrderManager { + OrderState state = OrderState.NEW; + OrderWorker worker; + OrderDeliverer deliverer; + + synchronized void deal(OrderWorker worker, OrderDeliverer deliverer) { + if (log.isDebugEnabled()) { + log.debug("Receiving query request."); + } + if (this.state == OrderState.NEW) { + this.worker = worker; + this.deliverer = deliverer; + this.worker.hereWeGo(); + this.state = OrderState.WORKING; + } + } + + synchronized void receipt(int receiptTimes) { + if (log.isDebugEnabled()) { + log.debug("Receiving receipt request."); + } + this.worker.setReceipt(receiptTimes); + } + + synchronized void finished() { + if (log.isDebugEnabled()) { + log.debug("Receiving finished request."); + } +/* if (this.state.value > OrderState.NEW.value + && this.state.value < OrderState.COMPLETE.value) { + this.state = OrderState.COMPLETE; + }*/ + this.breakdown(); + } + + synchronized void breakdown() { + if (this.worker != null) { + this.worker.breakdown(); + } + } + } + + private static class OrderDeliverer { + private final StreamObserver responseObserver; + private final AtomicBoolean finishFlag = new AtomicBoolean(); + private final String delivererId; + private final AtomicLong count = new AtomicLong(); + + OrderDeliverer(String delivererId, StreamObserver responseObserver) { + this.responseObserver = responseObserver; + this.delivererId = delivererId; + } + + void deliver(KvPageRes.Builder dataBuilder, int times, boolean isOver) { + if (this.finishFlag.get()) { + return; + } + count.addAndGet(dataBuilder.getDataCount()); + this.responseObserver.onNext(dataBuilder.setOver(isOver).setTimes(times).build()); + if (log.isDebugEnabled()) { + log.debug("deliver times : {}, over: {}", times, isOver); + } + + if (isOver) { + if (log.isDebugEnabled()) { + if (delivererId != null && !delivererId.isEmpty()) { + log.debug("[ANALYSIS OVER] [{}] count: {}, times: {}", delivererId, count, + times); + } + } + this.finish(); + } + } + + void finish() { + if (!finishFlag.getAndSet(true)) { + this.responseObserver.onCompleted(); + } + } + + void error(String msg) { + if (!finishFlag.getAndSet(true)) { + this.responseObserver.onError(HgGrpc.toErr(msg)); + } + } + + void error(String msg, Throwable t) { + if (!finishFlag.getAndSet(true)) { + this.responseObserver.onError(HgGrpc.toErr(Status.INTERNAL, + msg, t)); + } + } + } + + /*** Worker ***/ + private static class OrderWorker { + private final ScanIterator iterator; + private final OrderDeliverer deliverer; + private final AtomicBoolean pauseFlag = new AtomicBoolean(); + private final AtomicBoolean completeFlag = new AtomicBoolean(); + private final ReentrantLock workingLock = new ReentrantLock(); + private final AtomicBoolean isWorking = new AtomicBoolean(); + private final AtomicBoolean breakdown = new AtomicBoolean(); + private final AtomicInteger receiptTimes = new AtomicInteger(); + private final AtomicInteger curTimes = new AtomicInteger(); + private final ThreadPoolExecutor executor; + private final long limit; + private long packageSize; + private long counter; + + OrderWorker(long limit, long packageSize, ScanIterator iterator, OrderDeliverer deliverer, + ThreadPoolExecutor executor) { + this.limit = limit; + this.packageSize = packageSize; + this.iterator = iterator; + this.deliverer = deliverer; + this.executor = executor; + + if (this.packageSize <= 0) { + this.packageSize = DEFAULT_PACKAGE_SIZE; + log.warn( + "As page-Size is less than or equals 0, default package-size was " + + "effective.[ {} ]", + DEFAULT_PACKAGE_SIZE); + } + + } + + void hereWeGo() { + if (this.completeFlag.get()) { + log.warn("job complete."); + return; + } + if (this.isWorking.get()) { + log.warn("has been working."); + return; + } + + if (this.workingLock.isLocked()) { + log.warn("working now"); + return; + } + + executor.execute(() -> working()); + Thread.yield(); + } + + void setReceipt(int times) { + this.receiptTimes.set(times); + this.continueWorking(); + } + + boolean checkContinue() { + return (this.curTimes.get() - this.receiptTimes.get() < MAX_NOT_RECEIPT); + } + + void continueWorking() { + if (this.checkContinue()) { + synchronized (this.iterator) { + this.iterator.notify(); + } + } + } + + void breakdown() { + this.breakdown.set(true); + synchronized (this.iterator) { + this.iterator.notify(); + } + } + + private void working() { + if (this.isWorking.getAndSet(true)) { + return; + } + + this.workingLock.lock(); + + try { + synchronized (this.iterator) { + KvPageRes.Builder dataBuilder = KvPageRes.newBuilder(); + Kv.Builder kvBuilder = Kv.newBuilder(); + long packageCount = 0; + + while (iterator.hasNext()) { + if (++this.counter > limit) { + this.completeFlag.set(true); + break; + } + + if (++packageCount > packageSize) { + + if (this.breakdown.get()) { + break; + } + + deliverer.deliver(dataBuilder, curTimes.incrementAndGet(), false); + Thread.yield(); + + if (!this.checkContinue()) { + long start = System.currentTimeMillis(); + iterator.wait( + HgStoreConst.SCAN_WAIT_CLIENT_TAKING_TIME_OUT_SECONDS * + 1000); + + if (System.currentTimeMillis() - start + >= + HgStoreConst.SCAN_WAIT_CLIENT_TAKING_TIME_OUT_SECONDS * 1000) { + throw new TimeoutException("Waiting continue more than " + + + HgStoreConst.SCAN_WAIT_CLIENT_TAKING_TIME_OUT_SECONDS + + " seconds."); + } + + if (this.breakdown.get()) { + break; + } + + } + + packageCount = 1; + dataBuilder = KvPageRes.newBuilder(); + } + + RocksDBSession.BackendColumn col = iterator.next(); + + dataBuilder.addData(kvBuilder + .setKey(ByteString.copyFrom(col.name)) + .setValue(ByteString.copyFrom(col.value)) + .setCode(HgStoreNodeUtil.toInt( + iterator.position())) +//position == partition-id. + ); + + } + + this.completeFlag.set(true); + + deliverer.deliver(dataBuilder, curTimes.incrementAndGet(), true); + + } + + } catch (InterruptedException e) { + log.error("Interrupted waiting of iterator, canceled while.", e); + this.deliverer.error("Failed to finish scanning, cause by InterruptedException."); + } catch (TimeoutException t) { + log.info(t.getMessage()); + this.deliverer.error("Sever waiting exceeded [" + + HgStoreConst.SCAN_WAIT_CLIENT_TAKING_TIME_OUT_SECONDS + + "] seconds."); + } catch (Throwable t) { + log.error("Failed to do while for scanning, cause by:", t); + this.deliverer.error("Failed to finish scanning ", t); + } finally { + this.workingLock.unlock(); + this.iterator.close(); + } + } + + } + +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ScanBatchResponseFactory.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ScanBatchResponseFactory.java new file mode 100644 index 000000000..76c05ef3c --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ScanBatchResponseFactory.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.grpc; + +import java.util.Set; +import java.util.concurrent.ThreadPoolExecutor; + +import org.apache.hugegraph.store.grpc.stream.KvStream; + +import com.alipay.sofa.jraft.util.concurrent.ConcurrentHashSet; + +import io.grpc.stub.StreamObserver; + +public class ScanBatchResponseFactory { + private final static ScanBatchResponseFactory instance = new ScanBatchResponseFactory(); + private final Set streamObservers = new ConcurrentHashSet<>(); + + public static ScanBatchResponseFactory getInstance() { + return instance; + } + + public static StreamObserver of(StreamObserver responseObserver, + HgStoreWrapperEx wrapper, ThreadPoolExecutor executor) { + StreamObserver observer = new ScanBatchResponse(responseObserver, wrapper, executor); + getInstance().addStreamObserver(observer); + getInstance().checkStreamActive(); + return observer; + } + + public int addStreamObserver(StreamObserver observer) { + streamObservers.add(observer); + return streamObservers.size(); + } + + public int removeStreamObserver(StreamObserver observer) { + streamObservers.remove(observer); + return streamObservers.size(); + } + + /** + * 检查是否Stream是否活跃,超时的Stream及时关闭 + */ + public void checkStreamActive() { + streamObservers.forEach(streamObserver -> { + ((ScanBatchResponse) streamObserver).checkActiveTimeout(); + }); + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ScanOneShotResponse.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ScanOneShotResponse.java new file mode 100644 index 000000000..5880b71c5 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ScanOneShotResponse.java @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.grpc; + +import org.apache.hugegraph.rocksdb.access.RocksDBSession; +import org.apache.hugegraph.rocksdb.access.ScanIterator; +import org.apache.hugegraph.store.grpc.common.Kv; +import org.apache.hugegraph.store.grpc.stream.KvPageRes; +import org.apache.hugegraph.store.grpc.stream.ScanStreamReq; +import org.apache.hugegraph.store.node.util.HgGrpc; +import org.apache.hugegraph.store.node.util.HgStoreNodeUtil; + +import com.google.protobuf.ByteString; + +import io.grpc.Status; +import io.grpc.stub.StreamObserver; +import lombok.extern.slf4j.Slf4j; + + +/** + * created on 2022/02/17 + * + * @version 3.6.0 + */ +@Slf4j +public class ScanOneShotResponse { + + /** + * Handle one-shot scan + * + * @param request + * @param responseObserver + */ + public static void scanOneShot(ScanStreamReq request, + StreamObserver responseObserver, + HgStoreWrapperEx wrapper) { + KvPageRes.Builder resBuilder = KvPageRes.newBuilder(); + Kv.Builder kvBuilder = Kv.newBuilder(); + ScanIterator iterator = ScanUtil.getIterator(ScanUtil.toSq(request), wrapper); + + long limit = request.getLimit(); + + if (limit <= 0) { + responseObserver.onError(HgGrpc.toErr("limit<=0, please to invoke stream scan.")); + return; + } + + int count = 0; + + try { + while (iterator.hasNext()) { + + if (++count > limit) { + break; + } + + RocksDBSession.BackendColumn col = iterator.next(); + + resBuilder.addData(kvBuilder + .setKey(ByteString.copyFrom(col.name)) + .setValue(ByteString.copyFrom(col.value)) + .setCode(HgStoreNodeUtil.toInt(iterator.position())) +//position == partition-id. + ); + + } + + responseObserver.onNext(resBuilder.build()); + responseObserver.onCompleted(); + + } catch (Throwable t) { + String msg = "an exception occurred during data scanning"; + responseObserver.onError(HgGrpc.toErr(Status.INTERNAL, msg, t)); + } finally { + iterator.close(); + } + + } + + +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ScanQuery.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ScanQuery.java new file mode 100644 index 000000000..5c1791022 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ScanQuery.java @@ -0,0 +1,104 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.grpc; + +import java.util.Arrays; + +import org.apache.hugegraph.store.grpc.common.ScanMethod; + +/** + * 2022/2/28 + */ +class ScanQuery implements QueryCondition { + String graph; + String table; + ScanMethod method; + + byte[] start; + byte[] end; + byte[] prefix; + int keyCode; + int scanType; + byte[] query; + byte[] position; + int serialNo; + + private ScanQuery() { + } + + static ScanQuery of() { + return new ScanQuery(); + } + + @Override + public byte[] getStart() { + return this.start; + } + + @Override + public byte[] getEnd() { + return this.end; + } + + @Override + public byte[] getPrefix() { + return this.prefix; + } + + @Override + public int getKeyCode() { + return this.keyCode; + } + + @Override + public int getScanType() { + return this.scanType; + } + + @Override + public byte[] getQuery() { + return this.query; + } + + @Override + public byte[] getPosition() { + return this.position; + } + + @Override + public int getSerialNo() { + return this.serialNo; + } + + @Override + public String toString() { + return "ScanQuery{" + + "graph='" + graph + '\'' + + ", table='" + table + '\'' + + ", method=" + method + + ", start=" + Arrays.toString(start) + + ", end=" + Arrays.toString(end) + + ", prefix=" + Arrays.toString(prefix) + + ", partition=" + keyCode + + ", scanType=" + scanType + + ", serialNo=" + serialNo + + ", query=" + Arrays.toString(query) + + ", position=" + Arrays.toString(position) + + '}'; + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ScanQueryProducer.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ScanQueryProducer.java new file mode 100644 index 000000000..963ff66f4 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ScanQueryProducer.java @@ -0,0 +1,262 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.grpc; + +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; + +import javax.annotation.concurrent.NotThreadSafe; + +import org.apache.hugegraph.store.grpc.common.ScanMethod; +import org.apache.hugegraph.store.grpc.stream.ScanCondition; +import org.apache.hugegraph.store.grpc.stream.ScanQueryRequest; +import org.apache.hugegraph.store.node.util.HgAssert; + +import lombok.extern.slf4j.Slf4j; + +/** + * Buffering the data of ScanQueryRequest and generating ScanQuery. + * It will not hold the reference of ScanQueryRequest. + *

+ * 2023/2/8 + */ +@NotThreadSafe +@Slf4j +class ScanQueryProducer implements Iterable { + + private String graph; + private String[] tables; + private ScanMethod method; + private int scanType; + private byte[] query; + private byte[] position; + + private List conditionList; + + private ScanQueryProducer() { + } + + public static ScanQueryProducer requestOf(String graph, String[] tables, + ScanQueryRequest request) { + HgAssert.isArgumentValid(graph, "graph"); + HgAssert.isArgumentNotNull(tables, "tables"); + HgAssert.isArgumentNotNull(request, "ScanQueryRequest"); + + ScanQueryProducer res = new ScanQueryProducer(); + res.graph = graph; + res.tables = tables; // a trick that reduce the data-size transferred through network; + + res.method = request.getMethod(); + res.scanType = request.getScanType(); + res.query = request.getQuery().toByteArray(); + res.position = request.getPosition().toByteArray(); + + res.conditionList = request.getConditionList(); + + if (res.conditionList == null) { + res.conditionList = Collections.emptyList(); + } + + if (res.conditionList.isEmpty()) { + log.warn("the condition-list of ScanQueryRequest is empty."); + } + + return res; + } + + private ScanQuery createQuery(String tableName, ScanCondition condition) { + ScanQuery sq = ScanQuery.of(); + sq.graph = this.graph; + sq.table = tableName; + sq.method = this.method; + sq.scanType = this.scanType; + sq.query = this.query; + sq.position = this.position; + + if (condition != null) { + sq.keyCode = condition.getCode(); + sq.start = condition.getStart().toByteArray(); + sq.end = condition.getEnd().toByteArray(); + sq.prefix = condition.getPrefix().toByteArray(); + sq.serialNo = condition.getSerialNo(); + } + + return sq; + } + + private String getTableName(int tableIndex) { + if (tableIndex + 1 > this.tables.length) { + return null; + } + + return this.tables[tableIndex]; + } + + @Override + public Iterator iterator() { + if (this.conditionList.isEmpty()) { + return new NoConditionsIterator(); + } else { + return new ConditionsIterator(); + } + } + + /** + * Return an Iterator contains Scan-Queries grouped ScanQuery that + * created by same resource but filled with different tables; + * + * @return + */ + public Iterator groupedIterator() { + if (this.conditionList.isEmpty()) { + return new GroupedNoConditionsIterator(); + } else { + return new GroupedConditionsIterator(); + } + } + + /*---------------inner classes below--------------------*/ + + private class GroupedNoConditionsIterator implements Iterator { + private boolean isHasNext = true; + + @Override + public boolean hasNext() { + return isHasNext; + } + + @Override + public ScanQuery[] next() { + if (!this.hasNext()) { + throw new NoSuchElementException(); + } + + ScanQuery[] res = new ScanQuery[ScanQueryProducer.this.tables.length]; + + for (int i = 0; i < res.length; i++) { + res[i] = ScanQueryProducer.this.createQuery(ScanQueryProducer.this.tables[i], null); + } + + this.isHasNext = false; + + return res; + } + } + + private class GroupedConditionsIterator implements Iterator { + private final Iterator conditionIterator = + ScanQueryProducer.this.conditionList.iterator(); + + @Override + public boolean hasNext() { + return conditionIterator.hasNext(); + } + + @Override + public ScanQuery[] next() { + ScanCondition condition = this.conditionIterator.next(); + ScanQuery[] res = new ScanQuery[ScanQueryProducer.this.tables.length]; + + for (int i = 0; i < res.length; i++) { + res[i] = ScanQueryProducer.this.createQuery(ScanQueryProducer.this.tables[i], + condition); + } + + return res; + } + } + + /** + * TODO: no testing + */ + private class NoConditionsIterator implements Iterator { + private String tableName; + private int tableIndex; + + @Override + public boolean hasNext() { + if (this.tableName != null) { + return true; + } + + this.tableName = ScanQueryProducer.this.getTableName(this.tableIndex); + + return this.tableName != null; + } + + @Override + public ScanQuery next() { + if (!this.hasNext()) { + throw new NoSuchElementException(); + } + + ScanQuery res = ScanQueryProducer.this.createQuery(this.tableName, null); + this.tableIndex++; + this.tableName = ScanQueryProducer.this.getTableName(this.tableIndex); + + return res; + } + + } + + /** + * TODO: no testing + */ + private class ConditionsIterator implements Iterator { + private final Iterator conditionIterator = + ScanQueryProducer.this.conditionList.iterator(); + private ScanCondition condition; + private String tableName; + private int tableIndex; + + @Override + public boolean hasNext() { + if (this.condition != null) { + return true; + } + return conditionIterator.hasNext(); + } + + @Override + public ScanQuery next() { + if (!this.hasNext()) { + throw new NoSuchElementException(); + } + if (this.condition == null) { + this.condition = conditionIterator.next(); + } + if (this.tableName == null) { + this.tableName = ScanQueryProducer.this.getTableName(this.tableIndex); + } + + ScanQuery res = ScanQueryProducer.this.createQuery(this.tableName, this.condition); + this.tableIndex++; + this.tableName = ScanQueryProducer.this.getTableName(this.tableIndex); + + if (this.tableName == null) { + this.condition = null; + this.tableIndex = 0; + } + + return res; + } + + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ScanStreamResponse.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ScanStreamResponse.java new file mode 100644 index 000000000..16ff7f0e0 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ScanStreamResponse.java @@ -0,0 +1,261 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.grpc; + +import static org.apache.hugegraph.store.node.grpc.ScanUtil.getIterator; + +import java.util.Collections; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.apache.hugegraph.rocksdb.access.RocksDBSession; +import org.apache.hugegraph.rocksdb.access.ScanIterator; +import org.apache.hugegraph.store.grpc.common.Kv; +import org.apache.hugegraph.store.grpc.stream.KvPageRes; +import org.apache.hugegraph.store.grpc.stream.ScanStreamReq; +import org.apache.hugegraph.store.node.AppConfig; +import org.apache.hugegraph.store.node.util.HgAssert; +import org.apache.hugegraph.store.node.util.HgChannel; +import org.apache.hugegraph.store.node.util.HgGrpc; +import org.apache.hugegraph.store.node.util.HgStoreNodeUtil; + +import com.google.protobuf.ByteString; + +import io.grpc.Status; +import io.grpc.StatusRuntimeException; +import io.grpc.stub.StreamObserver; +import lombok.extern.slf4j.Slf4j; + +/** + * created on 2022/02/17 + * + * @version 3.6.0 + */ +@Slf4j +public class ScanStreamResponse implements StreamObserver { + private static final String msg = + "to wait for client taking data exceeded max time: [{}] seconds,stop scanning."; + private final StreamObserver responseObserver; + private final HgStoreWrapperEx wrapper; + private final AtomicBoolean finishFlag = new AtomicBoolean(); + private final ThreadPoolExecutor executor; + private final AtomicBoolean isStarted = new AtomicBoolean(); + private final AtomicBoolean isStop = new AtomicBoolean(false); + private final AppConfig config; + private final int waitTime; + private final HgChannel channel; + private ScanIterator iterator; + private long limit = 0; + private int times = 0; + private long pageSize = 0; + private int total = 0; + private String graph; + private String table; + + ScanStreamResponse(StreamObserver responseObserver, + HgStoreWrapperEx wrapper, + ThreadPoolExecutor executor, AppConfig appConfig) { + this.responseObserver = responseObserver; + this.wrapper = wrapper; + this.executor = executor; + this.config = appConfig; + this.waitTime = this.config.getServerWaitTime(); + this.channel = HgChannel.of(waitTime); + } + + public static ScanStreamResponse of(StreamObserver responseObserver, + HgStoreWrapperEx wrapper, + ThreadPoolExecutor executor, AppConfig appConfig) { + HgAssert.isArgumentNotNull(responseObserver, "responseObserver"); + HgAssert.isArgumentNotNull(wrapper, "wrapper"); + HgAssert.isArgumentNotNull(executor, "executor"); + return new ScanStreamResponse(responseObserver, wrapper, executor, appConfig); + } + + @Override + public void onNext(ScanStreamReq request) { + try { + if (request.getCloseFlag() == 1) { + close(); + } else { + next(request); + } + } catch (Exception e) { + responseObserver.onError(e); + } + } + + @Override + public void onError(Throwable t) { + this.isStop.set(true); + this.finishServer(); + log.warn("onError from client [ graph: {} , table: {}]; Reason: {}]", graph, table, + t.getMessage()); + } + + @Override + public void onCompleted() { + this.isStop.set(true); + this.finishServer(); + } + + private void initIterator(ScanStreamReq request) { + try { + if (this.isStarted.getAndSet(true)) { + return; + } + this.iterator = getIterator(request, this.wrapper); + this.graph = request.getHeader().getGraph(); + this.table = request.getTable(); + this.limit = request.getLimit(); + this.pageSize = request.getPageSize(); + if (this.pageSize <= 0) { + log.warn( + "As page-Size is less than or equals 0, no data will be send to the " + + "client."); + } + /*** Start scanning loop ***/ + Runnable scanning = () -> + { + // log.debug("Start scanning, graph = {}, table= {}, limit = " + + // "{}, page size = {}", this.graph, this.table, this.limit, + // this.pageSize); + KvPageRes.Builder dataBuilder = KvPageRes.newBuilder(); + Kv.Builder kvBuilder = Kv.newBuilder(); + int pageCount = 0; + try { + while (iterator.hasNext()) { + if (limit > 0 && ++this.total > limit) { + break; + } + if (++pageCount > pageSize) { + long start = System.currentTimeMillis(); + if (!this.channel.send(dataBuilder)) { + if (System.currentTimeMillis() - start >= waitTime * 1000L) { + log.warn(msg, waitTime); + this.timeoutSever(); + } + return; + } + if (this.isStop.get()) { + return; + } + pageCount = 1; + dataBuilder = KvPageRes.newBuilder(); + } + dataBuilder.addData(toKv(kvBuilder, iterator.next(), iterator.position())); + } + this.channel.send(dataBuilder); + } catch (Throwable t) { + String msg = "an exception occurred while scanning data:"; + StatusRuntimeException ex = + HgGrpc.toErr(Status.INTERNAL, msg + t.getMessage(), t); + responseObserver.onError(ex); + } finally { + try { + this.iterator.close(); + this.channel.close(); + } catch (Exception e) { + + } + } + + }; + this.executor.execute(scanning); + } catch (Exception e) { + StatusRuntimeException ex = HgGrpc.toErr(Status.INTERNAL, null, e); + responseObserver.onError(ex); + try { + this.iterator.close(); + this.channel.close(); + } catch (Exception exception) { + + } + } + + /*** Scanning loop end ***/ + } + + private Kv toKv(Kv.Builder kvBuilder, RocksDBSession.BackendColumn col, + byte[] position) { + return kvBuilder + .setKey(ByteString.copyFrom(col.name)) + .setValue(ByteString.copyFrom(col.value)) + .setCode(HgStoreNodeUtil.toInt(position)) + .build(); + } + + private void close() { + this.isStop.set(true); + this.channel.close(); + if (!this.finishFlag.get()) { + responseObserver.onNext(KvPageRes.newBuilder() + .addAllData(Collections.EMPTY_LIST) + .setOver(true) + .setTimes(++times) + .build() + ); + } + + this.finishServer(); + } + + private void next(ScanStreamReq request) { + this.initIterator(request); + KvPageRes.Builder resBuilder; + + try { + resBuilder = this.channel.receive(); + times++; + } catch (Exception e) { + String msg = "failed to poll a page of data, cause by:"; + log.error(msg, e); + responseObserver.onError(HgGrpc.toErr(msg + e.getMessage())); + return; + } + boolean isOver = false; + if (resBuilder == null || resBuilder.getDataList() == null || + resBuilder.getDataList().isEmpty()) { + isOver = true; + resBuilder = KvPageRes.newBuilder().addAllData(Collections.EMPTY_LIST); + } + if (!this.finishFlag.get()) { + responseObserver.onNext(resBuilder.setOver(isOver).setTimes(times).build()); + } + if (isOver) { + this.finishServer(); + } + + } + + private void finishServer() { + if (!this.finishFlag.getAndSet(true)) { + responseObserver.onCompleted(); + } + } + + private void timeoutSever() { + if (!this.finishFlag.getAndSet(true)) { + String msg = "server wait time exceeds the threshold[" + waitTime + + "] seconds."; + responseObserver.onError( + HgGrpc.toErr(Status.Code.DEADLINE_EXCEEDED, msg)); + } + } + +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ScanUtil.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ScanUtil.java new file mode 100644 index 000000000..2fb2a2104 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/ScanUtil.java @@ -0,0 +1,331 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.grpc; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import javax.annotation.concurrent.NotThreadSafe; + +import org.apache.hugegraph.pd.common.KVPair; +import org.apache.hugegraph.rocksdb.access.ScanIterator; +import org.apache.hugegraph.store.business.SelectIterator; +import org.apache.hugegraph.store.grpc.common.ScanMethod; +import org.apache.hugegraph.store.grpc.stream.ScanQueryRequest; +import org.apache.hugegraph.store.grpc.stream.ScanStreamReq; +import org.apache.hugegraph.store.grpc.stream.SelectParam; + +import lombok.extern.slf4j.Slf4j; + + +/** + * created on 2022/02/22 + * + * @version 1.0.0 + */ +@Slf4j +class ScanUtil { + + private final static Map tableKeyMap = new HashMap<>(); + + + static ScanIterator getIterator(ScanStreamReq request, HgStoreWrapperEx wrapper) { + String graph = request.getHeader().getGraph(); + String table = request.getTable(); + ScanMethod method = request.getMethod(); + byte[] start = request.getStart().toByteArray(); + byte[] end = request.getEnd().toByteArray(); + byte[] prefix = request.getPrefix().toByteArray(); + int partition = request.getCode(); + int scanType = request.getScanType(); + byte[] query = request.getQuery().toByteArray(); + + ScanIterator iter = null; + switch (method) { + case ALL: + iter = wrapper.scanAll(graph, table, query); + break; + case PREFIX: + iter = wrapper.scanPrefix(graph, partition, table, prefix, scanType, query); + break; + case RANGE: + iter = wrapper.scan(graph, partition, table, start, end, scanType, query); + break; + } + if (iter == null) { + log.warn("Failed to create a scanIterator with ScanMethod: [" + method + "]"); + iter = new EmptyIterator(); + } + SelectParam selects = request.getSelects(); + List properties = null; + if (selects != null) { + properties = selects.getPropertiesList(); + } + iter = new SelectIterator(iter, properties); + iter.seek(request.getPosition().toByteArray()); + return iter; + } + + static ScanIterator getIterator(ScanQuery sq, HgStoreWrapperEx wrapper) { + if (log.isDebugEnabled()) { + log.debug("{}", sq); + } + + ScanIterator iter = null; + switch (sq.method) { + case ALL: + iter = wrapper.scanAll(sq.graph, sq.table, sq.query); + break; + case PREFIX: + iter = wrapper.scanPrefix(sq.graph, sq.keyCode, sq.table, sq.prefix, sq.scanType, + sq.query); + break; + case RANGE: + iter = wrapper.scan(sq.graph, sq.keyCode, sq.table, sq.start, sq.end, sq.scanType, + sq.query); + break; + } + + if (iter == null) { + log.warn("Failed to create a scanIterator with ScanMethod: [" + sq.method + "]"); + iter = new EmptyIterator(); + } + + iter.seek(sq.position); + + return iter; + + } + + static ScanQuery toSq(ScanStreamReq request) { + ScanQuery res = ScanQuery.of(); + + res.graph = request.getHeader().getGraph(); + res.table = request.getTable(); + res.method = request.getMethod(); + + res.keyCode = request.getCode(); + res.start = request.getStart().toByteArray(); + res.end = request.getEnd().toByteArray(); + res.prefix = request.getPrefix().toByteArray(); + res.scanType = request.getScanType(); + res.query = request.getQuery().toByteArray(); + res.position = request.getPosition().toByteArray(); + + if (log.isDebugEnabled()) { + log.debug("{}", res); + } + //TODO: removed below. + + return res; + } + + static ScanIterator getIterator(String graph, ScanQueryRequest request, + HgStoreWrapperEx wrapper) { + ScanIteratorSupplier supplier = new ScanIteratorSupplier(graph, request, wrapper); + return BatchScanIterator.of(supplier, supplier.getLimitSupplier()); + } + + /** + * 支持并行读取的多迭代器 + */ + static ScanIterator getParallelIterator(String graph, ScanQueryRequest request, + HgStoreWrapperEx wrapper, ThreadPoolExecutor executor) { + ScanIteratorSupplier supplier = new ScanIteratorSupplier(graph, request, wrapper); + return ParallelScanIterator.of(supplier, supplier.getLimitSupplier(), + request, executor); + } + + @NotThreadSafe + private static class ScanIteratorSupplier implements + Supplier> { + + private final AtomicBoolean isEmpty = new AtomicBoolean(); + + private final String graph; + private final long perKeyMax; + private final long skipDegree; + private final HgStoreWrapperEx wrapper; + private long perKeyLimit; + private List sqs = new LinkedList<>(); + private Iterator sqIterator; + + private ScanQueryProducer scanQueryProducer; + private Iterator scanQueryIterator; + + ScanIteratorSupplier(String graph, ScanQueryRequest request, HgStoreWrapperEx wrapper) { + this.graph = graph; + this.perKeyLimit = request.getPerKeyLimit(); + this.perKeyMax = request.getPerKeyMax(); + this.skipDegree = + request.getSkipDegree() == 0 ? Integer.MAX_VALUE : request.getSkipDegree(); + this.wrapper = wrapper; + + if (this.perKeyLimit <= 0) { + this.perKeyLimit = Integer.MAX_VALUE; + log.warn("as perKeyLimit <=0 so default perKeyLimit was effective: {}", + Integer.MAX_VALUE); + } + //init(request); + init2(request); + } + + private void init(ScanQueryRequest request) { + this.sqs = Arrays.stream(request.getTable().split(",")) + .map(table -> { + if (table == null) { + return null; + } + if (table.isEmpty()) { + return null; + } + + List list = request.getConditionList() + .stream() + .map(condition -> { + ScanQuery sq = + ScanQuery.of(); + sq.graph = this.graph; + sq.table = table; + sq.method = + request.getMethod(); + sq.scanType = + request.getScanType(); + sq.query = + request.getQuery() + .toByteArray(); + sq.position = + request.getPosition() + .toByteArray(); + + sq.keyCode = + condition.getCode(); + sq.start = + condition.getStart() + .toByteArray(); + sq.end = condition.getEnd() + .toByteArray(); + sq.prefix = + condition.getPrefix() + .toByteArray(); + sq.serialNo = + condition.getSerialNo(); + return sq; + }) + .filter(e -> e != null) + .collect(Collectors.toList()); + + if (list == null || list.isEmpty()) { + ScanQuery sq = ScanQuery.of(); + sq.graph = this.graph; + sq.table = table; + sq.method = request.getMethod(); + sq.scanType = request.getScanType(); + sq.query = request.getQuery().toByteArray(); + sq.position = request.getPosition().toByteArray(); + list = Collections.singletonList(sq); + } + return list; + + } + ) + .flatMap(e -> e.stream()) + .collect(Collectors.toList()); + + this.sqIterator = this.sqs.iterator(); + } + + + //@Override + public KVPair get1() { + ScanIterator iterator = null; + ScanQuery query = null; + if (this.sqIterator != null && this.sqIterator.hasNext()) { + query = this.sqIterator.next(); + iterator = getIterator(query, this.wrapper); + } else { + this.sqs.clear(); + this.sqIterator = null; + } + return new KVPair<>(query, iterator); + } + + public Supplier getLimitSupplier() { + return () -> Math.min(perKeyLimit, skipDegree); + } + + /*----------- new -to add max --------------*/ + + private void init2(ScanQueryRequest request) { + List tableList = Arrays.stream(request.getTable().split(",")) + .filter(e -> e != null && !e.isEmpty()) + .collect(Collectors.toList()); + + if (tableList.isEmpty()) { + throw new RuntimeException("table name is invalid"); + } + + String[] tables = tableList.toArray(new String[tableList.size()]); + this.scanQueryProducer = ScanQueryProducer.requestOf(this.graph, tables, request); + this.scanQueryIterator = this.scanQueryProducer.groupedIterator(); + } + + @Override + public KVPair get() { + ScanIterator iterator = null; + ScanQuery query = null; + + if (this.scanQueryIterator != null && this.scanQueryIterator.hasNext()) { + ScanQuery[] queries = this.scanQueryIterator.next(); + query = queries[0]; + iterator = FusingScanIterator.maxOf(this.perKeyMax, new Query2Iterator((queries))); + } else { + this.scanQueryProducer = null; + this.scanQueryIterator = null; + } + return new KVPair<>(query, iterator); + } + + private class Query2Iterator implements Supplier { + ScanQuery[] queries; + int index; + + Query2Iterator(ScanQuery[] queries) { + this.queries = queries; + } + + @Override + public ScanIterator get() { + if (index + 1 > queries.length) { + return null; + } + return getIterator(queries[index++], wrapper); + } + } + + } +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/scan/GraphStoreImpl.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/scan/GraphStoreImpl.java new file mode 100644 index 000000000..b7cd6c11c --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/scan/GraphStoreImpl.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.grpc.scan; + +import java.util.concurrent.ThreadPoolExecutor; + +import org.apache.hugegraph.store.business.BusinessHandler; +import org.apache.hugegraph.store.grpc.GraphStoreGrpc.GraphStoreImplBase; +import org.apache.hugegraph.store.grpc.Graphpb; +import org.apache.hugegraph.store.grpc.Graphpb.ResponseHeader; +import org.apache.hugegraph.store.grpc.Graphpb.ScanPartitionRequest; +import org.apache.hugegraph.store.grpc.Graphpb.ScanResponse; +import org.apache.hugegraph.store.node.grpc.HgStoreNodeService; +import org.apache.hugegraph.store.node.grpc.HgStoreStreamImpl; +import org.lognet.springboot.grpc.GRpcService; +import org.springframework.beans.factory.annotation.Autowired; + +import io.grpc.stub.StreamObserver; +import lombok.extern.slf4j.Slf4j; + +/** + * graphpb.proto 实现类 + */ +@Slf4j +@GRpcService +public class GraphStoreImpl extends GraphStoreImplBase { + + private final ResponseHeader okHeader = + ResponseHeader.newBuilder().setError( + Graphpb.Error.newBuilder().setType(Graphpb.ErrorType.OK)) + .build(); + BusinessHandler handler; + @Autowired + private HgStoreNodeService storeService; + @Autowired + private HgStoreStreamImpl storeStream; + + public BusinessHandler getHandler() { + if (this.handler == null) { + synchronized (this) { + if (this.handler == null) { + this.handler = + storeService.getStoreEngine().getBusinessHandler(); + } + } + } + return this.handler; + } + + public ThreadPoolExecutor getExecutor() { + return this.storeStream.getExecutor(); + } + + + /** + * 流式回复消息,每个消息带有seqNo + * 客户端每消费一个消息,应答一个seqNo + * 服务端根据客户端的seqNo决定发送几个数据包 + * + * @param ro + * @return + */ + @Override + public StreamObserver scanPartition( + StreamObserver ro) { + return new ScanResponseObserver(ro, getHandler(), getExecutor()); + } + +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/scan/ScanResponseObserver.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/scan/ScanResponseObserver.java new file mode 100644 index 000000000..83de8b6cd --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/grpc/scan/ScanResponseObserver.java @@ -0,0 +1,270 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.grpc.scan; + +import java.util.ArrayList; +import java.util.concurrent.Future; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReentrantLock; + +import org.apache.hugegraph.store.business.BusinessHandler; +import org.apache.hugegraph.store.business.GraphStoreIterator; +import org.apache.hugegraph.store.grpc.Graphpb.Error; +import org.apache.hugegraph.store.grpc.Graphpb.ErrorType; +import org.apache.hugegraph.store.grpc.Graphpb.ResponseHeader; +import org.apache.hugegraph.store.grpc.Graphpb.ScanPartitionRequest; +import org.apache.hugegraph.store.grpc.Graphpb.ScanPartitionRequest.Request; +import org.apache.hugegraph.store.grpc.Graphpb.ScanPartitionRequest.ScanType; +import org.apache.hugegraph.store.grpc.Graphpb.ScanResponse; + +import com.google.protobuf.Descriptors; + +import io.grpc.stub.StreamObserver; +import lombok.extern.slf4j.Slf4j; + +/** + * @date 2022/10/26 + **/ +@Slf4j +public class ScanResponseObserver implements + StreamObserver { + + private static final int BATCH_SIZE = 100000; + private static final int MAX_PAGE = 8; // + private static final Error ok = Error.newBuilder().setType(ErrorType.OK).build(); + private static final ResponseHeader okHeader = + ResponseHeader.newBuilder().setError(ok).build(); + private final BusinessHandler handler; + private final AtomicInteger nextSeqNo = new AtomicInteger(0); + private final AtomicInteger cltSeqNo = new AtomicInteger(0); + private final ThreadPoolExecutor executor; + private final AtomicBoolean readOver = new AtomicBoolean(false); + private final LinkedBlockingQueue packages = + new LinkedBlockingQueue(MAX_PAGE * 2); + private final Descriptors.FieldDescriptor vertexField = + ScanResponse.getDescriptor().findFieldByNumber(3); + private final Descriptors.FieldDescriptor edgeField = + ScanResponse.getDescriptor().findFieldByNumber(4); + private final ReentrantLock readLock = new ReentrantLock(); + private final ReentrantLock sendLock = new ReentrantLock(); + private StreamObserver sender; + private ScanPartitionRequest scanReq; + private GraphStoreIterator iter; + private volatile long leftCount; + private volatile Future sendTask; + private volatile Future readTask; + + /* + * 2022年11月1日 + * 1.onNext 需要进行异步处理,以防止grpc的调用阻塞 + * 2.不要读取迭代器或者发送数据不要产生线程等待 + * 3.在发送前,尽量准备好要发送的数据 + * */ + + /* + * 2022年11月2日 + * 1.读取rocksdb迭代器的线程read + * 2.进行数据转换并发送到阻塞队列的线程offer + * 3.从阻塞队列读取数据,并发送的线程,包括在没有读取到数据的情况下唤醒读取和发送的线程send + * */ + + public ScanResponseObserver(StreamObserver sender, + BusinessHandler handler, + ThreadPoolExecutor executor) { + this.sender = sender; + this.handler = handler; + this.executor = executor; + } + + private boolean readCondition() { + return packages.remainingCapacity() != 0 && !readOver.get(); + } + + private boolean readTaskCondition() { + return readCondition() && (readTask == null || readTask.isDone()); + } + + private boolean sendCondition() { + return nextSeqNo.get() - cltSeqNo.get() < MAX_PAGE; + } Runnable rr = new Runnable() { + @Override + public void run() { + try { + if (readCondition()) { + synchronized (iter) { + while (readCondition()) { + Request r = scanReq.getScanRequest(); + ScanType t = r.getScanType(); + boolean isVertex = t.equals(ScanType.SCAN_VERTEX); + ArrayList data = new ArrayList<>(BATCH_SIZE); + int count = 0; + while (iter.hasNext() && leftCount > -1) { + count++; + leftCount--; + T next = (T) iter.next(); + data.add(next); + if (count >= BATCH_SIZE) { + offer(data, isVertex); + // data.clear(); + break; + } + } + if (!(iter.hasNext() && leftCount > -1)) { + if (data.size() > 0 && + data.size() < BATCH_SIZE) { + offer(data, isVertex); + } + readOver.set(true); + data = null; + //log.warn("scan complete , count: {},time: {}", + // sum, System.currentTimeMillis()); + return; + } + } + } + } + } catch (Exception e) { + log.warn("read data with error: ", e); + sender.onError(e); + } + } + }; + + private boolean sendTaskCondition() { + return sendCondition() && (sendTask == null || sendTask.isDone()); + } + + private void offer(Iterable data, boolean isVertex) { + ScanResponse.Builder builder = ScanResponse.newBuilder(); + builder.setHeader(okHeader).setSeqNo(nextSeqNo.get()); + if (isVertex) { + builder = builder.setField(vertexField, data); + } else { + builder = builder.setField(edgeField, data); + } + ScanResponse response = builder.build(); + packages.offer(response); + startSend(); + } + + private void startRead() { + if (readTaskCondition()) { + if (readLock.tryLock()) { + if (readTaskCondition()) { + readTask = executor.submit(rr); + } + readLock.unlock(); + } + } + } Runnable sr = () -> { + while (sendCondition()) { + ScanResponse response; + try { + if (readOver.get()) { + if ((response = packages.poll()) == null) { + sender.onCompleted(); + } else { + sender.onNext(response); + nextSeqNo.incrementAndGet(); + } + } else { + response = packages.poll(10, + TimeUnit.MILLISECONDS); + if (response != null) { + sender.onNext(response); + nextSeqNo.incrementAndGet(); + startRead(); + } else { + break; + } + } + + } catch (InterruptedException e) { + break; + } + } + }; + + private void startSend() { + if (sendTaskCondition()) { + if (sendLock.tryLock()) { + if (sendTaskCondition()) { + sendTask = executor.submit(sr); + } + sendLock.unlock(); + } + } + } + + @Override + public void onNext(ScanPartitionRequest scanReq) { + if (scanReq.hasScanRequest() && !scanReq.hasReplyRequest()) { + this.scanReq = scanReq; + Request request = scanReq.getScanRequest(); + long rl = request.getLimit(); + leftCount = rl > 0 ? rl : Long.MAX_VALUE; + iter = handler.scan(scanReq); + if (!iter.hasNext()) { + close(); + sender.onCompleted(); + } else { + readTask = executor.submit(rr); + } + } else { + cltSeqNo.getAndIncrement(); + startSend(); + } + } + + @Override + public void onError(Throwable t) { + close(); + log.warn("receive client error:", t); + } + + @Override + public void onCompleted() { + close(); + } + + private void close() { + try { + nextSeqNo.set(0); + if (sendTask != null) { + sendTask.cancel(true); + } + if (readTask != null) { + readTask.cancel(true); + } + readOver.set(true); + iter.close(); + } catch (Exception e) { + log.warn("on Complete with error:", e); + } + } + + + + + + +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/listener/ContextClosedListener.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/listener/ContextClosedListener.java new file mode 100644 index 000000000..676b90445 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/listener/ContextClosedListener.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.listener; + +import java.util.concurrent.ThreadPoolExecutor; + +import org.apache.hugegraph.store.node.grpc.HgStoreStreamImpl; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextClosedEvent; + +import lombok.extern.slf4j.Slf4j; + +/** + * @date 2023/2/17 + **/ +@Slf4j +public class ContextClosedListener implements ApplicationListener { + + @Autowired + HgStoreStreamImpl storeStream; + + @Override + public void onApplicationEvent(ContextClosedEvent event) { + try { + log.info("closing scan threads...."); + ThreadPoolExecutor executor = storeStream.getRealExecutor(); + if (executor != null) { + try { + executor.shutdownNow(); + } catch (Exception e) { + + } + } + } catch (Exception ignored) { + + } finally { + log.info("closed scan threads"); + } + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/listener/PdConfigureListener.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/listener/PdConfigureListener.java new file mode 100644 index 000000000..b67546198 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/listener/PdConfigureListener.java @@ -0,0 +1,215 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.listener; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.security.SecureRandom; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.function.Consumer; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.hugegraph.pd.client.KvClient; +import org.apache.hugegraph.pd.client.PDConfig; +import org.apache.hugegraph.pd.common.PDException; +import org.apache.hugegraph.pd.grpc.kv.ScanPrefixResponse; +import org.apache.hugegraph.pd.grpc.kv.WatchResponse; +import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.env.MutablePropertySources; +import org.springframework.core.env.PropertiesPropertySource; +import org.yaml.snakeyaml.Yaml; + +import com.google.common.base.Charsets; + +import lombok.extern.slf4j.Slf4j; + +/** + * @date 2022/7/5 + **/ +@Slf4j +public class PdConfigureListener implements + ApplicationListener { + + private static final String CONFIG_PREFIX = "S:"; + private static final String CONFIG_FIX_PREFIX = "S:FS"; + private static final String TIMESTAMP_KEY = "S:Timestamp"; + private static final String PD_CONFIG_FILE_NAME = "application-pd.yml"; + private final String workDir = System.getProperty("user.dir"); + private final String fileSeparator = System.getProperty("file.separator"); + private final String configFilePath = + workDir + fileSeparator + "conf" + fileSeparator + PD_CONFIG_FILE_NAME; + private final String restartShellPath = workDir + fileSeparator + "bin" + fileSeparator + + "restart-hugegraph-store.sh"; + private ConfigurableApplicationContext context; + private File pdConfFile; + // private String restartPath = workDir + fileSeparator + "lib" + fileSeparator; + + @Override + public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { + MutablePropertySources sources = event.getEnvironment().getPropertySources(); + String pdAddress = event.getEnvironment().getProperty("pdserver.address"); + pdConfFile = new File(configFilePath); + // String[] defaultProfiles = event.getEnvironment().getDefaultProfiles(); + // String defaultProfile = defaultProfiles[0]; + // PropertySource appSource = null; + // for (PropertySource source : sources) { + // log.info("source name:{},{}", source.getName(), source.getSource()); + // boolean applicationConfig = source.getName().contains("application.yml"); + // if (applicationConfig) { + // appSource = source; + // break; + // } + // } + // Map appSourceMap = (Map) + // appSource + // .getSource(); + // OriginTrackedValue pdTrackedValue = appSourceMap.get("pdserver.address"); + // String pdAddress = pdTrackedValue.getValue().toString(); + KvClient client = new KvClient(PDConfig.of(pdAddress)); + try { + ScanPrefixResponse response = client.scanPrefix(CONFIG_PREFIX); + Map kvsMap = response.getKvsMap(); + String pdConfig = kvsMap.get(CONFIG_FIX_PREFIX); + if (!StringUtils.isEmpty(pdConfig)) { + updatePdConfig(sources, client, pdConfig); + } else { + // send local application-pd.yml to pd + if (pdConfFile.exists()) { + String commons = FileUtils.readFileToString(pdConfFile, Charsets.UTF_8); + log.info("send local application-pd.yml to pd....{}", commons); + client.put(CONFIG_FIX_PREFIX, commons); + } + } + log.info("Start listening for keys :" + TIMESTAMP_KEY); + client.listen(TIMESTAMP_KEY, (Consumer) o -> { + log.info("receive message to restart :" + o); + try { + // 优先更新最新配置文件,以免修改像端口之类的参数导致旧文件被优先加载 + ScanPrefixResponse responseNew = client.scanPrefix(CONFIG_PREFIX); + Map kvsMapNew = responseNew.getKvsMap(); + String config = kvsMapNew.get(CONFIG_FIX_PREFIX); + updatePdConfig(sources, client, config); + restart(); + } catch (Exception e) { + log.error("start listener with error:", e); + } + }); + + } catch (Exception e) { + log.error("start listener with error:", e); + } + + } + + private void updatePdConfig(MutablePropertySources sources, KvClient client, + String pdConfig) throws + PDException, + IOException { + Properties configs = getYmlConfig(pdConfig); + String property = client.get(TIMESTAMP_KEY).getValue(); + long pdLastModified = 0; + if (!StringUtils.isEmpty(property)) { + pdLastModified = Long.parseLong(property); + } + if (!pdConfFile.exists() || pdConfFile.lastModified() <= pdLastModified) { + log.info("update local application-pd.yml from pd....{}", pdConfig); + writeYml(pdConfig); + PropertiesPropertySource source = new PropertiesPropertySource("pd-config", configs); + sources.addFirst(source); + } + } + + private Properties getYmlConfig(String yml) { + Yaml yaml = new Yaml(); + Iterable load = yaml.loadAll(yml); + Iterator iterator = load.iterator(); + Properties properties = new Properties(); + while (iterator.hasNext()) { + Map next = (Map) iterator.next(); + map2Properties(next, "", properties); + } + return properties; + } + + private void map2Properties(Map map, String prefix, Properties properties) { + + for (Map.Entry entry : map.entrySet()) { + String key = entry.getKey(); + String newPrefix = StringUtils.isEmpty(prefix) ? key : prefix + "." + key; + Object value = entry.getValue(); + if (!(value instanceof Map)) { + properties.put(newPrefix, value); + } else { + map2Properties((Map) value, newPrefix, properties); + } + + } + } + + public ConfigurableApplicationContext getContext() { + return context; + } + + public void setContext(ConfigurableApplicationContext context) { + this.context = context; + } + + // private void restartBySpringBootApplication() { + // ApplicationArguments args = context.getBean(ApplicationArguments.class); + // Thread thread = new Thread(() -> { + // context.close(); + // try { + // Thread.sleep(5000L); + // } catch (InterruptedException e) { + // + // } + // StoreNodeApplication.start(); + // }); + // thread.setDaemon(false); + // thread.start(); + // } + + private void restart() throws InterruptedException, IOException { + ProcessBuilder builder; + String os = System.getProperty("os.name"); + if (os.toLowerCase(Locale.getDefault()).contains("win")) { + builder = new ProcessBuilder("cmd", "/c", restartShellPath).inheritIO(); + } else { + log.info("run shell {}", restartShellPath); + builder = new ProcessBuilder("sh", "-c", restartShellPath).inheritIO(); + } + SecureRandom random = new SecureRandom(); + int sleepTime = random.nextInt(60); + log.info("app will restart in {} seconds:", sleepTime); + Thread.sleep(sleepTime * 1000); + Process process = builder.start(); + log.info("waiting restart.... {}", restartShellPath); + process.waitFor(); + } + + private void writeYml(String yml) throws IOException { + FileUtils.writeStringToFile(pdConfFile, yml, Charset.defaultCharset(), false); + } +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/DriveMetrics.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/DriveMetrics.java new file mode 100644 index 000000000..9e51f62ac --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/DriveMetrics.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.metrics; + +import java.io.File; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * 2021/11/23 + */ +@Deprecated +public class DriveMetrics { + + private static final long MIB = 1024 * 1024; + + // TODO: add a cache + public Map> metrics() { + File[] rootDrive = File.listRoots(); + + if (rootDrive == null) { + return new LinkedHashMap(0); + } + + Map> metrics = new HashMap<>(); + + for (File d : rootDrive) { + Map buf = new HashMap<>(); + buf.put("total_space", d.getTotalSpace() / MIB); + buf.put("free_space", d.getFreeSpace() / MIB); + buf.put("usable_space", d.getUsableSpace() / MIB); + buf.put("size_unit", "MB"); + + metrics.put(d.getPath().replace("\\", ""), buf); + + } + + return metrics; + + } + +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/GRpcExMetrics.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/GRpcExMetrics.java new file mode 100644 index 000000000..d9aa233f9 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/GRpcExMetrics.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.metrics; + +import java.util.concurrent.ThreadPoolExecutor; + +import org.apache.hugegraph.store.node.grpc.GRpcServerConfig; +import org.apache.hugegraph.store.node.util.HgExecutorUtil; + +import io.micrometer.core.instrument.Gauge; +import io.micrometer.core.instrument.MeterRegistry; + +/** + * 2022/3/8 + */ +public class GRpcExMetrics { + public final static String PREFIX = "grpc"; + private final static ExecutorWrapper wrapper = new ExecutorWrapper(); + private static MeterRegistry registry; + + private GRpcExMetrics() { + } + + public synchronized static void init(MeterRegistry meterRegistry) { + if (registry == null) { + registry = meterRegistry; + registerMeters(); + } + } + + private static void registerMeters() { + registerExecutor(); + + } + + private static void registerExecutor() { + + Gauge.builder(PREFIX + ".executor.pool.size", wrapper, (e) -> e.getPoolSize()) + .description("The current number of threads in the pool.") + .register(registry); + + Gauge.builder(PREFIX + ".executor.core.pool.size", wrapper, (e) -> e.getCorePoolSize()) + .description( + "The largest number of threads that have ever simultaneously been in the " + + "pool.") + .register(registry); + + Gauge.builder(PREFIX + ".executor.active.count", wrapper, (e) -> e.getActiveCount()) + .description("The approximate number of threads that are actively executing tasks.") + .register(registry); + } + + private static class ExecutorWrapper { + ThreadPoolExecutor pool; + + void init() { + if (this.pool == null) { + pool = HgExecutorUtil.getThreadPoolExecutor(GRpcServerConfig.EXECUTOR_NAME); + } + } + + double getPoolSize() { + init(); + return this.pool == null ? 0d : this.pool.getPoolSize(); + } + + int getCorePoolSize() { + init(); + return this.pool == null ? 0 : this.pool.getCorePoolSize(); + } + + int getActiveCount() { + init(); + return this.pool == null ? 0 : this.pool.getActiveCount(); + } + + } + +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/JRaftMetrics.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/JRaftMetrics.java new file mode 100644 index 000000000..1d7029861 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/JRaftMetrics.java @@ -0,0 +1,314 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.metrics; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.hugegraph.store.HgStoreEngine; +import org.apache.hugegraph.store.node.util.HgRegexUtil; + +import com.alipay.sofa.jraft.core.NodeMetrics; +import com.codahale.metrics.Counter; +import com.codahale.metrics.Meter; +import com.codahale.metrics.Snapshot; +import com.codahale.metrics.Timer; + +import io.micrometer.core.instrument.Gauge; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Tag; +import lombok.extern.slf4j.Slf4j; + +/** + * 2022/1/4 + */ +@Slf4j +public class JRaftMetrics { + public final static String PREFIX = "jraft"; + public static final String LABELS = "quantile"; + public static final String LABEL_50 = "0.5"; + public static final String LABEL_75 = "0.75"; + public static final String LABEL_95 = "0.95"; + public static final String LABEL_98 = "0.98"; + public static final String LABEL_99 = "0.99"; + public static final String LABEL_999 = "0.999"; + private final static HgStoreEngine storeEngine = HgStoreEngine.getInstance(); + private final static AtomicInteger groups = new AtomicInteger(0); + private final static Tag handleDataTag = Tag.of("handle", "data"); + // private final static Tag handleTxTag = Tag.of("handle", "tx"); //reservation + private final static Set groupSet = new HashSet<>(); + private final static String REGEX_REFINE_REPLICATOR = "(replicator)(.+?:\\d+)(.*)"; + private static MeterRegistry registry; + + private JRaftMetrics() { + } + + public synchronized static void init(MeterRegistry meterRegistry) { + if (registry == null) { + registry = meterRegistry; + registerMeters(); + } + } + + private static void registerMeters() { + Gauge.builder(PREFIX + ".groups", JRaftMetrics::updateGroups) + .description("Number of raft-groups, which handled the data of graph.") + .tags(Collections.singleton(handleDataTag)) + .register(registry); + + } + + private static int updateGroups() { + int buf = getGroups(); + if (buf != groups.get()) { + groups.set(buf); + registerNodeMetrics(); + } + return buf; + } + + private static int getGroups() { + return storeEngine.getRaftGroupCount(); + } + + private static Map getRaftGroupMetrics() { + Map map = storeEngine.getNodeMetrics(); + + if (map == null) { + return Collections.emptyMap(); + } + + return map; + } + + private static void registerNodeMetrics() { + Map map = getRaftGroupMetrics(); + + synchronized (groupSet) { + map.forEach((group, metrics) -> { + if (!groupSet.add(group)) { + return; + } + + metrics.getMetricRegistry().getGauges() + .forEach((k, v) -> registerGauge(group, k, v)); + metrics.getMetricRegistry().getMeters() + .forEach((k, v) -> registerMeter(group, k, v)); + metrics.getMetricRegistry().getCounters() + .forEach((k, v) -> registerCounter(group, k, v)); + metrics.getMetricRegistry().getTimers() + .forEach((k, v) -> registerTimer(group, k, v)); + metrics.getMetricRegistry().getHistograms() + .forEach((k, v) -> registerHistogram(group, k, v)); + }); + } + + } + + private static HistogramWrapper toWrapper(com.codahale.metrics.Histogram histogram) { + return new HistogramWrapper(histogram); + } + + private static String refineMetrics(String name, List tags) { + if (name == null || name.isEmpty()) { + return name; + } + + List buf = HgRegexUtil.toGroupValues(REGEX_REFINE_REPLICATOR, name); + String res = null; + + /*Extracted name of replicator into a tag.*/ + + if (buf != null && buf.size() == 4) { + res = buf.get(1) + buf.get(3); + + String value = buf.get(2); + + if (value != null && value.startsWith("-")) { + value = value.substring(1); + } + + tags.add(Tag.of("replicator", value)); + } else { + res = name; + } + + return res; + } + + private static void registerHistogram(String group, String name, + com.codahale.metrics.Histogram histogram) { + if (histogram == null) { + return; + } + + List tags = new LinkedList<>(); + tags.add(handleDataTag); + tags.add(Tag.of("group", group)); + + name = refineMetrics(name, tags); + + String baseName = PREFIX + "." + name.toLowerCase(); + + HistogramWrapper wrapper = toWrapper(histogram); + + Gauge.builder(baseName + ".median", wrapper, (d) -> d.getSnapshot().getMedian()) + .tags(tags).register(registry); + Gauge.builder(baseName + ".min", wrapper, (d) -> d.getSnapshot().getMin()) + .tags(tags).register(registry); + Gauge.builder(baseName + ".max", wrapper, (d) -> d.getSnapshot().getMax()) + .tags(tags).register(registry); + Gauge.builder(baseName + ".mean", wrapper, (d) -> d.getSnapshot().getMean()) + .tags(tags).register(registry); + + baseName = baseName + ".summary"; + Gauge.builder(baseName, wrapper, (d) -> d.getSnapshot().getMedian()) + .tags(tags).tag(LABELS, LABEL_50).register(registry); + Gauge.builder(baseName, wrapper, (d) -> d.getSnapshot().get75thPercentile()) + .tags(tags).tag(LABELS, LABEL_75).register(registry); + Gauge.builder(baseName, wrapper, (d) -> d.getSnapshot().get95thPercentile()) + .tags(tags).tag(LABELS, LABEL_95).register(registry); + Gauge.builder(baseName, wrapper, (d) -> d.getSnapshot().get98thPercentile()) + .tags(tags).tag(LABELS, LABEL_98).register(registry); + Gauge.builder(baseName, wrapper, (d) -> d.getSnapshot().get99thPercentile()) + .tags(tags).tag(LABELS, LABEL_99).register(registry); + Gauge.builder(baseName, wrapper, (d) -> d.getSnapshot().get999thPercentile()) + .tags(tags).tag(LABELS, LABEL_999).register(registry); + + Gauge.builder(baseName + ".sum", wrapper, + (d) -> Arrays.stream(d.getSnapshot().getValues()).sum()) + .tags(tags).register(registry); + Gauge.builder(baseName + ".count", wrapper, (d) -> d.getSnapshot().size()) + .tags(tags).register(registry); + + } + + private static void registerTimer(String group, String name, com.codahale.metrics.Timer timer) { + List tags = new LinkedList<>(); + tags.add(handleDataTag); + tags.add(Tag.of("group", group)); + + name = refineMetrics(name, tags); + + String baseName = PREFIX + "." + name.toLowerCase(); + + Gauge.builder(baseName + ".count", timer, Timer::getCount) + .tags(tags).register(registry); + + Gauge.builder(baseName + ".timer", timer, Timer::getCount) + .tags(tags).tag("rate", "1m").register(registry); + Gauge.builder(baseName + ".timer", timer, Timer::getCount) + .tags(tags).tag("rate", "5m").register(registry); + Gauge.builder(baseName + ".timer", timer, Timer::getCount) + .tags(tags).tag("rate", "15m").register(registry); + Gauge.builder(baseName + ".timer", timer, Timer::getCount) + .tags(tags).tag("rate", "mean").register(registry); + + } + + private static void registerMeter(String group, String name, com.codahale.metrics.Meter meter) { + List tags = new LinkedList<>(); + tags.add(handleDataTag); + tags.add(Tag.of("group", group)); + + name = refineMetrics(name, tags); + + String baseName = PREFIX + "." + name.toLowerCase(); + + Gauge.builder(baseName + ".count", meter, Meter::getCount) + .tags(tags) + .register(registry); + + Gauge.builder(baseName + ".rate", meter, Meter::getCount) + .tags(tags).tag("rate", "1m") + .register(registry); + Gauge.builder(baseName + ".rate", meter, Meter::getCount) + .tags(tags).tag("rate", "5m") + .register(registry); + Gauge.builder(baseName + ".rate", meter, Meter::getCount) + .tags(tags).tag("rate", "15m") + .register(registry); + Gauge.builder(baseName + ".rate", meter, Meter::getCount) + .tags(tags).tag("rate", "mean") + .register(registry); + + } + + private static void registerCounter(String group, String name, + com.codahale.metrics.Counter counter) { + List tags = new LinkedList<>(); + tags.add(handleDataTag); + tags.add(Tag.of("group", group)); + + name = refineMetrics(name, tags); + + name = name.toLowerCase(); + + //Adapted a counter to be a gauge. + Gauge.builder(PREFIX + "." + name + ".count", counter, Counter::getCount) + .tags(tags).register(registry); + } + + private static void registerGauge(String group, String name, + com.codahale.metrics.Gauge gauge) { + List tags = new LinkedList<>(); + tags.add(handleDataTag); + tags.add(Tag.of("group", group)); + + name = refineMetrics(name, tags); + + name = name.toLowerCase(); + + if (gauge.getValue() instanceof Number) { + Gauge.builder(PREFIX + "." + name, gauge, (g) -> ((Number) g.getValue()).doubleValue()) + .tags(tags).register(registry); + } else { + Gauge.builder(PREFIX + "." + name, () -> 1.0) + .tags(tags) + .tag("str.gauge", String.valueOf(gauge.getValue())).register(registry); + } + + } + + private static class HistogramWrapper { + private final com.codahale.metrics.Histogram histogram; + + private Snapshot snapshot; + private long ts = System.currentTimeMillis(); + + HistogramWrapper(com.codahale.metrics.Histogram histogram) { + this.histogram = histogram; + this.snapshot = this.histogram.getSnapshot(); + } + + Snapshot getSnapshot() { + if (System.currentTimeMillis() - this.ts > 30_000) { + this.snapshot = this.histogram.getSnapshot(); + this.ts = System.currentTimeMillis(); + } + return this.snapshot; + } + } + +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/MetricsConfig.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/MetricsConfig.java new file mode 100644 index 000000000..801470370 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/MetricsConfig.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.metrics; + +import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import io.micrometer.core.instrument.MeterRegistry; + +/** + * 2021/11/24 + */ +@Configuration +public class MetricsConfig { + + @Bean + public MeterRegistryCustomizer metricsCommonTags() { + return (registry) -> registry.config().commonTags("hg", "store"); + } + + @Bean + public MeterRegistryCustomizer registerMeters() { + return (registry) -> { + StoreMetrics.init(registry); + RocksDBMetrics.init(registry); + JRaftMetrics.init(registry); + ProcfsMetrics.init(registry); + GRpcExMetrics.init(registry); + }; + } + +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/MetricsUtil.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/MetricsUtil.java new file mode 100644 index 000000000..987d3080d --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/MetricsUtil.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.metrics; + +import com.codahale.metrics.Counter; +import com.codahale.metrics.Gauge; +import com.codahale.metrics.Histogram; +import com.codahale.metrics.Meter; +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.Timer; + +@Deprecated +public class MetricsUtil { + + private static final MetricRegistry registry = new MetricRegistry(); + + public static Gauge registerGauge(Class clazz, String name, + Gauge gauge) { + return registry.register(MetricRegistry.name(clazz, name), gauge); + } + + public static Counter registerCounter(Class clazz, String name) { + return registry.counter(MetricRegistry.name(clazz, name)); + } + + public static Histogram registerHistogram(Class clazz, String name) { + return registry.histogram(MetricRegistry.name(clazz, name)); + } + + public static Meter registerMeter(Class clazz, String name) { + return registry.meter(MetricRegistry.name(clazz, name)); + } + + public static Timer registerTimer(Class clazz, String name) { + return registry.timer(MetricRegistry.name(clazz, name)); + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/ProcfsEntry.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/ProcfsEntry.java new file mode 100644 index 000000000..f4266d89f --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/ProcfsEntry.java @@ -0,0 +1,63 @@ +/* + * Copyright © 2016 Michael Weirauch (michael.weirauch@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hugegraph.store.node.metrics; + + +import static org.apache.hugegraph.store.node.metrics.ProcfsReader.ReadResult; + +import java.io.IOException; +import java.util.Collection; +import java.util.Objects; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +// TODO: refer license later, 83% match, maybe refer to metrics-jvm-extras (0.1.3) APL2.0 +abstract class ProcfsEntry { + + private static final Logger log = LoggerFactory.getLogger(ProcfsEntry.class); + + private final Object lock = new Object(); + + private final ProcfsReader reader; + + private long lastHandle = -1; + + protected ProcfsEntry(ProcfsReader reader) { + this.reader = Objects.requireNonNull(reader); + } + + protected final void collect() { + synchronized (lock) { + try { + final ReadResult result = reader.read(); + if (result != null && (lastHandle == -1 || lastHandle != result.getReadTime())) { + reset(); + handle(result.getLines()); + lastHandle = result.getReadTime(); + } + } catch (IOException e) { + reset(); + log.warn("Failed reading '" + reader.getEntryPath() + "'!", e); + } + } + } + + protected abstract void reset(); + + protected abstract void handle(Collection lines); + +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/ProcfsMetrics.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/ProcfsMetrics.java new file mode 100644 index 000000000..997b95d8e --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/ProcfsMetrics.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.metrics; + + +import io.micrometer.core.instrument.Gauge; +import io.micrometer.core.instrument.MeterRegistry; + +/** + * 2022/3/1 + * + * @version 0.1.0 + */ +public class ProcfsMetrics { + + public final static String PREFIX = "process_memory"; + private final static ProcfsSmaps smaps = new ProcfsSmaps(); + private static MeterRegistry registry; + + private ProcfsMetrics() { + + } + + public synchronized static void init(MeterRegistry meterRegistry) { + if (registry == null) { + registry = meterRegistry; + registerMeters(); + } + } + + private static void registerMeters() { + registerProcessGauge(); + } + + private static void registerProcessGauge() { + Gauge.builder(PREFIX + ".rss.bytes", () -> smaps.get(ProcfsSmaps.KEY.RSS)) + .register(registry); + + Gauge.builder(PREFIX + ".pss.bytes", () -> smaps.get(ProcfsSmaps.KEY.PSS)) + .register(registry); + + Gauge.builder(PREFIX + ".vss.bytes", () -> smaps.get(ProcfsSmaps.KEY.VSS)) + .register(registry); + + Gauge.builder(PREFIX + ".swap.bytes", () -> smaps.get(ProcfsSmaps.KEY.SWAP)) + .register(registry); + + Gauge.builder(PREFIX + ".swappss.bytes", () -> smaps.get(ProcfsSmaps.KEY.SWAPPSS)) + .register(registry); + } + +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/ProcfsReader.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/ProcfsReader.java new file mode 100644 index 000000000..108719ca5 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/ProcfsReader.java @@ -0,0 +1,141 @@ +/* + * Copyright © 2016 Michael Weirauch (michael.weirauch@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hugegraph.store.node.metrics; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; + +// TODO: refer license later, 90% match, maybe refer to metrics-jvm-extras (0.1.3) APL2.0 +class ProcfsReader { + + /* default */ static final long CACHE_DURATION_MS = 100; + private static final Map instances = new HashMap<>(); + private static final Object instancesLock = new Object(); + private static final Map> data = new HashMap<>(); + private static final Object dataLock = new Object(); + private static final Path BASE = Paths.get("/proc", "self"); + private final Path entryPath; + private final boolean osSupport; + /* default */ long lastReadTime = -1; + + private ProcfsReader(String entry) { + this(BASE, entry, false); + } + + /* default */ ProcfsReader(Path base, String entry) { + this(base, entry, true); + } + + private ProcfsReader(Path base, String entry, boolean forceOSSupport) { + Objects.requireNonNull(base); + Objects.requireNonNull(entry); + + this.entryPath = base.resolve(entry); + + this.osSupport = forceOSSupport + || System.getProperty("os.name").toLowerCase(Locale.ENGLISH) + .startsWith("linux"); + } + + /* default */ + static ProcfsReader getInstance(String entry) { + Objects.requireNonNull(entry); + + synchronized (instancesLock) { + ProcfsReader reader = instances.get(entry); + if (reader == null) { + reader = new ProcfsReader(entry); + instances.put(entry, reader); + } + return reader; + } + } + + /* default */ Path getEntryPath() { + return entryPath; + } + + /* default */ ReadResult read() throws IOException { + return read(currentTime()); + } + + /* default */ ReadResult read(long currentTimeMillis) throws IOException { + synchronized (dataLock) { + final Path key = getEntryPath().getFileName(); + + final ReadResult readResult; + if (lastReadTime == -1 || lastReadTime + CACHE_DURATION_MS < currentTimeMillis) { + final List lines = readPath(entryPath); + cacheResult(key, lines); + lastReadTime = currentTime(); + readResult = new ReadResult(lines, lastReadTime); + } else { + readResult = new ReadResult(data.get(key), lastReadTime); + } + return readResult; + } + } + + /* default */ List readPath(Path path) throws IOException { + Objects.requireNonNull(path); + + if (!osSupport) { + return Collections.emptyList(); + } + return Files.readAllLines(path); + } + + /* default */ void cacheResult(Path key, List lines) { + Objects.requireNonNull(key); + Objects.requireNonNull(lines); + + data.put(key, lines); + } + + /* default */ long currentTime() { + return System.currentTimeMillis(); + } + + /* default */ static class ReadResult { + + private final List lines; + + private final long readTime; + + /* default */ ReadResult(List lines, long readTime) { + this.lines = Objects.requireNonNull(lines); + this.readTime = readTime; + } + + public long getReadTime() { + return readTime; + } + + public List getLines() { + return lines; + } + + } + +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/ProcfsSmaps.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/ProcfsSmaps.java new file mode 100644 index 000000000..72efa4716 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/ProcfsSmaps.java @@ -0,0 +1,113 @@ +/* + * Copyright © 2016 Michael Weirauch (michael.weirauch@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hugegraph.store.node.metrics; + +import java.util.Collection; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.LongUnaryOperator; + +// TODO: refer license later, 88% match, maybe refer to metrics-jvm-extras (0.1.0) APL2.0 +public class ProcfsSmaps extends ProcfsEntry { + + private static final int KILOBYTE = 1024; + private final Map values = new HashMap<>(); + + public ProcfsSmaps() { + super(ProcfsReader.getInstance("smaps")); + } + + /* default */ ProcfsSmaps(ProcfsReader reader) { + super(reader); + } + + private static long parseKiloBytes(String line) { + Objects.requireNonNull(line); + + return Long.parseLong(line.split("\\s+")[1]); + } + + @Override + protected void reset() { + EnumSet.allOf(KEY.class).forEach(key -> values.put(key, new AtomicLong(-1))); + } + + @Override + protected void handle(Collection lines) { + Objects.requireNonNull(lines); + + for (final String line : lines) { + if (line.startsWith("Size:")) { + inc(KEY.VSS, parseKiloBytes(line) * KILOBYTE); + } else if (line.startsWith("Rss:")) { + inc(KEY.RSS, parseKiloBytes(line) * KILOBYTE); + } else if (line.startsWith("Pss:")) { + inc(KEY.PSS, parseKiloBytes(line) * KILOBYTE); + } else if (line.startsWith("Swap:")) { + inc(KEY.SWAP, parseKiloBytes(line) * KILOBYTE); + } else if (line.startsWith("SwapPss:")) { + inc(KEY.SWAPPSS, parseKiloBytes(line) * KILOBYTE); + } + } + } + + public Long get(KEY key) { + Objects.requireNonNull(key); + + collect(); + return Long.valueOf(values.get(key).longValue()); + } + + private void inc(KEY key, long increment) { + Objects.requireNonNull(key); + + values.get(key).getAndUpdate(new LongUnaryOperator() { + + @Override + public long applyAsLong(long currentValue) { + return currentValue + increment + (currentValue == -1 ? 1 : 0); + } + + }); + } + + public enum KEY { + /** + * Virtual set size + */ + VSS, + /** + * Resident set size + */ + RSS, + /** + * Proportional set size + */ + PSS, + /** + * Paged out memory + */ + SWAP, + /** + * Paged out memory accounting shared pages. Since Linux 4.3. + */ + SWAPPSS + } + +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/RocksDBMetrics.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/RocksDBMetrics.java new file mode 100644 index 000000000..4df4b2b71 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/RocksDBMetrics.java @@ -0,0 +1,419 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.metrics; + +import static org.apache.hugegraph.store.node.metrics.RocksDBMetricsConst.HISTOGRAMS; +import static org.apache.hugegraph.store.node.metrics.RocksDBMetricsConst.LABELS; +import static org.apache.hugegraph.store.node.metrics.RocksDBMetricsConst.LABEL_50; +import static org.apache.hugegraph.store.node.metrics.RocksDBMetricsConst.LABEL_95; +import static org.apache.hugegraph.store.node.metrics.RocksDBMetricsConst.LABEL_99; +import static org.apache.hugegraph.store.node.metrics.RocksDBMetricsConst.PREFIX; +import static org.apache.hugegraph.store.node.metrics.RocksDBMetricsConst.TICKERS; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import org.apache.hugegraph.rocksdb.access.RocksDBFactory; +import org.apache.hugegraph.rocksdb.access.RocksDBSession; +import org.apache.hugegraph.store.HgStoreEngine; +import org.apache.hugegraph.store.node.util.HgAssert; +import org.rocksdb.HistogramData; +import org.rocksdb.HistogramType; +import org.rocksdb.MemoryUsageType; +import org.rocksdb.Statistics; +import org.rocksdb.TickerType; + +import io.micrometer.core.instrument.Gauge; +import io.micrometer.core.instrument.Meter; +import io.micrometer.core.instrument.MeterRegistry; +import lombok.extern.slf4j.Slf4j; + +/** + * 2021/12/30 + * + * @version 1.2.0 on 2022/03/22 added auto meter removing when graph has been closed. + */ +@Slf4j +public class RocksDBMetrics { + private final static RocksDBFactory rocksDBFactory = RocksDBFactory.getInstance(); + private final static AtomicInteger rocks = new AtomicInteger(0); + private final static Set graphSet = new HashSet<>(); + private final static HgStoreEngine storeEngine = HgStoreEngine.getInstance(); + private final static MemoryUseWrapper memoryUseWrapper = new MemoryUseWrapper(); + private final static Map statisticsHolder = new HashMap<>(); + private final static Map histogramHolder = new HashMap<>(); + private final static Map> graphMeterMap = new ConcurrentHashMap<>(); + private static MeterRegistry registry; + + private RocksDBMetrics() { + } + + public static void init(final MeterRegistry meterRegistry) { + HgAssert.isArgumentNotNull(meterRegistry, "meterRegistry"); + + if (registry != null) { + return; + + } + + registry = meterRegistry; + + Gauge.builder("rocks.num", RocksDBMetrics::updateRocks) + .description("Number of instance of RocksDB running in this node") + .register(registry); + + registerMemoryUse(); + } + + private static int updateRocks() { + int buf = getRocks(); + + if (buf != rocks.get()) { + rocks.set(buf); + registerMeter(); + } + + return buf; + } + + private static int getRocks() { + return rocksDBFactory.getSessionSize(); + } + + private static Set getGraphs() { + return rocksDBFactory.getGraphNames(); + } + + private static RocksDBSession getRocksDBSession(String graph) { + return rocksDBFactory.queryGraphDB(graph); + } + + + private static synchronized void registerMeter() { + Set graphs = getGraphs(); + + if (graphs == null) { + log.error( + "Failed to fetch the collection of names of graph, when invoking to register " + + "RocksDB gauge."); + return; + } + + graphs.forEach(g -> { + if (!graphSet.add(g)) { + return; + } + + StatisticsWrapper stats = new StatisticsWrapper(g); + statisticsHolder.put(g, stats); + + for (final TickerType ticker : TICKERS) { + String gaugeName = PREFIX + "." + ticker.name().toLowerCase(); + + saveGraphMeter(g, + Gauge.builder(gaugeName, () -> stats.getTickerCount(ticker)) + .description("RocksDB reported statistics for " + ticker.name()) + .tag("graph", g) + .register(registry) + ); + } + + for (final HistogramType histogram : HISTOGRAMS) { + registerHistogram(g, registry, histogram, stats); + } + + registrySessionRefNum(g); + + }); + + graphSet.removeAll(graphSet.stream().filter(g -> !graphs.contains(g)) + .peek(g -> removeMeters(g)) + .collect(Collectors.toList()) + ); + + } + + private static void saveGraphMeter(String g, Meter meter) { + graphMeterMap.computeIfAbsent(g, k -> new HashSet<>()).add(meter); + } + + private static void removeMeters(String g) { + graphMeterMap.getOrDefault(g, Collections.emptySet()).forEach(e -> registry.remove(e)); + } + + private static void registerHistogram(String graph, MeterRegistry registry, HistogramType + histogramType, StatisticsWrapper stats) { + + HistogramDataWrapper histogram = new HistogramDataWrapper(histogramType, + () -> stats.getHistogramData( + histogramType)); + histogramHolder.put(histogram, histogramType); + + String baseName = PREFIX + "." + histogramType.name().toLowerCase(); + saveGraphMeter(graph, + Gauge.builder(baseName + ".max", histogram, HistogramDataWrapper::getMax) + .tag("graph", graph).register(registry)); + saveGraphMeter(graph, Gauge.builder(baseName + ".mean", histogram, + HistogramDataWrapper::getAverage).tag("graph", graph) + .register(registry)); + saveGraphMeter(graph, + Gauge.builder(baseName + ".min", histogram, HistogramDataWrapper::getMin) + .tag("graph", graph).register(registry)); + + baseName = baseName + ".summary"; + saveGraphMeter(graph, Gauge.builder(baseName, histogram, HistogramDataWrapper::getMedian) + .tags("graph", graph, LABELS, LABEL_50).register(registry)); + saveGraphMeter(graph, + Gauge.builder(baseName, histogram, HistogramDataWrapper::getPercentile95) + .tags("graph", graph, LABELS, LABEL_95).register(registry)); + saveGraphMeter(graph, + Gauge.builder(baseName, histogram, HistogramDataWrapper::getPercentile99) + .tags("graph", graph, LABELS, LABEL_99).register(registry)); + saveGraphMeter(graph, + Gauge.builder(baseName + ".sum", histogram, HistogramDataWrapper::getSum) + .tags("graph", graph).register(registry)); + saveGraphMeter(graph, + Gauge.builder(baseName + ".count", histogram, HistogramDataWrapper::getCount) + .tags("graph", graph).register(registry)); + + } + + private static void registerMemoryUse() { + Gauge.builder(PREFIX + ".table.reader.total", memoryUseWrapper, + (e) -> e.getTableReaderTotal()) + .description("The current number of threads in the pool.") + .register(registry); + Gauge.builder(PREFIX + ".mem.table.total", memoryUseWrapper, (e) -> e.getMemTableTotal()) + .description("The current number of threads in the pool.") + .register(registry); + Gauge.builder(PREFIX + ".mem.table.unFlushed", memoryUseWrapper, + (e) -> e.getMemTableUnFlushed()) + .description("The current number of threads in the pool.") + .register(registry); + Gauge.builder(PREFIX + ".cache.total", memoryUseWrapper, (e) -> e.getCacheTotal()) + .description("The current number of threads in the pool.") + .register(registry); + Gauge.builder(PREFIX + ".block.cache.pinned-usage", memoryUseWrapper, + (e) -> e.getProperty("rocksdb.block-cache-pinned-usage")) + .description("The current number of threads in the pool.") + .register(registry); + + } + + private static void registrySessionRefNum(String graph) { + + SessionWrapper sessionWrapper = new SessionWrapper(graph); + saveGraphMeter(graph, + Gauge.builder(PREFIX + ".session.ref.count", sessionWrapper, + (e) -> e.getRefCount() - 1) + .description("The current amount of reference of session") + .tag("ref", "self").tag("graph", graph) + .strongReference(true) + .register(registry) + ); + + } + + private static T getValue(S stat, Function fun, T defaultValue) { + if (stat == null) { + return defaultValue; + } + return fun.apply(stat); + } + + private static class SessionWrapper { + + private final String graphName; + + SessionWrapper(String graph) { + + this.graphName = graph; + } + + public int getRefCount() { + try (RocksDBSession session = getRocksDBSession(graphName)) { + if (session != null) { + return getValue(session, e -> e.getRefCount(), -1); + } + return 0; + } + } + } + + private static class MemoryUseWrapper { + Map mems = null; + long lastTime = 0; + + private void loadData() { + if (mems == null || System.currentTimeMillis() - lastTime > 30000) { + mems = storeEngine.getBusinessHandler().getApproximateMemoryUsageByType(null); + lastTime = System.currentTimeMillis(); + } + } + + public Long getTableReaderTotal() { + loadData(); + return mems.get(MemoryUsageType.kTableReadersTotal); + } + + public Long getMemTableTotal() { + loadData(); + return mems.get(MemoryUsageType.kMemTableTotal); + } + + public Long getCacheTotal() { + loadData(); + return mems.get(MemoryUsageType.kCacheTotal); + } + + public Long getMemTableUnFlushed() { + loadData(); + return mems.get(MemoryUsageType.kMemTableUnFlushed); + } + + public Long getProperty(String property) { + Set graphs = rocksDBFactory.getGraphNames(); + if (graphs.size() > 0) { + try (RocksDBSession session = getRocksDBSession((String) graphs.toArray()[0])) { + if (session != null) { + return Long.parseLong(session.getProperty(property)); + } + } + } + return null; + } + } + + private static class StatisticsWrapper { + + private final String graphName; + private final Map tickerCounteMap = new ConcurrentHashMap<>(); + private final Map histogramDataMap = + new ConcurrentHashMap<>(); + long lastTime = 0; + + StatisticsWrapper(String graph) { + + this.graphName = graph; + loadData(); + + } + + private void loadData() { + if (System.currentTimeMillis() - lastTime < 30000) { + return; + } + lastTime = System.currentTimeMillis(); + try (RocksDBSession session = getRocksDBSession(graphName)) { + if (session == null) { + // log.error("Failed to fetch the RocksDBSession with graph's name: [ " + + // graph + " ]"); + return; + } + + Statistics statistics = session.getRocksDbStats(); + for (final TickerType ticker : TICKERS) { + tickerCounteMap.put(ticker, statistics.getTickerCount(ticker)); + } + + for (final HistogramType histogram : HISTOGRAMS) { + histogramDataMap.put(histogram, statistics.getHistogramData(histogram)); + } + } + } + + public long getTickerCount(TickerType tickerType) { + this.loadData(); + return tickerCounteMap.containsKey(tickerType) ? tickerCounteMap.get(tickerType) : 0; + } + + public HistogramData getHistogramData(HistogramType histogramType) { + this.loadData(); + return histogramDataMap.get(histogramType); + } + + } + + private static class HistogramDataWrapper { + private final Supplier supplier; + private final HistogramType histogramType; + private HistogramData data = new HistogramData(0d, 0d, 0d, 0d, 0d); + private long ts = System.currentTimeMillis() - 30_000; + + HistogramDataWrapper(HistogramType histogramType, Supplier supplier) { + this.supplier = supplier; + this.histogramType = histogramType; + } + + private HistogramData getData() { + if (System.currentTimeMillis() - this.ts > 30_000) { + HistogramData buf = this.supplier.get(); + if (buf != null) { + this.data = buf; + this.ts = System.currentTimeMillis(); + } + } + return this.data; + } + + public double getMedian() { + return getData().getMedian(); + } + + public double getPercentile95() { + return getData().getPercentile95(); + } + + public double getPercentile99() { + return getData().getPercentile99(); + } + + public double getAverage() { + return getData().getAverage(); + } + + public double getStandardDeviation() { + return getData().getStandardDeviation(); + } + + public double getMax() { + return getData().getMax(); + } + + public long getCount() { + return getData().getCount(); + } + + public long getSum() { + return getData().getSum(); + } + + public double getMin() { + return getData().getMin(); + } + + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/RocksDBMetricsConst.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/RocksDBMetricsConst.java new file mode 100644 index 000000000..e0a89e9f5 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/RocksDBMetricsConst.java @@ -0,0 +1,165 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.metrics; + +import org.rocksdb.HistogramType; +import org.rocksdb.TickerType; + +/** + * TODO: refer license later, 80% match, maybe refer to pantheon, This file need refactor! + */ +public final class RocksDBMetricsConst { + public static final String PREFIX = "rocks.stats"; + public static final String LABELS = "quantile"; + public static final String LABEL_50 = "0.5"; + public static final String LABEL_95 = "0.95"; + public static final String LABEL_99 = "0.99"; + + // Tickers - RocksDB equivalent of counters + public static final TickerType[] TICKERS = { + TickerType.BLOCK_CACHE_ADD, + TickerType.BLOCK_CACHE_HIT, + TickerType.BLOCK_CACHE_ADD_FAILURES, + TickerType.BLOCK_CACHE_INDEX_MISS, + TickerType.BLOCK_CACHE_INDEX_HIT, + TickerType.BLOCK_CACHE_INDEX_ADD, + TickerType.BLOCK_CACHE_INDEX_BYTES_INSERT, + TickerType.BLOCK_CACHE_INDEX_BYTES_EVICT, + TickerType.BLOCK_CACHE_FILTER_MISS, + TickerType.BLOCK_CACHE_FILTER_HIT, + TickerType.BLOCK_CACHE_FILTER_ADD, + TickerType.BLOCK_CACHE_FILTER_BYTES_INSERT, + TickerType.BLOCK_CACHE_FILTER_BYTES_EVICT, + TickerType.BLOCK_CACHE_DATA_MISS, + TickerType.BLOCK_CACHE_DATA_HIT, + TickerType.BLOCK_CACHE_DATA_ADD, + TickerType.BLOCK_CACHE_DATA_BYTES_INSERT, + TickerType.BLOCK_CACHE_BYTES_READ, + TickerType.BLOCK_CACHE_BYTES_WRITE, + TickerType.BLOOM_FILTER_USEFUL, + TickerType.PERSISTENT_CACHE_HIT, + TickerType.PERSISTENT_CACHE_MISS, + TickerType.SIM_BLOCK_CACHE_HIT, + TickerType.SIM_BLOCK_CACHE_MISS, + TickerType.MEMTABLE_HIT, + TickerType.MEMTABLE_MISS, + TickerType.GET_HIT_L0, + TickerType.GET_HIT_L1, + TickerType.GET_HIT_L2_AND_UP, + TickerType.COMPACTION_KEY_DROP_NEWER_ENTRY, + TickerType.COMPACTION_KEY_DROP_OBSOLETE, + TickerType.COMPACTION_KEY_DROP_RANGE_DEL, + TickerType.COMPACTION_KEY_DROP_USER, + TickerType.COMPACTION_RANGE_DEL_DROP_OBSOLETE, + TickerType.NUMBER_KEYS_WRITTEN, + TickerType.NUMBER_KEYS_READ, + TickerType.NUMBER_KEYS_UPDATED, + TickerType.BYTES_WRITTEN, + TickerType.BYTES_READ, + TickerType.NUMBER_DB_SEEK, + TickerType.NUMBER_DB_NEXT, + TickerType.NUMBER_DB_PREV, + TickerType.NUMBER_DB_SEEK_FOUND, + TickerType.NUMBER_DB_NEXT_FOUND, + TickerType.NUMBER_DB_PREV_FOUND, + TickerType.ITER_BYTES_READ, + TickerType.NO_FILE_CLOSES, + TickerType.NO_FILE_OPENS, + TickerType.NO_FILE_ERRORS, + // TickerType.STALL_L0_SLOWDOWN_MICROS, + // TickerType.STALL_MEMTABLE_COMPACTION_MICROS, + // TickerType.STALL_L0_NUM_FILES_MICROS, + TickerType.STALL_MICROS, + TickerType.DB_MUTEX_WAIT_MICROS, + TickerType.RATE_LIMIT_DELAY_MILLIS, + TickerType.NO_ITERATORS, + TickerType.NUMBER_MULTIGET_BYTES_READ, + TickerType.NUMBER_MULTIGET_KEYS_READ, + TickerType.NUMBER_MULTIGET_CALLS, + TickerType.NUMBER_FILTERED_DELETES, + TickerType.NUMBER_MERGE_FAILURES, + TickerType.BLOOM_FILTER_PREFIX_CHECKED, + TickerType.BLOOM_FILTER_PREFIX_USEFUL, + TickerType.NUMBER_OF_RESEEKS_IN_ITERATION, + TickerType.GET_UPDATES_SINCE_CALLS, + TickerType.BLOCK_CACHE_COMPRESSED_MISS, + TickerType.BLOCK_CACHE_COMPRESSED_HIT, + TickerType.BLOCK_CACHE_COMPRESSED_ADD, + TickerType.BLOCK_CACHE_COMPRESSED_ADD_FAILURES, + TickerType.WAL_FILE_SYNCED, + TickerType.WAL_FILE_BYTES, + TickerType.WRITE_DONE_BY_SELF, + TickerType.WRITE_DONE_BY_OTHER, + TickerType.WRITE_TIMEDOUT, + TickerType.WRITE_WITH_WAL, + TickerType.COMPACT_READ_BYTES, + TickerType.COMPACT_WRITE_BYTES, + TickerType.FLUSH_WRITE_BYTES, + TickerType.NUMBER_DIRECT_LOAD_TABLE_PROPERTIES, + TickerType.NUMBER_SUPERVERSION_ACQUIRES, + TickerType.NUMBER_SUPERVERSION_RELEASES, + TickerType.NUMBER_SUPERVERSION_CLEANUPS, + TickerType.NUMBER_BLOCK_COMPRESSED, + TickerType.NUMBER_BLOCK_DECOMPRESSED, + TickerType.NUMBER_BLOCK_NOT_COMPRESSED, + TickerType.MERGE_OPERATION_TOTAL_TIME, + TickerType.FILTER_OPERATION_TOTAL_TIME, + TickerType.ROW_CACHE_HIT, + TickerType.ROW_CACHE_MISS, + TickerType.READ_AMP_ESTIMATE_USEFUL_BYTES, + TickerType.READ_AMP_TOTAL_READ_BYTES, + TickerType.NUMBER_RATE_LIMITER_DRAINS, + TickerType.NUMBER_ITER_SKIP, + TickerType.NUMBER_MULTIGET_KEYS_FOUND, + }; + + // Histograms - treated as prometheus summaries + public static final HistogramType[] HISTOGRAMS = { + HistogramType.DB_GET, + HistogramType.DB_WRITE, + HistogramType.COMPACTION_TIME, + HistogramType.SUBCOMPACTION_SETUP_TIME, + HistogramType.TABLE_SYNC_MICROS, + HistogramType.COMPACTION_OUTFILE_SYNC_MICROS, + HistogramType.WAL_FILE_SYNC_MICROS, + HistogramType.MANIFEST_FILE_SYNC_MICROS, + HistogramType.TABLE_OPEN_IO_MICROS, + HistogramType.DB_MULTIGET, + HistogramType.READ_BLOCK_COMPACTION_MICROS, + HistogramType.READ_BLOCK_GET_MICROS, + HistogramType.WRITE_RAW_BLOCK_MICROS, + HistogramType.STALL_L0_SLOWDOWN_COUNT, + HistogramType.STALL_MEMTABLE_COMPACTION_COUNT, + HistogramType.STALL_L0_NUM_FILES_COUNT, + HistogramType.HARD_RATE_LIMIT_DELAY_COUNT, + HistogramType.SOFT_RATE_LIMIT_DELAY_COUNT, + HistogramType.NUM_FILES_IN_SINGLE_COMPACTION, + HistogramType.DB_SEEK, + HistogramType.WRITE_STALL, + HistogramType.SST_READ_MICROS, + HistogramType.NUM_SUBCOMPACTIONS_SCHEDULED, + HistogramType.BYTES_PER_READ, + HistogramType.BYTES_PER_WRITE, + HistogramType.BYTES_PER_MULTIGET, + HistogramType.BYTES_COMPRESSED, + HistogramType.BYTES_DECOMPRESSED, + HistogramType.COMPRESSION_TIMES_NANOS, + HistogramType.DECOMPRESSION_TIMES_NANOS, + HistogramType.READ_NUM_MERGE_OPERANDS, + }; +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/StoreMetrics.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/StoreMetrics.java new file mode 100644 index 000000000..1abf176d7 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/StoreMetrics.java @@ -0,0 +1,112 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.metrics; + + +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; + +import org.apache.hugegraph.store.HgStoreEngine; +import org.apache.hugegraph.store.meta.Partition; + +import io.micrometer.core.instrument.Gauge; +import io.micrometer.core.instrument.MeterRegistry; + +/** + * 2021/12/28 + */ +public final class StoreMetrics { + public final static String PREFIX = "hg"; + private final static HgStoreEngine storeEngine = HgStoreEngine.getInstance(); + private final static AtomicInteger graphs = new AtomicInteger(0); + private static MeterRegistry registry; + + private StoreMetrics() { + } + + public synchronized static void init(MeterRegistry meterRegistry) { + if (registry == null) { + registry = meterRegistry; + registerMeters(); + } + } + + private static void registerMeters() { + Gauge.builder(PREFIX + ".up", () -> 1).register(registry); + Gauge.builder(PREFIX + ".graphs", StoreMetrics::updateGraphs) + .description("Number of graphs stored in this node") + .register(registry); + + } + + private static int getGraphs() { + return getGraphPartitions().size(); + } + + private static int updateGraphs() { + int buf = getGraphs(); + if (buf != graphs.get()) { + graphs.set(buf); + registerPartitionGauge(); + } + return buf; + } + + private static void registerPartitionGauge() { + Map> map = getGraphPartitions(); + + map.forEach((k, v) -> Gauge.builder(PREFIX + ".partitions", new PartitionsGetter(k)) + .description("Number of partitions stored in the node") + .tag("graph", k) + .register(registry)); + + } + + private static int getPartitions(String graph) { + Map map = getGraphPartitions().get(graph); + if (map == null) { + return 0; + } else { + return map.size(); + } + } + + private static Map> getGraphPartitions() { + Map> map = + storeEngine.getPartitionManager().getPartitions(); + if (map == null) { + return Collections.emptyMap(); + } + return map; + } + + private static class PartitionsGetter implements Supplier { + private final String graph; + + PartitionsGetter(String graph) { + this.graph = graph; + } + + @Override + public Number get() { + return getPartitions(this.graph); + } + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/SystemMetrics.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/SystemMetrics.java new file mode 100644 index 000000000..caf8c59b0 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/metrics/SystemMetrics.java @@ -0,0 +1,140 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.metrics; + +import java.lang.management.ClassLoadingMXBean; +import java.lang.management.GarbageCollectorMXBean; +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryUsage; +import java.lang.management.ThreadMXBean; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; +import org.apache.hugegraph.util.Bytes; + +@Deprecated +public class SystemMetrics { + + private static final long MB = Bytes.MB; + + private static long totalNonHeapMemory() { + try { + return ManagementFactory.getMemoryMXBean() + .getNonHeapMemoryUsage() + .getCommitted(); + } catch (Throwable ignored) { + return 0; + } + } + + private static String formatName(String name) { + return StringUtils.replace(name, " ", "_").toLowerCase(); + } + + public Map> metrics() { + Map> metrics = new LinkedHashMap<>(); + metrics.put("basic", this.getBasicMetrics()); + metrics.put("heap", this.getHeapMetrics()); + metrics.put("nonheap", this.getNonHeapMetrics()); + metrics.put("thread", this.getThreadMetrics()); + metrics.put("class_loading", this.getClassLoadingMetrics()); + metrics.put("garbage_collector", this.getGarbageCollectionMetrics()); + + return metrics; + } + + private Map getBasicMetrics() { + Map metrics = new LinkedHashMap<>(); + Runtime runtime = Runtime.getRuntime(); + // Heap allocated memory (measured in bytes) + long total = runtime.totalMemory(); + // Heap free memory + long free = runtime.freeMemory(); + long used = total - free; + + metrics.put("mem", (total + totalNonHeapMemory()) / MB); + metrics.put("mem_total", total / MB); + metrics.put("mem_used", used / MB); + metrics.put("mem_free", free / MB); + metrics.put("mem_unit", "MB"); + metrics.put("processors", runtime.availableProcessors()); + metrics.put("uptime", ManagementFactory.getRuntimeMXBean().getUptime()); + metrics.put("systemload_average", + ManagementFactory.getOperatingSystemMXBean() + .getSystemLoadAverage()); + return metrics; + } + + private Map getHeapMetrics() { + Map metrics = new LinkedHashMap<>(); + MemoryUsage memoryUsage = ManagementFactory.getMemoryMXBean() + .getHeapMemoryUsage(); + metrics.put("committed", memoryUsage.getCommitted() / MB); + metrics.put("init", memoryUsage.getInit() / MB); + metrics.put("used", memoryUsage.getUsed() / MB); + metrics.put("max", memoryUsage.getMax() / MB); + return metrics; + } + + private Map getNonHeapMetrics() { + Map metrics = new LinkedHashMap<>(); + MemoryUsage memoryUsage = ManagementFactory.getMemoryMXBean() + .getNonHeapMemoryUsage(); + metrics.put("committed", memoryUsage.getCommitted() / MB); + metrics.put("init", memoryUsage.getInit() / MB); + metrics.put("used", memoryUsage.getUsed() / MB); + metrics.put("max", memoryUsage.getMax() / MB); + return metrics; + } + + private Map getThreadMetrics() { + Map metrics = new LinkedHashMap<>(); + ThreadMXBean threadMxBean = ManagementFactory.getThreadMXBean(); + metrics.put("peak", threadMxBean.getPeakThreadCount()); + metrics.put("daemon", threadMxBean.getDaemonThreadCount()); + metrics.put("total_started", threadMxBean.getTotalStartedThreadCount()); + metrics.put("count", threadMxBean.getThreadCount()); + return metrics; + } + + private Map getClassLoadingMetrics() { + Map metrics = new LinkedHashMap<>(); + ClassLoadingMXBean classLoadingMxBean = ManagementFactory + .getClassLoadingMXBean(); + metrics.put("count", classLoadingMxBean.getLoadedClassCount()); + metrics.put("loaded", classLoadingMxBean.getTotalLoadedClassCount()); + metrics.put("unloaded", classLoadingMxBean.getUnloadedClassCount()); + return metrics; + } + + private Map getGarbageCollectionMetrics() { + Map metrics = new LinkedHashMap<>(); + List gcMxBeans = ManagementFactory + .getGarbageCollectorMXBeans(); + for (GarbageCollectorMXBean gcMxBean : gcMxBeans) { + String name = formatName(gcMxBean.getName()); + metrics.put(name + "_count", gcMxBean.getCollectionCount()); + metrics.put(name + "_time", gcMxBean.getCollectionTime()); + } + metrics.put("time_unit", "ms"); + return metrics; + } + +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/model/HgNodeStatus.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/model/HgNodeStatus.java new file mode 100644 index 000000000..e615d8046 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/model/HgNodeStatus.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.model; + +import java.util.Objects; + +/** + * created on 2021/11/1 + */ +public class HgNodeStatus { + private int status; + private String text; + + public HgNodeStatus(int status, String text) { + this.status = status; + this.text = text; + } + + public int getStatus() { + return status; + } + + public HgNodeStatus setStatus(int status) { + this.status = status; + return this; + } + + public String getText() { + return text; + } + + public HgNodeStatus setText(String text) { + this.text = text; + return this; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + HgNodeStatus that = (HgNodeStatus) o; + return status == that.status && Objects.equals(text, that.text); + } + + @Override + public int hashCode() { + return Objects.hash(status, text); + } + + @Override + public String toString() { + return "HgNodeStatus{" + + "status=" + status + + ", text='" + text + '\'' + + '}'; + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/Base58.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/Base58.java new file mode 100644 index 000000000..925dd5231 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/Base58.java @@ -0,0 +1,172 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.util; + +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; + +/** + * TODO: refer license later, 78% match, maybe refer to google? ensure it later + */ +public class Base58 { + + public static final char[] ALPHABET = + "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray(); + private static final int[] INDEXES = new int[128]; + + static { + for (int i = 0; i < INDEXES.length; i++) { + INDEXES[i] = -1; + } + for (int i = 0; i < ALPHABET.length; i++) { + INDEXES[ALPHABET[i]] = i; + } + } + + /** + * Encodes the given bytes in base58. No checksum is appended. + */ + public static String encode(byte[] input) { + if (input.length == 0) { + return ""; + } + input = copyOfRange(input, 0, input.length); + // Count leading zeroes. + int zeroCount = 0; + while (zeroCount < input.length && input[zeroCount] == 0) { + ++zeroCount; + } + // The actual encoding. + byte[] temp = new byte[input.length * 2]; + int j = temp.length; + + int startAt = zeroCount; + while (startAt < input.length) { + byte mod = divmod58(input, startAt); + if (input[startAt] == 0) { + ++startAt; + } + temp[--j] = (byte) ALPHABET[mod]; + } + + // Strip extra '1' if there are some after decoding. + while (j < temp.length && temp[j] == ALPHABET[0]) { + ++j; + } + // Add as many leading '1' as there were leading zeros. + while (--zeroCount >= 0) { + temp[--j] = (byte) ALPHABET[0]; + } + + byte[] output = copyOfRange(temp, j, temp.length); + return new String(output, StandardCharsets.US_ASCII); + } + + public static byte[] decode(String input) throws IllegalArgumentException { + if (input.length() == 0) { + return new byte[0]; + } + byte[] input58 = new byte[input.length()]; + // Transform the String to a base58 byte sequence + for (int i = 0; i < input.length(); ++i) { + char c = input.charAt(i); + + int digit58 = -1; + if (c >= 0 && c < 128) { + digit58 = INDEXES[c]; + } + if (digit58 < 0) { + throw new IllegalArgumentException("Illegal character " + c + " at " + i); + } + + input58[i] = (byte) digit58; + } + // Count leading zeroes + int zeroCount = 0; + while (zeroCount < input58.length && input58[zeroCount] == 0) { + ++zeroCount; + } + // The encoding + byte[] temp = new byte[input.length()]; + int j = temp.length; + + int startAt = zeroCount; + while (startAt < input58.length) { + byte mod = divmod256(input58, startAt); + if (input58[startAt] == 0) { + ++startAt; + } + + temp[--j] = mod; + } + // Do no add extra leading zeroes, move j to first non null byte. + while (j < temp.length && temp[j] == 0) { + ++j; + } + + return copyOfRange(temp, j - zeroCount, temp.length); + } + + public static BigInteger decodeToBigInteger(String input) throws IllegalArgumentException { + return new BigInteger(1, decode(input)); + } + + // + // number -> number / 58, returns number % 58 + // + private static byte divmod58(byte[] number, int startAt) { + int remainder = 0; + for (int i = startAt; i < number.length; i++) { + int digit256 = (int) number[i] & 0xFF; + int temp = remainder * 256 + digit256; + + number[i] = (byte) (temp / 58); + + remainder = temp % 58; + } + + return (byte) remainder; + } + + // + // number -> number / 256, returns number % 256 + // + private static byte divmod256(byte[] number58, int startAt) { + int remainder = 0; + for (int i = startAt; i < number58.length; i++) { + int digit58 = (int) number58[i] & 0xFF; + int temp = remainder * 58 + digit58; + + number58[i] = (byte) (temp / 256); + + remainder = temp % 256; + } + + return (byte) remainder; + } + + private static byte[] copyOfRange(byte[] source, int from, int to) { + byte[] range = new byte[to - from]; + System.arraycopy(source, from, range, 0, range.length); + + return range; + } + + +} + diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/Err.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/Err.java new file mode 100644 index 000000000..d810f79ac --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/Err.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.util; + + +class Err { + private final String msg; + + private Err(String msg) { + this.msg = msg; + } + + public static Err of(String msg) { + return new Err(msg); + } + + @Override + public String toString() { + return "Err{" + + "msg='" + msg + '\'' + + '}'; + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/HgAssert.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/HgAssert.java new file mode 100644 index 000000000..466e24517 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/HgAssert.java @@ -0,0 +1,145 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.util; + +import java.util.Collection; +import java.util.Map; +import java.util.function.Supplier; + + +public final class HgAssert { + @Deprecated + public static void isTrue(boolean expression, String message) { + if (message == null) { + throw new IllegalArgumentException("message is null"); + } + if (!expression) { + throw new IllegalArgumentException(message); + } + } + + public static void isTrue(boolean expression, Supplier msg) { + if (msg == null) { + throw new IllegalArgumentException("message supplier is null"); + } + if (!expression) { + throw new IllegalArgumentException(msg.get()); + } + } + + @Deprecated + public static void isTrue(boolean expression, RuntimeException e) { + if (e == null) { + throw new IllegalArgumentException("e is null"); + } + if (!expression) { + throw e; + } + } + + public static void isFalse(boolean expression, String message) { + isTrue(!expression, message); + } + + public static void isFalse(boolean expression, Supplier msg) { + isTrue(!expression, msg); + } + + public static void isFalse(boolean expression, RuntimeException e) { + isTrue(!expression, e); + } + + public static void isArgumentValid(byte[] bytes, String parameter) { + isFalse(isInvalid(bytes), () -> "The argument is invalid: " + parameter); + } + + public static void isArgumentValid(String str, String parameter) { + isFalse(isInvalid(str), () -> "The argument is invalid: " + parameter); + } + + public static void isArgumentNotNull(Object obj, String parameter) { + isTrue(obj != null, () -> "The argument is null: " + parameter); + } + + public static void istValid(byte[] bytes, String msg) { + isFalse(isInvalid(bytes), msg); + } + + public static void isValid(String str, String msg) { + isFalse(isInvalid(str), msg); + } + + public static void isNotNull(Object obj, String msg) { + isTrue(obj != null, msg); + } + + public static boolean isContains(Object[] objs, Object obj) { + if (objs == null || objs.length == 0 || obj == null) { + return false; + } + for (Object item : objs) { + if (obj.equals(item)) { + return true; + } + } + return false; + } + + public static boolean isInvalid(String... strs) { + if (strs == null || strs.length == 0) { + return true; + } + for (String item : strs) { + if (item == null || "".equals(item.trim())) { + return true; + } + } + return false; + } + + public static boolean isInvalid(byte[] bytes) { + return bytes == null || bytes.length == 0; + } + + public static boolean isInvalid(Map map) { + return map == null || map.isEmpty(); + } + + public static boolean isInvalid(Collection list) { + return list == null || list.isEmpty(); + } + + public static boolean isContains(Collection list, T item) { + if (list == null || item == null) { + return false; + } + return list.contains(item); + } + + public static boolean isNull(Object... objs) { + if (objs == null) { + return true; + } + for (Object item : objs) { + if (item == null) { + return true; + } + } + return false; + } +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/HgBufferProxy.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/HgBufferProxy.java new file mode 100644 index 000000000..358899d50 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/HgBufferProxy.java @@ -0,0 +1,175 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.util; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Executor; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import javax.annotation.CheckForNull; + +import lombok.extern.slf4j.Slf4j; + +/** + * 2022/3/15 + * + * @version 0.2.0 + */ +@Slf4j +public final class HgBufferProxy { + private final BlockingQueue> queue; + private final AtomicBoolean closed = new AtomicBoolean(false); + private final ReentrantLock lock = new ReentrantLock(); + private final Runnable applier; + private final Executor executor; + + private HgBufferProxy(Executor executor, Runnable applier) { + this.executor = executor; + this.applier = applier; + this.queue = new LinkedBlockingQueue<>(); + } + + public static HgBufferProxy of(Executor executor, Runnable applier) { + HgAssert.isArgumentNotNull(applier, "applier"); + HgAssert.isArgumentNotNull(executor, "executor"); + return new HgBufferProxy(executor, applier); + } + + public void send(T t) { + if (t == null) { + throw new IllegalArgumentException("the argument t is null"); + } + + if (this.closed.get()) { + log.warn("the proxy has been closed"); + return; + } + + this.lock.lock(); + try { + this.queue.offer(() -> t); + } finally { + lock.unlock(); + } + } + + /** + * return an item from the chan + * + * @throws RuntimeException when fail to receive an item. + */ + @CheckForNull + public T receive(int timeoutSeconds) { + return receive(timeoutSeconds, (timeout) -> { + throw new RuntimeException("Timeout, max time: " + timeout + " seconds;"); + }); + } + + private void apply() { + this.lock.lock(); + try { + if (!this.closed.get()) { + this.executor.execute(this.applier); + Thread.yield(); + } + } finally { + this.lock.unlock(); + } + + } + + /** + * return an item from the chan + * + * @return null when the chan has been closed + * @throws RuntimeException + */ + @CheckForNull + public T receive(int timeoutSeconds, Consumer timeoutCallBack) { + Supplier s = null; + + if (this.closed.get()) { + s = this.queue.poll(); + return s != null ? s.get() : null; + } + + if (this.queue.size() <= 1) { + this.apply(); + } + + lock.lock(); + try { + if (this.isClosed()) { + s = this.queue.poll(); + return s != null ? s.get() : null; + } + } finally { + lock.unlock(); + } + + try { + s = this.queue.poll(timeoutSeconds, TimeUnit.SECONDS); + } catch (Throwable t) { + log.error("Failed to receive a item from chan. cause by: ", t); + throw new RuntimeException(t); + } + + if (s == null) { + if (this.closed.get()) { + s = this.queue.poll(); + } else { + if (timeoutCallBack == null) { + throw new RuntimeException( + "Timeout, max time: " + timeoutSeconds + " seconds;"); + } else { + timeoutCallBack.accept(timeoutSeconds); + } + } + } + + return s != null ? s.get() : null; + + } + + public boolean isClosed() { + return this.closed.get(); + } + + /** + * @throws RuntimeException when fail to close the chan + */ + public void close() { + if (this.closed.get()) { + return; + } + lock.lock(); + this.closed.set(true); + try { + this.queue.offer(() -> null); + } finally { + lock.unlock(); + } + + } + +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/HgChannel.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/HgChannel.java new file mode 100644 index 000000000..b51a38e10 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/HgChannel.java @@ -0,0 +1,151 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.util; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import javax.annotation.CheckForNull; + +import lombok.extern.slf4j.Slf4j; + +/** + * Golang style channel without buffering + *

+ * 2022/2/28 + * + * @version 1.1 on 2022/04/02 + */ +@Slf4j +public final class HgChannel { + + private final BlockingQueue> queue; + private final AtomicBoolean closed = new AtomicBoolean(false); + private final long timeoutSeconds; + + private HgChannel(long timeoutSeconds) { + this.timeoutSeconds = timeoutSeconds; + this.queue = new SynchronousQueue(); + } + + public static HgChannel of() { + return new HgChannel(Long.MAX_VALUE); + } + + public static HgChannel of(long timeoutSeconds) { + return new HgChannel(timeoutSeconds); + } + + /** + * @param t + * @return true if send successfully, false when it is timeout. + * @throws IllegalArgumentException when the argument t is null. + * @throws IllegalStateException when the chan has been closed. + * @throws RuntimeException when InterruptedException happen + */ + public boolean send(T t) { + if (t == null) { + throw new IllegalArgumentException("the argument t is null"); + } + synchronized (this.queue) { + if (this.closed.get()) { + return false; + } + boolean flag; + try { + flag = this.queue.offer(() -> t, timeoutSeconds, TimeUnit.SECONDS); + } catch (InterruptedException e) { + log.error("failed to send a item to chan. cause by: ", t); + throw new RuntimeException(e); + } + return flag; + } + } + + + /** + * return an item from the chan + * + * @throws RuntimeException when fail to receive an item. + */ + @CheckForNull + public T receive() { + return receive((timeout) -> { + throw new RuntimeException("Timeout, max time: " + timeout + " seconds;"); + }); + } + + /** + * return an item from the chan + * + * @return null when the chan has been closed + * @throws RuntimeException + */ + @CheckForNull + public T receive(Consumer timeoutCallBack) { + Supplier s; + synchronized (this.closed) { + if (this.closed.get()) { + s = this.queue.poll(); + } else { + try { + s = this.queue.poll(timeoutSeconds, TimeUnit.SECONDS); + } catch (Throwable t) { + log.error("Failed to receive a item from chan. cause by: ", t); + throw new RuntimeException(t); + } + if (s == null) { + if (timeoutCallBack == null) { + throw new RuntimeException( + "Timeout, max time: " + timeoutSeconds + " seconds;"); + } else { + timeoutCallBack.accept(timeoutSeconds); + } + } + } + } + if (s == null) { + return null; + } else { + return s.get(); + } + } + + public boolean isClosed() { + return this.closed.get(); + } + + /** + * @throws RuntimeException when fail to close the chan + */ + public void close() { + if (this.closed.get()) { + return; + } + this.closed.set(true); + this.queue.offer(() -> null); + Thread.yield(); + this.queue.poll(); + + } + +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/HgExecutorUtil.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/HgExecutorUtil.java new file mode 100644 index 000000000..250cc3210 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/HgExecutorUtil.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.util; + +import java.util.Map; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import lombok.extern.slf4j.Slf4j; + +/** + * 2022/03/04 + */ +@Slf4j +public final class HgExecutorUtil { + + private final static Map threadPoolMap = new ConcurrentHashMap<>(); + + public static ThreadFactory newThreadFactory(String namePrefix) { + return new HgDefaultThreadFactory(namePrefix); + } + + public static ThreadPoolExecutor getThreadPoolExecutor(String name) { + if (name == null) { + return null; + } + return threadPoolMap.get(name); + } + + public static ThreadPoolExecutor createExecutor(String name, int coreThreads, int maxThreads, + int queueSize) { + ThreadPoolExecutor res = threadPoolMap.get(name); + if (res != null) { + return res; + } + synchronized (threadPoolMap) { + res = threadPoolMap.get(name); + if (res != null) { + return res; + } + BlockingQueue queue; + if (queueSize <= 0) { + queue = new SynchronousQueue(); + } else { + queue = new LinkedBlockingQueue<>(queueSize); + } + res = new ThreadPoolExecutor(coreThreads, maxThreads, 60L, TimeUnit.SECONDS, queue, + newThreadFactory(name)); + threadPoolMap.put(name, res); + } + return res; + } + + /** + * The default thread factory, which added threadNamePrefix in construction method. + */ + static class HgDefaultThreadFactory implements ThreadFactory { + + private final ThreadGroup group; + private final AtomicInteger threadNumber = new AtomicInteger(1); + private final String namePrefix; + + HgDefaultThreadFactory(String threadNamePrefix) { + SecurityManager s = System.getSecurityManager(); + group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); + this.namePrefix = threadNamePrefix + "-"; + } + + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0); + t.setDaemon(true); + t.setPriority(Thread.NORM_PRIORITY); + return t; + } + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/HgGrpc.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/HgGrpc.java new file mode 100644 index 000000000..b438c8825 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/HgGrpc.java @@ -0,0 +1,122 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.util; + +import org.apache.hugegraph.store.grpc.common.Key; +import org.apache.hugegraph.store.grpc.common.Kv; +import org.apache.hugegraph.store.grpc.common.ResCode; +import org.apache.hugegraph.store.grpc.common.ResStatus; +import org.apache.hugegraph.store.grpc.common.Tk; +import org.apache.hugegraph.store.grpc.common.Tse; +import org.apache.hugegraph.store.term.HgPair; + +import com.google.common.base.Throwables; +import com.google.protobuf.ByteString; + +import io.grpc.Status; +import io.grpc.StatusRuntimeException; +import jline.internal.Log; + + +public abstract class HgGrpc { + private static final ResCode OK = ResCode.RES_CODE_OK; + + public static ResStatus not() { + return toStatus(ResCode.RES_CODE_NOT_EXIST, "not exist"); + } + + public static ResStatus fail() { + return toStatus(ResCode.RES_CODE_FAIL, "failure"); + } + + public static ResStatus fail(String msg) { + return toStatus(ResCode.RES_CODE_FAIL, msg); + } + + public static ResStatus success() { + return success("success"); + } + + public static ResStatus success(String msg) { + return toStatus(ResCode.RES_CODE_OK, msg); + } + + public static ResStatus toStatus(ResCode code, String msg) { + return ResStatus.newBuilder() + .setCode(code) + .setMsg(msg).build(); + } + + public static HgPair toHgPair(Key key) { + return new HgPair(key.getCode(), key.getKey().toByteArray()); + } + + public static HgPair toHgPair(Tk tk) { + return new HgPair( + tk.getTable(), tk.getKey().toByteArray() + ); + } + + public static Kv toKv(HgPair pair) { + return toKv(pair, Kv.newBuilder()); + } + + public static Kv toKv(HgPair pair, Kv.Builder builder) { + return builder.clear() + .setKey(ByteString.copyFrom(pair.getKey())) + .setValue(ByteString.copyFrom(pair.getValue())) + .build(); + } + + public static HgPair toHgPair(Tse tse) { + return new HgPair( + tse.getStart().getKey().toByteArray(), + tse.getEnd().getKey().toByteArray() + ); + } + + public static StatusRuntimeException toErr(String msg) { + return toErr(Status.INTERNAL, msg, null); + } + + public static StatusRuntimeException toErr(Status.Code code, + String des) { + return toErr(code, des, null); + } + + public static StatusRuntimeException toErr(Status.Code code, + String des, + Throwable t) { + return toErr(code.toStatus(), des, t); + } + + public static StatusRuntimeException toErr(Status status, + String des, + Throwable t) { + if (t != null) { + // 为给client返回完整异常信息 + des = (des == null ? "" : des + ",") + + Throwables.getStackTraceAsString(t); + } + Status wdStatus = status.withDescription(des); + Log.error(wdStatus); + Status fullStatus = wdStatus.withCause(t); + return new StatusRuntimeException(fullStatus); + } + +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/HgRegexUtil.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/HgRegexUtil.java new file mode 100644 index 000000000..3f243863b --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/HgRegexUtil.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.util; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * created on 2022/03/07 + */ +public class HgRegexUtil { + + public static String getGroupValue(String regex, String source, int groupId) { + if (regex == null || "".equals(regex) || source == null || "".equals(source)) { + return null; + } + + String value = ""; + + Pattern p = Pattern.compile(regex); + Matcher m = p.matcher(source); + + while (m.find()) { + for (int i = 0; i <= m.groupCount(); i++) { + if (i == groupId) { + value = m.group(i); + } + } + } + return value; + } + + public static List toGroupValues(String regex, String source) { + if (regex == null || "".equals(regex) || source == null || "".equals(source)) { + return null; + } + + Pattern p = Pattern.compile(regex); + Matcher m = p.matcher(source); + + List list = null; + + while (m.find()) { + list = new ArrayList<>(m.groupCount()); + for (int i = 0; i <= m.groupCount(); i++) { + list.add(m.group(i)); + } + } + + return list; + } + + public static List getMatchList(String regex, String source) { + if (regex == null || "".equals(regex) || source == null || "".equals(source)) { + return null; + } + Pattern p = Pattern.compile(regex, Pattern.MULTILINE); + Matcher m = p.matcher(source); + List list = new ArrayList<>(); + while (m.find()) { + list.add(m.group(0)); + } + return list.isEmpty() ? null : list; + } + + + public static void main(String[] args) { + List res = toGroupValues("(replicator)(.+?:\\d+)(.*)", + "replicator_10.14.139.10:8081_append_entries_times"); + if (res != null) { + res.stream().forEach(e -> System.out.println(e)); + } + } + +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/HgStoreConst.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/HgStoreConst.java new file mode 100644 index 000000000..ac5b222f7 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/HgStoreConst.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.util; + +import java.util.Collections; +import java.util.List; + +/** + * created on 2021/10/22 + */ +public final class HgStoreConst { + + public final static int SCAN_WAIT_CLIENT_TAKING_TIME_OUT_SECONDS = 300; + + public final static byte[] EMPTY_BYTES = new byte[0]; + + public static final List EMPTY_LIST = Collections.EMPTY_LIST; + + public final static int SCAN_ALL_PARTITIONS_ID = -1; // means scan all partitions. + + private HgStoreConst() { + } + +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/HgStoreNodeUtil.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/HgStoreNodeUtil.java new file mode 100644 index 000000000..e44bf1bc4 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/HgStoreNodeUtil.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.util; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.UUID; + +import lombok.extern.slf4j.Slf4j; + +/** + * created on 2021/10/29 + */ +@Slf4j +public final class HgStoreNodeUtil { + public static String toStr(byte[] b) { + if (b == null) { + return ""; + } + if (b.length == 0) { + return ""; + } + return new String(b, StandardCharsets.UTF_8); + } + + public static byte[] toBytes(String str) { + if (str == null) { + return null; + } + return str.getBytes(StandardCharsets.UTF_8); + } + + public static byte[] toBytes(long l) { + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.putLong(l); + return buffer.array(); + } + + private static byte[] toBytes(final int i) { + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.putInt(i); + return buffer.array(); + } + + public static long toLong(byte[] bytes) { + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.put(bytes); + buffer.flip();//need flip + return buffer.getLong(); + } + + public static int toInt(byte[] bytes) { + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.put(bytes); + buffer.flip();//need flip + return buffer.getInt(); + } + + public static String toUuidStr(byte[] bytes) { + try { + return UUID.nameUUIDFromBytes(bytes).toString(); + } catch (Throwable t) { + log.error("Failed to parse bytes to UUID", t); + } + return null; + } + +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/PropertyUtil.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/PropertyUtil.java new file mode 100644 index 000000000..5d24ba2aa --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/PropertyUtil.java @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.util; + +import java.security.AccessController; +import java.security.PrivilegedAction; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class PropertyUtil { + private static final Logger LOG = LoggerFactory.getLogger(PropertyUtil.class); + + public static String get(String key) { + return get(key, null); + } + + public static String get(final String key, String def) { + if (key == null) { + throw new NullPointerException("key"); + } + if (key.isEmpty()) { + throw new IllegalArgumentException("key must not be empty."); + } + + String value = null; + try { + if (System.getSecurityManager() == null) { + value = System.getProperty(key); + } else { + value = AccessController.doPrivileged( + (PrivilegedAction) () -> System.getProperty(key)); + } + } catch (Exception e) { + LOG.error("exception {}", e); + } + + if (value == null) { + return def; + } + + return value; + } + + + public static boolean getBoolean(String key, boolean def) { + String value = get(key, Boolean.toString(def)); + value = value.trim().toLowerCase(); + if (value.isEmpty()) { + return true; + } + + if ("true".equals(value) || "yes".equals(value) || "1".equals(value)) { + return true; + } + + if ("false".equals(value) || "no".equals(value) || "0".equals(value)) { + return false; + } + return def; + } + + public static int getInt(String key, int def) { + String value = get(key); + if (value == null) { + return def; + } + + value = value.trim().toLowerCase(); + try { + return Integer.parseInt(value); + } catch (Exception e) { + LOG.warn("exception ", e); + } + return def; + } + + public static Object setProperty(String key, String value) { + return System.getProperties().setProperty(key, value); + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/Result.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/Result.java new file mode 100644 index 000000000..a1073037b --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/Result.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.util; + + +public class Result { + private Err err; + private T t; + + private Result() { + } + + public static Result of() { + return new Result(); + } + + public T get() { + return t; + } + + public void set(T t) { + this.t = t; + } + + public void err(String msg) { + this.err = Err.of(msg); + } +} diff --git a/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/TkEntry.java b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/TkEntry.java new file mode 100644 index 000000000..57762de33 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/util/TkEntry.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.util; + +import java.util.Arrays; +import java.util.Objects; + +/** + * Table Key pair. + */ +public class TkEntry { + private final String table; + private final byte[] key; + + public TkEntry(String table, byte[] key) { + this.table = table; + this.key = key; + } + + public String getTable() { + return table; + } + + public byte[] getKey() { + return key; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof TkEntry)) { + return false; + } + TkEntry tk = (TkEntry) o; + return Objects.equals(table, tk.table) && Arrays.equals(key, tk.key); + } + + @Override + public int hashCode() { + int result = Objects.hash(table); + result = 31 * result + Arrays.hashCode(key); + return result; + } + + @Override + public String toString() { + return "Tk{" + + "table='" + table + '\'' + + ", key=" + Arrays.toString(key) + + '}'; + } +} diff --git a/hugegraph-store/hg-store-node/src/main/resources/application-pd.yml b/hugegraph-store/hg-store-node/src/main/resources/application-pd.yml new file mode 100644 index 000000000..62d3fab76 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/resources/application-pd.yml @@ -0,0 +1,21 @@ +pdserver: + # pd服务地址,多个pd地址用逗号分割 + address: localhost:8686 + +management: + metrics: + export: + prometheus: + enabled: true + endpoints: + web: + exposure: + include: "*" +logging: + config: classpath:log4j2-dev.xml + level: + root: info +rocksdb: + # rocksdb 使用的总内存大小 + total_memory_size: 32000000000 + write_buffer_size: 32000000 \ No newline at end of file diff --git a/hugegraph-store/hg-store-node/src/main/resources/application.yml b/hugegraph-store/hg-store-node/src/main/resources/application.yml new file mode 100644 index 000000000..40ee1eb6b --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/resources/application.yml @@ -0,0 +1,34 @@ +pdserver: + # pd服务地址,多个pd地址用逗号分割 + address: localhost:8686 + +grpc: + # grpc的服务地址 + host: 127.0.0.1 + port: 8500 + netty-server: + max-inbound-message-size: 1000MB +raft: + address: 127.0.0.1:8510 + max-log-file-size: 600000000000 + # 快照生成时间间隔,单位秒 + snapshotInterval: 1800 +server: + # rest 服务地址 + port: 8520 + +app: + # 存储路径,支持多个路径,逗号分割 + data-path: ./storage + +spring: + profiles: + active: default + include: pd + application: + name: store-node-grpc-server + +logging: + config: classpath:log4j2-dev.xml + level: + root: info diff --git a/hugegraph-store/hg-store-node/src/main/resources/banner.txt b/hugegraph-store/hg-store-node/src/main/resources/banner.txt new file mode 100644 index 000000000..d9a377f20 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/resources/banner.txt @@ -0,0 +1,6 @@ +██╗ ██╗ ██████╗ ███████╗████████╗ ██████╗ ██████╗ ███████╗ +██║ ██║██╔════╝ ██╔════╝╚══██╔══╝██╔═══██╗██╔══██╗██╔════╝ +███████║██║ ███╗█████╗███████╗ ██║ ██║ ██║██████╔╝█████╗ +██╔══██║██║ ██║╚════╝╚════██║ ██║ ██║ ██║██╔══██╗██╔══╝ +██║ ██║╚██████╔╝ ███████║ ██║ ╚██████╔╝██║ ██║███████╗ +╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ - 3.x \ No newline at end of file diff --git a/hugegraph-store/hg-store-node/src/main/resources/log4j2-dev.xml b/hugegraph-store/hg-store-node/src/main/resources/log4j2-dev.xml new file mode 100644 index 000000000..2ed3977ab --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/resources/log4j2-dev.xml @@ -0,0 +1,143 @@ + + + + + + + + logs + hugegraph-store + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hugegraph-store/hg-store-node/src/main/resources/version.txt b/hugegraph-store/hg-store-node/src/main/resources/version.txt new file mode 100644 index 000000000..b55f10804 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/main/resources/version.txt @@ -0,0 +1 @@ +3.6.5 \ No newline at end of file diff --git a/hugegraph-store/hg-store-node/src/test/java/org/apache/hugegraph/store/boot/StoreNodeServer00.java b/hugegraph-store/hg-store-node/src/test/java/org/apache/hugegraph/store/boot/StoreNodeServer00.java new file mode 100644 index 000000000..ce2858b35 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/test/java/org/apache/hugegraph/store/boot/StoreNodeServer00.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.boot; + +import java.io.File; +import java.util.Objects; + +import org.apache.hugegraph.store.node.StoreNodeApplication; +import org.springframework.boot.SpringApplication; + +import com.alipay.remoting.util.StringUtils; + +public class StoreNodeServer00 { + + public static void main(String[] args) { + // deleteDir(new File("tmp/8500")); + String logPath = System.getProperty("logging.path"); + if (StringUtils.isBlank(logPath)) { + System.setProperty("logging.path", "logs/8500"); + System.setProperty("com.alipay.remoting.client.log.level", "WARN"); + } + if (System.getProperty("bolt.channel_write_buf_low_water_mark") == null) { + System.setProperty("bolt.channel_write_buf_low_water_mark", + Integer.toString(4 * 1024 * 1024)); + } + if (System.getProperty("bolt.channel_write_buf_high_water_mark") == null) { + System.setProperty("bolt.channel_write_buf_high_water_mark", + Integer.toString(8 * 1024 * 1024)); + } + SpringApplication.run(StoreNodeApplication.class, "--spring.profiles.active=server00"); + System.out.println("StoreNodeServer00 started."); + } + + private static boolean deleteDir(File dir) { + if (dir.isDirectory()) { + for (File file : Objects.requireNonNull(dir.listFiles())) { + deleteDir(file); + } + } + return dir.delete(); + } +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-node/src/test/java/org/apache/hugegraph/store/boot/StoreNodeServer01.java b/hugegraph-store/hg-store-node/src/test/java/org/apache/hugegraph/store/boot/StoreNodeServer01.java new file mode 100644 index 000000000..a8fbe96e5 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/test/java/org/apache/hugegraph/store/boot/StoreNodeServer01.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.boot; + +import java.io.File; +import java.util.Objects; + +import org.apache.hugegraph.store.node.StoreNodeApplication; +import org.springframework.boot.SpringApplication; + +import com.alipay.remoting.util.StringUtils; + +public class StoreNodeServer01 { + + public static void main(String[] args) { + // deleteDir(new File("tmp/8501")); + String logPath = System.getProperty("logging.path"); + if (StringUtils.isBlank(logPath)) { + System.setProperty("logging.path", "logs/8501"); + System.setProperty("com.alipay.remoting.client.log.level", "WARN"); + } + if (System.getProperty("bolt.channel_write_buf_low_water_mark") == null) { + System.setProperty("bolt.channel_write_buf_low_water_mark", + Integer.toString(4 * 1024 * 1024)); + } + if (System.getProperty("bolt.channel_write_buf_high_water_mark") == null) { + System.setProperty("bolt.channel_write_buf_high_water_mark", + Integer.toString(8 * 1024 * 1024)); + } + + + SpringApplication.run(StoreNodeApplication.class, "--spring.profiles.active=server01"); + System.out.println("StoreNodeServer01 started."); + } + + private static boolean deleteDir(File dir) { + if (dir.isDirectory()) { + for (File file : Objects.requireNonNull(dir.listFiles())) { + deleteDir(file); + } + } + return dir.delete(); + } +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-node/src/test/java/org/apache/hugegraph/store/boot/StoreNodeServer02.java b/hugegraph-store/hg-store-node/src/test/java/org/apache/hugegraph/store/boot/StoreNodeServer02.java new file mode 100644 index 000000000..b1c5e87c4 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/test/java/org/apache/hugegraph/store/boot/StoreNodeServer02.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.boot; + +import java.io.File; + +import org.apache.hugegraph.store.node.StoreNodeApplication; +import org.springframework.boot.SpringApplication; + +import com.alipay.remoting.util.StringUtils; + +public class StoreNodeServer02 { + + public static void main(String[] args) { + // deleteDir(new File("tmp/8502")); + String logPath = System.getProperty("logging.path"); + if (StringUtils.isBlank(logPath)) { + System.setProperty("logging.path", "logs/8502"); + System.setProperty("com.alipay.remoting.client.log.level", "WARN"); + } + if (System.getProperty("bolt.channel_write_buf_low_water_mark") == null) { + System.setProperty("bolt.channel_write_buf_low_water_mark", + Integer.toString(4 * 1024 * 1024)); + } + if (System.getProperty("bolt.channel_write_buf_high_water_mark") == null) { + System.setProperty("bolt.channel_write_buf_high_water_mark", + Integer.toString(8 * 1024 * 1024)); + } + + SpringApplication.run(StoreNodeApplication.class, "--spring.profiles.active=server02"); + System.out.println("StoreNodeServer02 started."); + } + + private static boolean deleteDir(File dir) { + if (dir.isDirectory()) { + for (File file : dir.listFiles()) { + deleteDir(file); + } + } + return dir.delete(); + } +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-node/src/test/java/org/apache/hugegraph/store/boot/StoreNodeServer03.java b/hugegraph-store/hg-store-node/src/test/java/org/apache/hugegraph/store/boot/StoreNodeServer03.java new file mode 100644 index 000000000..1abac6618 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/test/java/org/apache/hugegraph/store/boot/StoreNodeServer03.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.boot; + +import java.io.File; + +import org.apache.hugegraph.store.node.StoreNodeApplication; +import org.springframework.boot.SpringApplication; + +import com.alipay.remoting.util.StringUtils; + +public class StoreNodeServer03 { + + public static void main(String[] args) { + // deleteDir(new File("tmp/8503")); + String logPath = System.getProperty("logging.path"); + if (StringUtils.isBlank(logPath)) { + System.setProperty("logging.path", "logs/8503"); + System.setProperty("com.alipay.remoting.client.log.level", "WARN"); + } + if (System.getProperty("bolt.channel_write_buf_low_water_mark") == null) { + System.setProperty("bolt.channel_write_buf_low_water_mark", + Integer.toString(4 * 1024 * 1024)); + } + if (System.getProperty("bolt.channel_write_buf_high_water_mark") == null) { + System.setProperty("bolt.channel_write_buf_high_water_mark", + Integer.toString(8 * 1024 * 1024)); + } + + SpringApplication.run(StoreNodeApplication.class, "--spring.profiles.active=server03"); + System.out.println("StoreNodeServer03 started."); + } + + private static boolean deleteDir(File dir) { + if (dir.isDirectory()) { + for (File file : dir.listFiles()) { + deleteDir(file); + } + } + return dir.delete(); + } +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-node/src/test/java/org/apache/hugegraph/store/boot/StoreNodeServer04.java b/hugegraph-store/hg-store-node/src/test/java/org/apache/hugegraph/store/boot/StoreNodeServer04.java new file mode 100644 index 000000000..1730c9505 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/test/java/org/apache/hugegraph/store/boot/StoreNodeServer04.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.boot; + +import java.io.File; + +import org.apache.hugegraph.store.node.StoreNodeApplication; +import org.springframework.boot.SpringApplication; + +public class StoreNodeServer04 { + + public static void main(String[] args) { + // deleteDir(new File("tmp/8504")); + SpringApplication.run(StoreNodeApplication.class, "--spring.profiles.active=server04"); + System.out.println("StoreNodeServer04 started."); + } + + private static boolean deleteDir(File dir) { + if (dir.isDirectory()) { + for (File file : dir.listFiles()) { + deleteDir(file); + } + } + return dir.delete(); + } +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-node/src/test/java/org/apache/hugegraph/store/boot/StoreNodeServer05.java b/hugegraph-store/hg-store-node/src/test/java/org/apache/hugegraph/store/boot/StoreNodeServer05.java new file mode 100644 index 000000000..61e6e86a1 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/test/java/org/apache/hugegraph/store/boot/StoreNodeServer05.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.boot; + +import java.io.File; + +import org.apache.hugegraph.store.node.StoreNodeApplication; +import org.springframework.boot.SpringApplication; + +public class StoreNodeServer05 { + + public static void main(String[] args) { + // deleteDir(new File("tmp/8503")); + SpringApplication.run(StoreNodeApplication.class, "--spring.profiles.active=server05"); + System.out.println("StoreNodeServer03 started."); + } + + private static boolean deleteDir(File dir) { + if (dir.isDirectory()) { + for (File file : dir.listFiles()) { + deleteDir(file); + } + } + return dir.delete(); + } +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-node/src/test/java/org/apache/hugegraph/store/boot/StoreNodeServer06.java b/hugegraph-store/hg-store-node/src/test/java/org/apache/hugegraph/store/boot/StoreNodeServer06.java new file mode 100644 index 000000000..3f3b5acc4 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/test/java/org/apache/hugegraph/store/boot/StoreNodeServer06.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.boot; + +import java.io.File; + +import org.apache.hugegraph.store.node.StoreNodeApplication; +import org.springframework.boot.SpringApplication; + +public class StoreNodeServer06 { + + public static void main(String[] args) { + // deleteDir(new File("tmp/8503")); + SpringApplication.run(StoreNodeApplication.class, "--spring.profiles.active=server06"); + System.out.println("StoreNodeServer03 started."); + } + + private static boolean deleteDir(File dir) { + if (dir.isDirectory()) { + for (File file : dir.listFiles()) { + deleteDir(file); + } + } + return dir.delete(); + } +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-node/src/test/java/org/apache/hugegraph/store/node/HgStoreNodeServiceTest.java b/hugegraph-store/hg-store-node/src/test/java/org/apache/hugegraph/store/node/HgStoreNodeServiceTest.java new file mode 100644 index 000000000..972b7703f --- /dev/null +++ b/hugegraph-store/hg-store-node/src/test/java/org/apache/hugegraph/store/node/HgStoreNodeServiceTest.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node; + +// import org.junit.Test; + +import java.io.IOException; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * HgStore单元测试 + * 1、测试raft多副本入库 + * 2、测试快照同步 + * 3、测试副本增减 + * 4、测试单幅本关闭日志入库 + */ +public class HgStoreNodeServiceTest { + + String yml = + "rocksdb:\n" + " # rocksdb 使用的总内存大小\n" + " total_memory_size: 32000000000\n" + + " max_background_jobs: 8\n" + " max_subcompactions: 4\n" + + " target_file_size_multiplier: 4\n" + " min_write_buffer_number_to_merge: 8\n" + + " target_file_size_base: 512000000"; + + // @Test + public void testRaft() { + + } + + // @Test + public void testYaml() throws InterruptedException, IOException { + + ExecutorService executor = new ThreadPoolExecutor(1000, 1000, + 10L, TimeUnit.SECONDS, + new ArrayBlockingQueue<>(10000)); + CountDownLatch latch = new CountDownLatch(100); + for (int i = 0; i < 100; i++) { + + executor.execute(() -> { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + System.out.println(executor); + System.out.println(Thread.activeCount()); + latch.countDown(); + }); + + } + latch.await(); + System.in.read(); + } +} diff --git a/hugegraph-store/hg-store-node/src/test/java/org/apache/hugegraph/store/node/metrics/JraftMetricsTest.java b/hugegraph-store/hg-store-node/src/test/java/org/apache/hugegraph/store/node/metrics/JraftMetricsTest.java new file mode 100644 index 000000000..b8fc64747 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/test/java/org/apache/hugegraph/store/node/metrics/JraftMetricsTest.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.store.node.metrics; + +import java.util.Arrays; +import java.util.List; + +import org.apache.hugegraph.store.node.util.HgRegexUtil; +import org.junit.Test; + + +/** + * 2022/3/7 + */ +public class JraftMetricsTest { + + @Test + public void testRefineLabel() { + String regex = "(replicator)(.+?:\\d+)(.*)"; + + String[] sources = { + "replicator_10.14.139.10:8081_append_entries_times", + "replicator10.14.139.10:8081appendentriestimes", + "replicator.10.14.139.10:8081.append.entries.times", + "replicator-10-14-139-10:8081-append-entries-times", + "replicator_hg_0_10_14_139_10:8081::100_replicate_inflights_count_min", + "replicasdf14-13dasfies-times", + }; + + + Arrays.stream(sources).forEach(e -> { + System.out.println("--- " + e + " ---"); + List list = HgRegexUtil.toGroupValues(regex, e); + if (list != null) { + list.forEach(System.out::println); + } else { + System.out.println("NONE"); + } + System.out.println(); + }); + + } + +} diff --git a/hugegraph-store/hg-store-node/src/test/resources/application-pd.yml b/hugegraph-store/hg-store-node/src/test/resources/application-pd.yml new file mode 100644 index 000000000..519870a9c --- /dev/null +++ b/hugegraph-store/hg-store-node/src/test/resources/application-pd.yml @@ -0,0 +1,11 @@ +rocksdb: + # rocksdb 使用的总内存大小 + total_memory_size: 32000000000 + max_background_jobs: 8 + max_subcompactions: 4 + target_file_size_multiplier: 4 + min_write_buffer_number_to_merge: 8 + target_file_size_base: 512000000 + bloom_filter_bits_per_key: 10 + compression_per_level: "[zstd, zstd, zstd, zstd, zstd, zstd, zstd]" + bottommost_compression: "zstd" \ No newline at end of file diff --git a/hugegraph-store/hg-store-node/src/test/resources/application-server00.yml b/hugegraph-store/hg-store-node/src/test/resources/application-server00.yml new file mode 100644 index 000000000..d588e5be7 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/test/resources/application-server00.yml @@ -0,0 +1,56 @@ +pdserver: + # pd服务地址,多个pd地址用逗号分割 + address: localhost:8686 +grpc: + # grpc的服务地址 + host: 127.0.0.1 + port: 8500 + netty-server: + max-inbound-message-size: 1000MB +raft: + # enable: false + address: 127.0.0.1:8510 + data-path: ${app.data-path}/raft + # 快照生成时间间隔,单位秒 + snapshotInterval: 30 + max-log-file-size: 60000000 +server: + # rest 服务地址 + port: 8520 + +app: + data-path: tmp/8500 + #fake-pd: true + label: + label-key: label-value +spring: + application: + name: store-node-grpc-server + +management: + metrics: + export: + prometheus: + enabled: true + endpoints: + web: + exposure: + include: "*" + +rocksdb: + data_path: ${app.data-path}/db + wal_path: ${app.data-path}/db + snapshot_path: ${app.data-path}/snapshot + bloom_filter_bits_per_key: 10 + compression_per_level: "[none, zstd, zstd, zstd, zstd, zstd, zstd]" +#fakePd配置参数 +fake-pd: + # fake-pd模式下,store grpc集群列表 + store-list: 127.0.0.1:8500 + # fake-pd模式下,设置raft集群列表 + peers-list: 127.0.0.1:8510 + # 分区数量 + partition-count: 10 + # 每个分区副本数量 + shard-count: 3 + diff --git a/hugegraph-store/hg-store-node/src/test/resources/application-server01.yml b/hugegraph-store/hg-store-node/src/test/resources/application-server01.yml new file mode 100644 index 000000000..bbb2e2735 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/test/resources/application-server01.yml @@ -0,0 +1,53 @@ +pdserver: + # pd服务地址,多个pd地址用逗号分割 + address: localhost:8686 +grpc: + # grpc的服务地址 + host: 127.0.0.1 + port: 8501 + netty-server: + max-inbound-message-size: 1000MB +raft: + # enable: false + address: 127.0.0.1:8511 + useRocksDBSegmentLogStorage: false + # 快照生成时间间隔,单位秒 + snapshotInterval: 300 + disruptorBufferSize: 128 +server: + # rest 服务地址 + port: 8521 + +app: + data-path: tmp/8501 + # fake-pd: true + label: + label-name: label-value +spring: + application: + name: store-node-grpc-server + +management: + metrics: + export: + prometheus: + enabled: true + endpoints: + web: + exposure: + include: "*" + +rocksdb: + write_buffer_size: 2000000 + level0_file_num_compaction_trigger: 2 + bloom_filter_bits_per_key: 10 +#fakePd配置参数 +fake-pd: + # fake-pd模式下,store grpc集群列表 + store-list: 127.0.0.1:8501,127.0.0.1:8502,127.0.0.1:8503 + # fake-pd模式下,设置raft集群列表 + peers-list: 127.0.0.1:8511,127.0.0.1:8512,127.0.0.1:8513 + # 分区数量 + partition-count: 10 + # 每个分区副本数量 + shard-count: 3 diff --git a/hugegraph-store/hg-store-node/src/test/resources/application-server02.yml b/hugegraph-store/hg-store-node/src/test/resources/application-server02.yml new file mode 100644 index 000000000..f24fb1a18 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/test/resources/application-server02.yml @@ -0,0 +1,52 @@ +pdserver: + # pd服务地址,多个pd地址用逗号分割 + address: localhost:8686 +grpc: + # grpc的服务地址 + host: 127.0.0.1 + port: 8502 + netty-server: + max-inbound-message-size: 1000MB +raft: + # enable: false + address: 127.0.0.1:8512 + useRocksDBSegmentLogStorage: false + # 快照生成时间间隔,单位秒 + snapshotInterval: 300 + disruptorBufferSize: 128 +server: + # rest 服务地址 + port: 8522 + +app: + data-path: tmp/8502 + # fake-pd: true + label: + label-key: label-value +spring: + application: + name: store-node-grpc-server + +management: + metrics: + export: + prometheus: + enabled: true + endpoints: + web: + exposure: + include: "*" + +rocksdb: + db_max_alive_time: 120 + +#fakePd配置参数 +fake-pd: + # fake-pd模式下,store grpc集群列表 + store-list: 127.0.0.1:8501,127.0.0.1:8502,127.0.0.1:8503 + # fake-pd模式下,设置raft集群列表 + peers-list: 127.0.0.1:8511,127.0.0.1:8512,127.0.0.1:8513 + # 分区数量 + partition-count: 10 + # 每个分区副本数量 + shard-count: 3 diff --git a/hugegraph-store/hg-store-node/src/test/resources/application-server03.yml b/hugegraph-store/hg-store-node/src/test/resources/application-server03.yml new file mode 100644 index 000000000..2a0058d9a --- /dev/null +++ b/hugegraph-store/hg-store-node/src/test/resources/application-server03.yml @@ -0,0 +1,54 @@ +pdserver: + # pd服务地址,多个pd地址用逗号分割 + address: localhost:8686 +grpc: + # grpc的服务地址 + host: 127.0.0.1 + port: 8503 + netty-server: + max-inbound-message-size: 1000MB +raft: + # enable: false + address: 127.0.0.1:8513 + snapshotLogIndexMargin: 1024 + useRocksDBSegmentLogStorage: false + # 快照生成时间间隔,单位秒 + snapshotInterval: 300 + disruptorBufferSize: 128 +server: + # rest 服务地址 + port: 8523 + +app: + data-path: tmp/8503 + # fake-pd: true + label: + label-key: label-value +spring: + application: + name: store-node-grpc-server + +management: + metrics: + export: + prometheus: + enabled: true + endpoints: + web: + exposure: + include: "*" + +rocksdb: + db_max_alive_time: 120 + + +#fakePd配置参数 +fake-pd: + # fake-pd模式下,store grpc集群列表 + store-list: 127.0.0.1:8501,127.0.0.1:8502,127.0.0.1:8503 + # fake-pd模式下,设置raft集群列表 + peers-list: 127.0.0.1:8511,127.0.0.1:8512,127.0.0.1:8513 + # 分区数量 + partition-count: 10 + # 每个分区副本数量 + shard-count: 3 diff --git a/hugegraph-store/hg-store-node/src/test/resources/application-server04.yml b/hugegraph-store/hg-store-node/src/test/resources/application-server04.yml new file mode 100644 index 000000000..30aaba999 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/test/resources/application-server04.yml @@ -0,0 +1,51 @@ +pdserver: + # pd服务地址,多个pd地址用逗号分割 + address: localhost:8686 +grpc: + # grpc的服务地址 + host: 127.0.0.1 + port: 8504 + netty-server: + max-inbound-message-size: 1000MB +raft: + # enable: false + address: 127.0.0.1:8514 + + # 快照生成时间间隔,单位秒 + snapshotInterval: 300 +server: + # rest 服务地址 + port: 8524 + +app: + data-path: tmp/8504 + # fake-pd: true + label: + label-key: label-value +spring: + application: + name: store-node-grpc-server + +management: + metrics: + export: + prometheus: + enabled: true + endpoints: + web: + exposure: + include: "*" + +rocksdb: + + +#fakePd配置参数 +fake-pd: + # fake-pd模式下,store grpc集群列表 + store-list: 127.0.0.1:8501,127.0.0.1:8502,127.0.0.1:8503 + # fake-pd模式下,设置raft集群列表 + peers-list: 127.0.0.1:8511,127.0.0.1:8512,127.0.0.1:8513 + # 分区数量 + partition-count: 3 + # 每个分区副本数量 + shard-count: 3 diff --git a/hugegraph-store/hg-store-node/src/test/resources/application-server05.yml b/hugegraph-store/hg-store-node/src/test/resources/application-server05.yml new file mode 100644 index 000000000..832e1b04f --- /dev/null +++ b/hugegraph-store/hg-store-node/src/test/resources/application-server05.yml @@ -0,0 +1,53 @@ +pdserver: + # pd服务地址,多个pd地址用逗号分割 + address: localhost:8686 +grpc: + # grpc的服务地址 + host: 127.0.0.1 + port: 8505 + netty-server: + max-inbound-message-size: 1000MB +raft: + # enable: false + address: 127.0.0.1:8515 + data-path: ${app.data-path}/raft + # 快照生成时间间隔,单位秒 + snapshotInterval: 300 +server: + # rest 服务地址 + port: 8525 + +app: + data-path: tmp/8505 + # fake-pd: true + label: + label-key: label-value +spring: + application: + name: store-node-grpc-server + +management: + metrics: + export: + prometheus: + enabled: true + endpoints: + web: + exposure: + include: "*" + +rocksdb: + data_path: ${app.data-path}/db + wal_path: ${app.data-path}/db + snapshot_path: ${app.data-path}/snapshot + +#fakePd配置参数 +fake-pd: + # fake-pd模式下,store grpc集群列表 + store-list: 127.0.0.1:8501,127.0.0.1:8502,127.0.0.1:8503 + # fake-pd模式下,设置raft集群列表 + peers-list: 127.0.0.1:8511,127.0.0.1:8512,127.0.0.1:8513 + # 分区数量 + partition-count: 3 + # 每个分区副本数量 + shard-count: 3 diff --git a/hugegraph-store/hg-store-node/src/test/resources/application-server06.yml b/hugegraph-store/hg-store-node/src/test/resources/application-server06.yml new file mode 100644 index 000000000..236501437 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/test/resources/application-server06.yml @@ -0,0 +1,53 @@ +pdserver: + # pd服务地址,多个pd地址用逗号分割 + address: localhost:8686 +grpc: + # grpc的服务地址 + host: 127.0.0.1 + port: 8506 + netty-server: + max-inbound-message-size: 1000MB +raft: + # enable: false + address: 127.0.0.1:8516 + data-path: ${app.data-path}/raft + # 快照生成时间间隔,单位秒 + snapshotInterval: 300 +server: + # rest 服务地址 + port: 8526 + +app: + data-path: tmp/8506 + # fake-pd: true + label: + label-key: label-value +spring: + application: + name: store-node-grpc-server + +management: + metrics: + export: + prometheus: + enabled: true + endpoints: + web: + exposure: + include: "*" + +rocksdb: + data_path: ${app.data-path}/db + wal_path: ${app.data-path}/db + snapshot_path: ${app.data-path}/snapshot + +#fakePd配置参数 +fake-pd: + # fake-pd模式下,store grpc集群列表 + store-list: 127.0.0.1:8501,127.0.0.1:8502,127.0.0.1:8503 + # fake-pd模式下,设置raft集群列表 + peers-list: 127.0.0.1:8511,127.0.0.1:8512,127.0.0.1:8513 + # 分区数量 + partition-count: 3 + # 每个分区副本数量 + shard-count: 3 diff --git a/hugegraph-store/hg-store-node/src/test/resources/log4j2-dev.xml b/hugegraph-store/hg-store-node/src/test/resources/log4j2-dev.xml new file mode 100644 index 000000000..ba714c018 --- /dev/null +++ b/hugegraph-store/hg-store-node/src/test/resources/log4j2-dev.xml @@ -0,0 +1,139 @@ + + + + + + + + logs + hugegraph-store + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hugegraph-store/hg-store-rocksdb/pom.xml b/hugegraph-store/hg-store-rocksdb/pom.xml new file mode 100644 index 000000000..0c105dff4 --- /dev/null +++ b/hugegraph-store/hg-store-rocksdb/pom.xml @@ -0,0 +1,79 @@ + + + + + 4.0.0 + + + org.apache.hugegraph + hugegraph-store-root + ${revision} + ../pom.xml + + + hg-store-rocksdb + + + + org.apache.hugegraph + hugegraph-common + 1.0.1 + + + org.glassfish.jersey.inject + * + + + org.glassfish.jersey.media + * + + + org.glassfish.jersey.connectors + * + + + + + org.apache.hugegraph + hg-store-common + + + org.rocksdb + rocksdbjni + 7.7.3 + + + org.projectlombok + lombok + 1.18.22 + provided + + + org.apache.logging.log4j + log4j-slf4j-impl + 2.15.0 + + + com.alibaba + fastjson + 1.2.83 + + + \ No newline at end of file diff --git a/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/DBSnapshotMeta.java b/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/DBSnapshotMeta.java new file mode 100644 index 000000000..0db7cd07a --- /dev/null +++ b/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/DBSnapshotMeta.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.rocksdb.access; + +import java.util.Date; +import java.util.HashMap; + +import lombok.Data; + +/** + * metadata of a Rocksdb snapshot + */ +@Data +public class DBSnapshotMeta { + /** + * graph name + */ + private String graphName; + /** + * partition id + */ + private int partitionId; + /** + * star key with base64 encoding + */ + private byte[] startKey; + /** + * end key with base64 encoding + */ + private byte[] endKey; + /** + * created time of the snapshot + */ + private Date createdDate; + /** + * sst files, key is column family name value is the sst file name of the column + * family + */ + private HashMap sstFiles; +} diff --git a/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/DBStoreException.java b/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/DBStoreException.java new file mode 100644 index 000000000..7d1391157 --- /dev/null +++ b/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/DBStoreException.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.rocksdb.access; + +import java.io.InterruptedIOException; + +public class DBStoreException extends RuntimeException { + + private static final long serialVersionUID = 5956983547131986887L; + + public DBStoreException(String message) { + super(message); + } + + public DBStoreException(String message, Throwable cause) { + super(message, cause); + } + + public DBStoreException(String message, Object... args) { + super(String.format(message, args)); + } + + public DBStoreException(String message, Throwable cause, Object... args) { + super(String.format(message, args), cause); + } + + public DBStoreException(Throwable cause) { + this("Exception in DBStore " + cause.getMessage(), cause); + } + + public static Throwable rootCause(Throwable e) { + Throwable cause = e; + while (cause.getCause() != null) { + cause = cause.getCause(); + } + return cause; + } + + public static boolean isInterrupted(Throwable e) { + Throwable rootCause = DBStoreException.rootCause(e); + return rootCause instanceof InterruptedException || + rootCause instanceof InterruptedIOException; + } + + public Throwable rootCause() { + return rootCause(this); + } +} diff --git a/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/RocksDBFactory.java b/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/RocksDBFactory.java new file mode 100644 index 000000000..df6670e78 --- /dev/null +++ b/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/RocksDBFactory.java @@ -0,0 +1,317 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.rocksdb.access; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.stream.Collectors; + +import org.apache.commons.io.FileUtils; +import org.apache.hugegraph.config.HugeConfig; +import org.rocksdb.AbstractEventListener; +import org.rocksdb.Cache; +import org.rocksdb.CompactionJobInfo; +import org.rocksdb.MemoryUsageType; +import org.rocksdb.MemoryUtil; +import org.rocksdb.RocksDB; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public final class RocksDBFactory { + private static final List rocksdbChangedListeners = new ArrayList<>(); + private static RocksDBFactory dbFactory; + + static { + RocksDB.loadLibrary(); + } + + private final Map dbSessionMap = new ConcurrentHashMap<>(); + private final List destroyGraphDBs = new CopyOnWriteArrayList<>(); + private final ReentrantReadWriteLock operateLock; + ScheduledExecutorService scheduledExecutor; + private HugeConfig hugeConfig; + + private RocksDBFactory() { + this.operateLock = new ReentrantReadWriteLock(); + scheduledExecutor = Executors.newScheduledThreadPool(2); + scheduledExecutor.scheduleWithFixedDelay(() -> { + try { + Iterator itr = destroyGraphDBs.listIterator(); + while (itr.hasNext()) { + DBSessionWatcher watcher = itr.next(); + if (0 == watcher.dbSession.getRefCount()) { + try { + watcher.dbSession.shutdown(); + FileUtils.deleteDirectory(new File(watcher.dbSession.getDbPath())); + rocksdbChangedListeners.forEach(listener -> { + listener.onDBDeleted(watcher.dbSession.getGraphName(), + watcher.dbSession.getDbPath()); + }); + log.info("removed db {} and delete files", + watcher.dbSession.getDbPath()); + } catch (Exception e) { + log.error("DestroyGraphDB exception {}", e); + } + destroyGraphDBs.remove(watcher); + } else if (watcher.timestamp < (System.currentTimeMillis() - 1800 * 1000)) { + log.warn("DB {} has not been deleted refCount is {}, time is {} seconds", + watcher.dbSession.getDbPath(), + watcher.dbSession.getRefCount(), + (System.currentTimeMillis() - watcher.timestamp) / 1000); + } else { + // 超时强制删除 (30min) + watcher.dbSession.forceResetRefCount(); + } + } + + } catch (Exception e) { + log.error("RocksDBFactory scheduledExecutor exception {}", e); + } + }, 60, 60, TimeUnit.SECONDS); + } + + public static RocksDBFactory getInstance() { + if (dbFactory == null) { + synchronized (RocksDBFactory.class) { + if (dbFactory == null) { + dbFactory = new RocksDBFactory(); + } + } + } + return dbFactory; + } + + public int getSessionSize() { + return dbSessionMap.size(); + } + + public Set getGraphNames() { + return dbSessionMap.keySet(); + } + + public HugeConfig getHugeConfig() { + return this.hugeConfig; + } + + public void setHugeConfig(HugeConfig nodeConfig) { + this.hugeConfig = nodeConfig; + } + + public boolean findPathInRemovedList(String path) { + for (DBSessionWatcher pair : destroyGraphDBs) { + if (pair.dbSession.getDbPath().equals(path)) { + return true; + } + } + return false; + } + + public RocksDBSession queryGraphDB(String dbName) { + operateLock.readLock().lock(); + try { + RocksDBSession session = dbSessionMap.get(dbName); + if (session != null) { + return session.clone(); + } + } finally { + operateLock.readLock().unlock(); + } + return null; + } + + public RocksDBSession createGraphDB(String dbPath, String dbName) { + return createGraphDB(dbPath, dbName, 0); + } + + public RocksDBSession createGraphDB(String dbPath, String dbName, long version) { + operateLock.writeLock().lock(); + try { + RocksDBSession dbSession = dbSessionMap.get(dbName); + if (dbSession == null) { + log.info("create rocksdb for {}", dbName); + dbSession = new RocksDBSession(this.hugeConfig, dbPath, dbName, version); + dbSessionMap.put(dbName, dbSession); + } + return dbSession.clone(); + } finally { + operateLock.writeLock().unlock(); + } + } + + /** + * @param : + * @return long + * @description the size(KB) of the total rocksdb's data. + */ + public long getTotalSize() { + long kbSize = dbSessionMap.entrySet() + .stream() + .map(e -> e.getValue().getApproximateDataSize()) + .reduce(0L, Long::sum); + return kbSize; + } + + public Map getTotalKey() { + Map totalKeys = dbSessionMap.entrySet().stream() + .collect(Collectors.toMap(e -> e.getKey(), + e -> e.getValue() + .getEstimateNumKeys())); + return totalKeys; + } + + /** + * 释放rocksdb对象 + * + * @param dbName + * @return + */ + public boolean releaseGraphDB(String dbName) { + log.info("close {} 's rocksdb.", dbName); + operateLock.writeLock().lock(); + try { + RocksDBSession dbSession = dbSessionMap.get(dbName); + if (dbSession != null) { + dbSessionMap.remove(dbName); + rocksdbChangedListeners.forEach(listener -> { + listener.onDBSessionReleased(dbSession); + }); + dbSession.close(); + } + } finally { + operateLock.writeLock().unlock(); + } + + return false; + } + + /** + * 销毁图,并删除数据文件 + * + * @param dbName + */ + public void destroyGraphDB(String dbName) { + log.info("destroy {} 's rocksdb.", dbName); + RocksDBSession dbSession = dbSessionMap.get(dbName); + releaseGraphDB(dbName); + //增加删除标记 + if (dbSession != null) { + destroyGraphDBs.add(new DBSessionWatcher(dbSession)); + rocksdbChangedListeners.forEach(listener -> { + listener.onDBDeleteBegin(dbSession.getGraphName(), dbSession.getDbPath()); + }); + } + } + + public void releaseAllGraphDB() { + log.info("close all rocksdb."); + operateLock.writeLock().lock(); + try { + dbSessionMap.forEach((k, v) -> { + v.shutdown(); + }); + dbSessionMap.clear(); + } finally { + operateLock.writeLock().unlock(); + } + } + + public Map getApproximateMemoryUsageByType(List dbs, + List caches) { + if (dbs == null) { + dbs = new ArrayList<>(); + } else { + dbs = new ArrayList<>(dbs); + } + List sessions = new ArrayList<>(); + for (String dbName : getGraphNames()) { + RocksDBSession session = this.queryGraphDB(dbName); + if (session != null) { + dbs.add(session.getDB()); + sessions.add(session); + } + } + try { + HashSet allCaches = new HashSet<>(); + if (caches != null) { + allCaches.addAll(caches); + } + allCaches.add((Cache) hugeConfig.getProperty(RocksDBOptions.WRITE_CACHE)); + allCaches.add((Cache) hugeConfig.getProperty(RocksDBOptions.BLOCK_CACHE)); + return MemoryUtil.getApproximateMemoryUsageByType(dbs, allCaches); + } finally { + sessions.forEach(session -> { + session.close(); + }); + } + } + + public void addRocksdbChangedListener(RocksdbChangedListener listener) { + rocksdbChangedListeners.add(listener); + } + + public interface RocksdbChangedListener { + default void onCompacted(String dbName) { + } + + default void onDBDeleteBegin(String dbName, String filePath) { + } + + default void onDBDeleted(String dbName, String filePath) { + } + + default void onDBSessionReleased(RocksDBSession dbSession) { + } + } + + class RocksdbEventListener extends AbstractEventListener { + @Override + public void onCompactionCompleted(RocksDB db, CompactionJobInfo compactionJobInfo) { + super.onCompactionCompleted(db, compactionJobInfo); + rocksdbChangedListeners.forEach(listener -> { + listener.onCompacted(db.getName()); + }); + } + + @Override + public void onCompactionBegin(final RocksDB db, final CompactionJobInfo compactionJobInfo) { + log.info("RocksdbEventListener onCompactionBegin"); + } + } + + class DBSessionWatcher { + public RocksDBSession dbSession; + public Long timestamp; + + public DBSessionWatcher(RocksDBSession dbSession) { + this.dbSession = dbSession; + timestamp = System.currentTimeMillis(); + } + } +} diff --git a/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/RocksDBOptions.java b/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/RocksDBOptions.java new file mode 100644 index 000000000..37b7fdcbf --- /dev/null +++ b/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/RocksDBOptions.java @@ -0,0 +1,429 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.rocksdb.access; + +import static org.apache.hugegraph.config.OptionChecker.allowValues; +import static org.apache.hugegraph.config.OptionChecker.disallowEmpty; +import static org.apache.hugegraph.config.OptionChecker.inValues; +import static org.apache.hugegraph.config.OptionChecker.rangeDouble; +import static org.apache.hugegraph.config.OptionChecker.rangeInt; + +import org.apache.hugegraph.config.ConfigConvOption; +import org.apache.hugegraph.config.ConfigListConvOption; +import org.apache.hugegraph.config.ConfigOption; +import org.apache.hugegraph.config.OptionHolder; +import org.apache.hugegraph.util.Bytes; +import org.rocksdb.CompactionStyle; +import org.rocksdb.CompressionType; + +public class RocksDBOptions extends OptionHolder { + + public static final ConfigOption TOTAL_MEMORY_SIZE = + new ConfigOption<>( + "rocksdb.total_memory_size", + "Limit total memory of memtables for all dbs", + rangeInt(0L, Long.MAX_VALUE), + 48L * Bytes.GB + ); + public static final ConfigOption WRITE_BUFFER_RATIO = + new ConfigOption<>( + "rocksdb.write_buffer_ratio", + "write buffer ratio", + rangeDouble(0.0, 1.0), + 0.66 + ); + public static final ConfigOption WRITE_BUFFER_ALLOW_STALL = + new ConfigOption<>( + "rocksdb.write_buffer_allow_stall", + " if set true, it will enable stalling of writes when memory_usage() exceeds " + + "buffer_size." + + " It will wait for flush to complete and memory usage to drop down", + disallowEmpty(), + false + ); + + + // public static final ConfigListOption DATA_DISKS = +// new ConfigListOption<>( +// "rocksdb.data_disks", +// false, +// "The optimized disks for storing data of RocksDB. " + +// "The format of each element: `STORE/TABLE: /path/disk`." + +// "Allowed keys are [g/vertex, g/edge_out, g/edge_in, " + +// "g/vertex_label_index, g/edge_label_index, " + +// "g/range_int_index, g/range_float_index, " + +// "g/range_long_index, g/range_double_index, " + +// "g/secondary_index, g/search_index, g/shard_index, " + +// "g/unique_index, g/olap]", +// null, +// String.class, +// ImmutableList.of() +// ); + public static final ConfigOption SST_PATH = + new ConfigOption<>( + "rocksdb.sst_path", + "The path for ingesting SST file into RocksDB.", + null, + "" + ); + // TODO: support ConfigOption + public static final ConfigOption LOG_LEVEL = + new ConfigOption<>( + "rocksdb.log_level", + "The info log level of RocksDB.", + allowValues("DEBUG", "INFO", "WARN", "ERROR", "FATAL", "HEADER"), + "INFO" + ); + public static final ConfigOption NUM_LEVELS = + new ConfigOption<>( + "rocksdb.num_levels", + "Set the number of levels for this database.", + rangeInt(1, Integer.MAX_VALUE), + 7 + ); + // public static final ConfigOption BLOCK_CACHE_CAPACITY = +// new ConfigOption<>( +// "rocksdb.block_cache_capacity", +// "The amount of block cache in bytes that will be used by all RocksDBs", +// rangeInt(0L, Long.MAX_VALUE), +// 16L * Bytes.GB +// ); +// public static final ConfigOption SNAPSHOT_PATH = +// new ConfigOption<>( +// "rocksdb.snapshot_path", +// "The path for storing snapshot of RocksDB.", +// disallowEmpty(), +// "rocksdb-snapshot" +// ); + public static final ConfigConvOption COMPACTION_STYLE = + new ConfigConvOption<>( + "rocksdb.compaction_style", + "Set compaction style for RocksDB: LEVEL/UNIVERSAL/FIFO.", + allowValues("LEVEL", "UNIVERSAL", "FIFO"), + CompactionStyle::valueOf, + "LEVEL" + ); + public static final ConfigOption OPTIMIZE_MODE = + new ConfigOption<>( + "rocksdb.optimize_mode", + "Optimize for heavy workloads and big datasets.", + disallowEmpty(), + true + ); + public static final ConfigListConvOption LEVELS_COMPRESSIONS = + new ConfigListConvOption<>( + "rocksdb.compression_per_level", + "The compression algorithms for different levels of RocksDB, " + + "allowed values are none/snappy/z/bzip2/lz4/lz4hc/xpress/zstd.", + inValues("none", "snappy", "z", "bzip2", "lz4", "lz4hc", "xpress", "zstd"), + CompressionType::getCompressionType, + "none", "none", "snappy", "snappy", "snappy", "snappy", "snappy" + ); + public static final ConfigConvOption BOTTOMMOST_COMPRESSION = + new ConfigConvOption<>( + "rocksdb.bottommost_compression", + "The compression algorithm for the bottommost level of RocksDB, " + + "allowed values are none/snappy/z/bzip2/lz4/lz4hc/xpress/zstd.", + allowValues("none", "snappy", "z", "bzip2", "lz4", "lz4hc", "xpress", "zstd"), + CompressionType::getCompressionType, + "none" + ); + public static final ConfigConvOption COMPRESSION = + new ConfigConvOption<>( + "rocksdb.compression", + "The compression algorithm for compressing blocks of RocksDB, " + + "allowed values are none/snappy/z/bzip2/lz4/lz4hc/xpress/zstd.", + allowValues("none", "snappy", "z", "bzip2", "lz4", "lz4hc", "xpress", "zstd"), + CompressionType::getCompressionType, + "snappy" + ); + public static final ConfigOption MAX_BG_JOBS = + new ConfigOption<>( + "rocksdb.max_background_jobs", + "Maximum number of concurrent background jobs, including flushes and " + + "compactions.", + rangeInt(1, Integer.MAX_VALUE), + 8 + ); + public static final ConfigOption MAX_SUB_COMPACTIONS = + new ConfigOption<>( + "rocksdb.max_subcompactions", + "The value represents the maximum number of threads per compaction job.", + rangeInt(1, Integer.MAX_VALUE), + 4 + ); + public static final ConfigOption DELAYED_WRITE_RATE = + new ConfigOption<>( + "rocksdb.delayed_write_rate", + "The rate limit in bytes/s of user write requests " + + "when need to slow down if the compaction gets behind.", + rangeInt(1L, Long.MAX_VALUE), + 64L * Bytes.MB + ); + public static final ConfigOption MAX_OPEN_FILES = + new ConfigOption<>( + "rocksdb.max_open_files", + "The maximum number of open files that can be cached by RocksDB, " + + "-1 means no limit.", + rangeInt(-1, Integer.MAX_VALUE), + -1 + ); + public static final ConfigOption MAX_MANIFEST_FILE_SIZE = + new ConfigOption<>( + "rocksdb.max_manifest_file_size", + "The max size of manifest file in bytes.", + rangeInt(1L, Long.MAX_VALUE), + 100L * Bytes.MB + ); + public static final ConfigOption SKIP_STATS_UPDATE_ON_DB_OPEN = + new ConfigOption<>( + "rocksdb.skip_stats_update_on_db_open", + "Whether to skip statistics update when opening the database, " + + "setting this flag true allows us to not update statistics.", + disallowEmpty(), + false + ); + public static final ConfigOption MAX_FILE_OPENING_THREADS = + new ConfigOption<>( + "rocksdb.max_file_opening_threads", + "The max number of threads used to open files.", + rangeInt(1, Integer.MAX_VALUE), + 16 + ); + public static final ConfigOption MAX_TOTAL_WAL_SIZE = + new ConfigOption<>( + "rocksdb.max_total_wal_size", + "Total size of WAL files in bytes. Once WALs exceed this size, " + + "we will start forcing the flush of column families related, " + + "0 means no limit.", + rangeInt(0L, Long.MAX_VALUE), + 0L + ); + public static final ConfigOption DB_MEMTABLE_SIZE = + new ConfigOption<>( + "rocksdb.db_write_buffer_size", + "Total size of write buffers in bytes across all column families, " + + "0 means no limit.", + rangeInt(0L, Long.MAX_VALUE), + 0L + ); + public static final ConfigOption DELETE_OBSOLETE_FILE_PERIOD = + new ConfigOption<>( + "rocksdb.delete_obsolete_files_period", + "The periodicity in seconds when obsolete files get deleted, " + + "0 means always do full purge.", + rangeInt(0L, Long.MAX_VALUE), + 6L * 60 * 60 + ); + public static final ConfigOption MEMTABLE_SIZE = + new ConfigOption<>( + "rocksdb.write_buffer_size", + "Amount of data in bytes to build up in memory.", + rangeInt(Bytes.MB, Long.MAX_VALUE), + 32L * Bytes.MB + ); + public static final ConfigOption MAX_MEMTABLES = + new ConfigOption<>( + "rocksdb.max_write_buffer_number", + "The maximum number of write buffers that are built up in memory.", + rangeInt(1, Integer.MAX_VALUE), + 32 + ); + public static final ConfigOption MIN_MEMTABLES_TO_MERGE = + new ConfigOption<>( + "rocksdb.min_write_buffer_number_to_merge", + "The minimum number of write buffers that will be merged together.", + rangeInt(1, Integer.MAX_VALUE), + 16 + ); + public static final ConfigOption MAX_MEMTABLES_TO_MAINTAIN = + new ConfigOption<>( + "rocksdb.max_write_buffer_number_to_maintain", + "The total maximum number of write buffers to maintain in memory.", + rangeInt(0, Integer.MAX_VALUE), + 0 + ); + public static final ConfigOption DYNAMIC_LEVEL_BYTES = + new ConfigOption<>( + "rocksdb.level_compaction_dynamic_level_bytes", + "Whether to enable level_compaction_dynamic_level_bytes, " + + "if it's enabled we give max_bytes_for_level_multiplier a " + + "priority against max_bytes_for_level_base, the bytes of " + + "base level is dynamic for a more predictable LSM tree, " + + "it is useful to limit worse case space amplification. " + + "Turning this feature on/off for an existing DB can cause " + + "unexpected LSM tree structure so it's not recommended.", + disallowEmpty(), + false + ); + public static final ConfigOption MAX_LEVEL1_BYTES = + new ConfigOption<>( + "rocksdb.max_bytes_for_level_base", + "The upper-bound of the total size of level-1 files in bytes.", + rangeInt(Bytes.MB, Long.MAX_VALUE), + 10L * Bytes.GB + ); + public static final ConfigOption MAX_LEVEL_BYTES_MULTIPLIER = + new ConfigOption<>( + "rocksdb.max_bytes_for_level_multiplier", + "The ratio between the total size of level (L+1) files and " + + "the total size of level L files for all L.", + rangeDouble(1.0, Double.MAX_VALUE), + 10.0 + ); + public static final ConfigOption TARGET_FILE_SIZE_BASE = + new ConfigOption<>( + "rocksdb.target_file_size_base", + "The target file size for compaction in bytes.", + rangeInt(Bytes.MB, Long.MAX_VALUE), + 256L * Bytes.MB + ); + public static final ConfigOption TARGET_FILE_SIZE_MULTIPLIER = + new ConfigOption<>( + "rocksdb.target_file_size_multiplier", + "The size ratio between a level L file and a level (L+1) file.", + rangeInt(1, Integer.MAX_VALUE), + 2 + ); + public static final ConfigOption LEVEL0_COMPACTION_TRIGGER = + new ConfigOption<>( + "rocksdb.level0_file_num_compaction_trigger", + "Number of files to trigger level-0 compaction.", + rangeInt(0, Integer.MAX_VALUE), + 10 + ); + public static final ConfigOption LEVEL0_SLOWDOWN_WRITES_TRIGGER = + new ConfigOption<>( + "rocksdb.level0_slowdown_writes_trigger", + "Soft limit on number of level-0 files for slowing down writes.", + rangeInt(-1, Integer.MAX_VALUE), + 256 + ); + public static final ConfigOption LEVEL0_STOP_WRITES_TRIGGER = + new ConfigOption<>( + "rocksdb.level0_stop_writes_trigger", + "Hard limit on number of level-0 files for stopping writes.", + rangeInt(-1, Integer.MAX_VALUE), + 1024 + ); + public static final ConfigOption SOFT_PENDING_COMPACTION_LIMIT = + new ConfigOption<>( + "rocksdb.soft_pending_compaction_bytes_limit", + "The soft limit to impose on pending compaction in bytes.", + rangeInt(Bytes.GB, Long.MAX_VALUE), + 1024L * Bytes.GB + ); + public static final ConfigOption HARD_PENDING_COMPACTION_LIMIT = + new ConfigOption<>( + "rocksdb.hard_pending_compaction_bytes_limit", + "The hard limit to impose on pending compaction in bytes.", + rangeInt(Bytes.GB, Long.MAX_VALUE), + 2048L * Bytes.GB + ); + public static final ConfigOption ALLOW_MMAP_WRITES = + new ConfigOption<>( + "rocksdb.allow_mmap_writes", + "Allow the OS to mmap file for writing.", + disallowEmpty(), + false + ); + public static final ConfigOption ALLOW_MMAP_READS = + new ConfigOption<>( + "rocksdb.allow_mmap_reads", + "Allow the OS to mmap file for reading sst tables.", + disallowEmpty(), + false + ); + public static final ConfigOption USE_DIRECT_READS = + new ConfigOption<>( + "rocksdb.use_direct_reads", + "Enable the OS to use direct I/O for reading sst tables.", + disallowEmpty(), + false + ); + public static final ConfigOption USE_DIRECT_READS_WRITES_FC = + new ConfigOption<>( + "rocksdb.use_direct_io_for_flush_and_compaction", + "Enable the OS to use direct read/writes in flush and compaction.", + disallowEmpty(), + false + ); + public static final ConfigOption PIN_L0_FILTER_AND_INDEX_IN_CACHE = + new ConfigOption<>( + "rocksdb.pin_l0_filter_and_index_blocks_in_cache", + "Indicating if we'd put index/filter blocks to the block cache.", + disallowEmpty(), + true + ); + public static final ConfigOption PUT_FILTER_AND_INDEX_IN_CACHE = + new ConfigOption<>( + "rocksdb.cache_index_and_filter_blocks", + "Indicating if we'd put index/filter blocks to the block cache.", + disallowEmpty(), + true + ); + public static final ConfigOption BLOOM_FILTER_BITS_PER_KEY = + new ConfigOption<>( + "rocksdb.bloom_filter_bits_per_key", + "The bits per key in bloom filter, a good value is 10, " + + "which yields a filter with ~ 1% false positive rate, " + + "-1 means no bloom filter.", + rangeInt(-1, Integer.MAX_VALUE), + -1 + ); + public static final ConfigOption BLOOM_FILTER_MODE = + new ConfigOption<>( + "rocksdb.bloom_filter_block_based_mode", + "Use block based filter rather than full filter.", + disallowEmpty(), + false + ); + public static final ConfigOption BLOOM_FILTER_WHOLE_KEY = + new ConfigOption<>( + "rocksdb.bloom_filter_whole_key_filtering", + "True if place whole keys in the bloom filter, " + + "else place the prefix of keys.", + disallowEmpty(), + true + ); + public static final ConfigOption BLOOM_FILTERS_SKIP_LAST_LEVEL = + new ConfigOption<>( + "rocksdb.optimize_filters_for_hits", + "This flag allows us to not store filters for the last level.", + disallowEmpty(), + false + ); + public static final String BLOCK_TABLE_CONFIG = "rocksdb.block_table_config"; + public static final String WRITE_BUFFER_MANAGER = "rocksdb.write_buffer_manager"; + public static final String BLOCK_CACHE = "rocksdb.block_cache"; + public static final String WRITE_CACHE = "rocksdb.write_cache"; + public static final String ENV = "rocksdb.env"; + private static volatile RocksDBOptions instance; + + private RocksDBOptions() { + super(); + } + + public static synchronized RocksDBOptions instance() { + if (instance == null) { + instance = new RocksDBOptions(); + instance.registerOptions(); + } + return instance; + } +} diff --git a/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/RocksDBScanIterator.java b/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/RocksDBScanIterator.java new file mode 100644 index 000000000..becaa17bb --- /dev/null +++ b/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/RocksDBScanIterator.java @@ -0,0 +1,236 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.rocksdb.access; + +import java.util.Arrays; +import java.util.NoSuchElementException; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.apache.hugegraph.rocksdb.access.RocksDBSession.BackendColumn; +import org.apache.hugegraph.util.Bytes; +import org.apache.hugegraph.util.E; +import org.rocksdb.RocksIterator; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class RocksDBScanIterator implements ScanIterator { + private static final byte[] EMPTY_VALUE = new byte[0]; + private final RocksIterator rawIt; + private final byte[] keyBegin; + private final byte[] keyEnd; + private final int scanType; + + private final AtomicBoolean closed = new AtomicBoolean(false); + private final RocksDBSession.RefCounter iterReference; + private byte[] key; + private boolean matched; + + public RocksDBScanIterator(RocksIterator rawIt, byte[] keyBegin, byte[] keyEnd, + int scanType, RocksDBSession.RefCounter iterReference) { + this.rawIt = rawIt; + this.keyBegin = keyBegin; + this.keyEnd = keyEnd; + this.scanType = scanType; + + this.key = keyBegin; + this.matched = false; + this.iterReference = iterReference; + this.seek(); + } + + private void checkArguments() { + E.checkArgument(!(this.match(ScanIterator.Trait.SCAN_PREFIX_BEGIN) && + this.match(ScanIterator.Trait.SCAN_PREFIX_END)), + "Can't set SCAN_PREFIX_WITH_BEGIN and " + + "SCAN_PREFIX_WITH_END at the same time"); + + E.checkArgument(!(this.match(ScanIterator.Trait.SCAN_PREFIX_BEGIN) && + this.match(ScanIterator.Trait.SCAN_GT_BEGIN)), + "Can't set SCAN_PREFIX_WITH_BEGIN and " + + "SCAN_GT_BEGIN/SCAN_GTE_BEGIN at the same time"); + + E.checkArgument(!(this.match(ScanIterator.Trait.SCAN_PREFIX_END) && + this.match(ScanIterator.Trait.SCAN_LT_END)), + "Can't set SCAN_PREFIX_WITH_END and " + + "SCAN_LT_END/SCAN_LTE_END at the same time"); + + if (this.match(ScanIterator.Trait.SCAN_PREFIX_BEGIN)) { + E.checkArgument(this.keyBegin != null, + "Parameter `keyBegin` can't be null " + + "if set SCAN_PREFIX_WITH_BEGIN"); + E.checkArgument(this.keyEnd == null, + "Parameter `keyEnd` must be null " + + "if set SCAN_PREFIX_WITH_BEGIN"); + } + + if (this.match(ScanIterator.Trait.SCAN_PREFIX_END)) { + E.checkArgument(this.keyEnd != null, + "Parameter `keyEnd` can't be null " + + "if set SCAN_PREFIX_WITH_END"); + } + + if (this.match(ScanIterator.Trait.SCAN_GT_BEGIN)) { + E.checkArgument(this.keyBegin != null, + "Parameter `keyBegin` can't be null " + + "if set SCAN_GT_BEGIN or SCAN_GTE_BEGIN"); + } + + if (this.match(ScanIterator.Trait.SCAN_LT_END)) { + E.checkArgument(this.keyEnd != null, + "Parameter `keyEnd` can't be null " + + "if set SCAN_LT_END or SCAN_LTE_END"); + } + } + + @Override + public boolean hasNext() { + + if (this.closed.get()) { + //log.warn("Iterator has been closed"); + return false; + } + this.matched = this.rawIt.isOwningHandle(); + if (!this.matched) { + // Maybe closed + return this.matched; + } + + this.matched = this.rawIt.isValid(); + if (this.matched) { + // Update position for paging + this.key = this.rawIt.key(); + this.matched = this.filter(this.key); + } + if (!this.matched) { + // The end + this.key = null; + // Free the iterator if finished + this.close(); + } + return this.matched; + } + + private void seek() { + if (this.closed.get()) { + log.warn("Iterator has been closed"); + return; + } + if (this.keyBegin == null) { + // Seek to the first if no `keyBegin` + this.rawIt.seekToFirst(); + } else { + /* + * Seek to `keyBegin`: + * if set SCAN_GT_BEGIN/SCAN_GTE_BEGIN (key > / >= 'xx') + * or if set SCAN_PREFIX_WITH_BEGIN (key prefix with 'xx') + */ + this.rawIt.seek(this.keyBegin); + + // Skip `keyBegin` if set SCAN_GT_BEGIN (key > 'xx') + if (this.match(ScanIterator.Trait.SCAN_GT_BEGIN) && + !this.match(ScanIterator.Trait.SCAN_GTE_BEGIN)) { + while (this.rawIt.isValid() && + Bytes.equals(this.rawIt.key(), this.keyBegin)) { + this.rawIt.next(); + } + } + } + } + + @Override + public boolean isValid() { + return this.rawIt.isValid(); + } + + @Override + public BackendColumn next() { + if (this.closed.get()) { + log.warn("Iterator has been closed"); + throw new NoSuchElementException(); + } + if (!this.matched) { + if (!this.hasNext()) { + throw new NoSuchElementException(); + } + } + + BackendColumn col = BackendColumn.of(this.key, + this.match(Trait.SCAN_KEYONLY) ? EMPTY_VALUE : + this.rawIt.value()); + + this.rawIt.next(); + this.matched = false; + + return col; + } + + @Override + public long count() { + long count = 0L; + while (this.hasNext()) { + this.rawIt.next(); + count++; + } + return count; + } + + @Override + public byte[] position() { + return this.key; + } + + private boolean filter(byte[] v) { + + if (this.match(ScanIterator.Trait.SCAN_PREFIX_BEGIN)) { + return Bytes.prefixWith(v, this.keyBegin); + + } else if (this.match(ScanIterator.Trait.SCAN_PREFIX_END)) { + assert this.keyEnd != null; + return Bytes.prefixWith(v, this.keyEnd); + + } else if (this.match(ScanIterator.Trait.SCAN_LT_END)) { + assert this.keyEnd != null; + + if (this.match(ScanIterator.Trait.SCAN_LTE_END)) { + v = Arrays.copyOfRange(v, 0, this.keyEnd.length); + return Bytes.compare(v, this.keyEnd) <= 0; + } else { + return Bytes.compare(v, this.keyEnd) < 0; + } + } else { + + return true; + } + } + + @Override + public void close() { + if (!this.closed.getAndSet(true)) { + if (this.rawIt.isOwningHandle()) { + this.rawIt.close(); + } + this.iterReference.release(); + } + } + + private boolean match(int expected) { + return (expected & this.scanType) == expected; + } + +} diff --git a/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/RocksDBSession.java b/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/RocksDBSession.java new file mode 100644 index 000000000..0edadffb6 --- /dev/null +++ b/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/RocksDBSession.java @@ -0,0 +1,1057 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.rocksdb.access; + +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.stream.Collectors; + +import org.apache.commons.io.FileUtils; +import org.apache.hugegraph.config.HugeConfig; +import org.apache.hugegraph.rocksdb.access.util.Asserts; +import org.apache.hugegraph.store.term.HgPair; +import org.apache.hugegraph.util.Bytes; +import org.apache.hugegraph.util.E; +import org.rocksdb.BlockBasedTableConfig; +import org.rocksdb.Checkpoint; +import org.rocksdb.ColumnFamilyDescriptor; +import org.rocksdb.ColumnFamilyHandle; +import org.rocksdb.ColumnFamilyOptions; +import org.rocksdb.ColumnFamilyOptionsInterface; +import org.rocksdb.CompressionType; +import org.rocksdb.DBOptions; +import org.rocksdb.DBOptionsInterface; +import org.rocksdb.Env; +import org.rocksdb.FlushOptions; +import org.rocksdb.InfoLogLevel; +import org.rocksdb.IngestExternalFileOptions; +import org.rocksdb.MutableColumnFamilyOptionsInterface; +import org.rocksdb.MutableDBOptionsInterface; +import org.rocksdb.Options; +import org.rocksdb.Range; +import org.rocksdb.RocksDB; +import org.rocksdb.RocksDBException; +import org.rocksdb.SizeApproximationFlag; +import org.rocksdb.Slice; +import org.rocksdb.Statistics; +import org.rocksdb.WriteBufferManager; +import org.rocksdb.WriteOptions; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class RocksDBSession implements AutoCloseable, Cloneable { + + private static final int CPUS = Runtime.getRuntime().availableProcessors(); + final Statistics rocksDbStats; + final WriteOptions writeOptions; + final AtomicInteger refCount; + final AtomicBoolean shutdown; + final String tempSuffix = "_temp_"; + private final transient String graphName; + private final HugeConfig hugeConfig; + private final ReentrantReadWriteLock cfHandleLock; + private final Map tables; + private transient String dbPath; + private RocksDB rocksDB; + private DBOptions dbOptions; + private volatile boolean closed = false; + + public RocksDBSession(HugeConfig hugeConfig, String dbDataPath, String graphName, + long version) { + this.hugeConfig = hugeConfig; + this.graphName = graphName; + this.cfHandleLock = new ReentrantReadWriteLock(); + this.tables = new ConcurrentHashMap<>(); + this.refCount = new AtomicInteger(1); + this.shutdown = new AtomicBoolean(false); + this.writeOptions = new WriteOptions(); + this.rocksDbStats = new Statistics(); + openRocksDB(dbDataPath, version); + } + + private RocksDBSession(RocksDBSession origin) { + this.hugeConfig = origin.hugeConfig; + this.graphName = origin.graphName; + this.cfHandleLock = origin.cfHandleLock; + this.tables = origin.tables; + this.dbPath = origin.dbPath; + this.rocksDB = origin.rocksDB; + this.dbOptions = origin.dbOptions; + this.writeOptions = origin.writeOptions; + this.rocksDbStats = origin.rocksDbStats; + this.shutdown = origin.shutdown; + this.refCount = origin.refCount; + this.refCount.incrementAndGet(); + } + + /** + * create directory + */ + public static boolean createDirectory(String dirPath) { + Asserts.isTrue(!dirPath.isEmpty(), "dir path is empty"); + + File dirObject = new File(dirPath); + if (!dirObject.exists() || !dirObject.isDirectory()) { + dirObject.mkdirs(); + } + + return true; + } + + public static void initOptions(HugeConfig conf, + DBOptionsInterface db, + MutableDBOptionsInterface mdb, + ColumnFamilyOptionsInterface cf, + MutableColumnFamilyOptionsInterface mcf) { + final boolean optimize = conf.get(RocksDBOptions.OPTIMIZE_MODE); + if (db != null) { + /* + * Set true then the database will be created if it is missing. + * should we use options.setCreateMissingColumnFamilies()? + */ + db.setCreateIfMissing(true); + if (optimize) { + int processors = RocksDBSession.CPUS; + db.setIncreaseParallelism(Math.max(processors / 2, 1)); + db.setAllowConcurrentMemtableWrite(true); + db.setEnableWriteThreadAdaptiveYield(true); + } + db.setInfoLogLevel(InfoLogLevel.valueOf(conf.get(RocksDBOptions.LOG_LEVEL) + "_LEVEL")); + db.setMaxSubcompactions(conf.get(RocksDBOptions.MAX_SUB_COMPACTIONS)); + db.setAllowMmapWrites(conf.get(RocksDBOptions.ALLOW_MMAP_WRITES)); + db.setAllowMmapReads(conf.get(RocksDBOptions.ALLOW_MMAP_READS)); + db.setUseDirectReads(conf.get(RocksDBOptions.USE_DIRECT_READS)); + db.setUseDirectIoForFlushAndCompaction( + conf.get(RocksDBOptions.USE_DIRECT_READS_WRITES_FC)); + db.setMaxManifestFileSize(conf.get(RocksDBOptions.MAX_MANIFEST_FILE_SIZE)); + db.setSkipStatsUpdateOnDbOpen(conf.get(RocksDBOptions.SKIP_STATS_UPDATE_ON_DB_OPEN)); + db.setMaxFileOpeningThreads(conf.get(RocksDBOptions.MAX_FILE_OPENING_THREADS)); + db.setDbWriteBufferSize(conf.get(RocksDBOptions.DB_MEMTABLE_SIZE)); + WriteBufferManager bufferManager = + (WriteBufferManager) conf.getProperty(RocksDBOptions.WRITE_BUFFER_MANAGER); + if (bufferManager != null) { + db.setWriteBufferManager(bufferManager); + log.info("rocksdb use global WriteBufferManager"); + } + Env env = (Env) conf.getProperty(RocksDBOptions.ENV); + if (env != null) { + db.setEnv(env); + } + } + + if (mdb != null) { + /* + * Migrate to max_background_jobs option + * https://github.com/facebook/rocksdb/wiki/Thread-Pool + * https://github.com/facebook/rocksdb/pull/2205/files + */ + mdb.setMaxBackgroundJobs(conf.get(RocksDBOptions.MAX_BG_JOBS)); + mdb.setDelayedWriteRate(conf.get(RocksDBOptions.DELAYED_WRITE_RATE)); + mdb.setMaxOpenFiles(conf.get(RocksDBOptions.MAX_OPEN_FILES)); + mdb.setMaxTotalWalSize(conf.get(RocksDBOptions.MAX_TOTAL_WAL_SIZE)); + mdb.setDeleteObsoleteFilesPeriodMicros( + 1000000 * conf.get(RocksDBOptions.DELETE_OBSOLETE_FILE_PERIOD)); + } + + if (cf != null) { + if (optimize) { + // Optimize RocksDB + cf.optimizeLevelStyleCompaction(); + cf.optimizeUniversalStyleCompaction(); + } + + int numLevels = conf.get(RocksDBOptions.NUM_LEVELS); + List compressions = conf.get( + RocksDBOptions.LEVELS_COMPRESSIONS); + E.checkArgument(compressions.isEmpty() || + compressions.size() == numLevels, + "Elements number of '%s' must be 0 or " + + "be the same as '%s', but got %s != %s", + RocksDBOptions.LEVELS_COMPRESSIONS.name(), + RocksDBOptions.NUM_LEVELS.name(), + compressions.size(), numLevels); + + cf.setNumLevels(numLevels); + cf.setCompactionStyle(conf.get(RocksDBOptions.COMPACTION_STYLE)); + + cf.setBottommostCompressionType( + conf.get(RocksDBOptions.BOTTOMMOST_COMPRESSION)); + if (!compressions.isEmpty()) { + cf.setCompressionPerLevel(compressions); + } + + cf.setMinWriteBufferNumberToMerge( + conf.get(RocksDBOptions.MIN_MEMTABLES_TO_MERGE)); + cf.setMaxWriteBufferNumberToMaintain( + conf.get(RocksDBOptions.MAX_MEMTABLES_TO_MAINTAIN)); + + cf.setLevelCompactionDynamicLevelBytes( + conf.get(RocksDBOptions.DYNAMIC_LEVEL_BYTES)); + + + // https://github.com/facebook/rocksdb/wiki/Block-Cache + BlockBasedTableConfig tableConfig = + (BlockBasedTableConfig) conf.getProperty(RocksDBOptions.BLOCK_TABLE_CONFIG); + + if (tableConfig != null) { + cf.setTableFormatConfig(tableConfig); + } + + cf.setOptimizeFiltersForHits( + conf.get(RocksDBOptions.BLOOM_FILTERS_SKIP_LAST_LEVEL)); + + // https://github.com/facebook/rocksdb/tree/master/utilities/merge_operators + cf.setMergeOperatorName("uint64add"); // uint64add/stringappend + } + + if (mcf != null) { + mcf.setCompressionType(conf.get(RocksDBOptions.COMPRESSION)); + + mcf.setWriteBufferSize(conf.get(RocksDBOptions.MEMTABLE_SIZE)); + mcf.setMaxWriteBufferNumber(conf.get(RocksDBOptions.MAX_MEMTABLES)); + + mcf.setMaxBytesForLevelBase( + conf.get(RocksDBOptions.MAX_LEVEL1_BYTES)); + mcf.setMaxBytesForLevelMultiplier( + conf.get(RocksDBOptions.MAX_LEVEL_BYTES_MULTIPLIER)); + + mcf.setTargetFileSizeBase( + conf.get(RocksDBOptions.TARGET_FILE_SIZE_BASE)); + mcf.setTargetFileSizeMultiplier( + conf.get(RocksDBOptions.TARGET_FILE_SIZE_MULTIPLIER)); + + mcf.setLevel0FileNumCompactionTrigger( + conf.get(RocksDBOptions.LEVEL0_COMPACTION_TRIGGER)); + mcf.setLevel0SlowdownWritesTrigger( + conf.get(RocksDBOptions.LEVEL0_SLOWDOWN_WRITES_TRIGGER)); + mcf.setLevel0StopWritesTrigger( + conf.get(RocksDBOptions.LEVEL0_STOP_WRITES_TRIGGER)); + + mcf.setSoftPendingCompactionBytesLimit( + conf.get(RocksDBOptions.SOFT_PENDING_COMPACTION_LIMIT)); + mcf.setHardPendingCompactionBytesLimit( + conf.get(RocksDBOptions.HARD_PENDING_COMPACTION_LIMIT)); + + // conf.get(RocksDBOptions.BULKLOAD_MODE); + } + } + + @Override + public RocksDBSession clone() { + return new RocksDBSession(this); + } + + public RocksDB getDB() { + return this.rocksDB; + } + + public HugeConfig getHugeConfig() { + return this.hugeConfig; + } + + public String getGraphName() { + return this.graphName; + } + + public WriteOptions getWriteOptions() { + return this.writeOptions; + } + + public Map getTables() { + return tables; + } + + public ReentrantReadWriteLock.ReadLock getCfHandleReadLock() { + return this.cfHandleLock.readLock(); + } + + private String findLatestDBPath(String path, long version) { + File file = new File(path); + int strIndex = file.getName().indexOf("_"); + + String defaultName; + if (strIndex < 0) { + defaultName = file.getName(); + } else { + defaultName = file.getName().substring(0, strIndex); + } + String prefix = defaultName + "_"; + File parentFile = new File(file.getParent()); + ArrayList> dbs = new ArrayList<>(); + File[] files = parentFile.listFiles(); + if (files != null) { + dbs.ensureCapacity(files.length); + // search all db path + for (final File sFile : files) { + final String name = sFile.getName(); + if (!name.startsWith(prefix) && !name.equals(defaultName)) { + continue; + } + if (name.endsWith(tempSuffix)) { + continue; + } + long v1 = -1L; + long v2 = -1L; + if (name.length() > defaultName.length()) { + String[] versions = name.substring(prefix.length()).split("_"); + if (versions.length == 1) { + v1 = Long.parseLong(versions[0]); + } else if (versions.length == 2) { + v1 = Long.parseLong(versions[0]); + v2 = Long.parseLong(versions[1]); + } else { + continue; + } + } + dbs.add(new HgPair<>(v1, v2)); + } + } + + RocksDBFactory factory = RocksDBFactory.getInstance(); + // get last index db path + String latestDBPath = ""; + if (!dbs.isEmpty()) { + + dbs.sort((o1, o2) -> o1.getKey().equals(o2.getKey()) ? + o1.getValue().compareTo(o2.getValue()) : + o1.getKey().compareTo(o2.getKey())); + final int dbCount = dbs.size(); + for (int i = 0; i < dbCount; i++) { + final HgPair pair = dbs.get(i); + String curDBName; + if (pair.getKey() == -1L) { + curDBName = defaultName; + } else if (pair.getValue() == -1L) { + curDBName = String.format("%s_%d", defaultName, pair.getKey()); + } else { + curDBName = + String.format("%s_%d_%d", defaultName, pair.getKey(), pair.getValue()); + } + String curDBPath = Paths.get(parentFile.getPath(), curDBName).toString(); + if (i == dbCount - 1) { + latestDBPath = curDBPath; + } else { + // delete old db,在删除队列的文件不要删除 + if (!factory.findPathInRemovedList(curDBPath)) { + try { + FileUtils.deleteDirectory(new File(curDBPath)); + log.info("delete old dbpath {}", curDBPath); + } catch (IOException e) { + log.error("fail to delete old dbpath {}", curDBPath, e); + } + } + } + } + } else { + latestDBPath = Paths.get(parentFile.getPath(), defaultName).toString(); + } + if (factory.findPathInRemovedList(latestDBPath)) { + // 已经被删除,创建新的目录 + latestDBPath = + Paths.get(parentFile.getPath(), String.format("%s_%d", defaultName, version)) + .toString(); + } + // + log.info("{} latest db path {}", this.graphName, latestDBPath); + return latestDBPath; + } + + public boolean setDisableWAL(final boolean flag) { + this.writeOptions.setSync(!flag); + return this.writeOptions.setDisableWAL(flag).disableWAL(); + } + + public void checkTable(String table) { + try { + ColumnFamilyHandle handle = tables.get(table); + if (handle == null) { + createTable(table); + } + } catch (RocksDBException e) { + throw new DBStoreException(e); + } + } + + public boolean tableIsExist(String table) { + return this.tables.containsKey(table); + } + + private void openRocksDB(String dbDataPath, long version) { + + if (dbDataPath.endsWith(File.separator)) { + this.dbPath = dbDataPath + this.graphName; + } else { + this.dbPath = dbDataPath + File.separator + this.graphName; + } + + this.dbPath = findLatestDBPath(dbPath, version); + + Asserts.isTrue((dbPath != null), + () -> new DBStoreException("the data-path of RocksDB is null")); + + //makedir for rocksdb + createDirectory(dbPath); + + Options opts = new Options(); + RocksDBSession.initOptions(hugeConfig, opts, opts, opts, opts); + dbOptions = new DBOptions(opts); + dbOptions.setStatistics(rocksDbStats); + + try { + List columnFamilyDescriptorList = + new ArrayList<>(); + List columnFamilyBytes = RocksDB.listColumnFamilies(new Options(), dbPath); + + ColumnFamilyOptions cfOptions = new ColumnFamilyOptions(); + RocksDBSession.initOptions(this.hugeConfig, null, null, cfOptions, cfOptions); + + if (columnFamilyBytes.size() > 0) { + for (byte[] columnFamilyByte : columnFamilyBytes) { + columnFamilyDescriptorList.add( + new ColumnFamilyDescriptor(columnFamilyByte, cfOptions)); + } + } else { + columnFamilyDescriptorList.add( + new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOptions)); + } + List columnFamilyHandleList = new ArrayList<>(); + this.rocksDB = RocksDB.open(dbOptions, dbPath, columnFamilyDescriptorList, + columnFamilyHandleList); + Asserts.isTrue(columnFamilyHandleList.size() > 0, "must have column family"); + + for (ColumnFamilyHandle handle : columnFamilyHandleList) { + this.tables.put(new String(handle.getDescriptor().getName()), handle); + } + } catch (RocksDBException e) { + throw new DBStoreException(e); + } + } + + private ColumnFamilyHandle createTable(String table) throws RocksDBException { + cfHandleLock.writeLock().lock(); + try { + ColumnFamilyHandle handle = tables.get(table); + if (handle == null) { + ColumnFamilyOptions cfOptions = new ColumnFamilyOptions(); + RocksDBSession.initOptions(this.hugeConfig, null, null, cfOptions, cfOptions); + ColumnFamilyDescriptor cfDescriptor = + new ColumnFamilyDescriptor(table.getBytes(), cfOptions); + handle = this.rocksDB.createColumnFamily(cfDescriptor); + tables.put(table, handle); + } + return handle; + } finally { + cfHandleLock.writeLock().unlock(); + } + } + + public ColumnFamilyHandle getCF(String table) { + ColumnFamilyHandle handle = this.tables.get(table); + try { + if (handle == null) { + getCfHandleReadLock().unlock(); + handle = createTable(table); + getCfHandleReadLock().lock(); + } + } catch (RocksDBException e) { + throw new DBStoreException(e); + } + return handle; + } + + public CFHandleLock getCFHandleLock(String table) { + this.cfHandleLock.readLock().lock(); + ColumnFamilyHandle handle = this.tables.get(table); + try { + if (handle == null) { + this.cfHandleLock.readLock().unlock(); + handle = createTable(table); + this.cfHandleLock.readLock().lock(); + handle = this.tables.get(table); + } + } catch (RocksDBException e) { + throw new DBStoreException(e); + } + return new CFHandleLock(handle, this.cfHandleLock); + } + + @Override + public String toString() { + return "RocksDBSession"; + } + + /** + * Delete all data of table + * + * @param tables + * @throws DBStoreException + */ + public void deleteTables(String... tables) throws DBStoreException { + // TODO: Is this right? + dropTables(tables); + createTables(tables); + } + + public void createTables(String... tables) throws DBStoreException { + if (!this.rocksDB.isOwningHandle()) { + return; + } + cfHandleLock.writeLock().lock(); + try { + List cfList = new ArrayList<>(); + for (String table : tables) { + if (this.tables.get(table) != null) { + continue; + } + + ColumnFamilyDescriptor cfDescriptor = new ColumnFamilyDescriptor(table.getBytes()); + cfList.add(cfDescriptor); + } + + if (cfList.size() > 0) { + List cfHandles = this.rocksDB.createColumnFamilies(cfList); + for (ColumnFamilyHandle handle : cfHandles) { + this.tables.put(new String(handle.getDescriptor().getName()), handle); + } + } + + } catch (RocksDBException e) { + throw new DBStoreException(e); + } finally { + cfHandleLock.writeLock().unlock(); + } + + } + + public void dropTables(String... tables) throws DBStoreException { + if (!this.rocksDB.isOwningHandle()) { + return; + } + + cfHandleLock.writeLock().lock(); + try { + List cfHandles = new ArrayList<>(); + for (String table : tables) { + ColumnFamilyHandle handle = this.tables.get(table); + if (handle != null) { + cfHandles.add(handle); + } else { + this.tables.remove(table); + } + } + + if (cfHandles.size() > 0) { + this.rocksDB.dropColumnFamilies(cfHandles); + } + + for (ColumnFamilyHandle h : cfHandles) { + String tName = new String(h.getDescriptor().getName()); + h.close(); + log.info("drop table: {}", tName); + this.tables.remove(tName); + } + + } catch (RocksDBException e) { + throw new DBStoreException(e); + } finally { + cfHandleLock.writeLock().unlock(); + } + } + + public synchronized void truncate() { + Set tableNames = this.tables.keySet(); + String defaultCF = new String(RocksDB.DEFAULT_COLUMN_FAMILY); + tableNames.remove(defaultCF); + + log.info("truncate table: {}", String.join(",", tableNames)); + this.dropTables(tableNames.toArray(new String[0])); + this.createTables(tableNames.toArray(new String[0])); + } + + public void flush(boolean wait) { + cfHandleLock.readLock().lock(); + try { + rocksDB.flush(new FlushOptions().setWaitForFlush(wait), + tables.entrySet() + .stream().map(e -> e.getValue()) + .collect(Collectors.toList())); + + } catch (RocksDBException e) { + throw new DBStoreException(e); + } finally { + cfHandleLock.readLock().unlock(); + } + } + + void shutdown() { + if (!shutdown.compareAndSet(false, true)) { + return; + } + log.info("shutdown db {}, path is {} ", getGraphName(), getDbPath()); + + cfHandleLock.writeLock().lock(); + try { + this.tables.forEach((k, v) -> { + v.close(); + }); + this.tables.clear(); + + if (rocksDB != null) { + try { + this.rocksDB.syncWal(); + } catch (RocksDBException e) { + log.warn("exception ", e); + } + this.rocksDB.close(); + } + rocksDB = null; + if (dbOptions != null) { + this.dbOptions.close(); + this.writeOptions.close(); + this.rocksDbStats.close(); + dbOptions = null; + } + } finally { + cfHandleLock.writeLock().unlock(); + } + } + + public SessionOperator sessionOp() { + return new SessionOperatorImpl(this); + } + + public long getLatestSequenceNumber() { + return rocksDB.getLatestSequenceNumber(); + } + + public Statistics getRocksDbStats() { + return rocksDbStats; + } + + public void saveSnapshot(String snapshotPath) throws DBStoreException { + long startTime = System.currentTimeMillis(); + log.info("begin save snapshot at {}", snapshotPath); + cfHandleLock.readLock().lock(); + try (final Checkpoint checkpoint = Checkpoint.create(this.rocksDB)) { + final String tempPath = Paths.get(snapshotPath) + "_temp"; + final File tempFile = new File(tempPath); + FileUtils.deleteDirectory(tempFile); + checkpoint.createCheckpoint(tempPath); + final File snapshotFile = new File(snapshotPath); + try { + FileUtils.deleteDirectory(snapshotFile); + FileUtils.moveDirectory(tempFile, snapshotFile); + } catch (IOException e) { + log.error("Fail to rename {} to {}", tempPath, snapshotPath, e); + throw new DBStoreException( + String.format("Fail to rename %s to %s", tempPath, snapshotPath)); + } + } catch (final DBStoreException e) { + throw e; + } catch (final Exception e) { + log.error("Fail to write snapshot at path: {}", snapshotPath, e); + throw new DBStoreException( + String.format("Fail to write snapshot at path %s", snapshotPath)); + } finally { + cfHandleLock.readLock().unlock(); + } + log.info("saved snapshot into {}, time cost {} ms", snapshotPath, + System.currentTimeMillis() - startTime); + } + + private boolean verifySnapshot(String snapshotPath) { + try { + try (final Options options = new Options(); + final ColumnFamilyOptions cfOptions = new ColumnFamilyOptions()) { + List columnFamilyDescriptorList = new ArrayList<>(); + List columnFamilyBytes = RocksDB.listColumnFamilies(options, snapshotPath); + + if (columnFamilyBytes.size() > 0) { + for (byte[] columnFamilyByte : columnFamilyBytes) { + columnFamilyDescriptorList.add( + new ColumnFamilyDescriptor(columnFamilyByte, cfOptions)); + } + } else { + columnFamilyDescriptorList.add( + new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOptions)); + } + List columnFamilyHandleList = new ArrayList<>(); + + try (final DBOptions dbOptions = new DBOptions(); + final RocksDB db = RocksDB.openReadOnly(dbOptions, snapshotPath, + columnFamilyDescriptorList, + columnFamilyHandleList)) { + for (ColumnFamilyHandle handle : columnFamilyHandleList) { + if (handle == null) { + log.error("verifySnapshot some ColumnFamilyHandle is null in {}", + snapshotPath); + return false; + } + } + } + } + log.info("verifySnapshot {} Ok", snapshotPath); + } catch (RocksDBException e) { + log.error("verifySnapshot {} failed. {}", snapshotPath, e.toString()); + return false; + } + return true; + } + + public void loadSnapshot(String snapshotPath, long v1) throws DBStoreException { + long startTime = System.currentTimeMillis(); + log.info("begin load snapshot from {}", snapshotPath); + cfHandleLock.writeLock().lock(); + try { + final File snapshotFile = new File(snapshotPath); + if (!snapshotFile.exists()) { + log.error("Snapshot file {} not exists.", snapshotPath); + throw new DBStoreException( + String.format("Snapshot file %s not exists", snapshotPath)); + } + + // replace rocksdb data with snapshot data + File dbFile = new File(this.dbPath); + String parent = dbFile.getParent(); + String defaultName = dbFile.getName().split("_")[0]; + String defaultPath = Paths.get(parent, defaultName).toString(); + String newDBPath = String.format("%s_%d", defaultPath, v1); + String tempDBPath = newDBPath + tempSuffix; + + // first link snapshot to temp dir + try { + final File tempFile = new File(tempDBPath); + FileUtils.deleteDirectory(tempFile); + FileUtils.forceMkdir(tempFile); + File[] fs = snapshotFile.listFiles(); + for (File f : fs) { + if (!f.isDirectory()) { + File target = Paths.get(tempFile.getPath(), f.getName()).toFile(); + // create hard link + try { + Files.createLink(target.toPath(), f.toPath()); + } catch (IOException e) { + log.error("link failed, {} -> {}, error:{}", + f.getAbsolutePath(), + target.getAbsolutePath(), + e.getMessage()); + // diff disk + Files.copy(f.toPath(), target.toPath()); + } + } + } + } catch (IOException e) { + log.error("Fail to copy {} to {}. {}", snapshotPath, tempDBPath, e.toString()); + try { + FileUtils.deleteDirectory(new File(tempDBPath)); + } catch (IOException e2) { + log.error("Fail to delete directory {}. {}", tempDBPath, e2.toString()); + } + throw new DBStoreException( + String.format("Fail to copy %s to %s", snapshotPath, tempDBPath)); + } + // verify temp path + if (!verifySnapshot(tempDBPath)) { + throw new DBStoreException( + String.format("failed to verify snapshot %s", tempDBPath)); + } + + // move temp to newDBPath + try { + FileUtils.deleteDirectory(new File(newDBPath)); + FileUtils.moveDirectory(new File(tempDBPath), new File(newDBPath)); + } catch (IOException e) { + log.error("Fail to copy {} to {}. {}", snapshotPath, tempDBPath, e.toString()); + throw new DBStoreException( + String.format("Fail to move %s to %s", tempDBPath, newDBPath)); + } + } catch (final DBStoreException e) { + throw e; + } catch (final Exception e) { + log.error("failed to load snapshot from {}", snapshotPath); + throw new DBStoreException( + String.format("Fail to write snapshot at path %s", snapshotPath)); + } finally { + cfHandleLock.writeLock().unlock(); + } + log.info("loaded snapshot from {}, time cost {} ms", snapshotPath, + System.currentTimeMillis() - startTime); + } + + /** + * @param : + * @return the approximate size of the data. + * @description TODO + */ + public long getApproximateDataSize() { + return getApproximateDataSize("\0".getBytes(StandardCharsets.UTF_8), + "\255".getBytes(StandardCharsets.UTF_8)); + } + + public long getEstimateNumKeys() { + cfHandleLock.readLock().lock(); + try { + long totalCount = 0; + for (ColumnFamilyHandle h : this.tables.values()) { + long count = this.rocksDB.getLongProperty(h, "rocksdb.estimate-num-keys"); + totalCount += count; + } + return totalCount; + } catch (RocksDBException e) { + throw new DBStoreException(e); + } finally { + cfHandleLock.readLock().unlock(); + } + } + + /** + * @param : + * @return the approximate size of the data. N KB. + * @description TODO + */ + public long getApproximateDataSize(byte[] start, byte[] end) { + cfHandleLock.readLock().lock(); + try { + long kbSize = 0; + long bytesSize = 0; + Range r1 = new Range(new Slice(start), new Slice(end)); + for (ColumnFamilyHandle h : this.tables.values()) { + long[] sizes = this.rocksDB.getApproximateSizes( + h, + List.of(r1), + SizeApproximationFlag.INCLUDE_FILES, + SizeApproximationFlag.INCLUDE_MEMTABLES); + + bytesSize += sizes[0]; + kbSize += bytesSize / 1024; + bytesSize = bytesSize % 1024; + } + if (bytesSize != 0) { + kbSize += 1; + } + return kbSize; + } finally { + cfHandleLock.readLock().unlock(); + } + } + + public Map getApproximateCFDataSize(byte[] start, byte[] end) { + Map map = new ConcurrentHashMap<>(this.tables.size()); + cfHandleLock.readLock().lock(); + try { + Range r1 = new Range(new Slice(start), new Slice(end)); + for (ColumnFamilyHandle h : this.tables.values()) { + long[] sizes = this.rocksDB.getApproximateSizes( + h, + List.of(r1), + SizeApproximationFlag.INCLUDE_FILES, + SizeApproximationFlag.INCLUDE_MEMTABLES); + String name = new String(h.getDescriptor().getName()); + String size = FileUtils.byteCountToDisplaySize(sizes[0]); + map.put(name, size); + } + } catch (RocksDBException e) { + throw new DBStoreException(e); + } finally { + cfHandleLock.readLock().unlock(); + } + return map; + } + + public Map getKeyCountPerCF(byte[] start, byte[] end, boolean accurate) throws + DBStoreException { + ConcurrentHashMap map = new ConcurrentHashMap<>(this.tables.size()); + cfHandleLock.readLock().lock(); + try { + SessionOperator op = this.sessionOp(); + for (ColumnFamilyHandle h : this.tables.values()) { + String name = new String(h.getDescriptor().getName()); + long count = 0; + if (accurate) { + count = op.keyCount(start, end, name); + } else { + count = op.estimatedKeyCount(name); + } + map.put(name, count); + } + } catch (RocksDBException e) { + throw new DBStoreException(e); + } finally { + cfHandleLock.readLock().unlock(); + } + return map; + } + + public String getDbPath() { + return this.dbPath; + } + + public void ingestSstFile(Map> sstFiles) { + long startTime = System.currentTimeMillis(); + log.info("begin ingestSstFile. graphName {}", this.graphName); + try { + for (Map.Entry> entry : sstFiles.entrySet()) { + String cfName = new String(entry.getKey()); + try (CFHandleLock cfHandle = this.getCFHandleLock(cfName)) { + try (final IngestExternalFileOptions ingestOptions = + new IngestExternalFileOptions() + .setMoveFiles(true)) { + this.rocksDB.ingestExternalFile(cfHandle.get(), entry.getValue(), + ingestOptions); + log.info("Rocksdb {} ingestSstFile cf:{}, sst: {}", this.graphName, cfName, + entry.getValue()); + } + } + } + } catch (RocksDBException e) { + throw new DBStoreException("Rocksdb ingestSstFile error " + this.graphName, e); + } + log.info("end ingestSstFile. graphName {}, time cost {} ms", this.graphName, + System.currentTimeMillis() - startTime); + } + + public String getProperty(String property) { + try { + return rocksDB.getProperty(property); + } catch (RocksDBException e) { + log.error("getProperty exception {}", e.getMessage()); + return "0"; + } + } + + /** + * @return true when all iterators were closed. + */ + public int getRefCount() { + return this.refCount.get(); + } + + public void forceResetRefCount() { + this.refCount.set(0); + } + + @Override + public synchronized void close() { + if (closed) { + log.warn("RocksDBSession has been closed!"); + return; + } + try { + if (this.refCount.decrementAndGet() > 0) { + return; + } + } finally { + closed = true; + } + + assert this.refCount.get() == 0; + this.shutdown(); + } + + RefCounter getRefCounter() { + return new RefCounter(this.refCount); + } + + public static class CFHandleLock implements Closeable { + private final ColumnFamilyHandle handle; + private final ReentrantReadWriteLock lock; + + public CFHandleLock(ColumnFamilyHandle handle, ReentrantReadWriteLock lock) { + this.handle = handle; + this.lock = lock; + } + + @Override + public void close() { + lock.readLock().unlock(); + } + + public ColumnFamilyHandle get() { + return handle; + } + } + + /** + * A wrapper for RocksIterator that convert RocksDB results to std Iterator + */ + + public static class BackendColumn implements Comparable { + + public byte[] name; + public byte[] value; + + public static BackendColumn of(byte[] name, byte[] value) { + BackendColumn col = new BackendColumn(); + col.name = name; + col.value = value; + return col; + } + + @Override + public String toString() { + return String.format("%s=%s", + new String(name, StandardCharsets.UTF_8), + new String(value, StandardCharsets.UTF_8)); + } + + @Override + public int compareTo(BackendColumn other) { + if (other == null) { + return 1; + } + return Bytes.compare(this.name, other.name); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof BackendColumn)) { + return false; + } + BackendColumn other = (BackendColumn) obj; + return Bytes.equals(this.name, other.name) && + Bytes.equals(this.value, other.value); + } + } + + class RefCounter { + final AtomicInteger refCount; + + RefCounter(AtomicInteger refCount) { + (this.refCount = refCount).incrementAndGet(); + } + + public void release() { + if (0 == this.refCount.decrementAndGet()) { + shutdown(); + } + } + } +} diff --git a/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/ScanIterator.java b/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/ScanIterator.java new file mode 100644 index 000000000..bfb795dc5 --- /dev/null +++ b/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/ScanIterator.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.rocksdb.access; + +import java.io.Closeable; + +public interface ScanIterator extends Closeable { + + boolean hasNext(); + + boolean isValid(); + + T next(); + + default long count() { + return 0; + } + + default byte[] position() { + return new byte[0]; + } + + default void seek(byte[] position) { + } + + @Override + void close(); + + abstract class Trait { + public static final int SCAN_ANY = 0x80; + public static final int SCAN_PREFIX_BEGIN = 0x01; + public static final int SCAN_PREFIX_END = 0x02; + public static final int SCAN_GT_BEGIN = 0x04; + public static final int SCAN_GTE_BEGIN = 0x0c; + public static final int SCAN_LT_END = 0x10; + public static final int SCAN_LTE_END = 0x30; + public static final int SCAN_KEYONLY = 0x40; + public static final int SCAN_HASHCODE = 0x100; + } +} diff --git a/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/SessionOperator.java b/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/SessionOperator.java new file mode 100644 index 000000000..19a750464 --- /dev/null +++ b/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/SessionOperator.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.rocksdb.access; + +import org.apache.hugegraph.store.term.HgPair; + +public interface SessionOperator { + + HgPair keyRange(String table); + + void compactRange(String table) throws DBStoreException; + + void compactRange() throws DBStoreException; + + void put(String table, byte[] key, byte[] value) throws DBStoreException; + + ScanIterator scan(String tableName); + + ScanIterator scan(String tableName, byte[] prefix); + + ScanIterator scan(String tableName, byte[] prefix, int scanType); + + ScanIterator scan(String tableName, byte[] keyFrom, byte[] keyTo, int scanType); + + /** + * 扫描所有cf指定范围的数据 + */ + ScanIterator scanRaw(byte[] keyFrom, byte[] keyTo, long startSeqNum); + + long keyCount(byte[] start, byte[] end, String tableName); + + long estimatedKeyCount(String tableName); + + /* + * only support 'long data' operator + * */ + void merge(String table, byte[] key, byte[] value) throws DBStoreException; + + void increase(String table, byte[] key, byte[] value) throws DBStoreException; + + void delete(String table, byte[] key) throws DBStoreException; + + void deleteSingle(String table, byte[] key) throws DBStoreException; + + void deletePrefix(String table, byte[] key) throws DBStoreException; + + void deleteRange(String table, byte[] keyFrom, byte[] keyTo) throws DBStoreException; + + /** + * 删除所有cf指定范围的数据 + */ + void deleteRange(byte[] keyFrom, byte[] keyTo) throws DBStoreException; + + byte[] get(String table, byte[] key) throws DBStoreException; + + void prepare(); + + Integer commit() throws DBStoreException; + + void rollback(); + + RocksDBSession getDBSession(); +} diff --git a/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/SessionOperatorImpl.java b/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/SessionOperatorImpl.java new file mode 100644 index 000000000..cca5c6b09 --- /dev/null +++ b/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/SessionOperatorImpl.java @@ -0,0 +1,418 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.rocksdb.access; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Iterator; + +import org.apache.hugegraph.rocksdb.access.RocksDBSession.CFHandleLock; +import org.apache.hugegraph.rocksdb.access.util.Asserts; +import org.apache.hugegraph.store.term.HgPair; +import org.apache.hugegraph.util.Bytes; +import org.rocksdb.ReadOptions; +import org.rocksdb.RocksDB; +import org.rocksdb.RocksDBException; +import org.rocksdb.RocksIterator; +import org.rocksdb.Slice; +import org.rocksdb.Snapshot; +import org.rocksdb.WriteBatch; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class SessionOperatorImpl implements SessionOperator { + + private final RocksDB db; + private final RocksDBSession session; + private WriteBatch batch; + + public SessionOperatorImpl(RocksDBSession session) { + this.session = session; + this.db = session.getDB(); + } + + public static final byte[] increaseOne(byte[] bytes) { + final byte BYTE_MAX_VALUE = (byte) 0xff; + assert bytes.length > 0; + byte last = bytes[bytes.length - 1]; + if (last != BYTE_MAX_VALUE) { + bytes[bytes.length - 1] += 0x01; + } else { + // Process overflow (like [1, 255] => [2, 0]) + int i = bytes.length - 1; + for (; i > 0 && bytes[i] == BYTE_MAX_VALUE; --i) { + bytes[i] += 0x01; + } + if (bytes[i] == BYTE_MAX_VALUE) { + assert i == 0; + throw new DBStoreException("Unable to increase bytes: %s", Bytes.toHex(bytes)); + } + bytes[i] += 0x01; + } + return bytes; + } + + public RocksDB rocksdb() { + return db; + } + + @Override + public RocksDBSession getDBSession() { + return session; + } + + private CFHandleLock getLock(String table) { + CFHandleLock cf = this.session.getCFHandleLock(table); + return cf; + } + + @Override + public HgPair keyRange(String table) { + byte[] startKey, endKey; + try (CFHandleLock handle = this.getLock(table); + RocksIterator iter = rocksdb().newIterator(handle.get())) { + iter.seekToFirst(); + if (!iter.isValid()) { + return null; + } + startKey = iter.key(); + iter.seekToLast(); + if (!iter.isValid()) { + return new HgPair<>(startKey, null); + } + endKey = iter.key(); + } + return new HgPair<>(startKey, endKey); + } + + @Override + public void compactRange(String table) throws DBStoreException { + try (CFHandleLock handle = this.getLock(table)) { + rocksdb().compactRange(handle.get()); + } catch (RocksDBException e) { + throw new DBStoreException(e); + } + } + + @Override + public void compactRange() throws DBStoreException { + for (String name : session.getTables().keySet()) { + compactRange(name); + } + } + + @Override + public void put(String table, byte[] key, byte[] value) throws DBStoreException { + try { + this.getBatch().put(session.getCF(table), key, value); + } catch (RocksDBException e) { + throw new DBStoreException(e); + } + } + + /* + * only support 'long data' operator + */ + @Override + public void merge(String table, byte[] key, byte[] value) throws DBStoreException { + try { + this.getBatch().merge(session.getCF(table), key, value); + } catch (RocksDBException e) { + throw new DBStoreException(e); + } + } + + @Override + public void increase(String table, byte[] key, byte[] value) throws DBStoreException { + try (CFHandleLock cf = this.getLock(table)) { + rocksdb().merge(cf.get(), key, value); + } catch (RocksDBException e) { + throw new DBStoreException(e); + } + } + + @Override + public void delete(String table, byte[] key) throws DBStoreException { + try { + this.getBatch().delete(session.getCF(table), key); + } catch (RocksDBException e) { + throw new DBStoreException(e); + } + } + + @Override + public void deleteSingle(String table, byte[] key) throws DBStoreException { + try { + this.getBatch().singleDelete(session.getCF(table), key); + } catch (RocksDBException e) { + throw new DBStoreException(e); + } + } + + @Override + public void deletePrefix(String table, byte[] key) throws DBStoreException { + byte[] keyFrom = key; + byte[] keyTo = Arrays.copyOf(key, key.length); + keyTo = increaseOne(keyTo); + try { + this.getBatch().deleteRange(session.getCF(table), keyFrom, keyTo); + } catch (RocksDBException e) { + throw new DBStoreException(e); + } + } + + @Override + public void deleteRange(String table, byte[] keyFrom, byte[] keyTo) throws DBStoreException { + Asserts.isTrue(keyFrom != null, "KeyFrom is null"); + Asserts.isTrue(keyTo != null, "KeyTo is null"); + + if (Bytes.compare(keyTo, keyFrom) < 0) { + throw new DBStoreException("[end key: %s ] is lower than [start key: %s]", + Arrays.toString(keyTo), Arrays.toString(keyFrom)); + } + + try { + this.prepare(); + this.getBatch().deleteRange(session.getCF(table), keyFrom, keyTo); + this.commit(); + } catch (RocksDBException e) { + this.rollback(); + throw new DBStoreException(e); + } + } + + @Override + public void deleteRange(byte[] keyFrom, byte[] keyTo) throws DBStoreException { + for (String name : session.getTables().keySet()) { + deleteRange(name, keyFrom, keyTo); + } + } + + @Override + public byte[] get(String table, byte[] key) throws DBStoreException { + try (CFHandleLock cf = this.getLock(table)) { + return rocksdb().get(cf.get(), key); + } catch (RocksDBException e) { + throw new DBStoreException(e); + } + } + + @Override + public void prepare() { + this.session.getCfHandleReadLock().lock(); + } + + /** + * commit抛出异常后一定要调用rollback,否则会造成cfHandleReadLock未释放 + */ + @Override + public Integer commit() throws DBStoreException { + int count = this.getBatch().count(); + if (count > 0) { + try { + rocksdb().write(session.getWriteOptions(), this.batch); + } catch (RocksDBException e) { + throw new DBStoreException(e); + } + // Clear batch if write() successfully (retained if failed) + this.batch.clear(); + this.batch.close(); + this.batch = null; + } + this.session.getCfHandleReadLock().unlock(); + return count; + } + + @Override + public void rollback() { + try { + if (this.batch != null) { + this.batch.clear(); + this.batch.close(); + } + this.batch = null; + } finally { + try { + this.session.getCfHandleReadLock().unlock(); + } catch (Exception e) { + log.error("rollback {}", e); + } + } + } + + @Override + public ScanIterator scan(String tableName) { + try (CFHandleLock handle = this.getLock(tableName)) { + if (handle == null) { + log.info("no find table : {}", tableName); + return null; + } + return new RocksDBScanIterator(this.rocksdb().newIterator(handle.get()), null, null, + ScanIterator.Trait.SCAN_ANY, + this.session.getRefCounter()); + } + } + + @Override + public ScanIterator scan(String tableName, byte[] prefix) { + return scan(tableName, prefix, 0); + } + + @Override + public ScanIterator scan(String tableName, byte[] prefix, int scanType) { + try (CFHandleLock handle = this.getLock(tableName)) { + if (handle == null) { + log.info("no find table: {} for scanning with prefix: {}", tableName, + new String(prefix)); + return null; + } + return new RocksDBScanIterator(this.rocksdb().newIterator(handle.get()), prefix, null, + ScanIterator.Trait.SCAN_PREFIX_BEGIN | scanType, + this.session.getRefCounter()); + } + } + + @Override + public ScanIterator scan(String tableName, byte[] keyFrom, byte[] keyTo, int scanType) { + try (CFHandleLock handle = this.getLock(tableName)) { + if (handle == null) { + log.info("no find table: {} for scantype: {}", tableName, scanType); + return null; + } + return new RocksDBScanIterator(this.rocksdb().newIterator(handle.get()), keyFrom, keyTo, + scanType, + this.session.getRefCounter()); + } + } + + /** + * 遍历所有cf指定范围的数据 + */ + @Override + public ScanIterator scanRaw(byte[] keyFrom, byte[] keyTo, long startSeqNum) { + int kNumInternalBytes = 8; //internal key 增加的8个字节后缀 + Snapshot snapshot = rocksdb().getSnapshot(); + Iterator cfIterator = session.getTables().keySet().iterator(); + + return new ScanIterator() { + String cfName = null; + + @Override + public boolean hasNext() { + return cfIterator.hasNext(); + } + + @Override + public boolean isValid() { + return cfIterator.hasNext(); + } + + @Override + public T next() { + RocksIterator iterator = null; + ReadOptions readOptions = new ReadOptions() + .setSnapshot(snapshot); + // TODO rocksdb7.x不支持 setStartSeqNum,改为使用Timestamp + if (keyFrom != null) { + readOptions.setIterateLowerBound(new Slice(keyFrom)); + } + if (keyTo != null) { + readOptions.setIterateUpperBound(new Slice(keyTo)); + } + while (iterator == null && cfIterator.hasNext()) { + cfName = cfIterator.next(); + try (CFHandleLock handle = getLock(cfName)) { + iterator = rocksdb().newIterator(handle.get(), readOptions); + iterator.seekToFirst(); + } + } + if (iterator == null) { + return null; + } + RocksIterator finalIterator = iterator; + return (T) new ScanIterator() { + private final ReadOptions holdReadOptions = readOptions; + + @Override + public boolean hasNext() { + return finalIterator.isValid(); + } + + @Override + public boolean isValid() { + return finalIterator.isValid(); + } + + @Override + public T next() { + byte[] key = finalIterator.key(); + if (startSeqNum > 0) { + key = Arrays.copyOfRange(key, 0, key.length - kNumInternalBytes); + } + RocksDBSession.BackendColumn col = + RocksDBSession.BackendColumn.of(key, finalIterator.value()); + finalIterator.next(); + return (T) col; + } + + @Override + public void close() { + finalIterator.close(); + holdReadOptions.close(); + } + + }; + } + + @Override + public void close() { + rocksdb().releaseSnapshot(snapshot); + } + + @Override + public byte[] position() { + return cfName.getBytes(StandardCharsets.UTF_8); + + } + }; + } + + @Override + public long keyCount(byte[] start, byte[] end, String tableName) { + ScanIterator it = scan(tableName, start, end, ScanIterator.Trait.SCAN_LT_END); + return it.count(); + } + + @Override + public long estimatedKeyCount(String tableName) throws DBStoreException { + try { + return this.rocksdb() + .getLongProperty(session.getCF(tableName), "rocksdb.estimate-num-keys"); + } catch (RocksDBException e) { + throw new DBStoreException(e); + } + } + + private WriteBatch getBatch() { + if (this.batch == null) { + this.batch = new WriteBatch(); + } + return this.batch; + } +} diff --git a/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/util/Asserts.java b/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/util/Asserts.java new file mode 100644 index 000000000..892f315ba --- /dev/null +++ b/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/util/Asserts.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.rocksdb.access.util; + +import java.util.function.Supplier; + + +public final class Asserts { + + public static void isTrue(boolean expression, String message) { + if (message == null) { + throw new IllegalArgumentException("message is null"); + } + if (!expression) { + throw new IllegalArgumentException(message); + } + } + + public static void isTrue(boolean expression, Supplier s) { + if (s == null) { + throw new IllegalArgumentException("Supplier is null"); + } + if (!expression) { + throw s.get(); + } + } + +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/util/CRC64.java b/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/util/CRC64.java new file mode 100644 index 000000000..f5e02c052 --- /dev/null +++ b/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/util/CRC64.java @@ -0,0 +1,187 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.rocksdb.access.util; + +import java.util.zip.Checksum; + +public class CRC64 implements Checksum { + private static final long[] CRC_TABLE = new long[]{0x0000000000000000L, 0x42F0E1EBA9EA3693L, + 0x85E1C3D753D46D26L, 0xC711223CFA3E5BB5L, + 0x493366450E42ECDFL, 0x0BC387AEA7A8DA4CL, + 0xCCD2A5925D9681F9L, 0x8E224479F47CB76AL, + 0x9266CC8A1C85D9BEL, 0xD0962D61B56FEF2DL, + 0x17870F5D4F51B498L, 0x5577EEB6E6BB820BL, + 0xDB55AACF12C73561L, 0x99A54B24BB2D03F2L, + 0x5EB4691841135847L, 0x1C4488F3E8F96ED4L, + 0x663D78FF90E185EFL, 0x24CD9914390BB37CL, + 0xE3DCBB28C335E8C9L, 0xA12C5AC36ADFDE5AL, + 0x2F0E1EBA9EA36930L, 0x6DFEFF5137495FA3L, + 0xAAEFDD6DCD770416L, 0xE81F3C86649D3285L, + 0xF45BB4758C645C51L, 0xB6AB559E258E6AC2L, + 0x71BA77A2DFB03177L, 0x334A9649765A07E4L, + 0xBD68D2308226B08EL, 0xFF9833DB2BCC861DL, + 0x388911E7D1F2DDA8L, 0x7A79F00C7818EB3BL, + 0xCC7AF1FF21C30BDEL, 0x8E8A101488293D4DL, + 0x499B3228721766F8L, 0x0B6BD3C3DBFD506BL, + 0x854997BA2F81E701L, 0xC7B97651866BD192L, + 0x00A8546D7C558A27L, 0x4258B586D5BFBCB4L, + 0x5E1C3D753D46D260L, 0x1CECDC9E94ACE4F3L, + 0xDBFDFEA26E92BF46L, 0x990D1F49C77889D5L, + 0x172F5B3033043EBFL, 0x55DFBADB9AEE082CL, + 0x92CE98E760D05399L, 0xD03E790CC93A650AL, + 0xAA478900B1228E31L, 0xE8B768EB18C8B8A2L, + 0x2FA64AD7E2F6E317L, 0x6D56AB3C4B1CD584L, + 0xE374EF45BF6062EEL, 0xA1840EAE168A547DL, + 0x66952C92ECB40FC8L, 0x2465CD79455E395BL, + 0x3821458AADA7578FL, 0x7AD1A461044D611CL, + 0xBDC0865DFE733AA9L, 0xFF3067B657990C3AL, + 0x711223CFA3E5BB50L, 0x33E2C2240A0F8DC3L, + 0xF4F3E018F031D676L, 0xB60301F359DBE0E5L, + 0xDA050215EA6C212FL, 0x98F5E3FE438617BCL, + 0x5FE4C1C2B9B84C09L, 0x1D14202910527A9AL, + 0x93366450E42ECDF0L, 0xD1C685BB4DC4FB63L, + 0x16D7A787B7FAA0D6L, 0x5427466C1E109645L, + 0x4863CE9FF6E9F891L, 0x0A932F745F03CE02L, + 0xCD820D48A53D95B7L, 0x8F72ECA30CD7A324L, + 0x0150A8DAF8AB144EL, 0x43A04931514122DDL, + 0x84B16B0DAB7F7968L, 0xC6418AE602954FFBL, + 0xBC387AEA7A8DA4C0L, 0xFEC89B01D3679253L, + 0x39D9B93D2959C9E6L, 0x7B2958D680B3FF75L, + 0xF50B1CAF74CF481FL, 0xB7FBFD44DD257E8CL, + 0x70EADF78271B2539L, 0x321A3E938EF113AAL, + 0x2E5EB66066087D7EL, 0x6CAE578BCFE24BEDL, + 0xABBF75B735DC1058L, 0xE94F945C9C3626CBL, + 0x676DD025684A91A1L, 0x259D31CEC1A0A732L, + 0xE28C13F23B9EFC87L, 0xA07CF2199274CA14L, + 0x167FF3EACBAF2AF1L, 0x548F120162451C62L, + 0x939E303D987B47D7L, 0xD16ED1D631917144L, + 0x5F4C95AFC5EDC62EL, 0x1DBC74446C07F0BDL, + 0xDAAD56789639AB08L, 0x985DB7933FD39D9BL, + 0x84193F60D72AF34FL, 0xC6E9DE8B7EC0C5DCL, + 0x01F8FCB784FE9E69L, 0x43081D5C2D14A8FAL, + 0xCD2A5925D9681F90L, 0x8FDAB8CE70822903L, + 0x48CB9AF28ABC72B6L, 0x0A3B7B1923564425L, + 0x70428B155B4EAF1EL, 0x32B26AFEF2A4998DL, + 0xF5A348C2089AC238L, 0xB753A929A170F4ABL, + 0x3971ED50550C43C1L, 0x7B810CBBFCE67552L, + 0xBC902E8706D82EE7L, 0xFE60CF6CAF321874L, + 0xE224479F47CB76A0L, 0xA0D4A674EE214033L, + 0x67C58448141F1B86L, 0x253565A3BDF52D15L, + 0xAB1721DA49899A7FL, 0xE9E7C031E063ACECL, + 0x2EF6E20D1A5DF759L, 0x6C0603E6B3B7C1CAL, + 0xF6FAE5C07D3274CDL, 0xB40A042BD4D8425EL, + 0x731B26172EE619EBL, 0x31EBC7FC870C2F78L, + 0xBFC9838573709812L, 0xFD39626EDA9AAE81L, + 0x3A28405220A4F534L, 0x78D8A1B9894EC3A7L, + 0x649C294A61B7AD73L, 0x266CC8A1C85D9BE0L, + 0xE17DEA9D3263C055L, 0xA38D0B769B89F6C6L, + 0x2DAF4F0F6FF541ACL, 0x6F5FAEE4C61F773FL, + 0xA84E8CD83C212C8AL, 0xEABE6D3395CB1A19L, + 0x90C79D3FEDD3F122L, 0xD2377CD44439C7B1L, + 0x15265EE8BE079C04L, 0x57D6BF0317EDAA97L, + 0xD9F4FB7AE3911DFDL, 0x9B041A914A7B2B6EL, + 0x5C1538ADB04570DBL, 0x1EE5D94619AF4648L, + 0x02A151B5F156289CL, 0x4051B05E58BC1E0FL, + 0x87409262A28245BAL, 0xC5B073890B687329L, + 0x4B9237F0FF14C443L, 0x0962D61B56FEF2D0L, + 0xCE73F427ACC0A965L, 0x8C8315CC052A9FF6L, + 0x3A80143F5CF17F13L, 0x7870F5D4F51B4980L, + 0xBF61D7E80F251235L, 0xFD913603A6CF24A6L, + 0x73B3727A52B393CCL, 0x31439391FB59A55FL, + 0xF652B1AD0167FEEAL, 0xB4A25046A88DC879L, + 0xA8E6D8B54074A6ADL, 0xEA16395EE99E903EL, + 0x2D071B6213A0CB8BL, 0x6FF7FA89BA4AFD18L, + 0xE1D5BEF04E364A72L, 0xA3255F1BE7DC7CE1L, + 0x64347D271DE22754L, 0x26C49CCCB40811C7L, + 0x5CBD6CC0CC10FAFCL, 0x1E4D8D2B65FACC6FL, + 0xD95CAF179FC497DAL, 0x9BAC4EFC362EA149L, + 0x158E0A85C2521623L, 0x577EEB6E6BB820B0L, + 0x906FC95291867B05L, 0xD29F28B9386C4D96L, + 0xCEDBA04AD0952342L, 0x8C2B41A1797F15D1L, + 0x4B3A639D83414E64L, 0x09CA82762AAB78F7L, + 0x87E8C60FDED7CF9DL, 0xC51827E4773DF90EL, + 0x020905D88D03A2BBL, 0x40F9E43324E99428L, + 0x2CFFE7D5975E55E2L, 0x6E0F063E3EB46371L, + 0xA91E2402C48A38C4L, 0xEBEEC5E96D600E57L, + 0x65CC8190991CB93DL, 0x273C607B30F68FAEL, + 0xE02D4247CAC8D41BL, 0xA2DDA3AC6322E288L, + 0xBE992B5F8BDB8C5CL, 0xFC69CAB42231BACFL, + 0x3B78E888D80FE17AL, 0x7988096371E5D7E9L, + 0xF7AA4D1A85996083L, 0xB55AACF12C735610L, + 0x724B8ECDD64D0DA5L, 0x30BB6F267FA73B36L, + 0x4AC29F2A07BFD00DL, 0x08327EC1AE55E69EL, + 0xCF235CFD546BBD2BL, 0x8DD3BD16FD818BB8L, + 0x03F1F96F09FD3CD2L, 0x41011884A0170A41L, + 0x86103AB85A2951F4L, 0xC4E0DB53F3C36767L, + 0xD8A453A01B3A09B3L, 0x9A54B24BB2D03F20L, + 0x5D45907748EE6495L, 0x1FB5719CE1045206L, + 0x919735E51578E56CL, 0xD367D40EBC92D3FFL, + 0x1476F63246AC884AL, 0x568617D9EF46BED9L, + 0xE085162AB69D5E3CL, 0xA275F7C11F7768AFL, + 0x6564D5FDE549331AL, 0x279434164CA30589L, + 0xA9B6706FB8DFB2E3L, 0xEB46918411358470L, + 0x2C57B3B8EB0BDFC5L, 0x6EA7525342E1E956L, + 0x72E3DAA0AA188782L, 0x30133B4B03F2B111L, + 0xF7021977F9CCEAA4L, 0xB5F2F89C5026DC37L, + 0x3BD0BCE5A45A6B5DL, 0x79205D0E0DB05DCEL, + 0xBE317F32F78E067BL, 0xFCC19ED95E6430E8L, + 0x86B86ED5267CDBD3L, 0xC4488F3E8F96ED40L, + 0x0359AD0275A8B6F5L, 0x41A94CE9DC428066L, + 0xCF8B0890283E370CL, 0x8D7BE97B81D4019FL, + 0x4A6ACB477BEA5A2AL, 0x089A2AACD2006CB9L, + 0x14DEA25F3AF9026DL, 0x562E43B4931334FEL, + 0x913F6188692D6F4BL, 0xD3CF8063C0C759D8L, + 0x5DEDC41A34BBEEB2L, 0x1F1D25F19D51D821L, + 0xD80C07CD676F8394L, 0x9AFCE626CE85B507L}; + private long crc = 0; + + @Override + public void update(final int b) { + update((byte) (b & 0xFF)); + } + + public void update(final byte b) { + final int tab_index = ((int) (this.crc >> 56) ^ b) & 0xFF; + this.crc = CRC_TABLE[tab_index] ^ (this.crc << 8); + } + + @Override + public void update(final byte[] buffer, final int offset, int length) { + for (int i = offset; length > 0; length--) { + update(buffer[i++]); + } + } + + @Override + public void update(final byte[] buffer) { + for (int i = 0; i < buffer.length; i++) { + update(buffer[i]); + } + } + + @Override + public long getValue() { + return this.crc; + } + + @Override + public void reset() { + this.crc = 0; + } + +} diff --git a/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/util/ZipUtils.java b/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/util/ZipUtils.java new file mode 100644 index 000000000..b76484329 --- /dev/null +++ b/hugegraph-store/hg-store-rocksdb/src/main/java/org/apache/hugegraph/rocksdb/access/util/ZipUtils.java @@ -0,0 +1,129 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.rocksdb.access.util; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.zip.CheckedInputStream; +import java.util.zip.CheckedOutputStream; +import java.util.zip.Checksum; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.output.NullOutputStream; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public final class ZipUtils { + // /** + // * create a direcotry if not exist + // * + // * @param dir + // */ + // public static void createDirectory(String dir) throws IOException { + // Path path = Paths.get(dir); + // if (!Files.exists(path)) { + // Files.createDirectories(path); + // } + // } + + // /** + // * delete the directory recursively with all its files and subdirectories + // * + // * @param dir + // */ + // public static void deleteDirectory(String dir) throws IOException { + // Path path = Paths.get(dir); + // if (Files.exists(path)) { + // Files.walk(path).sorted(Comparator.reverseOrder()).map(Path::toFile) + // .forEach(File::delete); + // } + // } + + // /** + // * move a directory form source path to target path target will be deleted if exist + // * + // * @param source + // * @param target + // * @throws IOException + // */ + // public static void moveDirectory(String source, String target) throws IOException { + // ZipUtils.deleteDirectory(target); + // Files.move(Paths.get(source), Paths.get(target), StandardCopyOption.REPLACE_EXISTING); + // } + + public static void compress(final String rootDir, final String sourceDir, + final String outputFile, final Checksum checksum) throws + IOException { + try (final FileOutputStream fos = new FileOutputStream(outputFile); + final CheckedOutputStream cos = new CheckedOutputStream(fos, checksum); + final ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(cos))) { + ZipUtils.compressDirectoryToZipFile(rootDir, sourceDir, zos); + zos.flush(); + fos.getFD().sync(); + } + } + + private static void compressDirectoryToZipFile(final String rootDir, final String sourceDir, + final ZipOutputStream zos) throws IOException { + final String dir = Paths.get(rootDir, sourceDir).toString(); + final File[] files = new File(dir).listFiles(); + for (final File file : files) { + final String child = Paths.get(sourceDir, file.getName()).toString(); + if (file.isDirectory()) { + compressDirectoryToZipFile(rootDir, child, zos); + } else { + zos.putNextEntry(new ZipEntry(child)); + try (final FileInputStream fis = new FileInputStream(file); + final BufferedInputStream bis = new BufferedInputStream(fis)) { + IOUtils.copy(bis, zos); + } + } + } + } + + public static void decompress(final String sourceFile, final String outputDir, + final Checksum checksum) throws IOException { + try (final FileInputStream fis = new FileInputStream(sourceFile); + final CheckedInputStream cis = new CheckedInputStream(fis, checksum); + final ZipInputStream zis = new ZipInputStream(new BufferedInputStream(cis))) { + ZipEntry entry; + while ((entry = zis.getNextEntry()) != null) { + final String fileName = entry.getName(); + final File entryFile = new File(Paths.get(outputDir, fileName).toString()); + FileUtils.forceMkdir(entryFile.getParentFile()); + try (final FileOutputStream fos = new FileOutputStream(entryFile); + final BufferedOutputStream bos = new BufferedOutputStream(fos)) { + IOUtils.copy(zis, bos); + bos.flush(); + fos.getFD().sync(); + } + } + IOUtils.copy(cis, NullOutputStream.NULL_OUTPUT_STREAM); + } + } +} diff --git a/hugegraph-store/hg-store-rocksdb/src/main/java/place-holder.txt b/hugegraph-store/hg-store-rocksdb/src/main/java/place-holder.txt new file mode 100644 index 000000000..e69de29bb diff --git a/hugegraph-store/hg-store-rocksdb/src/test/java/org/apache/hugegraph/rocksdb/access/RocksDBFactoryTest.java b/hugegraph-store/hg-store-rocksdb/src/test/java/org/apache/hugegraph/rocksdb/access/RocksDBFactoryTest.java new file mode 100644 index 000000000..a6f10a67f --- /dev/null +++ b/hugegraph-store/hg-store-rocksdb/src/test/java/org/apache/hugegraph/rocksdb/access/RocksDBFactoryTest.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.rocksdb.access; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.configuration2.MapConfiguration; +import org.apache.hugegraph.config.HugeConfig; +import org.apache.hugegraph.config.OptionSpace; +import org.junit.BeforeClass; + +public class RocksDBFactoryTest { + + @BeforeClass + public static void init() { + OptionSpace.register("rocksdb", + "org.apache.hugegraph.rocksdb.access.RocksDBOptions"); + RocksDBOptions.instance(); + + Map configMap = new HashMap<>(); + configMap.put("rocksdb.write_buffer_size", "1048576"); + configMap.put("rocksdb.bloom_filter_bits_per_key", "10"); + + HugeConfig hConfig = new HugeConfig(new MapConfiguration(configMap)); + RocksDBFactory rFactory = RocksDBFactory.getInstance(); + rFactory.setHugeConfig(hConfig); + } + + // @Test + public void testCreateSession() throws InterruptedException { + RocksDBFactory factory = RocksDBFactory.getInstance(); + try (RocksDBSession dbSession = factory.createGraphDB("./tmp", "test1")) { + SessionOperator op = dbSession.sessionOp(); + op.prepare(); + try { + op.put("tbl", "k1".getBytes(), "v1".getBytes()); + op.commit(); + } catch (Exception e) { + op.rollback(); + } + + } + factory.destroyGraphDB("test1"); + + Thread.sleep(100000); + } + + // @Test + public void testTotalKeys() { + RocksDBFactory dbFactory = RocksDBFactory.getInstance(); + System.out.println(dbFactory.getTotalSize()); + + System.out.println(dbFactory.getTotalKey().entrySet() + .stream().map(e -> e.getValue()).reduce(0L, Long::sum)); + } + + // @Test + public void releaseAllGraphDB() { + System.out.println(RocksDBFactory.class); + + RocksDBFactory rFactory = RocksDBFactory.getInstance(); + + if (rFactory.queryGraphDB("bj01") == null) { + rFactory.createGraphDB("./tmp", "bj01"); + } + + if (rFactory.queryGraphDB("bj02") == null) { + rFactory.createGraphDB("./tmp", "bj02"); + } + + if (rFactory.queryGraphDB("bj03") == null) { + rFactory.createGraphDB("./tmp", "bj03"); + } + + RocksDBSession dbSession = rFactory.queryGraphDB("bj01"); + + dbSession.checkTable("test"); + SessionOperator sessionOp = dbSession.sessionOp(); + sessionOp.prepare(); + + sessionOp.put("test", "hi".getBytes(), "byebye".getBytes()); + sessionOp.commit(); + + rFactory.releaseAllGraphDB(); + } +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-rocksdb/src/test/java/org/apache/hugegraph/rocksdb/access/RocksDBSessionTest.java b/hugegraph-store/hg-store-rocksdb/src/test/java/org/apache/hugegraph/rocksdb/access/RocksDBSessionTest.java new file mode 100644 index 000000000..30e722b2f --- /dev/null +++ b/hugegraph-store/hg-store-rocksdb/src/test/java/org/apache/hugegraph/rocksdb/access/RocksDBSessionTest.java @@ -0,0 +1,480 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.rocksdb.access; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.configuration2.MapConfiguration; +import org.apache.hugegraph.config.HugeConfig; +import org.apache.hugegraph.config.OptionSpace; +import org.junit.BeforeClass; + +public class RocksDBSessionTest { + + private final String graphName = "testDummy"; + + @BeforeClass + public static void init() { + OptionSpace.register("rocksdb", + "org.apache.hugegraph.rocksdb.access.RocksDBOptions"); + RocksDBOptions.instance(); + Map configMap = new HashMap<>(); + configMap.put("rocksdb.write_buffer_size", "1048576"); + configMap.put("rocksdb.bloom_filter_bits_per_key", "10"); + + HugeConfig hConfig = new HugeConfig(new MapConfiguration(configMap)); + RocksDBFactory rFactory = RocksDBFactory.getInstance(); + rFactory.setHugeConfig(hConfig); + } + + private static byte[] intToBytesForPartId(int v) { + short s = (short) v; + ByteBuffer buffer = ByteBuffer.allocate(Short.BYTES).order(ByteOrder.BIG_ENDIAN); + buffer.putShort(s); + return buffer.array(); + } + + public static byte[] byteCompose(byte[] b1, byte[] b2) { + byte[] b3 = new byte[b1.length + b2.length]; + System.arraycopy(b1, 0, b3, 0, b1.length); + System.arraycopy(b2, 0, b3, b1.length, b2.length); + return b3; + } + + private static byte[] keyAppendPartId(int partId, byte[] key) { + byte[] partBytes = intToBytesForPartId(partId); + byte[] targetKey = byteCompose(partBytes, key); + return targetKey; + } + + private static byte[] getPartStartKey(int partId) { + return intToBytesForPartId(partId); + } + + private static byte[] getPartEndKey(int partId) { + return intToBytesForPartId(partId + 1); + } + + // @Test + public void put() { + System.out.println("RocksDBSessionTest::put test"); + + RocksDBFactory rFactory = RocksDBFactory.getInstance(); + + RocksDBSession rSession = rFactory.createGraphDB("./tmp", graphName); + + String tName = "t1"; + rSession.checkTable(tName); + + SessionOperator sessionOp = rSession.sessionOp(); + sessionOp.put(tName, "a1".getBytes(), "f1".getBytes()); + sessionOp.commit(); + + String vRet = new String(rSession.sessionOp().get(tName, "a1".getBytes())); + assertEquals(vRet, "f1"); + } + + // @Test + public void selectTable() { + + System.out.println("RocksDBSessionTest::selectTable test"); + + RocksDBFactory rFactory = RocksDBFactory.getInstance(); + + RocksDBSession rSession = rFactory.createGraphDB("./tmp", graphName); + + String tName = "t2"; + rSession.checkTable(tName); + + assertTrue(rSession.tableIsExist(tName)); + + } + + // @Test + public void batchPut() { + System.out.println("RocksDBSessionTest::batchPut test"); + + RocksDBFactory rFactory = RocksDBFactory.getInstance(); + + RocksDBSession rSession = rFactory.createGraphDB("./tmp", graphName); + + String tName = "t3"; + String tName2 = "t4"; + + SessionOperator sessionOp = rSession.sessionOp(); + sessionOp.put(tName, "a1".getBytes(), "f1".getBytes()); + sessionOp.put(tName, "a2".getBytes(), "f2".getBytes()); + sessionOp.put(tName, "a3".getBytes(), "f3".getBytes()); + sessionOp.put(tName, "a4".getBytes(), "f4".getBytes()); + sessionOp.put(tName, "a5".getBytes(), "f5".getBytes()); + sessionOp.put(tName, "a6".getBytes(), "f6".getBytes()); + sessionOp.commit(); + + + sessionOp.put(tName2, "m1".getBytes(), "k1".getBytes()); + sessionOp.put(tName2, "m2".getBytes(), "k2".getBytes()); + sessionOp.put(tName2, "m3".getBytes(), "k3".getBytes()); + sessionOp.put(tName, "a7".getBytes(), "f7".getBytes()); + sessionOp.commit(); + + + String vRet1 = new String(rSession.sessionOp().get(tName, "a1".getBytes())); + String vRet2 = new String(rSession.sessionOp().get(tName2, "m1".getBytes())); + String vRet3 = new String(rSession.sessionOp().get(tName, "a7".getBytes())); + + assertEquals(vRet1, "f1"); + assertEquals(vRet2, "k1"); + assertEquals(vRet3, "f7"); + + } + + // @Test + public void get() { + System.out.println("RocksDBSessionTest::get test"); + + RocksDBFactory rFactory = RocksDBFactory.getInstance(); + + RocksDBSession rSession = rFactory.createGraphDB("./tmp", graphName); + + String tName = "t5"; + rSession.checkTable(tName); + SessionOperator sessionOp = rSession.sessionOp(); + sessionOp.put(tName, "hash".getBytes(), "youareok".getBytes()); + sessionOp.commit(); + + String vRet = new String(rSession.sessionOp().get(tName, "hash".getBytes())); + assertEquals(vRet, "youareok"); + } + + // @Test + public void createTables() { + System.out.println("RocksDBSessionTest::createTables test"); + + RocksDBFactory rFactory = RocksDBFactory.getInstance(); + + RocksDBSession rSession = rFactory.createGraphDB("./tmp", graphName); + + rSession.createTables("fly1", "fly2", "fly3"); + + SessionOperator sessionOp = rSession.sessionOp(); + sessionOp.put("fly1", "cat1".getBytes(), "hit1".getBytes()); + sessionOp.put("fly2", "cat2".getBytes(), "hit2".getBytes()); + sessionOp.put("fly3", "cat3".getBytes(), "hit3".getBytes()); + sessionOp.commit(); + + assertTrue(rSession.tableIsExist("fly1")); + assertTrue(rSession.tableIsExist("fly2")); + assertTrue(rSession.tableIsExist("fly3")); + + } + + // @Test + public void dropTables() { + System.out.println("RocksDBSessionTest::dropTables test"); + + RocksDBFactory rFactory = RocksDBFactory.getInstance(); + + RocksDBSession rSession = rFactory.createGraphDB("./tmp", graphName); + + rSession.createTables("dummy1"); + + assertTrue(rSession.tableIsExist("dummy1")); + + rSession.dropTables("dummy1"); + + assertFalse(rSession.tableIsExist("dummy1")); + + } + + // @Test + public void scan() { + System.out.println("RocksDBSessionTest::scan test"); + + RocksDBFactory rFactory = RocksDBFactory.getInstance(); + String graph1 = "tBatch"; + rFactory.createGraphDB("./tmp", graphName); + RocksDBSession rSession = rFactory.createGraphDB("./tmp", graph1); + + String tName = "t6"; + rSession.checkTable(tName); + + SessionOperator sessionOp = rSession.sessionOp(); + sessionOp.put(tName, "box1".getBytes(), "gift1".getBytes()); + sessionOp.put(tName, "box2".getBytes(), "gift2".getBytes()); + sessionOp.put(tName, "box3".getBytes(), "gift3".getBytes()); + sessionOp.put(tName, "box4".getBytes(), "gift4".getBytes()); + sessionOp.put(tName, "box5".getBytes(), "gift5".getBytes()); + sessionOp.put(tName, "box6".getBytes(), "gift6".getBytes()); + sessionOp.put(tName, "box7".getBytes(), "gift7".getBytes()); + sessionOp.put(tName, "box8".getBytes(), "gift8".getBytes()); + sessionOp.commit(); + + //scan table + ScanIterator it = sessionOp.scan(tName); + while (it.hasNext()) { + RocksDBSession.BackendColumn col = it.next(); + System.out.println(new String(col.name) + " : " + new String(col.value)); + } + + long c = rSession.getApproximateDataSize(); + System.out.println(c); + + long c1 = RocksDBFactory.getInstance().getTotalSize(); + System.out.println(c1); + } + + // @Test + public void testScan() { + System.out.println("RocksDBSessionTest::testScan test"); + + RocksDBFactory rFactory = RocksDBFactory.getInstance(); + + RocksDBSession rSession = rFactory.createGraphDB("./tmp", graphName); + + String tName = "t7"; + rSession.checkTable(tName); + + SessionOperator sessionOp = rSession.sessionOp(); + sessionOp.put(tName, "box1".getBytes(), "gift1".getBytes()); + sessionOp.put(tName, "box2".getBytes(), "gift2".getBytes()); + sessionOp.put(tName, "box3".getBytes(), "gift3".getBytes()); + sessionOp.put(tName, "room1".getBytes(), "killer1".getBytes()); + sessionOp.put(tName, "box4".getBytes(), "gift4".getBytes()); + sessionOp.put(tName, "box5".getBytes(), "gift5".getBytes()); + sessionOp.put(tName, "room2".getBytes(), "killer2".getBytes()); + sessionOp.put(tName, "box6".getBytes(), "gift6".getBytes()); + sessionOp.put(tName, "boat1".getBytes(), "girl1".getBytes()); + sessionOp.put(tName, "box7".getBytes(), "gift7".getBytes()); + sessionOp.put(tName, "box8".getBytes(), "gift8".getBytes()); + sessionOp.commit(); + + //prefix scan + ScanIterator it = sessionOp.scan(tName, "bo".getBytes()); + while (it.hasNext()) { + RocksDBSession.BackendColumn col = it.next(); + System.out.println(new String(col.name) + " : " + new String(col.value)); + } + + } + + // @Test + public void testScan1() { + System.out.println("RocksDBSessionTest::testScan1 test"); + + RocksDBFactory rFactory = RocksDBFactory.getInstance(); + + RocksDBSession rSession = rFactory.createGraphDB("./tmp", graphName); + + String tName = "t8"; + rSession.checkTable(tName); + + SessionOperator sessionOp = rSession.sessionOp(); + sessionOp.put(tName, "box1".getBytes(), "gift1".getBytes()); + sessionOp.put(tName, "box2".getBytes(), "gift2".getBytes()); + sessionOp.put(tName, "box3".getBytes(), "gift3".getBytes()); + sessionOp.put(tName, "room1".getBytes(), "killer1".getBytes()); + sessionOp.put(tName, "box4".getBytes(), "gift4".getBytes()); + sessionOp.put(tName, "box5".getBytes(), "gift5".getBytes()); + sessionOp.put(tName, "room2".getBytes(), "killer2".getBytes()); + sessionOp.put(tName, "box6".getBytes(), "gift6".getBytes()); + sessionOp.put(tName, "boat1".getBytes(), "girl1".getBytes()); + sessionOp.put(tName, "box7".getBytes(), "gift7".getBytes()); + sessionOp.put(tName, "box8".getBytes(), "gift8".getBytes()); + sessionOp.commit(); + + //range scan + ScanIterator it = sessionOp.scan(tName, "box2".getBytes(), "box5".getBytes(), + ScanIterator.Trait.SCAN_GTE_BEGIN | + ScanIterator.Trait.SCAN_LTE_END); + while (it.hasNext()) { + RocksDBSession.BackendColumn col = it.next(); + System.out.println(new String(col.name) + " : " + new String(col.value)); + } + + } + + // @Test + public void testCount() { + System.out.println("RocksDBSessionTest::testCount test"); + RocksDBFactory rFactory = RocksDBFactory.getInstance(); + RocksDBSession rSession = rFactory.createGraphDB("./tmp", graphName); + String tName = "c1"; + rSession.checkTable(tName); + + int part1 = 1; + int part2 = 2; + int part3 = 3; + SessionOperator sessionOp = rSession.sessionOp(); + sessionOp.prepare(); + sessionOp.put(tName, keyAppendPartId(part1, "k1".getBytes()), "v1".getBytes()); + sessionOp.put(tName, keyAppendPartId(part1, "k2".getBytes()), "v2".getBytes()); + sessionOp.put(tName, keyAppendPartId(part1, "k3".getBytes()), "v3".getBytes()); + sessionOp.put(tName, intToBytesForPartId(part2), "v".getBytes()); + sessionOp.put(tName, keyAppendPartId(part2, "k4".getBytes()), "v4".getBytes()); + sessionOp.put(tName, keyAppendPartId(part2, "k5".getBytes()), "v5".getBytes()); + sessionOp.put(tName, keyAppendPartId(part2, "k6".getBytes()), "v6".getBytes()); + sessionOp.put(tName, intToBytesForPartId(part3), "v".getBytes()); + sessionOp.put(tName, keyAppendPartId(part3, "k8".getBytes()), "v8".getBytes()); + sessionOp.put(tName, keyAppendPartId(part3, "k9".getBytes()), "v9".getBytes()); + sessionOp.put(tName, keyAppendPartId(part3, "k10".getBytes()), "v10".getBytes()); + sessionOp.put(tName, keyAppendPartId(part3, "k11".getBytes()), "v11".getBytes()); + sessionOp.commit(); + + +// assertEquals(3, rSession.sessionOp().keyCount(tName, getPartStartKey(part1), +// getPartEndKey(part1))); +// assertEquals(4, rSession.sessionOp().keyCount(tName, getPartStartKey(part2), +// getPartEndKey(part2))); +// assertEquals(5, rSession.sessionOp().keyCount(tName, getPartStartKey(part3), +// getPartEndKey(part3))); + + assertEquals(12, rSession.sessionOp().keyCount("\0".getBytes(StandardCharsets.UTF_8), + "\255".getBytes(StandardCharsets.UTF_8), + tName)); + assertEquals(12, rSession.sessionOp().estimatedKeyCount(tName)); + } + + // @Test + public void merge() { + System.out.println("RocksDBSessionTest::merge test"); + + RocksDBFactory rFactory = RocksDBFactory.getInstance(); + + RocksDBSession rSession = rFactory.createGraphDB("./tmp", graphName); + + String tName = "room1"; + rSession.checkTable(tName); + byte[] x2 = longToByteArray(19); + System.out.println(x2); + SessionOperator sessionOp = rSession.sessionOp(); + byte[] x1 = sessionOp.get(tName, "p3".getBytes()); + String str1 = new String(x1); + if (x1 != null) { + long curValue = longFromByteArray(x1); + System.out.println("current value:" + curValue); + sessionOp.merge(tName, "p3".getBytes(), longToByteArray(10)); + sessionOp.commit(); + } + + + byte[] value = rSession.sessionOp().get(tName, "p3".getBytes()); + final long longValue = longFromByteArray(value); + System.out.println("after merge value: " + longValue); + + sessionOp.put(tName, "p3".getBytes(), "19".getBytes()); + sessionOp.commit(); + + ScanIterator it = sessionOp.scan(tName, "p".getBytes()); + System.out.println("after put ------>"); + while (it.hasNext()) { + RocksDBSession.BackendColumn col = it.next(); + System.out.println(new String(col.name) + ":" + new String(col.value)); + } + + } + + // @Test + public void truncate() { + System.out.println("RocksDBSessionTest::truncate test"); + RocksDBFactory rFactory = RocksDBFactory.getInstance(); + RocksDBSession rSession = rFactory.createGraphDB("./tmp", "gg1"); + rSession.checkTable("f1"); + SessionOperator op = rSession.sessionOp(); + op.put("f1", "m1".getBytes(), "n1".getBytes()); + op.put("f1", "m2".getBytes(), "n2".getBytes()); + op.put("f1", "m3".getBytes(), "n3".getBytes()); + op.commit(); + rSession.checkTable("f2"); + + RocksDBSession rocksDBSession2 = rFactory.createGraphDB("./tmp", "gg2"); + rocksDBSession2.checkTable("txt"); + + rSession.truncate(); + op = rSession.sessionOp(); + op.put("f1", "beijing".getBytes(), "renkou".getBytes()); + op.commit(); + + rFactory.releaseAllGraphDB(); + } + + // @Test + public void batchPut2() { + RocksDBFactory rFactory = RocksDBFactory.getInstance(); + RocksDBSession rSession = rFactory.createGraphDB("./tmp", "gg1"); + rSession.checkTable("f1"); + SessionOperator op = rSession.sessionOp(); + try { + op.put("f1", "m2".getBytes(), "xx2".getBytes()); + op.put("f1", "m1".getBytes(), "xx1".getBytes()); + op.put("f1", "m3".getBytes(), "xx3".getBytes()); + op.put("f1", "m4".getBytes(), "xx5".getBytes()); + op.commit(); + op.deleteRange("f1", "m5".getBytes(), "m2".getBytes()); + + op.put("f2", new byte[]{1, 1}, new byte[]{1, -3}); + op.put("f2", new byte[]{1, -2}, new byte[]{2, 0}); + op.put("f2", new byte[]{1, 10}, new byte[]{1, 5}); + op.put("f2", new byte[]{1, 9}, new byte[]{1, 3}); + op.commit(); + + + op.deleteRange("f2", new byte[]{1, 1}, new byte[]{1, -1}); + op.commit(); + + ScanIterator it = op.scan("f2"); + while (it.hasNext()) { + RocksDBSession.BackendColumn col = it.next(); + System.out.println(Arrays.toString(col.name) + "=>" + Arrays.toString(col.value)); + } + + } catch (DBStoreException e) { + System.out.println(e); + } + + } + + // @Test + public void doStuff() { + byte[] stuff = new byte[]{1, 3, 1, -3}; + System.out.println(Arrays.toString(stuff)); + } + + private byte[] longToByteArray(long l) { + ByteBuffer buf = ByteBuffer.allocate(Long.BYTES).order(ByteOrder.LITTLE_ENDIAN); + buf.putLong(l); + return buf.array(); + } + + private long longFromByteArray(byte[] a) { + ByteBuffer buf = ByteBuffer.allocate(Long.BYTES).order(ByteOrder.LITTLE_ENDIAN); + buf.put(a); + buf.flip(); + return buf.getLong(); + } + + +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-rocksdb/src/test/java/org/apache/hugegraph/rocksdb/access/RocksdbDump.java b/hugegraph-store/hg-store-rocksdb/src/test/java/org/apache/hugegraph/rocksdb/access/RocksdbDump.java new file mode 100644 index 000000000..ab324da84 --- /dev/null +++ b/hugegraph-store/hg-store-rocksdb/src/test/java/org/apache/hugegraph/rocksdb/access/RocksdbDump.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.rocksdb.access; + +import java.util.ArrayList; +import java.util.List; + +import org.rocksdb.ColumnFamilyDescriptor; +import org.rocksdb.ColumnFamilyHandle; +import org.rocksdb.ColumnFamilyOptions; +import org.rocksdb.DBOptions; +import org.rocksdb.Options; +import org.rocksdb.ReadOptions; +import org.rocksdb.RocksDB; +import org.rocksdb.RocksDBException; +import org.rocksdb.RocksIterator; + +public class RocksdbDump { + + // @Test + public void dump() throws RocksDBException { + String dbPath = + "D:\\Workspaces\\baidu\\hugegraph\\hugegraph-store\\tmp\\8500\\db\\default" + + "\\hugegraph\\g"; + List cfDescriptors = new ArrayList<>(); + List columnFamilyBytes = RocksDB.listColumnFamilies(new Options(), dbPath); + ColumnFamilyOptions cfOptions = new ColumnFamilyOptions(); + if (columnFamilyBytes.size() > 0) { + for (byte[] columnFamilyByte : columnFamilyBytes) { + cfDescriptors.add(new ColumnFamilyDescriptor(columnFamilyByte, cfOptions)); + } + } + + + final List columnFamilyHandles = new ArrayList<>(); + try (final DBOptions options = new DBOptions(); + final RocksDB db = RocksDB.open(options, dbPath, cfDescriptors, columnFamilyHandles)) { + for (ColumnFamilyHandle handle : columnFamilyHandles) { + System.out.println(new String(handle.getName()) + "---------------"); + try (RocksIterator iterator = db.newIterator(handle, new ReadOptions())) { + iterator.seekToFirst(); + while (iterator.isValid()) { + byte[] key = iterator.key(); + // System.out.println(new String(key) + " -- " + Bytes.toHex(key)); + iterator.next(); + } + } + } + } + } +} diff --git a/hugegraph-store/hg-store-rocksdb/src/test/java/org/apache/hugegraph/rocksdb/access/SnapshotManagerTest.java b/hugegraph-store/hg-store-rocksdb/src/test/java/org/apache/hugegraph/rocksdb/access/SnapshotManagerTest.java new file mode 100644 index 000000000..77eb2d554 --- /dev/null +++ b/hugegraph-store/hg-store-rocksdb/src/test/java/org/apache/hugegraph/rocksdb/access/SnapshotManagerTest.java @@ -0,0 +1,431 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package org.apache.hugegraph.rocksdb.access; + +import java.io.File; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +import org.apache.hugegraph.store.term.HgPair; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class SnapshotManagerTest { + final String tempSuffix = "_temp_"; + final String graphName = "test_graph"; + + // @Test + public void testRetrieveIndex() throws DBStoreException { + String snapshotPath = + "D:\\Code\\baidu\\starhugegraph\\hugegraph-store\\tmp\\8501\\raft\\1/snapshot" + + "\\snapshot_40\\graph_1_p1"; + long lastIndex = 0L; + File file = new File(snapshotPath); + File parentFile = new File(file.getParent()); + String[] arr = parentFile.getName().split("_"); + if (arr.length >= 2) { + lastIndex = Long.parseLong(arr[arr.length - 1]); + } else { + throw new DBStoreException(String.format("Invalid Snapshot path %s", snapshotPath)); + } + + System.out.println(lastIndex); + } + + // @Test + public void testFoundMaxDir() { +// String path = "D:\\Code\\baidu\\starhugegraph\\hugegraph-store\\tmp\\8503\\db\\default +// \\hugegraph\\g\\0_123"; +// String path = "D:\\Code\\baidu\\starhugegraph\\hugegraph-store\\tmp\\8503\\db\\default +// \\hugegraph\\g\\0"; +// String path = "D:\\Code\\baidu\\starhugegraph\\hugegraph-store\\tmp\\8501\\db\\default +// \\hugegraph\\g\\0"; +// String path = "D:\\tmp\\db\\0"; +// String path = "D:\\tmp\\db\\0_111"; + String path = "D:\\tmp\\db\\0_111_22222"; + File file = new File(path); + int strIndex = file.getName().indexOf("_"); + + String defaultName; + if (strIndex < 0) { + defaultName = file.getName(); + } else { + defaultName = file.getName().substring(0, strIndex); + } + String prefix = defaultName + "_"; + File parentFile = new File(file.getParent()); + final List> dbs = new ArrayList<>(); + final File[] files = parentFile.listFiles(); + if (files != null) { + // search all db path + for (final File sFile : files) { + final String name = sFile.getName(); + if (!name.startsWith(prefix) && !name.equals(defaultName)) { + continue; + } + if (name.endsWith(tempSuffix)) { + continue; + } + long v1 = -1L; + long v2 = -1L; + if (name.length() > defaultName.length()) { + String[] versions = name.substring(prefix.length()).split("_"); + if (versions.length == 1) { + v1 = Long.parseLong(versions[0]); + } else if (versions.length == 2) { + v1 = Long.parseLong(versions[0]); + v2 = Long.parseLong(versions[1]); + } else { + continue; + } + } + dbs.add(new HgPair<>(v1, v2)); + } + } + + // get last index db path + String latestDBPath = ""; + if (!dbs.isEmpty()) { + dbs.sort((o1, o2) -> o1.getKey().equals(o2.getKey()) ? + o1.getValue().compareTo(o2.getValue()) : + o1.getKey().compareTo(o2.getKey())); + final int dbCount = dbs.size(); + + // delete old db + for (int i = 0; i < dbCount; i++) { + final HgPair pair = dbs.get(i); + String curDBName; + if (pair.getKey() == -1L) { + curDBName = defaultName; + } else if (pair.getValue() == -1L) { + curDBName = String.format("%s_%d", defaultName, pair.getKey()); + } else { + curDBName = + String.format("%s_%d_%d", defaultName, pair.getKey(), pair.getValue()); + } + String curDBPath = Paths.get(parentFile.getPath(), curDBName).toString(); + if (i == dbCount - 1) { + latestDBPath = curDBPath; + } else { + log.info("delete old dbpath {}", curDBPath); + } + } + + } else { + latestDBPath = Paths.get(parentFile.getPath(), defaultName).toString(); + } + log.info("{} latest db path {}", this.graphName, latestDBPath); + + + } + + // @Test + public void testDefaultPath() { +// String latestDBPath = "D:\\Code\\baidu\\starhugegraph\\hugegraph-store\\tmp\\8501\\db +// \\default\\hugegraph\\g\\0_123"; + String latestDBPath = + "D:\\Code\\baidu\\starhugegraph\\hugegraph-store\\tmp\\8501\\db\\default" + + "\\hugegraph\\g\\0"; + File file = new File(latestDBPath); + String parent = file.getParent(); + String defaultName = file.getName().split("_")[0]; + String defaultPath = Paths.get(parent, defaultName).toString(); + System.out.println(defaultPath); + } + + +// private static final String graphName = "unit-test-graph"; +// private static final String graphName2 = "unit-test-graph-2"; +// private static final int partition_1 = 1; +// private static final int partition_2 = 2; +// private static final int partition_3 = 3; +// private static final int partition_4 = 4; +// private static ArrayList tableList = new ArrayList(); +// private static ArrayList partitionList = new ArrayList(); +// private static RocksDBFactory factory = RocksDBFactory.getInstance(); +// private static HugeConfig hConfig; +// private static HugeConfig hConfig2; +// private int index = 0; +// +// private static byte[] values = new byte[1024]; +// +// +// // @BeforeClass +// public static void beforeClass() { +// // Register config +// OptionSpace.register("rocksdb", +// "org.apache.hugegraph.rocksdb.access.RocksDBOptions"); +// // Register backend +// URL configPath = SnapshotManagerTest.class.getResource("/hugegraph.properties"); +// URL configPath2 = SnapshotManagerTest.class.getResource("/hugegraph-2.properties"); +// hConfig = new HugeConfig(configPath.getPath()); +// hConfig2 = new HugeConfig(configPath2.getPath()); +// factory.setHugeConfig(hConfig); +// // init tables +// tableList.add("t1"); +// tableList.add("t2"); +// tableList.add("t3"); +// tableList.add("t4"); +// tableList.add("abc/t5"); +// tableList.add("../t6"); +// // init partitons +// partitionList.add(partition_1); +// partitionList.add(partition_2); +// partitionList.add(partition_3); +// partitionList.add(partition_4); +// +// for (int i = 0; i < 1024; i++) +// values[i] = (byte) (i % 0x7F); +// +// } +// +// @AfterClass +// public static void afterClass() { +// factory.releaseAllGraphDB(); +// } +// // @Test +// public void test(){ +// Map configMap = new HashMap<>(); +// configMap.put("rocksdb.data_path", "tmp/test2/huge/"); +// configMap.put("rocksdb.wal_path", "tmp/test2/wal"); +// configMap.put("rocksdb.snapshot_path", "tmp/test2/snapshot"); +// configMap.put("rocksdb.write_buffer_size","1048576"); +// HugeConfig config = new HugeConfig(configMap); +// factory.setHugeConfig(config); +// RocksDBSession session = factory.createGraphDB(graphName, false); +// SessionOperator sessionOp = session.sessionOp(); +// +// String table = "t1"; +// +// String startKey = String.format("%d_%08d", partition_1, 0); +// String lastKey = String.format("%d_%08d", partition_1, 99); +// String endKey = String.format("%d_%08d", partition_1, 100); +// +// +// printKV(sessionOp, table, startKey); +// printKV(sessionOp, table, lastKey); +// } +// +// private void printKV(SessionOperator sessionOp, String table, String key) { +// byte[] value = sessionOp.get(table, key.getBytes()); +// if (value != null) +// System.out.printf("table=%s, key=%s, value=%s\n", table, key, new String(value)); +// else +// System.out.printf("table=%s, key=%s, does not found\n", table, key); +// } +// +// private void insertData(int count) { +// RocksDBSession session = factory.createGraphDB(graphName, false); +// SessionOperator sessionOp = session.sessionOp(); +// sessionOp.prepare(); +// sessionOp.prepare(); +// final int begin = index; +// final int end = index + count; +// System.out.printf("begin to insert data from index %d to %d\n", begin, end); +// +// for (; index < end; index++) { +// for (String table : tableList) { +// for (Integer partition : partitionList) { +// String key = String.format("%d_%08d", partition, index); +// String value = String.format("%s_%08d", table, index); +// sessionOp.put(table, key.getBytes(), value.getBytes()); +// } +// } +// if (index % 1000 == 999) { +// System.out.printf("insertData, commit index %d\n", index+1); +// sessionOp.commit(); +// sessionOp.prepare(); +// } +// } +// sessionOp.commit(); +// session.flush(); +// System.out.printf("inserted %d pairs data\n", count); +// } +// +// // @Test +// public void testSnapshotMetadata() { +// DBSnapshotMeta metadata = new DBSnapshotMeta(); +// metadata.setGraphName(graphName); +// metadata.setPartitionId(partition_1); +// metadata.setStartKey("k1".getBytes()); +// metadata.setEndKey("k9".getBytes()); +// HashMap sstFiles = new HashMap(); +// sstFiles.put("cf1", "cf1.sst"); +// sstFiles.put("cf2", "cf2.sst"); +// sstFiles.put("cf3", "cf3.sst"); +// sstFiles.put("cf4", "cf4.sst"); +// metadata.setCreatedDate(new Date()); +// metadata.setSstFiles(sstFiles); +// +// System.out.println("metadata object:"); +// System.out.println(metadata.toString()); +// +// String jsonStr = JSON.toJSONString(metadata); +// System.out.println("metadata json:"); +// System.out.println(jsonStr); +// +// DBSnapshotMeta metadata2 = JSON.parseObject(jsonStr, DBSnapshotMeta.class); +// System.out.println("metadata2 object:"); +// System.out.println(metadata2.toString()); +// +// assertEquals(metadata, metadata2); +// } +// +// // @Test +// public void testCompress() throws IOException { +// final String rootDir = "./tmp/fileutils"; +// final File dir1 = Paths.get(rootDir, "dir1").toFile(); +// final File dir2 = Paths.get(rootDir, "dir2").toFile(); +// final File dir3 = Paths.get(rootDir, "dir3").toFile(); +// final File dir4 = Paths.get(rootDir, "dir4").toFile(); +// final File file0 = Paths.get(rootDir, "file0").toFile(); +// final File file1 = Paths.get(dir1.getAbsolutePath(), "file1").toFile(); +// FileUtils.forceMkdirParent(dir1); +// FileUtils.forceMkdirParent(dir2); +// FileUtils.forceMkdirParent(dir3); +// FileUtils.deleteDirectory(dir2); +// FileUtils.forceMkdirParent(dir4); +// FileUtils.moveDirectory(dir3, dir1); +// FileUtils.touch(file0); +// FileUtils.touch(file1); +// final Checksum c1 = new CRC64(); +// ZipUtils.compress("./tmp", "fileutils", "./tmp/fileutils.zip", c1); +// +// FileUtils.deleteDirectory(new File("./tmp/fileutils")); +// +// final Checksum c2 = new CRC64(); +// ZipUtils.decompress("./tmp/fileutils.zip", "./tmp", c2); +// Assert.assertEquals(c1.getValue(), c2.getValue()); +// } +// +// +// // @Test +// public void testDecompress() throws IOException { +// final Checksum checksum = new CRC64(); +// String dir = "./tmp/server-00/snapshot/test/import/1"; +// String source = Paths.get(dir, "snapshot.zip").toString(); +// FileUtils.deleteDirectory(Paths.get(dir, "snapshot").toFile()); +// ZipUtils.decompress(source, dir, checksum); +// } +// +// +// // @Test +// public void testExportSnapshot() throws DBStoreException, IOException { +// final String exportDir = hConfig.getString("rocksdb.snapshot_path"); +// File exportDirFile = new File(exportDir); +// if (exportDirFile.exists()) { +// FileUtils.forceDelete(new File(exportDir)); +// } +// FileUtils.forceMkdir(new File(exportDir)); +// +// RocksDBSession session = factory.createGraphDB(graphName, false); +// insertData(1000); +// String startKey = String.format("%d_%08d", partition_1, 0); +// String endKey = String.format("%d_%08d", partition_1, 100); +// session.exportSnapshot(exportDir, 1, startKey.getBytes(), endKey.getBytes()); +// } +// +// // @Test +// public void testImportSnapshot() throws DBStoreException, IOException { +// final String importDir = hConfig.getString("rocksdb.snapshot_path"); +// FileUtils.forceMkdir(new File(importDir)); +// +// factory.setHugeConfig(hConfig2); +// RocksDBSession session = factory.createGraphDB(graphName, false); +// SessionOperator sessionOp = session.sessionOp(); +// +// String table = "t1"; +// +// String startKey = String.format("%d_%08d", partition_1, 0); +// String lastKey = String.format("%d_%08d", partition_1, 99); +// String endKey = String.format("%d_%08d", partition_1, 100); +// +// session.importSnapshot(importDir, partition_1); +// long keyCount = session.sessionOp().keyCount(table, startKey.getBytes(), endKey +// .getBytes()); +// System.out.printf("key count of %s is %d\n", graphName, keyCount); +// +// +// System.out.printf("after load snapshot\n"); +// printKV(sessionOp, table, startKey); +// printKV(sessionOp, table, lastKey); +// } +// +// // @Test +// public void testExportSnapshot2() throws DBStoreException, IOException { +// final String exportDir = hConfig.getString("rocksdb.snapshot_path"); +// deleteDir(new File(hConfig.getString("rocksdb.data_path"))); +// deleteDir(new File(exportDir)); +// FileUtils.forceMkdir(new File(exportDir)); +// +// RocksDBSession session = factory.createGraphDB(graphName, false); +// +// long seqNum = session.getLatestSequenceNumber(); +// System.out.println(seqNum); +// insertData(500); +// System.out.println(session.getLatestSequenceNumber()); +// String startKey = String.format("%d_%08d", partition_1, index - 200); +// String endKey = String.format("%d_%08d", partition_1, index - 100); +// +// ScanIterator cfIterator = session.sessionOp().scanRaw(startKey.getBytes(), endKey +// .getBytes(), seqNum); +// while (cfIterator.hasNext()) { +// ScanIterator iterator = cfIterator.next(); +// System.out.println("cf name is " + new String(cfIterator.position())); +// while (iterator.hasNext()) { +// RocksDBSession.BackendColumn col = iterator.next(); +// System.out.println(new String(col.name)); +// } +// System.out.println("-----------------------------------------"); +// iterator.close(); +// } +// cfIterator.close(); +// } +// +// // @Test +// public void testImportSnapshot2() throws DBStoreException, IOException { +// final String importDir = "/tmp/snapshot"; +// deleteDir(new File("/tmp/rocksdb/data" + "/" + graphName2)); +// RocksDBSession session = factory.createGraphDB(graphName2, false); +// SessionOperator sessionOp = session.sessionOp(); +// +// +// session.flush(); +// +// String table = "t1"; +// +// String k1 = String.format("%d_%08d", partition_1, 100); +// String k2 = String.format("%d_%08d", partition_1, 109); +// String k3 = String.format("%d_%08d", partition_1, 110); +// +// printKV(sessionOp, table, k1); +// printKV(sessionOp, table, k2); +// printKV(sessionOp, table, k3); +// +// } +// +// +// private static boolean deleteDir(File dir) { +// if (dir.isDirectory()) +// for (File file : dir.listFiles()) +// deleteDir(file); +// return dir.delete(); +// } +} diff --git a/hugegraph-store/hg-store-rocksdb/src/test/java/place-holder.txt b/hugegraph-store/hg-store-rocksdb/src/test/java/place-holder.txt new file mode 100644 index 000000000..e69de29bb diff --git a/hugegraph-store/hg-store-rocksdb/src/test/resources/hugegraph-2.properties b/hugegraph-store/hg-store-rocksdb/src/test/resources/hugegraph-2.properties new file mode 100644 index 000000000..4b90d123e --- /dev/null +++ b/hugegraph-store/hg-store-rocksdb/src/test/resources/hugegraph-2.properties @@ -0,0 +1,31 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to You under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# rocksdb backend config +rocksdb.data_path=./tmp/rocksdb-2/data +rocksdb.wal_path=./tmp/rocksdb-2/data +rocksdb.snapshot_path=./tmp/rocksdb-2/snapshot +rocksdb.write_buffer_size=1048576 +rocksdb.min_write_buffer_number_to_merge=2 +rocksdb.max_write_buffer_number=4 +rocksdb.level0_file_num_compaction_trigger=2 +rocksdb.target_file_size_base=1048576 +rocksdb.target_file_size_multiplier=2 +rocksdb.max_bytes_for_level_base=1048576 +rocksdb.max_bytes_for_level_multiplier=2 + + + diff --git a/hugegraph-store/hg-store-rocksdb/src/test/resources/hugegraph.properties b/hugegraph-store/hg-store-rocksdb/src/test/resources/hugegraph.properties new file mode 100644 index 000000000..b2d37ad4f --- /dev/null +++ b/hugegraph-store/hg-store-rocksdb/src/test/resources/hugegraph.properties @@ -0,0 +1,31 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to You under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# rocksdb backend config +rocksdb.data_path=./tmp/rocksdb/data +rocksdb.wal_path=./tmp/rocksdb/data +rocksdb.snapshot_path=./tmp/rocksdb/snapshot +rocksdb.write_buffer_size=1048576 +rocksdb.min_write_buffer_number_to_merge=2 +rocksdb.max_write_buffer_number=4 +rocksdb.level0_file_num_compaction_trigger=2 +rocksdb.target_file_size_base=1048576 +rocksdb.target_file_size_multiplier=2 +rocksdb.max_bytes_for_level_base=1048576 +rocksdb.max_bytes_for_level_multiplier=2 + + + diff --git a/hugegraph-store/hg-store-rocksdb/src/test/resources/log4j2.xml b/hugegraph-store/hg-store-rocksdb/src/test/resources/log4j2.xml new file mode 100644 index 000000000..30f1ebd3d --- /dev/null +++ b/hugegraph-store/hg-store-rocksdb/src/test/resources/log4j2.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + diff --git a/hugegraph-store/hg-store-test/pom.xml b/hugegraph-store/hg-store-test/pom.xml new file mode 100644 index 000000000..94d2b4f75 --- /dev/null +++ b/hugegraph-store/hg-store-test/pom.xml @@ -0,0 +1,347 @@ + + + + + + hugegraph-store-root + org.apache.hugegraph + ${revision} + ../pom.xml + + 4.0.0 + + hg-store-test + + + + 2.15.0 + 1.18.20 + + + + + jacoco + + false + + + + + org.jacoco + jacoco-maven-plugin + 0.8.4 + + + + default + verify + + report-aggregate + + + ${project.basedir}/../target/site/jacoco + + + + + + + + + + + + + org.apache.hugegraph + hg-store-grpc + ${revision} + + + org.apache.hugegraph + hg-store-common + ${revision} + + + org.projectlombok + lombok + ${lombok.version} + + + org.apache.logging.log4j + log4j-slf4j-impl + ${log4j2.version} + + + org.apache.hugegraph + hg-pd-client + ${revision} + + + io.grpc + * + + + + + org.apache.hugegraph + hg-store-core + + + org.apache.hugegraph + hg-pd-grpc + ${revision} + + + commons-io + commons-io + 2.7 + + + com.fasterxml.jackson.core + jackson-databind + 2.13.0 + + + + junit + junit + 4.13.2 + + + com.google.protobuf + protobuf-java-util + 3.17.2 + compile + + + org.springframework + spring-context-support + + + org.springframework + spring-test + test + + + com.fasterxml.jackson.core + jackson-core + 2.13.0 + + + com.fasterxml.jackson.core + jackson-annotations + 2.13.0 + + + org.springframework.boot + spring-boot-starter-test + + + org.springframework.boot + spring-boot-starter-logging + + + + + org.powermock + powermock-classloading-xstream + 2.0.0-RC.3 + + + org.powermock + powermock-module-junit4-rule + 2.0.0-RC.3 + + + org.powermock + powermock-api-support + 2.0.0-RC.3 + + + org.powermock + powermock-module-junit4 + 2.0.0-RC.3 + compile + + + org.powermock + powermock-api-mockito2 + 2.0.0-RC.3 + compile + + + org.apache.hugegraph + hg-store-client + + + com.alipay.sofa + jraft-core + 1.3.9 + + + org.rocksdb + rocksdbjni + + + + + org.apache.hugegraph + hugegraph-store + ${revision} + compile + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.20 + + + client-test + + ${basedir}/src/main/java/ + + ${basedir}/target/classes/ + + + **/ClientSuiteTest.java + + + + + core-test + + ${basedir}/src/main/java/ + + ${basedir}/target/classes/ + + + **/CoreSuiteTest.java + + + + + common-test + + ${basedir}/src/main/java/ + + ${basedir}/target/classes/ + + + **/CommonSuiteTest.java + + + + + rocksdb-test + + ${basedir}/src/main/java/ + + ${basedir}/target/classes/ + + + **/RocksDbSuiteTest.java + + + + + server-test + + ${basedir}/src/main/java/ + + ${basedir}/target/classes/ + + + **/ServerSuiteTest.java + + + **/node/**/* + + + + + raftcore-test + + ${basedir}/src/main/java/ + + ${basedir}/target/classes/ + + + **/RaftSuiteTest.java + + + + + + + org.jacoco + jacoco-maven-plugin + 0.8.4 + + + pre-test + + + prepare-agent + + + + post-test + test + + report-aggregate + + + ${basedir}/target/site/jacoco + + + + + + **/grpc/**/* + **/jraft/**/* + **/*Iterator* + **/node/**/* + + + + + + + src/main/resources/ + true + + + + + + Baidu_Local + http://maven.baidu-int.com/nexus/content/repositories/Baidu_Local + + + Baidu_Local_Snapshots + http://maven.baidu-int.com/nexus/content/repositories/Baidu_Local_Snapshots + + + + \ No newline at end of file diff --git a/hugegraph-store/hg-store-test/src/main/java/client/BaseClientTest.java b/hugegraph-store/hg-store-test/src/main/java/client/BaseClientTest.java new file mode 100644 index 000000000..bf3fc127e --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/client/BaseClientTest.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package client; + +import org.apache.hugegraph.pd.client.PDClient; +import org.apache.hugegraph.pd.client.PDConfig; +import org.apache.hugegraph.store.HgStoreClient; +import org.junit.After; +import org.junit.BeforeClass; +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class BaseClientTest { + public static HgStoreClient storeClient; + + public static PDClient pdClient; + public static String tableName0 = "cli-table0"; + protected static String graphName = "testGraphName"; + protected static String tableName = "testTableName"; + + @BeforeClass + public static void beforeClass() { + storeClient = HgStoreClient.create(PDConfig.of("127.0.0.1:8686") + .setEnableCache(true)); + pdClient = storeClient.getPdClient(); + } + + @After + public void teardown() { + // pass + } +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-test/src/main/java/client/ChangeShardNumTest.java b/hugegraph-store/hg-store-test/src/main/java/client/ChangeShardNumTest.java new file mode 100644 index 000000000..5fbc5b255 --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/client/ChangeShardNumTest.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package client; + +import org.apache.hugegraph.pd.common.PDException; +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.store.HgKvEntry; +import org.apache.hugegraph.store.HgKvIterator; +import org.apache.hugegraph.store.HgStoreSession; +import org.junit.Assert; +import org.junit.Test; + +import util.HgStoreTestUtil; + +/** + * 测试修改副本数 + */ +public class ChangeShardNumTest extends BaseClientTest { + @Test + public void test3To1() throws PDException { + int number = 10000; + HgStoreSession session = storeClient.openSession(graphName); + HgStoreTestUtil.batchPut(session, tableName, "testKey", number); + + try (HgKvIterator iterators = session.scanIterator(tableName)) { +// Assert.assertEquals(number, HgStoreTestUtil.amountOf(iterators)); + } + + Metapb.PDConfig pdConfig = pdClient.getPDConfig(); + pdConfig = pdConfig.toBuilder().setShardCount(1).build(); + + pdClient.setPDConfig(pdConfig); + pdClient.balancePartition(); + } + + // @Test + public void test1To3() throws PDException { + int number = 10000; + HgStoreSession session = storeClient.openSession(graphName); + HgStoreTestUtil.batchPut(session, tableName, "testKey", number); + + try (HgKvIterator iterators = session.scanIterator(tableName)) { + Assert.assertEquals(number, HgStoreTestUtil.amountOf(iterators)); + } + + Metapb.PDConfig pdConfig = pdClient.getPDConfig(); + pdConfig = pdConfig.toBuilder().setShardCount(3).build(); + + pdClient.setPDConfig(pdConfig); + pdClient.balancePartition(); + } +} diff --git a/hugegraph-store/hg-store-test/src/main/java/client/ClientSuiteTest.java b/hugegraph-store/hg-store-test/src/main/java/client/ClientSuiteTest.java new file mode 100644 index 000000000..e42884345 --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/client/ClientSuiteTest.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package client; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +import lombok.extern.slf4j.Slf4j; + + +@RunWith(Suite.class) +@Suite.SuiteClasses({ +// GraphStoreClientTest.class + HgKvStoreTest.class, + HgStoreClientTest.class, + HgStoreNodeStateTest.class, + ChangeShardNumTest.class, + HgSessionManagerRaftPDTest.class, + HgAssertTest.class, + HgPairTest.class +}) + +@Slf4j +public class ClientSuiteTest { + +} diff --git a/hugegraph-store/hg-store-test/src/main/java/client/ClientTest.java b/hugegraph-store/hg-store-test/src/main/java/client/ClientTest.java new file mode 100644 index 000000000..c22fff804 --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/client/ClientTest.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package client; + +import org.junit.Assert; +import org.junit.Test; + +import lombok.extern.slf4j.Slf4j; + +/** + * @date 2022/10/18 + **/ +@Slf4j +public class ClientTest { + + @Test + public void testDemo() { + String s = "i am client"; + log.info("UT:{}", s); + Assert.assertTrue(s.startsWith("i")); + } +} diff --git a/hugegraph-store/hg-store-test/src/main/java/client/HgAssertTest.java b/hugegraph-store/hg-store-test/src/main/java/client/HgAssertTest.java new file mode 100644 index 000000000..1ffcb89d9 --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/client/HgAssertTest.java @@ -0,0 +1,169 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package client; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.List; +import java.util.Map; + +import org.apache.hugegraph.store.client.util.HgAssert; +import org.junit.Test; + +public class HgAssertTest { + + @Test + public void testIsTrue1() { + // Setup + // Run the test + try { + HgAssert.isTrue(false, "message"); + } catch (Exception e) { + assertTrue(e instanceof IllegalArgumentException); + } + } + + @Test + public void testIsTrue2() { + try { + HgAssert.isTrue(false, () -> "message"); + } catch (Exception e) { + assertTrue(e instanceof IllegalArgumentException); + } + } + + @Test + public void testIsFalse1() { + try { + HgAssert.isFalse(true, "message"); + } catch (Exception e) { + assertTrue(e instanceof IllegalArgumentException); + } + } + + @Test + public void testIsFalse2() { + try { + HgAssert.isFalse(true, () -> "message"); + } catch (Exception e) { + assertTrue(e instanceof IllegalArgumentException); + } + } + + @Test + public void testIsArgumentValid1() { + // Setup + // Run the test + HgAssert.isArgumentValid("content".getBytes(), "parameter"); + + // Verify the results + } + + @Test + public void testIsArgumentValid2() { + // Setup + // Run the test + HgAssert.isArgumentValid("str", "parameter"); + + // Verify the results + } + + @Test + public void testIsArgumentValid3() { + // Setup + // Run the test + HgAssert.isArgumentValid(List.of("value"), "parameter"); + + // Verify the results + } + + @Test + public void testIsArgumentNotNull() { + // Setup + // Run the test + HgAssert.isArgumentNotNull("obj", "parameter"); + + // Verify the results + } + + @Test + public void testIstValid() { + // Setup + // Run the test + HgAssert.istValid("content".getBytes(), "message"); + + // Verify the results + } + + @Test + public void testIsValid() { + // Setup + // Run the test + HgAssert.isValid("str", "message"); + + // Verify the results + } + + @Test + public void testIsNotNull() { + // Setup + // Run the test + HgAssert.isNotNull("obj", "message"); + + // Verify the results + } + + @Test + public void testIsContains1() { + assertTrue(HgAssert.isContains(new Object[]{"obj"}, "obj")); + } + + @Test + public void testIsInvalid1() { + assertFalse(HgAssert.isInvalid("strs")); + } + + @Test + public void testIsInvalid2() { + assertFalse(HgAssert.isInvalid("content".getBytes())); + } + + @Test + public void testIsInvalid3() { + // Setup + final Map map = Map.ofEntries(Map.entry("value", "value")); + // Verify the results + assertFalse(HgAssert.isInvalid(map)); + } + + @Test + public void testIsInvalid4() { + assertFalse(HgAssert.isInvalid(List.of("value"))); + } + + @Test + public void testIsContains2() { + assertTrue(HgAssert.isContains(List.of("item"), "item")); + } + + @Test + public void testIsNull() { + assertFalse(HgAssert.isNull("objs")); + } +} diff --git a/hugegraph-store/hg-store-test/src/main/java/client/HgKvStoreTest.java b/hugegraph-store/hg-store-test/src/main/java/client/HgKvStoreTest.java new file mode 100644 index 000000000..aa0555aa9 --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/client/HgKvStoreTest.java @@ -0,0 +1,191 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package client; + +import static org.apache.hugegraph.store.client.util.HgStoreClientUtil.toBytes; +import static org.apache.hugegraph.store.client.util.HgStoreClientUtil.toOwnerKey; +import static org.apache.hugegraph.store.client.util.HgStoreClientUtil.toStr; + +import org.apache.hugegraph.store.HgKvEntry; +import org.apache.hugegraph.store.HgKvIterator; +import org.apache.hugegraph.store.HgOwnerKey; +import org.apache.hugegraph.store.HgStoreSession; +import org.junit.Assert; +import org.junit.Test; + +public class HgKvStoreTest extends BaseClientTest { + public static final String TABLE_NAME = "unit-table"; + + @Test + public void truncateTest() { + System.out.println("--- test truncateTest ---"); + HgStoreSession graph0 = storeClient.openSession("hugegraph0"); + HgStoreSession graph1 = storeClient.openSession("hugegraph1"); + + graph0.truncate(); + graph1.truncate(); + + for (int i = 0; i < 3; i++) { + HgOwnerKey key = toOwnerKey("owner-" + i, "ownerKey-" + i); + byte[] value0 = toBytes("g0 owner-" + i + ";ownerKey-" + i); + graph0.put(TABLE_NAME, key, value0); + + byte[] value1 = toBytes("g1 owner-" + i + ";ownerKey-" + i); + graph1.put(TABLE_NAME, key, value1); + } + + HgKvIterator iterator = graph0.scanIterator(TABLE_NAME); + Assert.assertTrue(iterator.hasNext()); + while (iterator.hasNext()) { + HgKvEntry entry = iterator.next(); +// log.info("key:{} value:{}", toStr(entry.key()), toStr(entry.value())); + Assert.assertEquals(0, toStr(entry.value()).indexOf("g0")); + } + iterator = graph1.scanIterator(TABLE_NAME); + Assert.assertTrue(iterator.hasNext()); + while (iterator.hasNext()) { + HgKvEntry entry = iterator.next(); +// log.info("key:{} value:{}", toStr(entry.key()), toStr(entry.value())); + Assert.assertEquals(0, toStr(entry.value()).indexOf("g1")); + } + + graph0.truncate(); + iterator = graph0.scanIterator(TABLE_NAME); + Assert.assertFalse(iterator.hasNext()); + + iterator = graph1.scanIterator(TABLE_NAME); + Assert.assertTrue(iterator.hasNext()); + while (iterator.hasNext()) { + HgKvEntry entry = iterator.next(); + // System.out.println("key:" + toStr(entry.key()) + " value:" + toStr(entry.value())); + Assert.assertEquals(0, toStr(entry.value()).indexOf("g1")); + } + } + + @Test + public void deleteTableTest() { + System.out.println("--- test deleteTableTest ---"); + HgStoreSession graph0 = storeClient.openSession("hugegraph0"); + HgStoreSession graph1 = storeClient.openSession("hugegraph1"); + graph0.truncate(); + graph1.truncate(); + + for (int i = 0; i < 2; i++) { + HgOwnerKey key = toOwnerKey("owner-" + i, "ownerKey-" + i); + byte[] value0 = toBytes("g0 owner-" + i + ";ownerKey-" + i); + graph0.put(TABLE_NAME, key, value0); + + byte[] value1 = toBytes("g1 owner-" + i + ";ownerKey-" + i); + graph1.put(TABLE_NAME, key, value1); + } + + HgKvIterator iterator = graph0.scanIterator(TABLE_NAME); + Assert.assertTrue(iterator.hasNext()); + while (iterator.hasNext()) { + HgKvEntry entry = iterator.next(); + Assert.assertEquals(0, toStr(entry.value()).indexOf("g0")); + } + iterator = graph1.scanIterator(TABLE_NAME); + Assert.assertTrue(iterator.hasNext()); + while (iterator.hasNext()) { + HgKvEntry entry = iterator.next(); + Assert.assertEquals(0, toStr(entry.value()).indexOf("g1")); + } + + graph0.deleteTable(TABLE_NAME); + Assert.assertTrue(graph0.existsTable(TABLE_NAME)); + iterator = graph0.scanIterator(TABLE_NAME); + Assert.assertFalse(iterator.hasNext()); + while (iterator.hasNext()) { + HgKvEntry entry = iterator.next(); + } + + iterator = graph1.scanIterator(TABLE_NAME); + Assert.assertTrue(iterator.hasNext()); + while (iterator.hasNext()) { + HgKvEntry entry = iterator.next(); + Assert.assertEquals(0, toStr(entry.value()).indexOf("g1")); + } + } + + @Test + public void dropTableTest() { + System.out.println("--- test dropTableTest ---"); + HgStoreSession graph0 = storeClient.openSession("hugegraph0"); + HgStoreSession graph1 = storeClient.openSession("hugegraph1"); + graph0.truncate(); + graph1.truncate(); + + for (int i = 0; i < 2; i++) { + HgOwnerKey key = toOwnerKey("owner-" + i, "ownerKey-" + i); + byte[] value0 = toBytes("g0 owner-" + i + ";ownerKey-" + i); + graph0.put(TABLE_NAME, key, value0); + + byte[] value1 = toBytes("g1 owner-" + i + ";ownerKey-" + i); + graph1.put(TABLE_NAME, key, value1); + } + + HgKvIterator iterator = graph0.scanIterator(TABLE_NAME); + Assert.assertTrue(iterator.hasNext()); + while (iterator.hasNext()) { + HgKvEntry entry = iterator.next(); + Assert.assertEquals(0, toStr(entry.value()).indexOf("g0")); + } + iterator = graph1.scanIterator(TABLE_NAME); + Assert.assertTrue(iterator.hasNext()); + while (iterator.hasNext()) { + HgKvEntry entry = iterator.next(); + Assert.assertEquals(0, toStr(entry.value()).indexOf("g1")); + } + + graph0.dropTable(TABLE_NAME); + Assert.assertTrue(graph0.existsTable(TABLE_NAME)); + iterator = graph0.scanIterator(TABLE_NAME); + Assert.assertFalse(iterator.hasNext()); + while (iterator.hasNext()) { + HgKvEntry entry = iterator.next(); + } + + iterator = graph1.scanIterator(TABLE_NAME); + Assert.assertTrue(iterator.hasNext()); + while (iterator.hasNext()) { + HgKvEntry entry = iterator.next(); + Assert.assertEquals(0, toStr(entry.value()).indexOf("g1")); + } + } + + @Test + public void moveDataPathTest() { + System.out.println("--- test moveDataPathTest ---"); + HgStoreSession graph0 = storeClient.openSession("hugegraph0"); + graph0.truncate(); + + for (int i = 0; i < 2; i++) { + HgOwnerKey key = toOwnerKey("owner-" + i, "ownerKey-" + i); + byte[] value0 = toBytes("g0 owner-" + i + ";ownerKey-" + i); + graph0.put(TABLE_NAME, key, value0); + } + + HgKvIterator iterator = graph0.scanIterator(TABLE_NAME); + Assert.assertTrue(iterator.hasNext()); + while (iterator.hasNext()) { + HgKvEntry entry = iterator.next(); + Assert.assertEquals(0, toStr(entry.value()).indexOf("g0")); + } + } +} diff --git a/hugegraph-store/hg-store-test/src/main/java/client/HgPairTest.java b/hugegraph-store/hg-store-test/src/main/java/client/HgPairTest.java new file mode 100644 index 000000000..6077c4394 --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/client/HgPairTest.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package client; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import org.apache.hugegraph.store.term.Bits; +import org.apache.hugegraph.store.term.HgPair; +import org.apache.hugegraph.store.term.HgTriple; +import org.junit.Before; +import org.junit.Test; + +public class HgPairTest { + + private HgPair pair; + private HgTriple triple; + + @Before + public void setUp() { + pair = new HgPair<>("key", "value"); + triple = new HgTriple<>("x", "y", "z"); + } + + @Test + public void testPair() { + int hashCode = pair.hashCode(); + pair.toString(); + pair.setKey("key1"); + pair.setValue("value1"); + pair.getKey(); + pair.getValue(); + assertEquals(new HgPair<>("key1", "value1"), pair); + var pair2 = new HgPair<>(); + pair2.setKey("key1"); + pair2.hashCode(); + assertNotEquals(pair2, pair); + triple.getZ(); + triple.getX(); + triple.getY(); + triple.toString(); + triple.hashCode(); + triple.hashCode(); + assertEquals(triple, new HgTriple<>("x", "y", "z")); + assertNotEquals(pair2, triple); + } + + @Test + public void testBits() { + byte[] buf = new byte[4]; + Bits.putInt(buf, 0, 3); + int i = Bits.getInt(buf, 0); + assertEquals(3, i); + buf = new byte[2]; + Bits.putShort(buf, 0, 2); + int s = Bits.getShort(buf, 0); + assertEquals(2, s); + buf = new byte[4]; + Bits.put(buf, 0, new byte[]{0, 0, 0, 66}); + int toInt = Bits.toInt(buf); + assertEquals(66, toInt); + } +} diff --git a/hugegraph-store/hg-store-test/src/main/java/client/HgSessionManagerRaftPDTest.java b/hugegraph-store/hg-store-test/src/main/java/client/HgSessionManagerRaftPDTest.java new file mode 100644 index 000000000..b8dbd6743 --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/client/HgSessionManagerRaftPDTest.java @@ -0,0 +1,699 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package client; + +import static client.HgKvStoreTest.TABLE_NAME; +import static org.apache.hugegraph.store.client.util.HgStoreClientConst.EMPTY_BYTES; +import static util.HgStoreTestUtil.GRAPH_NAME; +import static util.HgStoreTestUtil.amountOf; +import static util.HgStoreTestUtil.batchPut; +import static util.HgStoreTestUtil.padLeftZeros; +import static util.HgStoreTestUtil.println; +import static util.HgStoreTestUtil.toAllPartitionKey; +import static util.HgStoreTestUtil.toBytes; +import static util.HgStoreTestUtil.toLong; +import static util.HgStoreTestUtil.toOwnerKey; +import static util.HgStoreTestUtil.toStr; +import static util.HgStoreTestUtil.toSuffix; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.stream.Collectors; + +import org.apache.commons.io.FileUtils; +import org.apache.hugegraph.pd.client.PDClient; +import org.apache.hugegraph.pd.client.PDConfig; +import org.apache.hugegraph.store.HgKvEntry; +import org.apache.hugegraph.store.HgKvIterator; +import org.apache.hugegraph.store.HgKvStore; +import org.apache.hugegraph.store.HgOwnerKey; +import org.apache.hugegraph.store.HgScanQuery; +import org.apache.hugegraph.store.HgStoreClient; +import org.apache.hugegraph.store.HgStoreSession; +import org.apache.hugegraph.store.client.grpc.KvCloseableIterator; +import org.apache.hugegraph.store.client.util.HgStoreClientConfig; +import org.apache.hugegraph.store.client.util.HgStoreClientConst; +import org.apache.hugegraph.store.client.util.MetricX; +import org.apache.hugegraph.store.grpc.common.ScanOrderType; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +import util.HgStoreTestUtil; + +public class HgSessionManagerRaftPDTest { + public static final String GRAPH_NAME_X = "default/hugegraph/x"; + public static final String GRAPH_NAME_Y = "default/hugegraph/y"; + public static final String GRAPH_NAME_Z = "default/hugegraph/z"; + public static final String TABLE_NAME_1 = "table1"; + public static final String TABLE_NAME_2 = "table2"; + public static final String TABLE_NAME_3 = "table3"; + private static final String PD_ADDRESS = "localhost:8686"; + public static HgStoreClient storeClient; + private static PDClient pdClient; + + @BeforeClass + public static void init() { + pdClient = PDClient.create(PDConfig.of(PD_ADDRESS).setEnableCache(true)); + storeClient = HgStoreClient.create(pdClient); + } + + private static HgStoreSession getStoreSession() { + return storeClient.openSession(GRAPH_NAME); + } + + private static HgStoreSession getStoreSession(String graph) { + return storeClient.openSession(graph); + } + + @Test + public void putGet() { + System.out.println("--- test put & get ---"); + + HgStoreSession session = getStoreSession(GRAPH_NAME); + + HgOwnerKey key = toOwnerKey("FOR-PUT-KEY"); + byte[] value = toBytes("FOR-PUT-VALUE"); + + long stime = System.currentTimeMillis(); + batchPut(session, TABLE_NAME, "BATCH-PUT-TEST", 30000); + System.out.println("Time is " + (System.currentTimeMillis() - stime)); + } + + public void putGet2() { + System.out.println("--- test put & get ---"); + + HgStoreSession session = getStoreSession("testGraph"); + HgOwnerKey key = toOwnerKey("FOR-PUT-KEY"); + byte[] value = toBytes("FOR-PUT-VALUE"); + long stime = System.currentTimeMillis(); + batchPut(session, "testTable", "BATCH-PUT-TEST", 30000); + System.out.println("Time is " + (System.currentTimeMillis() - stime)); + } + + @Test + public void scanPartition() { + println("--- test scanPartition ---"); + + HgStoreSession session = getStoreSession(); + + Iterator iterator = + session.scanIterator(TABLE_NAME, 0, 65535, HgKvStore.SCAN_HASHCODE, EMPTY_BYTES); + System.out.println(amountOf(iterator)); + } + + @Test + public void check() { + System.out.println("--- test check ---"); + + HgStoreSession session = getStoreSession(); + HgKvIterator iterator = session.scanIterator(TABLE_NAME); + int count = 0; + while (iterator.hasNext()) { + iterator.next(); + count++; + } + System.out.println(count); + } + + @Test + public void putGetUnique() { + System.out.println("--- test put & get ---"); + + HgStoreSession session = getStoreSession(); + + // add timestamp into key to avoid key duplication + Date date = new Date(); + SimpleDateFormat formatter = new SimpleDateFormat("HHmmss"); + String timestamp = formatter.format(date); + + HgOwnerKey key = toOwnerKey("FOR-PUT-KEY-" + timestamp); + byte[] value = toBytes("FOR-PUT-VALUE"); + + Assert.assertTrue(session.put(TABLE_NAME, key, value)); + + Assert.assertEquals(toStr(value), toStr(session.get(TABLE_NAME, key))); + } + + @Test + public void testBatchPutExt() throws IOException { + System.out.println("--- test batchPut ---"); + HgStoreSession session = getStoreSession(); + session.truncate(); + String keyPrefix = "BATCH-GET-UNIT"; + + Map map = batchPut(session, keyPrefix, 10); + + File outputFile = new File("tmp/batch_put_list"); + if (outputFile.exists()) { + FileUtils.forceDelete(outputFile); + } + FileUtils.forceMkdir(new File("tmp/")); + + ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(outputFile)); + oo.writeObject(map); + oo.close(); + System.out.printf("%d entries have be put into graph %s\n", map.size(), GRAPH_NAME); + + int count = 0; + HgKvIterator iterator = null; + iterator = session.scanIterator(TABLE_NAME); + while (iterator.hasNext()) { + iterator.next(); + count++; + } + Assert.assertEquals(10, count); + } + + // @Test + public void testBatchGetExt() throws IOException, ClassNotFoundException { + File outputFile = new File("tmp/batch_put_list"); + ObjectInputStream ois = new ObjectInputStream(new FileInputStream(outputFile)); + Map map = (Map) ois.readObject(); + ois.close(); + System.out.printf("%d entries get from %s\n", map.size(), outputFile.getPath()); + + HgStoreSession session = getStoreSession(); + List keyList = + map.entrySet().stream().map(e -> e.getKey()).collect(Collectors.toList()); + List resList = session.batchGetOwner(TABLE_NAME, keyList); + + Assert.assertTrue( + (resList.stream().map(e -> map.containsKey(toOwnerKey(e.key()))) + .allMatch(Boolean::booleanValue))); + } + + @Test + public void testBatchPutUniqueExt() throws IOException { + System.out.println("--- test batchPut ---"); + HgStoreSession session = getStoreSession(); + + // add timestamp into key to avoid key duplication + Date date = new Date(); + SimpleDateFormat formatter = new SimpleDateFormat("HHmmss"); + String timestamp = formatter.format(date); + + String keyPrefix = "BATCH-GET-UNIT-" + timestamp; + + Map map = batchPut(session, keyPrefix, 30); + + File outputFile = new File("tmp/batch_put_list"); + if (outputFile.exists()) { + FileUtils.forceDelete(outputFile); + } + FileUtils.forceMkdir(new File("tmp/")); + + ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(outputFile)); + oo.writeObject(map); + oo.close(); + System.out.printf("%d entries have be put into graph %s\n", map.size(), GRAPH_NAME); + } + + @Test + public void testBatchPutMultiGraph() throws IOException { + System.out.println("--- test testBatchPutMultiGraph ---"); + Date date = new Date(); + SimpleDateFormat formatter = new SimpleDateFormat("HHmmss"); + String timestamp = formatter.format(date); + HgStoreSession session1 = getStoreSession(GRAPH_NAME_X); + HgStoreSession session2 = getStoreSession(GRAPH_NAME_Y); + HgStoreSession session3 = getStoreSession(GRAPH_NAME_Z); + String keyPrefix1 = "BATCH-PUT-UNIT-X-" + timestamp; + String keyPrefix2 = "BATCH-PUT-UNIT-Y-" + timestamp; + String keyPrefix3 = "BATCH-PUT-UNIT-Z-" + timestamp; + batchPut(session1, TABLE_NAME_1, keyPrefix1, 1000); + batchPut(session1, TABLE_NAME_2, keyPrefix1, 1000); + batchPut(session1, TABLE_NAME_3, keyPrefix1, 1000); + batchPut(session2, TABLE_NAME_1, keyPrefix2, 1000); + batchPut(session2, TABLE_NAME_2, keyPrefix2, 1000); + batchPut(session2, TABLE_NAME_3, keyPrefix2, 1000); + batchPut(session3, TABLE_NAME_1, keyPrefix3, 1000); + batchPut(session3, TABLE_NAME_2, keyPrefix3, 1000); + batchPut(session3, TABLE_NAME_3, keyPrefix3, 1000); + } + + // @Test + // CAUTION: ONLY FOR LONG! + // 注意:目前只可以对long类型value进行Merge操作。 + public void merge() { + System.out.println("--- test merge (1+1=2)---"); + HgStoreSession session = getStoreSession(); + String mergeKey = "merge-key"; + HgOwnerKey key = toOwnerKey(mergeKey); + byte[] value = toBytes(1L); + + System.out.println("- put " + mergeKey + ":1 -"); + session.put(TABLE_NAME, key, value); + System.out.println("- merge " + mergeKey + ":1 -"); + session.merge(TABLE_NAME, key, value); + long res = toLong(session.get(TABLE_NAME, key)); + System.out.printf("after merge " + mergeKey + "=%s%n", res); + Assert.assertEquals(2L, res); + + String putStr = "19"; + session.put(TABLE_NAME, key, toBytes(putStr)); + byte[] b1 = session.get(TABLE_NAME, key); + Assert.assertEquals(putStr, toStr(b1)); + } + + @Test + public void delete() { + System.out.println("--- test delete ---"); + HgStoreSession session = getStoreSession(); + + String delKey = "del-key"; + String delValue = "del-value"; + HgOwnerKey key = toOwnerKey(delKey); + byte[] value = toBytes(delValue); + + println("- put " + delKey + ": " + delValue); + session.put(TABLE_NAME, key, value); + println("- delete " + delKey); + session.delete(TABLE_NAME, key); + value = session.get(TABLE_NAME, key); + println("- get " + delKey + ": " + toStr(value)); + Assert.assertEquals(EMPTY_BYTES, value); + } + + @Test + public void deleteSingle() { + System.out.println("--- test deleteSingle ---"); + HgStoreSession session = getStoreSession(); + + String delKey = "del-single-key"; + String delValue = "del-single-value"; + HgOwnerKey key = toOwnerKey(delKey); + byte[] value = toBytes(delValue); + + session.put(TABLE_NAME, key, value); + value = session.get(TABLE_NAME, key); + Assert.assertEquals(delValue, toStr(value)); + + println("- delete-single : [" + delKey + "]"); + session.deleteSingle(TABLE_NAME, key); + value = session.get(TABLE_NAME, key); + println("- after del, get [" + delKey + "] = " + toStr(value)); + Assert.assertEquals("", toStr(value)); + } + + @Test + public void deleteRange() { + println("--- test deleteRange ---"); + HgStoreSession session = getStoreSession(); + + String rangePrefix = "DEL-RANGE-KEY"; + String owner = "batch-delete-owner"; + Map map = + batchPut( + session, + TABLE_NAME, + rangePrefix, + 10, + key -> { + return toOwnerKey(owner, key); + }); + + HgOwnerKey startKey = toOwnerKey(owner, rangePrefix + "-00"); + HgOwnerKey endKey = toOwnerKey(owner, rangePrefix + "-05"); + + Assert.assertTrue(session.deleteRange(TABLE_NAME, startKey, endKey)); + + println("- after delete range from [" + toStr(startKey.getKey()) + "] to [" + + toStr(endKey.getKey()) + "]"); + + for (int i = 0; i < 10; i++) { + HgOwnerKey key = + toOwnerKey(owner, rangePrefix + "-" + padLeftZeros(String.valueOf(i), 2)); + String value = toStr(session.get(TABLE_NAME, key)); + + // TODO: [start,end)? + if (i < 5) { + Assert.assertEquals("", value); + } else { + // println(value); + Assert.assertNotEquals("", value); + } + } + } + + @Test + public void deletePrefix() { + System.out.println("--- test deletePrefix ---"); + HgStoreSession session = getStoreSession(); + + String prefixStr = "DEL-PREFIX-KEY"; + String owner = "batch-delete-owner"; + Map map = + batchPut( + session, + TABLE_NAME, + prefixStr, + 10, + key -> { + return toOwnerKey(owner, key); + }); + + // printOwner(map.entrySet().stream().map(e -> e.getKey()).collect(Collectors.toList())); + + HgOwnerKey prefix = toOwnerKey(owner, prefixStr); + + Assert.assertEquals(10, amountOf(session.scanIterator(TABLE_NAME, prefix))); + session.deletePrefix(TABLE_NAME, prefix); + Assert.assertEquals(0, amountOf(session.scanIterator(TABLE_NAME, prefix))); + + println("- after delete by prefix:[" + prefixStr + "]"); + + for (int i = 0; i < 10; i++) { + HgOwnerKey key = toOwnerKey(owner, prefixStr + toSuffix(i, 2)); + String value = toStr(session.get(TABLE_NAME, key)); + Assert.assertEquals("", value); + } + } + + @Test + public void scanIterator() { + println("--- test scanIterator ---"); + String tableName = "UNIT_SCAN"; + String keyName = "SCAN-ITER"; + HgStoreSession session = getStoreSession(); + batchPut(session, tableName, keyName, 10000); + int count = 0; + int limit = 0; + int max = 99999; + HgKvIterator iterator = null; + + println("-- test 0 element --"); + iterator = session.scanIterator(tableName, toAllPartitionKey("__SCAN-001"), + toAllPartitionKey("__SCAN-100"), 0); + Assert.assertFalse(iterator.hasNext()); + try { + iterator.next(); + Assert.fail(); + } catch (Throwable t) { + println("-- test NoSuchElementException --"); + Assert.assertTrue(t instanceof NoSuchElementException); + } + + for (int i = 1; i <= 10; i++) { + limit = i; + iterator = + session.scanIterator( + tableName, toAllPartitionKey(keyName + "-0"), + toAllPartitionKey(keyName + "-1"), limit); + count = 0; + while (iterator.hasNext()) { + count++; + HgKvEntry entry = iterator.next(); + } + Assert.assertEquals(limit, count); + } + + for (int i = 1; i <= 10; i++) { + limit = i; + iterator = + session.scanIterator( + tableName, toAllPartitionKey(keyName + "-00001"), + toAllPartitionKey(keyName + "-00005"), limit); + count = 0; + while (iterator.hasNext()) { + count++; + HgKvEntry entry = iterator.next(); + } + if (i <= 5) { + Assert.assertEquals(limit, count); + } else { + Assert.assertEquals(5, count); + } + } + + limit = 0; + iterator = + session.scanIterator( + tableName, toAllPartitionKey(keyName + "-0"), + toAllPartitionKey(keyName + "-1"), limit); + + count = 0; + while (iterator.hasNext()) { + count++; + HgKvEntry entry = iterator.next(); + if (count >= max) { + break; + } + } + Assert.assertEquals(10000, count); + + iterator = session.scanIterator(tableName); + count = 0; + while (iterator.hasNext()) { + count++; + HgKvEntry entry = iterator.next(); + if (count >= max) { + break; + } + } + Assert.assertEquals(10000, count); + + iterator = session.scanIterator(tableName, toAllPartitionKey(keyName + "-01")); + count = 0; + while (iterator.hasNext()) { + count++; + HgKvEntry entry = iterator.next(); + if (count >= max) { + break; + } + } + Assert.assertEquals(1000, count); + } + + @Test + public void paging() { + println("--- test scanIterator_range ---"); + String graph = "UNIT/paging"; + String tableName = "UNIT_SCAN_PAGING"; + String keyName = "SCAN-PAGING"; + int keyAmt = 100; + HgStoreSession session = getStoreSession(graph); + batchPut(session, tableName, keyName, keyAmt); + int count = 0; + int limit = 0; + int max = 99999; + HgKvIterator iterator = null; + + count = 0; + + iterator = + session.scanIterator( + tableName, + toAllPartitionKey(keyName + "-000"), + HgStoreClientConst.EMPTY_OWNER_KEY, + 0, + HgKvStore.SCAN_ANY, + EMPTY_BYTES); + List positionList = new LinkedList<>(); + while (iterator.hasNext()) { + HgKvEntry entry = iterator.next(); + positionList.add(iterator.position()); + if (count >= max) { + break; + } + } + + iterator = session.scanIterator(tableName, 100); + + byte[] position = positionList.get(50); + println("seek: " + Arrays.toString(position)); + iterator.seek(position); + HgStoreTestUtil.println(iterator); + // Assert.assertEquals(50, HgStoreTestUtil.amountOf(iterator)); + + } + + @Test + public void truncate() { + println("--- test truncate ---"); + String graph = "graph_truncate"; + String tableName = "UNIT_TRUNCATE_1"; + String keyName = "KEY_TRUNCATE"; + + HgStoreSession session = getStoreSession(graph); + batchPut(session, tableName, keyName, 100); + Assert.assertEquals(100, amountOf(session.scanIterator(tableName))); + + String tableName2 = "UNIT_TRUNCATE_2"; + batchPut(session, tableName2, keyName, 100); + Assert.assertEquals(100, amountOf(session.scanIterator(tableName2))); + + session.truncate(); + Assert.assertEquals(0, amountOf(session.scanIterator(tableName))); + Assert.assertEquals(0, amountOf(session.scanIterator(tableName2))); + } + + // // @Test + public void scanIteratorHuge() { + + /*************** test no limit, with 10 millions **************/ + String tableName = "UNIT_HUGE"; + String keyName = "SCAN-HUGE"; + int amount = 10_000_000; + HgStoreSession session = getStoreSession(); + + if (amountOf(session.scanIterator(tableName, toAllPartitionKey(keyName), 10)) < 10) { + batchPut(session, tableName, keyName, amount); + } + + int count = 0; + HgKvIterator iterator = session.scanIterator(tableName); + + while (iterator.hasNext()) { + count++; + HgKvEntry entry = iterator.next(); + } + + Assert.assertEquals(amount, count); + } + + @Test + public void scanTable() { + HgStoreSession session = getStoreSession("DEFAULT/hg1/g"); + HgStoreTestUtil.println(session.scanIterator("g+v", 10)); + } + + @Test + public void testDelGraph() { + HgStoreSession session = getStoreSession(); + session.deleteGraph(GRAPH_NAME); + } + + @Test + public void benchmarkScanBatch() { + println("--- Benchmark scanBatch ---"); + String tableName = "Benchmark_SCAN_BATCH"; + String keyName = "SCAN-BATCH"; + int keyAmt = 30001; + + HgStoreSession session = getStoreSession(); + + if (amountOf(session.scanIterator(tableName, toAllPartitionKey(keyName), 1)) < 1) { + batchPut(session, tableName, keyName, keyAmt); + } + println("-- Starting scan --"); + MetricX metrics = MetricX.ofStart(); + // HgStoreTestUtil.println(session.scanIterator(tableName)); + List> iterators = session.scanBatch(HgScanQuery.tableOf(tableName)); + Assert.assertEquals(keyAmt, HgStoreTestUtil.amountIn(iterators)); + } + + @Test + public void benchmarkScanBatch2() throws IOException { + println("--- Benchmark scanBatch2 ---"); + String tableName = TABLE_NAME; + HgStoreSession session = getStoreSession(); + String keyName = "SCAN-BATCH"; + int keyAmt = 300; + Map data = batchPut(session, tableName, keyName, keyAmt); + println("-- Starting scan --"); + MetricX metrics = MetricX.ofStart(); + // HgStoreTestUtil.println(session.scanIterator(tableName)); + long t = System.currentTimeMillis(); + int count = 0; + List keys = new ArrayList<>(); + data.forEach((k, v) -> keys.add(k)); + KvCloseableIterator> iterators = + session.scanBatch2( + HgScanQuery.prefixIteratorOf(tableName, keys.iterator(), + ScanOrderType.ORDER_NONE) + .builder() + .setScanType(0x40) + .build()); + while (iterators.hasNext()) { + HgKvIterator iterator = iterators.next(); + count += amountOf(iterator); + } + keys.clear(); + iterators.close(); + } + + @Test + public void benchmarkScanBatchSkipDegree() throws IOException { + println("--- Benchmark scanBatch2 1Owner---"); + String tableName = TABLE_NAME; + HgStoreSession session = getStoreSession(); + String keyName = "SCAN-BATCH"; + int keyAmt = 300; + byte[] owner = "Owner".getBytes(); + Map data = + batchPut( + session, + tableName, + keyName, + keyAmt, + key -> { + return toOwnerKey(owner, key); + }); + println("-- Starting scan --"); + MetricX metrics = MetricX.ofStart(); + long t = System.currentTimeMillis(); + int count = 0; + + List keys = new ArrayList<>(); + keys.add(toOwnerKey(owner, keyName)); + + KvCloseableIterator> iterators = + session.scanBatch2( + HgScanQuery.prefixIteratorOf(tableName, keys.iterator(), + ScanOrderType.ORDER_NONE) + .builder() + .setScanType(0x40) + .setSkipDegree(1) + .build()); + while (iterators.hasNext()) { + HgKvIterator iterator = iterators.next(); + count += amountOf(iterator); + } + keys.clear(); + iterators.close(); + iterators = null; + + println("time is " + (System.currentTimeMillis() - t)); + metrics.end(); + println("*************************************************"); + println("*********** Batch Scan Completed **************"); + println("Total : " + (metrics.past() / 1000) + " (sec)"); + println(" Sum : " + (MetricX.getIteratorWait() / 1000) + " (sec)"); + println(" Amt : " + MetricX.getIteratorCount() + " (scans)."); + println(" Avg : " + MetricX.getIteratorWaitAvg() + " (ms)"); + println(" Max : " + MetricX.getIteratorWaitMax() + " (ms)"); + println(" Fail : " + metrics.getFailureCount() + " (times)"); + println(" Page : " + HgStoreClientConfig.of().getNetKvScannerPageSize() + " (KVs)"); + println(" size is " + count); + println("*************************************************"); + } +} diff --git a/hugegraph-store/hg-store-test/src/main/java/client/HgStoreClientTest.java b/hugegraph-store/hg-store-test/src/main/java/client/HgStoreClientTest.java new file mode 100644 index 000000000..7a23089ec --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/client/HgStoreClientTest.java @@ -0,0 +1,211 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package client; + +import static org.apache.hugegraph.store.client.util.HgStoreClientConst.ALL_PARTITION_OWNER; + +import java.util.List; + +import org.apache.hugegraph.pd.common.PDException; +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.store.HgKvEntry; +import org.apache.hugegraph.store.HgKvIterator; +import org.apache.hugegraph.store.HgKvStore; +import org.apache.hugegraph.store.HgOwnerKey; +import org.apache.hugegraph.store.HgStoreSession; +import org.junit.Assert; +import org.junit.Test; + +import util.HgStoreTestUtil; + +public class HgStoreClientTest extends BaseClientTest { + public static final byte[] EMPTY_BYTES = new byte[0]; + private static final String graphName = "testGraphName"; + private static String tableName = "testTableName"; + + @Test + public void testPutData() { + HgStoreSession session = storeClient.openSession(graphName); + long start = System.currentTimeMillis(); + int loop = 100000; + session.truncate(); + HgStoreTestUtil.batchPut(session, tableName, "testKey", loop); + + System.out.println("Time is " + (System.currentTimeMillis() - start)); + try (HgKvIterator iterator = session.scanIterator(tableName)) { + Assert.assertEquals(loop, HgStoreTestUtil.amountOf(iterator)); + } + } + + @Test + public void testPutData2() { + String graphName = "testGraphName2"; + HgStoreSession session = storeClient.openSession(graphName); + long start = System.currentTimeMillis(); + int loop = 100000; + session.truncate(); + HgStoreTestUtil.batchPut(session, tableName, "testKey", loop); + + System.out.println("Time is " + (System.currentTimeMillis() - start)); + try (HgKvIterator iterator = session.scanIterator(tableName)) { + Assert.assertEquals(loop, HgStoreTestUtil.amountOf(iterator)); + } + } + + @Test + public void testScan() throws PDException { + + HgStoreSession session = storeClient.openSession(graphName); + HgStoreTestUtil.batchPut(session, tableName, "testKey", 12); + + int count = 0; + byte[] position = null; + try (HgKvIterator iterator = session.scanIterator(tableName)) { + while (iterator.hasNext()) { + iterator.next(); + position = iterator.position(); + dumpPosition(position); + if (++count > 5) { + break; + } + } + } + + + System.out.println("--------------------------------"); + try (HgKvIterator iterator = session.scanIterator(tableName)) { + iterator.seek(position); + while (iterator.hasNext()) { + iterator.next(); + dumpPosition(iterator.position()); + } + } + + System.out.println("--------------------------------"); + + + byte[] start = new byte[]{0x0}; + byte[] end = new byte[]{-1}; + try (HgKvIterator iterator = session.scanIterator(tableName, + HgOwnerKey.of( + ALL_PARTITION_OWNER, + start), + HgOwnerKey.of( + ALL_PARTITION_OWNER, + end))) { + iterator.seek(position); + while (iterator.hasNext()) { + iterator.next(); + dumpPosition(iterator.position()); + } + } + } + + public void dumpPosition(byte[] b) { + byte[] buf = new byte[Long.BYTES]; + System.arraycopy(b, 0, buf, 0, Long.BYTES); + // long storeId = HgStoreTestUtil.toLong(buf); + buf = new byte[Integer.BYTES]; + System.arraycopy(b, Long.BYTES, buf, 0, Integer.BYTES); + // int partId = HgStoreTestUtil.toInt(buf); + // String key = new String(b); + + // System.out.println(" " + storeId + ", " + partId + ", " + key); + } + + @Test + public void testDeleteData() { + tableName = "deleteData5"; + HgStoreSession session = storeClient.openSession(graphName); + int ownerCode = 1; + HgStoreTestUtil.batchPut(session, tableName, "T", 10, (key) -> { + return HgStoreTestUtil.toOwnerKey(ownerCode, key); + } + ); + try (HgKvIterator iterators = session.scanIterator(tableName)) { +// while (iterators.hasNext()){ +// System.out.println(new String(iterators.next().key())); +// } + Assert.assertEquals(10, HgStoreTestUtil.amountOf(iterators)); + } + session.beginTx(); + session.deletePrefix(tableName, HgStoreTestUtil.toOwnerKey(ownerCode, "T")); + session.commit(); + + System.out.println("================================="); + try (HgKvIterator iterators = session.scanIterator(tableName)) { + Assert.assertEquals(0, HgStoreTestUtil.amountOf(iterators)); +// while (iterators.hasNext()){ +// System.out.println(new String(iterators.next().key())); +// } + } + } + + @Test + public void testDropTable() throws PDException { + HgStoreSession session = storeClient.openSession(graphName); + + String table1 = "Table1"; + session.createTable(table1); + HgStoreTestUtil.batchPut(session, table1, "testKey", 1000); + + try (HgKvIterator iterators = session.scanIterator(table1)) { + Assert.assertEquals(1000, HgStoreTestUtil.amountOf(iterators)); + } + + session.dropTable(table1); + try (HgKvIterator iterators = session.scanIterator(table1)) { + Assert.assertEquals(0, HgStoreTestUtil.amountOf(iterators)); + } + + deleteGraph(graphName); + } + + + public void deleteGraph(String graphName) throws PDException { + HgStoreSession session = storeClient.openSession(graphName); + session.deleteGraph(graphName); + pdClient.delGraph(graphName); + + Metapb.Graph graph = null; + try { + graph = pdClient.getGraph(graphName); + } catch (PDException e) { + Assert.assertEquals(103, e.getErrorCode()); + } + Assert.assertNull(graph); + } + + @Test + public void testScanPartition() throws PDException { + // testPutData(); + List partitions = pdClient.getPartitions(0, "DEFAULT/hugegraph/g"); + HgStoreSession session = storeClient.openSession("DEFAULT/hugegraph/g"); + for (Metapb.Partition partition : partitions) { + try (HgKvIterator iterators = session.scanIterator("g+v", + (int) (partition.getStartKey()), + (int) (partition.getEndKey()), + HgKvStore.SCAN_HASHCODE, + EMPTY_BYTES)) { + + System.out.println( + " " + partition.getId() + " " + HgStoreTestUtil.amountOf(iterators)); + } + } + } +} diff --git a/hugegraph-store/hg-store-test/src/main/java/client/HgStoreNodeStateTest.java b/hugegraph-store/hg-store-test/src/main/java/client/HgStoreNodeStateTest.java new file mode 100644 index 000000000..ae59bb81b --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/client/HgStoreNodeStateTest.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package client; + +import static util.HgStoreTestUtil.GRAPH_NAME; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.hugegraph.store.client.HgStoreNodeManager; +import org.junit.Test; + +public class HgStoreNodeStateTest { + private static final HgStoreNodeManager NODE_MANAGER = HgStoreNodeManager.getInstance(); + static int nodeNumber = 0; + + static { + registerNode(GRAPH_NAME, Long.valueOf(nodeNumber++), "localhost:9180"); + registerNode(GRAPH_NAME, Long.valueOf(nodeNumber++), "localhost:9280"); + registerNode(GRAPH_NAME, Long.valueOf(nodeNumber++), "localhost:9380"); + } + + private static void registerNode(String graphName, Long nodeId, String address) { + NODE_MANAGER.addNode(graphName, + NODE_MANAGER.getNodeBuilder().setNodeId(nodeId).setAddress(address) + .build()); + } + + + @Test + public void isNodeHealthy() { + AtomicInteger count = new AtomicInteger(0); + + for (int i = 0; i < 100; i++) { + NODE_MANAGER.getStoreNodes(GRAPH_NAME) + .stream().map( + node -> { + System.out.println(node.getNodeId() + " " + node.getAddress() + + "is healthy: " + node.isHealthy()); + return node.isHealthy(); + } + ).count(); + + Thread.yield(); + } + } +} diff --git a/hugegraph-store/hg-store-test/src/main/java/client/graph/GraphStoreClientTest.java b/hugegraph-store/hg-store-test/src/main/java/client/graph/GraphStoreClientTest.java new file mode 100644 index 000000000..a52dc2670 --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/client/graph/GraphStoreClientTest.java @@ -0,0 +1,182 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package client.graph; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.hugegraph.store.grpc.GraphStoreGrpc; +import org.apache.hugegraph.store.grpc.GraphStoreGrpc.GraphStoreStub; +import org.apache.hugegraph.store.grpc.Graphpb.ScanPartitionRequest; +import org.apache.hugegraph.store.grpc.Graphpb.ScanPartitionRequest.Reply; +import org.apache.hugegraph.store.grpc.Graphpb.ScanResponse; +import org.junit.Test; + +import com.google.protobuf.util.JsonFormat; + +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; +import io.grpc.stub.StreamObserver; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class GraphStoreClientTest { + + private final AtomicInteger sum = new AtomicInteger(); + ConcurrentHashMap> observers = + new ConcurrentHashMap<>(); + + @Test + public void getData() { + long start = System.currentTimeMillis(); + String[] addresses = new String[]{"10.14.139.71:8500", + "10.14.139.70:8500", + "10.14.139.69:8500"}; + Arrays.stream(addresses).parallel().forEach(address -> { + int size = 72; + CountDownLatch latch = new CountDownLatch(size); + + for (int i = 0; i < size; i++) { + int finalI = i; + new Thread(() -> getData(finalI, latch, address)).start(); + } + try { + latch.await(); + + } catch (InterruptedException e) { + e.printStackTrace(); + } + }); + long end = System.currentTimeMillis(); + System.out.println( + "all rows are: " + sum.get() + ", end: " + (end - start)); + } + + public void getData(int pId, CountDownLatch latch, String address) { + try { + ScanPartitionRequest.Builder builder = + ScanPartitionRequest.newBuilder(); + ScanPartitionRequest.Request.Builder srb = + ScanPartitionRequest.Request.newBuilder(); + ArrayList properties = new ArrayList<>() {{ + add(2L); + add(3L); + }}; + ScanPartitionRequest.Request request = + srb.setGraphName("DEFAULT/hugegraph0/g") + .setScanType( + ScanPartitionRequest.ScanType.SCAN_VERTEX).setBoundary(0x10) + // .addAllProperties(properties) + // .setCondition("element.id().asString().equals + // ('1:marko1')") + .setPartitionId(pId).build(); + ManagedChannel c = + ManagedChannelBuilder.forTarget(address) + .usePlaintext().build(); + int maxSize = 1024 * 1024 * 1024; + GraphStoreStub stub; + stub = GraphStoreGrpc.newStub(c) + .withMaxInboundMessageSize(maxSize) + .withMaxOutboundMessageSize(maxSize); + + AtomicInteger count = new AtomicInteger(); + long start = System.currentTimeMillis(); + long id = Thread.currentThread().getId(); + StreamObserver ro = + new StreamObserver() { + @Override + public void onNext(ScanResponse value) { + try { + int edgeSize = value.getEdgeCount(); + int vertexSize = value.getVertexCount(); + if (request.getScanType().equals( + ScanPartitionRequest.ScanType.SCAN_VERTEX)) { + count.getAndAdd(vertexSize); + } else { + count.getAndAdd(edgeSize); + } + String print = JsonFormat.printer().print + (value); + System.out.println(print); + ScanPartitionRequest.Builder builder = + ScanPartitionRequest.newBuilder(); + builder.setScanRequest(request); + Reply.Builder reply = Reply.newBuilder(); + reply.setSeqNo(1); + builder.setReplyRequest(reply); + observers.get(id).onNext(builder.build()); + + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void onError(Throwable t) { + System.out.println(t); + latch.countDown(); + } + + @Override + public void onCompleted() { + long time = System.currentTimeMillis() - start; + System.out.println( + "scan id :" + pId + ", complete: " + + count.get() + ",time: " + time); + sum.addAndGet(count.get()); + latch.countDown(); + } + }; + StreamObserver observer = + stub.scanPartition(ro); + observers.put(id, observer); + builder.setScanRequest(request); + observer.onNext(builder.build()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + + @Test + public void getDataSingle() { + CountDownLatch latch = new CountDownLatch(1); + new Thread(() -> getData(58, latch, "10.14.139.71:8500")).start(); + try { + latch.await(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + System.out.println("all rows are: " + sum.get()); + } + + @Test + public void getNativeDataSingle() { + CountDownLatch latch = new CountDownLatch(1); + new Thread(() -> getData(0, latch, "127.0.0.1:8500")).start(); + try { + latch.await(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + System.out.println("all rows are: " + sum.get()); + } +} diff --git a/hugegraph-store/hg-store-test/src/main/java/common/BaseCommonTest.java b/hugegraph-store/hg-store-test/src/main/java/common/BaseCommonTest.java new file mode 100644 index 000000000..2b48ace2a --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/common/BaseCommonTest.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package common; + +import org.junit.After; +import org.junit.BeforeClass; + +public class BaseCommonTest { + @BeforeClass + public static void beforeClass() throws Exception { + } + + @After + public void teardown() throws Exception { + // pass + } +} diff --git a/hugegraph-store/hg-store-test/src/main/java/common/ByteBufferAllocatorTest.java b/hugegraph-store/hg-store-test/src/main/java/common/ByteBufferAllocatorTest.java new file mode 100644 index 000000000..b38ab5f26 --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/common/ByteBufferAllocatorTest.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package common; + +import java.util.concurrent.CountDownLatch; + +import org.apache.hugegraph.store.buffer.ByteBufferAllocator; +import org.junit.Assert; +import org.junit.Test; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class ByteBufferAllocatorTest extends BaseCommonTest { + @Test + public void getAndReleaseTest() throws InterruptedException { + final CountDownLatch latch = new CountDownLatch(2); + + ByteBufferAllocator allocator = new ByteBufferAllocator(1, 2); + + new Thread(() -> { + try { + var buffer1 = allocator.get(); + var buffer2 = allocator.get(); + Thread.sleep(2000); + Assert.assertEquals(buffer1.limit(), 1); + allocator.release(buffer1); + allocator.release(buffer2); + latch.countDown(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }).start(); + + new Thread(() -> { + try { + Thread.sleep(1000); + var buffer1 = allocator.get(); + var buffer2 = allocator.get(); + Assert.assertEquals(buffer1.limit(), 1); + allocator.release(buffer1); + allocator.release(buffer2); + latch.countDown(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }).start(); + + latch.await(); + } +} diff --git a/hugegraph-store/hg-store-test/src/main/java/common/CommonSuiteTest.java b/hugegraph-store/hg-store-test/src/main/java/common/CommonSuiteTest.java new file mode 100644 index 000000000..5ffc7dab2 --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/common/CommonSuiteTest.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package common; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +import lombok.extern.slf4j.Slf4j; + +@RunWith(Suite.class) +@Suite.SuiteClasses({ + ByteBufferAllocatorTest.class, + KVByteBufferTest.class +}) + +@Slf4j +public class CommonSuiteTest { +} diff --git a/hugegraph-store/hg-store-test/src/main/java/common/KVByteBufferTest.java b/hugegraph-store/hg-store-test/src/main/java/common/KVByteBufferTest.java new file mode 100644 index 000000000..0e617b10d --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/common/KVByteBufferTest.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package common; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.nio.ByteBuffer; + +import org.apache.hugegraph.store.buffer.KVByteBuffer; +import org.junit.Test; + +public class KVByteBufferTest { + + @Test + public void testOps() { + KVByteBuffer buffer1 = new KVByteBuffer(10); + buffer1.put((byte) 10); + // just put a byte + assertEquals(1, buffer1.position()); + // 9 left + assertTrue(buffer1.hasRemaining()); + buffer1.clear(); + assertEquals(10, buffer1.get()); + + buffer1.clear(); + buffer1.putInt(10); + buffer1.clear(); + assertEquals(10, buffer1.getInt()); + + buffer1.flip(); + // just write to a int + assertEquals(4, buffer1.getBuffer().limit()); + + byte[] bytes = new byte[]{10, 20, 30}; + KVByteBuffer buffer2 = new KVByteBuffer(bytes); + assertArrayEquals(buffer2.array(), bytes); + + + ByteBuffer bb = ByteBuffer.allocate(10); + KVByteBuffer buffer3 = new KVByteBuffer(bb); + buffer3.put(bytes); + buffer3.clear(); + assertArrayEquals(buffer3.getBytes(), bytes); + + // int (4) + byte(3) + assertEquals(7, buffer3.getBuffer().position()); + + ByteBuffer bb2 = buffer3.copyBuffer(); + assertEquals(7, bb2.capacity()); + } +} diff --git a/hugegraph-store/hg-store-test/src/main/java/core/BaseCoreTest.java b/hugegraph-store/hg-store-test/src/main/java/core/BaseCoreTest.java new file mode 100644 index 000000000..9df71adcd --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/core/BaseCoreTest.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package core; + +import java.io.File; +import java.io.IOException; + +import org.apache.commons.io.FileUtils; +import org.junit.After; +import org.junit.BeforeClass; + + +public class BaseCoreTest { + @BeforeClass + public static void init() throws Exception { + + } + + public static void deleteDirectory(File dir) { + try { + FileUtils.deleteDirectory(dir); + } catch (IOException e) { + System.out.printf("Failed to start ....,%s%n", e.getMessage()); + } + } + + @After + public void teardown() throws Exception { + // pass + } +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-test/src/main/java/core/CoreSuiteTest.java b/hugegraph-store/hg-store-test/src/main/java/core/CoreSuiteTest.java new file mode 100644 index 000000000..932e1544b --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/core/CoreSuiteTest.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package core; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +import core.raft.RaftOperationTest; +import core.raft.RaftUtilsTest; +import core.snapshot.HgSnapshotHandlerTest; +import core.store.HgStoreEngineTest; +import core.store.PartitionEngineTest; +import core.store.PartitionInstructionProcessorTest; +import core.store.meta.MetadataKeyHelperTest; +import core.store.meta.PartitionManagerTest; +import core.store.meta.TaskManagerTest; +import core.store.meta.asynctask.CleanTaskTest; +import core.store.util.MiscUtilClassTest; +import core.store.util.PartitionMetaStoreWrapperTest; +import core.store.util.ZipUtilsTest; +import lombok.extern.slf4j.Slf4j; +import util.CopyOnWriteCacheTest; +import util.UnsafeUtilTest; + +@RunWith(Suite.class) +@Suite.SuiteClasses({ + HgCmdClientTest.class, + HgSnapshotHandlerTest.class, + RaftUtilsTest.class, + RaftOperationTest.class, + UnsafeUtilTest.class, + CopyOnWriteCacheTest.class, + MetricServiceTest.class, + TaskManagerTest.class, + CleanTaskTest.class, + MetadataKeyHelperTest.class, + HgStoreEngineTest.class, + PartitionEngineTest.class, + PartitionManagerTest.class, + PartitionMetaStoreWrapperTest.class, + ZipUtilsTest.class, + MiscUtilClassTest.class, + PartitionInstructionProcessorTest.class, + // 尽量放到最后 + HgBusinessImplTest.class +}) + +@Slf4j +public class CoreSuiteTest { + + +} \ No newline at end of file diff --git a/hugegraph-store/hg-store-test/src/main/java/core/CoreTest.java b/hugegraph-store/hg-store-test/src/main/java/core/CoreTest.java new file mode 100644 index 000000000..f89033c14 --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/core/CoreTest.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package core; + +import org.junit.Assert; +import org.junit.Test; + +import lombok.extern.slf4j.Slf4j; + +/** + * @date 2022/10/18 + **/ +@Slf4j +public class CoreTest { + + @Test + public void testDemo() { + String s = "i am core"; + log.info("UT:{}", s); + Assert.assertTrue(s.startsWith("i")); + } +} diff --git a/hugegraph-store/hg-store-test/src/main/java/core/HgBusinessImplTest.java b/hugegraph-store/hg-store-test/src/main/java/core/HgBusinessImplTest.java new file mode 100644 index 000000000..5106f3c71 --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/core/HgBusinessImplTest.java @@ -0,0 +1,178 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package core; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.apache.hugegraph.rocksdb.access.RocksDBFactory; +import org.apache.hugegraph.rocksdb.access.RocksDBSession; +import org.apache.hugegraph.rocksdb.access.ScanIterator; +import org.apache.hugegraph.store.business.BusinessHandler; +import org.apache.hugegraph.util.Bytes; +import org.junit.Assert; +import org.junit.Test; + +import lombok.extern.slf4j.Slf4j; + + +@Slf4j +public class HgBusinessImplTest extends StoreEngineTestBase { + + public BusinessHandler getBusinessHandler() { + return getStoreEngine().getBusinessHandler(); + } + + @Test + public void testPut() throws InterruptedException { + + String graph1 = "testPut-graph1"; + String graph2 = "testPut-graph2"; + + createPartitionEngine(0, graph1); + createPartitionEngine(0, graph2); + + String table = "testPut"; + BusinessHandler handler = getBusinessHandler(); + handler.doPut(graph1, 0, table, "key1".getBytes(), "value1".getBytes()); + handler.doPut(graph1, 0xF, table, "key2".getBytes(), "value1".getBytes()); + handler.doPut(graph1, 0xFF, table, "key3".getBytes(), "value1".getBytes()); + handler.doPut(graph1, 0xFFF, table, "key4".getBytes(), "value1".getBytes()); + + handler.doPut(graph2, 0, table, "key21".getBytes(), "value1".getBytes()); + handler.doPut(graph2, 0xF, table, "key22".getBytes(), "value1".getBytes()); + handler.doPut(graph2, 0xFF, table, "key23".getBytes(), "value1".getBytes()); + handler.doPut(graph2, 0xFFF, table, "key24".getBytes(), "value1".getBytes()); + + System.out.println("--------------------dump all -------"); + dump(handler, graph1, 0); + System.out.println("--------------------dump scan 0 0xff -------"); + ScanIterator iterator = handler.scan(graph1, table, 0, 0xff); + int count = 0; + while (iterator.hasNext()) { + RocksDBSession.BackendColumn entry = iterator.next(); + System.out.println(new String(entry.name) + " -- " + Bytes.toHex(entry.name)); + count++; + } + + Assert.assertEquals(2, count); + + System.out.println("--------------------dump scan prefix -------"); + iterator = handler.scanPrefix(graph1, 0, table, "key".getBytes()); + + count = 0; + while (iterator.hasNext()) { + RocksDBSession.BackendColumn entry = iterator.next(); + System.out.println(new String(entry.name) + " -- " + Bytes.toHex(entry.name)); + count++; + } + + Assert.assertEquals(4, count); + System.out.println("--------------------dump scan range key1 key4 -------"); + iterator = handler.scan(graph1, 0, table, "key1".getBytes(), "key4".getBytes(), + ScanIterator.Trait.SCAN_LT_END); + count = 0; + while (iterator.hasNext()) { + RocksDBSession.BackendColumn entry = iterator.next(); + System.out.println(new String(entry.name) + " -- " + Bytes.toHex(entry.name)); + count++; + } + + Assert.assertEquals(3, count); + boolean cleanResult = handler.cleanPartition(graph1, 0); + Assert.assertTrue(cleanResult); + Thread.sleep(2000); + + handler.closeAll(); + } + + @Test + public void testLoadSnapshot() throws InterruptedException { + + String graph1 = "LoadSnapshot-graph1"; + createPartitionEngine(0, graph1); + BusinessHandler handler = getBusinessHandler(); + + String table = "LoadSnapshot-table"; + for (int i = 0; i < 256; i++) { + handler.doPut(graph1, i, table, ("key" + i).getBytes(), "value1".getBytes()); + } + ScanIterator iterator = handler.scanAll(graph1, table); + + System.out.println(iterator.count()); + + String snapshotPath; + try (RocksDBSession session = handler.getSession(0)) { + snapshotPath = session.getDbPath(); + } + + handler.closeAll(); + + System.out.println("start loadSnapshot"); + handler.loadSnapshot(snapshotPath, graph1, 0, 10); + iterator = handler.scanAll(graph1, table); + Assert.assertEquals(255, iterator.count()); + try (RocksDBSession session = handler.getSession(0)) { + System.out.println(session.getDbPath()); + } + + CountDownLatch latch = new CountDownLatch(1); + RocksDBFactory.getInstance() + .addRocksdbChangedListener(new RocksDBFactory.RocksdbChangedListener() { + @Override + public void onCompacted(String dbName) { + RocksDBFactory.RocksdbChangedListener.super.onCompacted(dbName); + } + + @Override + public void onDBDeleteBegin(String dbName, String filePath) { + RocksDBFactory.RocksdbChangedListener.super.onDBDeleteBegin(dbName, + filePath); + } + + @Override + public void onDBDeleted(String dbName, String filePath) { + latch.countDown(); + } + + @Override + public void onDBSessionReleased(RocksDBSession dbSession) { + RocksDBFactory.RocksdbChangedListener.super.onDBSessionReleased( + dbSession); + } + }); + + latch.await(1, TimeUnit.SECONDS); + + } + + public void dump(BusinessHandler handler, String graph, int partId) { + ScanIterator cfIterator = handler.scanRaw(graph, partId, 0); + while (cfIterator.hasNext()) { + try (ScanIterator iterator = cfIterator.next()) { + byte[] cfName = cfIterator.position(); + System.out.println(graph + "-" + partId + "-" + new String(cfName) + "--------"); + while (iterator.hasNext()) { + RocksDBSession.BackendColumn col = iterator.next(); + System.out.println(new String(col.name) + " -- " + Bytes.toHex(col.name)); + } + } + } + cfIterator.close(); + } +} diff --git a/hugegraph-store/hg-store-test/src/main/java/core/HgCmdClientTest.java b/hugegraph-store/hg-store-test/src/main/java/core/HgCmdClientTest.java new file mode 100644 index 000000000..d1e94c1bc --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/core/HgCmdClientTest.java @@ -0,0 +1,357 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package core; + +import static org.apache.hugegraph.store.client.util.HgStoreClientUtil.toIntBytes; +import static org.apache.hugegraph.store.client.util.HgStoreClientUtil.toOwnerKey; +import static org.apache.hugegraph.store.client.util.HgStoreClientUtil.toStr; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.hugegraph.pd.client.PDClient; +import org.apache.hugegraph.pd.client.PDConfig; +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.store.HgKvEntry; +import org.apache.hugegraph.store.HgKvIterator; +import org.apache.hugegraph.store.HgOwnerKey; +import org.apache.hugegraph.store.HgStoreClient; +import org.apache.hugegraph.store.HgStoreSession; +import org.apache.hugegraph.store.cmd.BatchPutRequest; +import org.apache.hugegraph.store.cmd.BatchPutResponse; +import org.apache.hugegraph.store.cmd.CleanDataRequest; +import org.apache.hugegraph.store.cmd.CleanDataResponse; +import org.apache.hugegraph.store.cmd.HgCmdClient; +import org.apache.hugegraph.store.meta.Store; +import org.apache.hugegraph.store.pd.DefaultPdProvider; +import org.apache.hugegraph.store.pd.PdProvider; +import org.junit.Assert; +import org.junit.Test; + +import com.alipay.sofa.jraft.JRaftUtils; +import com.alipay.sofa.jraft.option.RpcOptions; +import com.alipay.sofa.jraft.util.Endpoint; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class HgCmdClientTest { + private static AtomicLong id; + private final String pdAddress = "127.0.0.1:8686"; + private final String graphName = "hugegraph"; + private final String tableName = "table-1"; + private PdProvider pdProvider; + private HgCmdClient hgCmdClient; + private HgStoreClient storeClient; + private PDClient pdClient; + + public static String getMd5(String txt) { + String rs = ""; + String[] hexDigits = + {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"}; + try { + MessageDigest messageDigest = MessageDigest.getInstance("MD5"); + byte[] b = messageDigest.digest(txt.getBytes()); + StringBuffer resultSb = new StringBuffer(); + for (int i = 0; i < b.length; i++) { + int n = b[i]; + if (n < 0) { + n = 256 + n; + } + int d1 = n / 16; + int d2 = n % 16; + resultSb.append(hexDigits[d1] + hexDigits[d2]); + } + rs = resultSb.toString(); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + return rs; + } + + public static Long getId() { + // 如果需要更长 或者更大冗余空间, 只需要 time * 10^n 即可 + // 当前可保证1毫秒 生成 10000条不重复 + Long time = Long.valueOf(new SimpleDateFormat("HHmmssSSS").format(new Date())) * 10000 + + (long) (Math.random() * 100); +// Long time = Long.valueOf(new SimpleDateFormat("MMddhhmmssSSS").format(new Date()) +// .toString()); +// System.out.println(time); + if (id == null) { + id = new AtomicLong(time); + return id.get(); + } + if (time <= id.get()) { + id.addAndGet(1); + } else { + id = new AtomicLong(time); + } + return id.get(); + } + + @Test + public void testGetStoreInfo() { + + hgCmdClient = new HgCmdClient(); + pdProvider = new DefaultPdProvider(pdAddress); + hgCmdClient.init(new RpcOptions(), null); + + Store response = hgCmdClient.getStoreInfo(pdAddress); + + } + + // @Test + public void testBatchPut() { + + hgCmdClient = new HgCmdClient(); + pdProvider = new DefaultPdProvider(pdAddress); + hgCmdClient.init(new RpcOptions(), new HgCmdClient.PartitionAgent() { + @Override + public Endpoint getPartitionLeader(String graphName, int partitionId) { + Metapb.Shard shard = pdProvider.getPartitionLeader(graphName, partitionId); + return JRaftUtils.getEndPoint( + pdProvider.getStoreByID(shard.getStoreId()).getRaftAddress()); + } + }); + + storeClient = HgStoreClient.create(PDConfig.of(pdAddress) + .setEnableCache(true)); + HgStoreSession session = storeClient.openSession(graphName); + pdClient = storeClient.getPdClient(); + session.createTable(tableName); + String createGraph = "create_graph"; + HgOwnerKey hgOwnerKey = toOwnerKey(createGraph); + // 需要写数据,才会创建图 + session.put(tableName, + hgOwnerKey, createGraph.getBytes(StandardCharsets.UTF_8)); + Assert.assertEquals(createGraph, toStr(session.get(tableName, hgOwnerKey))); + + Integer partId = 0; + String key = "key-1"; + List kvs = new LinkedList<>(); + int x = 0; + for (int i = 1; i <= 3; i++) { + key = "key-" + i; + BatchPutRequest.KV kv = BatchPutRequest.KV.of(tableName, 1, + key.getBytes(StandardCharsets.UTF_8), + key.getBytes(StandardCharsets.UTF_8)); + kvs.add(kv); + + BatchPutRequest request = new BatchPutRequest(); + request.setGraphName(graphName); + request.setPartitionId(partId); + request.setEntries(kvs); + + try { + BatchPutResponse response = hgCmdClient.batchPut(request); + if (response == null) { + log.error("response is null "); + } else if (response.getStatus() == null) { + log.error("response status is null"); + } + + log.info("response status:{} {}", response.getStatus(), i); + + Assert.assertTrue(response.getStatus().isOK()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + + HgKvIterator hgKvIterator = session.scanIterator(tableName); + Assert.assertTrue(hgKvIterator.hasNext()); + boolean findKey = false; + while (hgKvIterator.hasNext()) { + HgKvEntry entry = hgKvIterator.next(); + if (toStr(entry.key()).equals(key) && toStr(entry.value()).equals(key)) { + log.info("key={} value={}", toStr(entry.key()), toStr(entry.value())); + findKey = true; + } + } + Assert.assertTrue(findKey); + } + + // @Test + public void testCleanData() { + + hgCmdClient = new HgCmdClient(); + pdProvider = new DefaultPdProvider(pdAddress); + hgCmdClient.init(new RpcOptions(), new HgCmdClient.PartitionAgent() { + @Override + public Endpoint getPartitionLeader(String graphName, int partitionId) { + Metapb.Shard shard = pdProvider.getPartitionLeader(graphName, partitionId); + return JRaftUtils.getEndPoint( + pdProvider.getStoreByID(shard.getStoreId()).getRaftAddress()); + } + }); + + storeClient = HgStoreClient.create(PDConfig.of(pdAddress) + .setEnableCache(true)); + HgStoreSession session = storeClient.openSession(graphName); + pdClient = storeClient.getPdClient(); + session.createTable(tableName); + String createGraph = "create_graph"; + HgOwnerKey hgOwnerKey = toOwnerKey(createGraph); + // 需要写数据,才会创建图 + session.put(tableName, + hgOwnerKey, createGraph.getBytes(StandardCharsets.UTF_8)); + Assert.assertEquals(createGraph, toStr(session.get(tableName, hgOwnerKey))); + + Integer partId = 0; + + Metapb.Partition pt = Metapb.Partition.newBuilder().build(); + CleanDataRequest request = new CleanDataRequest(); + request.setGraphName(graphName); + request.setPartitionId(partId); + + try { + CleanDataResponse response = hgCmdClient.cleanData(request); + if (response == null) { + log.error("response is null "); + } else if (response.getStatus() == null) { + log.error("response status is null"); + } + + log.info("response status:{} ", response.getStatus()); + + Assert.assertTrue(response.getStatus().isOK()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Test + public void testUpdatePartitionLeader() { + hgCmdClient = new HgCmdClient(); + pdProvider = new DefaultPdProvider(pdAddress); + hgCmdClient.init(new RpcOptions(), new HgCmdClient.PartitionAgent() { + @Override + public Endpoint getPartitionLeader(String graphName, int partitionId) { + Metapb.Shard shard = pdProvider.getPartitionLeader(graphName, partitionId); + return JRaftUtils.getEndPoint( + pdProvider.getStoreByID(shard.getStoreId()).getRaftAddress()); + } + }); + + storeClient = HgStoreClient.create(PDConfig.of(pdAddress) + .setEnableCache(true)); + HgStoreSession session = storeClient.openSession(graphName); + pdClient = storeClient.getPdClient(); + session.createTable(tableName); + String createGraph = "create_graph"; + HgOwnerKey hgOwnerKey = toOwnerKey(createGraph); + // 需要写数据,才会创建图 + session.put(tableName, + hgOwnerKey, createGraph.getBytes(StandardCharsets.UTF_8)); + Assert.assertEquals(createGraph, toStr(session.get(tableName, hgOwnerKey))); + + } + + @Test + public void testData() { + hgCmdClient = new HgCmdClient(); + pdProvider = new DefaultPdProvider(pdAddress); + hgCmdClient.init(new RpcOptions(), new HgCmdClient.PartitionAgent() { + @Override + public Endpoint getPartitionLeader(String graphName, int partitionId) { + Metapb.Shard shard = pdProvider.getPartitionLeader(graphName, partitionId); + return JRaftUtils.getEndPoint( + pdProvider.getStoreByID(shard.getStoreId()).getRaftAddress()); + } + }); + + storeClient = HgStoreClient.create(PDConfig.of(pdAddress) + .setEnableCache(true)); + HgStoreSession session = storeClient.openSession("hugegraphtest"); + pdClient = storeClient.getPdClient(); + session.truncate(); + + String tableName = "cli-table"; + int loop = 3; + + for (int i = 0; i < loop; i++) { + HgOwnerKey hgOwnerKey = toOwnerKey(i + "owner:" + i, i + "k:" + i); + session.put(tableName, hgOwnerKey, toIntBytes(i)); + } + + try { + HgKvIterator iterable = session.scanIterator(tableName); + int x = 0; + while (iterable.hasNext()) { + HgKvEntry entry = iterable.next(); + log.info("data:{}-{}", toStr(entry.key()), entry.value()); + x++; + } + log.info("x={}", x); + Assert.assertEquals(x, loop); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Test + public void testComPressionData() { + + hgCmdClient = new HgCmdClient(); + pdProvider = new DefaultPdProvider(pdAddress); + hgCmdClient.init(new RpcOptions(), new HgCmdClient.PartitionAgent() { + @Override + public Endpoint getPartitionLeader(String graphName, int partitionId) { + Metapb.Shard shard = pdProvider.getPartitionLeader(graphName, partitionId); + return JRaftUtils.getEndPoint( + pdProvider.getStoreByID(shard.getStoreId()).getRaftAddress()); + } + }); + + storeClient = HgStoreClient.create(PDConfig.of(pdAddress) + .setEnableCache(true)); + HgStoreSession session = storeClient.openSession("hugegraphtest"); + pdClient = storeClient.getPdClient(); + session.truncate(); + + String tableName = "cli-table"; + int loop = 10; + + for (int i = 0; i < loop; i++) { + String key = "d41d8cd98f00b204e9800998ecf8427e" + getMd5("a" + i) + getId(); + String value = "10000" + getId() + getId(); + HgOwnerKey hgOwnerKey = toOwnerKey("d41d8cd98f00b204e9800998ecf8427e", key); + session.put(tableName, hgOwnerKey, value.getBytes()); + } + + try { + HgKvIterator iterable = session.scanIterator(tableName); + int x = 0; + while (iterable.hasNext()) { + HgKvEntry entry = iterable.next(); + x++; + } + log.info("x={}", x); + Assert.assertEquals(x, loop); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/hugegraph-store/hg-store-test/src/main/java/core/MetricServiceTest.java b/hugegraph-store/hg-store-test/src/main/java/core/MetricServiceTest.java new file mode 100644 index 000000000..8ea2a0245 --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/core/MetricServiceTest.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package core; + +import java.util.Map; + +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.store.HgStoreEngine; +import org.apache.hugegraph.store.metric.HgMetricService; +import org.apache.hugegraph.store.metric.SystemMetricService; +import org.junit.Before; +import org.junit.Test; + +public class MetricServiceTest { + + private SystemMetricService service; + private HgMetricService hgService; + + @Before + public void setUp() { + service = new SystemMetricService(); + HgStoreEngine instance = HgStoreEngine.getInstance(); + service.setStoreEngine(instance); + hgService = HgMetricService.getInstance().setHgStoreEngine(instance); + } + + @Test + public void testGetStorageEngine() { + HgStoreEngine result = service.getStorageEngine(); + } + + @Test + public void testGetSystemMetrics() { + try { + Map systemMetrics = service.getSystemMetrics(); + Thread.sleep(1000); + systemMetrics = service.getSystemMetrics(); + } catch (Exception e) { + + } + } + + @Test + public void testGetHgMetrics() { + // Setup + Metapb.StoreStats.Builder systemMetrics = hgService.getMetrics(); + } +} diff --git a/hugegraph-store/hg-store-test/src/main/java/core/StoreEngineTestBase.java b/hugegraph-store/hg-store-test/src/main/java/core/StoreEngineTestBase.java new file mode 100644 index 000000000..9cdb27b79 --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/core/StoreEngineTestBase.java @@ -0,0 +1,135 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package core; + +import java.io.File; +import java.util.HashMap; + +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.store.HgStoreEngine; +import org.apache.hugegraph.store.PartitionEngine; +import org.apache.hugegraph.store.business.DefaultDataMover; +import org.apache.hugegraph.store.meta.Partition; +import org.apache.hugegraph.store.meta.ShardGroup; +import org.apache.hugegraph.store.options.HgStoreEngineOptions; +import org.apache.hugegraph.store.options.RaftRocksdbOptions; +import org.apache.hugegraph.store.pd.FakePdServiceProvider; +import org.junit.AfterClass; +import org.junit.BeforeClass; + +import com.alipay.sofa.jraft.util.StorageOptionsFactory; + +import lombok.extern.slf4j.Slf4j; +import util.UnitTestBase; + +/** + * 使用FakePd 和 FakePdOptions,初始化HgStoreEngine,该类的各项get函数可用 + */ +@Slf4j +public class StoreEngineTestBase { + private static final String DB_PATH = "/tmp/junit"; + + @BeforeClass + public static void initEngine() { + UnitTestBase.deleteDir(new File(DB_PATH)); + + HgStoreEngineOptions options = new HgStoreEngineOptions(); + options.setDataPath(DB_PATH); + options.setRaftPath(DB_PATH); + options.setFakePD(true); + options.setRocksdbConfig(new HashMap<>() {{ + put("rocksdb.write_buffer_size", "1048576"); + }}); + options.setGrpcAddress("127.0.0.1:6511"); + options.setRaftAddress("127.0.0.1:6510"); + options.setDataTransfer(new DefaultDataMover()); + + options.setFakePdOptions(new HgStoreEngineOptions.FakePdOptions() {{ + setStoreList("127.0.0.1"); + setPeersList("127.0.0.1"); + setPartitionCount(1); + setShardCount(1); + }}); + + StorageOptionsFactory.clear(); + RaftRocksdbOptions.initRocksdbGlobalConfig(options.getRocksdbConfig()); + + HgStoreEngine.getInstance().init(options); + } + + public static Partition getPartition(int partitionId) { + return getPartition(partitionId, "graph0"); + } + + public static Partition getPartition(int partitionId, String graphName) { + Partition partition = new Partition(); + partition.setId(partitionId); + partition.setGraphName(graphName); + partition.setStartKey(0); + partition.setEndKey(65535); + partition.setWorkState(Metapb.PartitionState.PState_Normal); + partition.setVersion(1); + return partition; + } + + /** + * 创建 分区为0的partition engine. 该分区1个shard,为leader, graph name: graph0 + * + * @return + */ + public static PartitionEngine createPartitionEngine(int partitionId) { + return createPartitionEngine(partitionId, "graph0"); + } + + + public static PartitionEngine createPartitionEngine(int partitionId, String graphName) { + Metapb.Shard shard = Metapb.Shard.newBuilder() + .setStoreId(FakePdServiceProvider.makeStoreId( + "127.0.0.1:6511")) + .setRole(Metapb.ShardRole.Leader) + .build(); + + Metapb.ShardGroup shardGroup = Metapb.ShardGroup.newBuilder() + .setId(partitionId) + .setConfVer(1) + .setVersion(1) + .setState( + Metapb.PartitionState.PState_Normal) + .addShards(shard) + .build(); + + getStoreEngine().getPartitionManager().updateShardGroup(ShardGroup.from(shardGroup)); + + var engine = getStoreEngine().createPartitionEngine(getPartition(partitionId, graphName)); + engine.waitForLeader(2000); + return engine; + } + + public static HgStoreEngine getStoreEngine() { + return HgStoreEngine.getInstance(); + } + + @AfterClass + public static void shutDownEngine() { + try { + HgStoreEngine.getInstance().shutdown(); + } catch (Exception e) { + log.error("shut down engine error: {}", e.getMessage()); + } + } +} diff --git a/hugegraph-store/hg-store-test/src/main/java/core/raft/HgStoreStateMachineTest.java b/hugegraph-store/hg-store-test/src/main/java/core/raft/HgStoreStateMachineTest.java new file mode 100644 index 000000000..0b1aba27c --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/core/raft/HgStoreStateMachineTest.java @@ -0,0 +1,245 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package core.raft; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +import org.apache.hugegraph.store.raft.HgStoreStateMachine; +import org.apache.hugegraph.store.raft.RaftClosure; +import org.apache.hugegraph.store.raft.RaftOperation; +import org.apache.hugegraph.store.raft.RaftStateListener; +import org.apache.hugegraph.store.raft.RaftTaskHandler; +import org.apache.hugegraph.store.snapshot.HgSnapshotHandler; +import org.apache.hugegraph.store.util.HgStoreException; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import com.alipay.sofa.jraft.Closure; +import com.alipay.sofa.jraft.Iterator; +import com.alipay.sofa.jraft.Status; +import com.alipay.sofa.jraft.conf.Configuration; +import com.alipay.sofa.jraft.entity.LeaderChangeContext; +import com.alipay.sofa.jraft.entity.PeerId; +import com.alipay.sofa.jraft.entity.Task; +import com.alipay.sofa.jraft.error.RaftError; +import com.alipay.sofa.jraft.error.RaftException; + +@RunWith(MockitoJUnitRunner.class) +public class HgStoreStateMachineTest { + + @Mock + private HgSnapshotHandler mockSnapshotHandler; + + private HgStoreStateMachine hgStoreStateMachineUnderTest; + + @Before + public void setUp() { + hgStoreStateMachineUnderTest = new HgStoreStateMachine(0, mockSnapshotHandler); + } + + @Test + public void testAddTaskHandler() { + // Setup + final RaftTaskHandler handler = new RaftTaskHandler() { + @Override + public boolean invoke(int groupId, byte[] request, RaftClosure response) throws + HgStoreException { + return false; + } + + @Override + public boolean invoke(int groupId, byte methodId, Object req, RaftClosure response) + throws HgStoreException { + return false; + } + }; + + // Run the test + hgStoreStateMachineUnderTest.addTaskHandler(handler); + + // Verify the results + } + + @Test + public void testAddStateListener() { + // Setup + final RaftStateListener mockListener = new RaftStateListener() { + @Override + public void onLeaderStart(long newTerm) { + + } + + @Override + public void onError(RaftException e) { + + } + }; + + // Run the test + hgStoreStateMachineUnderTest.addStateListener(mockListener); + + // Verify the results + } + + @Test + public void testIsLeader() { + // Setup + // Run the test + final boolean result = hgStoreStateMachineUnderTest.isLeader(); + + // Verify the results + assertFalse(result); + } + + @Test + public void testOnApply() { + RaftOperation op = RaftOperation.create((byte) 0b0); + final Task task = new Task(); + task.setData(ByteBuffer.wrap(op.getValues())); + task.setDone(new HgStoreStateMachine.RaftClosureAdapter(op, closure -> { + + })); + + List tasks = new ArrayList<>(); + tasks.add(task); + // Setup + final Iterator inter = new Iterator() { + final java.util.Iterator iterator = tasks.iterator(); + Task task; + + @Override + public ByteBuffer getData() { + return task.getData(); + } + + @Override + public long getIndex() { + return 0; + } + + @Override + public long getTerm() { + return 0; + } + + @Override + public Closure done() { + return null; + } + + @Override + public void setErrorAndRollback(long ntail, Status st) { + + } + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public ByteBuffer next() { + task = iterator.next(); + return task.getData(); + } + }; + + // Run the test + hgStoreStateMachineUnderTest.onApply(inter); + + // Verify the results + } + + @Test + public void testGetLeaderTerm() { + // Setup + // Run the test + final long result = hgStoreStateMachineUnderTest.getLeaderTerm(); + + // Verify the results + assertEquals(-1L, result); + } + + + @Test + public void testOnLeaderStart() { + // Setup + // Run the test + hgStoreStateMachineUnderTest.onLeaderStart(0L); + + // Verify the results + } + + @Test + public void testOnLeaderStop() { + // Setup + final Status status = new Status(RaftError.UNKNOWN, "fmt", "args"); + + // Run the test + hgStoreStateMachineUnderTest.onLeaderStop(status); + + // Verify the results + } + + @Test + public void testOnStartFollowing() { + // Setup + final LeaderChangeContext ctx = + new LeaderChangeContext(new PeerId("ip", 0, 0, 0), "groupId", 0L, + new Status(RaftError.UNKNOWN, "fmt", "args")); + + // Run the test + hgStoreStateMachineUnderTest.onStartFollowing(ctx); + + // Verify the results + } + + @Test + public void testOnStopFollowing() { + // Setup + final LeaderChangeContext ctx = + new LeaderChangeContext(new PeerId("ip", 0, 0, 0), "groupId", 0L, + new Status(RaftError.UNKNOWN, "fmt", "args")); + + // Run the test + hgStoreStateMachineUnderTest.onStopFollowing(ctx); + + // Verify the results + } + + @Test + public void testOnConfigurationCommitted() { + // Setup + final Configuration conf = new Configuration(List.of(new PeerId("ip", 0, 0, 0)), + List.of(new PeerId("ip", 0, 0, 0))); + + // Run the test + hgStoreStateMachineUnderTest.onConfigurationCommitted(conf); + + // Verify the results + } + +} diff --git a/hugegraph-store/hg-store-test/src/main/java/core/raft/MockStateMachine.java b/hugegraph-store/hg-store-test/src/main/java/core/raft/MockStateMachine.java new file mode 100644 index 000000000..8ebf2c94f --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/core/raft/MockStateMachine.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package core.raft; + +import com.alipay.sofa.jraft.Iterator; +import com.alipay.sofa.jraft.core.StateMachineAdapter; + +public class MockStateMachine extends StateMachineAdapter { + @Override + public void onApply(Iterator iter) { + while (iter.hasNext()) { + iter.next(); + } + } +} diff --git a/hugegraph-store/hg-store-test/src/main/java/core/raft/RaftOperationTest.java b/hugegraph-store/hg-store-test/src/main/java/core/raft/RaftOperationTest.java new file mode 100644 index 000000000..0ed9b09ca --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/core/raft/RaftOperationTest.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package core.raft; + +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.store.raft.RaftOperation; +import org.junit.Before; +import org.junit.Test; + +import com.google.protobuf.GeneratedMessageV3; + +public class RaftOperationTest { + + private RaftOperation raftOperationUnderTest; + + @Before + public void setUp() { + raftOperationUnderTest = new RaftOperation(); + } + + + @Test + public void testCreate1() { + // Run the test + final RaftOperation result = RaftOperation.create((byte) 0b0); + assertEquals(null, result.getReq()); + assertEquals((byte) 0b0, result.getOp()); + } + + @Test + public void testCreate2() { + // Run the test + final RaftOperation result = RaftOperation.create((byte) 0b0, "content".getBytes(), "req"); + assertArrayEquals("content".getBytes(), result.getValues()); + assertEquals("req", result.getReq()); + assertEquals((byte) 0b0, result.getOp()); + } + + @Test + public void testCreate3() { + // Run the test + final RaftOperation result = RaftOperation.create((byte) 0b0, "req"); + assertEquals("req", result.getReq()); + assertEquals((byte) 0b0, result.getOp()); + } + + @Test + public void testCreate4() throws Exception { + // Setup + final GeneratedMessageV3 req = Metapb.Graph.newBuilder().setGraphName("name").build(); + + // Run the test + final RaftOperation result = RaftOperation.create((byte) 0b0, req); + assertEquals((byte) 0b0, result.getOp()); + + } +} diff --git a/hugegraph-store/hg-store-test/src/main/java/core/raft/RaftUtilsTest.java b/hugegraph-store/hg-store-test/src/main/java/core/raft/RaftUtilsTest.java new file mode 100644 index 000000000..e9c66eba6 --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/core/raft/RaftUtilsTest.java @@ -0,0 +1,180 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package core.raft; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.util.Collections; +import java.util.List; + +import org.apache.hugegraph.store.raft.util.RaftUtils; +import org.junit.Before; +import org.junit.Test; + +import com.alipay.sofa.jraft.Node; +import com.alipay.sofa.jraft.NodeManager; +import com.alipay.sofa.jraft.conf.Configuration; +import com.alipay.sofa.jraft.core.NodeImpl; +import com.alipay.sofa.jraft.entity.PeerId; +import com.alipay.sofa.jraft.option.NodeOptions; +import com.alipay.sofa.jraft.util.Endpoint; + +import util.UnitTestBase; + +public class RaftUtilsTest { + final String ip = "127.0.0.1"; + final int port = 12456; + final String dataPath = "tmp/raftUtils"; + + @Before + public void setUp() { + + UnitTestBase.deleteDir(new File(dataPath)); + new File(dataPath).mkdirs(); + } + + @Test + public void testGetAllEndpoints1() throws InterruptedException { + final Endpoint addr = new Endpoint(ip, port); + final PeerId peer = new PeerId(addr, 0); + NodeManager.getInstance().addAddress(addr); + final NodeOptions nodeOptions = new NodeOptions(); + nodeOptions.setFsm(new MockStateMachine()); + nodeOptions.setLogUri(this.dataPath + File.separator + "log"); + nodeOptions.setRaftMetaUri(this.dataPath + File.separator + "meta"); + nodeOptions.setSnapshotUri(this.dataPath + File.separator + "snapshot"); + nodeOptions.setInitialConf(new Configuration(Collections.singletonList(peer))); + final Node node = new NodeImpl("unittest", new PeerId(addr, 0)); + assertTrue(node.init(nodeOptions)); + node.isLeader(true); + + // Run the test + final List result = RaftUtils.getAllEndpoints(node); + // Verify the results + assertEquals(1, result.size()); + node.shutdown(); + node.join(); + + } + + @Test + public void testGetAllEndpoints2() { + final Endpoint addr = new Endpoint(ip, port); + final PeerId peer = new PeerId(addr, 0); + + Configuration conf = new Configuration(Collections.singletonList(peer)); + // Run the test + final List result = RaftUtils.getAllEndpoints(conf); + + // Verify the results + assertEquals(1, result.size()); + } + + @Test + public void testGetPeerEndpoints1() throws InterruptedException { + final Endpoint addr = new Endpoint(ip, port); + final PeerId peer = new PeerId(addr, 0); + NodeManager.getInstance().addAddress(addr); + final NodeOptions nodeOptions = new NodeOptions(); + nodeOptions.setFsm(new MockStateMachine()); + nodeOptions.setLogUri(this.dataPath + File.separator + "log"); + nodeOptions.setRaftMetaUri(this.dataPath + File.separator + "meta"); + nodeOptions.setSnapshotUri(this.dataPath + File.separator + "snapshot"); + nodeOptions.setInitialConf(new Configuration(Collections.singletonList(peer))); + final Node node = new NodeImpl("unittest", new PeerId(addr, 0)); + assertTrue(node.init(nodeOptions)); + node.isLeader(true); + + + // Run the test + final List result = RaftUtils.getPeerEndpoints(node); + // Verify the results + assertEquals(1, result.size()); + node.shutdown(); + node.join(); + } + + @Test + public void testGetPeerEndpoints2() { + // Setup + final Configuration conf = new Configuration(List.of(new PeerId("ip", 0, 0, 0)), + List.of(new PeerId("ip", 0, 0, 0))); + + // Run the test + final List result = RaftUtils.getPeerEndpoints(conf); + + // Verify the results + assertEquals(1, result.size()); + } + + @Test + public void testGetLearnerEndpoints1() throws InterruptedException { + // Setup + final Endpoint addr = new Endpoint(ip, port); + final PeerId peer = new PeerId(addr, 0); + final PeerId peer2 = new PeerId(new Endpoint(ip, 13456), 0); + NodeManager.getInstance().addAddress(addr); + final NodeOptions nodeOptions = new NodeOptions(); + nodeOptions.setFsm(new MockStateMachine()); + nodeOptions.setLogUri(this.dataPath + File.separator + "log"); + nodeOptions.setRaftMetaUri(this.dataPath + File.separator + "meta"); + nodeOptions.setSnapshotUri(this.dataPath + File.separator + "snapshot"); + nodeOptions.setInitialConf(new Configuration(Collections.singletonList(peer), + Collections.singletonList(peer2))); + final Node node = new NodeImpl("unittest", new PeerId(addr, 0)); + assertTrue(node.init(nodeOptions)); + node.isLeader(true); + // Run the test + final List result = RaftUtils.getLearnerEndpoints(node); + + // Verify the results + assertEquals(1, result.size()); + node.shutdown(); + node.join(); + } + + @Test + public void testGetLearnerEndpoints2() { + // Setup + final Configuration conf = new Configuration(List.of(new PeerId("ip", 0, 0, 0)), + List.of(new PeerId("ip", 0, 0, 0))); + + // Run the test + final List result = RaftUtils.getLearnerEndpoints(conf); + + // Verify the results + assertEquals(1, result.size()); + } + + @Test + public void testConfigurationEquals() { + // Setup + final Configuration oldConf = new Configuration(List.of(new PeerId("ip", 0, 0, 0)), + List.of(new PeerId("ip", 0, 0, 0))); + final Configuration newConf = new Configuration(List.of(new PeerId("ip", 0, 0, 0)), + List.of(new PeerId("ip", 0, 0, 0))); + + // Run the test + final boolean result = RaftUtils.configurationEquals(oldConf, newConf); + + // Verify the results + assertTrue(result); + } +} diff --git a/hugegraph-store/hg-store-test/src/main/java/core/snapshot/HgSnapshotHandlerTest.java b/hugegraph-store/hg-store-test/src/main/java/core/snapshot/HgSnapshotHandlerTest.java new file mode 100644 index 000000000..efd4a6ca3 --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/core/snapshot/HgSnapshotHandlerTest.java @@ -0,0 +1,188 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package core.snapshot; + +import static org.junit.Assert.assertEquals; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.io.FileUtils; +import org.apache.hugegraph.store.meta.Partition; +import org.apache.hugegraph.store.snapshot.HgSnapshotHandler; +import org.junit.Before; +import org.junit.Test; + +import com.alipay.sofa.jraft.entity.RaftOutter; +import com.alipay.sofa.jraft.storage.snapshot.SnapshotReader; +import com.alipay.sofa.jraft.storage.snapshot.SnapshotWriter; +import com.google.protobuf.Message; + +import core.StoreEngineTestBase; + + +public class HgSnapshotHandlerTest extends StoreEngineTestBase { + + private static HgSnapshotHandler hgSnapshotHandlerUnderTest; + + @Before + public void setUp() throws IOException { + hgSnapshotHandlerUnderTest = new HgSnapshotHandler(createPartitionEngine(0)); + FileUtils.forceMkdir(new File("/tmp/snapshot")); + FileUtils.forceMkdir(new File("/tmp/snapshot/data")); + } + + @Test + public void testGetPartitions() { + // Run the test + final Map result = hgSnapshotHandlerUnderTest.getPartitions(); + // Verify the results + assertEquals(1, result.size()); + } + + @Test + public void testOnSnapshotSaveAndLoad() { + String path = "/tmp/snapshot"; + // Setup + final SnapshotWriter writer = new SnapshotWriter() { + @Override + public boolean saveMeta(RaftOutter.SnapshotMeta meta) { + return false; + } + + @Override + public boolean addFile(String fileName, Message fileMeta) { + return false; + } + + @Override + public boolean removeFile(String fileName) { + return false; + } + + @Override + public void close(boolean keepDataOnError) throws IOException { + + } + + @Override + public boolean init(Void opts) { + return false; + } + + @Override + public void shutdown() { + + } + + @Override + public String getPath() { + return path; + } + + @Override + public Set listFiles() { + return null; + } + + @Override + public Message getFileMeta(String fileName) { + return null; + } + + @Override + public void close() throws IOException { + + } + }; + + // Run the test + hgSnapshotHandlerUnderTest.onSnapshotSave(writer); + + // Verify the results + + // Setup + final SnapshotReader reader = new SnapshotReader() { + final String path = "/tmp/snapshot"; + + @Override + public RaftOutter.SnapshotMeta load() { + return null; + } + + @Override + public String generateURIForCopy() { + return null; + } + + @Override + public boolean init(Void opts) { + return false; + } + + @Override + public void shutdown() { + + } + + @Override + public String getPath() { + return path; + } + + @Override + public Set listFiles() { + return null; + } + + @Override + public Message getFileMeta(String fileName) { + return null; + } + + @Override + public void close() throws IOException { + + } + }; + + // Run the test + hgSnapshotHandlerUnderTest.onSnapshotLoad(reader, 0L); + } + + + @Test + public void testTrimStartPath() { + assertEquals("str", HgSnapshotHandler.trimStartPath("str", "prefix")); + } + + @Test + public void testFindFileList() { + // Setup + final File dir = new File("filename.txt"); + final File rootDir = new File("filename.txt"); + + // Run the test + HgSnapshotHandler.findFileList(dir, rootDir, List.of("value")); + + // Verify the results + } +} diff --git a/hugegraph-store/hg-store-test/src/main/java/core/store/HgStoreEngineTest.java b/hugegraph-store/hg-store-test/src/main/java/core/store/HgStoreEngineTest.java new file mode 100644 index 000000000..a72cf3b63 --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/core/store/HgStoreEngineTest.java @@ -0,0 +1,175 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package core.store; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.util.List; + +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.store.HgStoreEngine; +import org.apache.hugegraph.store.pd.FakePdServiceProvider; +import org.apache.hugegraph.testutil.Assert; +import org.junit.Before; +import org.junit.Test; + +import core.StoreEngineTestBase; + +public class HgStoreEngineTest extends StoreEngineTestBase { + + private HgStoreEngine engine; + + @Before + public void setup() { + engine = HgStoreEngine.getInstance(); + } + + @Test + public void testGetInstance() { + assertNotNull(HgStoreEngine.getInstance()); + } + + @Test + public void testStateChanged() { + createPartitionEngine(0); + var storeId = FakePdServiceProvider.makeStoreId("127.0.0.1:6511"); + var store = engine.getPartitionManager().getStore(storeId); + engine.stateChanged(store, Metapb.StoreState.Offline, Metapb.StoreState.Up); + assertEquals(engine.getPartitionEngines().size(), 1); + } + + @Test + public void testCreatePartitionEngine() { + var partition = getPartition(0); + assertNotNull(engine.createPartitionEngine(partition)); + } + + @Test + public void testCreatePartitionGroups() { + var partition = getPartition(0); + engine.createPartitionGroups(partition); + } + + @Test + public void testDestroyPartitionEngine() { + createPartitionEngine(16); + engine.destroyPartitionEngine(16, List.of("graph0")); + // assertEquals(engine.getPartitionEngines().size(), 0); + } + + @Test + public void testDeletePartition() { + createPartitionEngine(0); + engine.deletePartition(0, "graph0"); + // TODO: check logic + assertEquals(engine.getPartitionEngines().size(), 1); + } + + @Test + public void testGetLeaderPartition() throws InterruptedException { + createPartitionEngine(0); + assertEquals(engine.getLeaderPartition().size(), 1); + } + + @Test + public void testGetAlivePeers() throws InterruptedException { + createPartitionEngine(0); + assertEquals(engine.getAlivePeers(0).size(), 1); + } + + @Test + public void testGetLeaderTerm() { + createPartitionEngine(0); + // no vote + assertEquals(engine.getLeaderTerm(0), -1); + } + + @Test + public void testGetCommittedIndex() throws InterruptedException { + createPartitionEngine(0); + // write something background + Assert.assertTrue(engine.getCommittedIndex(0) > 0); + } + + @Test + public void testGetRaftRpcServer() { + assertNotNull(engine.getRaftRpcServer()); + } + + @Test + public void testGetPartitionManager() { + assertNotNull(engine.getPartitionManager()); + } + + @Test + public void testGetDataMover() { + assertNotNull(engine.getDataMover()); + } + + @Test + public void testGetPdProvider() { + assertNotNull(engine.getPdProvider()); + } + + @Test + public void testGetCmdClient() { + assertNotNull(engine.getHgCmdClient()); + } + + @Test + public void testGetHeartbeatService() { + assertNotNull(engine.getHeartbeatService()); + } + + @Test + public void testIsClusterReady() throws InterruptedException { + // wait heart beat + Thread.sleep(2000); + assertNotNull(engine.isClusterReady()); + } + + @Test + public void testGetDataLocations() { + assertEquals(engine.getDataLocations().size(), 1); + } + + @Test + public void testGetPartitionEngine() { + createPartitionEngine(0); + assertNotNull(engine.getPartitionEngine(0)); + } + + @Test + public void testGetPartitionEngines() { + createPartitionEngine(0); + assertEquals(engine.getPartitionEngines().size(), 1); + } + + @Test + public void testGetNodeMetrics() { + assertNotNull(engine.getNodeMetrics()); + } + + @Test + public void testGetRaftGroupCount() { + createPartitionEngine(0); + assertEquals(engine.getRaftGroupCount(), 1); + } + +} diff --git a/hugegraph-store/hg-store-test/src/main/java/core/store/PartitionEngineTest.java b/hugegraph-store/hg-store-test/src/main/java/core/store/PartitionEngineTest.java new file mode 100644 index 000000000..63162d4d3 --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/core/store/PartitionEngineTest.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package core.store; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import org.apache.hugegraph.store.PartitionEngine; +import org.junit.Before; +import org.junit.Test; + +import core.StoreEngineTestBase; + +public class PartitionEngineTest extends StoreEngineTestBase { + + PartitionEngine engine; + + @Before + public void setup() { + engine = createPartitionEngine(0); + } + + @Test + public void testHasPartition() { + assertTrue(engine.hasPartition("graph0")); + } + + @Test + public void testGetGroupId() { + assertEquals(engine.getGroupId().intValue(), 0); + } + + @Test + public void testGetShardGroup() { + assertEquals(engine.getShardGroup().getShards().size(), 1); + } + + @Test + public void testIsLeader() { + System.out.println(engine.isLeader()); + } + + @Test + public void testGetLeader() { + assertEquals(engine.getLeader().toString(), engine.getOptions().getRaftAddress()); + } + + @Test + public void testGetAlivePeers() { + try { + System.out.println(engine.getAlivePeers().size()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Test + public void testGetRaftNode() { + assertNotNull(engine.getRaftNode()); + } + + @Test + public void testGetPartitions() { + assertEquals(engine.getPartitions().get("graph0").getId(), 0); + } + + @Test + public void testGetPartition() { + assertEquals(engine.getPartition("graph0").getId(), 0); + } + + @Test + public void testGetCommittedIndex() throws InterruptedException { + Thread.sleep(1000); + System.out.println(engine.getCommittedIndex()); + } + +} diff --git a/hugegraph-store/hg-store-test/src/main/java/core/store/PartitionInstructionProcessorTest.java b/hugegraph-store/hg-store-test/src/main/java/core/store/PartitionInstructionProcessorTest.java new file mode 100644 index 000000000..1d76817f8 --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/core/store/PartitionInstructionProcessorTest.java @@ -0,0 +1,110 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package core.store; + +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.pd.grpc.pulse.DbCompaction; +import org.apache.hugegraph.pd.grpc.pulse.SplitPartition; +import org.apache.hugegraph.pd.grpc.pulse.TransferLeader; +import org.apache.hugegraph.store.PartitionInstructionProcessor; +import org.apache.hugegraph.store.pd.FakePdServiceProvider; +import org.junit.Before; +import org.junit.Test; + +import core.StoreEngineTestBase; + +public class PartitionInstructionProcessorTest extends StoreEngineTestBase { + + PartitionInstructionProcessor processor; + + @Before + public void init() { + processor = new PartitionInstructionProcessor(getStoreEngine()); + } + + @Test + public void testTransferLeader() { + var engine = createPartitionEngine(0); + engine.waitForLeader(1000); + var shard = Metapb.Shard.newBuilder() + .setStoreId(FakePdServiceProvider.makeStoreId("127.0.0.1:6511")) + .setRole(Metapb.ShardRole.Leader) + .build(); + + TransferLeader trans = TransferLeader.newBuilder() + .setShard(shard) + .build(); + processor.onTransferLeader(1, getPartition(0), trans, integer -> { + assertEquals(0, integer.intValue()); + }); + } + + @Test + public void testDbCompaction() throws InterruptedException { + var engine = createPartitionEngine(0); + engine.waitForLeader(1000); + DbCompaction dbCompaction = DbCompaction.newBuilder() + .setTableName("test") + .build(); + processor.onDbCompaction(2, getPartition(0), dbCompaction, integer -> { + assertEquals(0, integer.intValue()); + }); + + Thread.sleep(2000); + } + + @Test + public void testSplitPartition() throws InterruptedException { + var engine = createPartitionEngine(0); + engine.waitForLeader(1000); + + var partition = getPartition(0); + System.out.println(partition); + + List list = new ArrayList<>(); + list.add(Metapb.Partition.newBuilder(partition.getProtoObj()) + .setStartKey(0) + .setEndKey(20000) + .build()); + list.add(Metapb.Partition.newBuilder(partition.getProtoObj()) + .setStartKey(20000) + .setEndKey(65535) + .setId(1).build()); + + SplitPartition splitPartition = SplitPartition.newBuilder() + .addAllNewPartition(list) + .build(); + + processor.onSplitPartition(3, partition, splitPartition, integer -> { + assertEquals(0, integer.intValue()); + }); + + Thread.sleep(2000); + System.out.println(getStoreEngine().getPartitionEngines()); + } + + @Test + public void testChangeShard() { + + } +} diff --git a/hugegraph-store/hg-store-test/src/main/java/core/store/meta/MetadataKeyHelperTest.java b/hugegraph-store/hg-store-test/src/main/java/core/store/meta/MetadataKeyHelperTest.java new file mode 100644 index 000000000..655344d5c --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/core/store/meta/MetadataKeyHelperTest.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package core.store.meta; + +import static org.junit.Assert.assertArrayEquals; + +import org.apache.hugegraph.store.meta.MetadataKeyHelper; +import org.junit.Test; + +public class MetadataKeyHelperTest { + + @Test + public void testKey() { + assertArrayEquals("HUGEGRAPH/TASK/".getBytes(), MetadataKeyHelper.getTaskPrefix()); + assertArrayEquals("HUGEGRAPH/TASK/0/".getBytes(), MetadataKeyHelper.getTaskPrefix(0)); + assertArrayEquals("HUGEGRAPH/TASK_DONE/0000000000000000".getBytes(), + MetadataKeyHelper.getDoneTaskKey(0)); + } +} diff --git a/hugegraph-store/hg-store-test/src/main/java/core/store/meta/PartitionManagerTest.java b/hugegraph-store/hg-store-test/src/main/java/core/store/meta/PartitionManagerTest.java new file mode 100644 index 000000000..657417dd7 --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/core/store/meta/PartitionManagerTest.java @@ -0,0 +1,198 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package core.store.meta; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.hugegraph.pd.common.PDException; +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.store.cmd.UpdatePartitionRequest; +import org.apache.hugegraph.store.meta.Graph; +import org.apache.hugegraph.store.meta.GraphManager; +import org.apache.hugegraph.store.meta.PartitionManager; +import org.apache.hugegraph.store.pd.FakePdServiceProvider; +import org.junit.Before; +import org.junit.Test; + +import core.StoreEngineTestBase; + +public class PartitionManagerTest extends StoreEngineTestBase { + + private PartitionManager manager; + + @Before + public void setup() { + manager = getStoreEngine().getPartitionManager(); + } + + @Test + public void testGetDeletedFileManager() { + assertNotNull(manager.getDeletedFileManager()); + } + + @Test + public void testGetPdProvider() { + assertEquals(manager.getPdProvider().getClass(), FakePdServiceProvider.class); + } + + @Test + public void testGetStoreMetadata() { + assertNotNull(manager.getStoreMetadata()); + } + + @Test + public void testSetStore() { + var storeId = FakePdServiceProvider.makeStoreId("127.0.0.1:6511"); + var store = manager.getStore(storeId); + manager.setStore(store); + var store2 = manager.getStoreMetadata().getStore(); + + assertSame(store.getId(), store2.getId()); + } + + @Test + public void testUpdatePartition() { + var partition = getPartition(5); + manager.updatePartition(partition.getProtoObj(), true); + + var partition2 = manager.findPartition("graph0", 5); + assertEquals(partition.getGraphName(), partition2.getGraphName()); + + var partition3 = manager.loadPartitionFromSnapshot(partition2); + assertEquals(partition3.getGraphName(), partition2.getGraphName()); + } + + @Test + public void testChangeState() { + createPartitionEngine(4); + var partition = getPartition(4); + manager.changeState(partition, Metapb.PartitionState.PState_Offline); + var partition2 = manager.findPartition("graph0", 4); + assertEquals(partition2.getWorkState(), Metapb.PartitionState.PState_Offline); + } + + @Test + public void testChangeKeyRange() { + createPartitionEngine(4); + var partition = getPartition(4); + manager.changeKeyRange(partition, 1000, 2000); + + var partition2 = manager.findPartition("graph0", 4); + assertEquals(partition2.getStartKey(), 1000); + assertEquals(partition2.getEndKey(), 2000); + } + + + @Test + public void testUpdatePartitionRangeOrState() { + createPartitionEngine(4); + UpdatePartitionRequest request = new UpdatePartitionRequest(); + request.setPartitionId(4); + request.setGraphName("graph0"); + request.setStartKey(2000); + request.setEndKey(3000); + request.setWorkState(Metapb.PartitionState.PState_Offline); + manager.updatePartitionRangeOrState(request); + + var partition = manager.findPartition("graph0", 4); + assertEquals(partition.getStartKey(), 2000); + assertEquals(partition.getEndKey(), 3000); + assertEquals(partition.getWorkState(), Metapb.PartitionState.PState_Offline); + } + + @Test + public void testGetLeaderPartitionIds() { + createPartitionEngine(0); + createPartitionEngine(4); + createPartitionEngine(5); + System.out.println(manager.getLeaderPartitionIds("graph0")); + assertEquals(manager.getLeaderPartitionIds("graph0").size(), 3); + } + + @Test + public void testisLocal() { + createPartitionEngine(0); + assertTrue(manager.isLocalPartition(0)); + assertTrue(manager.isLocalPartition(getPartition(0))); + assertTrue(manager.isLocalStore(manager.getStore())); + } + + @Test + public void testUploadToPd() throws PDException { + createPartitionEngine(0); + var partition = manager.findPartition("graph0", 0); + var list = new ArrayList(); + list.add(partition.getProtoObj()); + // fake pd, return nothing + assertEquals(1, manager.updatePartitionToPD(list).size()); + + manager.reportTask(null); + + var partitions = manager.changePartitionToOnLine(list); + assertSame(partitions.get(0).getState(), Metapb.PartitionState.PState_Normal); + // fake pd + assertNotNull(manager.findPartition("graph0", 1000)); + + } + + @Test + public void testShards2Peers() { + var storeId = FakePdServiceProvider.makeStoreId("127.0.0.1:6511"); + Metapb.Shard shard = Metapb.Shard.newBuilder() + .setStoreId(storeId) + .setRole(Metapb.ShardRole.Leader) + .build(); + + List list = new ArrayList<>(); + list.add(shard); + + var peers = manager.shards2Peers(list); + assertEquals("127.0.0.1:6510", peers.get(0)); + } + + @Test + public void testLoad() { + createPartitionEngine(0); + var graphManager = new GraphManager(manager.getOptions(), manager.getPdProvider()); + var graph = Metapb.Graph.newBuilder() + .setGraphName("graph0") + .setPartitionCount(12) + .build(); + graphManager.updateGraph(new Graph(graph)); + + manager.load(); + assertNotNull(manager.getLeaderPartitionIds("graph0")); + } + + @Test + public void testSyncPartitionsFromPD() throws PDException { + createPartitionEngine(0); + // from fake pd + manager.syncPartitionsFromPD(partition -> { + }); + + assertTrue(manager.getPartitions().isEmpty()); + } + +} diff --git a/hugegraph-store/hg-store-test/src/main/java/core/store/meta/TaskManagerTest.java b/hugegraph-store/hg-store-test/src/main/java/core/store/meta/TaskManagerTest.java new file mode 100644 index 000000000..866db9491 --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/core/store/meta/TaskManagerTest.java @@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package core.store.meta; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import org.apache.hugegraph.pd.grpc.MetaTask; +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.store.meta.TaskManager; +import org.apache.hugegraph.store.meta.asynctask.AsyncTaskState; +import org.apache.hugegraph.store.meta.asynctask.CleanTask; +import org.junit.Before; +import org.junit.Test; + +import core.StoreEngineTestBase; + +public class TaskManagerTest extends StoreEngineTestBase { + + private TaskManager manager; + + @Before + public void setup() { + createPartitionEngine(0, "graph0"); + manager = getStoreEngine().getPartitionEngine(0).getTaskManager(); + } + + @Test + public void testAsyncTaskReadAndWrite() { + var task1 = new CleanTask(0, "graph0", AsyncTaskState.START, null); + var task2 = new CleanTask(0, "graph0", AsyncTaskState.FAILED, null); + var task3 = new CleanTask(0, "graph0", AsyncTaskState.SUCCESS, null); + var task4 = new CleanTask(0, "graph0", AsyncTaskState.FAILED, null); + + manager.putAsyncTask(task1); + manager.putAsyncTask(task2); + manager.putAsyncTask(task3); + manager.putAsyncTask(task4); + + var list = manager.scanAsyncTasks(0, "graph0"); + assertEquals(list.size(), 4); + + var newTask1 = (CleanTask) manager.getOneAsyncTask(0, "graph0", task1.getId()); + assertEquals(task1.getState(), newTask1.getState()); + assertEquals(task1.getType(), newTask1.getType()); + + manager.updateAsyncTaskState(0, "graph0", task4.getId(), AsyncTaskState.SUCCESS); + var newTask4 = (CleanTask) manager.getOneAsyncTask(0, "graph0", task4.getId()); + assertEquals(newTask4.getState(), AsyncTaskState.SUCCESS); + + assertNull(manager.getOneAsyncTask(1, "graph0", "")); + } + + @Test + public void testTaskOp() { + MetaTask.Task task1 = MetaTask.Task.newBuilder() + .setId(1) + .setState(MetaTask.TaskState.Task_Ready) + .setType(MetaTask.TaskType.Split_Partition) + .setPartition(Metapb.Partition.newBuilder() + .setGraphName("graph0") + .setId(0).build()) + .build(); + + manager.updateTask(task1); + assertTrue(manager.taskExists(task1)); + assertFalse(manager.taskExists(0, "graph0", + MetaTask.TaskType.Split_Partition.name())); + assertFalse(manager.partitionTaskRepeat(0, "graph0", + MetaTask.TaskType.Split_Partition.name())); + + MetaTask.Task task2 = MetaTask.Task.newBuilder(task1).setId(2).build(); + manager.updateTask(task2); + + assertTrue(manager.taskExists(0, "graph0", + MetaTask.TaskType.Split_Partition.name())); + assertTrue(manager.partitionTaskRepeat(0, "graph0", + MetaTask.TaskType.Split_Partition.name())); + + MetaTask.Task task3 = MetaTask.Task.newBuilder(task1) + .setId(3) + .setState(MetaTask.TaskState.Task_Success) + .setPartition( + Metapb.Partition.newBuilder(task1.getPartition()) + .setGraphName("graph1") + .setId(1) + .build()) + .build(); + manager.updateTask(task3); + assertFalse(manager.taskExists(task3)); + } +} diff --git a/hugegraph-store/hg-store-test/src/main/java/core/store/meta/asynctask/CleanTaskTest.java b/hugegraph-store/hg-store-test/src/main/java/core/store/meta/asynctask/CleanTaskTest.java new file mode 100644 index 000000000..e98c7fd29 --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/core/store/meta/asynctask/CleanTaskTest.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package core.store.meta.asynctask; + +import static org.junit.Assert.assertEquals; + +import org.apache.hugegraph.store.meta.asynctask.AbstractAsyncTask; +import org.apache.hugegraph.store.meta.asynctask.AsyncTask; +import org.apache.hugegraph.store.meta.asynctask.AsyncTaskState; +import org.apache.hugegraph.store.meta.asynctask.CleanTask; +import org.junit.Test; + +import core.StoreEngineTestBase; + +public class CleanTaskTest extends StoreEngineTestBase { + + @Test + public void testSerialize() { + CleanTask task = new CleanTask(0, "graph0", AsyncTaskState.SUCCESS, null); + byte[] bytes = task.toBytes(); + + AsyncTask task2 = AbstractAsyncTask.fromBytes(bytes); + assertEquals(CleanTask.class, task2.getClass()); + System.out.println(task2); + + createPartitionEngine(0); + + CleanTask task3 = new CleanTask(0, "graph0", AsyncTaskState.START, null); + CleanTask task4 = new CleanTask(0, "graph0", AsyncTaskState.FAILED, null); + task3.handleTask(); + task4.handleTask(); + } + +} diff --git a/hugegraph-store/hg-store-test/src/main/java/core/store/util/MiscUtilClassTest.java b/hugegraph-store/hg-store-test/src/main/java/core/store/util/MiscUtilClassTest.java new file mode 100644 index 000000000..d12026be4 --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/core/store/util/MiscUtilClassTest.java @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package core.store.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.concurrent.TimeUnit; + +import org.apache.hugegraph.store.util.Asserts; +import org.apache.hugegraph.store.util.HgRaftError; +import org.apache.hugegraph.store.util.HgStoreException; +import org.apache.hugegraph.store.util.ManualResetEvent; +import org.junit.Test; + +import com.alipay.sofa.jraft.Status; + +public class MiscUtilClassTest { + + @Test + public void testHgRaftError() { + HgRaftError error = HgRaftError.forNumber(0); + assertEquals(0, error.getNumber()); + assertEquals("OK", error.getMsg()); + assertEquals(Status.OK().getCode(), error.toStatus().getCode()); + } + + @Test(expected = NullPointerException.class) + public void testAsserts() { + assertTrue(Asserts.isInvalid(null)); + assertTrue(Asserts.isInvalid()); + assertTrue(Asserts.isInvalid(null)); + assertTrue(Asserts.isInvalid("")); + assertFalse(Asserts.isInvalid("a")); + + Asserts.isNonNull(null); + Asserts.isNonNull(null, "msg"); + } + + @Test(expected = IllegalArgumentException.class) + public void testAsserts2() { + Asserts.isTrue(false, ""); + Asserts.isFalse(true, ""); + Asserts.isTrue(true, null); + } + + @Test + public void testHgStoreException() { + var exception = new HgStoreException(); + assertEquals(0, exception.getCode()); + exception = new HgStoreException("invalid"); + assertEquals(1000, exception.getCode()); + exception = new HgStoreException(1000, "invalid"); + assertEquals(1000, exception.getCode()); + exception = new HgStoreException(1000, new Throwable()); + assertEquals(1000, exception.getCode()); + exception = new HgStoreException("invalid", new Throwable()); + assertEquals(1000, exception.getCode()); + exception = new HgStoreException(1000, "%s", "invalid"); + assertEquals(1000, exception.getCode()); + } + + @Test + public void testManualResetEvent() throws InterruptedException { + ManualResetEvent event = new ManualResetEvent(false); + assertFalse(event.isSignalled()); + event.set(); + assertTrue(event.isSignalled()); + event.reset(); + assertFalse(event.waitOne(1, TimeUnit.SECONDS)); + event.set(); + event.waitOne(); + assertTrue(event.isSignalled()); + } + + +} diff --git a/hugegraph-store/hg-store-test/src/main/java/core/store/util/PartitionMetaStoreWrapperTest.java b/hugegraph-store/hg-store-test/src/main/java/core/store/util/PartitionMetaStoreWrapperTest.java new file mode 100644 index 000000000..453adc687 --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/core/store/util/PartitionMetaStoreWrapperTest.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package core.store.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import org.apache.hugegraph.pd.grpc.Metapb; +import org.apache.hugegraph.store.meta.MetadataKeyHelper; +import org.apache.hugegraph.store.util.PartitionMetaStoreWrapper; +import org.junit.Before; +import org.junit.Test; + +import core.StoreEngineTestBase; + +public class PartitionMetaStoreWrapperTest extends StoreEngineTestBase { + + private PartitionMetaStoreWrapper wrapper; + + private Metapb.Partition partition; + + public static void putToDb(Metapb.Partition partition, PartitionMetaStoreWrapper wrapper) { + byte[] key = MetadataKeyHelper.getPartitionKey(partition.getGraphName(), partition.getId()); + wrapper.put(partition.getId(), key, partition.toByteArray()); + } + + @Before + public void setup() { + wrapper = new PartitionMetaStoreWrapper(); + partition = Metapb.Partition.newBuilder() + .setId(1) + .setGraphName("graph0") + .setStartKey(0L) + .setEndKey(65535L) + .build(); + } + + @Test + public void testGet() { + putToDb(partition, wrapper); + byte[] key = MetadataKeyHelper.getPartitionKey(partition.getGraphName(), partition.getId()); + assertEquals(partition, wrapper.get(1, key, Metapb.Partition.parser())); + byte[] key2 = MetadataKeyHelper.getPartitionKey("not_exists", partition.getId()); + assertNull(wrapper.get(1, key2, Metapb.Partition.parser())); + } + + @Test + public void testPut() { + putToDb(partition, wrapper); + byte[] key = MetadataKeyHelper.getPartitionKey(partition.getGraphName(), partition.getId()); + var list = wrapper.scan(partition.getId(), Metapb.Partition.parser(), key); + assertEquals(list.size(), 1); + assertEquals(list.get(0).getGraphName(), partition.getGraphName()); + } + + @Test + public void testDelete() { + putToDb(partition, wrapper); + byte[] key = MetadataKeyHelper.getPartitionKey(partition.getGraphName(), partition.getId()); + wrapper.delete(partition.getId(), key); + var list = wrapper.scan(partition.getId(), Metapb.Partition.parser(), key); + assertEquals(list.size(), 0); + } + + @Test + public void testScan() { + putToDb(partition, wrapper); + byte[] key = MetadataKeyHelper.getPartitionKey(partition.getGraphName(), partition.getId()); + var list = wrapper.scan(partition.getId(), Metapb.Partition.parser(), key); + assertEquals(list.size(), 1); + } + +} diff --git a/hugegraph-store/hg-store-test/src/main/java/core/store/util/ZipUtilsTest.java b/hugegraph-store/hg-store-test/src/main/java/core/store/util/ZipUtilsTest.java new file mode 100644 index 000000000..420266778 --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/core/store/util/ZipUtilsTest.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package core.store.util; + +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.zip.CRC32; + +import org.apache.hugegraph.store.util.ZipUtils; +import org.apache.logging.log4j.core.util.FileUtils; +import org.junit.Before; +import org.junit.Test; + +import util.UnitTestBase; + +public class ZipUtilsTest { + private static final String ZIP_TEST_PATH = "/tmp/zip_util_test"; + + @Before + public void init() throws IOException { + UnitTestBase.deleteDir(new File(ZIP_TEST_PATH)); + FileUtils.mkdir(new File(ZIP_TEST_PATH), true); + FileUtils.mkdir(new File(ZIP_TEST_PATH + "/input"), true); + FileUtils.mkdir(new File(ZIP_TEST_PATH + "/output"), true); + Files.createFile(Paths.get(ZIP_TEST_PATH + "/input/foo.txt")); + } + + @Test + public void testZip() throws IOException { + ZipUtils.compress(ZIP_TEST_PATH, "input", ZIP_TEST_PATH + "/foo.zip", new CRC32()); + ZipUtils.decompress(ZIP_TEST_PATH + "/foo.zip", ZIP_TEST_PATH + "/output", new CRC32()); + assertTrue(Files.exists(Paths.get(ZIP_TEST_PATH + "/output/input/foo.txt"))); + } +} diff --git a/hugegraph-store/hg-store-test/src/main/java/raftcore/BytesCarrierTest.java b/hugegraph-store/hg-store-test/src/main/java/raftcore/BytesCarrierTest.java new file mode 100644 index 000000000..afa180e67 --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/raftcore/BytesCarrierTest.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package raftcore; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.junit.Test; + +import com.google.protobuf.BytesCarrier; + +public class BytesCarrierTest { + + @Test + public void testWrite() throws IOException { + byte[] bytes = new byte[]{10, 20, 30}; + BytesCarrier carrier = new BytesCarrier(); + + // not valid + carrier.write((byte) 1); + assertNull(carrier.getValue()); + assertFalse(carrier.isValid()); + + // not valid + ByteBuffer buffer = ByteBuffer.allocate(10); + carrier.write(buffer); + assertNull(carrier.getValue()); + + // not valid + carrier.writeLazy(buffer); + assertNull(carrier.getValue()); + + // ok, write done + carrier.write(bytes, 0, bytes.length); + assertNotNull(carrier.getValue()); + assertTrue(carrier.isValid()); + + // has data + carrier.writeLazy(bytes, 0, bytes.length); + assertFalse(carrier.isValid()); + } +} diff --git a/hugegraph-store/hg-store-test/src/main/java/raftcore/RaftSuiteTest.java b/hugegraph-store/hg-store-test/src/main/java/raftcore/RaftSuiteTest.java new file mode 100644 index 000000000..ef9cd984a --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/raftcore/RaftSuiteTest.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package raftcore; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +@RunWith(Suite.class) +@Suite.SuiteClasses({ + BytesCarrierTest.class, + ZeroByteStringHelperTest.class +}) +public class RaftSuiteTest { + +} diff --git a/hugegraph-store/hg-store-test/src/main/java/raftcore/ZeroByteStringHelperTest.java b/hugegraph-store/hg-store-test/src/main/java/raftcore/ZeroByteStringHelperTest.java new file mode 100644 index 000000000..dd1cc630f --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/raftcore/ZeroByteStringHelperTest.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package raftcore; + +import static org.junit.Assert.assertTrue; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; + +import org.junit.Test; + +import com.google.protobuf.ZeroByteStringHelper; + +public class ZeroByteStringHelperTest { + + private static final String STR = "hello word!"; + + @Test + public void testWrap() { + byte[] b1 = new byte[]{10, 20, 30}; + byte[] b2 = new byte[]{40, 50}; + + var h1 = ZeroByteStringHelper.wrap(b1); + var h2 = ZeroByteStringHelper.wrap(b2, 0, b2.length); + + ByteBuffer buffer = ByteBuffer.allocate(5); + buffer.put(b1); + buffer.put(b2); + var h3 = ZeroByteStringHelper.wrap(buffer); + assertEquals(h3.isEmpty(), true); + var h4 = ZeroByteStringHelper.concatenate(h1, h2); + assertTrue(Arrays.equals(ZeroByteStringHelper.getByteArray(h4), buffer.array())); + } + + @Test + public void testConcatenate() { + byte[] b1 = new byte[]{10, 20, 30}; + byte[] b2 = new byte[]{40, 50}; + ByteBuffer buffer1 = ByteBuffer.allocate(5); + buffer1.put(b1); + + ByteBuffer buffer2 = ByteBuffer.allocate(5); + buffer1.put(b2); + + var array = new ArrayList(); + array.add(buffer1); + array.add(buffer2); + + var bs = ZeroByteStringHelper.concatenate(array); + assertEquals(bs.toByteArray().length, 5); + } +} diff --git a/hugegraph-store/hg-store-test/src/main/java/rocksdb/BaseRocksDbTest.java b/hugegraph-store/hg-store-test/src/main/java/rocksdb/BaseRocksDbTest.java new file mode 100644 index 000000000..4f128f0fe --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/rocksdb/BaseRocksDbTest.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package rocksdb; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.hugegraph.config.HugeConfig; +import org.apache.hugegraph.config.OptionSpace; +import org.apache.hugegraph.rocksdb.access.RocksDBFactory; +import org.apache.hugegraph.rocksdb.access.RocksDBOptions; +import org.junit.After; +import org.junit.BeforeClass; + +public class BaseRocksDbTest { + @BeforeClass + public static void init() { + OptionSpace.register("rocksdb", + "org.apache.hugegraph.rocksdb.access.RocksDBOptions"); + RocksDBOptions.instance(); + + Map configMap = new HashMap<>(); + configMap.put("rocksdb.write_buffer_size", "1048576"); + configMap.put("rocksdb.bloom_filter_bits_per_key", "10"); + + HugeConfig hConfig = new HugeConfig(configMap); + RocksDBFactory rFactory = RocksDBFactory.getInstance(); + rFactory.setHugeConfig(hConfig); + + } + + @After + public void teardown() throws Exception { + // pass + } +} diff --git a/hugegraph-store/hg-store-test/src/main/java/rocksdb/RocksDBFactoryTest.java b/hugegraph-store/hg-store-test/src/main/java/rocksdb/RocksDBFactoryTest.java new file mode 100644 index 000000000..eec4efdf3 --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/rocksdb/RocksDBFactoryTest.java @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package rocksdb; + +import org.apache.hugegraph.rocksdb.access.RocksDBFactory; +import org.apache.hugegraph.rocksdb.access.RocksDBSession; +import org.apache.hugegraph.rocksdb.access.SessionOperator; +import org.junit.Test; + +public class RocksDBFactoryTest extends BaseRocksDbTest { + @Test + public void testCreateSession() { + RocksDBFactory factory = RocksDBFactory.getInstance(); + try (RocksDBSession dbSession = factory.createGraphDB("./tmp", "test1")) { + SessionOperator op = dbSession.sessionOp(); + op.prepare(); + try { + op.put("tbl", "k1".getBytes(), "v1".getBytes()); + op.commit(); + } catch (Exception e) { + op.rollback(); + } + + } + factory.destroyGraphDB("test1"); + } + + @Test + public void testTotalKeys() { + RocksDBFactory dbFactory = RocksDBFactory.getInstance(); + System.out.println(dbFactory.getTotalSize()); + + System.out.println(dbFactory.getTotalKey().entrySet() + .stream().map(e -> e.getValue()).reduce(0L, Long::sum)); + } + + @Test + public void releaseAllGraphDB() { + System.out.println(RocksDBFactory.class); + + RocksDBFactory rFactory = RocksDBFactory.getInstance(); + + if (rFactory.queryGraphDB("bj01") == null) { + rFactory.createGraphDB("./tmp", "bj01"); + } + + if (rFactory.queryGraphDB("bj02") == null) { + rFactory.createGraphDB("./tmp", "bj02"); + } + + if (rFactory.queryGraphDB("bj03") == null) { + rFactory.createGraphDB("./tmp", "bj03"); + } + + RocksDBSession dbSession = rFactory.queryGraphDB("bj01"); + + dbSession.checkTable("test"); + SessionOperator sessionOp = dbSession.sessionOp(); + sessionOp.prepare(); + + sessionOp.put("test", "hi".getBytes(), "byebye".getBytes()); + sessionOp.commit(); + + rFactory.releaseAllGraphDB(); + } +} diff --git a/hugegraph-store/hg-store-test/src/main/java/rocksdb/RocksDbSuiteTest.java b/hugegraph-store/hg-store-test/src/main/java/rocksdb/RocksDbSuiteTest.java new file mode 100644 index 000000000..5669784ad --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/rocksdb/RocksDbSuiteTest.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package rocksdb; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +import lombok.extern.slf4j.Slf4j; + +@RunWith(Suite.class) +@Suite.SuiteClasses({ + RocksDBFactoryTest.class +}) + +@Slf4j +public class RocksDbSuiteTest { +} diff --git a/hugegraph-store/hg-store-test/src/main/java/service/BaseServerTest.java b/hugegraph-store/hg-store-test/src/main/java/service/BaseServerTest.java new file mode 100644 index 000000000..ae1cab5c6 --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/service/BaseServerTest.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package service; + +import org.junit.After; +import org.junit.BeforeClass; + +public class BaseServerTest { + @BeforeClass + public static void init() { + + } + + @After + public void teardown() { + // pass + } +} diff --git a/hugegraph-store/hg-store-test/src/main/java/service/ServerSuiteTest.java b/hugegraph-store/hg-store-test/src/main/java/service/ServerSuiteTest.java new file mode 100644 index 000000000..f648579d2 --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/service/ServerSuiteTest.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package service; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +import lombok.extern.slf4j.Slf4j; + +@RunWith(Suite.class) +@Suite.SuiteClasses({ +}) + +@Slf4j + +public class ServerSuiteTest { +} diff --git a/hugegraph-store/hg-store-test/src/main/java/service/SessionTest.java b/hugegraph-store/hg-store-test/src/main/java/service/SessionTest.java new file mode 100644 index 000000000..dd2a8596b --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/service/SessionTest.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package service; + +import org.junit.Test; + +import lombok.extern.slf4j.Slf4j; + +/** + * @date 2022/11/15 + **/ +@Slf4j +public class SessionTest { + @Test + public void testQuota() { + + } +} diff --git a/hugegraph-store/hg-store-test/src/main/java/util/CopyOnWriteCacheTest.java b/hugegraph-store/hg-store-test/src/main/java/util/CopyOnWriteCacheTest.java new file mode 100644 index 000000000..dd399d164 --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/util/CopyOnWriteCacheTest.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package util; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.Map; +import java.util.Set; + +import org.apache.hugegraph.store.util.CopyOnWriteCache; +import org.junit.Before; +import org.junit.Test; + +public class CopyOnWriteCacheTest { + + private CopyOnWriteCache writeCache; + + @Before + public void setUp() { + writeCache = new CopyOnWriteCache<>(5000L); + } + + @Test + public void testContainsKey() { + // Setup + // Run the test + writeCache.put("k", "v"); + final boolean result = writeCache.containsKey("k"); + Map allKeys = + Map.ofEntries(Map.entry("key1", "value1")); + writeCache.putAll(allKeys); + // Verify the results + assertTrue(result); + final Set> entries = writeCache.entrySet(); + Set keySet = writeCache.keySet(); + String val = writeCache.get("k"); + boolean isEmpty = writeCache.isEmpty(); + writeCache.size(); + writeCache.values(); + // Verify the results + assertFalse(isEmpty); + writeCache.remove("k"); + writeCache.putIfAbsent("k", "v"); + writeCache.replace("k", "original", "replacement"); + writeCache.replace("k", "v"); + writeCache.clear(); + assertTrue(writeCache.isEmpty()); + } +} diff --git a/hugegraph-store/hg-store-test/src/main/java/util/HgStoreTestUtil.java b/hugegraph-store/hg-store-test/src/main/java/util/HgStoreTestUtil.java new file mode 100644 index 000000000..e268016c7 --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/util/HgStoreTestUtil.java @@ -0,0 +1,480 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package util; + +import java.io.Closeable; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; + +import org.apache.hugegraph.store.HgKvEntry; +import org.apache.hugegraph.store.HgKvIterator; +import org.apache.hugegraph.store.HgOwnerKey; +import org.apache.hugegraph.store.HgStoreSession; +import org.apache.hugegraph.store.client.util.HgStoreClientConst; +import org.apache.hugegraph.store.client.util.MetricX; + +public class HgStoreTestUtil { + public static final String GRAPH_NAME = "default/hugegraph/g"; + public static final String GRAPH_NAME2 = "default/hugegraph2/g"; + public static final String TABLE_NAME = "unit-table"; + public static final String TABLE_NAME2 = "unit-table-2"; + + public static Map batchPut(HgStoreSession session, String keyPrefix) { + return batchPut(session, keyPrefix, 100); + } + + public static Map batchPut(HgStoreSession session, String keyPrefix, + int loop) { + return batchPut(session, TABLE_NAME, keyPrefix, loop); + } + + public static Map batchPut( + HgStoreSession session, String tableName, String keyPrefix, int loop) { + return batchPut(session, tableName, keyPrefix, loop, 1, key -> toOwnerKey(key)); + } + + public static Map batchPut( + HgStoreSession session, String tableName, byte[] keyPrefix, int loop) { + return batchPut(session, tableName, keyPrefix, loop, 1, + (prefix, key) -> toOwnerKey(prefix, key)); + } + + public static Map batchPut( + HgStoreSession session, String tableName, String keyPrefix, int loop, int start) { + return batchPut(session, tableName, keyPrefix, loop, start, key -> toOwnerKey(key)); + } + + public static Map batchPut( + HgStoreSession session, String tableName, String keyPrefix, int loop, + Function f) { + return batchPut(session, tableName, keyPrefix, loop, 1, f); + } + + public static Map batchPut( + HgStoreSession session, + String tableName, + String keyPrefix, + int loop, + int start, + Function f) { + + Map res = new LinkedHashMap<>(); + + int length = String.valueOf(loop).length(); + + session.beginTx(); + for (int i = start; i <= loop; i++) { + + HgOwnerKey key = f.apply(keyPrefix + "-" + padLeftZeros(String.valueOf(i), length)); + + byte[] value = toBytes(keyPrefix + "-V-" + i); + res.put(key, value); + session.put(tableName, key, value); + + if ((i + 1) % 10000 == 0) { + println("commit: " + (i + 1)); + session.commit(); + session.beginTx(); + } + } + if (session.isTx()) { + session.commit(); + } + + return res; + } + + public static Map batchPut( + HgStoreSession session, + String tableName, + byte[] keyPrefix, + int loop, + int start, + BiFunction f) { + + Map res = new LinkedHashMap<>(); + + int length = String.valueOf(loop).length(); + + session.beginTx(); + for (int i = start; i <= loop; i++) { + + HgOwnerKey key = f.apply(keyPrefix, padLeftZeros(String.valueOf(i), length)); + + byte[] value = toBytes(keyPrefix + "-V-" + i); + res.put(key, value); + session.put(tableName, key, value); + + if ((i + 1) % 10000 == 0) { + println("commit: " + (i + 1)); + session.commit(); + session.beginTx(); + } + } + if (session.isTx()) { + session.commit(); + } + + return res; + } + + /*---- common -----*/ + public static void printOwner(List list) { + if (list == null) { + return; + } + + for (HgOwnerKey entry : list) { + println(entry); + } + } + + public static void printNum(List list, String title) { + if (list == null) { + return; + } + + println(title + " size: " + list.size()); + } + + public static int println(Iterable> iterators) { + AtomicInteger counter = new AtomicInteger(); + iterators.forEach(e -> counter.addAndGet(HgStoreTestUtil.println(e))); + return counter.get(); + } + + public static int println(HgKvIterator iterator) { + if (iterator == null) { + return 0; + } + + AtomicInteger counter = new AtomicInteger(); + + while (iterator.hasNext()) { + counter.incrementAndGet(); + print(iterator.next()); + println(" " + Arrays.toString(iterator.position())); + } + + iterator.close(); + + return counter.get(); + } + + public static void println(HgKvIterator iterator, Function mod) { + if (iterator == null) { + return; + } + int count = 0; + + while (iterator.hasNext()) { + count++; + if (mod.apply(count)) { + print(iterator.next()); + println(" " + Arrays.toString(iterator.position())); + } + } + + iterator.close(); + } + + public static void println(List list) { + if (list == null) { + return; + } + + for (HgKvEntry entry : list) { + println(entry); + } + } + + public static void println(List list, int mod) { + if (list == null) { + return; + } + + for (int i = 0; i < list.size(); i++) { + if (i % mod == 0) { + println(list.get(i)); + } + } + } + + public static void println(HgKvEntry kv) { + if (kv == null) { + System.out.println("null"); + return; + } + println("[ " + kv.code() + " " + toStr(kv.key()) + " : " + toStr(kv.value()) + " ]"); + } + + public static void println(HgOwnerKey key) { + if (key == null) { + System.out.println("null"); + return; + } + println("[ " + toInt(key.getOwner()) + " : " + toStr(key.getKey()) + " ]"); + } + + public static void println(String str) { + System.out.println(str); + } + + public static void println(Number num) { + System.out.println(num); + } + + public static void print(String str) { + System.out.print(str); + } + + public static void print(HgKvEntry kv) { + if (kv == null) { + System.out.println("null"); + return; + } + print("[ " + kv.code() + " " + toStr(kv.key()) + " : " + toStr(kv.value()) + " ]"); + } + + private static byte[] getOwner(String key) { + return getOwner(toBytes(key)); + } + + private static byte[] getOwner(byte[] key) { + return toBytes(Arrays.hashCode(key)); + } + + public static HgOwnerKey toAllPartitionKey(String key) { + return HgOwnerKey.of(HgStoreClientConst.ALL_PARTITION_OWNER, toBytes(key)); + } + + public static HgOwnerKey toAllPartitionKey(byte[] prefix, String key) { + return HgOwnerKey.of(HgStoreClientConst.ALL_PARTITION_OWNER, toBytes(prefix, key)); + } + + public static HgOwnerKey toAllPartitionKey(byte[] prefix) { + return HgOwnerKey.of(HgStoreClientConst.ALL_PARTITION_OWNER, prefix); + } + + public static HgOwnerKey toOwnerKey(byte[] prefix, String key) { + byte[] newKey = toBytes(prefix, key); + return new HgOwnerKey(getOwner(newKey), newKey); + } + + public static HgOwnerKey toOwnerKey(String key) { + return new HgOwnerKey(getOwner(key), toBytes(key)); + } + + public static HgOwnerKey toOwnerKey(byte[] key) { + return new HgOwnerKey(getOwner(key), key); + } + + public static HgOwnerKey toOwnerKey(String owner, String key) { + return HgOwnerKey.of(toBytes(owner), toBytes(key)); + } + + public static HgOwnerKey toOwnerKey(int code, String key) { + return HgOwnerKey.of(code, toBytes(key)); + } + + public static String toStr(byte[] b) { + if (b == null) { + return ""; + } + if (b.length == 0) { + return ""; + } + return new String(b, StandardCharsets.UTF_8); + } + + public static byte[] toBytes(byte[] prefix, String str) { + if (str == null) { + return null; + } + byte[] buf = str.getBytes(StandardCharsets.UTF_8); + byte[] res = new byte[buf.length + prefix.length]; + System.arraycopy(prefix, 0, res, 0, prefix.length); + System.arraycopy(buf, 0, res, prefix.length, buf.length); + return res; + } + + public static byte[] toBytes(String str) { + if (str == null) { + return null; + } + return str.getBytes(StandardCharsets.UTF_8); + } + + public static byte[] toBytes(long l) { + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.putLong(l); + return buffer.array(); + } + + private static byte[] toBytes(final int i) { + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.putInt(i); + return buffer.array(); + } + + public static long toLong(byte[] bytes) { + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.put(bytes); + buffer.flip(); // need flip + return buffer.getLong(); + } + + public static int toInt(byte[] bytes) { + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.put(bytes); + buffer.flip(); // need flip + return buffer.getInt(); + } + + public static String padLeftZeros(String str, int n) { + return String.format("%1$" + n + "s", str).replace(' ', '0'); + } + + public static String toSuffix(int num, int length) { + return "-" + padLeftZeros(String.valueOf(num), length); + } + + public static int amountOf(List list) { + if (list == null) { + return 0; + } + return list.size(); + } + + public static int amountOf(Iterator iterator) { + if (iterator == null) { + return 0; + } + int count = 0; + while (iterator.hasNext()) { + Object obj = iterator.next(); + ++count; + } + return count; + } + + public static int oOMAmountOf(Iterator iterator) { + if (iterator == null) { + return 0; + } + int count = 0; + while (iterator.hasNext()) { + Object obj = iterator.next(); + ++count; + if (count % 10000 == 0) { + println(count); + sleeping(10); + } + if (count % 100000 == 0) { + break; + } + } + if (iterator instanceof Closeable) { + try { + ((Closeable) iterator).close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + return count; + } + + public static int amountIn(List iterators) { + return iterators.stream().map(e -> HgStoreTestUtil.amountOf(e)).reduce(0, Integer::sum); + } + + public static void sleeping(long time) { + try { + Thread.sleep(time); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + public static void parallelTest(int threads, Runnable runner, + Consumer throwableConsumer) { + int threadsAmount = threads; + CountDownLatch countDownLatch = new CountDownLatch(threadsAmount); + ExecutorService pool = new ThreadPoolExecutor(threadsAmount, threadsAmount + 20, + 200, TimeUnit.SECONDS, + new ArrayBlockingQueue<>(1000)); + for (int i = 0; i < threadsAmount; i++) { + pool.submit( + () -> { + try { + runner.run(); + } catch (Throwable t) { + throwableConsumer.accept(t); + } + countDownLatch.countDown(); + }); + } + + try { + countDownLatch.await(); + pool.shutdown(); + + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + public static void repeatedlyTest(int times, Runnable runner) { + MetricX metrics = MetricX.ofStart(); + + for (int j = 0; j < times; j++) { + try { + runner.run(); + Thread.sleep(1); + } catch (InterruptedException e) { + e.printStackTrace(); + break; + } catch (Throwable t) { + metrics.countFail(); + t.printStackTrace(); + } + } + metrics.end(); + System.out.println("*************************************************"); + System.out.println("************* Repeatedly Test Completed **************"); + System.out.println("Total: " + metrics.past() / 1000 + " sec."); + System.out.println("Repeated: " + times + " times."); + System.out.println("Fail: " + metrics.getFailureCount() + " times."); + System.out.println("*************************************************"); + } +} diff --git a/hugegraph-store/hg-store-test/src/main/java/util/UnitTestBase.java b/hugegraph-store/hg-store-test/src/main/java/util/UnitTestBase.java new file mode 100644 index 000000000..dfab83e72 --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/util/UnitTestBase.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package util; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +import org.apache.hugegraph.rocksdb.access.RocksDBFactory; +import org.apache.hugegraph.rocksdb.access.RocksDBSession; +import org.apache.hugegraph.store.business.BusinessHandler; +import org.apache.hugegraph.store.business.BusinessHandlerImpl; +import org.apache.hugegraph.store.meta.PartitionManager; +import org.apache.hugegraph.store.options.HgStoreEngineOptions; +import org.apache.hugegraph.store.options.RaftRocksdbOptions; +import org.apache.hugegraph.store.pd.FakePdServiceProvider; +import org.apache.hugegraph.store.pd.PdProvider; + +public class UnitTestBase { + public static final RocksDBFactory FACTORY = RocksDBFactory.getInstance(); + private String dbPath; + + private BusinessHandler handler; + + public static boolean deleteDir(File dir) { + if (dir.isDirectory()) { + for (File file : dir.listFiles()) { + deleteDir(file); + } + } + return dir.delete(); + } + + public void initDB(String dbPath) { + this.dbPath = dbPath; + UnitTestBase.deleteDir(new File(dbPath)); + Map configMap = new HashMap<>(); + configMap.put("rocksdb.write_buffer_size", "1048576"); + configMap.put("rocksdb.bloom_filter_bits_per_key", "10"); + + RaftRocksdbOptions.initRocksdbGlobalConfig(configMap); + BusinessHandlerImpl.initRocksdb(configMap, null); + + + } + + protected BusinessHandler getBusinessHandler() { + if (handler == null) { + synchronized (this) { + if (handler == null) { + int partitionCount = 2; + HgStoreEngineOptions options = new HgStoreEngineOptions() {{ + setDataPath(dbPath); + setFakePdOptions(new HgStoreEngineOptions.FakePdOptions() {{ + setPartitionCount(partitionCount); + setPeersList("127.0.0.1"); + setStoreList("127.0.0.1"); + + }}); + }}; + + PdProvider pdProvider = new FakePdServiceProvider(options.getFakePdOptions()); + PartitionManager partitionManager = new PartitionManager(pdProvider, options); + BusinessHandler handler = new BusinessHandlerImpl(partitionManager); + } + } + } + + return handler; + } + + public RocksDBSession getDBSession(String dbName) { + RocksDBSession session = FACTORY.queryGraphDB(dbName); + if (session == null) { + session = FACTORY.createGraphDB(dbPath, dbName); + } + return session; + } + + public void close() { + handler.closeAll(); + } +} diff --git a/hugegraph-store/hg-store-test/src/main/java/util/UnsafeUtilTest.java b/hugegraph-store/hg-store-test/src/main/java/util/UnsafeUtilTest.java new file mode 100644 index 000000000..8392ca4f7 --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/java/util/UnsafeUtilTest.java @@ -0,0 +1,175 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.lang.reflect.Field; + +import org.apache.hugegraph.store.util.UnsafeUtf8Util; +import org.apache.hugegraph.store.util.UnsafeUtil; +import org.junit.Test; + +import lombok.Data; +import sun.misc.Unsafe; + +public class UnsafeUtilTest { + + + @Test + public void testHasUnsafe() { + assertTrue(UnsafeUtil.hasUnsafe()); + } + + @Test + public void testPutByte() { + UnsafeUtil.putByte("content".getBytes(), 0L, (byte) 99); + assertEquals((byte) 99, UnsafeUtil.getByte("content".getBytes(), 0L)); + } + + @Test + public void testMoveToString() { + assertEquals("a", UnsafeUtil.moveToString(new char[]{'a'})); + } + + @Test + public void testEncodedLength() { + assertEquals(10, UnsafeUtf8Util.encodedLength("aa中文aa")); + } + + @Test + public void testEncodeUtf8() { + assertEquals(10, UnsafeUtf8Util.encodeUtf8("aa中文aa", new byte[16], 0, 16)); + } + + @Test + public void testDecodeUtf8() { + assertEquals("co", UnsafeUtf8Util.decodeUtf8("content".getBytes(), 0, 2)); + } + + @Test + public void testUnsafeUtf8Util() { + String content = "content"; + UnsafeUtf8Util.decodeUtf8(content.getBytes(), 0, content.length()); + byte[] out = new byte[content.length()]; + UnsafeUtf8Util.encodeUtf8(content, out, 0, content.length()); + UnsafeUtf8Util.encodedLength(content); + } + + @Test + public void testUnsafeAccessor() { + Unsafe unsafe = null; + long offset = 0; + try { + Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe"); + unsafeField.setAccessible(true); + unsafe = (Unsafe) unsafeField.get(null); + UnsafeUtil.UnsafeAccessor acc = new UnsafeUtil.UnsafeAccessor(unsafe); + acc.getUnsafe(); + TestObject to = new TestObject(); + byte byteValue = 126; + offset = acc.objectFieldOffset(TestObject.class.getDeclaredField("b")); + acc.putByte(to, offset, byteValue); + byte b = acc.getByte(to, offset); + assertEquals(byteValue, b); + short shortValue = 1; + acc.putShort(to, offset, shortValue); + short shortResult = acc.getShort(to, offset); + assertEquals(shortValue, shortResult); + int intValue = 99; + acc.putInt(to, offset, intValue); + int i = acc.getInt(to, offset); + assertEquals(intValue, i); + long longValue = 11L; + acc.putLong(to, offset, longValue); + long l = acc.getLong(to, offset); + assertEquals(longValue, l); + acc.putBoolean(to, offset, false); + assertFalse(acc.getBoolean(to, offset)); + float f = 1; + acc.putFloat(to, offset, f); + float v = acc.getFloat(to, offset); + assertEquals(f, v, 0.0); + double d = 2; + acc.putDouble(to, offset, d); + double v1 = acc.getDouble(to, offset); + assertEquals(d, v1, 0.0); + TestObject o1 = new TestObject(); + acc.putObject(to, offset, o1); + Object o2 = acc.getObject(to, offset); + assertEquals(o1, o2); + offset = unsafe.allocateMemory(1024); + acc.putByte(offset, byteValue); + byte bResult = acc.getByte(offset); + assertEquals(byteValue, bResult); + acc.putShort(offset, shortValue); + short aShort1 = acc.getShort(offset); + assertEquals(aShort1, shortValue); + acc.putInt(offset, intValue); + assertEquals(acc.getInt(offset), intValue); + acc.putLong(offset, longValue); + assertEquals(acc.getLong(offset), longValue); + unsafe.freeMemory(offset); + offset = acc.objectFieldOffset(TestObject.class.getDeclaredField("b")); + acc.putByteVolatile(to, offset, byteValue); + b = acc.getByteVolatile(to, offset); + assertEquals(byteValue, b); + acc.putShortVolatile(to, offset, shortValue); + shortResult = acc.getShortVolatile(to, offset); + assertEquals(shortValue, shortResult); + acc.putIntVolatile(to, offset, intValue); + i = acc.getIntVolatile(to, offset); + assertEquals(intValue, i); + acc.putLongVolatile(to, offset, longValue); + l = acc.getLongVolatile(to, offset); + assertEquals(longValue, l); + acc.putBooleanVolatile(to, offset, false); + assertFalse(acc.getBooleanVolatile(to, offset)); + acc.putFloatVolatile(to, offset, f); + v = acc.getFloatVolatile(to, offset); + assertEquals(f, v, 0.0); + acc.putDoubleVolatile(to, offset, d); + v1 = acc.getDoubleVolatile(to, offset); + assertEquals(d, v1, 0.0); + acc.putObjectVolatile(to, offset, o1); + o2 = acc.getObjectVolatile(to, offset); + assertEquals(o1, o2); + + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Data + private class TestObject { + private Object o; + private byte b; + + public TestObject() { + + } + + public TestObject(Object o, byte b) { + this.o = o; + this.b = b; + } + } + +} diff --git a/hugegraph-store/hg-store-test/src/main/resources/version.txt b/hugegraph-store/hg-store-test/src/main/resources/version.txt new file mode 100644 index 000000000..b55f10804 --- /dev/null +++ b/hugegraph-store/hg-store-test/src/main/resources/version.txt @@ -0,0 +1 @@ +3.6.5 \ No newline at end of file diff --git a/hugegraph-store/init.sh b/hugegraph-store/init.sh new file mode 100644 index 000000000..6458ef618 --- /dev/null +++ b/hugegraph-store/init.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to You under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +mkdir output +touch output/1 +export JAVA_HOME=$ORACLEJDK_1_8_0_HOME + +mvn -N install #only install root project + +#$MAVEN_3_5_3_BIN/mvn -U deploy diff --git a/hugegraph-store/local-release.sh b/hugegraph-store/local-release.sh new file mode 100644 index 000000000..53a9bfce2 --- /dev/null +++ b/hugegraph-store/local-release.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to You under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +readonly VER=3.6.3 + +#mvn -DnewVersion=${VER}-SNAPSHOT -DprocessAllModules=true -DgenerateBackupPoms=false versions:set + +mvn -DremoveSnapshot=true -DprocessAllModules=true -DgenerateBackupPoms=true versions:set +mvn --settings ./settings.xml -Dmaven.test.skip=true clean install +mvn versions:revert \ No newline at end of file diff --git a/hugegraph-store/lombok.config b/hugegraph-store/lombok.config new file mode 100644 index 000000000..a23edb413 --- /dev/null +++ b/hugegraph-store/lombok.config @@ -0,0 +1,2 @@ +config.stopBubbling = true +lombok.addLombokGeneratedAnnotation = true \ No newline at end of file diff --git a/hugegraph-store/mvnw b/hugegraph-store/mvnw new file mode 100644 index 000000000..41c0f0c23 --- /dev/null +++ b/hugegraph-store/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/hugegraph-store/mvnw.cmd b/hugegraph-store/mvnw.cmd new file mode 100644 index 000000000..86115719e --- /dev/null +++ b/hugegraph-store/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/hugegraph-store/pom.xml b/hugegraph-store/pom.xml new file mode 100644 index 000000000..fdee09ee1 --- /dev/null +++ b/hugegraph-store/pom.xml @@ -0,0 +1,304 @@ + + + + + + 4.0.0 + + org.apache.hugegraph + hugegraph-store-root + ${revision} + pom + + + org.springframework.boot + spring-boot-starter-parent + 2.5.14 + + + + hg-store-common + hg-store-grpc + hg-store-client + hg-store-rocksdb + hg-store-core + hg-store-node + hg-store-dist + hg-store-cli + + + + + + 1.5.0 + 11 + 11 + 1.5.0 + 2.15.0 + + + + + + org.apache.hugegraph + hg-store-common + ${project.version} + + + org.apache.hugegraph + hg-store-grpc + ${project.version} + + + org.apache.hugegraph + hg-store-rocksdb + ${project.version} + + + org.apache.hugegraph + hg-store-client + ${project.version} + + + org.apache.hugegraph + hg-store-core + ${project.version} + + + org.apache.hugegraph + hg-store-transfer + ${project.version} + + + org.apache.logging.log4j + log4j-slf4j-impl + 2.15.0 + + + + + + + junit + junit + test + + + + + + + Baidu_Local + http://maven.baidu-int.com/nexus/content/repositories/Baidu_Local + + + Baidu_Local_Snapshots + http://maven.baidu-int.com/nexus/content/repositories/Baidu_Local_Snapshots + + + + + + + org.jacoco + jacoco-maven-plugin + 0.8.4 + + + + prepare-agent + + + + + + org.codehaus.mojo + flatten-maven-plugin + 1.2.7 + + true + resolveCiFriendliesOnly + + + + flatten + process-resources + + flatten + + + + flatten.clean + clean + + clean + + + + + + org.apache.maven.plugins + maven-clean-plugin + + + + ${project.basedir}/ + + *.tar + *.tar.gz + .flattened-pom.xml + + + + false + + + + + + + + + + + + client-test + + true + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.20 + + + client-test + + test + + test + + + + + + + + core-test + + true + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.20 + + + core-test + + test + + test + + + + + + + + common-test + + true + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.20 + + + common-test + + test + + test + + + + + + + + rocksdb-test + + true + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.20 + + + rocksdb-test + + test + + test + + + + + + + + + + + diff --git a/hugegraph-store/run.sh b/hugegraph-store/run.sh new file mode 100644 index 000000000..ae7c44dba --- /dev/null +++ b/hugegraph-store/run.sh @@ -0,0 +1,22 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to You under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +#/bin/bash + +export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64 +export PATH=${JAVA_HOME}/bin:$PATH + diff --git a/hugegraph-store/settings.xml b/hugegraph-store/settings.xml new file mode 100644 index 000000000..5a7da620a --- /dev/null +++ b/hugegraph-store/settings.xml @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + baidu + + + baidu-nexus + http://maven.baidu-int.com/nexus/content/groups/public + + true + + + false + + + + baidu-nexus-snapshot + http://maven.baidu-int.com/nexus/content/groups/public-snapshots + + false + + + false + + + + + + + + + + + + + + + + + + + + Baidu_Local + http://maven.baidu-int.com/nexus/content/repositories/Baidu_Local + + true + + + false + + + + Baidu_Local_Snapshots + + http://maven.baidu-int.com/nexus/content/repositories/Baidu_Local_Snapshots + + + false + + + true + always + + + + + + baidu-nexus + http://maven.baidu-int.com/nexus/content/groups/public + + true + + + false + + + + baidu-nexus-snapshot + http://maven.baidu-int.com/nexus/content/groups/public-snapshots + + false + + + true + + + + + + + + + baidu + + \ No newline at end of file diff --git a/hugegraph-store/start_server.sh b/hugegraph-store/start_server.sh new file mode 100644 index 000000000..280fe25a7 --- /dev/null +++ b/hugegraph-store/start_server.sh @@ -0,0 +1,55 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with this +# work for additional information regarding copyright ownership. The ASF +# licenses this file to You under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +function rename() +{ + cfilelist=$(find -maxdepth 1 -type d -printf '%f\n' ) + for cfilename in $cfilelist + do + if [[ $cfilename =~ SNAPSHOT ]] + then + mv $cfilename ${cfilename/-?.?.?-SNAPSHOT/} + fi + done +} + +wget -q -O output.tar.gz $AGILE_PRODUCT_HTTP_URL +tar -zxf output.tar.gz +cd output +rm -rf hugegraph-pd +find . -name "*.tar.gz" -exec tar -zxvf {} \; +rename + + +# start pd +pushd hugegraph-pd +sed -i 's/initial-store-list:.*/initial-store-list: 127.0.0.1:8500\n initial-store-count: 1/' conf/application.yml +sed -i 's/,127.0.0.1:8611,127.0.0.1:8612//' conf/application.yml +bin/start-hugegraph-pd.sh +popd +jps +sleep 10 + + +# start store +pushd hugegraph-store +sed -i 's#local os=`uname`#local os=Linux#g' bin/util.sh +sed -i 's/export LD_PRELOAD/#export LD_PRELOAD/' bin/start-hugegraph-store.sh +bin/start-hugegraph-store.sh +popd +jps +sleep 5 \ No newline at end of file