Merge branch 'pd-store' into merge-store

This commit is contained in:
imbajin 2023-08-07 23:26:28 +08:00 committed by GitHub
commit c72bda6237
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
236 changed files with 36208 additions and 2 deletions

1
hugegraph-pd/.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
* text=auto eol=lf

13
hugegraph-pd/.gitignore vendored Normal file
View File

@ -0,0 +1,13 @@
/hg-pd-grpc/src/main/java/
/.idea/
/hg-pd-grpc/target/
/dist/
**/target/
.DS_Store
**/tmp/
*.log
*.iml
/hg-pd-common/target_B000000405016P_Oct-28-114458-2021_conflict_parent/
dist/
.flattened-pom.xml

View File

@ -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();
}
}

Binary file not shown.

View File

@ -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.0/apache-maven-3.6.0-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar

View File

@ -0,0 +1,940 @@
# PD部署说明
## PD配置
- 配置文件在application.yml
````
license:
# 验证使用的配置文件所在目录,包括主题、密码等
verify-path: 'conf/verify-license.json'
# license文件所在目录通过hugegraph-signature项目生成
license-path: 'conf/hugegraph.license'
pd:
# 存储路径
data-path: ./pd_data
# 自动扩容的检查周期定时检查每个Store的分区数量自动进行分区数量平衡
patrol-interval: 1800
# 是否允许批量单副本入库
enable-batch-load: false
store:
# store下线时间。超过该时间认为store永久不可用分配副本到其他机器单位秒
max-down-time: 172800
partition:
# 默认每个分区副本数
default-shard-count: 3
# 默认每机器最大副本数,初始分区数= store-max-shard-count * store-number / default-shard-count
store-max-shard-count: 12
````
##store配置
-配置文件在application.yml,配置pdserver的address
````
pdserver:
# pd服务地址多个pd地址用逗号分割
address: pdserver ip:端口
````
## Hugegraph配置
- 配置项在hugegraph的启动脚本start-hugegraph.sh中
````
if [ -z "$META_SERVERS" ];then
META_SERVERS="pdserver ip:端口"
fi
if [ -z "$PD_PEERS" ];then
PD_PEERS="pdserver ip:端口"
fi
````
## RESTFUL API
- pd提供了一些restful API可以获取集群分区存储节点等一系列信息
###获取集群统计信息
#### 获取集群统计信息
###### Method & Url
```
GET http://localhost:8620/v1/cluster
```
###### Response Status
```json
200
```
###### Response Body
```json
{
"message": "OK",
"data": {
"state": "Cluster_OK",
"pdList": [
{
"raftUrl": "10.232.132.38:8610",
"grpcUrl": "10.232.132.38:8686",
"restUrl": "10.232.132.38:8620",
"state": "Up",
"dataPath": "./pd_data",
"role": "Leader",
"serviceName": "10.232.132.38:8686-PD",
"serviceVersion": "",
"startTimeStamp": 0
}
],
"pdLeader": {
"raftUrl": "10.232.132.38:8610",
"grpcUrl": "10.232.132.38:8686",
"restUrl": "10.232.132.38:8620",
"state": "Up",
"dataPath": "./pd_data",
"role": "Leader",
"serviceName": "10.232.132.38:8686-PD",
"serviceVersion": "",
"startTimeStamp": 0
},
"memberSize": 1,
"stores": [
{
"storeId": 110645464809417136,
"address": "10.232.132.38:8500",
"raftAddress": "10.232.132.38:8510",
"version": "3.6.3",
"state": "Up"
}
],
"storeSize": 1,
"onlineStoreSize": 1,
"offlineStoreSize": 0,
"graphSize": 3,
"partitionSize": 4,
"shardCount": 3,
"keyCount": 1707,
"dataSize": 19
},
"status": 0
}
```
#### 获取pd集群成员信息
###### Method & Url
```
GET http://localhost:8620/v1/member
```
###### Response Status
```json
200
```
###### Response Body
```json
{
"message": "OK",
"data": {
"pdLeader": {
"raftUrl": "10.232.132.38:8610",
"grpcUrl": "10.232.132.38:8686",
"restUrl": "10.232.132.38:8620",
"state": "Up",
"dataPath": "./pd_data",
"role": "Leader",
"serviceName": "10.232.132.38:8686-PD",
"serviceVersion": "",
"startTimeStamp": 0
},
"pdList": [
{
"raftUrl": "10.232.132.38:8610",
"grpcUrl": "10.232.132.38:8686",
"restUrl": "10.232.132.38:8620",
"state": "Up",
"dataPath": "./pd_data",
"role": "Leader",
"serviceName": "10.232.132.38:8686-PD",
"serviceVersion": "",
"startTimeStamp": 0
}
],
"state": "Cluster_OK"
},
"status": 0
}
```
###存储节点相关
#### 获取集群所有的store的信息
###### Method & Url
```
GET http://localhost:8620/v1/stores
```
###### Response Status
```json
200
```
###### Request Body
```json
{
"message": "OK",
"data": {
"stores": [
{
"storeId": 110645464809417136,
"address": "10.232.132.38:8500",
"raftAddress": "10.232.132.38:8510",
"version": "3.6.3",
"state": "Up",
"deployPath": "",
"startTimeStamp": 1658491024,
"lastHeatBeat": 1658491748560,
"capacity": 1968740712448,
"available": 1959665557504,
"partitionCount": 4,
"graphSize": 3,
"keyCount": 1128,
"leaderCount": 4,
"serviceName": "10.232.132.38:8500-store",
"serviceVersion": "3.6.3",
"partitions": [
{
"partitionId": 0,
"graphName": "DEFAULT/hugegraph/s",
"role": "Leader",
"workState": "PState_Normal"
},
{
"partitionId": 0,
"graphName": "DEFAULT/hugegraph/g",
"role": "Leader",
"workState": "PState_Normal"
},
{
"partitionId": 1,
"graphName": "DEFAULT/hugegraph/g",
"role": "Leader",
"workState": "PState_Normal"
},
{
"partitionId": 2,
"graphName": "DEFAULT/hugegraph/g",
"role": "Leader",
"workState": "PState_Normal"
},
{
"partitionId": 3,
"graphName": "DEFAULT/hugegraph/g",
"role": "Leader",
"workState": "PState_Normal"
},
{
"partitionId": 0,
"graphName": "DEFAULT/hugegraph/m",
"role": "Leader",
"workState": "PState_Normal"
}
]
}
]
},
"status": 0
}
```
#### 获取单个store的信息
###### Method & Url
```
GET http://localhost:8620/v1/store/{storeId}
```
###### Response Status
```json
200
```
###### Request Body
```json
{
"message": "OK",
"data": {
"storeId": 110645464809417136,
"address": "10.232.132.38:8500",
"raftAddress": "10.232.132.38:8510",
"version": "3.6.3",
"state": "Up",
"deployPath": "",
"startTimeStamp": 1658491024,
"lastHeatBeat": 1658491838632,
"capacity": 1968740712448,
"available": 1959665549312,
"partitionCount": 4,
"graphSize": 3,
"keyCount": 1128,
"leaderCount": 4,
"serviceName": "10.232.132.38:8500-store",
"serviceVersion": "3.6.3",
"partitions": [
{
"partitionId": 0,
"graphName": "DEFAULT/hugegraph/s",
"role": "Leader",
"workState": "PState_Normal"
},
{
"partitionId": 0,
"graphName": "DEFAULT/hugegraph/g",
"role": "Leader",
"workState": "PState_Normal"
},
{
"partitionId": 1,
"graphName": "DEFAULT/hugegraph/g",
"role": "Leader",
"workState": "PState_Normal"
},
{
"partitionId": 2,
"graphName": "DEFAULT/hugegraph/g",
"role": "Leader",
"workState": "PState_Normal"
},
{
"partitionId": 3,
"graphName": "DEFAULT/hugegraph/g",
"role": "Leader",
"workState": "PState_Normal"
},
{
"partitionId": 0,
"graphName": "DEFAULT/hugegraph/m",
"role": "Leader",
"workState": "PState_Normal"
}
]
},
"status": 0
}
```
### 分区相关
#### 获取分区信息
###### Method & Url
```
GET http://localhost:8620/v1/highLevelPartitions
```
###### Response Status
```json
200
```
###### Response Body
```json
{
"message": "OK",
"data": {
"partitions": [
{
"partitionId": 0,
"state": "PState_Normal",
"leaderAddress": "10.232.132.38:8500",
"keyCount": 0,
"dataSize": 0,
"shardState": "SState_Normal",
"graphs": [
{
"graphName": "DEFAULT/hugegraph/g",
"keyCount": 361,
"startKey": 0,
"endKey": 0,
"dataSize": 8,
"workState": "PState_Normal",
"partitionId": 0
},
{
"graphName": "DEFAULT/hugegraph/m",
"keyCount": 361,
"startKey": 0,
"endKey": 0,
"dataSize": 13,
"workState": "PState_Normal",
"partitionId": 0
},
{
"graphName": "DEFAULT/hugegraph/s",
"keyCount": 361,
"startKey": 0,
"endKey": 65535,
"dataSize": 6,
"workState": "PState_Normal",
"partitionId": 0
}
],
"shards": [
{
"storeId": 110645464809417136,
"role": "Leader",
"state": "SState_Normal",
"progress": 0,
"partitionId": 0,
"address": "10.232.132.38:8500"
}
]
},
{
"partitionId": 1,
"state": "PState_Normal",
"leaderAddress": "10.232.132.38:8500",
"keyCount": 0,
"dataSize": 0,
"shardState": "SState_Normal",
"graphs": [
{
"graphName": "DEFAULT/hugegraph/g",
"keyCount": 8,
"startKey": 16384,
"endKey": 32768,
"dataSize": 5,
"workState": "PState_Normal",
"partitionId": 1
}
],
"shards": [
{
"storeId": 110645464809417136,
"role": "Leader",
"state": "SState_Normal",
"progress": 0,
"partitionId": 1,
"address": "10.232.132.38:8500"
}
]
},
{
"partitionId": 2,
"state": "PState_Normal",
"leaderAddress": "10.232.132.38:8500",
"keyCount": 0,
"dataSize": 0,
"shardState": "SState_Normal",
"graphs": [
{
"graphName": "DEFAULT/hugegraph/g",
"keyCount": 18,
"startKey": 32768,
"endKey": 49152,
"dataSize": 8,
"workState": "PState_Normal",
"partitionId": 2
}
],
"shards": [
{
"storeId": 110645464809417136,
"role": "Leader",
"state": "SState_Normal",
"progress": 0,
"partitionId": 2,
"address": "10.232.132.38:8500"
}
]
},
{
"partitionId": 3,
"state": "PState_Normal",
"leaderAddress": "10.232.132.38:8500",
"keyCount": 0,
"dataSize": 0,
"shardState": "SState_Normal",
"graphs": [
{
"graphName": "DEFAULT/hugegraph/g",
"keyCount": 19,
"startKey": 49152,
"endKey": 65536,
"dataSize": 8,
"workState": "PState_Normal",
"partitionId": 3
}
],
"shards": [
{
"storeId": 110645464809417136,
"role": "Leader",
"state": "SState_Normal",
"progress": 0,
"partitionId": 3,
"address": "10.232.132.38:8500"
}
]
}
]
},
"status": 0
}
```
###获取图信息
#### 获取所有的图信息
###### Method & Url
```
GET http://localhost:8620/v1/graphs
```
###### Response Status
```json
200
```
###### Response Body
```json
{
"message": "OK",
"data": {
"graphs": [
{
"graphName": "DEFAULT/hugegraph/g",
"partitionCount": 4,
"state": "PState_Normal",
"partitions": [
{
"partitionId": 0,
"graphName": "DEFAULT/hugegraph/g",
"workState": "PState_Normal",
"startKey": 0,
"endKey": 16384,
"shards": [
{
"partitionId": 0,
"storeId": 110645464809417136,
"state": "SState_Normal",
"role": "Leader",
"progress": 0
}
]
},
{
"partitionId": 1,
"graphName": "DEFAULT/hugegraph/g",
"workState": "PState_Normal",
"startKey": 16384,
"endKey": 32768,
"shards": [
{
"partitionId": 1,
"storeId": 110645464809417136,
"state": "SState_Normal",
"role": "Leader",
"progress": 0
}
]
},
{
"partitionId": 2,
"graphName": "DEFAULT/hugegraph/g",
"workState": "PState_Normal",
"startKey": 32768,
"endKey": 49152,
"shards": [
{
"partitionId": 2,
"storeId": 110645464809417136,
"state": "SState_Normal",
"role": "Leader",
"progress": 0
}
]
},
{
"partitionId": 3,
"graphName": "DEFAULT/hugegraph/g",
"workState": "PState_Normal",
"startKey": 49152,
"endKey": 65536,
"shards": [
{
"partitionId": 3,
"storeId": 110645464809417136,
"state": "SState_Normal",
"role": "Leader",
"progress": 0
}
]
}
],
"dataSize": 48,
"keyCount": 1128,
"nodeCount": 0,
"edgeCount": 0
},
{
"graphName": "DEFAULT/hugegraph/m",
"partitionCount": 1,
"state": "PState_Normal",
"partitions": [
{
"partitionId": 0,
"graphName": "DEFAULT/hugegraph/m",
"workState": "PState_Normal",
"startKey": 0,
"endKey": 65535,
"shards": [
{
"partitionId": 0,
"storeId": 110645464809417136,
"state": "SState_Normal",
"role": "Leader",
"progress": 0
}
]
}
],
"dataSize": 48,
"keyCount": 1128,
"nodeCount": 0,
"edgeCount": 0
},
{
"graphName": "DEFAULT/hugegraph/s",
"partitionCount": 1,
"state": "PState_Normal",
"partitions": [
{
"partitionId": 0,
"graphName": "DEFAULT/hugegraph/s",
"workState": "PState_Normal",
"startKey": 0,
"endKey": 65535,
"shards": [
{
"partitionId": 0,
"storeId": 110645464809417136,
"state": "SState_Normal",
"role": "Leader",
"progress": 0
}
]
}
],
"dataSize": 48,
"keyCount": 1128,
"nodeCount": 0,
"edgeCount": 0
}
]
},
"status": 0
}
```
#### 获取单个图信息
###### Method & Url
```
GET http://localhost:8620/v1/graph/{graphName}
```
###### Response Status
```json
200
```
###### Response Body
```json
{
"message": "OK",
"data": {
"graphName": "DEFAULT/hugegraph/g",
"partitionCount": 4,
"state": "PState_Normal",
"partitions": [
{
"partitionId": 0,
"graphName": "DEFAULT/hugegraph/g",
"workState": "PState_Normal",
"startKey": 0,
"endKey": 16384,
"shards": [
{
"partitionId": 0,
"storeId": 110645464809417136,
"state": "SState_Normal",
"role": "Leader",
"progress": 0
}
]
},
{
"partitionId": 1,
"graphName": "DEFAULT/hugegraph/g",
"workState": "PState_Normal",
"startKey": 16384,
"endKey": 32768,
"shards": [
{
"partitionId": 1,
"storeId": 110645464809417136,
"state": "SState_Normal",
"role": "Leader",
"progress": 0
}
]
},
{
"partitionId": 2,
"graphName": "DEFAULT/hugegraph/g",
"workState": "PState_Normal",
"startKey": 32768,
"endKey": 49152,
"shards": [
{
"partitionId": 2,
"storeId": 110645464809417136,
"state": "SState_Normal",
"role": "Leader",
"progress": 0
}
]
},
{
"partitionId": 3,
"graphName": "DEFAULT/hugegraph/g",
"workState": "PState_Normal",
"startKey": 49152,
"endKey": 65536,
"shards": [
{
"partitionId": 3,
"storeId": 110645464809417136,
"state": "SState_Normal",
"role": "Leader",
"progress": 0
}
]
}
],
"dataSize": 48,
"keyCount": 1128,
"nodeCount": 0,
"edgeCount": 0
},
"status": 0
}
```
###获取shard的信息
#### 获取所有shard的信息
###### Method & Url
```
GET http://localhost:8620/v1/shards
```
###### Response Status
```json
200
```
###### Response Body
```json
{
"message": "OK",
"data": {
"shards": [
{
"storeId": 110645464809417136,
"partitionId": 0,
"role": "Leader",
"state": "SState_Normal",
"graphName": "DEFAULT/hugegraph/g",
"progress": 0
},
{
"storeId": 110645464809417136,
"partitionId": 1,
"role": "Leader",
"state": "SState_Normal",
"graphName": "DEFAULT/hugegraph/g",
"progress": 0
},
{
"storeId": 110645464809417136,
"partitionId": 2,
"role": "Leader",
"state": "SState_Normal",
"graphName": "DEFAULT/hugegraph/g",
"progress": 0
},
{
"storeId": 110645464809417136,
"partitionId": 3,
"role": "Leader",
"state": "SState_Normal",
"graphName": "DEFAULT/hugegraph/g",
"progress": 0
},
{
"storeId": 110645464809417136,
"partitionId": 0,
"role": "Leader",
"state": "SState_Normal",
"graphName": "DEFAULT/hugegraph/m",
"progress": 0
},
{
"storeId": 110645464809417136,
"partitionId": 0,
"role": "Leader",
"state": "SState_Normal",
"graphName": "DEFAULT/hugegraph/s",
"progress": 0
}
]
},
"status": 0
}
```
###服务注册
#### 注册服务
###### Method & Url
```
POST http://127.0.0.1:8620/v1/registry
```
###### Request Body
```json
200
```
###### Response Status
```json
{
"appName":"aaaa",
"version":"version1",
"address":"address1",
"interval":"9223372036854775807",
"labels": {
"aaa": "aaaavalue"
}
}
```
appName所属服务名
version所属服务版本号
address服务实例地址+端口
interval实例心跳间隔字符串最大9223372036854775807
labels: 自定义标签,若服务名为'hg'即hugeserver时需要提供key为cores的项进行cpu核数的验证
###### 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
200
```
###### Response Status
```json
{
"appName":"aaaa",
"version":"version1",
"labels": {
"aaa": "aaaavalue"
}
}
```
以下三项可全部为空,则获取所有服务节点的信息:
-- appName过滤所属服务名的条件
-- version过滤所属服务版本号的条件此项有值则appName不能为空
-- labels: 过滤自定义标签的条件
###### Response Body
```json
{
"errorType": "OK",
"message": null,
"data": [
{
"id": null,
"appName": "aaaa",
"version": "version1",
"address": "address1",
"interval": "9223372036854775807",
"labels": {
"aaa": "aaaavalue"
}
}
]
}
```
errorType状态码
message状态码为错误时的具体出错信息
data获取的服务节点信息

54
hugegraph-pd/build-pre.sh Normal file
View File

@ -0,0 +1,54 @@
#!/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://127.0.0.1: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-----------------------
readonly FILE_NAME=hugegraph-pd-3.6.3.tar.gz
readonly REPO_URL_FILE=http://127.0.0.1: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"

39
hugegraph-pd/build.sh Normal file
View File

@ -0,0 +1,39 @@
#!/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
# TODO: remove later
readonly REPO_URL=http://maven.baidu-int.com/nexus/content/repositories/Baidu_Local_Snapshots
if [ ! -n "$1" ] ;then
GOAL=package
else
GOAL=$1
fi
$MAVEN_3_5_3_BIN/mvn -Dmaven.test.skip=true -DaltDeploymentRepository=Baidu_Local_Snapshots::default::${REPO_URL} clean ${GOAL}
echo "mv dist...."
mv dist output
ls output
echo "mv dist done"
echo "show output...."
ls output
echo "show output done"

43
hugegraph-pd/ci.yml Normal file
View File

@ -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.
#
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: deploy
tool: build_submitter
env: DECK_CENTOS7U5_K3
command: sh build.sh deploy
release: true

Binary file not shown.

View File

@ -0,0 +1,6 @@
{
"subject": "hugegraph-license",
"public_alias": "publiccert",
"store_ticket": "803b6cc3-d144-47e8-948f-ec8b39c8881e",
"publickey_path": "/public-certs.store"
}

View File

@ -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://127.0.0.1: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

View File

@ -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://127.0.0.1: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

View File

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with this
work for additional information regarding copyright ownership. The ASF
licenses this file to You under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
-->
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.hugegraph</groupId>
<artifactId>hugegraph-pd</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>hg-pd-client</artifactId>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.17.0</version>
</dependency>
<dependency>
<groupId>org.apache.hugegraph</groupId>
<artifactId>hg-pd-grpc</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.apache.hugegraph</groupId>
<artifactId>hg-pd-common</artifactId>
<version>${revision}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.28</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -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.pd.client;
import java.io.Closeable;
import java.util.LinkedList;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;
import java.util.stream.Stream;
import org.apache.hugegraph.pd.common.KVPair;
import org.apache.hugegraph.pd.common.PDException;
import org.apache.hugegraph.pd.grpc.Metapb;
import org.apache.hugegraph.pd.grpc.PDGrpc;
import org.apache.hugegraph.pd.grpc.PDGrpc.PDBlockingStub;
import org.apache.hugegraph.pd.grpc.Pdpb;
import org.apache.hugegraph.pd.grpc.Pdpb.GetMembersRequest;
import org.apache.hugegraph.pd.grpc.Pdpb.GetMembersResponse;
import io.grpc.Channel;
import io.grpc.ClientCall;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.MethodDescriptor;
import io.grpc.StatusRuntimeException;
import io.grpc.stub.AbstractBlockingStub;
import io.grpc.stub.AbstractStub;
import io.grpc.stub.ClientCalls;
import io.grpc.stub.StreamObserver;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public abstract class AbstractClient implements Closeable {
private static final ConcurrentHashMap<String, ManagedChannel> chs = new ConcurrentHashMap<>();
public static Pdpb.ResponseHeader okHeader = Pdpb.ResponseHeader.newBuilder().setError(
Pdpb.Error.newBuilder().setType(Pdpb.ErrorType.OK)).build();
protected final Pdpb.RequestHeader header;
protected final AbstractClientStubProxy stubProxy;
protected final PDConfig config;
protected ManagedChannel channel = null;
protected volatile ConcurrentMap<String, AbstractBlockingStub> stubs = null;
protected AbstractClient(PDConfig config) {
String[] hosts = config.getServerHost().split(",");
this.stubProxy = new AbstractClientStubProxy(hosts);
this.header = Pdpb.RequestHeader.getDefaultInstance();
this.config = config;
}
public static Pdpb.ResponseHeader newErrorHeader(int errorCode, String errorMsg) {
Pdpb.ResponseHeader header = Pdpb.ResponseHeader.newBuilder().setError(
Pdpb.Error.newBuilder().setTypeValue(errorCode).setMessage(errorMsg)).build();
return header;
}
protected static void handleErrors(Pdpb.ResponseHeader header) throws PDException {
if (header.hasError() && header.getError().getType() != Pdpb.ErrorType.OK) {
throw new PDException(header.getError().getTypeValue(),
String.format("PD request error, error code = %d, msg = %s",
header.getError().getTypeValue(),
header.getError().getMessage()));
}
}
protected AbstractBlockingStub getBlockingStub() throws PDException {
if (stubProxy.getBlockingStub() == null) {
synchronized (this) {
if (stubProxy.getBlockingStub() == null) {
String host = resetStub();
if (host.isEmpty()) {
throw new PDException(Pdpb.ErrorType.PD_UNREACHABLE_VALUE,
"PD unreachable, pd.peers=" +
config.getServerHost());
}
}
}
}
return (AbstractBlockingStub) stubProxy.getBlockingStub()
.withDeadlineAfter(config.getGrpcTimeOut(),
TimeUnit.MILLISECONDS);
}
protected AbstractStub getStub() throws PDException {
if (stubProxy.getStub() == null) {
synchronized (this) {
if (stubProxy.getStub() == null) {
String host = resetStub();
if (host.isEmpty()) {
throw new PDException(Pdpb.ErrorType.PD_UNREACHABLE_VALUE,
"PD unreachable, pd.peers=" +
config.getServerHost());
}
}
}
}
return stubProxy.getStub();
}
protected abstract AbstractStub createStub();
protected abstract AbstractBlockingStub createBlockingStub();
private String resetStub() {
String leaderHost = "";
for (int i = 0; i < stubProxy.getHostCount(); i++) {
String host = stubProxy.nextHost();
channel = ManagedChannelBuilder.forTarget(host).usePlaintext().build();
PDBlockingStub blockingStub = PDGrpc.newBlockingStub(channel)
.withDeadlineAfter(config.getGrpcTimeOut(),
TimeUnit.MILLISECONDS);
try {
GetMembersRequest request = Pdpb.GetMembersRequest.newBuilder()
.setHeader(header).build();
GetMembersResponse members = blockingStub.getMembers(request);
Metapb.Member leader = members.getLeader();
leaderHost = leader.getGrpcUrl();
close();
channel = ManagedChannelBuilder.forTarget(leaderHost).usePlaintext().build();
stubProxy.setBlockingStub(createBlockingStub());
stubProxy.setStub(createStub());
log.info("PDClient connect to host = {} success", leaderHost);
break;
} catch (Exception e) {
log.error("PDClient connect to {} exception {}, {}", host, e.getMessage(),
e.getCause() != null ? e.getCause().getMessage() : "");
}
}
return leaderHost;
}
protected <ReqT, RespT, StubT extends AbstractBlockingStub<StubT>> RespT blockingUnaryCall(
MethodDescriptor<ReqT, RespT> method, ReqT req) throws PDException {
return blockingUnaryCall(method, req, 5);
}
protected <ReqT, RespT, StubT extends AbstractBlockingStub<StubT>> RespT blockingUnaryCall(
MethodDescriptor<ReqT, RespT> method, ReqT req, int retry) throws PDException {
AbstractBlockingStub stub = getBlockingStub();
try {
RespT resp =
ClientCalls.blockingUnaryCall(stub.getChannel(), method, stub.getCallOptions(),
req);
return resp;
} catch (Exception e) {
log.error(method.getFullMethodName() + " exception, {}", e.getMessage());
if (e instanceof StatusRuntimeException) {
if (retry < stubProxy.getHostCount()) {
// 网络不通关掉之前连接换host重新连接
synchronized (this) {
stubProxy.setBlockingStub(null);
}
return blockingUnaryCall(method, req, ++retry);
}
}
}
return null;
}
// this.stubs = new ConcurrentHashMap<String,AbstractBlockingStub>(hosts.length);
private AbstractBlockingStub getConcurrentBlockingStub(String address) {
AbstractBlockingStub stub = stubs.get(address);
if (stub != null) {
return stub;
}
Channel ch = ManagedChannelBuilder.forTarget(address).usePlaintext().build();
PDBlockingStub blockingStub =
PDGrpc.newBlockingStub(ch).withDeadlineAfter(config.getGrpcTimeOut(),
TimeUnit.MILLISECONDS);
stubs.put(address, blockingStub);
return blockingStub;
}
protected <ReqT, RespT> KVPair<Boolean, RespT> concurrentBlockingUnaryCall(
MethodDescriptor<ReqT, RespT> method, ReqT req, Predicate<RespT> predicate) {
LinkedList<String> hostList = this.stubProxy.getHostList();
if (this.stubs == null) {
synchronized (this) {
if (this.stubs == null) {
this.stubs = new ConcurrentHashMap<>(hostList.size());
}
}
}
Stream<RespT> respTStream = hostList.parallelStream().map((address) -> {
AbstractBlockingStub stub = getConcurrentBlockingStub(address);
RespT resp = ClientCalls.blockingUnaryCall(stub.getChannel(),
method, stub.getCallOptions(), req);
return resp;
});
KVPair<Boolean, RespT> pair;
AtomicReference<RespT> response = new AtomicReference<>();
boolean result = respTStream.anyMatch((r) -> {
response.set(r);
return predicate.test(r);
});
if (result) {
pair = new KVPair<>(true, null);
} else {
pair = new KVPair<>(false, response.get());
}
return pair;
}
protected <ReqT, RespT> void streamingCall(MethodDescriptor<ReqT, RespT> method, ReqT request,
StreamObserver<RespT> responseObserver,
int retry) throws PDException {
AbstractStub stub = getStub();
try {
ClientCall<ReqT, RespT> call = stub.getChannel().newCall(method, stub.getCallOptions());
ClientCalls.asyncServerStreamingCall(call, request, responseObserver);
} catch (Exception e) {
if (e instanceof StatusRuntimeException) {
if (retry < stubProxy.getHostCount()) {
synchronized (this) {
stubProxy.setStub(null);
}
streamingCall(method, request, responseObserver, ++retry);
return;
}
}
log.error("rpc call with exception, {}", e.getMessage());
}
}
@Override
public void close() {
closeChannel(channel);
if (stubs != null) {
for (AbstractBlockingStub stub : stubs.values()) {
closeChannel((ManagedChannel) stub.getChannel());
}
}
}
private void closeChannel(ManagedChannel channel) {
try {
while (channel != null &&
!channel.shutdownNow().awaitTermination(100, TimeUnit.MILLISECONDS)) {
continue;
}
} catch (Exception e) {
log.info("Close channel with error : ", e);
}
}
}

View File

@ -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.pd.client;
import java.util.LinkedList;
import io.grpc.stub.AbstractBlockingStub;
import io.grpc.stub.AbstractStub;
public class AbstractClientStubProxy {
private final LinkedList<String> hostList = new LinkedList<>();
private AbstractBlockingStub blockingStub;
private AbstractStub stub;
public AbstractClientStubProxy(String[] hosts) {
for (String host : hosts) {
if (!host.isEmpty()) {
hostList.offer(host);
}
}
}
public LinkedList<String> getHostList() {
return hostList;
}
public String nextHost() {
String host = hostList.poll();
hostList.offer(host); //移到尾部
return host;
}
public AbstractBlockingStub getBlockingStub() {
return this.blockingStub;
}
public void setBlockingStub(AbstractBlockingStub stub) {
this.blockingStub = stub;
}
public String getHost() {
return hostList.peek();
}
public int getHostCount() {
return hostList.size();
}
public AbstractStub getStub() {
return stub;
}
public void setStub(AbstractStub stub) {
this.stub = stub;
}
}

View File

@ -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.pd.client;
import java.util.concurrent.ConcurrentHashMap;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
public class Channels {
private static final ConcurrentHashMap<String, ManagedChannel> chs = new ConcurrentHashMap<>();
public static ManagedChannel getChannel(String target) {
ManagedChannel channel;
if ((channel = chs.get(target)) == null || channel.isShutdown() || channel.isTerminated()) {
synchronized (chs) {
if ((channel = chs.get(target)) == null || channel.isShutdown() ||
channel.isTerminated()) {
channel = ManagedChannelBuilder.forTarget(target).usePlaintext().build();
chs.put(target, channel);
}
}
}
return channel;
}
}

View File

@ -0,0 +1,338 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.apache.hugegraph.pd.client;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.hugegraph.pd.common.GraphCache;
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.pd.grpc.Metapb.Partition;
import org.apache.hugegraph.pd.grpc.Metapb.Shard;
import org.apache.hugegraph.pd.grpc.Metapb.ShardGroup;
import org.apache.hugegraph.pd.grpc.Pdpb.CachePartitionResponse;
import org.apache.hugegraph.pd.grpc.Pdpb.CacheResponse;
import com.google.common.collect.Range;
import com.google.common.collect.RangeMap;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class ClientCache {
private final AtomicBoolean initialized = new AtomicBoolean(false);
private final org.apache.hugegraph.pd.client.PDClient client;
private volatile Map<Integer, KVPair<ShardGroup, Shard>> groups;
private volatile Map<Long, Metapb.Store> stores;
private volatile Map<String, GraphCache> caches = new ConcurrentHashMap<>();
public ClientCache(org.apache.hugegraph.pd.client.PDClient pdClient) {
groups = new ConcurrentHashMap<>();
stores = new ConcurrentHashMap<>();
client = pdClient;
}
private GraphCache getGraphCache(String graphName) {
GraphCache graph;
if ((graph = caches.get(graphName)) == null) {
synchronized (caches) {
if ((graph = caches.get(graphName)) == null) {
graph = new GraphCache();
caches.put(graphName, graph);
}
}
}
return graph;
}
public KVPair<Partition, Shard> getPartitionById(String graphName, int partId) {
try {
GraphCache graph = initGraph(graphName);
Partition partition = graph.getPartition(partId);
Shard shard = groups.get(partId).getValue();
if (partition == null || shard == null) {
return null;
}
return new KVPair<>(partition, shard);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private KVPair<Partition, Shard> getPair(int partId, GraphCache graph) {
Partition p = graph.getPartition(partId);
KVPair<ShardGroup, Shard> pair = groups.get(partId);
if (p != null && pair != null) {
Shard s = pair.getValue();
if (s == null) {
pair.setValue(getLeader(partId));
return new KVPair<>(p, pair.getValue());
} else {
return new KVPair<>(p, s);
}
}
return null;
}
/**
* 根据key的hashcode返回分区信息
*
* @param graphName
* @param code
* @return
*/
public KVPair<Partition, Shard> getPartitionByCode(String graphName, long code) {
try {
GraphCache graph = initGraph(graphName);
RangeMap<Long, Integer> range = graph.getRange();
Integer pId = range.get(code);
if (pId != null) {
return getPair(pId, graph);
}
return null;
} catch (PDException e) {
throw new RuntimeException(e);
}
}
private GraphCache initGraph(String graphName) throws PDException {
initCache();
GraphCache graph = getGraphCache(graphName);
if (!graph.getInitialized().get()) {
synchronized (graph) {
if (!graph.getInitialized().get()) {
CachePartitionResponse pc = client.getPartitionCache(graphName);
RangeMap<Long, Integer> range = graph.getRange();
List<Partition> ps = pc.getPartitionsList();
HashMap<Integer, Partition> gps = new HashMap<>(ps.size(), 1);
for (Partition p : ps) {
gps.put(p.getId(), p);
range.put(Range.closedOpen(p.getStartKey(), p.getEndKey()), p.getId());
}
graph.setPartitions(gps);
graph.getInitialized().set(true);
}
}
}
return graph;
}
private void initCache() throws PDException {
if (!initialized.get()) {
synchronized (this) {
if (!initialized.get()) {
CacheResponse cache = client.getClientCache();
List<ShardGroup> shardGroups = cache.getShardsList();
for (ShardGroup s : shardGroups) {
this.groups.put(s.getId(), new KVPair<>(s, getLeader(s.getId())));
}
List<Metapb.Store> stores = cache.getStoresList();
for (Metapb.Store store : stores) {
this.stores.put(store.getId(), store);
}
List<Metapb.Graph> graphs = cache.getGraphsList();
for (Metapb.Graph g : graphs) {
GraphCache c = new GraphCache(g);
caches.put(g.getGraphName(), c);
}
initialized.set(true);
}
}
}
}
/**
* 返回key所在的分区信息
*
* @param key
* @return
*/
public KVPair<Partition, Shard> getPartitionByKey(String graphName, byte[] key) {
int code = PartitionUtils.calcHashcode(key);
return getPartitionByCode(graphName, code);
}
public boolean update(String graphName, int partId, Partition partition) {
GraphCache graph = getGraphCache(graphName);
try {
Partition p = graph.getPartition(partId);
if (p != null && p.equals(partition)) {
return false;
}
RangeMap<Long, Integer> range = graph.getRange();
graph.addPartition(partId, partition);
if (p != null) {
// old [1-3) [2-3)覆盖了 [1-3) 变成[1-2) 不应该删除原先的[1-3)
// 当确认老的 start, end 都是自己的时候才可以删除老的. (即还没覆盖
if (Objects.equals(partition.getId(), range.get(partition.getStartKey())) &&
Objects.equals(partition.getId(), range.get(partition.getEndKey() - 1))) {
range.remove(range.getEntry(partition.getStartKey()).getKey());
}
}
range.put(Range.closedOpen(partition.getStartKey(), partition.getEndKey()), partId);
} catch (Exception e) {
throw new RuntimeException(e);
}
return true;
}
public void removePartition(String graphName, int partId) {
GraphCache graph = getGraphCache(graphName);
Partition p = graph.removePartition(partId);
if (p != null) {
RangeMap<Long, Integer> range = graph.getRange();
if (Objects.equals(p.getId(), range.get(p.getStartKey())) &&
Objects.equals(p.getId(), range.get(p.getEndKey() - 1))) {
range.remove(range.getEntry(p.getStartKey()).getKey());
}
}
}
/**
* remove all partitions
*/
public void removePartitions() {
for (Entry<String, GraphCache> entry : caches.entrySet()) {
removePartitions(entry.getValue());
}
}
private void removePartitions(GraphCache graph) {
graph.getState().clear();
graph.getRange().clear();
}
/**
* remove partition cache of graphName
*
* @param graphName
*/
public void removeAll(String graphName) {
GraphCache graph = caches.get(graphName);
if (graph != null) {
removePartitions(graph);
}
}
public boolean updateShardGroup(ShardGroup shardGroup) {
KVPair<ShardGroup, Shard> old = groups.get(shardGroup.getId());
Shard leader = getLeader(shardGroup);
if (old != null) {
old.setKey(shardGroup);
old.setValue(leader);
return false;
}
groups.put(shardGroup.getId(), new KVPair<>(shardGroup, leader));
return true;
}
public void deleteShardGroup(int shardGroupId) {
groups.remove(shardGroupId);
}
public ShardGroup getShardGroup(int groupId) {
KVPair<ShardGroup, Shard> pair = groups.get(groupId);
if (pair != null) {
return pair.getKey();
}
return null;
}
public boolean addStore(Long storeId, Metapb.Store store) {
Metapb.Store oldStore = stores.get(storeId);
if (oldStore != null && oldStore.equals(store)) {
return false;
}
stores.put(storeId, store);
return true;
}
public Metapb.Store getStoreById(Long storeId) {
return stores.get(storeId);
}
public void removeStore(Long storeId) {
stores.remove(storeId);
}
public void reset() {
groups = new ConcurrentHashMap<>();
stores = new ConcurrentHashMap<>();
caches = new ConcurrentHashMap<>();
}
public Shard getLeader(int partitionId) {
KVPair<ShardGroup, Shard> pair = groups.get(partitionId);
if (pair != null) {
if (pair.getValue() != null) {
return pair.getValue();
}
for (Shard shard : pair.getKey().getShardsList()) {
if (shard.getRole() == Metapb.ShardRole.Leader) {
pair.setValue(shard);
return shard;
}
}
}
return null;
}
public Shard getLeader(ShardGroup shardGroup) {
if (shardGroup != null) {
for (Shard shard : shardGroup.getShardsList()) {
if (shard.getRole() == Metapb.ShardRole.Leader) {
return shard;
}
}
}
return null;
}
public void updateLeader(int partitionId, Shard leader) {
KVPair<ShardGroup, Shard> pair = groups.get(partitionId);
if (pair != null && leader != null) {
Shard l = getLeader(partitionId);
if (l == null || leader.getStoreId() != l.getStoreId()) {
ShardGroup shardGroup = pair.getKey();
ShardGroup.Builder builder = ShardGroup.newBuilder(shardGroup).clearShards();
for (var shard : shardGroup.getShardsList()) {
builder.addShards(
Shard.newBuilder()
.setStoreId(shard.getStoreId())
.setRole(shard.getStoreId() == leader.getStoreId() ?
Metapb.ShardRole.Leader : Metapb.ShardRole.Follower)
.build()
);
}
pair.setKey(builder.build());
pair.setValue(leader);
}
}
}
}

View File

@ -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 org.apache.hugegraph.pd.client;
import org.apache.hugegraph.pd.grpc.discovery.NodeInfos;
import org.apache.hugegraph.pd.grpc.discovery.Query;
public interface Discoverable {
NodeInfos getNodeInfos(Query query);
void scheduleTask();
void cancelTask();
}

View File

@ -0,0 +1,221 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.apache.hugegraph.pd.client;
import java.io.Closeable;
import java.util.LinkedList;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Consumer;
import java.util.function.Function;
import org.apache.hugegraph.pd.common.PDException;
import org.apache.hugegraph.pd.grpc.discovery.DiscoveryServiceGrpc;
import org.apache.hugegraph.pd.grpc.discovery.NodeInfo;
import org.apache.hugegraph.pd.grpc.discovery.NodeInfos;
import org.apache.hugegraph.pd.grpc.discovery.Query;
import org.apache.hugegraph.pd.grpc.discovery.RegisterInfo;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public abstract class DiscoveryClient implements Closeable, Discoverable {
private final Timer timer = new Timer("serverHeartbeat", true);
private final AtomicBoolean requireResetStub = new AtomicBoolean(false);
protected int period; //心跳周期
LinkedList<String> pdAddresses = new LinkedList<>();
ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private volatile int currentIndex; // 当前在用pd地址位置
private int maxTime = 6;
private ManagedChannel channel = null;
private DiscoveryServiceGrpc.DiscoveryServiceBlockingStub registerStub;
private DiscoveryServiceGrpc.DiscoveryServiceBlockingStub blockingStub;
public DiscoveryClient(String centerAddress, int delay) {
String[] addresses = centerAddress.split(",");
for (int i = 0; i < addresses.length; i++) {
String singleAddress = addresses[i];
if (singleAddress == null || singleAddress.length() <= 0) {
continue;
}
pdAddresses.add(addresses[i]);
}
this.period = delay;
if (maxTime < addresses.length) {
maxTime = addresses.length;
}
}
private <V, R> R tryWithTimes(Function<V, R> function, V v) {
R r;
Exception ex = null;
for (int i = 0; i < maxTime; i++) {
try {
r = function.apply(v);
return r;
} catch (Exception e) {
requireResetStub.set(true);
resetStub();
ex = e;
}
}
if (ex != null) {
log.error("Try discovery method with error: {}", ex.getMessage());
}
return null;
}
/***
* 按照pd列表重置stub
*/
private void resetStub() {
String errLog = null;
for (int i = currentIndex + 1; i <= pdAddresses.size() + currentIndex; i++) {
currentIndex = i % pdAddresses.size();
String singleAddress = pdAddresses.get(currentIndex);
try {
if (requireResetStub.get()) {
resetChannel(singleAddress);
}
errLog = null;
break;
} catch (Exception e) {
requireResetStub.set(true);
if (errLog == null) {
errLog = e.getMessage();
}
continue;
}
}
if (errLog != null) {
log.error(errLog);
}
}
/***
* 按照某个pd的地址重置channel和stub
* @param singleAddress
* @throws PDException
*/
private void resetChannel(String singleAddress) throws PDException {
readWriteLock.writeLock().lock();
try {
if (requireResetStub.get()) {
while (channel != null && !channel.shutdownNow().awaitTermination(
100, TimeUnit.MILLISECONDS)) {
continue;
}
channel = ManagedChannelBuilder.forTarget(
singleAddress).usePlaintext().build();
this.registerStub = DiscoveryServiceGrpc.newBlockingStub(
channel);
this.blockingStub = DiscoveryServiceGrpc.newBlockingStub(
channel);
requireResetStub.set(false);
}
} catch (Exception e) {
throw new PDException(-1, String.format(
"Reset channel with error : %s.", e.getMessage()));
} finally {
readWriteLock.writeLock().unlock();
}
}
/***
* 获取注册节点信息
* @param query
* @return
*/
@Override
public NodeInfos getNodeInfos(Query query) {
return tryWithTimes((q) -> {
this.readWriteLock.readLock().lock();
NodeInfos nodes;
try {
nodes = this.blockingStub.getNodes(q);
} catch (Exception e) {
throw e;
} finally {
this.readWriteLock.readLock().unlock();
}
return nodes;
}, query);
}
/***
* 启动心跳任务
*/
@Override
public void scheduleTask() {
timer.schedule(new TimerTask() {
@Override
public void run() {
NodeInfo nodeInfo = getRegisterNode();
tryWithTimes((t) -> {
RegisterInfo register;
readWriteLock.readLock().lock();
try {
register = registerStub.register(t);
log.debug("Discovery Client work done.");
Consumer<RegisterInfo> consumer = getRegisterConsumer();
if (consumer != null) {
consumer.accept(register);
}
} catch (Exception e) {
throw e;
} finally {
readWriteLock.readLock().unlock();
}
return register;
}, nodeInfo);
}
}, 0, period);
}
abstract NodeInfo getRegisterNode();
abstract Consumer<RegisterInfo> getRegisterConsumer();
@Override
public void cancelTask() {
this.timer.cancel();
}
@Override
public void close() {
this.timer.cancel();
readWriteLock.writeLock().lock();
try {
while (channel != null && !channel.shutdownNow().awaitTermination(
100, TimeUnit.MILLISECONDS)) {
continue;
}
} catch (Exception e) {
log.info("Close channel with error : {}.", e);
} finally {
readWriteLock.writeLock().unlock();
}
}
}

View File

@ -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.pd.client;
import java.util.Map;
import java.util.function.Consumer;
import org.apache.hugegraph.pd.grpc.discovery.NodeInfo;
import org.apache.hugegraph.pd.grpc.discovery.RegisterType;
public class DiscoveryClientImpl extends DiscoveryClient {
private final String id;
private final RegisterType type; // 心跳类型备用
private final String version;
private final String appName;
private final int times; // 心跳过期次数备用
private final String address;
private final Map labels;
private final Consumer registerConsumer;
private DiscoveryClientImpl(Builder builder) {
super(builder.centerAddress, builder.delay);
period = builder.delay;
id = builder.id;
type = builder.type;
version = builder.version;
appName = builder.appName;
times = builder.times;
address = builder.address;
labels = builder.labels;
registerConsumer = builder.registerConsumer;
}
public static Builder newBuilder() {
return new Builder();
}
@Override
NodeInfo getRegisterNode() {
return NodeInfo.newBuilder().setAddress(this.address)
.setVersion(this.version)
.setAppName(this.appName).setInterval(this.period)
.setId(this.id).putAllLabels(labels).build();
}
@Override
Consumer getRegisterConsumer() {
return registerConsumer;
}
public static final class Builder {
private int delay;
private String centerAddress;
private String id;
private RegisterType type;
private String address;
private Map labels;
private String version;
private String appName;
private int times;
private Consumer registerConsumer;
private Builder() {
}
public Builder setDelay(int val) {
delay = val;
return this;
}
public Builder setCenterAddress(String val) {
centerAddress = val;
return this;
}
public Builder setId(String val) {
id = val;
return this;
}
public Builder setType(RegisterType val) {
type = val;
return this;
}
public Builder setAddress(String val) {
address = val;
return this;
}
public Builder setLabels(Map val) {
labels = val;
return this;
}
public Builder setVersion(String val) {
version = val;
return this;
}
public Builder setAppName(String val) {
appName = val;
return this;
}
public Builder setTimes(int val) {
times = val;
return this;
}
public Builder setRegisterConsumer(Consumer registerConsumer) {
this.registerConsumer = registerConsumer;
return this;
}
public DiscoveryClientImpl build() {
return new DiscoveryClientImpl(this);
}
}
}

View File

@ -0,0 +1,352 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.apache.hugegraph.pd.client;
import java.io.Closeable;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.apache.hugegraph.pd.client.AbstractClient;
import org.apache.hugegraph.pd.client.PDConfig;
import org.apache.hugegraph.pd.common.PDException;
import org.apache.hugegraph.pd.grpc.kv.K;
import org.apache.hugegraph.pd.grpc.kv.KResponse;
import org.apache.hugegraph.pd.grpc.kv.Kv;
import org.apache.hugegraph.pd.grpc.kv.KvResponse;
import org.apache.hugegraph.pd.grpc.kv.KvServiceGrpc;
import org.apache.hugegraph.pd.grpc.kv.LockRequest;
import org.apache.hugegraph.pd.grpc.kv.LockResponse;
import org.apache.hugegraph.pd.grpc.kv.ScanPrefixResponse;
import org.apache.hugegraph.pd.grpc.kv.TTLRequest;
import org.apache.hugegraph.pd.grpc.kv.TTLResponse;
import org.apache.hugegraph.pd.grpc.kv.WatchEvent;
import org.apache.hugegraph.pd.grpc.kv.WatchKv;
import org.apache.hugegraph.pd.grpc.kv.WatchRequest;
import org.apache.hugegraph.pd.grpc.kv.WatchResponse;
import org.apache.hugegraph.pd.grpc.kv.WatchType;
import io.grpc.stub.AbstractBlockingStub;
import io.grpc.stub.AbstractStub;
import io.grpc.stub.StreamObserver;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class KvClient<T extends WatchResponse> extends AbstractClient implements Closeable {
private final AtomicLong clientId = new AtomicLong(0);
private final Semaphore semaphore = new Semaphore(1);
private final ConcurrentHashMap<Long, StreamObserver> observers = new ConcurrentHashMap<>();
public KvClient(PDConfig pdConfig) {
super(pdConfig);
}
@Override
protected AbstractStub createStub() {
return KvServiceGrpc.newStub(channel);
}
@Override
protected AbstractBlockingStub createBlockingStub() {
return KvServiceGrpc.newBlockingStub(channel);
}
public KvResponse put(String key, String value) throws PDException {
Kv kv = Kv.newBuilder().setKey(key).setValue(value).build();
KvResponse response = blockingUnaryCall(KvServiceGrpc.getPutMethod(), kv);
handleErrors(response.getHeader());
return response;
}
public KResponse get(String key) throws PDException {
K k = K.newBuilder().setKey(key).build();
KResponse response = blockingUnaryCall(KvServiceGrpc.getGetMethod(), k);
handleErrors(response.getHeader());
return response;
}
public KvResponse delete(String key) throws PDException {
K k = K.newBuilder().setKey(key).build();
KvResponse response = blockingUnaryCall(KvServiceGrpc.getDeleteMethod(), k);
handleErrors(response.getHeader());
return response;
}
public KvResponse deletePrefix(String prefix) throws PDException {
K k = K.newBuilder().setKey(prefix).build();
KvResponse response = blockingUnaryCall(KvServiceGrpc.getDeletePrefixMethod(), k);
handleErrors(response.getHeader());
return response;
}
public ScanPrefixResponse scanPrefix(String prefix) throws PDException {
K k = K.newBuilder().setKey(prefix).build();
ScanPrefixResponse response = blockingUnaryCall(KvServiceGrpc.getScanPrefixMethod(), k);
handleErrors(response.getHeader());
return response;
}
public TTLResponse keepTTLAlive(String key) throws PDException {
TTLRequest request = TTLRequest.newBuilder().setKey(key).build();
TTLResponse response = blockingUnaryCall(KvServiceGrpc.getKeepTTLAliveMethod(), request);
handleErrors(response.getHeader());
return response;
}
public TTLResponse putTTL(String key, String value, long ttl) throws PDException {
TTLRequest request =
TTLRequest.newBuilder().setKey(key).setValue(value).setTtl(ttl).build();
TTLResponse response = blockingUnaryCall(KvServiceGrpc.getPutTTLMethod(), request);
handleErrors(response.getHeader());
return response;
}
private void onEvent(WatchResponse value, Consumer<T> consumer) {
log.info("receive message for {},event Count:{}", value, value.getEventsCount());
clientId.compareAndSet(0L, value.getClientId());
if (value.getEventsCount() != 0) {
consumer.accept((T) value);
}
}
private StreamObserver<WatchResponse> getObserver(String key, Consumer<T> consumer,
BiConsumer<String, Consumer> listenWrapper,
long client) {
StreamObserver<WatchResponse> observer;
if ((observer = observers.get(client)) == null) {
synchronized (this) {
if ((observer = observers.get(client)) == null) {
observer = getObserver(key, consumer, listenWrapper);
observers.put(client, observer);
}
}
}
return observer;
}
private StreamObserver<WatchResponse> getObserver(String key, Consumer<T> consumer,
BiConsumer<String, Consumer> listenWrapper) {
return new StreamObserver<WatchResponse>() {
@Override
public void onNext(WatchResponse value) {
switch (value.getState()) {
case Starting:
boolean b = clientId.compareAndSet(0, value.getClientId());
if (b) {
observers.put(value.getClientId(), this);
log.info("set watch client id to :{}", value.getClientId());
}
semaphore.release();
break;
case Started:
onEvent(value, consumer);
break;
case Leader_Changed:
listenWrapper.accept(key, consumer);
break;
case Alive:
// only for check client is alive, do nothing
break;
default:
break;
}
}
@Override
public void onError(Throwable t) {
listenWrapper.accept(key, consumer);
}
@Override
public void onCompleted() {
}
};
}
public void listen(String key, Consumer<T> consumer) throws PDException {
long value = clientId.get();
StreamObserver<WatchResponse> observer = getObserver(key, consumer, listenWrapper, value);
acquire();
WatchRequest k = WatchRequest.newBuilder().setClientId(value).setKey(key).build();
streamingCall(KvServiceGrpc.getWatchMethod(), k, observer, 1);
}
public void listenPrefix(String prefix, Consumer<T> consumer) throws PDException {
long value = clientId.get();
StreamObserver<WatchResponse> observer =
getObserver(prefix, consumer, prefixListenWrapper, value);
acquire();
WatchRequest k =
WatchRequest.newBuilder().setClientId(clientId.get()).setKey(prefix).build();
streamingCall(KvServiceGrpc.getWatchPrefixMethod(), k, observer, 1);
}
private void acquire() {
if (clientId.get() == 0L) {
try {
semaphore.acquire();
if (clientId.get() != 0L) {
semaphore.release();
}
} catch (InterruptedException e) {
log.error("get semaphore with error:", e);
}
}
}
public List<String> getWatchList(T response) {
List<String> values = new LinkedList<>();
List<WatchEvent> eventsList = response.getEventsList();
for (WatchEvent event : eventsList) {
if (event.getType() != WatchType.Put) {
return null;
}
String value = event.getCurrent().getValue();
values.add(value);
}
return values;
}
public Map<String, String> getWatchMap(T response) {
Map<String, String> values = new HashMap<>();
List<WatchEvent> eventsList = response.getEventsList();
for (WatchEvent event : eventsList) {
if (event.getType() != WatchType.Put) {
return null;
}
WatchKv current = event.getCurrent();
String key = current.getKey();
String value = current.getValue();
values.put(key, value);
}
return values;
}
public LockResponse lock(String key, long ttl) throws PDException {
acquire();
LockResponse response;
try {
LockRequest k =
LockRequest.newBuilder().setKey(key).setClientId(clientId.get()).setTtl(ttl)
.build();
response = blockingUnaryCall(KvServiceGrpc.getLockMethod(), k);
handleErrors(response.getHeader());
if (clientId.compareAndSet(0L, response.getClientId())) {
semaphore.release();
}
} catch (Exception e) {
if (clientId.get() == 0L) {
semaphore.release();
}
throw e;
}
return response;
} BiConsumer<String, Consumer> listenWrapper = (key, consumer) -> {
try {
listen(key, consumer);
} catch (PDException e) {
try {
log.warn("start listen with warning:", e);
Thread.sleep(1000);
} catch (InterruptedException ex) {
}
}
};
public LockResponse lockWithoutReentrant(String key, long ttl) throws PDException {
acquire();
LockResponse response;
try {
LockRequest k =
LockRequest.newBuilder().setKey(key).setClientId(clientId.get()).setTtl(ttl)
.build();
response = blockingUnaryCall(KvServiceGrpc.getLockWithoutReentrantMethod(), k);
handleErrors(response.getHeader());
if (clientId.compareAndSet(0L, response.getClientId())) {
semaphore.release();
}
} catch (Exception e) {
if (clientId.get() == 0L) {
semaphore.release();
}
throw e;
}
return response;
}
public LockResponse isLocked(String key) throws PDException {
LockRequest k = LockRequest.newBuilder().setKey(key).setClientId(clientId.get()).build();
LockResponse response = blockingUnaryCall(KvServiceGrpc.getIsLockedMethod(), k);
handleErrors(response.getHeader());
return response;
}
public LockResponse unlock(String key) throws PDException {
assert clientId.get() != 0;
LockRequest k = LockRequest.newBuilder().setKey(key).setClientId(clientId.get()).build();
LockResponse response = blockingUnaryCall(KvServiceGrpc.getUnlockMethod(), k);
handleErrors(response.getHeader());
clientId.compareAndSet(0L, response.getClientId());
assert clientId.get() == response.getClientId();
return response;
}
public LockResponse keepAlive(String key) throws PDException {
assert clientId.get() != 0;
LockRequest k = LockRequest.newBuilder().setKey(key).setClientId(clientId.get()).build();
LockResponse response = blockingUnaryCall(KvServiceGrpc.getKeepAliveMethod(), k);
handleErrors(response.getHeader());
clientId.compareAndSet(0L, response.getClientId());
assert clientId.get() == response.getClientId();
return response;
}
@Override
public void close() {
super.close();
}
BiConsumer<String, Consumer> prefixListenWrapper = (key, consumer) -> {
try {
listenPrefix(key, consumer);
} catch (PDException e) {
try {
log.warn("start listenPrefix with warning:", e);
Thread.sleep(1000);
} catch (InterruptedException ex) {
}
}
};
}

View File

@ -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.pd.client;
import org.apache.hugegraph.pd.common.KVPair;
import org.apache.hugegraph.pd.grpc.PDGrpc;
import org.apache.hugegraph.pd.grpc.Pdpb;
import com.google.protobuf.ByteString;
import io.grpc.stub.AbstractBlockingStub;
import io.grpc.stub.AbstractStub;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class LicenseClient extends AbstractClient {
public LicenseClient(PDConfig config) {
super(config);
}
@Override
protected AbstractStub createStub() {
return PDGrpc.newStub(channel);
}
@Override
protected AbstractBlockingStub createBlockingStub() {
return PDGrpc.newBlockingStub(channel);
}
public Pdpb.PutLicenseResponse putLicense(byte[] content) {
Pdpb.PutLicenseRequest request = Pdpb.PutLicenseRequest.newBuilder()
.setContent(
ByteString.copyFrom(content))
.build();
try {
KVPair<Boolean, Pdpb.PutLicenseResponse> pair = concurrentBlockingUnaryCall(
PDGrpc.getPutLicenseMethod(), request,
(rs) -> rs.getHeader().getError().getType().equals(Pdpb.ErrorType.OK));
if (pair.getKey()) {
Pdpb.PutLicenseResponse.Builder builder = Pdpb.PutLicenseResponse.newBuilder();
builder.setHeader(okHeader);
return builder.build();
} else {
return pair.getValue();
}
} catch (Exception e) {
e.printStackTrace();
log.debug("put license with error:{} ", e);
Pdpb.ResponseHeader rh =
newErrorHeader(Pdpb.ErrorType.LICENSE_ERROR_VALUE, e.getMessage());
return Pdpb.PutLicenseResponse.newBuilder().setHeader(rh).build();
}
}
}

View File

@ -0,0 +1,82 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.apache.hugegraph.pd.client;
public final class PDConfig {
//TODO multi-server
private String serverHost = "localhost:9000";
private long grpcTimeOut = 60000; // grpc调用超时时间 10秒
// 是否接收PD异步通知
private boolean enablePDNotify = false;
private boolean enableCache = false;
private PDConfig() {
}
public static PDConfig of() {
return new PDConfig();
}
public static PDConfig of(String serverHost) {
PDConfig config = new PDConfig();
config.serverHost = serverHost;
return config;
}
public static PDConfig of(String serverHost, long timeOut) {
PDConfig config = new PDConfig();
config.serverHost = serverHost;
config.grpcTimeOut = timeOut;
return config;
}
public String getServerHost() {
return serverHost;
}
public long getGrpcTimeOut() {
return grpcTimeOut;
}
@Deprecated
public PDConfig setEnablePDNotify(boolean enablePDNotify) {
this.enablePDNotify = enablePDNotify;
// TODO 临时代码hugegraph修改完后删除
this.enableCache = enablePDNotify;
return this;
}
public boolean isEnableCache() {
return enableCache;
}
public PDConfig setEnableCache(boolean enableCache) {
this.enableCache = enableCache;
return this;
}
@Override
public String toString() {
return "PDConfig{" +
"serverHost='" + serverHost + '\'' +
'}';
}
}

View File

@ -0,0 +1,152 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.apache.hugegraph.pd.client;
import java.io.Closeable;
import java.util.function.Consumer;
import org.apache.hugegraph.pd.grpc.pulse.PartitionHeartbeatRequest;
import org.apache.hugegraph.pd.grpc.pulse.PulseResponse;
import org.apache.hugegraph.pd.pulse.PulseServerNotice;
/**
* Bidirectional communication interface of pd-client and pd-server
*/
public interface PDPulse {
/*** inner static methods ***/
static <T> Listener<T> listener(Consumer<T> onNext) {
return listener(onNext, t -> {
}, () -> {
});
}
static <T> Listener<T> listener(Consumer<T> onNext, Consumer<Throwable> onError) {
return listener(onNext, onError, () -> {
});
}
static <T> Listener<T> listener(Consumer<T> onNext, Runnable onCompleted) {
return listener(onNext, t -> {
}, onCompleted);
}
static <T> Listener<T> listener(Consumer<T> onNext, Consumer<Throwable> onError,
Runnable onCompleted) {
return new Listener<>() {
@Override
public void onNext(T response) {
onNext.accept(response);
}
@Override
public void onNotice(PulseServerNotice<T> notice) {
}
@Override
public void onError(Throwable throwable) {
onError.accept(throwable);
}
@Override
public void onCompleted() {
onCompleted.run();
}
};
}
/**
* @param listener
* @return
*/
Notifier<PartitionHeartbeatRequest.Builder> connectPartition(Listener<PulseResponse> listener);
/**
* 切换成新的host channel/host的检查如果需要关闭notifier调用close方法
*
* @param host new host
* @param notifier notifier
* @return true if create new stub, otherwise false
*/
boolean resetStub(String host, Notifier notifier);
/**
* Interface of pulse.
*/
interface Listener<T> {
/**
* Invoked on new events.
*
* @param response the response.
*/
@Deprecated
default void onNext(T response) {
}
/**
* Invoked on new events.
*
* @param notice a wrapper of response
*/
default void onNotice(PulseServerNotice<T> notice) {
notice.ack();
}
/**
* Invoked on errors.
*
* @param throwable the error.
*/
void onError(Throwable throwable);
/**
* Invoked on completion.
*/
void onCompleted();
}
/**
* Interface of notifier that can send notice to server.
*
* @param <T>
*/
interface Notifier<T> extends Closeable {
/**
* closes this watcher and all its resources.
*/
@Override
void close();
/**
* Send notice to pd-server.
*
* @return
*/
void notifyServer(T t);
/**
* Send an error report to pd-server.
*
* @param error
*/
void crash(String error);
}
}

View File

@ -0,0 +1,196 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.apache.hugegraph.pd.client;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.hugegraph.pd.grpc.pulse.HgPdPulseGrpc;
import org.apache.hugegraph.pd.grpc.pulse.PartitionHeartbeatRequest;
import org.apache.hugegraph.pd.grpc.pulse.PulseAckRequest;
import org.apache.hugegraph.pd.grpc.pulse.PulseCreateRequest;
import org.apache.hugegraph.pd.grpc.pulse.PulseNoticeRequest;
import org.apache.hugegraph.pd.grpc.pulse.PulseRequest;
import org.apache.hugegraph.pd.grpc.pulse.PulseResponse;
import org.apache.hugegraph.pd.grpc.pulse.PulseType;
import org.apache.hugegraph.pd.pulse.PartitionNotice;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import io.grpc.ManagedChannel;
import io.grpc.stub.StreamObserver;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public final class PDPulseImpl implements PDPulse {
private static final ConcurrentHashMap<String, ManagedChannel> chs = new ConcurrentHashMap<>();
private final ExecutorService threadPool;
private HgPdPulseGrpc.HgPdPulseStub stub;
private String pdServerAddress;
// TODO: support several servers.
public PDPulseImpl(String pdServerAddress) {
this.pdServerAddress = pdServerAddress;
this.stub = HgPdPulseGrpc.newStub(Channels.getChannel(pdServerAddress));
var namedThreadFactory =
new ThreadFactoryBuilder().setNameFormat("ack-notice-pool-%d").build();
threadPool = Executors.newSingleThreadExecutor(namedThreadFactory);
}
private String getCurrentHost() {
return this.pdServerAddress;
}
private boolean checkChannel() {
return stub != null && !((ManagedChannel) stub.getChannel()).isShutdown();
}
/* TODO: handle this override problem */
@Override
public Notifier<PartitionHeartbeatRequest.Builder> connectPartition(Listener<PulseResponse>
listener) {
return new PartitionHeartbeat(listener);
}
@Override
public boolean resetStub(String host, Notifier notifier) {
log.info("reset stub: current, {}, new: {}, channel state:{}", getCurrentHost(), host,
checkChannel());
if (Objects.equals(host, getCurrentHost()) && checkChannel()) {
return false;
}
if (notifier != null) {
notifier.close();
}
this.stub = HgPdPulseGrpc.newStub(Channels.getChannel(host));
log.info("pd pulse connect to {}", host);
this.pdServerAddress = host;
return true;
}
/*** PartitionHeartbeat's implement ***/
private class PartitionHeartbeat extends
AbstractConnector<PartitionHeartbeatRequest.Builder,
PulseResponse> {
private long observerId = -1;
PartitionHeartbeat(Listener<PulseResponse> listener) {
super(listener, PulseType.PULSE_TYPE_PARTITION_HEARTBEAT);
}
private void setObserverId(long observerId) {
if (this.observerId == -1) {
this.observerId = observerId;
}
}
@Override
public void notifyServer(PartitionHeartbeatRequest.Builder requestBuilder) {
this.reqStream.onNext(PulseRequest.newBuilder()
.setNoticeRequest(
PulseNoticeRequest.newBuilder()
.setPartitionHeartbeatRequest(
requestBuilder.build()
).build()
).build()
);
}
@Override
public void onNext(PulseResponse pulseResponse) {
this.setObserverId(pulseResponse.getObserverId());
long noticeId = pulseResponse.getNoticeId();
this.listener.onNext(pulseResponse);
this.listener.onNotice(new PartitionNotice(noticeId,
e -> super.ackNotice(e, observerId),
pulseResponse));
}
}
private abstract class AbstractConnector<N, L> implements Notifier<N>,
StreamObserver<PulseResponse> {
Listener<L> listener;
StreamObserver<PulseRequest> reqStream;
PulseType pulseType;
PulseRequest.Builder reqBuilder = PulseRequest.newBuilder();
PulseAckRequest.Builder ackBuilder = PulseAckRequest.newBuilder();
private AbstractConnector(Listener<L> listener, PulseType pulseType) {
this.listener = listener;
this.pulseType = pulseType;
this.init();
}
void init() {
PulseCreateRequest.Builder builder = PulseCreateRequest.newBuilder()
.setPulseType(this.pulseType);
this.reqStream = PDPulseImpl.this.stub.pulse(this);
this.reqStream.onNext(reqBuilder.clear().setCreateRequest(builder).build());
}
/*** notifier ***/
@Override
public void close() {
this.reqStream.onCompleted();
}
@Override
public abstract void notifyServer(N t);
@Override
public void crash(String error) {
this.reqStream.onError(new Throwable(error));
}
/*** listener ***/
@Override
public abstract void onNext(PulseResponse pulseResponse);
@Override
public void onError(Throwable throwable) {
this.listener.onError(throwable);
}
@Override
public void onCompleted() {
this.listener.onCompleted();
}
protected void ackNotice(long noticeId, long observerId) {
threadPool.execute(() -> {
// log.info("send ack: {}, ts: {}", noticeId, System.currentTimeMillis());
this.reqStream.onNext(reqBuilder.clear()
.setAckRequest(
this.ackBuilder.clear()
.setNoticeId(noticeId)
.setObserverId(observerId)
.build()
).build()
);
});
}
}
}

View File

@ -0,0 +1,137 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.apache.hugegraph.pd.client;
import java.io.Closeable;
import java.util.function.Consumer;
import org.apache.hugegraph.pd.grpc.watch.WatchResponse;
import org.apache.hugegraph.pd.watch.NodeEvent;
import org.apache.hugegraph.pd.watch.PartitionEvent;
public interface PDWatch {
/**
* Watch the events of all store-nodes registered in the remote PD-Server.
*
* @param listener
* @return
*/
//PDWatcher watchNode(Listener<NodeEvent> listener);
/**
* Watch the events of the store-nodes assigned to a specified graph.
*
* @param graph the graph name which you want to watch
* @param listener
* @return
*/
//PDWatcher watchNode(String graph, Listener<NodeEvent> listener);
String getCurrentHost();
boolean checkChannel();
/*** inner static methods ***/
static <T> Listener<T> listener(Consumer<T> onNext) {
return listener(onNext, t -> {
}, () -> {
});
}
static <T> Listener<T> listener(Consumer<T> onNext, Consumer<Throwable> onError) {
return listener(onNext, onError, () -> {
});
}
static <T> Listener<T> listener(Consumer<T> onNext, Runnable onCompleted) {
return listener(onNext, t -> {
}, onCompleted);
}
static <T> Listener<T> listener(Consumer<T> onNext, Consumer<Throwable> onError,
Runnable onCompleted) {
return new Listener<T>() {
@Override
public void onNext(T response) {
onNext.accept(response);
}
@Override
public void onError(Throwable throwable) {
onError.accept(throwable);
}
@Override
public void onCompleted() {
onCompleted.run();
}
};
}
/**
* @param listener
* @return
*/
Watcher watchPartition(Listener<PartitionEvent> listener);
Watcher watchNode(Listener<NodeEvent> listener);
Watcher watchGraph(Listener<WatchResponse> listener);
Watcher watchShardGroup(Listener<WatchResponse> listener);
/**
* Interface of Watcher.
*/
interface Listener<T> {
/**
* Invoked on new events.
*
* @param response the response.
*/
void onNext(T response);
/**
* Invoked on errors.
*
* @param throwable the error.
*/
void onError(Throwable throwable);
/**
* Invoked on completion.
*/
default void onCompleted() {};
}
interface Watcher extends Closeable {
/**
* closes this watcher and all its resources.
*/
@Override
void close();
/**
* Requests the latest revision processed and propagates it to listeners
*/
// TODO: what's it for?
//void requestProgress();
}
}

View File

@ -0,0 +1,202 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.apache.hugegraph.pd.client;
import java.util.function.Supplier;
import org.apache.hugegraph.pd.grpc.watch.HgPdWatchGrpc;
import org.apache.hugegraph.pd.grpc.watch.WatchCreateRequest;
import org.apache.hugegraph.pd.grpc.watch.WatchNodeResponse;
import org.apache.hugegraph.pd.grpc.watch.WatchPartitionResponse;
import org.apache.hugegraph.pd.grpc.watch.WatchRequest;
import org.apache.hugegraph.pd.grpc.watch.WatchResponse;
import org.apache.hugegraph.pd.grpc.watch.WatchType;
import org.apache.hugegraph.pd.watch.NodeEvent;
import org.apache.hugegraph.pd.watch.PartitionEvent;
import io.grpc.ManagedChannel;
import io.grpc.stub.StreamObserver;
final class PDWatchImpl implements PDWatch {
private final HgPdWatchGrpc.HgPdWatchStub stub;
private final String pdServerAddress;
// TODO: support several servers.
PDWatchImpl(String pdServerAddress) {
this.pdServerAddress = pdServerAddress;
this.stub = HgPdWatchGrpc.newStub(Channels.getChannel(pdServerAddress));
}
@Override
public String getCurrentHost() {
return this.pdServerAddress;
}
@Override
public boolean checkChannel() {
return stub != null && !((ManagedChannel) stub.getChannel()).isShutdown();
}
/**
* Get Partition change watcher.
*
* @param listener
* @return
*/
@Override
public Watcher watchPartition(Listener<PartitionEvent> listener) {
return new PartitionWatcher(listener);
}
/**
* Get Store-Node change watcher.
*
* @param listener
* @return
*/
@Override
public Watcher watchNode(Listener<NodeEvent> listener) {
return new NodeWatcher(listener);
}
@Override
public Watcher watchGraph(Listener<WatchResponse> listener) {
return new GraphWatcher(listener);
}
@Override
public Watcher watchShardGroup(Listener<WatchResponse> listener) {
return new ShardGroupWatcher(listener);
}
private class GraphWatcher extends AbstractWatcher<WatchResponse> {
private GraphWatcher(Listener listener) {
super(listener,
() -> WatchCreateRequest
.newBuilder()
.setWatchType(WatchType.WATCH_TYPE_GRAPH_CHANGE)
.build()
);
}
@Override
public void onNext(WatchResponse watchResponse) {
this.listener.onNext(watchResponse);
}
}
private class ShardGroupWatcher extends AbstractWatcher<WatchResponse> {
private ShardGroupWatcher(Listener listener) {
super(listener,
() -> WatchCreateRequest
.newBuilder()
.setWatchType(WatchType.WATCH_TYPE_SHARD_GROUP_CHANGE)
.build()
);
}
@Override
public void onNext(WatchResponse watchResponse) {
this.listener.onNext(watchResponse);
}
}
private class PartitionWatcher extends AbstractWatcher<PartitionEvent> {
private PartitionWatcher(Listener listener) {
super(listener,
() -> WatchCreateRequest
.newBuilder()
.setWatchType(WatchType.WATCH_TYPE_PARTITION_CHANGE)
.build()
);
}
@Override
public void onNext(WatchResponse watchResponse) {
WatchPartitionResponse res = watchResponse.getPartitionResponse();
PartitionEvent event = new PartitionEvent(res.getGraph(), res.getPartitionId(),
PartitionEvent.ChangeType.grpcTypeOf(
res.getChangeType()));
this.listener.onNext(event);
}
}
private class NodeWatcher extends AbstractWatcher<NodeEvent> {
private NodeWatcher(Listener listener) {
super(listener,
() -> WatchCreateRequest
.newBuilder()
.setWatchType(WatchType.WATCH_TYPE_STORE_NODE_CHANGE)
.build()
);
}
@Override
public void onNext(WatchResponse watchResponse) {
WatchNodeResponse res = watchResponse.getNodeResponse();
NodeEvent event = new NodeEvent(res.getGraph(), res.getNodeId(),
NodeEvent.EventType.grpcTypeOf(res.getNodeEventType()));
this.listener.onNext(event);
}
}
private abstract class AbstractWatcher<T> implements Watcher, StreamObserver<WatchResponse> {
Listener<T> listener;
StreamObserver<WatchRequest> reqStream;
Supplier<WatchCreateRequest> requestSupplier;
private AbstractWatcher(Listener<T> listener,
Supplier<WatchCreateRequest> requestSupplier) {
this.listener = listener;
this.requestSupplier = requestSupplier;
this.init();
}
void init() {
this.reqStream = PDWatchImpl.this.stub.watch(this);
this.reqStream.onNext(WatchRequest.newBuilder().setCreateRequest(
this.requestSupplier.get()
).build());
}
@Override
public void close() {
this.reqStream.onCompleted();
}
@Override
public abstract void onNext(WatchResponse watchResponse);
@Override
public void onError(Throwable throwable) {
this.listener.onError(throwable);
}
@Override
public void onCompleted() {
this.listener.onCompleted();
}
}
}

View File

@ -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 org.apache.hugegraph.pd.pulse;
import java.util.function.Consumer;
import org.apache.hugegraph.pd.grpc.pulse.PulseResponse;
public class PartitionNotice implements PulseServerNotice<PulseResponse> {
private final long noticeId;
private final Consumer<Long> ackConsumer;
private final PulseResponse content;
public PartitionNotice(long noticeId, Consumer<Long> ackConsumer, PulseResponse content) {
this.noticeId = noticeId;
this.ackConsumer = ackConsumer;
this.content = content;
}
@Override
public void ack() {
this.ackConsumer.accept(this.noticeId);
}
@Override
public long getNoticeId() {
return this.noticeId;
}
@Override
public PulseResponse getContent() {
return this.content;
}
}

View File

@ -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.pd.pulse;
public interface PulseServerNotice<T> {
/**
* @throws RuntimeException when failed to send ack-message to pd-server
*/
void ack();
long getNoticeId();
/**
* Return a response object of gRPC stream.
*
* @return
*/
T getContent();
}

View File

@ -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.pd.watch;
import java.util.Objects;
import org.apache.hugegraph.pd.grpc.watch.NodeEventType;
public class NodeEvent {
private final String graph;
private final long nodeId;
private final EventType eventType;
public NodeEvent(String graph, long nodeId, EventType eventType) {
this.graph = graph;
this.nodeId = nodeId;
this.eventType = eventType;
}
public String getGraph() {
return graph;
}
public long getNodeId() {
return nodeId;
}
public EventType getEventType() {
return eventType;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
NodeEvent nodeEvent = (NodeEvent) o;
return nodeId == nodeEvent.nodeId && Objects.equals(graph,
nodeEvent.graph) &&
eventType == nodeEvent.eventType;
}
@Override
public int hashCode() {
return Objects.hash(graph, nodeId, eventType);
}
@Override
public String toString() {
return "NodeEvent{" +
"graph='" + graph + '\'' +
", nodeId=" + nodeId +
", eventType=" + eventType +
'}';
}
public enum EventType {
UNKNOWN,
NODE_ONLINE,
NODE_OFFLINE,
NODE_RAFT_CHANGE,
NODE_PD_LEADER_CHANGE;
public static EventType grpcTypeOf(NodeEventType grpcType) {
switch (grpcType) {
case NODE_EVENT_TYPE_NODE_ONLINE:
return NODE_ONLINE;
case NODE_EVENT_TYPE_NODE_OFFLINE:
return NODE_OFFLINE;
case NODE_EVENT_TYPE_NODE_RAFT_CHANGE:
return NODE_RAFT_CHANGE;
case NODE_EVENT_TYPE_PD_LEADER_CHANGE:
return NODE_PD_LEADER_CHANGE;
default:
return UNKNOWN;
}
}
}
}

View File

@ -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.pd.watch;
public class PDWatcher {
}

View File

@ -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.pd.watch;
import java.util.Objects;
import org.apache.hugegraph.pd.grpc.watch.WatchChangeType;
public class PartitionEvent {
private final String graph;
private final int partitionId;
private final ChangeType changeType;
public PartitionEvent(String graph, int partitionId, ChangeType changeType) {
this.graph = graph;
this.partitionId = partitionId;
this.changeType = changeType;
}
public String getGraph() {
return this.graph;
}
public int getPartitionId() {
return this.partitionId;
}
public ChangeType getChangeType() {
return this.changeType;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
PartitionEvent that = (PartitionEvent) o;
return partitionId == that.partitionId && Objects.equals(graph, that.graph) &&
changeType == that.changeType;
}
@Override
public int hashCode() {
return Objects.hash(graph, partitionId, changeType);
}
@Override
public String toString() {
return "PartitionEvent{" +
"graph='" + graph + '\'' +
", partitionId=" + partitionId +
", changeType=" + changeType +
'}';
}
public enum ChangeType {
UNKNOWN,
ADD,
ALTER,
DEL;
public static ChangeType grpcTypeOf(WatchChangeType grpcType) {
switch (grpcType) {
case WATCH_CHANGE_TYPE_ADD:
return ADD;
case WATCH_CHANGE_TYPE_ALTER:
return ALTER;
case WATCH_CHANGE_TYPE_DEL:
return DEL;
default:
return UNKNOWN;
}
}
}
}

View File

@ -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 org.apache.hugegraph.pd.watch;
enum WatchType {
PARTITION_CHANGE(10);
private final int value;
WatchType(int value) {
this.value = value;
}
}

View File

@ -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.pd;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.hugegraph.pd.common.KVPair;
import org.apache.hugegraph.pd.common.PartitionCache;
import org.apache.hugegraph.pd.grpc.Metapb;
import com.google.common.collect.Range;
import com.google.common.collect.RangeMap;
import com.google.common.collect.TreeRangeMap;
public class PartitionCacheTest {
// @Test
public void test() {
PartitionCache cache = new PartitionCache();
for (int i = 0; i < 10; i++) {
KVPair<Metapb.Partition, Metapb.Shard> partShards =
new KVPair<>(Metapb.Partition.newBuilder()
.setStartKey(i * 10)
.setEndKey((i + 1) * 10)
.build(), null);
cache.updatePartition("aa", i, partShards.getKey());
}
for (int i = 0; i < 100; i++) {
KVPair<Metapb.Partition, Metapb.Shard> partShards = cache.getPartitionByCode("aa", i);
System.out.println(" " + i + " " + partShards.getKey().getStartKey());
}
}
// @Test
public void test1() {
Map<String, RangeMap<Long, Integer>> keyToPartIdCache = new HashMap<>();
// graphName + PartitionID组成key
Map<String, KVPair<Metapb.Partition, Metapb.Shard>> partitionCache = new HashMap<>();
// 缓存全部Store用于全库查询需要优化
Map<String, List<Metapb.Store>> allStoresCache = new HashMap<>();
keyToPartIdCache.put("a", TreeRangeMap.create());
keyToPartIdCache.get("a")
.put(Range.closedOpen(1L, 2L), 1);
allStoresCache.put("a", new ArrayList<>());
allStoresCache.get("a").add(Metapb.Store.newBuilder().setId(34).build());
Map<String, RangeMap<Long, Integer>> keyToPartIdCache2 =
cloneKeyToPartIdCache(keyToPartIdCache);
System.out.println(keyToPartIdCache2.size());
}
public Map<String, RangeMap<Long, Integer>> cloneKeyToPartIdCache(
Map<String, RangeMap<Long, Integer>> cache) {
Map<String, RangeMap<Long, Integer>> cacheClone = new HashMap<>();
cache.forEach((k1, v1) -> {
cacheClone.put(k1, TreeRangeMap.create());
v1.asMapOfRanges().forEach((k2, v2) -> {
cacheClone.get(k1).put(k2, v2);
});
});
return cacheClone;
}
public Map<String, KVPair<Metapb.Partition, Metapb.Shard>>
clonePartitionCache(Map<String, KVPair<Metapb.Partition, Metapb.Shard>> cache) {
Map<String, KVPair<Metapb.Partition, Metapb.Shard>> cacheClone = new HashMap<>();
cacheClone.putAll(cache);
return cacheClone;
}
public Map<String, List<Metapb.Store>>
cloneStoreCache(Map<String, List<Metapb.Store>> cache) {
Map<String, List<Metapb.Store>> cacheClone = new HashMap<>();
cacheClone.putAll(cache);
return cacheClone;
}
}

View File

@ -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 org.apache.hugegraph.pd;
import java.nio.charset.StandardCharsets;
import java.util.List;
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.Metapb;
import org.apache.hugegraph.pd.grpc.pulse.PartitionHeartbeatRequest;
import org.apache.hugegraph.pd.grpc.pulse.PulseResponse;
import org.apache.hugegraph.pd.pulse.PulseServerNotice;
import org.junit.Assert;
import org.junit.BeforeClass;
public class StoreRegisterTest {
private static PDClient pdClient;
private final String storeAddr = "localhost";
private final String graphName = "default/hugegraph/g";
private long storeId = 0;
@BeforeClass
public static void beforeClass() throws Exception {
PDConfig config = PDConfig.of("localhost:8686");
config.setEnableCache(true);
pdClient = PDClient.create(config);
}
// @Test
public void testRegisterStore() throws PDException {
Metapb.Store store = Metapb.Store.newBuilder().setAddress(storeAddr).build();
try {
storeId = pdClient.registerStore(store);
} catch (Exception e) {
e.printStackTrace();
}
Assert.assertTrue("RegisterStore store_id = " + storeId, storeId != 0);
}
// @Test
public void testGetStore() throws PDException {
testRegisterStore();
Metapb.Store store = pdClient.getStore(storeId);
Assert.assertEquals(storeAddr, store.getAddress());
System.out.println(store);
}
// @Test
public void testGetActiveStores() throws PDException {
testRegisterStore();
List<Metapb.Store> stores = pdClient.getActiveStores(graphName);
stores.forEach((e) -> {
System.out.println("-------------------------------------");
System.out.println(e);
});
}
// @Test
public void testStoreHeartbeat() throws PDException {
testRegisterStore();
Metapb.StoreStats stats = Metapb.StoreStats.newBuilder().setStoreId(storeId).build();
pdClient.storeHeartbeat(stats);
List<Metapb.Store> stores = pdClient.getActiveStores(graphName);
boolean exist = false;
for (Metapb.Store store : stores) {
if (store.getId() == storeId) {
exist = true;
break;
}
}
Assert.assertTrue(exist);
}
// @Test
public void testPartitionHeartbeat() throws InterruptedException, PDException {
testRegisterStore();
PDPulse pdPulse = new PDPulseImpl(pdClient.getLeaderIp());
PDPulse.Notifier<PartitionHeartbeatRequest.Builder> notifier = pdPulse.connectPartition(
new PDPulse.Listener<PulseResponse>() {
@Override
public void onNext(PulseResponse response) {
}
@Override
public void onNotice(PulseServerNotice<PulseResponse> notice) {
}
@Override
public void onError(Throwable throwable) {
}
@Override
public void onCompleted() {
}
});
KVPair<Metapb.Partition, Metapb.Shard> partShard =
pdClient.getPartition("test", "1".getBytes(StandardCharsets.UTF_8));
notifier.notifyServer(PartitionHeartbeatRequest.newBuilder().setStates(
Metapb.PartitionStats.newBuilder().addGraphName("test")
.setId(partShard.getKey().getId())
.setLeader(Metapb.Shard.newBuilder().setStoreId(1).build())));
Thread.sleep(10000);
}
}

View File

@ -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.pd.client;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.hugegraph.pd.grpc.discovery.NodeInfos;
import org.apache.hugegraph.pd.grpc.discovery.Query;
import org.junit.Assert;
public class DiscoveryClientImplTest {
private static final AtomicLong label = new AtomicLong();
String address = "localhost:80";
int delay = 1000;
int wait = delay * 3 + 500;
// @Test
public void registerStore() throws InterruptedException {
HashMap<String, String> labels = new HashMap<>();
labels.put("metrics", "/actuator/prometheus");
labels.put("target", "10.81.116.77:8520");
labels.put("scheme", "http");
labels.put("__relabeling", "http");
labels.put("no_relabeling", "http");
getClient("store", "address1", labels);
labels.put("metrics", "/actuator/prometheus");
labels.put("target", "10.81.116.78:8520");
labels.put("scheme", "http");
getClient("store", "address2", labels);
labels.put("metrics", "/actuator/prometheus");
labels.put("target", "10.81.116.79:8520");
labels.put("scheme", "http");
getClient("store", "address3", labels);
labels.put("metrics", "/actuator/prometheus");
labels.put("target", "10.81.116.78:8620");
labels.put("scheme", "http");
getClient("pd", "address1", labels);
labels.put("metrics", "/graph/metrics");
labels.put("target", "10.37.1.1:9200");
labels.put("scheme", "https");
getClient("hugegraph", "address1", labels);
}
// @Test
public void testNodes() throws InterruptedException {
String appName = "hugegraph";
register(appName, address);
}
// @Test
public void testMultiNode() throws InterruptedException {
for (int i = 0; i < 2; i++) {
register("app" + i, address + i);
}
}
// @Test
public void testParallelMultiNode() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(30);
Vector<Exception> exceptions = new Vector<>();
for (int i = 0; i < 30; i++) {
int finalI = i;
new Thread(() -> {
try {
for (int j = 0; j < 3; j++) {
register("app" + finalI, address + j);
}
} catch (Exception e) {
exceptions.add(e);
} finally {
latch.countDown();
}
}).start();
}
latch.await();
Assert.assertEquals(0, exceptions.size());
}
private void register(String appName, String address) throws InterruptedException {
HashMap<String, String> labels = new HashMap<>();
String labelValue = String.valueOf(label.incrementAndGet());
labels.put("address", labelValue);
labels.put("address1", labelValue);
Query query = Query.newBuilder().setAppName(
appName).setVersion("0.13.0").putAllLabels(labels).build();
DiscoveryClientImpl discoveryClient = getClient(appName, address, labels);
Thread.sleep(10000);
NodeInfos nodeInfos1 = discoveryClient.getNodeInfos(query);
Assert.assertEquals(1, nodeInfos1.getInfoCount());
DiscoveryClientImpl discoveryClient1 = getClient(appName, address + 0, labels);
Thread.sleep(10000);
Assert.assertEquals(2, discoveryClient.getNodeInfos(query).getInfoCount());
Query query1 = Query.newBuilder().setAppName(
appName).setVersion("0.12.0").putAllLabels(labels).build();
Assert.assertEquals(0, discoveryClient.getNodeInfos(query1).getInfoCount());
discoveryClient.cancelTask();
discoveryClient1.cancelTask();
Thread.sleep(wait);
NodeInfos nodeInfos = discoveryClient.getNodeInfos(query);
System.out.println(nodeInfos);
Assert.assertEquals(0, nodeInfos.getInfoCount());
discoveryClient.close();
discoveryClient1.close();
}
private DiscoveryClientImpl getClient(String appName, String address, Map labels) {
DiscoveryClientImpl discoveryClient = null;
try {
discoveryClient = DiscoveryClientImpl.newBuilder().setCenterAddress(
"localhost:8687,localhost:8686,localhost:8688").setAddress(address).setAppName(
appName).setDelay(delay).setVersion("0.13.0").setId(
"0").setLabels(labels).build();
discoveryClient.scheduleTask();
} catch (Exception e) {
e.printStackTrace();
}
return discoveryClient;
}
}

View File

@ -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.pd.client;
import java.io.File;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.io.FileUtils;
import org.apache.hugegraph.pd.grpc.Pdpb;
import org.apache.hugegraph.pd.grpc.kv.KResponse;
import org.apache.hugegraph.pd.grpc.kv.KvResponse;
import org.yaml.snakeyaml.Yaml;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class LicenseClientImplTest {
// @Test
public void putLicense() {
PDConfig pdConfig = PDConfig.of("localhost:8686,localhost:8687,localhost:8688");
//PDConfig pdConfig = PDConfig.of("localhost:8686");
pdConfig.setEnableCache(true);
try (LicenseClient c = new LicenseClient(pdConfig)) {
File file = new File("../conf/hugegraph.license");
byte[] bytes = FileUtils.readFileToByteArray(file);
Pdpb.PutLicenseResponse putLicenseResponse = c.putLicense(bytes);
Pdpb.Error error = putLicenseResponse.getHeader().getError();
log.info(error.getMessage());
assert error.getType().equals(Pdpb.ErrorType.OK);
} catch (Exception e) {
log.error("put license with error: ", e);
}
}
// @Test
public void getKv() {
PDConfig pdConfig = PDConfig.of("10.157.12.36:8686");
pdConfig.setEnableCache(true);
try (KvClient c = new KvClient(pdConfig)) {
KResponse kResponse = c.get("S:FS");
Pdpb.Error error = kResponse.getHeader().getError();
log.info(error.getMessage());
assert error.getType().equals(Pdpb.ErrorType.OK);
Properties ymlConfig = getYmlConfig(kResponse.getValue());
Object property = ymlConfig.get("rocksdb.write_buffer_size");
assert property.toString().equals("32000000");
} catch (Exception e) {
log.error("put license with error: ", e);
}
}
// @Test
public void putKv() {
PDConfig pdConfig = PDConfig.of("127.0.0.1.70:8688");
pdConfig.setEnableCache(true);
try (KvClient c = new KvClient(pdConfig)) {
long l = System.currentTimeMillis();
KvResponse kvResponse = c.put("S:Timestamp", String.valueOf(l));
Pdpb.Error error = kvResponse.getHeader().getError();
log.info(error.getMessage());
assert error.getType().equals(Pdpb.ErrorType.OK);
} catch (Exception e) {
log.error("put license with error: ", e);
}
}
// @Test
public void putKvLocal() {
PDConfig pdConfig = PDConfig.of("localhost:8686");
pdConfig.setEnableCache(true);
try (KvClient c = new KvClient(pdConfig)) {
long l = System.currentTimeMillis();
KvResponse kvResponse = c.put("S:Timestamp", String.valueOf(l));
Pdpb.Error error = kvResponse.getHeader().getError();
log.info(error.getMessage());
assert error.getType().equals(Pdpb.ErrorType.OK);
} catch (Exception e) {
log.error("put license with error: ", e);
}
}
private Properties getYmlConfig(String yml) {
Yaml yaml = new Yaml();
Iterable<Object> load = yaml.loadAll(yml);
Iterator<Object> iterator = load.iterator();
Properties properties = new Properties();
while (iterator.hasNext()) {
Map<String, Object> next = (Map<String, Object>) iterator.next();
map2Properties(next, "", properties);
}
return properties;
}
private void map2Properties(Map<String, Object> map, String prefix, Properties properties) {
for (Map.Entry<String, Object> entry : map.entrySet()) {
String key = entry.getKey();
String newPrefix = prefix == null || prefix.length() == 0 ? key : prefix + "." + key;
Object value = entry.getValue();
if (!(value instanceof Map)) {
properties.put(newPrefix, value);
} else {
map2Properties((Map<String, Object>) value, newPrefix, properties);
}
}
}
}

View File

@ -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 org.apache.hugegraph.pd.client;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.apache.hugegraph.pd.client.test.HgPDTestUtil;
import org.apache.hugegraph.pd.grpc.pulse.PartitionHeartbeatRequest;
import org.apache.hugegraph.pd.pulse.PulseServerNotice;
import org.junit.BeforeClass;
public class PDPulseTest {
private static PDClient pdClient;
private final long storeId = 0;
private final String storeAddress = "localhost";
private final String graphName = "graph1";
@BeforeClass
public static void beforeClass() throws Exception {
PDConfig pdConfig = PDConfig.of("localhost:8686");
pdConfig.setEnableCache(true);
pdClient = PDClient.create(pdConfig);
pdClient.getLeader();
}
// @Test
public void listen() {
PDPulse pulse = new PDPulseImpl(pdClient.getLeaderIp());
CountDownLatch latch = new CountDownLatch(60);
PDPulse.Notifier<PartitionHeartbeatRequest.Builder> notifier1 =
pulse.connectPartition(new PulseListener(latch, "listener1"));
PDPulse.Notifier<PartitionHeartbeatRequest.Builder> notifier2 =
pulse.connectPartition(new PulseListener(latch, "listener2"));
PDPulse.Notifier<PartitionHeartbeatRequest.Builder> notifier3 =
pulse.connectPartition(new PulseListener(latch, "listener3"));
try {
latch.await(120, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
PartitionHeartbeatRequest.Builder builder = PartitionHeartbeatRequest.newBuilder();
notifier1.notifyServer(builder);
notifier2.notifyServer(builder);
notifier3.notifyServer(builder);
notifier1.close();
notifier2.close();
notifier3.close();
}
private class PulseListener<T> implements PDPulse.Listener<T> {
private final String listenerName;
CountDownLatch latch = new CountDownLatch(10);
private PulseListener(CountDownLatch latch, String listenerName) {
this.latch = latch;
this.listenerName = listenerName;
}
@Override
public void onNext(T response) {
// println(this.listenerName+" res: "+response);
// this.latch.countDown();
}
@Override
public void onNotice(PulseServerNotice<T> notice) {
HgPDTestUtil.println(this.listenerName + " ---> res: " + notice.getContent());
notice.ack();
this.latch.countDown();
}
@Override
public void onError(Throwable throwable) {
HgPDTestUtil.println(this.listenerName + " error: " + throwable.toString());
}
@Override
public void onCompleted() {
HgPDTestUtil.println(this.listenerName + " is completed");
}
}
}

View File

@ -0,0 +1,86 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.apache.hugegraph.pd.client;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.apache.hugegraph.pd.client.test.HgPDTestUtil;
import org.junit.BeforeClass;
import org.junit.Test;
@Deprecated
public class PDWatchTest {
private static PDClient pdClient;
private final long storeId = 0;
private final String storeAddr = "localhost";
private final String graphName = "graph1";
@BeforeClass
public static void beforeClass() {
pdClient = PDClient.create(PDConfig.of("localhost:9000"));
}
@Test
public void watch() {
PDWatch watch = pdClient.getWatchClient();
CountDownLatch latch = new CountDownLatch(10);
PDWatch.Watcher watcher1 = watch.watchPartition(new WatchListener<>(latch, "watcher1"));
PDWatch.Watcher watcher2 = watch.watchPartition(new WatchListener<>(latch, "watcher2"));
PDWatch.Watcher watcher3 = watch.watchPartition(new WatchListener<>(latch, "watcher3"));
PDWatch.Watcher nodeWatcher1 = watch.watchNode(new WatchListener<>(latch, "nodeWatcher1"));
try {
latch.await(15, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
watcher1.close();
watcher2.close();
watcher3.close();
}
private class WatchListener<T> implements PDWatch.Listener<T> {
private final String watcherName;
CountDownLatch latch;
private WatchListener(CountDownLatch latch, String watcherName) {
this.latch = latch;
this.watcherName = watcherName;
}
@Override
public void onNext(T response) {
HgPDTestUtil.println(this.watcherName + " res: " + response);
this.latch.countDown();
}
@Override
public void onError(Throwable throwable) {
HgPDTestUtil.println(this.watcherName + " error: " + throwable.toString());
}
@Override
public void onCompleted() {
HgPDTestUtil.println(this.watcherName + " is completed");
}
}
}

View File

@ -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.pd.client.test;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.List;
public class HgPDTestUtil {
public static void println(Object str) {
System.out.println(str);
}
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;
}
}

View File

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with this
work for additional information regarding copyright ownership. The ASF
licenses this file to You under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
-->
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>hugegraph-pd</artifactId>
<groupId>org.apache.hugegraph</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>hg-pd-clitools</artifactId>
<dependencies>
<dependency>
<groupId>org.apache.hugegraph</groupId>
<artifactId>hg-pd-client</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<archive>
<manifest>
<mainClass>
org.apache.hugegraph.pd.clitools.Main
</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -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.pd.clitools;
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;
public class Main {
public static void main(String[] args) throws PDException {
if (args.length < 3) {
String error = " usage: pd-address config key[=value] \n key list: " +
"\n\tenableBatchLoad";
System.out.println(error);
System.exit(0);
}
String pd = args[0];
String cmd = args[1];
String param = args[2];
System.out.println(pd + " " + cmd + " " + param);
System.out.println("Result: \n");
switch (cmd) {
case "config":
doConfig(pd, param);
case "change_raft":
doChangeRaft(pd, param);
}
}
private static void doChangeRaft(String pd, String param) throws PDException {
PDClient pdClient = PDClient.create(PDConfig.of(pd));
pdClient.updatePdRaft(param);
}
public static void doConfig(String pd, String param) throws PDException {
PDClient pdClient = PDClient.create(PDConfig.of(pd));
String[] pair = param.split("=");
String key = pair[0].trim();
Object value = null;
if (pair.length > 1) {
value = pair[1].trim();
}
if (value == null) {
Metapb.PDConfig pdConfig = pdClient.getPDConfig();
switch (key) {
case "enableBatchLoad":
// value = pdConfig.getEnableBatchLoad();
break;
case "shardCount":
value = pdConfig.getShardCount();
break;
}
System.out.println("Get config " + key + "=" + value);
} else {
Metapb.PDConfig.Builder builder = Metapb.PDConfig.newBuilder();
switch (key) {
case "enableBatchLoad":
// builder.setEnableBatchLoad(Boolean.valueOf((String)value));
case "shardCount":
builder.setShardCount(Integer.valueOf((String) value));
}
pdClient.setPDConfig(builder.build());
System.out.println("Set config " + key + "=" + value);
}
}
}

View File

@ -0,0 +1,80 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.apache.hugegraph.pd.clitools;
import java.util.Arrays;
import java.util.List;
import org.apache.hugegraph.pd.common.PDException;
public class MainTest {
public static boolean test2sup(List<Integer> arrays, int tail, int res) {
System.out.printf("%d %d%n", tail, res);
if (tail == 0) {
System.out.printf("a = %d %d%n", tail, res);
return false;
} else if (tail == 1) {
System.out.printf("b = %d %d%n", arrays.get(0), res);
return (arrays.get(0) == res);
} else if (tail == 2) {
System.out.printf("c = %d %d %d%n", arrays.get(0), arrays.get(1), res);
return (arrays.get(0) + arrays.get(1) == Math.abs(res)) ||
(Math.abs(arrays.get(0) - arrays.get(1)) == Math.abs(res));
} else {
return test2sup(arrays, tail - 1, res + arrays.get(tail - 1)) ||
test2sup(arrays, tail - 1, res - arrays.get(tail - 1));
}
}
// @Test
public void getConfig() throws PDException {
Main.main(new String[]{"127.0.0.1:8686", "config", "enableBatchLoad"});
}
// @Test
public void setBatchTrue() throws PDException {
Main.main(new String[]{"127.0.0.1:8686", "config", "enableBatchLoad= true "});
}
// @Test
public void setBatchFalse() throws PDException {
Main.main(new String[]{"127.0.0.1:8686", "config", "enableBatchLoad=false"});
}
// @Test
public void getConfig2() throws PDException {
Main.main(new String[]{"127.0.0.1:8686", "config", "shardCount"});
}
// @Test
public void setShardCount1() throws PDException {
Main.main(new String[]{"127.0.0.1:8686", "config", "shardCount=1"});
}
// @Test
public void setShardCount3() throws PDException {
Main.main(new String[]{"127.0.0.1:8686", "config", "shardCount=3"});
}
// @Test
public void test2() {
Integer[] a = new Integer[]{1, 0, 3, 2};
List<Integer> aa = Arrays.asList(a);
System.out.printf(test2sup(aa, aa.size(), 0) ? "TRUE" : "FALSE");
}
}

View File

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with this
work for additional information regarding copyright ownership. The ASF
licenses this file to You under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
-->
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.hugegraph</groupId>
<artifactId>hugegraph-pd</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>hg-pd-common</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.hugegraph</groupId>
<artifactId>hg-pd-grpc</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>
</dependencies>
</project>

View File

@ -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.pd.common;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.apache.hugegraph.pd.grpc.Metapb.Graph;
import org.apache.hugegraph.pd.grpc.Metapb.Partition;
import com.google.common.collect.RangeMap;
import com.google.common.collect.TreeRangeMap;
import lombok.Data;
@Data
public class GraphCache {
private Graph graph;
private AtomicBoolean initialized = new AtomicBoolean(false);
private AtomicBoolean writing = new AtomicBoolean(false);
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private Map<Integer, AtomicBoolean> state = new ConcurrentHashMap<>();
private Map<Integer, Partition> partitions = new ConcurrentHashMap<>();
private RangeMap<Long, Integer> range = TreeRangeMap.create();
public GraphCache(Graph graph) {
this.graph = graph;
}
public GraphCache() {
}
public Partition getPartition(Integer id) {
return partitions.get(id);
}
public Partition addPartition(Integer id, Partition p) {
return partitions.put(id, p);
}
public Partition removePartition(Integer id) {
return partitions.remove(id);
}
}

View File

@ -0,0 +1,117 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.apache.hugegraph.pd.common;
import java.util.Collection;
import java.util.Map;
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 isFalse(boolean expression, String message) {
isTrue(!expression, message);
}
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 <T> boolean isContains(Collection<T> 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;
}
}

View File

@ -0,0 +1,132 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.apache.hugegraph.pd.common;
import java.io.Serializable;
import java.util.Objects;
public class KVPair<K, V> implements Serializable {
/**
* Key of this <code>Pair</code>.
*/
private K key;
/**
* Value of this this <code>Pair</code>.
*/
private V value;
/**
* Creates a new pair
*
* @param key The key for this pair
* @param value The value to use for this pair
*/
public KVPair(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;
}
/**
* <p><code>String</code> representation of this
* <code>Pair</code>.</p>
*
* <p>The default name/value delimiter '=' is always used.</p>
*
* @return <code>String</code> representation of this <code>Pair</code>
*/
@Override
public String toString() {
return key + "=" + value;
}
/**
* <p>Generate a hash code for this <code>Pair</code>.</p>
*
* <p>The hash code is calculated using both the name and
* the value of the <code>Pair</code>.</p>
*
* @return hash code for this <code>Pair</code>
*/
@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());
}
/**
* <p>Test this <code>Pair</code> for equality with another
* <code>Object</code>.</p>
*
* <p>If the <code>Object</code> to be tested is not a
* <code>Pair</code> or is <code>null</code>, then this method
* returns <code>false</code>.</p>
*
* <p>Two <code>Pair</code>s are considered equal if and only if
* both the names and values are equal.</p>
*
* @param o the <code>Object</code> to test for
* equality with this <code>Pair</code>
* @return <code>true</code> if the given <code>Object</code> is
* equal to this <code>Pair</code> else <code>false</code>
*/
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o instanceof KVPair) {
KVPair pair = (KVPair) o;
if (!Objects.equals(key, pair.key)) {
return false;
}
return Objects.equals(value, pair.value);
}
return false;
}
}

View File

@ -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.pd.common;
public class PDException extends Exception {
private final int errorCode;
public PDException(int error) {
super(String.format("Error code = %d", error));
this.errorCode = error;
}
public PDException(int error, String msg) {
super(msg);
this.errorCode = error;
}
public PDException(int error, Throwable e) {
super(e);
this.errorCode = error;
}
public PDException(int error, String msg, Throwable e) {
super(msg, e);
this.errorCode = error;
}
public int getErrorCode() {
return errorCode;
}
}

View File

@ -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 org.apache.hugegraph.pd.common;
public class PDRuntimeException extends RuntimeException {
// public static final int LICENSE_ERROR = -11;
private int errorCode = 0;
public PDRuntimeException(int error) {
super(String.format("Error code = %d", error));
this.errorCode = error;
}
public PDRuntimeException(int error, String msg) {
super(msg);
this.errorCode = error;
}
public PDRuntimeException(int error, Throwable e) {
super(e);
this.errorCode = error;
}
public PDRuntimeException(int error, String msg, Throwable e) {
super(msg, e);
this.errorCode = error;
}
public int getErrorCode() {
return errorCode;
}
}

View File

@ -0,0 +1,460 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.apache.hugegraph.pd.common;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.apache.hugegraph.pd.grpc.Metapb;
import com.google.common.collect.Range;
import com.google.common.collect.RangeMap;
import com.google.common.collect.TreeRangeMap;
/**
* 放弃 copy on write 的方式
* 1. graph * partition 数量极多的时候效率严重下降不能用
*/
public class PartitionCache {
// 读写锁对象
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
Lock writeLock = readWriteLock.writeLock();
// 每张图一个缓存
private volatile Map<String, RangeMap<Long, Integer>> keyToPartIdCache;
// graphName + PartitionID 组成 key
private volatile Map<String, Map<Integer, Metapb.Partition>> partitionCache;
private volatile Map<Integer, Metapb.ShardGroup> shardGroupCache;
private volatile Map<Long, Metapb.Store> storeCache;
private volatile Map<String, Metapb.Graph> graphCache;
private final Map<String, AtomicBoolean> locks = new HashMap<>();
public PartitionCache() {
keyToPartIdCache = new HashMap<>();
partitionCache = new HashMap<>();
shardGroupCache = new ConcurrentHashMap<>();
storeCache = new ConcurrentHashMap<>();
graphCache = new ConcurrentHashMap<>();
}
private AtomicBoolean getOrCreateGraphLock(String graphName) {
var lock = this.locks.get(graphName);
if (lock == null) {
try {
writeLock.lock();
if ((lock = this.locks.get(graphName)) == null) {
lock = new AtomicBoolean();
locks.put(graphName, lock);
}
} finally {
writeLock.unlock();
}
}
return lock;
}
public void waitGraphLock(String graphName) {
var lock = getOrCreateGraphLock(graphName);
while (lock.get()) {
Thread.onSpinWait();
}
}
public void lockGraph(String graphName) {
var lock = getOrCreateGraphLock(graphName);
while (lock.compareAndSet(false, true)) {
Thread.onSpinWait();
}
}
public void unlockGraph(String graphName) {
var lock = getOrCreateGraphLock(graphName);
lock.set(false);
}
/**
* 根据 partitionId 返回分区信息
*
* @param graphName
* @param partId
* @return
*/
public KVPair<Metapb.Partition, Metapb.Shard> getPartitionById(String graphName, int partId) {
waitGraphLock(graphName);
var graphs = partitionCache.get(graphName);
if (graphs != null) {
var partition = graphs.get(partId);
if (partition != null) {
return new KVPair<>(partition, getLeaderShard(partId));
}
}
return null;
}
/**
* 返回 key 所在的分区信息
*
* @param key
* @return
*/
public KVPair<Metapb.Partition, Metapb.Shard> getPartitionByKey(String graphName, byte[] key) {
int code = PartitionUtils.calcHashcode(key);
return getPartitionByCode(graphName, code);
}
/**
* 根据 key hashcode 返回分区信息
*
* @param graphName
* @param code
* @return
*/
public KVPair<Metapb.Partition, Metapb.Shard> getPartitionByCode(String graphName, long code) {
waitGraphLock(graphName);
RangeMap<Long, Integer> rangeMap = keyToPartIdCache.get(graphName);
if (rangeMap != null) {
Integer partId = rangeMap.get(code);
if (partId != null) {
return getPartitionById(graphName, partId);
}
}
return null;
}
public List<Metapb.Partition> getPartitions(String graphName) {
waitGraphLock(graphName);
List<Metapb.Partition> partitions = new ArrayList<>();
if (!partitionCache.containsKey(graphName)) {
return partitions;
}
partitionCache.get(graphName).forEach((k, v) -> {
partitions.add(v);
});
return partitions;
}
public boolean addPartition(String graphName, int partId, Metapb.Partition partition) {
waitGraphLock(graphName);
Metapb.Partition old = null;
if (partitionCache.containsKey(graphName)) {
old = partitionCache.get(graphName).get(partId);
}
if (old != null && old.equals(partition)) {
return false;
}
try {
lockGraph(graphName);
partitionCache.computeIfAbsent(graphName, k -> new HashMap<>()).put(partId, partition);
if (old != null) {
// old [1-3) [2-3) 覆盖了 [1-3) 变成 [1-2) 不应该删除原先的 [1-3)
// 当确认老的 start, end 都是自己的时候才可以删除老的(即还没覆盖
var graphRange = keyToPartIdCache.get(graphName);
if (Objects.equals(partition.getId(), graphRange.get(partition.getStartKey())) &&
Objects.equals(partition.getId(), graphRange.get(partition.getEndKey() - 1))) {
graphRange.remove(graphRange.getEntry(partition.getStartKey()).getKey());
}
}
keyToPartIdCache.computeIfAbsent(graphName, k -> TreeRangeMap.create())
.put(Range.closedOpen(partition.getStartKey(),
partition.getEndKey()), partId);
} finally {
unlockGraph(graphName);
}
return true;
}
public void updatePartition(String graphName, int partId, Metapb.Partition partition) {
try {
lockGraph(graphName);
Metapb.Partition old = null;
var graphs = partitionCache.get(graphName);
if (graphs != null) {
old = graphs.get(partId);
}
if (old != null) {
var graphRange = keyToPartIdCache.get(graphName);
if (Objects.equals(partition.getId(), graphRange.get(partition.getStartKey())) &&
Objects.equals(partition.getId(), graphRange.get(partition.getEndKey() - 1))) {
graphRange.remove(graphRange.getEntry(partition.getStartKey()).getKey());
}
}
partitionCache.computeIfAbsent(graphName, k -> new HashMap<>()).put(partId, partition);
keyToPartIdCache.computeIfAbsent(graphName, k -> TreeRangeMap.create())
.put(Range.closedOpen(partition.getStartKey(), partition.getEndKey()),
partId);
} finally {
unlockGraph(graphName);
}
}
public boolean updatePartition(Metapb.Partition partition) {
var graphName = partition.getGraphName();
var partitionId = partition.getId();
var old = getPartitionById(graphName, partitionId);
if (old != null && Objects.equals(partition, old.getKey())) {
return false;
}
updatePartition(graphName, partitionId, partition);
return true;
}
public void removePartition(String graphName, int partId) {
try {
lockGraph(graphName);
var partition = partitionCache.get(graphName).remove(partId);
if (partition != null) {
var graphRange = keyToPartIdCache.get(graphName);
if (Objects.equals(partition.getId(), graphRange.get(partition.getStartKey())) &&
Objects.equals(partition.getId(), graphRange.get(partition.getEndKey() - 1))) {
graphRange.remove(graphRange.getEntry(partition.getStartKey()).getKey());
}
}
} finally {
unlockGraph(graphName);
}
}
/**
* remove partition id of graph name
*
* @param graphName
* @param id
*/
public void remove(String graphName, int id) {
removePartition(graphName, id);
}
/**
* remove all partitions
*/
public void removePartitions() {
writeLock.lock();
try {
partitionCache = new HashMap<>();
keyToPartIdCache = new HashMap<>();
locks.clear();
} finally {
writeLock.unlock();
}
}
/**
* remove partition cache of graphName
*
* @param graphName
*/
public void removeAll(String graphName) {
try {
lockGraph(graphName);
partitionCache.remove(graphName);
keyToPartIdCache.remove(graphName);
locks.remove(graphName);
} finally {
unlockGraph(graphName);
}
}
private String makePartitionKey(String graphName, int partId) {
return graphName + "/" + partId;
}
public boolean updateShardGroup(Metapb.ShardGroup shardGroup) {
Metapb.ShardGroup oldShardGroup = shardGroupCache.get(shardGroup.getId());
if (oldShardGroup != null && oldShardGroup.equals(shardGroup)) {
return false;
}
shardGroupCache.put(shardGroup.getId(), shardGroup);
return true;
}
public void deleteShardGroup(int shardGroupId) {
shardGroupCache.remove(shardGroupId);
}
public Metapb.ShardGroup getShardGroup(int groupId) {
return shardGroupCache.get(groupId);
}
public boolean addStore(Long storeId, Metapb.Store store) {
Metapb.Store oldStore = storeCache.get(storeId);
if (oldStore != null && oldStore.equals(store)) {
return false;
}
storeCache.put(storeId, store);
return true;
}
public Metapb.Store getStoreById(Long storeId) {
return storeCache.get(storeId);
}
public void removeStore(Long storeId) {
storeCache.remove(storeId);
}
public boolean hasGraph(String graphName) {
return getPartitions(graphName).size() > 0;
}
public void updateGraph(Metapb.Graph graph) {
if (Objects.equals(graph, getGraph(graph.getGraphName()))) {
return;
}
graphCache.put(graph.getGraphName(), graph);
}
public Metapb.Graph getGraph(String graphName) {
return graphCache.get(graphName);
}
public List<Metapb.Graph> getGraphs() {
List<Metapb.Graph> graphs = new ArrayList<>();
graphCache.forEach((k, v) -> {
graphs.add(v);
});
return graphs;
}
public void reset() {
writeLock.lock();
try {
partitionCache = new HashMap<>();
keyToPartIdCache = new HashMap<>();
shardGroupCache = new ConcurrentHashMap<>();
storeCache = new ConcurrentHashMap<>();
graphCache = new ConcurrentHashMap<>();
locks.clear();
} finally {
writeLock.unlock();
}
}
public void clear() {
reset();
}
public String debugCacheByGraphName(String graphName) {
StringBuilder builder = new StringBuilder();
builder.append("Graph:").append(graphName).append(", cache info: range info: {");
var rangeMap = keyToPartIdCache.get(graphName);
builder.append(rangeMap == null ? "" : rangeMap).append("}");
if (rangeMap != null) {
builder.append(", partition info : {");
rangeMap.asMapOfRanges().forEach((k, v) -> {
var partition = partitionCache.get(graphName).get(v);
builder.append("[part_id:").append(v);
if (partition != null) {
builder.append(", start_key:").append(partition.getStartKey())
.append(", end_key:").append(partition.getEndKey())
.append(", state:").append(partition.getState().name());
}
builder.append("], ");
});
builder.append("}");
}
builder.append(", graph info:{");
var graph = graphCache.get(graphName);
if (graph != null) {
builder.append("partition_count:").append(graph.getPartitionCount())
.append(", state:").append(graph.getState().name());
}
builder.append("}]");
return builder.toString();
}
public Metapb.Shard getLeaderShard(int partitionId) {
var shardGroup = shardGroupCache.get(partitionId);
if (shardGroup != null) {
for (Metapb.Shard shard : shardGroup.getShardsList()) {
if (shard.getRole() == Metapb.ShardRole.Leader) {
return shard;
}
}
}
return null;
}
public void updateShardGroupLeader(int partitionId, Metapb.Shard leader) {
if (shardGroupCache.containsKey(partitionId) && leader != null) {
if (!Objects.equals(getLeaderShard(partitionId), leader)) {
var shardGroup = shardGroupCache.get(partitionId);
var builder = Metapb.ShardGroup.newBuilder(shardGroup).clearShards();
for (var shard : shardGroup.getShardsList()) {
builder.addShards(
Metapb.Shard.newBuilder()
.setStoreId(shard.getStoreId())
.setRole(shard.getStoreId() == leader.getStoreId() ?
Metapb.ShardRole.Leader :
Metapb.ShardRole.Follower)
.build()
);
}
shardGroupCache.put(partitionId, builder.build());
}
}
}
public String debugShardGroup() {
StringBuilder builder = new StringBuilder();
builder.append("shard group cache:{");
shardGroupCache.forEach((partitionId, shardGroup) -> {
builder.append(partitionId).append("::{")
.append("version:").append(shardGroup.getVersion())
.append(", conf_version:").append(shardGroup.getConfVer())
.append(", state:").append(shardGroup.getState().name())
.append(", shards:[");
for (var shard : shardGroup.getShardsList()) {
builder.append("{store_id:").append(shard.getStoreId())
.append(", role:").append(shard.getRole().name())
.append("},");
}
builder.append("], ");
});
builder.append("}");
return builder.toString();
}
}

View File

@ -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.pd.common;
public class PartitionUtils {
public static final int MAX_VALUE = 0xffff;
/**
* 计算key的hashcode
*
* @param key
* @return hashcode
*/
public static int calcHashcode(byte[] key) {
final int p = 16777619;
int hash = (int) 2166136261L;
for (byte element : key) {
hash = (hash ^ element) * p;
}
hash += hash << 13;
hash ^= hash >> 7;
hash += hash << 3;
hash ^= hash >> 17;
hash += hash << 5;
hash = hash & PartitionUtils.MAX_VALUE;
if (hash == PartitionUtils.MAX_VALUE) {
hash = PartitionUtils.MAX_VALUE - 1;
}
return hash;
}
}

View File

@ -0,0 +1,94 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with this
work for additional information regarding copyright ownership. The ASF
licenses this file to You under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
-->
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.hugegraph</groupId>
<artifactId>hugegraph-pd</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>hg-pd-core</artifactId>
<properties>
<jetcd-version>0.5.10</jetcd-version>
</properties>
<dependencies>
<dependency>
<groupId>com.alipay.sofa</groupId>
<artifactId>jraft-core</artifactId>
<!-- TODO: use open source version & adopt the code later -->
<version>1.3.13</version>
<exclusions>
<exclusion>
<groupId>org.rocksdb</groupId>
<artifactId>rocksdbjni</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.rocksdb</groupId>
<artifactId>rocksdbjni</artifactId>
<version>6.29.5</version>
</dependency>
<dependency>
<groupId>org.apache.hugegraph</groupId>
<artifactId>hg-pd-grpc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.20</version>
</dependency>
<dependency>
<groupId>org.apache.hugegraph</groupId>
<artifactId>hg-pd-common</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<version>2.5.14</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.9</version>
</dependency>
<dependency>
<groupId>org.apache.hugegraph</groupId>
<artifactId>hg-store-grpc</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
</project>

View File

@ -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 org.apache.hugegraph.pd;
import java.util.List;
import org.apache.hugegraph.pd.common.PDException;
import org.apache.hugegraph.pd.config.PDConfig;
import org.apache.hugegraph.pd.grpc.Metapb;
import org.apache.hugegraph.pd.meta.ConfigMetaStore;
import org.apache.hugegraph.pd.meta.MetadataFactory;
import org.apache.hugegraph.pd.raft.RaftStateListener;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class ConfigService implements RaftStateListener {
private final ConfigMetaStore meta;
private PDConfig pdConfig;
public ConfigService(PDConfig config) {
this.pdConfig = config;
config.setConfigService(this);
meta = MetadataFactory.newConfigMeta(config);
}
public Metapb.PDConfig getPDConfig(long version) throws PDException {
return this.meta.getPdConfig(version);
}
public Metapb.PDConfig getPDConfig() throws PDException {
return this.meta.getPdConfig(0);
}
public Metapb.PDConfig setPDConfig(Metapb.PDConfig mConfig) throws PDException {
Metapb.PDConfig oldCfg = getPDConfig();
Metapb.PDConfig.Builder builder = oldCfg.toBuilder().mergeFrom(mConfig)
.setVersion(oldCfg.getVersion() + 1)
.setTimestamp(System.currentTimeMillis());
mConfig = this.meta.setPdConfig(builder.build());
log.info("PDConfig has been modified, new PDConfig is {}", mConfig);
updatePDConfig(mConfig);
return mConfig;
}
public List<Metapb.GraphSpace> getGraphSpace(String graphSpaceName) throws PDException {
return this.meta.getGraphSpace(graphSpaceName);
}
public Metapb.GraphSpace setGraphSpace(Metapb.GraphSpace graphSpace) throws PDException {
return this.meta.setGraphSpace(graphSpace.toBuilder()
.setTimestamp(System.currentTimeMillis())
.build());
}
/**
* 从存储中读取配置项并覆盖全局的PDConfig对象
*
* @return
*/
public PDConfig loadConfig() {
try {
Metapb.PDConfig mConfig = this.meta.getPdConfig(0);
if (mConfig == null) {
mConfig = Metapb.PDConfig.newBuilder()
.setPartitionCount(pdConfig.getInitialPartitionCount())
.setShardCount(pdConfig.getPartition().getShardCount())
.setVersion(1)
.setTimestamp(System.currentTimeMillis())
.setMaxShardsPerStore(
pdConfig.getPartition().getMaxShardsPerStore())
.build();
this.meta.setPdConfig(mConfig);
}
pdConfig = updatePDConfig(mConfig);
} catch (Exception e) {
log.error("ConfigService loadConfig exception {}", e);
}
return pdConfig;
}
public synchronized PDConfig updatePDConfig(Metapb.PDConfig mConfig) {
log.info("update pd config: mConfig:{}", mConfig);
pdConfig.getPartition().setShardCount(mConfig.getShardCount());
pdConfig.getPartition().setTotalCount(mConfig.getPartitionCount());
pdConfig.getPartition().setMaxShardsPerStore(mConfig.getMaxShardsPerStore());
return pdConfig;
}
public synchronized PDConfig setPartitionCount(int count) {
Metapb.PDConfig mConfig = null;
try {
mConfig = getPDConfig();
mConfig = mConfig.toBuilder().setPartitionCount(count).build();
setPDConfig(mConfig);
} catch (PDException e) {
log.error("ConfigService exception {}", e);
e.printStackTrace();
}
return pdConfig;
}
/**
* meta store中的数量
* 由于可能会受分区分裂/合并的影响原始的partition count不推荐使用
*
* @return partition count of cluster
* @throws PDException when io error
*/
public int getPartitionCount() throws PDException {
return getPDConfig().getPartitionCount();
}
@Override
public void onRaftLeaderChanged() {
loadConfig();
}
}

View File

@ -0,0 +1,82 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.apache.hugegraph.pd;
import org.apache.hugegraph.pd.common.PDException;
import org.apache.hugegraph.pd.config.PDConfig;
import org.apache.hugegraph.pd.meta.IdMetaStore;
import org.apache.hugegraph.pd.meta.MetadataFactory;
public class IdService {
private final IdMetaStore meta;
private PDConfig pdConfig;
public IdService(PDConfig config) {
this.pdConfig = config;
meta = MetadataFactory.newHugeServerMeta(config);
}
public PDConfig getPdConfig() {
return pdConfig;
}
public void setPdConfig(PDConfig pdConfig) {
this.pdConfig = pdConfig;
}
public long getId(String key, int delta) throws PDException {
return meta.getId(key, delta);
}
public void resetId(String key) throws PDException {
meta.resetId(key);
}
/**
* 获取自增循环不重复id, 达到上限后从0开始自增.自动跳过正在使用的cid
*
* @param key
* @param max
* @return
* @throws PDException
*/
public long getCId(String key, long max) throws PDException {
return meta.getCId(key, max);
}
public long getCId(String key, String name, long max) throws PDException {
return meta.getCId(key, name, max);
}
/**
* 删除一个自增循环id
*
* @param key
* @param cid
* @return
* @throws PDException
*/
public long delCId(String key, long cid) throws PDException {
return meta.delCId(key, cid);
}
public long delCIdDelay(String key, String name, long cid) throws PDException {
return meta.delCIdDelay(key, name, cid);
}
}

View File

@ -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.pd;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.hugegraph.pd.common.PDException;
import org.apache.hugegraph.pd.config.PDConfig;
import org.apache.hugegraph.pd.grpc.kv.Kv;
import org.apache.hugegraph.pd.grpc.kv.V;
import org.apache.hugegraph.pd.meta.MetadataKeyHelper;
import org.apache.hugegraph.pd.meta.MetadataRocksDBStore;
import org.apache.hugegraph.pd.store.KV;
import org.springframework.stereotype.Service;
import com.google.protobuf.InvalidProtocolBufferException;
import lombok.extern.slf4j.Slf4j;
/**
*
**/
@Slf4j
@Service
public class KvService {
public static final char KV_DELIMITER = '@';
// TODO 主前缀之后增加类名做区分
private static final String TTL_PREFIX = "T";
private static final String KV_PREFIX = "K";
private static final String LOCK_PREFIX = "L";
private static final String KV_PREFIX_DELIMITER = KV_PREFIX + KV_DELIMITER;
private static final byte[] EMPTY_VALUE = new byte[0];
private final MetadataRocksDBStore meta;
private PDConfig pdConfig;
public KvService(PDConfig config) {
this.pdConfig = config;
meta = new MetadataRocksDBStore(config);
}
public static String getKey(Object... keys) {
StringBuilder builder = MetadataKeyHelper.getStringBuilderHelper();
builder.append(KV_PREFIX).append(KV_DELIMITER);
for (Object key : keys) {
builder.append(key == null ? "" : key).append(KV_DELIMITER);
}
return builder.substring(0, builder.length() - 1);
}
public static byte[] getKeyBytes(Object... keys) {
String key = getKey(keys);
return key.getBytes(Charset.defaultCharset());
}
public static String getKeyWithoutPrefix(Object... keys) {
StringBuilder builder = MetadataKeyHelper.getStringBuilderHelper();
for (Object key : keys) {
builder.append(key == null ? "" : key).append(KV_DELIMITER);
}
return builder.substring(0, builder.length() - 1);
}
public static String getDelimiter() {
return String.valueOf(KV_DELIMITER);
}
public PDConfig getPdConfig() {
return pdConfig;
}
public void setPdConfig(PDConfig pdConfig) {
this.pdConfig = pdConfig;
}
public void put(String key, String value) throws PDException {
V storeValue = V.newBuilder().setValue(value).setTtl(0).build();
meta.put(getStoreKey(key), storeValue.toByteArray());
// log.warn("add key with key-{}:value-{}", key, value);
}
public void put(String key, String value, long ttl) throws PDException {
long curTime = System.currentTimeMillis();
curTime += ttl;
V storeValue = V.newBuilder().setValue(value).setSt(ttl).setTtl(curTime).build();
meta.put(getStoreKey(key), storeValue.toByteArray());
meta.put(getTTLStoreKey(key, curTime), EMPTY_VALUE);
// log.warn("add key with key-{}:value-{}:ttl-{}", key, value, ttl);
}
public String get(String key) throws PDException {
byte[] storeKey = getStoreKey(key);
return get(storeKey);
}
public String get(byte[] keyBytes) throws PDException {
byte[] bytes = meta.getOne(keyBytes);
String v = getValue(keyBytes, bytes);
return v;
}
private String getValue(byte[] keyBytes, byte[] valueBytes) throws PDException {
if (valueBytes == null || valueBytes.length == 0) {
return "";
}
try {
V v = V.parseFrom(valueBytes);
if (v.getTtl() == 0 || v.getTtl() >= System.currentTimeMillis()) {
return v.getValue();
} else {
meta.remove(keyBytes);
meta.remove(getTTLStoreKey(new String(keyBytes), v.getTtl()));
}
} catch (Exception e) {
log.error("parse value with error:{}", e.getMessage());
throw new PDException(-1, e.getMessage());
}
return null;
}
public boolean keepAlive(String key) throws PDException {
byte[] bytes = meta.getOne(getStoreKey(key));
try {
if (bytes == null || bytes.length == 0) {
return false;
}
V v = V.parseFrom(bytes);
if (v != null) {
long ttl = v.getTtl();
long st = v.getSt();
meta.remove(getTTLStoreKey(key, ttl));
put(key, v.getValue(), st);
return true;
} else {
return false;
}
} catch (InvalidProtocolBufferException e) {
throw new PDException(-1, e.getMessage());
}
}
public Kv delete(String key) throws PDException {
byte[] storeKey = getStoreKey(key);
String value = this.get(storeKey);
meta.remove(storeKey);
Kv.Builder builder = Kv.newBuilder().setKey(key);
if (value != null) {
builder.setValue(value);
}
Kv kv = builder.build();
// log.warn("delete kv with key :{}", key);
return kv;
}
public List<Kv> deleteWithPrefix(String key) throws PDException {
byte[] storeKey = getStoreKey(key);
//TODO to many rows for scan
List<KV> kvList = meta.scanPrefix(storeKey);
LinkedList<Kv> kvs = new LinkedList<>();
for (KV kv : kvList) {
String kvKey = new String(kv.getKey()).replaceFirst(KV_PREFIX_DELIMITER, "");
String kvValue = getValue(kv.getKey(), kv.getValue());
if (kvValue != null) {
kvs.add(Kv.newBuilder().setKey(kvKey).setValue(kvValue).build());
}
}
meta.removeByPrefix(storeKey);
// log.warn("delete kv with key prefix :{}", key);
return kvs;
}
/**
* scan result ranged from key start and key end
*
* @param keyStart
* @param keyEnd
* @return Records
* @throws PDException
*/
public Map<String, String> scanRange(String keyStart, String keyEnd) throws PDException {
List<KV> list = meta.scanRange(getStoreKey(keyStart), getStoreKey(keyEnd));
Map<String, String> map = new HashMap<>();
for (KV kv : list) {
String kvKey = new String(kv.getKey()).replaceFirst(KV_PREFIX_DELIMITER, "");
String kvValue = getValue(kv.getKey(), kv.getValue());
if (kvValue != null) {
map.put(kvKey, kvValue);
}
}
return map;
}
public Map<String, String> scanWithPrefix(String key) throws PDException {
List<KV> kvList = meta.scanPrefix(getStoreKey(key));
HashMap<String, String> map = new HashMap<>();
for (KV kv : kvList) {
String kvKey = new String(kv.getKey()).replaceFirst(KV_PREFIX_DELIMITER, "");
String kvValue = getValue(kv.getKey(), kv.getValue());
if (kvValue != null) {
map.put(kvKey, kvValue);
}
}
return map;
}
public boolean locked(String key) throws PDException {
String lockKey = KvService.getKeyWithoutPrefix(KvService.LOCK_PREFIX, key);
Map<String, String> allLock = scanWithPrefix(lockKey);
return allLock != null && allLock.size() != 0;
}
private boolean owned(String key, long clientId) throws PDException {
String lockKey = KvService.getKeyWithoutPrefix(KvService.LOCK_PREFIX, key);
Map<String, String> allLock = scanWithPrefix(lockKey);
if (allLock.size() == 0) {
return true;
}
for (Map.Entry<String, String> entry : allLock.entrySet()) {
String entryKey = entry.getKey();
String[] split = entryKey.split(String.valueOf(KV_DELIMITER));
if (Long.valueOf(split[split.length - 1]).equals(clientId)) {
return true;
}
}
return false;
}
public boolean lock(String key, long ttl, long clientId) throws PDException {
//TODO lock improvement
synchronized (KvService.class) {
if (!owned(key, clientId)) {
return false;
}
put(getLockKey(key, clientId), " ", ttl);
return true;
}
}
public boolean lockWithoutReentrant(String key, long ttl,
long clientId) throws PDException {
synchronized (KvService.class) {
if (locked(key)) {
return false;
}
put(getLockKey(key, clientId), " ", ttl);
return true;
}
}
public boolean unlock(String key, long clientId) throws PDException {
synchronized (KvService.class) {
if (!owned(key, clientId)) {
return false;
}
delete(getLockKey(key, clientId));
return true;
}
}
public boolean keepAlive(String key, long clientId) throws PDException {
String lockKey = getLockKey(key, clientId);
return keepAlive(lockKey);
}
public String getLockKey(String key, long clientId) {
return getKeyWithoutPrefix(LOCK_PREFIX, key, clientId);
}
public byte[] getStoreKey(String key) {
return getKeyBytes(key);
}
public byte[] getTTLStoreKey(String key, long time) {
return getKeyBytes(TTL_PREFIX, time, key);
}
public void clearTTLData() {
try {
byte[] ttlStartKey = getTTLStoreKey("", 0);
byte[] ttlEndKey = getTTLStoreKey("", System.currentTimeMillis());
List<KV> kvList = meta.scanRange(ttlStartKey, ttlEndKey);
for (KV kv : kvList) {
String key = new String(kv.getKey());
int index = key.indexOf(KV_DELIMITER, 2);
String delKey = key.substring(index + 1);
delete(delKey);
meta.remove(kv.getKey());
}
} catch (Exception e) {
log.error("clear ttl data with error :", e);
}
}
public MetadataRocksDBStore getMeta() {
return meta;
}
}

View File

@ -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.pd;
import java.util.List;
import org.apache.hugegraph.pd.common.PDException;
import org.apache.hugegraph.pd.config.PDConfig;
import org.apache.hugegraph.pd.grpc.Metapb;
import org.apache.hugegraph.pd.meta.LogMeta;
import org.apache.hugegraph.pd.meta.MetadataFactory;
import org.springframework.stereotype.Service;
import com.google.protobuf.Any;
import com.google.protobuf.GeneratedMessageV3;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
public class LogService {
public static final String GRPC = "GRPC";
public static final String REST = "REST";
public static final String TASK = "TASK";
public static final String NODE_CHANGE = "NODE_CHANGE";
public static final String PARTITION_CHANGE = "PARTITION_CHANGE";
private final LogMeta logMeta;
public LogService(PDConfig pdConfig) {
logMeta = MetadataFactory.newLogMeta(pdConfig);
}
public List<Metapb.LogRecord> getLog(String action, Long start, Long end) throws PDException {
return logMeta.getLog(action, start, end);
}
public void insertLog(String action, String message, GeneratedMessageV3 target) {
try {
Metapb.LogRecord logRecord = Metapb.LogRecord.newBuilder()
.setAction(action)
.setMessage(message)
.setTimestamp(System.currentTimeMillis())
.setObject(Any.pack(target))
.build();
logMeta.insertLog(logRecord);
} catch (PDException e) {
log.debug("Insert log with error:{}", e);
}
}
}

View File

@ -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.pd;
import org.apache.hugegraph.pd.common.PDException;
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;
/**
* 分区命令监听
*/
public interface PartitionInstructionListener {
void changeShard(Metapb.Partition partition, ChangeShard changeShard) throws PDException;
void transferLeader(Metapb.Partition partition, TransferLeader transferLeader) throws
PDException;
void splitPartition(Metapb.Partition partition, SplitPartition splitPartition) throws
PDException;
void dbCompaction(Metapb.Partition partition, DbCompaction dbCompaction) throws PDException;
void movePartition(Metapb.Partition partition, MovePartition movePartition) throws PDException;
void cleanPartition(Metapb.Partition partition, CleanPartition cleanPartition) throws
PDException;
void changePartitionKeyRange(Metapb.Partition partition,
PartitionKeyRange partitionKeyRange) throws PDException;
}

View File

@ -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.pd;
import org.apache.hugegraph.pd.grpc.Metapb;
/**
* 分区状态监听
*/
public interface PartitionStatusListener {
void onPartitionChanged(Metapb.Partition partition, Metapb.Partition newPartition);
void onPartitionRemoved(Metapb.Partition partition);
}

View File

@ -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.pd;
import org.apache.hugegraph.pd.common.PDException;
import org.apache.hugegraph.pd.config.PDConfig;
import org.apache.hugegraph.pd.grpc.discovery.NodeInfo;
import org.apache.hugegraph.pd.grpc.discovery.NodeInfos;
import org.apache.hugegraph.pd.grpc.discovery.Query;
import org.apache.hugegraph.pd.meta.DiscoveryMetaStore;
import org.apache.hugegraph.pd.meta.MetadataFactory;
public class RegistryService {
private final PDConfig pdConfig;
private final DiscoveryMetaStore meta;
public RegistryService(PDConfig config) {
this.pdConfig = config;
meta = MetadataFactory.newDiscoveryMeta(config);
}
public void register(NodeInfo nodeInfo, int outTimes) throws PDException {
meta.register(nodeInfo, outTimes);
}
public NodeInfos getNodes(Query query) {
return meta.getNodes(query);
}
}

View File

@ -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.pd;
import org.apache.hugegraph.pd.grpc.Metapb;
public interface ShardGroupStatusListener {
void onShardListChanged(Metapb.ShardGroup shardGroup, Metapb.ShardGroup newShardGroup);
void onShardListOp(Metapb.ShardGroup shardGroup);
}

View File

@ -0,0 +1,266 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.apache.hugegraph.pd;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.apache.hugegraph.pd.common.PDException;
import org.apache.hugegraph.pd.config.PDConfig;
import org.apache.hugegraph.pd.grpc.Metapb;
import org.apache.hugegraph.pd.meta.MetadataKeyHelper;
import org.springframework.stereotype.Service;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
public class StoreMonitorDataService {
private static final String MONITOR_DATA_PREFIX = "SMD";
private final PDConfig pdConfig;
private final KvService kvService;
/**
* the last timestamp of the store monitor data,
* used for determine the gap of store's heartbeat.
*/
private final Map<Long, Long> lastStoreStateTimestamp;
public StoreMonitorDataService(PDConfig pdConfig) {
this.pdConfig = pdConfig;
this.kvService = new KvService(pdConfig);
this.lastStoreStateTimestamp = new HashMap<>();
}
/**
* save the store stats
*
* @param storeStats
*/
public void saveMonitorData(Metapb.StoreStats storeStats) throws PDException {
long storeId = storeStats.getStoreId();
/**
* load the latest store timestamp when start up or alter leader
*/
if (!lastStoreStateTimestamp.containsKey(storeId)) {
long lastTimestamp = getLatestStoreMonitorDataTimeStamp(storeId);
log.debug("store id : {}, last timestamp :{}", storeId, lastTimestamp);
lastStoreStateTimestamp.put(storeId, lastTimestamp);
}
long current = System.currentTimeMillis() / 1000;
long interval = this.pdConfig.getStore().getMonitorInterval();
// exceed the interval
if (current - lastStoreStateTimestamp.getOrDefault(storeId, 0L) >= interval) {
saveMonitorDataToDb(storeStats, current);
log.debug("store id: {}, system info:{}", storeId,
debugMonitorInfo(storeStats.getSystemMetricsList()));
lastStoreStateTimestamp.put(storeId, current);
}
}
/**
* save the snapshot of store status
*
* @param storeStats store status
* @param ts, timestamp
* @return store status
* @throws PDException
*/
private void saveMonitorDataToDb(Metapb.StoreStats storeStats, long ts) throws PDException {
String key = getMonitorDataKey(storeStats.getStoreId(), ts);
log.debug("store id: {}, save monitor data info, ts:{}, my key:{}", storeStats.getStoreId(),
ts, key);
kvService.put(key, extractMetricsFromStoreStatus(storeStats));
}
public String debugMonitorInfo(List<Metapb.RecordPair> systemInfo) {
StringBuilder sb = new StringBuilder();
sb.append("[");
for (Metapb.RecordPair pair : systemInfo) {
sb.append(pair.getKey());
sb.append(":");
sb.append(pair.getValue());
sb.append(",");
}
sb.append("]");
return sb.toString();
}
/**
* get the historical monitor data by store id, by range(start, end)
*
* @param storeId store id
* @param start range start
* @param end range end
* @return list of store stats
*/
public Map<String, String> getStoreMonitorData(long storeId, long start, long end) throws
PDException {
log.debug("get monitor data, store id:{}, start{}, end:{}",
storeId,
getMonitorDataKey(storeId, start),
getMonitorDataKey(storeId, end));
return kvService.scanRange(getMonitorDataKey(storeId, start),
getMonitorDataKey(storeId, end));
}
/**
* for api service
*
* @param storeId
* @return
* @throws PDException
*/
public List<Map<String, Long>> getStoreMonitorData(long storeId) throws PDException {
List<Map<String, Long>> result = new LinkedList<>();
long current = System.currentTimeMillis() / 1000;
long start = current - this.pdConfig.getStore().getRetentionPeriod();
try {
for (Map.Entry<String, String> entry : getStoreMonitorData(storeId, start,
current).entrySet()) {
String[] arr =
entry.getKey().split(String.valueOf(MetadataKeyHelper.getDelimiter()));
Map<String, Long> map = new HashMap();
long timestamp = Long.parseLong(arr[arr.length - 1]);
map.put("ts", timestamp);
for (String pair : entry.getValue().split(",")) {
String[] p = pair.split(":");
if (p.length == 2) {
map.put(p[0], Long.parseLong(p[1]));
}
}
result.add(map);
}
result.sort((o1, o2) -> o1.get("ts").compareTo(o2.get("ts")));
} catch (PDException e) {
log.error(e.getMessage());
}
return result;
}
/**
* for api service, export txt
*
* @param storeId
* @return
* @throws PDException
*/
public String getStoreMonitorDataText(long storeId) throws PDException {
List<Map<String, Long>> result = getStoreMonitorData(storeId);
StringBuilder sb = new StringBuilder();
if (result.size() > 0) {
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
Map<String, Long> lastRow = result.get(result.size() - 1);
List<String> columns = new ArrayList<>();
// construct columns, ts + sorted keys
columns.add("ts");
columns.addAll(lastRow.keySet().stream()
.filter(x -> !"ts".equals(x))
.sorted()
.collect(Collectors.toList()));
sb.append(String.join(",", columns).replace("\"", "")).append("\r\n");
for (Map<String, Long> row : result) {
for (String key : columns) {
// ts + , + ...
if ("ts".equals(key)) {
// format ts
sb.append(dtf.format(
LocalDateTime.ofInstant(Instant.ofEpochSecond(row.get(key)),
ZoneId.systemDefault())));
continue;
} else {
sb.append(",").append(row.getOrDefault(key, 0L));
}
}
sb.append("\r\n");
}
}
return sb.toString();
}
/**
* remove the monitor data of the store that before till(not include)
*
* @param storeId store id
* @param till expire time
* @return affect rows
*/
public int removeExpiredMonitorData(long storeId, long till) throws PDException {
String keyStart = getMonitorDataKey(storeId, 1);
String keyEnd = getMonitorDataKey(storeId, till);
int records = 0;
for (String key : kvService.scanRange(keyStart, keyEnd).keySet()) {
kvService.delete(key);
log.debug("remove monitor data, key: {}", key);
records += 1;
}
return records;
}
/**
* get the latest timestamp of the store monitor data
*
* @param storeId
* @return timestamp(by seconds)
*/
public long getLatestStoreMonitorDataTimeStamp(long storeId) {
long maxId = 0L;
long current = System.currentTimeMillis() / 1000;
long start = current - this.pdConfig.getStore().getMonitorInterval();
String keyStart = getMonitorDataKey(storeId, start);
String keyEnd = getMonitorDataKey(storeId, current);
try {
for (String key : kvService.scanRange(keyStart, keyEnd).keySet()) {
String[] arr = key.split(String.valueOf(MetadataKeyHelper.getDelimiter()));
maxId = Math.max(maxId, Long.parseLong(arr[arr.length - 1]));
}
} catch (PDException e) {
}
return maxId;
}
private String getMonitorDataKey(long storeId, long ts) {
String builder = MONITOR_DATA_PREFIX +
MetadataKeyHelper.getDelimiter() +
storeId +
MetadataKeyHelper.getDelimiter() +
ts;
return builder;
}
private String extractMetricsFromStoreStatus(Metapb.StoreStats storeStats) {
List<String> list = new ArrayList<>();
for (Metapb.RecordPair pair : storeStats.getSystemMetricsList()) {
list.add("\"" + pair.getKey() + "\":" + pair.getValue());
}
return String.join(",", list);
}
}

View File

@ -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.pd;
import org.apache.hugegraph.pd.grpc.Metapb;
public interface StoreStatusListener {
void onStoreStatusChanged(Metapb.Store store, Metapb.StoreState old,
Metapb.StoreState status);
void onGraphChange(Metapb.Graph graph, Metapb.GraphState stateOld,
Metapb.GraphState stateNew);
void onStoreRaftChanged(Metapb.Store store);
}

View File

@ -0,0 +1,853 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.apache.hugegraph.pd;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.PriorityQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import org.apache.hugegraph.pd.common.KVPair;
import org.apache.hugegraph.pd.common.PDException;
import org.apache.hugegraph.pd.config.PDConfig;
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.pd.meta.TaskInfoMeta;
import org.apache.hugegraph.pd.raft.RaftEngine;
import lombok.extern.slf4j.Slf4j;
/**
* 任务调度服务定时检查Store资源分区的状态及时迁移数据错误节点
* 1监测Store是否离线
* 2监测Partition的副本是否正确
* 3监测Partition的工作模式是否正确
* 4监测Partition是否需要分裂,监测分裂是否完成
*/
@Slf4j
public class TaskScheduleService {
private static final String BALANCE_SHARD_KEY = "BALANCE_SHARD_KEY";
private final long TurnOffAndBalanceInterval = 30 * 60 * 1000; //机器下线30后才能进行动态平衡
private final long BalanceLeaderInterval = 30 * 1000; // leader平衡时间间隔
private final PDConfig pdConfig;
private final long clusterStartTime; //
private final StoreNodeService storeService;
private final PartitionService partitionService;
private final ScheduledExecutorService executor;
private final TaskInfoMeta taskInfoMeta;
private final StoreMonitorDataService storeMonitorDataService;
private final KvService kvService;
private final LogService logService;
// 先按照value排序再按照key排序
private final Comparator<KVPair<Long, Integer>> kvPairComparatorAsc = (o1, o2) -> {
if (o1.getValue() == o2.getValue()) {
return o1.getKey().compareTo(o2.getKey());
}
return o1.getValue().compareTo(o2.getValue());
};
// 先按照value排序(倒序)再按照key排序(升序
private final Comparator<KVPair<Long, Integer>> kvPairComparatorDesc = (o1, o2) -> {
if (o1.getValue() == o2.getValue()) {
return o2.getKey().compareTo(o1.getKey());
}
return o2.getValue().compareTo(o1.getValue());
};
private long lastStoreTurnoffTime = 0;
private long lastBalanceLeaderTime = 0;
public TaskScheduleService(PDConfig config, StoreNodeService storeService,
PartitionService partitionService) {
this.pdConfig = config;
this.storeService = storeService;
this.partitionService = partitionService;
this.taskInfoMeta = new TaskInfoMeta(config);
this.logService = new LogService(pdConfig);
this.storeMonitorDataService = new StoreMonitorDataService(pdConfig);
this.clusterStartTime = System.currentTimeMillis();
this.kvService = new KvService(pdConfig);
this.executor = new ScheduledThreadPoolExecutor(16);
}
public void init() {
executor.scheduleWithFixedDelay(() -> {
try {
patrolStores();
} catch (Throwable e) {
log.error("patrolStores exception: ", e);
}
}, 60, 60, TimeUnit.SECONDS);
executor.scheduleWithFixedDelay(() -> {
try {
patrolPartitions();
balancePartitionLeader(false);
balancePartitionShard();
} catch (Throwable e) {
log.error("patrolPartitions exception: ", e);
}
}, pdConfig.getPatrolInterval(), pdConfig.getPatrolInterval(), TimeUnit.SECONDS);
executor.scheduleWithFixedDelay(() -> {
if (isLeader()) {
kvService.clearTTLData();
}
}, 1000, 1000, TimeUnit.MILLISECONDS);
executor.scheduleWithFixedDelay(
() -> {
if (isLeader()) {
storeService.getQuotaChecker();
}
}, 2, 30,
TimeUnit.SECONDS);
// clean expired monitor data each 10 minutes, delay 3min.
if (isLeader() && this.pdConfig.getStore().isMonitorDataEnabled()) {
executor.scheduleAtFixedRate(() -> {
Long expTill = System.currentTimeMillis() / 1000 -
this.pdConfig.getStore().getRetentionPeriod();
log.debug("monitor data keys before " + expTill + " will be deleted");
int records = 0;
try {
for (Metapb.Store store : storeService.getStores()) {
int cnt =
this.storeMonitorDataService.removeExpiredMonitorData(store.getId(),
expTill);
log.debug("store id :{}, records:{}", store.getId(), cnt);
records += cnt;
}
} catch (PDException e) {
throw new RuntimeException(e);
}
log.debug(String.format("%d records has been deleted", records));
}, 180, 600, TimeUnit.SECONDS);
}
storeService.addStatusListener(new StoreStatusListener() {
@Override
public void onStoreStatusChanged(Metapb.Store store, Metapb.StoreState old,
Metapb.StoreState status) {
if (status == Metapb.StoreState.Tombstone) {
lastStoreTurnoffTime = System.currentTimeMillis();
}
if (status == Metapb.StoreState.Up) {
executor.schedule(() -> {
try { //store 上线后延时1分钟进行leader平衡
balancePartitionLeader(false);
} catch (PDException e) {
log.error("exception {}", e);
}
}, BalanceLeaderInterval, TimeUnit.MILLISECONDS);
}
}
@Override
public void onGraphChange(Metapb.Graph graph,
Metapb.GraphState stateOld,
Metapb.GraphState stateNew) {
}
@Override
public void onStoreRaftChanged(Metapb.Store store) {
}
});
}
public void shutDown() {
executor.shutdownNow();
}
private boolean isLeader() {
return RaftEngine.getInstance().isLeader();
}
/**
* 巡查所有的store检查是否在线存储空间是否充足
*/
public List<Metapb.Store> patrolStores() throws PDException {
if (!isLeader()) {
return null;
}
List<Metapb.Store> changedStores = new ArrayList<>();
// 检查store在线状态
List<Metapb.Store> stores = storeService.getStores("");
Map<Long, Metapb.Store> activeStores = storeService.getActiveStores("")
.stream().collect(
Collectors.toMap(Metapb.Store::getId, t -> t));
for (Metapb.Store store : stores) {
Metapb.Store changeStore = null;
if ((store.getState() == Metapb.StoreState.Up
|| store.getState() == Metapb.StoreState.Unknown)
&& !activeStores.containsKey(store.getId())) {
// 不在线修改状态为离线
changeStore = Metapb.Store.newBuilder(store)
.setState(Metapb.StoreState.Offline)
.build();
} else if ((store.getState() == Metapb.StoreState.Exiting &&
!activeStores.containsKey(store.getId())) ||
(store.getState() == Metapb.StoreState.Offline &&
(System.currentTimeMillis() - store.getLastHeartbeat() >
pdConfig.getStore().getMaxDownTime() * 1000) &&
(System.currentTimeMillis() - clusterStartTime >
pdConfig.getStore().getMaxDownTime() * 1000))) {
//手工修改为下线或者离线达到时长
// 修改状态为关机, 增加 checkStoreCanOffline 检测
if (storeService.checkStoreCanOffline(store)) {
changeStore = Metapb.Store.newBuilder(store)
.setState(Metapb.StoreState.Tombstone).build();
this.logService.insertLog(LogService.NODE_CHANGE,
LogService.TASK, changeStore);
log.info("patrolStores store {} Offline", changeStore.getId());
}
}
if (changeStore != null) {
storeService.updateStore(changeStore);
changedStores.add(changeStore);
}
}
return changedStores;
}
/**
* 巡查所有的分区检查副本数是否正确
*/
public List<Metapb.Partition> patrolPartitions() throws PDException {
if (!isLeader()) {
return null;
}
// 副本数不一致重新分配副本
for (Metapb.ShardGroup group : storeService.getShardGroups()) {
if (group.getShardsCount() != pdConfig.getPartition().getShardCount()) {
storeService.reallocShards(group);
// 避免后面的 balance partition shard 马上执行.
kvService.put(BALANCE_SHARD_KEY, "DOING", 180 * 1000);
}
}
//检查shard是否在线
Map<Long, Metapb.Store> tombStores = storeService.getTombStores().stream().collect(
Collectors.toMap(Metapb.Store::getId, t -> t));
var partIds = new HashSet<Integer>();
for (var pair : tombStores.entrySet()) {
for (var partition : partitionService.getPartitionByStore(pair.getValue())) {
if (partIds.contains(partition.getId())) {
continue;
}
partIds.add(partition.getId());
storeService.storeTurnoff(pair.getValue());
partitionService.shardOffline(partition, pair.getValue().getId());
}
}
return null;
}
/**
* 在Store之间平衡分区的数量
* 机器转为UP半小时后才能进行动态平衡
*/
public synchronized Map<Integer, KVPair<Long, Long>> balancePartitionShard() throws
PDException {
log.info("balancePartitions starting, isleader:{}", isLeader());
if (!isLeader()) {
return null;
}
if (System.currentTimeMillis() - lastStoreTurnoffTime < TurnOffAndBalanceInterval) {
return null;//机器下线半小时后才能进行动态平衡
}
int activeStores = storeService.getActiveStores().size();
if (activeStores == 0) {
log.warn("balancePartitionShard non active stores, skip to balancePartitionShard");
return null;
}
// 避免频繁调用. (当改变副本数需要调整shard list此时又需要平衡分区会发送重复的指令造成结果不可预料
// 严重会删除掉分区.
if (Objects.equals(kvService.get(BALANCE_SHARD_KEY), "DOING")) {
return null;
}
int totalShards = pdConfig.getConfigService().getPartitionCount() *
pdConfig.getPartition().getShardCount();
int averageCount = totalShards / activeStores;
int remainder = totalShards % activeStores;
// 统计每个store上分区, StoreId ->PartitionID, ShardRole
Map<Long, Map<Integer, Metapb.ShardRole>> partitionMap = new HashMap<>();
storeService.getActiveStores().forEach(store -> {
partitionMap.put(store.getId(), new HashMap<>());
});
// 如果是leaner 说明迁移正在进行不要重复提交任务
AtomicReference<Boolean> isLeaner = new AtomicReference<>(false);
partitionService.getPartitions().forEach(partition -> {
try {
storeService.getShardList(partition.getId()).forEach(shard -> {
Long storeId = shard.getStoreId();
// 判断每个shard为leaner或者状态非正常状态
if (shard.getRole() == Metapb.ShardRole.Learner
|| partition.getState() != Metapb.PartitionState.PState_Normal) {
isLeaner.set(true);
}
if (partitionMap.containsKey(storeId)) {
partitionMap.get(storeId).put(partition.getId(), shard.getRole());
}
});
} catch (PDException e) {
log.error("get partition {} shard list error:{}.", partition.getId(),
e.getMessage());
}
});
if (isLeaner.get()) {
log.warn("balancePartitionShard is doing, skip this balancePartitionShard task");
return null;
}
// 按照shard数量由高到低排序store
List<KVPair<Long, Integer>> sortedList = new ArrayList<>();
partitionMap.forEach((storeId, shards) -> {
sortedList.add(new KVPair(storeId, shards.size()));
});
// 由大到小排序的list
sortedList.sort(((o1, o2) -> o2.getValue().compareTo(o1.getValue())));
// 最大堆
PriorityQueue<KVPair<Long, Integer>> maxHeap = new PriorityQueue<>(sortedList.size(),
(o1, o2) -> o2.getValue()
.compareTo(
o1.getValue()));
// 各个副本的 committedIndex
Map<Integer, Map<Long, Long>> committedIndexMap = partitionService.getCommittedIndexStats();
// 分区ID --> 源StoreID,目标StoreID
Map<Integer, KVPair<Long, Long>> movedPartitions = new HashMap<>();
// 移除多余的shard, 按照shards由多到少的顺序遍历store余数remainder优先给shards多的store分配减少迁移的概率
for (int index = 0; index < sortedList.size(); index++) {
long storeId = sortedList.get(index).getKey();
if (!partitionMap.containsKey(storeId)) {
log.error("cannot found storeId {} in partitionMap", storeId);
return null;
}
Map<Integer, Metapb.ShardRole> shards = partitionMap.get(storeId);
int targetCount = index < remainder ? averageCount + 1 : averageCount;
// 移除多余的shard, 添加源StoreID. 非Leader并且该分区唯一
if (shards.size() > targetCount) {
int movedCount = shards.size() - targetCount;
log.info(
"balancePartitionShard storeId {}, shardsSize {}, targetCount {}, " +
"moveCount {}",
storeId, shards.size(), targetCount, movedCount);
for (Iterator<Integer> iterator = shards.keySet().iterator();
movedCount > 0 && iterator.hasNext(); ) {
Integer id = iterator.next();
if (!movedPartitions.containsKey(id)) {
log.info("store {}, shard of partition {} can be moved", storeId, id);
movedPartitions.put(id, new KVPair<>(storeId, 0L));
movedCount--;
}
}
} else if (shards.size() < targetCount) {
int addCount = targetCount - shards.size();
log.info(
"balancePartitionShard storeId {}, shardsSize {}, targetCount {}, " +
"addCount {}",
storeId, shards.size(), targetCount, addCount);
maxHeap.add(new KVPair<>(storeId, addCount));
}
}
if (movedPartitions.size() == 0) {
log.warn(
"movedPartitions is empty, totalShards:{} averageCount:{} remainder:{} " +
"sortedList:{}",
totalShards, averageCount, remainder, sortedList);
}
Iterator<Map.Entry<Integer, KVPair<Long, Long>>> moveIterator =
movedPartitions.entrySet().iterator();
while (moveIterator.hasNext()) {
if (maxHeap.size() == 0) {
break;
}
Map.Entry<Integer, KVPair<Long, Long>> moveEntry = moveIterator.next();
int partitionId = moveEntry.getKey();
long sourceStoreId = moveEntry.getValue().getKey();
List<KVPair<Long, Integer>> tmpList = new ArrayList<>(maxHeap.size());
while (maxHeap.size() > 0) {
KVPair<Long, Integer> pair = maxHeap.poll();
long destStoreId = pair.getKey();
boolean destContains = false;
if (partitionMap.containsKey(destStoreId)) {
destContains = partitionMap.get(destStoreId).containsKey(partitionId);
}
// 如果目的store已经包含了该partition则取一下store
if (!destContains) {
moveEntry.getValue().setValue(pair.getKey());
log.info(
"balancePartitionShard will move partition {} from store {} to store " +
"{}",
moveEntry.getKey(),
moveEntry.getValue().getKey(),
moveEntry.getValue().getValue());
if (pair.getValue() > 1) {
pair.setValue(pair.getValue() - 1);
tmpList.add(pair);
}
break;
}
tmpList.add(pair);
}
maxHeap.addAll(tmpList);
}
kvService.put(BALANCE_SHARD_KEY, "DOING", 180 * 1000);
// 开始迁移
movedPartitions.forEach((partId, storePair) -> {
// 源和目标storeID都不为0
if (storePair.getKey() > 0 && storePair.getValue() > 0) {
partitionService.movePartitionsShard(partId, storePair.getKey(),
storePair.getValue());
} else {
log.warn("balancePartitionShard key or value is zero, partId:{} storePair:{}",
partId, storePair);
}
});
return movedPartitions;
}
/**
* 在Store之间平衡分区的Leader的数量
*/
public synchronized Map<Integer, Long> balancePartitionLeader(boolean immediately) throws
PDException {
Map<Integer, Long> results = new HashMap<>();
if (!isLeader()) {
return results;
}
if (!immediately &&
System.currentTimeMillis() - lastBalanceLeaderTime < BalanceLeaderInterval) {
return results;
}
lastBalanceLeaderTime = System.currentTimeMillis();
List<Metapb.ShardGroup> shardGroups = storeService.getShardGroups();
// 分裂或者缩容任务的时候退出
var taskMeta = storeService.getTaskInfoMeta();
if (taskMeta.hasSplitTaskDoing() || taskMeta.hasMoveTaskDoing()) {
throw new PDException(1001, "split or combine task is processing, please try later!");
}
// 数据迁移的时候退出
if (Objects.equals(kvService.get(BALANCE_SHARD_KEY), "DOING")) {
throw new PDException(1001, "balance shard is processing, please try later!");
}
if (shardGroups.size() == 0) {
return results;
}
Map<Long, Integer> storeShardCount = new HashMap<>();
shardGroups.forEach(group -> {
group.getShardsList().forEach(shard -> {
storeShardCount.put(shard.getStoreId(),
storeShardCount.getOrDefault(shard.getStoreId(), 0) + 1);
});
});
log.info("balancePartitionLeader, shard group size: {}, by store: {}", shardGroups.size(),
storeShardCount);
// 按照 target count store id稳定排序
PriorityQueue<KVPair<Long, Integer>> targetCount =
new PriorityQueue<>(kvPairComparatorDesc);
var sortedGroups = storeShardCount.entrySet().stream()
.map(entry -> new KVPair<>(entry.getKey(),
entry.getValue()))
.sorted(kvPairComparatorAsc)
.collect(Collectors.toList());
int sum = 0;
for (int i = 0; i < sortedGroups.size() - 1; i++) {
// at least one
int v = Math.max(
sortedGroups.get(i).getValue() / pdConfig.getPartition().getShardCount(), 1);
targetCount.add(new KVPair<>(sortedGroups.get(i).getKey(), v));
sum += v;
}
// 最后一个, 除不尽的情况保证总数正确
targetCount.add(new KVPair<>(sortedGroups.get(sortedGroups.size() - 1).getKey(),
shardGroups.size() - sum));
log.info("target count: {}", targetCount);
for (var group : shardGroups) {
var map = group.getShardsList().stream()
.collect(Collectors.toMap(Metapb.Shard::getStoreId, shard -> shard));
var tmpList = new ArrayList<KVPair<Long, Integer>>();
// store比较多的情况可能不包含对应的store id. 则先将不符合的store保存到临时列表直到找到一个合适的store
while (!targetCount.isEmpty()) {
var pair = targetCount.poll();
var storeId = pair.getKey();
if (map.containsKey(storeId)) {
if (map.get(storeId).getRole() != Metapb.ShardRole.Leader) {
log.info("shard group{}, store id:{}, set to leader", group.getId(),
storeId);
partitionService.transferLeader(group.getId(), map.get(storeId));
results.put(group.getId(), storeId);
} else {
log.info("shard group {}, store id :{}, is leader, no need change",
group.getId(), storeId);
}
if (pair.getValue() > 1) {
// count -1
pair.setValue(pair.getValue() - 1);
tmpList.add(pair);
}
// 找到了则处理完成
break;
} else {
tmpList.add(pair);
}
}
targetCount.addAll(tmpList);
}
return results;
}
private long getMaxIndexGap(Map<Integer, Map<Long, Long>> committedIndexMap, int partitionId) {
long maxGap = Long.MAX_VALUE;
if (committedIndexMap == null || !committedIndexMap.containsKey(partitionId)) {
return maxGap;
}
Map<Long, Long> shardMap = committedIndexMap.get(partitionId);
if (shardMap == null || shardMap.size() == 0) {
return maxGap;
}
List<Long> sortedList = new ArrayList<>();
shardMap.forEach((storeId, committedIndex) -> {
sortedList.add(committedIndex);
});
// 由大到小排序的list
sortedList.sort(Comparator.reverseOrder());
maxGap = sortedList.get(0) - sortedList.get(sortedList.size() - 1);
return maxGap;
}
/**
* 执行分区分裂分为自动分裂和手工分裂
*
* @return
* @throws PDException
*/
public List<Metapb.Partition> splitPartition(
Pdpb.OperationMode mode, List<Pdpb.SplitDataParam> params) throws PDException {
if (mode == Pdpb.OperationMode.Auto) {
return autoSplitPartition();
}
var list = params.stream()
.map(param -> new KVPair<>(param.getPartitionId(), param.getCount()))
.collect(Collectors.toList());
storeService.splitShardGroups(list);
return null;
}
/**
* 自动进行分区分裂每个store达到最大分区数量
* 执行条件
* 分裂后每台机器分区数量少于partition.max-partitions-per-store
*
* @throws PDException
*/
public List<Metapb.Partition> autoSplitPartition() throws PDException {
if (!isLeader()) {
return null;
}
if (Metapb.ClusterState.Cluster_OK != storeService.getClusterStats().getState()) {
if (Metapb.ClusterState.Cluster_Offline == storeService.getClusterStats().getState()) {
throw new PDException(Pdpb.ErrorType.Split_Partition_Doing_VALUE,
"The data is splitting");
} else {
throw new PDException(Pdpb.ErrorType.Cluster_State_Forbid_Splitting_VALUE,
"The current state of the cluster prohibits splitting data");
}
}
//For TEST
// pdConfig.getPartition().setMaxShardsPerStore(pdConfig.getPartition()
// .getMaxShardsPerStore()*2);
// 计算集群能能支持的最大split count
int splitCount = pdConfig.getPartition().getMaxShardsPerStore() *
storeService.getActiveStores().size() /
(storeService.getShardGroups().size() *
pdConfig.getPartition().getShardCount());
if (splitCount < 2) {
throw new PDException(Pdpb.ErrorType.Too_Many_Partitions_Per_Store_VALUE,
"Too many partitions per store, partition.store-max-shard-count" +
" = "
+ pdConfig.getPartition().getMaxShardsPerStore());
}
// 每store未达最大分区数进行分裂
log.info("Start to split partitions..., split count = {}", splitCount);
// 设置集群状态为下线
storeService.updateClusterStatus(Metapb.ClusterState.Cluster_Offline);
// 修改默认分区数量
// pdConfig.getConfigService().setPartitionCount(storeService.getShardGroups().size() *
// splitCount);
var list = storeService.getShardGroups().stream()
.map(shardGroup -> new KVPair<>(shardGroup.getId(), splitCount))
.collect(Collectors.toList());
storeService.splitShardGroups(list);
return null;
}
/**
* Store汇报任务状态
* 分区状态发生改变重新计算分区所在的ShardGroup图和整个集群的状态
*
* @param task
*/
public void reportTask(MetaTask.Task task) {
try {
switch (task.getType()) {
case Split_Partition:
partitionService.handleSplitTask(task);
break;
case Move_Partition:
partitionService.handleMoveTask(task);
break;
case Clean_Partition:
partitionService.handleCleanPartitionTask(task);
break;
default:
break;
}
} catch (Exception e) {
log.error("Report task exception {}, {}", e, task);
}
}
/**
* 对rocksdb进行compaction
*
* @throws PDException
*/
public Boolean dbCompaction(String tableName) throws PDException {
if (!isLeader()) {
return false;
}
for (Metapb.ShardGroup shardGroup : storeService.getShardGroups()) {
storeService.shardGroupsDbCompaction(shardGroup.getId(), tableName);
}
//
return true;
}
/**
* 判断是否能把一个store的分区全部迁出给出判断结果和迁移方案
*/
public Map<String, Object> canAllPartitionsMovedOut(Metapb.Store sourceStore) throws
PDException {
if (!isLeader()) {
return null;
}
// 分析一个store上面的分区是否可以完全迁出
Map<String, Object> resultMap = new HashMap<>();
// 定义对象用于保存源store上面的分区 StoreId ->PartitionID, ShardRole
Map<Long, Map<Integer, Metapb.ShardRole>> sourcePartitionMap = new HashMap<>();
sourcePartitionMap.put(sourceStore.getId(), new HashMap<>());
// 定义对象用于保存其他活跃store上面的分区 StoreId ->PartitionID, ShardRole
Map<Long, Map<Integer, Metapb.ShardRole>> otherPartitionMap = new HashMap<>();
Map<Long, Long> availableDiskSpace = new HashMap<>(); // 每个store剩余的磁盘空间
Map<Integer, Long> partitionDataSize = new HashMap<>(); // 记录待迁移的分区的数据量
storeService.getActiveStores().forEach(store -> {
if (store.getId() != sourceStore.getId()) {
otherPartitionMap.put(store.getId(), new HashMap<>());
// 记录其他store的剩余的磁盘空间, 单位为Byte
availableDiskSpace.put(store.getId(), store.getStats().getAvailable());
} else {
resultMap.put("current_store_is_online", true);
}
});
// 统计待迁移的分区的数据大小 (从storeStats中统计单位为KB)
for (Metapb.GraphStats graphStats : sourceStore.getStats().getGraphStatsList()) {
partitionDataSize.put(graphStats.getPartitionId(),
partitionDataSize.getOrDefault(graphStats.getPartitionId(), 0L)
+ graphStats.getApproximateSize());
}
// 给sourcePartitionMap otherPartitionMap赋值
partitionService.getPartitions().forEach(partition -> {
try {
storeService.getShardList(partition.getId()).forEach(shard -> {
long storeId = shard.getStoreId();
if (storeId == sourceStore.getId()) {
sourcePartitionMap.get(storeId).put(partition.getId(), shard.getRole());
} else {
if (otherPartitionMap.containsKey(storeId)) {
otherPartitionMap.get(storeId).put(partition.getId(), shard.getRole());
}
}
});
} catch (PDException e) {
throw new RuntimeException(e);
}
});
// 统计待移除的分区即源store上面的所有分区
Map<Integer, KVPair<Long, Long>> movedPartitions = new HashMap<>();
for (Map.Entry<Integer, Metapb.ShardRole> entry : sourcePartitionMap.get(
sourceStore.getId()).entrySet()) {
movedPartitions.put(entry.getKey(), new KVPair<>(sourceStore.getId(), 0L));
}
// 统计其他store的分区数量, 用小顶堆保存以便始终把分区数量较少的store优先考虑
PriorityQueue<KVPair<Long, Integer>> minHeap = new PriorityQueue<>(otherPartitionMap.size(),
(o1, o2) -> o1.getValue()
.compareTo(
o2.getValue()));
otherPartitionMap.forEach((storeId, shards) -> {
minHeap.add(new KVPair(storeId, shards.size()));
});
// 遍历待迁移的分区,优先迁移到分区比较少的store
Iterator<Map.Entry<Integer, KVPair<Long, Long>>> moveIterator =
movedPartitions.entrySet().iterator();
while (moveIterator.hasNext()) {
Map.Entry<Integer, KVPair<Long, Long>> moveEntry = moveIterator.next();
int partitionId = moveEntry.getKey();
List<KVPair<Long, Integer>> tmpList = new ArrayList<>(); // 记录已经弹出优先队列的元素
while (minHeap.size() > 0) {
KVPair<Long, Integer> pair = minHeap.poll(); //弹出首个元素
long storeId = pair.getKey();
int partitionCount = pair.getValue();
Map<Integer, Metapb.ShardRole> shards = otherPartitionMap.get(storeId);
final int unitRate = 1024; // 平衡不同存储单位的进率
if ((!shards.containsKey(partitionId)) && (
availableDiskSpace.getOrDefault(storeId, 0L) / unitRate >=
partitionDataSize.getOrDefault(partitionId, 0L))) {
// 如果目标store上面不包含该分区且目标store剩余空间能容纳该分区则进行迁移
moveEntry.getValue().setValue(storeId); //设置移动的目标store
log.info("plan to move partition {} to store {}, " +
"available disk space {}, current partitionSize:{}",
partitionId,
storeId,
availableDiskSpace.getOrDefault(storeId, 0L) / unitRate,
partitionDataSize.getOrDefault(partitionId, 0L)
);
// 更新该store预期的剩余空间
availableDiskSpace.put(storeId, availableDiskSpace.getOrDefault(storeId, 0L)
- partitionDataSize.getOrDefault(partitionId,
0L) *
unitRate);
// 更新统计变量中该store的分区数量
partitionCount += 1;
pair.setValue(partitionCount);
tmpList.add(pair);
break;
} else {
tmpList.add(pair);
}
}
minHeap.addAll(tmpList);
}
//检查是否未存在未分配目标store的分区
List<Integer> remainPartitions = new ArrayList<>();
movedPartitions.forEach((partId, storePair) -> {
if (storePair.getValue() == 0L) {
remainPartitions.add(partId);
}
});
if (remainPartitions.size() > 0) {
resultMap.put("flag", false);
resultMap.put("movedPartitions", null);
} else {
resultMap.put("flag", true);
resultMap.put("movedPartitions", movedPartitions);
}
return resultMap;
}
public Map<Integer, KVPair<Long, Long>> movePartitions(
Map<Integer, KVPair<Long, Long>> movedPartitions) {
if (!isLeader()) {
return null;
}
// 开始迁移
log.info("begin move partitions:");
movedPartitions.forEach((partId, storePair) -> {
// 源和目标storeID都不为0
if (storePair.getKey() > 0 && storePair.getValue() > 0) {
partitionService.movePartitionsShard(partId, storePair.getKey(),
storePair.getValue());
}
});
return movedPartitions;
}
}

View File

@ -0,0 +1,277 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.apache.hugegraph.pd.config;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.hugegraph.pd.ConfigService;
import org.apache.hugegraph.pd.IdService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import lombok.Data;
/**
* PD配置文件
*/
@Data
@Component
public class PDConfig {
@Value("${pd.cluster_id:1}")
private long clusterId; // 集群ID
@Value("${pd.patrol-interval:300}")
private long patrolInterval = 300; //巡查任务时间间隔
@Value("${pd.data-path}")
private String dataPath;
@Value("${pd.initial-store-count:3}")
private int minStoreCount;
// 初始store列表该列表内的store自动激活
@Value("${pd.initial-store-list: ''}")
private String initialStoreList;
@Value("${grpc.host}")
private String host;
@Value("${license.verify-path}")
private String verifyPath;
@Value("${license.license-path}")
private String licensePath;
@Autowired
private ThreadPoolGrpc threadPoolGrpc;
@Autowired
private Raft raft;
@Autowired
private Store store;
@Autowired
private Partition partition;
@Autowired
private Discovery discovery;
private Map<String, String> initialStoreMap = null;
private ConfigService configService;
private IdService idService;
public Map<String, String> getInitialStoreMap() {
if (initialStoreMap == null) {
initialStoreMap = new HashMap<>();
Arrays.asList(initialStoreList.split(",")).forEach(s -> {
initialStoreMap.put(s, s);
});
}
return initialStoreMap;
}
/**
* 初始分区数量
* Store数量 * 每Store最大副本数 /每分区副本数
*
* @return
*/
public int getInitialPartitionCount() {
return getInitialStoreMap().size() * partition.getMaxShardsPerStore()
/ partition.getShardCount();
}
public ConfigService getConfigService() {
return configService;
}
public void setConfigService(ConfigService configService) {
this.configService = configService;
}
public IdService getIdService() {
return idService;
}
public void setIdService(IdService idService) {
this.idService = idService;
}
@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 Raft {
@Value("${raft.enable:true }")
private boolean enable;
@Value("${raft.address}")
private String address;
@Value("${pd.data-path}")
private String dataPath;
@Value("${raft.peers-list}")
private String peersList;
@Value("${raft.snapshotInterval: 300}")
private int snapshotInterval;
@Value("${raft.rpc-timeout:10000}")
private int rpcTimeout;
@Value("${grpc.host}")
private String host;
@Value("${server.port}")
private int port;
@Value("${pd.cluster_id:1}")
private long clusterId; // 集群ID
@Value("${grpc.port}")
private int grpcPort;
public String getGrpcAddress() {
return host + ":" + grpcPort;
}
}
@Data
@Configuration
public class Store {
// store 心跳超时时间
@Value("${store.keepAlive-timeout:300}")
private long keepAliveTimeout = 300;
@Value("${store.max-down-time:1800}")
private long maxDownTime = 1800;
@Value("${store.monitor_data_enabled:true}")
private boolean monitorDataEnabled = true;
@Value("${store.monitor_data_interval: 1 minute}")
private String monitorDataInterval = "1 minute";
@Value("${store.monitor_data_retention: 1 day}")
private String monitorDataRetention = "1 day";
/**
* interval -> seconds.
* minimum value is 1 seconds.
*
* @return the seconds of the interval
*/
public Long getMonitorInterval() {
return parseTimeExpression(this.monitorDataInterval);
}
/**
* the monitor data that saved in rocksdb, will be deleted
* out of period
*
* @return the period of the monitor data should keep
*/
public Long getRetentionPeriod() {
return parseTimeExpression(this.monitorDataRetention);
}
/**
* parse time expression , support pattern:
* [1-9][ ](second, minute, hour, day, month, year)
* unit could not be null, the number part is 1 by default.
*
* @param exp
* @return seconds value of the expression. 1 will return by illegal expression
*/
private Long parseTimeExpression(String exp) {
if (exp != null) {
Pattern pattern = Pattern.compile(
"(?<n>(\\d+)*)(\\s)*(?<unit>(second|minute|hour|day|month|year)$)");
Matcher matcher = pattern.matcher(exp.trim());
if (matcher.find()) {
String n = matcher.group("n");
String unit = matcher.group("unit");
if (null == n || n.length() == 0) {
n = "1";
}
Long interval;
switch (unit) {
case "minute":
interval = 60L;
break;
case "hour":
interval = 3600L;
break;
case "day":
interval = 86400L;
break;
case "month":
interval = 86400L * 30;
break;
case "year":
interval = 86400L * 365;
break;
case "second":
default:
interval = 1L;
}
// avoid n == '0'
return Math.max(1L, interval * Integer.parseInt(n));
}
}
return 1L;
}
}
@Data
@Configuration
public class Partition {
private int totalCount = 0;
// 每个Store最大副本数
@Value("${partition.store-max-shard-count:24}")
private int maxShardsPerStore = 24;
// 默认分副本数量
@Value("${partition.default-shard-count:3}")
private int shardCount = 3;
public int getTotalCount() {
if (totalCount == 0) {
totalCount = getInitialPartitionCount();
}
return totalCount;
}
public void setTotalCount(int totalCount) {
this.totalCount = totalCount;
}
}
@Data
@Configuration
public class Discovery {
// 客户端注册后无心跳最长次数超过后之前的注册信息会被删除
@Value("${discovery.heartbeat-try-count:3}")
private int heartbeatOutTimes = 3;
}
}

View File

@ -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.pd.meta;
import java.util.List;
import java.util.Optional;
import org.apache.hugegraph.pd.common.PDException;
import org.apache.hugegraph.pd.config.PDConfig;
import org.apache.hugegraph.pd.grpc.Metapb;
public class ConfigMetaStore extends MetadataRocksDBStore {
private final long clusterId;
public ConfigMetaStore(PDConfig pdConfig) {
super(pdConfig);
this.clusterId = pdConfig.getClusterId();
}
/**
* 更新图空间存储状态信息
*
* @param
*/
public Metapb.GraphSpace setGraphSpace(Metapb.GraphSpace graphSpace) throws PDException {
byte[] graphSpaceKey = MetadataKeyHelper.getGraphSpaceKey(graphSpace.getName());
graphSpace = graphSpace.toBuilder().setTimestamp(System.currentTimeMillis()).build();
put(graphSpaceKey, graphSpace.toByteArray());
return graphSpace;
}
public List<Metapb.GraphSpace> getGraphSpace(String graphSpace) throws PDException {
byte[] graphSpaceKey = MetadataKeyHelper.getGraphSpaceKey(graphSpace);
return scanPrefix(Metapb.GraphSpace.parser(), graphSpaceKey);
}
public Metapb.PDConfig setPdConfig(Metapb.PDConfig pdConfig) throws PDException {
byte[] graphSpaceKey =
MetadataKeyHelper.getPdConfigKey(String.valueOf(pdConfig.getVersion()));
Metapb.PDConfig config = Metapb.PDConfig.newBuilder(
pdConfig).setTimestamp(System.currentTimeMillis()).build();
put(graphSpaceKey, config.toByteArray());
return config;
}
public Metapb.PDConfig getPdConfig(long version) throws PDException {
byte[] graphSpaceKey = MetadataKeyHelper.getPdConfigKey(version <= 0 ? null :
String.valueOf(version));
Optional<Metapb.PDConfig> max = scanPrefix(
Metapb.PDConfig.parser(), graphSpaceKey).stream().max(
(o1, o2) -> (o1.getVersion() > o2.getVersion()) ? 1 : -1);
return max.isPresent() ? max.get() : null;
}
}

View File

@ -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.pd.meta;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.apache.hugegraph.pd.common.PDException;
import org.apache.hugegraph.pd.config.PDConfig;
import org.apache.hugegraph.pd.grpc.discovery.NodeInfo;
import org.apache.hugegraph.pd.grpc.discovery.NodeInfos;
import org.apache.hugegraph.pd.grpc.discovery.Query;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class DiscoveryMetaStore extends MetadataRocksDBStore {
/**
* appName --> address --> registryInfo
*/
private static final String PREFIX = "REGIS-";
private static final String SPLITTER = "-";
public DiscoveryMetaStore(PDConfig pdConfig) {
super(pdConfig);
}
public void register(NodeInfo nodeInfo, int outTimes) throws PDException {
putWithTTL(toKey(nodeInfo.getAppName(), nodeInfo.getVersion(), nodeInfo.getAddress()),
nodeInfo.toByteArray(), (nodeInfo.getInterval() / 1000) * outTimes);
}
byte[] toKey(String appName, String version, String address) {
StringBuilder builder = getPrefixBuilder(appName, version);
builder.append(SPLITTER);
builder.append(address);
return builder.toString().getBytes();
}
private StringBuilder getPrefixBuilder(String appName, String version) {
StringBuilder builder = new StringBuilder();
builder.append(PREFIX);
if (!StringUtils.isEmpty(appName)) {
builder.append(appName);
builder.append(SPLITTER);
}
if (!StringUtils.isEmpty(version)) {
builder.append(version);
}
return builder;
}
public NodeInfos getNodes(Query query) {
List<NodeInfo> nodeInfos = null;
try {
StringBuilder builder = getPrefixBuilder(query.getAppName(),
query.getVersion());
nodeInfos = getInstanceListWithTTL(
NodeInfo.parser(),
builder.toString().getBytes());
builder.setLength(0);
} catch (PDException e) {
log.error("An error occurred getting data from the store,{}", e);
}
if (query.getLabelsMap() != null && !query.getLabelsMap().isEmpty()) {
List result = new LinkedList<NodeInfo>();
for (NodeInfo node : nodeInfos) {
if (labelMatch(node, query)) {
result.add(node);
}
}
return NodeInfos.newBuilder().addAllInfo(result).build();
}
return NodeInfos.newBuilder().addAllInfo(nodeInfos).build();
}
private boolean labelMatch(NodeInfo node, Query query) {
Map<String, String> labelsMap = node.getLabelsMap();
for (Map.Entry<String, String> entry : query.getLabelsMap().entrySet()) {
if (!entry.getValue().equals(labelsMap.get(entry.getKey()))) {
return false;
}
}
return true;
}
}

View File

@ -0,0 +1,255 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.apache.hugegraph.pd.meta;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.hugegraph.pd.common.PDException;
import org.apache.hugegraph.pd.config.PDConfig;
import org.apache.hugegraph.pd.store.KV;
import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import lombok.extern.slf4j.Slf4j;
/**
* 自增id的实现类
*/
@Slf4j
public class IdMetaStore extends MetadataRocksDBStore {
private static final String ID_PREFIX = "@ID@";
private static final String CID_PREFIX = "@CID@";
private static final String CID_SLOT_PREFIX = "@CID_SLOT@";
private static final String CID_DEL_SLOT_PREFIX = "@CID_DEL_SLOT@";
private static final String SEPARATOR = "@";
private static final ConcurrentHashMap<String, Object> SEQUENCES = new ConcurrentHashMap<>();
public static long CID_DEL_TIMEOUT = 24 * 3600 * 1000;
private final long clusterId;
public IdMetaStore(PDConfig pdConfig) {
super(pdConfig);
this.clusterId = pdConfig.getClusterId();
}
public static long bytesToLong(byte[] b) {
ByteBuffer buf = ByteBuffer.wrap(b);
return buf.getLong();
}
public static byte[] longToBytes(long l) {
ByteBuffer buf = ByteBuffer.wrap(new byte[Long.SIZE]);
buf.putLong(l);
buf.flip();
return buf.array();
}
/**
* 获取自增id
*
* @param key
* @param delta
* @return
* @throws PDException
*/
public long getId(String key, int delta) throws PDException {
Object probableLock = getLock(key);
byte[] keyBs = (ID_PREFIX + key).getBytes(Charset.defaultCharset());
synchronized (probableLock) {
byte[] bs = getOne(keyBs);
long current = bs != null ? bytesToLong(bs) : 0L;
long next = current + delta;
put(keyBs, longToBytes(next));
return current;
}
}
private Object getLock(String key) {
Object probableLock = new Object();
Object currentLock = SEQUENCES.putIfAbsent(key, probableLock);
if (currentLock != null) {
probableLock = currentLock;
}
return probableLock;
}
public void resetId(String key) throws PDException {
Object probableLock = new Object();
Object currentLock = SEQUENCES.putIfAbsent(key, probableLock);
if (currentLock != null) {
probableLock = currentLock;
}
byte[] keyBs = (ID_PREFIX + key).getBytes(Charset.defaultCharset());
synchronized (probableLock) {
removeByPrefix(keyBs);
}
}
/**
* 在删除name标识的cid的24小时内重复申请同一个name的cid保持同一值
* 如此设计为了防止缓存的不一致造成数据错误
*
* @param key
* @param name cid 标识
* @param max
* @return
* @throws PDException
*/
public long getCId(String key, String name, long max) throws PDException {
// 检测是否有过期的cid删除图的频率比较低此处对性能影响不大
byte[] delKeyPrefix = (CID_DEL_SLOT_PREFIX +
key + SEPARATOR).getBytes(Charset.defaultCharset());
synchronized (this) {
scanPrefix(delKeyPrefix).forEach(kv -> {
long[] value = (long[]) deserialize(kv.getValue());
if (value.length >= 2) {
if (System.currentTimeMillis() - value[1] > CID_DEL_TIMEOUT) {
try {
delCId(key, value[0]);
remove(kv.getKey());
} catch (Exception e) {
log.error("Exception ", e);
}
}
}
});
// 从延时删除队列恢复Key
byte[] cidDelayKey = getCIDDelayKey(key, name);
byte[] value = getOne(cidDelayKey);
if (value != null) {
// 从延迟删除队列删除
remove(cidDelayKey);
return ((long[]) deserialize(value))[0];
} else {
return getCId(key, max);
}
}
}
/**
* 添加到删除队列延后删除
*/
public long delCIdDelay(String key, String name, long cid) throws PDException {
byte[] delKey = getCIDDelayKey(key, name);
put(delKey, serialize(new long[]{cid, System.currentTimeMillis()}));
return cid;
}
/**
* 获取自增循环不重复id, 达到上限后从0开始自增
*
* @param key
* @param max id上限达到该值后重新从0开始自增
* @return
* @throws PDException
*/
public long getCId(String key, long max) throws PDException {
Object probableLock = getLock(key);
byte[] keyBs = (CID_PREFIX + key).getBytes(Charset.defaultCharset());
synchronized (probableLock) {
byte[] bs = getOne(keyBs);
long current = bs != null ? bytesToLong(bs) : 0L;
long last = current == 0 ? max - 1 : current - 1;
{ // 查找一个未使用的cid
List<KV> kvs = scanRange(genCIDSlotKey(key, current), genCIDSlotKey(key, max));
for (KV kv : kvs) {
if (current == bytesToLong(kv.getValue())) {
current++;
} else {
break;
}
}
}
if (current == max) {
current = 0;
List<KV> kvs = scanRange(genCIDSlotKey(key, current), genCIDSlotKey(key, last));
for (KV kv : kvs) {
if (current == bytesToLong(kv.getValue())) {
current++;
} else {
break;
}
}
}
if (current == last) {
return -1;
}
put(genCIDSlotKey(key, current), longToBytes(current));
put(keyBs, longToBytes(current + 1));
return current;
}
}
private byte[] genCIDSlotKey(String key, long value) {
byte[] keySlot = (CID_SLOT_PREFIX + key + SEPARATOR).getBytes(Charset.defaultCharset());
ByteBuffer buf = ByteBuffer.allocate(keySlot.length + Long.SIZE);
buf.put(keySlot);
buf.put(longToBytes(value));
return buf.array();
}
private byte[] getCIDDelayKey(String key, String name) {
byte[] bsKey = (CID_DEL_SLOT_PREFIX +
key + SEPARATOR +
name).getBytes(Charset.defaultCharset());
return bsKey;
}
/**
* 删除一个循环id释放id值
*
* @param key
* @param cid
* @return
* @throws PDException
*/
public long delCId(String key, long cid) throws PDException {
return remove(genCIDSlotKey(key, cid));
}
private byte[] serialize(Object obj) {
try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
Hessian2Output output = new Hessian2Output(bos);
output.writeObject(obj);
output.flush();
return bos.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private Object deserialize(byte[] bytes) {
try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes)) {
Hessian2Input input = new Hessian2Input(bis);
Object obj = input.readObject();
input.close();
return obj;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -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.pd.meta;
import java.util.List;
import org.apache.hugegraph.pd.common.PDException;
import org.apache.hugegraph.pd.config.PDConfig;
import org.apache.hugegraph.pd.grpc.Metapb;
public class LogMeta extends MetadataRocksDBStore {
private final PDConfig pdConfig;
public LogMeta(PDConfig pdConfig) {
super(pdConfig);
this.pdConfig = pdConfig;
}
public void insertLog(Metapb.LogRecord record) throws PDException {
byte[] storeLogKey = MetadataKeyHelper.getLogKey(record);
put(storeLogKey, record.toByteArray());
}
public List<Metapb.LogRecord> getLog(String action, Long start, Long end) throws PDException {
byte[] keyStart = MetadataKeyHelper.getLogKeyPrefix(action, start);
byte[] keyEnd = MetadataKeyHelper.getLogKeyPrefix(action, end);
List<Metapb.LogRecord> stores = this.scanRange(Metapb.LogRecord.parser(),
keyStart, keyEnd);
return stores;
}
}

View File

@ -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.pd.meta;
import org.apache.hugegraph.pd.config.PDConfig;
import org.apache.hugegraph.pd.raft.RaftEngine;
import org.apache.hugegraph.pd.store.HgKVStore;
import org.apache.hugegraph.pd.store.HgKVStoreImpl;
import org.apache.hugegraph.pd.store.RaftKVStore;
/**
* 存储工厂类创建相关对象的存储类
*/
public class MetadataFactory {
private static HgKVStore store = null;
public static HgKVStore getStore(PDConfig pdConfig) {
if (store == null) {
synchronized (MetadataFactory.class) {
if (store == null) {
HgKVStore proto = new HgKVStoreImpl();
//proto.init(pdConfig);
store = pdConfig.getRaft().isEnable() ?
new RaftKVStore(RaftEngine.getInstance(), proto) :
proto;
store.init(pdConfig);
}
}
}
return store;
}
public static void closeStore() {
if (store != null) {
store.close();
}
}
public static StoreInfoMeta newStoreInfoMeta(PDConfig pdConfig) {
return new StoreInfoMeta(pdConfig);
}
public static PartitionMeta newPartitionMeta(PDConfig pdConfig) {
return new PartitionMeta(pdConfig);
}
public static IdMetaStore newHugeServerMeta(PDConfig pdConfig) {
return new IdMetaStore(pdConfig);
}
public static DiscoveryMetaStore newDiscoveryMeta(PDConfig pdConfig) {
return new DiscoveryMetaStore(pdConfig);
}
public static ConfigMetaStore newConfigMeta(PDConfig pdConfig) {
return new ConfigMetaStore(pdConfig);
}
public static TaskInfoMeta newTaskInfoMeta(PDConfig pdConfig) {
return new TaskInfoMeta(pdConfig);
}
public static QueueStore newQueueStore(PDConfig pdConfig) {
return new QueueStore(pdConfig);
}
public static LogMeta newLogMeta(PDConfig pdConfig) {
return new LogMeta(pdConfig);
}
}

View File

@ -0,0 +1,378 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.apache.hugegraph.pd.meta;
import java.nio.charset.Charset;
import org.apache.commons.lang3.StringUtils;
import org.apache.hugegraph.pd.grpc.Metapb;
public class MetadataKeyHelper {
public static final char DELIMITER = '/';
private static final String STORE = "STORE";
private static final String ACTIVESTORE = "ACTIVESTORE";
private static final String STORESTATUS = "STORESTATUS";
private static final String PARTITION = "PARTITION";
private static final String PARTITION_V36 = "PARTITION_V36";
private static final String SHARDGROUP = "SHARDGROUP";
private static final String PARTITION_STATUS = "PARTITION_STATUS";
private static final String GRAPH = "GRAPH";
private static final String GRAPHMETA = "GRAPHMETA";
private static final String GRAPH_SPACE = "GRAPH_SPACE";
private static final String PD_CONFIG = "PD_CONFIG";
private static final String TASK_SPLIT = "TASK_SPLIT";
private static final String TASK_MOVE = "TASK_MOVE";
private static final String LOG_RECORD = "LOG_RECORD";
private static final String QUEUE = "QUEUE";
public static byte[] getStoreInfoKey(final long storeId) {
//STORE/{storeId}
String key = StringBuilderHelper.get()
.append(STORE).append(DELIMITER)
.append(storeId)
.toString();
return key.getBytes(Charset.defaultCharset());
}
public static byte[] getActiveStoreKey(final long storeId) {
//ACTIVESTORE/{storeId}
String key = StringBuilderHelper.get()
.append(ACTIVESTORE).append(DELIMITER)
.append(storeId)
.toString();
return key.getBytes(Charset.defaultCharset());
}
public static byte[] getActiveStorePrefix() {
//ACTIVESTORE
String key = StringBuilderHelper.get()
.append(ACTIVESTORE).append(DELIMITER)
.toString();
return key.getBytes(Charset.defaultCharset());
}
public static byte[] getStorePrefix() {
//STORE
String key = StringBuilderHelper.get()
.append(STORE).append(DELIMITER)
.toString();
return key.getBytes(Charset.defaultCharset());
}
public static byte[] getStoreStatusKey(final long storeId) {
//STORESTATUS/{storeId}
String key = StringBuilderHelper.get()
.append(STORESTATUS).append(DELIMITER)
.append(storeId)
.toString();
return key.getBytes(Charset.defaultCharset());
}
public static byte[] getShardGroupKey(final long groupId) {
//SHARDGROUP/{storeId}
String key = StringBuilderHelper.get()
.append(SHARDGROUP).append(DELIMITER)
.append(groupId)
.toString();
return key.getBytes(Charset.defaultCharset());
}
public static byte[] getShardGroupPrefix() {
//SHARDGROUP
String key = StringBuilderHelper.get()
.append(SHARDGROUP).append(DELIMITER)
.toString();
return key.getBytes(Charset.defaultCharset());
}
public static byte[] getPartitionKey(final String graphName, final int partId) {
//GRAPH/{graphName}/Partition/{partId}
String key = StringBuilderHelper.get()
.append(GRAPH).append(DELIMITER)
.append(graphName).append(DELIMITER)
.append(PARTITION).append(DELIMITER)
.append(partId)
.toString();
return key.getBytes(Charset.defaultCharset());
}
public static byte[] getPartitionV36Key(final String graphName, final int partId) {
// GRAPH/{graphName}/PartitionV36/{partId}
String key = StringBuilderHelper.get()
.append(GRAPH).append(DELIMITER)
.append(graphName).append(DELIMITER)
.append(PARTITION_V36).append(DELIMITER)
.append(partId)
.toString();
return key.getBytes(Charset.defaultCharset());
}
public static byte[] getPartitionPrefix(final String graphName) {
//GRAPH/{graph}/Partition
String key = StringBuilderHelper.get()
.append(GRAPH).append(DELIMITER)
.append(graphName).append(DELIMITER)
.append(PARTITION).append(DELIMITER)
.toString();
return key.getBytes(Charset.defaultCharset());
}
public static byte[] getShardKey(final long storeId, final int partId) {
//SHARD/{graphName}/{type}
String key = StringBuilderHelper.get()
.append(SHARDGROUP).append(DELIMITER)
.append(storeId).append(DELIMITER)
.append(partId)
.toString();
return key.getBytes(Charset.defaultCharset());
}
public static byte[] getShardPrefix(final long storeId) {
//SHARD/{graphName}/{type}
String key = StringBuilderHelper.get()
.append(SHARDGROUP).append(DELIMITER)
.append(storeId).append(DELIMITER)
.toString();
return key.getBytes(Charset.defaultCharset());
}
public static byte[] getGraphKey(final String graphName) {
//GRAPHMETA/{graphName}
String key = StringBuilderHelper.get()
.append(GRAPHMETA).append(DELIMITER)
.append(graphName).append(DELIMITER)
.toString();
return key.getBytes(Charset.defaultCharset());
}
public static byte[] getGraphPrefix() {
//GRAPHMETA/{
String key = StringBuilderHelper.get()
.append(GRAPHMETA).append(DELIMITER)
.toString();
return key.getBytes(Charset.defaultCharset());
}
public static byte[] getPartitionStatusKey(String graphName, int id) {
//PARTITION_STATUS/{
String key = StringBuilderHelper.get()
.append(PARTITION_STATUS)
.append(DELIMITER)
.append(graphName).append(DELIMITER)
.append(id).append(DELIMITER)
.toString();
return key.getBytes(Charset.defaultCharset());
}
public static byte[] getPartitionStatusPrefixKey(String graphName) {
//PARTITION_STATUS/{
StringBuilder builder = StringBuilderHelper.get().append(PARTITION_STATUS)
.append(DELIMITER);
if (!StringUtils.isEmpty(graphName)) {
builder.append(graphName).append(DELIMITER);
}
return builder.toString().getBytes(Charset.defaultCharset());
}
public static byte[] getGraphSpaceKey(String graphSpace) {
//GRAPH_SPACE/{
StringBuilder builder = StringBuilderHelper.get().append(
GRAPH_SPACE).append(DELIMITER);
if (!StringUtils.isEmpty(graphSpace)) {
builder.append(graphSpace).append(DELIMITER);
}
return builder.toString().getBytes(Charset.defaultCharset());
}
public static byte[] getPdConfigKey(String configKey) {
//PD_CONFIG/{
StringBuilder builder = StringBuilderHelper.get().append(
PD_CONFIG).append(DELIMITER);
if (!StringUtils.isEmpty(configKey)) {
builder.append(configKey).append(DELIMITER);
}
return builder.toString().getBytes(Charset.defaultCharset());
}
public static byte[] getQueueItemPrefix() {
//QUEUE
String key = StringBuilderHelper.get()
.append(QUEUE).append(DELIMITER)
.toString();
return key.getBytes(Charset.defaultCharset());
}
public static byte[] getQueueItemKey(String itemId) {
//QUEUE
StringBuilder builder = StringBuilderHelper.get()
.append(QUEUE).append(DELIMITER);
if (!StringUtils.isEmpty(itemId)) {
builder.append(itemId).append(DELIMITER);
}
return builder.toString().getBytes(Charset.defaultCharset());
}
public static byte[] getSplitTaskKey(String graphName, int groupId) {
// TASK_SPLIT/{GraphName}/{partitionID}
StringBuilder builder = StringBuilderHelper.get()
.append(TASK_SPLIT).append(DELIMITER)
.append(graphName).append(DELIMITER)
.append(groupId);
return builder.toString().getBytes(Charset.defaultCharset());
}
public static byte[] getSplitTaskPrefix(String graphName) {
// TASK_SPLIT/{GraphName}/
StringBuilder builder = StringBuilderHelper.get()
.append(TASK_SPLIT).append(DELIMITER)
.append(graphName);
return builder.toString().getBytes(Charset.defaultCharset());
}
public static byte[] getAllSplitTaskPrefix() {
// TASK_SPLIT/{GraphName}/
StringBuilder builder = StringBuilderHelper.get()
.append(TASK_SPLIT).append(DELIMITER);
return builder.toString().getBytes(Charset.defaultCharset());
}
public static byte[] getMoveTaskKey(String graphName, int targetGroupId, int groupId) {
// TASK_MOVE/{GraphName}/to PartitionID/{source partitionID}
StringBuilder builder = StringBuilderHelper.get()
.append(TASK_MOVE).append(DELIMITER)
.append(graphName).append(DELIMITER)
.append(targetGroupId).append(DELIMITER)
.append(groupId);
return builder.toString().getBytes(Charset.defaultCharset());
}
public static byte[] getMoveTaskPrefix(String graphName) {
// TASK_MOVE/{graphName}/toPartitionId/
StringBuilder builder = StringBuilderHelper.get()
.append(TASK_MOVE).append(DELIMITER)
.append(graphName);
return builder.toString().getBytes(Charset.defaultCharset());
}
public static byte[] getAllMoveTaskPrefix() {
// TASK_MOVE/{graphName}/toPartitionId/
StringBuilder builder = StringBuilderHelper.get()
.append(TASK_MOVE).append(DELIMITER);
return builder.toString().getBytes(Charset.defaultCharset());
}
public static byte[] getLogKey(Metapb.LogRecord record) {
//LOG_RECORD/{action}/{time}/
StringBuilder builder = StringBuilderHelper.get()
.append(LOG_RECORD)
.append(DELIMITER)
.append(record.getAction())
.append(DELIMITER)
.append(record.getTimestamp());
return builder.toString().getBytes(Charset.defaultCharset());
}
public static byte[] getLogKeyPrefix(String action, long time) {
//LOG_DATA_SPLIT/{time}/{GraphName}
StringBuilder builder = StringBuilderHelper.get()
.append(LOG_RECORD)
.append(DELIMITER)
.append(action)
.append(DELIMITER)
.append(time);
return builder.toString().getBytes(Charset.defaultCharset());
}
public static byte[] getKVPrefix(String prefix, String key) {
//K@/{key}
StringBuilder builder = StringBuilderHelper.get()
.append(prefix).append(DELIMITER);
if (!StringUtils.isEmpty(key)) {
builder.append(key).append(DELIMITER);
}
return builder.toString().getBytes(Charset.defaultCharset());
}
public static byte[] getKVTTLPrefix(String ttlPrefix, String prefix, String key) {
StringBuilder builder = StringBuilderHelper.get().append(ttlPrefix)
.append(prefix).append(DELIMITER);
if (!StringUtils.isEmpty(key)) {
builder.append(key).append(DELIMITER);
}
return builder.toString().getBytes(Charset.defaultCharset());
}
public static String getKVWatchKeyPrefix(String key, String watchDelimiter, long clientId) {
StringBuilder builder = StringBuilderHelper.get();
builder.append(watchDelimiter).append(DELIMITER);
builder.append(key == null ? "" : key).append(DELIMITER);
builder.append(clientId);
return builder.toString();
}
public static String getKVWatchKeyPrefix(String key, String watchDelimiter) {
StringBuilder builder = StringBuilderHelper.get();
builder.append(watchDelimiter).append(DELIMITER);
builder.append(key == null ? "" : key).append(DELIMITER);
return builder.toString();
}
public static char getDelimiter() {
return DELIMITER;
}
public static StringBuilder getStringBuilderHelper() {
return StringBuilderHelper.get();
}
static class StringBuilderHelper {
private static final int DISCARD_LIMIT = 1024 << 3; // 8k
private static final ThreadLocal<StringBuilderHolder> 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);
}
}
}
}

View File

@ -0,0 +1,184 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.apache.hugegraph.pd.meta;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.hugegraph.pd.common.PDException;
import org.apache.hugegraph.pd.config.PDConfig;
import org.apache.hugegraph.pd.grpc.Pdpb;
import org.apache.hugegraph.pd.store.HgKVStore;
import org.apache.hugegraph.pd.store.KV;
import com.google.protobuf.Parser;
public class MetadataRocksDBStore extends MetadataStoreBase {
HgKVStore store;
PDConfig pdConfig;
public MetadataRocksDBStore(PDConfig pdConfig) {
store = MetadataFactory.getStore(pdConfig);
this.pdConfig = pdConfig;
}
public HgKVStore getStore() {
if (store == null) {
store = MetadataFactory.getStore(pdConfig);
}
return store;
}
@Override
public byte[] getOne(byte[] key) throws PDException {
try {
byte[] bytes = store.get(key);
return bytes;
} catch (Exception e) {
throw new PDException(Pdpb.ErrorType.ROCKSDB_READ_ERROR_VALUE, e);
}
}
@Override
public <E> E getOne(Parser<E> parser, byte[] key) throws PDException {
try {
byte[] bytes = store.get(key);
if (ArrayUtils.isEmpty(bytes)) {
return null;
}
return parser.parseFrom(bytes);
} catch (Exception e) {
throw new PDException(Pdpb.ErrorType.ROCKSDB_READ_ERROR_VALUE, e);
}
}
@Override
public void put(byte[] key, byte[] value) throws PDException {
try {
getStore().put(key, value);
} catch (Exception e) {
throw new PDException(Pdpb.ErrorType.ROCKSDB_WRITE_ERROR_VALUE, e);
}
}
@Override
public void putWithTTL(byte[] key, byte[] value, long ttl) throws PDException {
this.store.putWithTTL(key, value, ttl);
}
@Override
public void putWithTTL(byte[] key, byte[] value, long ttl, TimeUnit timeUnit) throws
PDException {
this.store.putWithTTL(key, value, ttl, timeUnit);
}
@Override
public byte[] getWithTTL(byte[] key) throws PDException {
return this.store.getWithTTL(key);
}
@Override
public List getListWithTTL(byte[] key) throws PDException {
return this.store.getListWithTTL(key);
}
@Override
public void removeWithTTL(byte[] key) throws PDException {
this.store.removeWithTTL(key);
}
@Override
public List<KV> scanPrefix(byte[] prefix) throws PDException {
//TODO 使用rocksdb 前缀查询
try {
return this.store.scanPrefix(prefix);
} catch (Exception e) {
throw new PDException(Pdpb.ErrorType.ROCKSDB_READ_ERROR_VALUE, e);
}
}
@Override
public List<KV> scanRange(byte[] start, byte[] end) throws PDException {
return this.store.scanRange(start, end);
}
@Override
public <E> List<E> scanRange(Parser<E> parser, byte[] start, byte[] end) throws PDException {
List<E> stores = new LinkedList<>();
try {
List<KV> kvs = this.scanRange(start, end);
for (KV keyValue : kvs) {
stores.add(parser.parseFrom(keyValue.getValue()));
}
} catch (Exception e) {
throw new PDException(Pdpb.ErrorType.ROCKSDB_READ_ERROR_VALUE, e);
}
return stores;
}
@Override
public <E> List<E> scanPrefix(Parser<E> parser, byte[] prefix) throws PDException {
List<E> stores = new LinkedList<>();
try {
List<KV> kvs = this.scanPrefix(prefix);
for (KV keyValue : kvs) {
stores.add(parser.parseFrom(keyValue.getValue()));
}
} catch (Exception e) {
throw new PDException(Pdpb.ErrorType.ROCKSDB_READ_ERROR_VALUE, e);
}
return stores;
}
@Override
public boolean containsKey(byte[] key) throws PDException {
return !ArrayUtils.isEmpty(store.get(key));
}
@Override
public long remove(byte[] key) throws PDException {
try {
return this.store.remove(key);
} catch (Exception e) {
throw new PDException(Pdpb.ErrorType.ROCKSDB_WRITE_ERROR_VALUE, e);
}
}
@Override
public long removeByPrefix(byte[] prefix) throws PDException {
try {
return this.store.removeByPrefix(prefix);
} catch (Exception e) {
throw new PDException(Pdpb.ErrorType.ROCKSDB_WRITE_ERROR_VALUE, e);
}
}
@Override
public void clearAllCache() throws PDException {
this.store.clear();
}
@Override
public void close() {
}
}

View File

@ -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.pd.meta;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.apache.hugegraph.pd.common.PDException;
import org.apache.hugegraph.pd.grpc.Pdpb;
import org.apache.hugegraph.pd.store.KV;
import com.google.protobuf.Parser;
public abstract class MetadataStoreBase {
// public long timeout = 3; // 请求超时时间默认三秒
public abstract byte[] getOne(byte[] key) throws PDException;
public abstract <E> E getOne(Parser<E> parser, byte[] key) throws PDException;
public abstract void put(byte[] key, byte[] value) throws PDException;
/**
* 带有过期时间的put
*/
public abstract void putWithTTL(byte[] key,
byte[] value,
long ttl) throws PDException;
public abstract void putWithTTL(byte[] key,
byte[] value,
long ttl, TimeUnit timeUnit) throws PDException;
public abstract byte[] getWithTTL(byte[] key) throws PDException;
public abstract List getListWithTTL(byte[] key) throws PDException;
public abstract void removeWithTTL(byte[] key) throws PDException;
/**
* 前缀查询
*
* @param prefix
* @return
* @throws PDException
*/
public abstract List<KV> scanPrefix(byte[] prefix) throws PDException;
/**
* 前缀查询
*
* @param prefix
* @return
* @throws PDException
*/
public abstract <E> List<E> scanPrefix(Parser<E> parser, byte[] prefix) throws PDException;
public abstract List<KV> scanRange(byte[] start, byte[] end) throws PDException;
public abstract <E> List<E> scanRange(Parser<E> parser, byte[] start, byte[] end) throws
PDException;
/**
* 检查Key是否存在
*
* @param key
* @return
* @throws PDException
*/
public abstract boolean containsKey(byte[] key) throws PDException;
public abstract long remove(byte[] key) throws PDException;
public abstract long removeByPrefix(byte[] prefix) throws PDException;
public abstract void clearAllCache() throws PDException;
public abstract void close() throws IOException;
public <T> T getInstanceWithTTL(Parser<T> parser, byte[] key) throws PDException {
try {
byte[] withTTL = this.getWithTTL(key);
return parser.parseFrom(withTTL);
} catch (Exception e) {
throw new PDException(Pdpb.ErrorType.ROCKSDB_READ_ERROR_VALUE, e);
}
}
public <T> List<T> getInstanceListWithTTL(Parser<T> parser, byte[] key)
throws PDException {
try {
List withTTL = this.getListWithTTL(key);
LinkedList<T> ts = new LinkedList<>();
for (int i = 0; i < withTTL.size(); i++) {
ts.add(parser.parseFrom((byte[]) withTTL.get(i)));
}
return ts;
} catch (Exception e) {
throw new PDException(Pdpb.ErrorType.ROCKSDB_READ_ERROR_VALUE, e);
}
}
}

View File

@ -0,0 +1,295 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.apache.hugegraph.pd.meta;
import java.util.ArrayList;
import java.util.List;
import org.apache.hugegraph.pd.common.PDException;
import org.apache.hugegraph.pd.common.PartitionCache;
import org.apache.hugegraph.pd.config.PDConfig;
import org.apache.hugegraph.pd.grpc.Metapb;
import lombok.extern.slf4j.Slf4j;
/**
* 分区信息管理
*/
@Slf4j
public class PartitionMeta extends MetadataRocksDBStore {
static String CID_GRAPH_ID_KEY = "GraphID";
static int CID_GRAPH_ID_MAX = 0xFFFE;
private final PDConfig pdConfig;
private final PartitionCache cache;
public PartitionMeta(PDConfig pdConfig) {
super(pdConfig);
this.pdConfig = pdConfig;
//this.timeout = pdConfig.getEtcd().getTimeout();
this.cache = new PartitionCache();
}
/**
* 初始化加载所有的分区
*/
public void init() throws PDException {
loadShardGroups();
loadGraphs();
}
public void reload() throws PDException {
cache.clear();
loadShardGroups();
loadGraphs();
}
private void loadGraphs() throws PDException {
byte[] key = MetadataKeyHelper.getGraphPrefix();
List<Metapb.Graph> graphs = scanPrefix(Metapb.Graph.parser(), key);
for (Metapb.Graph graph : graphs) {
cache.updateGraph(graph);
loadPartitions(graph);
}
}
/**
* partition shard group分开存储再init的时候需要加载进来
*
* @throws PDException
*/
private void loadShardGroups() throws PDException {
byte[] shardGroupPrefix = MetadataKeyHelper.getShardGroupPrefix();
for (var shardGroup : scanPrefix(Metapb.ShardGroup.parser(), shardGroupPrefix)) {
cache.updateShardGroup(shardGroup);
}
}
private void loadPartitions(Metapb.Graph graph) throws PDException {
byte[] prefix = MetadataKeyHelper.getPartitionPrefix(graph.getGraphName());
List<Metapb.Partition> partitions = scanPrefix(Metapb.Partition.parser(), prefix);
partitions.forEach(p -> {
cache.updatePartition(p);
});
}
/**
* 根据id查找分区 (先从缓存找再到数据库中找
*
* @param graphName
* @param partId
* @return
* @throws PDException
*/
public Metapb.Partition getPartitionById(String graphName, int partId) throws PDException {
var pair = cache.getPartitionById(graphName, partId);
Metapb.Partition partition;
if (pair == null) {
byte[] key = MetadataKeyHelper.getPartitionKey(graphName, partId);
partition = getOne(Metapb.Partition.parser(), key);
if (partition != null) {
cache.updatePartition(partition);
}
} else {
partition = pair.getKey();
}
return partition;
}
public List<Metapb.Partition> getPartitionById(int partId) throws PDException {
List<Metapb.Partition> partitions = new ArrayList<>();
cache.getGraphs().forEach(graph -> {
cache.getPartitions(graph.getGraphName()).forEach(partition -> {
if (partition.getId() == partId) {
partitions.add(partition);
}
});
});
return partitions;
}
/**
* 根据code查找分区
*/
public Metapb.Partition getPartitionByCode(String graphName, long code) throws PDException {
var pair = cache.getPartitionByCode(graphName, code);
if (pair != null) {
return pair.getKey();
}
return null;
}
public Metapb.Graph getAndCreateGraph(String graphName) throws PDException {
return getAndCreateGraph(graphName, pdConfig.getPartition().getTotalCount());
}
public Metapb.Graph getAndCreateGraph(String graphName, int partitionCount) throws PDException {
if (partitionCount > pdConfig.getPartition().getTotalCount()) {
partitionCount = pdConfig.getPartition().getTotalCount();
}
// 管理图只有一个分区
if (graphName.endsWith("/s") || graphName.endsWith("/m")) {
partitionCount = 1;
}
Metapb.Graph graph = cache.getGraph(graphName);
if (graph == null) {
// 保存图信息
graph = Metapb.Graph.newBuilder()
.setGraphName(graphName)
.setPartitionCount(partitionCount)
.setState(Metapb.PartitionState.PState_Normal)
.build();
updateGraph(graph);
}
return graph;
}
/**
* 保存分区信息
*
* @param partition
* @return
* @throws PDException
*/
public Metapb.Partition updatePartition(Metapb.Partition partition) throws PDException {
if (!cache.hasGraph(partition.getGraphName())) {
getAndCreateGraph(partition.getGraphName());
}
byte[] key = MetadataKeyHelper.getPartitionKey(partition.getGraphName(), partition.getId());
put(key, partition.toByteString().toByteArray());
cache.updatePartition(partition);
return partition;
}
/**
* 检查数据库是否存在对应的图不存在则创建
* 更新partition的 version, conf version shard list
*
* @param partition
* @return
* @throws PDException
*/
public Metapb.Partition updateShardList(Metapb.Partition partition) throws PDException {
if (!cache.hasGraph(partition.getGraphName())) {
getAndCreateGraph(partition.getGraphName());
}
Metapb.Partition pt = getPartitionById(partition.getGraphName(), partition.getId());
// pt = pt.toBuilder().setVersion(partition.getVersion())
// .setConfVer(partition.getConfVer())
// .clearShards()
// .addAllShards(partition.getShardsList()).build();
byte[] key = MetadataKeyHelper.getPartitionKey(pt.getGraphName(), pt.getId());
put(key, pt.toByteString().toByteArray());
cache.updatePartition(pt);
return partition;
}
/**
* 删除所有分区
*/
public long removeAllPartitions(String graphName) throws PDException {
cache.removeAll(graphName);
byte[] prefix = MetadataKeyHelper.getPartitionPrefix(graphName);
return removeByPrefix(prefix);
}
public long removePartition(String graphName, int id) throws PDException {
cache.remove(graphName, id);
byte[] key = MetadataKeyHelper.getPartitionKey(graphName, id);
return remove(key);
}
public void updatePartitionStats(Metapb.PartitionStats stats) throws PDException {
for (String graphName : stats.getGraphNameList()) {
byte[] prefix = MetadataKeyHelper.getPartitionStatusKey(graphName, stats.getId());
put(prefix, stats.toByteArray());
}
}
/**
* 获取分区状态
*/
public Metapb.PartitionStats getPartitionStats(String graphName, int id) throws PDException {
byte[] prefix = MetadataKeyHelper.getPartitionStatusKey(graphName, id);
return getOne(Metapb.PartitionStats.parser(), prefix);
}
/**
* 获取分区状态
*/
public List<Metapb.PartitionStats> getPartitionStats(String graphName) throws PDException {
byte[] prefix = MetadataKeyHelper.getPartitionStatusPrefixKey(graphName);
return scanPrefix(Metapb.PartitionStats.parser(), prefix);
}
/**
* 更新图信息
*
* @param graph
* @return
*/
public Metapb.Graph updateGraph(Metapb.Graph graph) throws PDException {
log.info("updateGraph {}", graph);
byte[] key = MetadataKeyHelper.getGraphKey(graph.getGraphName());
// 保存图信息
put(key, graph.toByteString().toByteArray());
cache.updateGraph(graph);
return graph;
}
public List<Metapb.Partition> getPartitions() {
List<Metapb.Partition> partitions = new ArrayList<>();
List<Metapb.Graph> graphs = cache.getGraphs();
graphs.forEach(e -> {
partitions.addAll(cache.getPartitions(e.getGraphName()));
});
return partitions;
}
public List<Metapb.Partition> getPartitions(String graphName) {
return cache.getPartitions(graphName);
}
public List<Metapb.Graph> getGraphs() throws PDException {
byte[] key = MetadataKeyHelper.getGraphPrefix();
return scanPrefix(Metapb.Graph.parser(), key);
}
public Metapb.Graph getGraph(String graphName) throws PDException {
byte[] key = MetadataKeyHelper.getGraphKey(graphName);
return getOne(Metapb.Graph.parser(), key);
}
/**
* 删除图并删除图id
*/
public long removeGraph(String graphName) throws PDException {
byte[] key = MetadataKeyHelper.getGraphKey(graphName);
long l = remove(key);
return l;
}
public PartitionCache getPartitionCache() {
return cache;
}
}

View File

@ -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.pd.meta;
import java.util.List;
import org.apache.hugegraph.pd.common.HgAssert;
import org.apache.hugegraph.pd.common.PDException;
import org.apache.hugegraph.pd.config.PDConfig;
import org.apache.hugegraph.pd.grpc.Metapb;
import org.apache.hugegraph.pd.raft.RaftEngine;
import org.apache.hugegraph.pd.store.RaftKVStore;
public class QueueStore extends MetadataRocksDBStore {
QueueStore(PDConfig pdConfig) {
super(pdConfig);
}
public void addItem(Metapb.QueueItem queueItem) throws PDException {
HgAssert.isArgumentNotNull(queueItem, "queueItem");
byte[] key = MetadataKeyHelper.getQueueItemKey(queueItem.getItemId());
put(key, queueItem.toByteString().toByteArray());
}
public void removeItem(String itemId) throws PDException {
if (RaftEngine.getInstance().isLeader()) {
remove(MetadataKeyHelper.getQueueItemKey(itemId));
} else {
var store = getStore();
// todo: delete record via client
if (store instanceof RaftKVStore) {
((RaftKVStore) store).doRemove(MetadataKeyHelper.getQueueItemKey(itemId));
}
}
}
public List<Metapb.QueueItem> getQueue() throws PDException {
byte[] prefix = MetadataKeyHelper.getQueueItemPrefix();
return scanPrefix(Metapb.QueueItem.parser(), prefix);
}
}

View File

@ -0,0 +1,206 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.apache.hugegraph.pd.meta;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import org.apache.hugegraph.pd.common.PDException;
import org.apache.hugegraph.pd.config.PDConfig;
import org.apache.hugegraph.pd.grpc.Metapb;
import lombok.extern.slf4j.Slf4j;
/**
* Store信息存储
*/
@Slf4j
public class StoreInfoMeta extends MetadataRocksDBStore {
private final PDConfig pdConfig;
public StoreInfoMeta(PDConfig pdConfig) {
super(pdConfig);
this.pdConfig = pdConfig;
// this.timeout = pdConfig.getDiscovery().getHeartbeatOutTimes();
}
public static boolean shardGroupEquals(List<Metapb.Shard> g1, List<Metapb.Shard> g2) {
ListIterator<Metapb.Shard> e1 = g1.listIterator();
ListIterator<Metapb.Shard> e2 = g2.listIterator();
while (e1.hasNext() && e2.hasNext()) {
Metapb.Shard o1 = e1.next();
Metapb.Shard o2 = e2.next();
if (!(o1 == null ? o2 == null : o1.getStoreId() == o2.getStoreId())) {
return false;
}
}
return !(e1.hasNext() || e2.hasNext());
}
/**
* 更新Store信息
*
* @param store
* @throws PDException
*/
public void updateStore(Metapb.Store store) throws PDException {
byte[] storeInfoKey = MetadataKeyHelper.getStoreInfoKey(store.getId());
put(storeInfoKey, store.toByteArray());
}
/**
* 更新Store的存活状态
*
* @param store
*/
public void keepStoreAlive(Metapb.Store store) throws PDException {
byte[] activeStoreKey = MetadataKeyHelper.getActiveStoreKey(store.getId());
putWithTTL(activeStoreKey, store.toByteArray(), pdConfig.getStore().getKeepAliveTimeout());
}
public void removeActiveStore(Metapb.Store store) throws PDException {
byte[] activeStoreKey = MetadataKeyHelper.getActiveStoreKey(store.getId());
removeWithTTL(activeStoreKey);
}
public Metapb.Store getStore(Long storeId) throws PDException {
byte[] storeInfoKey = MetadataKeyHelper.getStoreInfoKey(storeId);
Metapb.Store store = getOne(Metapb.Store.parser(), storeInfoKey);
return store;
}
/**
* 获取所有的store
*
* @param graphName
* @return
* @throws PDException
*/
public List<Metapb.Store> getStores(String graphName) throws PDException {
byte[] storePrefix = MetadataKeyHelper.getStorePrefix();
return scanPrefix(Metapb.Store.parser(), storePrefix);
}
/**
* 获取活跃的Store
*
* @param graphName
* @return
* @throws PDException
*/
public List<Metapb.Store> getActiveStores(String graphName) throws PDException {
byte[] activePrefix = MetadataKeyHelper.getActiveStorePrefix();
List listWithTTL = getInstanceListWithTTL(Metapb.Store.parser(),
activePrefix);
return listWithTTL;
}
public List<Metapb.Store> getActiveStores() throws PDException {
byte[] activePrefix = MetadataKeyHelper.getActiveStorePrefix();
List listWithTTL = getInstanceListWithTTL(Metapb.Store.parser(),
activePrefix);
return listWithTTL;
}
/**
* 检查storeid是否存在
*
* @param storeId
* @return
*/
public boolean storeExists(Long storeId) throws PDException {
byte[] storeInfoKey = MetadataKeyHelper.getStoreInfoKey(storeId);
return containsKey(storeInfoKey);
}
/**
* 更新存储状态信息
*
* @param storeStats
*/
public Metapb.StoreStats updateStoreStats(Metapb.StoreStats storeStats) throws PDException {
byte[] storeStatusKey = MetadataKeyHelper.getStoreStatusKey(storeStats.getStoreId());
put(storeStatusKey, storeStats.toByteArray());
return storeStats;
}
public long removeStore(long storeId) throws PDException {
byte[] storeInfoKey = MetadataKeyHelper.getStoreInfoKey(storeId);
return remove(storeInfoKey);
}
public long removeAll() throws PDException {
byte[] storePrefix = MetadataKeyHelper.getStorePrefix();
return this.removeByPrefix(storePrefix);
}
public void updateShardGroup(Metapb.ShardGroup group) throws PDException {
byte[] shardGroupKey = MetadataKeyHelper.getShardGroupKey(group.getId());
put(shardGroupKey, group.toByteArray());
}
public void deleteShardGroup(int groupId) throws PDException {
byte[] shardGroupKey = MetadataKeyHelper.getShardGroupKey(groupId);
remove(shardGroupKey);
}
public Metapb.ShardGroup getShardGroup(int groupId) throws PDException {
byte[] shardGroupKey = MetadataKeyHelper.getShardGroupKey(groupId);
return getOne(Metapb.ShardGroup.parser(), shardGroupKey);
}
public int getShardGroupCount() throws PDException {
byte[] shardGroupPrefix = MetadataKeyHelper.getShardGroupPrefix();
return scanPrefix(Metapb.ShardGroup.parser(), shardGroupPrefix).size();
}
public List<Metapb.ShardGroup> getShardGroups() throws PDException {
byte[] shardGroupPrefix = MetadataKeyHelper.getShardGroupPrefix();
return scanPrefix(Metapb.ShardGroup.parser(), shardGroupPrefix);
}
public Metapb.StoreStats getStoreStats(long storeId) throws PDException {
byte[] storeStatusKey = MetadataKeyHelper.getStoreStatusKey(storeId);
Metapb.StoreStats stats = getOne(Metapb.StoreStats.parser(),
storeStatusKey);
return stats;
}
/**
* @return store及状态信息
* @throws PDException
*/
public List<Metapb.Store> getStoreStatus(boolean isActive) throws PDException {
byte[] storePrefix = MetadataKeyHelper.getStorePrefix();
List<Metapb.Store> stores = isActive ? getActiveStores() :
scanPrefix(Metapb.Store.parser(), storePrefix);
LinkedList<Metapb.Store> list = new LinkedList<>();
for (int i = 0; i < stores.size(); i++) {
Metapb.Store store = stores.get(i);
Metapb.StoreStats stats = getStoreStats(store.getId());
if (stats != null) {
store = Metapb.Store.newBuilder(store).setStats(getStoreStats(store.getId()))
.build();
}
list.add(store);
}
return list;
}
}

View File

@ -0,0 +1,132 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.apache.hugegraph.pd.meta;
import java.util.List;
import org.apache.hugegraph.pd.common.PDException;
import org.apache.hugegraph.pd.config.PDConfig;
import org.apache.hugegraph.pd.grpc.MetaTask;
import org.apache.hugegraph.pd.grpc.Metapb;
import org.apache.hugegraph.pd.grpc.pulse.MovePartition;
import org.apache.hugegraph.pd.grpc.pulse.SplitPartition;
/**
* 任务管理
*/
public class TaskInfoMeta extends MetadataRocksDBStore {
public TaskInfoMeta(PDConfig pdConfig) {
super(pdConfig);
}
/**
* 添加分区分裂任务
*/
public void addSplitTask(int groupID, Metapb.Partition partition, SplitPartition splitPartition)
throws PDException {
byte[] key = MetadataKeyHelper.getSplitTaskKey(partition.getGraphName(), groupID);
MetaTask.Task task = MetaTask.Task.newBuilder()
.setType(MetaTask.TaskType.Split_Partition)
.setState(MetaTask.TaskState.Task_Doing)
.setStartTimestamp(System.currentTimeMillis())
.setPartition(partition)
.setSplitPartition(splitPartition)
.build();
put(key, task.toByteString().toByteArray());
}
public void updateSplitTask(MetaTask.Task task) throws PDException {
var partition = task.getPartition();
byte[] key = MetadataKeyHelper.getSplitTaskKey(partition.getGraphName(), partition.getId());
put(key, task.toByteString().toByteArray());
}
public MetaTask.Task getSplitTask(String graphName, int groupID) throws PDException {
byte[] key = MetadataKeyHelper.getSplitTaskKey(graphName, groupID);
return getOne(MetaTask.Task.parser(), key);
}
public List<MetaTask.Task> scanSplitTask(String graphName) throws PDException {
byte[] prefix = MetadataKeyHelper.getSplitTaskPrefix(graphName);
return scanPrefix(MetaTask.Task.parser(), prefix);
}
public void removeSplitTaskPrefix(String graphName) throws PDException {
byte[] key = MetadataKeyHelper.getSplitTaskPrefix(graphName);
removeByPrefix(key);
}
public boolean hasSplitTaskDoing() throws PDException {
byte[] key = MetadataKeyHelper.getAllSplitTaskPrefix();
return scanPrefix(key).size() > 0;
}
public void addMovePartitionTask(Metapb.Partition partition, MovePartition movePartition)
throws PDException {
byte[] key = MetadataKeyHelper.getMoveTaskKey(partition.getGraphName(),
movePartition.getTargetPartition().getId(),
partition.getId());
MetaTask.Task task = MetaTask.Task.newBuilder()
.setType(MetaTask.TaskType.Move_Partition)
.setState(MetaTask.TaskState.Task_Doing)
.setStartTimestamp(System.currentTimeMillis())
.setPartition(partition)
.setMovePartition(movePartition)
.build();
put(key, task.toByteArray());
}
public void updateMovePartitionTask(MetaTask.Task task)
throws PDException {
byte[] key = MetadataKeyHelper.getMoveTaskKey(task.getPartition().getGraphName(),
task.getMovePartition().getTargetPartition()
.getId(),
task.getPartition().getId());
put(key, task.toByteArray());
}
public MetaTask.Task getMovePartitionTask(String graphName, int targetId, int partId) throws
PDException {
byte[] key = MetadataKeyHelper.getMoveTaskKey(graphName, targetId, partId);
return getOne(MetaTask.Task.parser(), key);
}
public List<MetaTask.Task> scanMoveTask(String graphName) throws PDException {
byte[] prefix = MetadataKeyHelper.getMoveTaskPrefix(graphName);
return scanPrefix(MetaTask.Task.parser(), prefix);
}
/**
* 按照prefix删除迁移任务一次分组的
*
* @param graphName 图名称
* @throws PDException io error
*/
public void removeMoveTaskPrefix(String graphName) throws PDException {
byte[] key = MetadataKeyHelper.getMoveTaskPrefix(graphName);
removeByPrefix(key);
}
public boolean hasMoveTaskDoing() throws PDException {
byte[] key = MetadataKeyHelper.getAllMoveTaskPrefix();
return scanPrefix(key).size() > 0;
}
}

View File

@ -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.pd.raft;
import java.util.concurrent.CompletableFuture;
import com.alipay.sofa.jraft.Closure;
import com.alipay.sofa.jraft.Status;
public class FutureClosureAdapter<T> implements Closure {
public final CompletableFuture<T> 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) {
}
}

View File

@ -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.pd.raft;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import com.alipay.sofa.jraft.util.BytesUtil;
import com.alipay.sofa.jraft.util.Requires;
import com.caucho.hessian.io.Hessian2Input;
import com.caucho.hessian.io.Hessian2Output;
import lombok.Data;
@Data
public class KVOperation {
/**
* Put operation
*/
public static final byte PUT = 0x01;
/**
* Get operation
*/
public static final byte GET = 0x02;
public static final byte DEL = 0x03;
public static final byte REMOVE_BY_PREFIX = 0x04;
public static final byte REMOVE = 0x05;
public static final byte PUT_WITH_TTL = 0x06;
public static final byte CLEAR = 0x07;
public static final byte PUT_WITH_TTL_UNIT = 0x08;
public static final byte REMOVE_WITH_TTL = 0x09;
/**
* Snapshot operation
*/
public static final byte SAVE_SNAPSHOT = 0x10;
public static final byte LOAD_SNAPSHOT = 0x11;
private byte[] key;
private byte[] value;
private Object attach; // 原始对象用于本机处理减少一次反序列化操作
private Object arg;
private byte op;
public KVOperation() {
}
public KVOperation(byte[] key, byte[] value, Object attach, byte op) {
this.key = key;
this.value = value;
this.attach = attach;
this.op = op;
}
public KVOperation(byte[] key, byte[] value, Object attach, byte op, Object arg) {
this.key = key;
this.value = value;
this.attach = attach;
this.op = op;
this.arg = arg;
}
public static KVOperation fromByteArray(byte[] value) throws IOException {
try (ByteArrayInputStream bis = new ByteArrayInputStream(value, 1, value.length - 1)) {
Hessian2Input input = new Hessian2Input(bis);
KVOperation op = new KVOperation();
op.op = value[0];
op.key = input.readBytes();
op.value = input.readBytes();
op.arg = input.readObject();
input.close();
return op;
}
}
public static KVOperation createPut(final byte[] key, final byte[] value) {
Requires.requireNonNull(key, "key");
Requires.requireNonNull(value, "value");
return new KVOperation(key, value, null, PUT);
}
public static KVOperation createGet(final byte[] key) {
Requires.requireNonNull(key, "key");
return new KVOperation(key, BytesUtil.EMPTY_BYTES, null, GET);
}
public static KVOperation createPutWithTTL(byte[] key, byte[] value, long ttl) {
Requires.requireNonNull(key, "key");
Requires.requireNonNull(value, "value");
return new KVOperation(key, value, value, PUT_WITH_TTL,
ttl);
}
public static KVOperation createPutWithTTL(byte[] key, byte[] value, long ttl,
TimeUnit timeUnit) {
Requires.requireNonNull(key, "key");
Requires.requireNonNull(value, "value");
return new KVOperation(key, value, value, PUT_WITH_TTL_UNIT,
new Object[]{ttl, timeUnit});
}
public static KVOperation createRemoveWithTTL(byte[] key) {
Requires.requireNonNull(key, "key");
return new KVOperation(key, key, null, REMOVE_WITH_TTL);
}
public static KVOperation createRemoveByPrefix(byte[] key) {
Requires.requireNonNull(key, "key");
return new KVOperation(key, key, null, REMOVE_BY_PREFIX);
}
public static KVOperation createRemove(byte[] key) {
Requires.requireNonNull(key, "key");
return new KVOperation(key, key, null, REMOVE);
}
public static KVOperation createClear() {
return new KVOperation(null, null, null, CLEAR);
}
public static KVOperation createSaveSnapshot(String snapshotPath) {
Requires.requireNonNull(snapshotPath, "snapshotPath");
return new KVOperation(null, null, snapshotPath, SAVE_SNAPSHOT);
}
public static KVOperation createLoadSnapshot(String snapshotPath) {
Requires.requireNonNull(snapshotPath, "snapshotPath");
return new KVOperation(null, null, snapshotPath, LOAD_SNAPSHOT);
}
public byte[] toByteArray() throws IOException {
try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
bos.write(op);
Hessian2Output output = new Hessian2Output(bos);
output.writeObject(key);
output.writeObject(value);
output.writeObject(arg);
output.flush();
return bos.toByteArray();
}
}
}

View File

@ -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.pd.raft;
import org.apache.hugegraph.pd.grpc.Pdpb;
import com.alipay.sofa.jraft.Closure;
public interface KVStoreClosure extends Closure {
Pdpb.Error getError();
void setError(final Pdpb.Error error);
Object getData();
void setData(final Object data);
}

View File

@ -0,0 +1,377 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.apache.hugegraph.pd.raft;
import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.hugegraph.pd.common.PDException;
import org.apache.hugegraph.pd.config.PDConfig;
import org.apache.hugegraph.pd.grpc.Metapb;
import org.apache.hugegraph.pd.grpc.Pdpb;
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.Replicator;
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.option.NodeOptions;
import com.alipay.sofa.jraft.option.RaftOptions;
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 com.alipay.sofa.jraft.util.ThreadId;
import com.alipay.sofa.jraft.util.internal.ThrowUtil;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class RaftEngine {
private static final RaftEngine INSTANCE = new RaftEngine();
private final RaftStateMachine stateMachine;
private PDConfig.Raft config;
private RaftGroupService raftGroupService;
private RpcServer rpcServer;
private Node raftNode;
private RaftRpcClient raftRpcClient;
public RaftEngine() {
this.stateMachine = new RaftStateMachine();
}
public static RaftEngine getInstance() {
return INSTANCE;
}
public boolean init(PDConfig.Raft config) {
if (this.raftNode != null) {
return false;
}
this.config = config;
raftRpcClient = new RaftRpcClient();
raftRpcClient.init(new RpcOptions());
String groupId = "pd_raft";
String raftPath = config.getDataPath() + "/" + groupId;
new File(raftPath).mkdirs();
new File(config.getDataPath()).mkdirs();
Configuration initConf = new Configuration();
initConf.parse(config.getPeersList());
if (config.isEnable() && config.getPeersList().length() < 3) {
log.error("The RaftEngine parameter is incorrect." +
" When RAFT is enabled, the number of peers " + "cannot be less than 3");
}
// 设置 Node 参数包括日志存储路径和状态机实例
NodeOptions nodeOptions = new NodeOptions();
nodeOptions.setFsm(stateMachine);
nodeOptions.setEnableMetrics(true);
// 日志路径
nodeOptions.setLogUri(raftPath + "/log");
// raft 元数据路径
nodeOptions.setRaftMetaUri(raftPath + "/meta");
// 快照路径
nodeOptions.setSnapshotUri(raftPath + "/snapshot");
// 初始集群
nodeOptions.setInitialConf(initConf);
// 快照时间间隔
nodeOptions.setSnapshotIntervalSecs(config.getSnapshotInterval());
nodeOptions.setRpcConnectTimeoutMs(config.getRpcTimeout());
nodeOptions.setRpcDefaultTimeout(config.getRpcTimeout());
nodeOptions.setRpcInstallSnapshotTimeout(config.getRpcTimeout());
// 设置 raft 配置
RaftOptions raftOptions = nodeOptions.getRaftOptions();
nodeOptions.setEnableMetrics(true);
final PeerId serverId = JRaftUtils.getPeerId(config.getAddress());
rpcServer = createRaftRpcServer(config.getAddress());
// 构建 raft 组并启动 raft
this.raftGroupService =
new RaftGroupService(groupId, serverId, nodeOptions, rpcServer, true);
this.raftNode = raftGroupService.start(false);
log.info("RaftEngine start successfully: id = {}, peers list = {}", groupId,
nodeOptions.getInitialConf().getPeers());
return this.raftNode != null;
}
/**
* 创建 raft rpc server用于 pd 之间通讯
*/
private RpcServer createRaftRpcServer(String raftAddr) {
Endpoint endpoint = JRaftUtils.getEndPoint(raftAddr);
RpcServer rpcServer = RaftRpcServerFactory.createRaftRpcServer(endpoint);
RaftRpcProcessor.registerProcessor(rpcServer, this);
rpcServer.init(null);
return rpcServer;
}
public void shutDown() {
if (this.raftGroupService != null) {
this.raftGroupService.shutdown();
try {
this.raftGroupService.join();
} catch (final InterruptedException e) {
this.raftNode = null;
ThrowUtil.throwException(e);
}
this.raftGroupService = null;
}
if (this.rpcServer != null) {
this.rpcServer.shutdown();
this.rpcServer = null;
}
if (this.raftNode != null) {
this.raftNode.shutdown();
}
this.raftNode = null;
}
public boolean isLeader() {
return this.raftNode.isLeader(true);
}
/**
* 添加 Raft 任务grpc 通过该接口给 raft 发送数据
*/
public void addTask(Task task) {
if (!isLeader()) {
KVStoreClosure closure = (KVStoreClosure) task.getDone();
closure.setError(Pdpb.Error.newBuilder().setType(Pdpb.ErrorType.NOT_LEADER).build());
closure.run(new Status(RaftError.EPERM, "Not leader"));
return;
}
this.raftNode.apply(task);
}
public void addStateListener(RaftStateListener listener) {
this.stateMachine.addStateListener(listener);
}
public void addTaskHandler(RaftTaskHandler handler) {
this.stateMachine.addTaskHandler(handler);
}
public PDConfig.Raft getConfig() {
return this.config;
}
public PeerId getLeader() {
return raftNode.getLeaderId();
}
/**
* leader 发消息获取 grpc 地址
*/
public String getLeaderGrpcAddress() throws ExecutionException, InterruptedException {
if (isLeader()) {
return config.getGrpcAddress();
}
if (raftNode.getLeaderId() == null) {
waitingForLeader(10000);
}
return raftRpcClient.getGrpcAddress(raftNode.getLeaderId().getEndpoint().toString()).get()
.getGrpcAddress();
}
public Metapb.Member getLocalMember() {
Metapb.Member.Builder builder = Metapb.Member.newBuilder();
builder.setClusterId(config.getClusterId());
builder.setRaftUrl(config.getAddress());
builder.setDataPath(config.getDataPath());
builder.setGrpcUrl(config.getGrpcAddress());
builder.setState(Metapb.StoreState.Up);
return builder.build();
}
public List<Metapb.Member> getMembers() {
List<Metapb.Member> members = new ArrayList<>();
List<PeerId> peers = raftNode.listPeers();
peers.addAll(raftNode.listLearners());
var learners = new HashSet<>(raftNode.listLearners());
for (PeerId peerId : peers) {
Metapb.Member.Builder builder = Metapb.Member.newBuilder();
builder.setClusterId(config.getClusterId());
CompletableFuture<RaftRpcProcessor.GetMemberResponse> future =
raftRpcClient.getGrpcAddress(peerId.getEndpoint().toString());
Metapb.ShardRole role = Metapb.ShardRole.Follower;
if (peerEquals(peerId, raftNode.getLeaderId())) {
role = Metapb.ShardRole.Leader;
} else if (learners.contains(peerId)) {
role = Metapb.ShardRole.Learner;
var state = getReplicatorState(peerId);
if (state != null) {
builder.setReplicatorState(state.name());
}
}
builder.setRole(role);
try {
if (future.isCompletedExceptionally()) {
log.error("failed to getGrpcAddress of {}", peerId.getEndpoint().toString());
builder.setState(Metapb.StoreState.Offline);
builder.setRaftUrl(peerId.getEndpoint().toString());
members.add(builder.build());
} else {
RaftRpcProcessor.GetMemberResponse response = future.get();
builder.setState(Metapb.StoreState.Up);
builder.setRaftUrl(response.getRaftAddress());
builder.setDataPath(response.getDatePath());
builder.setGrpcUrl(response.getGrpcAddress());
builder.setRestUrl(response.getRestAddress());
members.add(builder.build());
}
} catch (Exception e) {
log.error("failed to getGrpcAddress of {}.", peerId.getEndpoint().toString(), e);
builder.setState(Metapb.StoreState.Offline);
builder.setRaftUrl(peerId.getEndpoint().toString());
members.add(builder.build());
}
}
return members;
}
public Status changePeerList(String peerList) {
AtomicReference<Status> result = new AtomicReference<>();
try {
String[] peers = peerList.split(",", -1);
if ((peers.length & 1) != 1) {
throw new PDException(-1, "the number of peer list must be odd.");
}
Configuration newPeers = new Configuration();
newPeers.parse(peerList);
CountDownLatch latch = new CountDownLatch(1);
this.raftNode.changePeers(newPeers, status -> {
result.set(status);
latch.countDown();
});
latch.await();
} catch (Exception e) {
log.error("failed to changePeerList to {}", peerList, e);
result.set(new Status(-1, e.getMessage()));
}
return result.get();
}
public PeerId waitingForLeader(long timeOut) {
PeerId leader = getLeader();
if (leader != null) {
return leader;
}
synchronized (this) {
leader = getLeader();
long start = System.currentTimeMillis();
while ((System.currentTimeMillis() - start < timeOut) && (leader == null)) {
try {
this.wait(1000);
} catch (InterruptedException e) {
log.error("Raft wait for leader exception", e);
}
leader = getLeader();
}
return leader;
}
}
public Node getRaftNode() {
return raftNode;
}
private boolean peerEquals(PeerId p1, PeerId p2) {
if (p1 == null && p2 == null) {
return true;
}
if (p1 == null || p2 == null) {
return false;
}
return Objects.equals(p1.getIp(), p2.getIp()) && Objects.equals(p1.getPort(), p2.getPort());
}
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;
}
}
}

View File

@ -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.pd.raft;
import java.util.concurrent.CompletableFuture;
import com.alipay.sofa.jraft.JRaftUtils;
import com.alipay.sofa.jraft.Status;
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 RaftRpcClient {
protected volatile RpcClient rpcClient;
private RpcOptions rpcOptions;
public synchronized boolean init(final RpcOptions rpcOptions) {
this.rpcOptions = rpcOptions;
final RaftRpcFactory factory = RpcFactoryHelper.rpcFactory();
this.rpcClient =
factory.createRpcClient(factory.defaultJRaftClientConfigHelper(this.rpcOptions));
return this.rpcClient.init(null);
}
/**
* 请求快照
*/
public CompletableFuture<RaftRpcProcessor.GetMemberResponse>
getGrpcAddress(final String address) {
RaftRpcProcessor.GetMemberRequest request = new RaftRpcProcessor.GetMemberRequest();
FutureClosureAdapter<RaftRpcProcessor.GetMemberResponse> response =
new FutureClosureAdapter<>();
internalCallAsyncWithRpc(JRaftUtils.getEndPoint(address), request, response);
return response.future;
}
private <V> void internalCallAsyncWithRpc(final Endpoint endpoint,
final RaftRpcProcessor.BaseRequest request,
final FutureClosureAdapter<V> closure) {
final InvokeContext invokeCtx = null;
final InvokeCallback invokeCallback = new InvokeCallback() {
@Override
public void complete(final Object result, final Throwable err) {
if (err == null) {
final RaftRpcProcessor.BaseResponse response =
(RaftRpcProcessor.BaseResponse) result;
closure.setResponse((V) response);
} else {
closure.failure(err);
closure.run(new Status(-1, err.getMessage()));
}
}
};
try {
this.rpcClient.invokeAsync(endpoint, request, invokeCtx, invokeCallback,
this.rpcOptions.getRpcDefaultTimeout());
} catch (final Throwable t) {
log.error("failed to call rpc to {}. {}", endpoint, t.getMessage());
closure.failure(t);
closure.run(new Status(-1, t.getMessage()));
}
}
}

View File

@ -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.pd.raft;
import java.io.Serializable;
import com.alipay.sofa.jraft.rpc.RpcContext;
import com.alipay.sofa.jraft.rpc.RpcProcessor;
import com.alipay.sofa.jraft.rpc.RpcServer;
import lombok.Data;
public class RaftRpcProcessor<T extends RaftRpcProcessor.BaseRequest> implements RpcProcessor<T> {
private final Class<?> requestClass;
private final RaftEngine raftEngine;
public RaftRpcProcessor(Class<?> requestClass, RaftEngine raftEngine) {
this.requestClass = requestClass;
this.raftEngine = raftEngine;
}
public static void registerProcessor(final RpcServer rpcServer, RaftEngine raftEngine) {
rpcServer.registerProcessor(new RaftRpcProcessor<>(GetMemberRequest.class, raftEngine));
}
@Override
public void handleRequest(RpcContext rpcCtx, T request) {
if (request.magic() == BaseRequest.GET_GRPC_ADDRESS) {
rpcCtx.sendResponse(getGrpcAddress());
}
}
@Override
public String interest() {
return this.requestClass.getName();
}
private GetMemberResponse getGrpcAddress() {
GetMemberResponse rep = new GetMemberResponse();
rep.setGrpcAddress(raftEngine.getConfig().getGrpcAddress());
rep.setClusterId(raftEngine.getConfig().getClusterId());
rep.setDatePath(raftEngine.getConfig().getDataPath());
rep.setRaftAddress(raftEngine.getConfig().getAddress());
rep.setRestAddress(
raftEngine.getConfig().getHost() + ":" + raftEngine.getConfig().getPort());
rep.setStatus(Status.OK);
return rep;
}
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"),
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 Status setMsg(String msg) {
this.msg = msg;
return this;
}
public boolean isOK() {
return this.code == 0;
}
}
public abstract static class BaseRequest implements Serializable {
public static final byte GET_GRPC_ADDRESS = 0x01;
public abstract byte magic();
}
@Data
public abstract static class BaseResponse implements Serializable {
private Status status;
}
@Data
public static class GetMemberRequest extends BaseRequest {
@Override
public byte magic() {
return GET_GRPC_ADDRESS;
}
}
@Data
public static class GetMemberResponse extends BaseResponse {
private long clusterId;
private String raftAddress;
private String grpcAddress;
private String datePath;
private String restAddress;
}
}

View File

@ -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.pd.raft;
public interface RaftStateListener {
void onRaftLeaderChanged();
}

View File

@ -0,0 +1,330 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.apache.hugegraph.pd.raft;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicLong;
import java.util.zip.Checksum;
import org.apache.commons.io.FileUtils;
import org.apache.hugegraph.pd.common.PDException;
import org.apache.hugegraph.pd.grpc.Pdpb;
import org.springframework.util.CollectionUtils;
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.LocalFileMetaOutter;
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.CRC64;
import com.alipay.sofa.jraft.util.Utils;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class RaftStateMachine extends StateMachineAdapter {
private static final String SNAPSHOT_DIR_NAME = "snapshot";
private static final String SNAPSHOT_ARCHIVE_NAME = "snapshot.zip";
private final AtomicLong leaderTerm = new AtomicLong(-1);
private final List<RaftTaskHandler> taskHandlers;
private final List<RaftStateListener> stateListeners;
public RaftStateMachine() {
this.taskHandlers = new CopyOnWriteArrayList<>();
this.stateListeners = 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 iter) {
while (iter.hasNext()) {
final RaftClosureAdapter done = (RaftClosureAdapter) iter.done();
try {
KVOperation kvOp;
if (done != null) {
kvOp = done.op;
} else {
kvOp = KVOperation.fromByteArray(iter.getData().array());
}
for (RaftTaskHandler taskHandler : taskHandlers) {
taskHandler.invoke(kvOp, done);
}
if (done != null) {
done.run(Status.OK());
}
} catch (Throwable t) {
log.error("StateMachine meet critical error: {}.", t);
if (done != null) {
done.run(new Status(RaftError.EINTERNAL, t.getMessage()));
}
}
iter.next();
}
}
@Override
public void onError(final RaftException e) {
log.error("Raft StateMachine on error {}", e);
}
@Override
public void onShutdown() {
super.onShutdown();
}
@Override
public void onLeaderStart(final long term) {
this.leaderTerm.set(term);
super.onLeaderStart(term);
log.info("Raft becomes leader");
Utils.runInThread(() -> {
if (!CollectionUtils.isEmpty(stateListeners)) {
stateListeners.forEach(listener -> {
listener.onRaftLeaderChanged();
});
}
});
}
@Override
public void onLeaderStop(final Status status) {
this.leaderTerm.set(-1);
super.onLeaderStop(status);
log.info("Raft lost leader ");
}
@Override
public void onStartFollowing(final LeaderChangeContext ctx) {
super.onStartFollowing(ctx);
Utils.runInThread(() -> {
if (!CollectionUtils.isEmpty(stateListeners)) {
stateListeners.forEach(listener -> {
listener.onRaftLeaderChanged();
});
}
});
}
@Override
public void onStopFollowing(final LeaderChangeContext ctx) {
super.onStopFollowing(ctx);
}
@Override
public void onConfigurationCommitted(final Configuration conf) {
log.info("Raft onConfigurationCommitted {}", conf);
}
@Override
public void onSnapshotSave(final SnapshotWriter writer, final Closure done) {
String snapshotDir = writer.getPath() + File.separator + SNAPSHOT_DIR_NAME;
try {
FileUtils.deleteDirectory(new File(snapshotDir));
FileUtils.forceMkdir(new File(snapshotDir));
} catch (IOException e) {
log.error("Failed to create snapshot directory {}", snapshotDir);
done.run(new Status(RaftError.EIO, e.toString()));
return;
}
CountDownLatch latch = new CountDownLatch(taskHandlers.size());
for (RaftTaskHandler taskHandler : taskHandlers) {
Utils.runInThread(() -> {
try {
KVOperation op = KVOperation.createSaveSnapshot(snapshotDir);
taskHandler.invoke(op, null);
log.info("Raft onSnapshotSave success");
latch.countDown();
} catch (PDException e) {
log.error("Raft onSnapshotSave failed. {}", e.toString());
done.run(new Status(RaftError.EIO, e.toString()));
}
});
}
try {
latch.await();
} catch (InterruptedException e) {
log.error("Raft onSnapshotSave failed. {}", e.toString());
done.run(new Status(RaftError.EIO, e.toString()));
return;
}
// compress
try {
compressSnapshot(writer);
FileUtils.deleteDirectory(new File(snapshotDir));
} catch (Exception e) {
log.error("Failed to delete snapshot directory {}, {}", snapshotDir, e.toString());
done.run(new Status(RaftError.EIO, e.toString()));
return;
}
done.run(Status.OK());
}
@Override
public boolean onSnapshotLoad(final SnapshotReader reader) {
if (isLeader()) {
log.warn("Leader is not supposed to load snapshot");
return false;
}
String snapshotDir = reader.getPath() + File.separator + SNAPSHOT_DIR_NAME;
String snapshotArchive = reader.getPath() + File.separator + SNAPSHOT_ARCHIVE_NAME;
// 2. decompress snapshot archive
try {
decompressSnapshot(reader);
} catch (PDException e) {
log.error("Failed to delete snapshot directory {}, {}", snapshotDir, e.toString());
return true;
}
CountDownLatch latch = new CountDownLatch(taskHandlers.size());
for (RaftTaskHandler taskHandler : taskHandlers) {
try {
KVOperation op = KVOperation.createLoadSnapshot(snapshotDir);
taskHandler.invoke(op, null);
log.info("Raft onSnapshotLoad success");
latch.countDown();
} catch (PDException e) {
log.error("Raft onSnapshotLoad failed. {}", e.toString());
return false;
}
}
try {
latch.await();
} catch (InterruptedException e) {
log.error("Raft onSnapshotSave failed. {}", e.toString());
return false;
}
try {
// TODO: remove file from meta
// SnapshotReader 沒有提供刪除文件的接口
FileUtils.deleteDirectory(new File(snapshotDir));
File file = new File(snapshotArchive);
if (file.exists()) {
FileUtils.forceDelete(file);
}
} catch (IOException e) {
log.error("Failed to delete snapshot directory {} and file {}", snapshotDir,
snapshotArchive);
return false;
}
return true;
}
private void compressSnapshot(final SnapshotWriter writer) throws PDException {
final Checksum checksum = new CRC64();
final String snapshotArchive = writer.getPath() + File.separator + SNAPSHOT_ARCHIVE_NAME;
try {
ZipUtils.compress(writer.getPath(), SNAPSHOT_DIR_NAME, snapshotArchive, checksum);
LocalFileMetaOutter.LocalFileMeta.Builder metaBuild =
LocalFileMetaOutter.LocalFileMeta.newBuilder();
metaBuild.setChecksum(Long.toHexString(checksum.getValue()));
if (!writer.addFile(SNAPSHOT_ARCHIVE_NAME, metaBuild.build())) {
throw new PDException(Pdpb.ErrorType.ROCKSDB_SAVE_SNAPSHOT_ERROR_VALUE,
"failed to add file to LocalFileMeta");
}
} catch (IOException e) {
throw new PDException(Pdpb.ErrorType.ROCKSDB_SAVE_SNAPSHOT_ERROR_VALUE, e);
}
}
private void decompressSnapshot(final SnapshotReader reader) throws PDException {
final LocalFileMetaOutter.LocalFileMeta meta =
(LocalFileMetaOutter.LocalFileMeta) reader.getFileMeta(SNAPSHOT_ARCHIVE_NAME);
final Checksum checksum = new CRC64();
final String snapshotArchive = reader.getPath() + File.separator + SNAPSHOT_ARCHIVE_NAME;
try {
ZipUtils.decompress(snapshotArchive, reader.getPath(), checksum);
if (meta.hasChecksum()) {
if (!meta.getChecksum().equals(Long.toHexString(checksum.getValue()))) {
throw new PDException(Pdpb.ErrorType.ROCKSDB_LOAD_SNAPSHOT_ERROR_VALUE,
"Snapshot checksum failed");
}
}
} catch (IOException e) {
throw new PDException(Pdpb.ErrorType.ROCKSDB_LOAD_SNAPSHOT_ERROR_VALUE, e);
}
}
public static class RaftClosureAdapter implements KVStoreClosure {
private final KVOperation op;
private final KVStoreClosure closure;
public RaftClosureAdapter(KVOperation op, KVStoreClosure closure) {
this.op = op;
this.closure = closure;
}
public KVStoreClosure getClosure() {
return closure;
}
@Override
public void run(Status status) {
closure.run(status);
}
@Override
public Pdpb.Error getError() {
return null;
}
@Override
public void setError(Pdpb.Error error) {
}
@Override
public Object getData() {
return null;
}
@Override
public void setData(Object data) {
}
}
}

View File

@ -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.pd.raft;
import org.apache.hugegraph.pd.common.PDException;
/**
* 接收raft发送的数据
*/
public interface RaftTaskHandler {
boolean invoke(final KVOperation op, KVStoreClosure response) throws PDException;
}

View File

@ -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.pd.raft;
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 {
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);
}
}
}

View File

@ -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.pd.store;
import org.apache.hugegraph.pd.grpc.Pdpb;
import org.apache.hugegraph.pd.raft.KVStoreClosure;
public abstract class BaseKVStoreClosure implements KVStoreClosure {
private Pdpb.Error error;
private Object data;
@Override
public Pdpb.Error getError() {
return error;
}
@Override
public void setError(Pdpb.Error error) {
this.error = error;
}
@Override
public Object getData() {
return data;
}
@Override
public void setData(Object data) {
this.data = data;
}
}

View File

@ -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.pd.store;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.apache.hugegraph.pd.common.PDException;
import org.apache.hugegraph.pd.config.PDConfig;
public interface HgKVStore {
void init(PDConfig config);
void put(byte[] key, byte[] value) throws PDException;
byte[] get(byte[] key) throws PDException;
List<KV> scanPrefix(byte[] prefix);
long remove(byte[] bytes) throws PDException;
long removeByPrefix(byte[] bytes) throws PDException;
void putWithTTL(byte[] key, byte[] value, long ttl) throws PDException;
void putWithTTL(byte[] key, byte[] value, long ttl, TimeUnit timeUnit) throws PDException;
byte[] getWithTTL(byte[] key) throws PDException;
void removeWithTTL(byte[] key) throws PDException;
List<byte[]> getListWithTTL(byte[] key) throws PDException;
void clear() throws PDException;
void saveSnapshot(String snapshotPath) throws PDException;
void loadSnapshot(String snapshotPath) throws PDException;
List<KV> scanRange(byte[] start, byte[] end);
void close();
}

View File

@ -0,0 +1,343 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.apache.hugegraph.pd.store;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.apache.commons.io.FileUtils;
import org.apache.hugegraph.pd.common.PDException;
import org.apache.hugegraph.pd.config.PDConfig;
import org.apache.hugegraph.pd.grpc.Pdpb;
import org.apache.hugegraph.pd.grpc.discovery.RegisterInfo;
import org.rocksdb.Checkpoint;
import org.rocksdb.Options;
import org.rocksdb.ReadOptions;
import org.rocksdb.RocksDB;
import org.rocksdb.RocksDBException;
import org.rocksdb.RocksIterator;
import org.rocksdb.Slice;
import com.alipay.sofa.jraft.util.Utils;
import com.google.common.cache.CacheBuilder;
import com.google.common.primitives.Bytes;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class HgKVStoreImpl implements HgKVStore {
private static final ConcurrentHashMap<String,
ConcurrentMap<String, Object>> CACHE = new ConcurrentHashMap();
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private RocksDB db;
private String dbPath;
private Options dbOptions;
@Override
public void init(PDConfig config) {
dbOptions = new Options().setCreateIfMissing(true);
final Lock writeLock = this.readWriteLock.writeLock();
writeLock.lock();
try {
this.dbPath = config.getDataPath() + "/rocksdb/";
File file = new File(this.dbPath);
if (!file.exists()) {
try {
FileUtils.forceMkdir(file);
} catch (IOException e) {
log.warn("Failed to create data file,{}", e);
}
}
openRocksDB(dbPath);
} catch (PDException e) {
log.error("Failed to open data file,{}", e);
} finally {
writeLock.unlock();
}
}
@Override
public void put(byte[] key, byte[] value) throws PDException {
final Lock readLock = this.readWriteLock.readLock();
readLock.lock();
try {
db.put(key, value);
} catch (RocksDBException e) {
throw new PDException(Pdpb.ErrorType.ROCKSDB_WRITE_ERROR_VALUE, e);
} finally {
readLock.unlock();
}
}
@Override
public byte[] get(byte[] key) throws PDException {
final Lock readLock = this.readWriteLock.readLock();
readLock.lock();
try {
return db.get(key);
} catch (RocksDBException e) {
throw new PDException(Pdpb.ErrorType.ROCKSDB_READ_ERROR_VALUE, e);
} finally {
readLock.unlock();
}
}
@Override
public List<KV> scanPrefix(byte[] prefix) {
final Lock readLock = this.readWriteLock.readLock();
readLock.lock();
try (ReadOptions options = new ReadOptions()
.setIterateLowerBound(new Slice(prefix))) {
List<KV> kvs = new ArrayList<>();
RocksIterator iterator = db.newIterator(options);
iterator.seekToFirst();
while (iterator.isValid() && 0 == Bytes.indexOf(iterator.key(), prefix)) {
kvs.add(new KV(iterator.key(), iterator.value()));
iterator.next();
}
return kvs;
} finally {
readLock.unlock();
}
}
@Override
public long remove(byte[] key) throws PDException {
final Lock readLock = this.readWriteLock.readLock();
readLock.lock();
try {
db.delete(key);
} catch (RocksDBException e) {
throw new PDException(Pdpb.ErrorType.ROCKSDB_DEL_ERROR_VALUE, e);
} finally {
readLock.unlock();
}
return 0;
}
@Override
public long removeByPrefix(byte[] prefix) throws PDException {
final Lock readLock = this.readWriteLock.readLock();
readLock.lock();
try (ReadOptions options = new ReadOptions()
.setIterateLowerBound(new Slice(prefix))) {
RocksIterator iterator = db.newIterator(options);
iterator.seekToFirst();
while (iterator.isValid()) {
if (0 == Bytes.indexOf(iterator.key(), prefix)) {
db.delete(iterator.key());
} else {
break;
}
iterator.next();
}
} catch (Exception e) {
throw new PDException(Pdpb.ErrorType.ROCKSDB_WRITE_ERROR_VALUE, e);
} finally {
readLock.unlock();
}
return 0;
}
@Override
public void clear() throws PDException {
CACHE.clear();
}
@Override
public List getListWithTTL(byte[] key) throws PDException {
String storeKey = new String(key, Charset.defaultCharset());
LinkedList<byte[]> ts = new LinkedList<>();
CACHE.keySet().forEach((cacheKey) -> {
if (cacheKey.startsWith(storeKey)) {
ConcurrentMap map;
if ((map = CACHE.get(cacheKey)) == null) {
return;
}
map.values().forEach((element) -> {
ts.add((byte[]) element);
});
}
});
return ts;
}
@Override
public byte[] getWithTTL(byte[] key) throws PDException {
ConcurrentMap map;
String storeKey = new String(key, Charset.defaultCharset());
if ((map = CACHE.get(storeKey)) == null) {
return null;
}
Object value = map.get(storeKey);
return value == null ? null : (byte[]) value;
}
@Override
public void removeWithTTL(byte[] key) throws PDException {
ConcurrentMap map;
String storeKey = new String(key, Charset.defaultCharset());
if ((map = CACHE.get(storeKey)) == null) {
return;
}
map.remove(storeKey);
}
@Override
public void putWithTTL(byte[] key, byte[] value, long ttl) throws PDException {
this.putWithTTL(key, value, ttl, TimeUnit.SECONDS);
}
@Override
public void putWithTTL(byte[] key, byte[] value, long ttl, TimeUnit timeUnit) throws
PDException {
try {
ConcurrentMap spaceNode = CacheBuilder.newBuilder().initialCapacity(200)
.expireAfterWrite(ttl,
timeUnit)
.<String, RegisterInfo>build().asMap();
String storeKey = new String(key, Charset.defaultCharset());
ConcurrentMap space = CACHE.putIfAbsent(storeKey, spaceNode);
if (space == null) {
space = spaceNode;
}
space.put(storeKey, value);
} catch (Exception e) {
throw new PDException(Pdpb.ErrorType.ROCKSDB_WRITE_ERROR_VALUE, e);
}
}
@Override
public void saveSnapshot(String snapshotPath) throws PDException {
log.info("begin save snapshot at {}", snapshotPath);
final Lock writeLock = this.readWriteLock.writeLock();
writeLock.lock();
try (final Checkpoint checkpoint = Checkpoint.create(this.db)) {
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);
FileUtils.deleteDirectory(snapshotFile);
if (!Utils.atomicMoveFile(tempFile, snapshotFile, true)) {
log.error("Fail to rename {} to {}", tempPath, snapshotPath);
throw new PDException(Pdpb.ErrorType.ROCKSDB_SAVE_SNAPSHOT_ERROR_VALUE,
String.format("Fail to rename %s to %s", tempPath,
snapshotPath));
}
} catch (final PDException e) {
throw e;
} catch (final Exception e) {
log.error("Fail to write snapshot at path: {}", snapshotPath, e);
throw new PDException(Pdpb.ErrorType.ROCKSDB_SAVE_SNAPSHOT_ERROR_VALUE, e);
} finally {
writeLock.unlock();
}
log.info("saved snapshot into {}", snapshotPath);
}
@Override
public void loadSnapshot(String snapshotPath) throws PDException {
log.info("begin load snapshot from {}", snapshotPath);
final Lock writeLock = this.readWriteLock.writeLock();
writeLock.lock();
try {
final File snapshotFile = new File(snapshotPath);
if (!snapshotFile.exists()) {
log.error("Snapshot file {} not exists.", snapshotPath);
return;
}
// close DB
closeRocksDB();
// replace rocksdb data with snapshot data
final File dbFile = new File(this.dbPath);
FileUtils.deleteDirectory(dbFile);
if (!Utils.atomicMoveFile(snapshotFile, dbFile, true)) {
log.error("Fail to rename {} to {}", snapshotPath, this.dbPath);
throw new PDException(Pdpb.ErrorType.ROCKSDB_LOAD_SNAPSHOT_ERROR_VALUE,
String.format("Fail to rename %s to %s", snapshotPath,
this.dbPath));
}
// reopen the db
openRocksDB(this.dbPath);
} catch (final PDException e) {
throw e;
} catch (final Exception e) {
log.error("failed to load snapshot from {}", snapshotPath);
throw new PDException(Pdpb.ErrorType.ROCKSDB_LOAD_SNAPSHOT_ERROR_VALUE, e);
} finally {
writeLock.unlock();
}
log.info("loaded snapshot from {}", snapshotPath);
}
@Override
public List<KV> scanRange(byte[] start, byte[] end) {
final Lock readLock = this.readWriteLock.readLock();
readLock.lock();
try (ReadOptions options = new ReadOptions()
.setIterateLowerBound(new Slice(start))
.setIterateUpperBound(new Slice(end))) {
List<KV> kvs = new ArrayList<>();
RocksIterator iterator = db.newIterator(options);
iterator.seekToFirst();
while (iterator.isValid()) {
kvs.add(new KV(iterator.key(), iterator.value()));
iterator.next();
}
return kvs;
} finally {
readLock.unlock();
}
}
@Override
public void close() {
closeRocksDB();
}
private void closeRocksDB() {
if (this.db != null) {
this.db.close();
this.db = null;
}
}
private void openRocksDB(String dbPath) throws PDException {
try {
this.db = RocksDB.open(dbOptions, dbPath);
} catch (RocksDBException e) {
log.error("Failed to open RocksDB from {}", dbPath, e);
throw new PDException(Pdpb.ErrorType.ROCKSDB_LOAD_SNAPSHOT_ERROR_VALUE, e);
}
}
}

View File

@ -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.pd.store;
public class KV {
private byte[] key;
private byte[] value;
public KV(byte[] key, byte[] value) {
this.key = key;
this.value = value;
}
public byte[] getKey() {
return key;
}
public void setKey(byte[] key) {
this.key = key;
}
public byte[] getValue() {
return value;
}
public void setValue(byte[] value) {
this.value = value;
}
}

View File

@ -0,0 +1,324 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.apache.hugegraph.pd.store;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import org.apache.hugegraph.pd.common.PDException;
import org.apache.hugegraph.pd.config.PDConfig;
import org.apache.hugegraph.pd.grpc.Pdpb;
import org.apache.hugegraph.pd.raft.KVOperation;
import org.apache.hugegraph.pd.raft.KVStoreClosure;
import org.apache.hugegraph.pd.raft.RaftEngine;
import org.apache.hugegraph.pd.raft.RaftStateMachine;
import org.apache.hugegraph.pd.raft.RaftTaskHandler;
import com.alipay.sofa.jraft.Status;
import com.alipay.sofa.jraft.entity.Task;
import com.alipay.sofa.jraft.error.RaftError;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class RaftKVStore implements HgKVStore, RaftTaskHandler {
private final RaftEngine engine;
private final HgKVStore store;
public RaftKVStore(RaftEngine engine, HgKVStore store) {
this.engine = engine;
this.store = store;
}
@Override
public void init(PDConfig config) {
this.store.init(config);
this.engine.addTaskHandler(this);
}
private BaseKVStoreClosure createClosure() {
return new BaseKVStoreClosure() {
@Override
public void run(Status status) {
if (!status.isOk()) {
log.error("An exception occurred while performing the RAFT,{}",
status.getErrorMsg());
} else {
log.info("RAFT done!");
}
}
};
}
@Override
public void put(byte[] key, byte[] value) throws PDException {
KVOperation operation = KVOperation.createPut(key, value);
try {
applyOperation(operation).get();
} catch (Exception e) {
throw new PDException(Pdpb.ErrorType.UNKNOWN_VALUE, e.getMessage());
}
}
/**
* 查询可以不走raft直接读取
*/
@Override
public byte[] get(byte[] key) throws PDException {
return store.get(key);
}
@Override
public List<KV> scanPrefix(byte[] prefix) {
return store.scanPrefix(prefix);
}
@Override
public long remove(byte[] bytes) throws PDException {
try {
applyOperation(KVOperation.createRemove(bytes)).get();
} catch (Exception e) {
throw new PDException(Pdpb.ErrorType.UNKNOWN_VALUE, e.getMessage());
}
return 0;
}
@Override
public long removeByPrefix(byte[] bytes) throws PDException {
try {
applyOperation(KVOperation.createRemoveByPrefix(bytes)).get();
} catch (Exception e) {
throw new PDException(Pdpb.ErrorType.UNKNOWN_VALUE, e.getMessage());
}
return 0;
}
@Override
public void clear() throws PDException {
try {
applyOperation(KVOperation.createClear()).get();
} catch (Exception e) {
throw new PDException(Pdpb.ErrorType.UNKNOWN_VALUE, e.getMessage());
}
}
@Override
public void putWithTTL(byte[] key, byte[] value, long ttl) throws PDException {
try {
applyOperation(KVOperation.createPutWithTTL(key, value, ttl)).get();
} catch (Exception e) {
throw new PDException(Pdpb.ErrorType.UNKNOWN_VALUE, e.getMessage());
}
}
@Override
public void putWithTTL(byte[] key, byte[] value, long ttl, TimeUnit timeUnit) throws
PDException {
try {
applyOperation(KVOperation.createPutWithTTL(key, value, ttl, timeUnit)).get();
} catch (Exception e) {
throw new PDException(Pdpb.ErrorType.UNKNOWN_VALUE, e.getMessage());
}
}
@Override
public List<byte[]> getListWithTTL(byte[] key) throws PDException {
return store.getListWithTTL(key);
}
@Override
public byte[] getWithTTL(byte[] key) throws PDException {
return store.getWithTTL(key);
}
@Override
public void removeWithTTL(byte[] key) throws PDException {
try {
applyOperation(KVOperation.createRemoveWithTTL(key)).get();
} catch (Exception e) {
throw new PDException(Pdpb.ErrorType.UNKNOWN_VALUE, e.getMessage());
}
}
@Override
public void saveSnapshot(String snapshotPath) throws PDException {
store.saveSnapshot(snapshotPath);
}
@Override
public void loadSnapshot(String snapshotPath) throws PDException {
store.loadSnapshot(snapshotPath);
}
@Override
public List<KV> scanRange(byte[] start, byte[] end) {
return store.scanRange(start, end);
}
@Override
public void close() {
store.close();
}
/**
* 需要走Raft的真实操作
*/
private void doPut(byte[] key, byte[] value) throws PDException {
store.put(key, value);
}
public long doRemove(byte[] bytes) throws PDException {
return this.store.remove(bytes);
}
public long doRemoveByPrefix(byte[] bytes) throws PDException {
return this.store.removeByPrefix(bytes);
}
public void doRemoveWithTTL(byte[] key) throws PDException {
this.store.removeWithTTL(key);
}
public void doClear() throws PDException {
this.store.clear();
}
public void doPutWithTTL(byte[] key, byte[] value, long ttl) throws PDException {
this.store.putWithTTL(key, value, ttl);
}
public void doPutWithTTL(byte[] key, byte[] value, long ttl, TimeUnit timeUnit) throws
PDException {
this.store.putWithTTL(key, value, ttl, timeUnit);
}
public void doSaveSnapshot(String snapshotPath) throws PDException {
this.store.saveSnapshot(snapshotPath);
}
public void doLoadSnapshot(String snapshotPath) throws PDException {
this.store.loadSnapshot(snapshotPath);
}
private <T> CompletableFuture<T> applyOperation(final KVOperation op) throws PDException {
CompletableFuture<T> future = new CompletableFuture<>();
try {
final Task task = new Task();
task.setData(ByteBuffer.wrap(op.toByteArray()));
task.setDone(new RaftStateMachine.RaftClosureAdapter(op, new KVStoreClosure() {
Object data;
Pdpb.Error error;
@Override
public Pdpb.Error getError() {
return error;
}
@Override
public void setError(Pdpb.Error error) {
this.error = error;
}
@Override
public Object getData() {
return data;
}
@Override
public void setData(Object data) {
this.data = data;
}
@Override
public void run(Status status) {
if (status.isOk()) {
future.complete((T) data);
} else {
RaftError raftError = status.getRaftError();
Pdpb.ErrorType type;
if (RaftError.EPERM.equals(raftError)) {
type = Pdpb.ErrorType.NOT_LEADER;
} else {
type = Pdpb.ErrorType.UNKNOWN;
}
error = Pdpb.Error.newBuilder().setType(type)
.setMessage(status.getErrorMsg())
.build();
future.completeExceptionally(
new PDException(error.getTypeValue()));
}
}
}));
this.engine.addTask(task);
return future;
} catch (Exception e) {
future.completeExceptionally(e);
return future;
}
}
private boolean isLeader() {
return this.engine.isLeader();
}
@Override
public boolean invoke(KVOperation op, KVStoreClosure response) throws PDException {
switch (op.getOp()) {
case KVOperation.GET:
break;
case KVOperation.PUT:
doPut(op.getKey(), op.getValue());
break;
case KVOperation.REMOVE:
doRemove(op.getKey());
break;
case KVOperation.PUT_WITH_TTL:
doPutWithTTL(op.getKey(), op.getValue(), (long) op.getArg());
break;
case KVOperation.PUT_WITH_TTL_UNIT:
Object[] arg = (Object[]) op.getArg();
doPutWithTTL(op.getKey(), op.getValue(), (long) arg[0], (TimeUnit) arg[1]);
break;
case KVOperation.REMOVE_BY_PREFIX:
doRemoveByPrefix(op.getKey());
break;
case KVOperation.REMOVE_WITH_TTL:
doRemoveWithTTL(op.getKey());
break;
case KVOperation.CLEAR:
doClear();
break;
case KVOperation.SAVE_SNAPSHOT:
doSaveSnapshot((String) op.getAttach());
break;
case KVOperation.LOAD_SNAPSHOT:
doLoadSnapshot((String) op.getAttach());
break;
default:
log.error("Err op {}", op.getOp());
}
return false;
}
}

View File

@ -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.pd;
import java.util.concurrent.ExecutionException;
import org.apache.hugegraph.pd.common.PDException;
import org.apache.hugegraph.pd.config.PDConfig;
import org.apache.hugegraph.pd.grpc.Metapb;
import org.junit.Assert;
import org.junit.BeforeClass;
// import org.junit.Test;
public class MonitorServiceTest {
static PDConfig pdConfig;
@BeforeClass
public static void init() throws ExecutionException, InterruptedException {
pdConfig = new PDConfig() {{
this.setClusterId(100);
this.setPatrolInterval(1);
}};
//pdConfig.setEtcd(new PDConfig().new Etcd() {{
// this.setAddress("http://localhost:2379");
//
//}});
pdConfig.setStore(new PDConfig().new Store() {{
this.setMaxDownTime(1);
this.setKeepAliveTimeout(5);
}});
pdConfig.setPartition(new PDConfig().new Partition() {{
this.setShardCount(3);
this.setTotalCount(10);
}});
clearClusterData();
}
public static void clearClusterData() throws ExecutionException, InterruptedException {
//Client client = Client.builder().endpoints(pdConfig.getEtcd().getAddress()).build();
//KV kvClient = client.getKVClient();
//
//ByteSequence key = ByteSequence.from("HUGEGRAPH/" + pdConfig.getClusterId(), Charset
// .forName("utf-8"));
//CompletableFuture<DeleteResponse> rsp = kvClient.delete(key, DeleteOption.newBuilder()
// .isPrefix(true).build());
//System.out.println("删除数量 : " + rsp.get().getDeleted());
//kvClient.close();
//client.close();
}
// @Test
public void testPatrolStores() throws PDException, InterruptedException {
StoreNodeService storeService = new StoreNodeService(pdConfig);
PartitionService partitionService = new PartitionService(pdConfig, storeService);
TaskScheduleService monitorService =
new TaskScheduleService(pdConfig, storeService, partitionService);
storeService.init(partitionService);
partitionService.init();
monitorService.init();
int count = 6;
Metapb.Store[] stores = new Metapb.Store[count];
for (int i = 0; i < count; i++) {
Metapb.Store store = Metapb.Store.newBuilder()
.setId(0)
.setAddress(String.valueOf(i))
.setDeployPath("/data")
.addLabels(Metapb.StoreLabel.newBuilder()
.setKey("namespace")
.setValue("default")
.build())
.build();
stores[i] = storeService.register(store);
System.out.println("新注册store id = " + Long.toHexString(stores[i].getId()));
}
Metapb.Graph graph = Metapb.Graph.newBuilder()
.setGraphName("defaultGH")
.setPartitionCount(10)
.build();
partitionService.updateGraph(graph);
Thread.sleep(10000);
count = 0;
count += storeService.getStores("").stream()
.filter(store -> store.getState() == Metapb.StoreState.Tombstone)
.count();
Assert.assertEquals(6, count);
}
}

View File

@ -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.pd;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.hugegraph.pd.grpc.Metapb;
import org.junit.Test;
public class PartitionServiceTest {
@Test
public void testPartitionHeartbeat() {
List<Metapb.Shard> shardList = new ArrayList<>();
shardList.add(Metapb.Shard.newBuilder().setStoreId(1).build());
shardList.add(Metapb.Shard.newBuilder().setStoreId(2).build());
shardList.add(Metapb.Shard.newBuilder().setStoreId(3).build());
shardList = new ArrayList<>(shardList);
Metapb.PartitionStats stats = Metapb.PartitionStats.newBuilder()
.addAllShard(shardList).build();
List<Metapb.Shard> shardList2 = new ArrayList<>(stats.getShardList());
Collections.shuffle(shardList2);
shardList2.forEach(shard -> {
System.out.println(shard.getStoreId());
});
}
}

View File

@ -0,0 +1,485 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.apache.hugegraph.pd;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.commons.io.FileUtils;
import org.apache.hugegraph.pd.common.PDException;
import org.apache.hugegraph.pd.config.PDConfig;
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.junit.Assert;
import org.junit.BeforeClass;
public class StoreNodeServiceTest {
static PDConfig pdConfig;
@BeforeClass
public static void init() throws Exception {
String path = "tmp/unitTest";
deleteDirectory(new File(path));
pdConfig = new PDConfig() {{
this.setClusterId(100);
this.setInitialStoreList(
"127.0.0.1:8500,127.0.0.1:8501,127.0.0.1:8502,127.0.0.1:8503,127.0.0.1:8504," +
"127.0.0.1:8505");
}};
pdConfig.setStore(new PDConfig().new Store() {{
this.setMaxDownTime(3600);
this.setKeepAliveTimeout(3600);
}});
pdConfig.setPartition(new PDConfig().new Partition() {{
this.setShardCount(3);
this.setMaxShardsPerStore(3);
}});
pdConfig.setRaft(new PDConfig().new Raft() {{
this.setEnable(false);
}});
pdConfig.setDiscovery(new PDConfig().new Discovery());
pdConfig.setDataPath(path);
ConfigService configService = new ConfigService(pdConfig);
pdConfig = configService.loadConfig();
}
public static byte[] intToByteArray(int i) {
byte[] result = new byte[4];
result[0] = (byte) ((i >> 24) & 0xFF);
result[1] = (byte) ((i >> 16) & 0xFF);
result[2] = (byte) ((i >> 8) & 0xFF);
result[3] = (byte) (i & 0xFF);
return result;
}
public static void deleteDirectory(File dir) {
try {
FileUtils.deleteDirectory(dir);
} catch (IOException e) {
System.out.printf("Failed to start ....,%s%n", e.getMessage());
}
}
// @Test
public void testStoreNodeService() throws PDException {
Assert.assertEquals(pdConfig.getPartition().getTotalCount(),
(long) pdConfig.getInitialStoreMap().size() *
pdConfig.getPartition().getMaxShardsPerStore()
/ pdConfig.getPartition().getShardCount());
StoreNodeService storeService = new StoreNodeService(pdConfig);
int count = 6;
Metapb.Store[] stores = new Metapb.Store[count];
for (int i = 0; i < count; i++) {
Metapb.Store store = Metapb.Store.newBuilder()
.setId(0)
.setAddress("127.0.0.1:850" + i)
.setDeployPath("/data")
.addLabels(Metapb.StoreLabel.newBuilder()
.setKey("namespace")
.setValue("default")
.build())
.build();
stores[i] = storeService.register(store);
System.out.println("新注册store id = " + stores[i].getId());
}
Assert.assertEquals(count, storeService.getStores("").size());
for (Metapb.Store store : stores) {
Metapb.StoreStats stats = Metapb.StoreStats.newBuilder()
.setStoreId(store.getId())
.build();
storeService.heartBeat(stats);
}
Assert.assertEquals(6, storeService.getActiveStores("").size());
Metapb.Graph graph = Metapb.Graph.newBuilder()
.setGraphName("defaultGH")
.setPartitionCount(10)
.build();
// 分配shard
List<Metapb.Shard> shards = storeService.allocShards(graph, 1);
Assert.assertEquals(3, shards.size());
Assert.assertEquals(pdConfig.getPartition().getTotalCount(),
storeService.getShardGroups().size()); // 设置leader
Metapb.Shard leader = Metapb.Shard.newBuilder(shards.get(0))
.setRole(Metapb.ShardRole.Leader).build();
shards = new ArrayList<>(shards);
shards.set(0, leader);
// 增加shard
pdConfig.getPartition().setShardCount(5);
Metapb.ShardGroup shardGroup = Metapb.ShardGroup.newBuilder()
.setId(1)
.addAllShards(shards).build();
shards = storeService.reallocShards(shardGroup);
Assert.assertEquals(5, shards.size());
// 减少shard
pdConfig.getPartition().setShardCount(3);
shards = storeService.reallocShards(shardGroup);
Assert.assertEquals(3, shards.size());
// 包含leaderleader不能被删除
Assert.assertTrue(shards.contains(leader));
// 减少shard
pdConfig.getPartition().setShardCount(1);
graph = Metapb.Graph.newBuilder(graph).build();
shards = storeService.reallocShards(shardGroup);
Assert.assertEquals(1, shards.size());
// 包含leaderleader不能被删除
Assert.assertTrue(shards.contains(leader));
for (Metapb.Store store : stores) {
storeService.removeStore(store.getId());
}
Assert.assertEquals(0, storeService.getStores("").size());
}
// @Test
public void testSplitPartition() throws PDException {
StoreNodeService storeService = new StoreNodeService(pdConfig);
PartitionService partitionService = new PartitionService(pdConfig, storeService);
storeService.init(partitionService);
partitionService.addInstructionListener(new PartitionInstructionListener() {
@Override
public void changeShard(Metapb.Partition partition, ChangeShard changeShard) throws
PDException {
}
@Override
public void transferLeader(Metapb.Partition partition,
TransferLeader transferLeader) throws PDException {
}
@Override
public void splitPartition(Metapb.Partition partition,
SplitPartition splitPartition) throws PDException {
splitPartition.getNewPartitionList().forEach(p -> {
System.out.println("SplitPartition " + p.getId() + " " + p.getStartKey() + "," +
p.getEndKey());
});
}
@Override
public void dbCompaction(Metapb.Partition partition, DbCompaction dbCompaction) throws
PDException {
}
@Override
public void movePartition(Metapb.Partition partition,
MovePartition movePartition) throws PDException {
}
@Override
public void cleanPartition(Metapb.Partition partition,
CleanPartition cleanPartition) throws PDException {
}
@Override
public void changePartitionKeyRange(Metapb.Partition partition,
PartitionKeyRange partitionKeyRange) throws
PDException {
}
});
int count = 6;
Metapb.Store[] stores = new Metapb.Store[count];
for (int i = 0; i < count; i++) {
Metapb.Store store = Metapb.Store.newBuilder()
.setId(0)
.setAddress("127.0.0.1:850" + i)
.setDeployPath("/data")
.addLabels(Metapb.StoreLabel.newBuilder()
.setKey("namespace")
.setValue("default")
.build())
.build();
stores[i] = storeService.register(store);
System.out.println("新注册store id = " + Long.toHexString(stores[i].getId()));
}
Assert.assertEquals(count, storeService.getStores().size());
Metapb.Graph graph = Metapb.Graph.newBuilder()
.setGraphName("defaultGH")
.build();
Metapb.PartitionShard ptShard =
partitionService.getPartitionByCode(graph.getGraphName(), 0);
System.out.println(ptShard.getPartition().getId());
{
Metapb.Partition pt = ptShard.getPartition();
System.out.println(pt.getId() + " " + pt.getStartKey() + "," + pt.getEndKey());
}
Assert.assertEquals(6, storeService.getShardGroups().size());
// storeService.splitShardGroups(ptShard.getPartition().getId(), 4);
Assert.assertEquals(9, storeService.getShardGroups().size());
storeService.getShardGroups().forEach(shardGroup -> {
System.out.println("shardGroup id = " + shardGroup.getId());
});
}
// @Test
public void testPartitionService() throws PDException, ExecutionException,
InterruptedException {
StoreNodeService storeService = new StoreNodeService(pdConfig);
int count = 6;
Metapb.Store[] stores = new Metapb.Store[count];
for (int i = 0; i < count; i++) {
Metapb.Store store = Metapb.Store.newBuilder()
.setId(0)
.setAddress(String.valueOf(i))
.setDeployPath("/data")
.addLabels(Metapb.StoreLabel.newBuilder()
.setKey("namespace")
.setValue("default")
.build())
.build();
stores[i] = storeService.register(store);
System.out.println("新注册store id = " + Long.toHexString(stores[i].getId()));
}
Assert.assertEquals(count, storeService.getStores("").size());
PartitionService partitionService = new PartitionService(pdConfig, storeService);
Metapb.Graph graph = Metapb.Graph.newBuilder()
.setGraphName("defaultGH")
.setPartitionCount(10)
.build();
// 申请分区
Metapb.PartitionShard[] partitions = new Metapb.PartitionShard[10];
for (int i = 0; i < partitions.length; i++) {
partitions[i] =
partitionService.getPartitionShard(graph.getGraphName(), intToByteArray(i));
Assert.assertEquals(3, storeService.getShardGroup(i).getShardsCount());
}
System.out.println(
"分区数量: " + partitionService.getPartitions(graph.getGraphName()).size());
int[] caseNo = {0}; //1 测试增加shard, 2 //测试store下线
Metapb.Shard leader = null;
int[] finalCaseNo = caseNo;
partitionService.addInstructionListener(new PartitionInstructionListener() {
@Override
public void changeShard(Metapb.Partition partition, ChangeShard changeShard) throws
PDException {
switch (finalCaseNo[0]) {
case 2:
Assert.assertEquals(5, storeService.getShardGroup(partition.getId())
.getShardsCount());
break;
case 3:
storeService.getShardGroup(partition.getId()).getShardsList()
.forEach(shard -> {
Assert.assertNotEquals(shard.getStoreId(),
stores[0].getId());
});
break;
}
}
@Override
public void transferLeader(Metapb.Partition partition, TransferLeader transferLeader) {
}
@Override
public void splitPartition(Metapb.Partition partition, SplitPartition splitPartition) {
}
@Override
public void dbCompaction(Metapb.Partition partition, DbCompaction dbCompaction) throws
PDException {
}
@Override
public void movePartition(Metapb.Partition partition,
MovePartition movePartition) throws PDException {
}
@Override
public void cleanPartition(Metapb.Partition partition,
CleanPartition cleanPartition) throws PDException {
}
@Override
public void changePartitionKeyRange(Metapb.Partition partition,
PartitionKeyRange partitionKeyRange)
throws PDException {
}
});
Metapb.Partition partition = partitions[0].getPartition();
leader = Metapb.Shard.newBuilder(
storeService.getShardGroup(partition.getId()).getShardsList().get(0)).build();
Metapb.Shard finalLeader = leader;
partitionService.addStatusListener(new PartitionStatusListener() {
@Override
public void onPartitionChanged(Metapb.Partition partition,
Metapb.Partition newPartition) {
}
@Override
public void onPartitionRemoved(Metapb.Partition partition) {
}
});
// 测试修改图
caseNo[0] = 1;
partitionService.updateGraph(graph);
for (int i = 0; i < partitions.length; i++) {
partitions[i] =
partitionService.getPartitionShard(graph.getGraphName(), intToByteArray(i));
Assert.assertEquals(3, storeService.getShardGroup(i).getShardsCount());
}
graph = Metapb.Graph.newBuilder(graph)
.setGraphName("defaultGH")
.setPartitionCount(10)
.build();
caseNo[0] = 2;
partitionService.updateGraph(graph);
// 测试store离线
caseNo[0] = 3;
partitionService.storeOffline(stores[0]);
Metapb.PartitionStats stats = Metapb.PartitionStats.newBuilder()
.addGraphName(partition.getGraphName())
.setId(partition.getId())
.setLeader(
Metapb.Shard.newBuilder(leader)
.setRole(
Metapb.ShardRole.Leader))
.build();
// 测试leader飘移
caseNo[0] = 4;
partitionService.partitionHeartbeat(stats);
AtomicReference<Metapb.Shard> shard = new AtomicReference<>();
Metapb.PartitionShard ss =
partitionService.getPartitionShardById(partition.getGraphName(), partition.getId());
storeService.getShardList(partition.getId()).forEach(s -> {
if (s.getRole() == Metapb.ShardRole.Leader) {
Assert.assertNull(shard.get());
shard.set(s);
}
});
Assert.assertEquals(leader.getStoreId(), shard.get().getStoreId());
}
// @Test
public void testMergeGraphParams() throws PDException {
StoreNodeService storeService = new StoreNodeService(pdConfig);
PartitionService partitionService = new PartitionService(pdConfig, storeService);
Metapb.Graph dfGraph = Metapb.Graph.newBuilder()
.setPartitionCount(
pdConfig.getPartition().getTotalCount())
.build();
Metapb.Graph graph1 = Metapb.Graph.newBuilder()
.setGraphName("test")
.setPartitionCount(20)
.build();
Metapb.Graph graph2 = Metapb.Graph.newBuilder()
.setGraphName("test")
.setPartitionCount(7).build();
Metapb.Graph graph3 = Metapb.Graph.newBuilder()
.setGraphName("test")
.build();
Metapb.Graph graph4 = Metapb.Graph.newBuilder()
.setGraphName("test")
.build();
Metapb.Graph graph = Metapb.Graph.newBuilder(dfGraph).mergeFrom(graph2).build();
Assert.assertEquals(graph2.getGraphName(), graph.getGraphName());
Assert.assertEquals(graph2.getPartitionCount(), graph.getPartitionCount());
graph = Metapb.Graph.newBuilder(dfGraph).mergeFrom(graph3).build();
Assert.assertEquals(graph3.getGraphName(), graph.getGraphName());
Assert.assertEquals(dfGraph.getPartitionCount(), graph.getPartitionCount());
graph = Metapb.Graph.newBuilder(dfGraph).mergeFrom(graph4).build();
Assert.assertEquals(graph4.getGraphName(), graph.getGraphName());
Assert.assertEquals(dfGraph.getPartitionCount(), graph.getPartitionCount());
}
// @Test
public void test() {
int[] n = new int[3];
if (++n[2] > 1) {
System.out.println(n[2]);
}
if (++n[2] > 1) {
System.out.println(n[2]);
}
if (++n[2] > 1) {
System.out.println(n[2]);
}
}
}

View File

@ -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.pd;
import java.io.File;
public class UnitTestBase {
public static boolean deleteDir(File dir) {
if (dir.isDirectory()) {
for (File file : dir.listFiles()) {
deleteDir(file);
}
}
return dir.delete();
}
}

View File

@ -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.pd.common;
// import org.junit.Test;
import java.nio.charset.StandardCharsets;
import org.apache.hugegraph.pd.common.PartitionUtils;
public class PartitionUtilsTest {
// @Test
public void testHashCode() {
int partCount = 10;
int partSize = PartitionUtils.MAX_VALUE / partCount + 1;
int[] counter = new int[partCount];
for (int i = 0; i < 10000; i++) {
String s = String.format("BATCH-GET-UNIT-%02d", i);
int c = PartitionUtils.calcHashcode(s.getBytes(StandardCharsets.UTF_8));
counter[c / partSize]++;
}
for (int i = 0; i < counter.length; i++) {
System.out.println(i + " " + counter[i]);
}
}
}

Some files were not shown because too many files have changed in this diff Show More