Lindorm 图引擎兼容 Apache TinkerPop Gremlin 协议,支持通过官方 gremlin-driver 在 Java 应用中提交 Gremlin 查询。本文介绍 Maven 依赖、连接方式、参数绑定与超时设置,并提供一个可直接编译运行的完整示例。
前提条件
已开通 Lindorm 图引擎,并配置白名单。
已完成图的 Schema 初始化,如未完成可参考Schema 定义。
本地已安装 JDK 8+ 和 Maven 3.6+。
Maven 依赖
在项目的 pom.xml 中加入 gremlin-driver 依赖。Lindorm 图引擎兼容 TinkerPop 3.7 及以上版本,建议使用 3.8.x:
<dependency>
<groupId>org.apache.tinkerpop</groupId>
<artifactId>gremlin-driver</artifactId>
<version>3.8.1</version>
</dependency>连接方式
Lindorm 图引擎支持两种常见的连接方式:通过 YAML 配置文件加载,或直接在代码中构造 Cluster 对象。生产环境建议使用 YAML 文件,便于将连接参数与代码解耦。
方式一:YAML 配置
创建 remote.yaml:
hosts: [实例名称-proxy-graph-vpc.lindorm.aliyuncs.com]
port: 16032
username: 账号
password: 密码
path: /gremlin/子图名称
connectionPool: {
maxSize: 16,
minSize: 16,
maxInProcessPerConnection: 4,
maxContentLength: ${yourExpectedSize}
}
serializer: { className: org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV1, config: { serializeResultToString: false }}
主要字段说明:
${yourExpectedSize}: 默认为65536,如果您的结果集过大,有可能会遇到Max frame length of 65536 has been exceeded该类问题,根据业务调整该值大小便可解决该问题。maxSize与minSize:客户端具有链接池,一个客户端支持多线程并发访问,需要把这两个参数设置成一样,且大于等于您的客户端线程访问数,这样也能避免Timed out while waiting for an available host错误。序列化协议(尽量使用
GraphBinaryMessageSerializerV1序列化协议):GraphBinaryMessageSerializerV1序列化协议:返回的Vertex、Edge为ReferenceVertex,ReferenceEdge,序列化时不会返回properties。性能好。
GraphSONMessageSerializerV3d0序列化协议:返回的 Vertex、Edge为DetachedVertex,DetachedEdge,序列化时会返回所有的properties,因此在Vertex可能存在大量properties时,会增加很多额外的序列化开销。
在 Java 代码中加载该配置并创建 Cluster:
import org.apache.tinkerpop.gremlin.driver.Client;
import org.apache.tinkerpop.gremlin.driver.Cluster;
Cluster cluster = Cluster.open("config/remote.yaml");
Client client = cluster.connect();
client.init();
// ... 执行操作 ...
client.close();
cluster.close();方式二:代码构造 Cluster
import org.apache.tinkerpop.gremlin.driver.Client;
import org.apache.tinkerpop.gremlin.driver.Cluster;
Cluster cluster = Cluster.build("ld-xx-proxy-graph-pub.lindorm.aliyuncs.com")
.port(16032)
.serializer(Serializers.GRAPHBINARY_V1)
.maxConnectionPoolSize(32)
.minConnectionPoolSize(32)
.credentials("root", "xx")
.path("/gremlin/default")
.create();
Client client = cluster.connect();
client.init();
// ... 执行操作 ...
client.close();
cluster.close();
参数绑定与超时设置
Java 客户端通过 RequestOptions.addParameter 将变量传入查询语句,避免拼接字符串,提升安全性和可读性。
import org.apache.tinkerpop.gremlin.driver.RequestOptions;
import org.apache.tinkerpop.gremlin.driver.Result;
import org.apache.tinkerpop.gremlin.driver.ResultSet;
String dsl = "g.V(G___id).hasLabel(G___label)";
RequestOptions.Builder options = RequestOptions.build().timeout(30000);
options.addParameter("G___id", "marko");
options.addParameter("G___label", "person");
ResultSet resultSet = client.submit(dsl, options.create());
List<Result> results = resultSet.all().join();
for (Result r : results) {
System.out.println(r.getObject());
}
//执行client关闭等操作
绑定变量名推荐使用 G___ 前缀加字段名的格式(如 G___id、G___label),在 DSL 中直接引用变量名即可,无需加引号。
完整示例
以下示例演示如何创建一个独立的 Maven 项目,连接 Lindorm 图引擎并执行 Gremlin 查询,包含初始化项目、配置依赖、编写主类、编译运行四个步骤。示例中集成的向量检索能力为可选模块,若业务无相关需求可直接移除。
初始化项目目录
mkdir lindorm-graph-java-demo
cd lindorm-graph-java-demo
mkdir -p config
mkdir -p src/main/java/com/aliyun/lindorm/demo创建 config/remote.yaml
hosts: [实例名称-proxy-graph-vpc.lindorm.aliyuncs.com]
port: 16032
username: 账号
password: 密码
path: /gremlin/子图名称
connectionPool: {
maxSize: 16,
minSize: 16,
maxInProcessPerConnection: 4,
maxContentLength: ${yourExpectedSize}
}
serializer: { className: org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV1, config: { serializeResultToString: false }}
创建 pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.aliyun.lindorm</groupId>
<artifactId>lindorm-graph-java-demo</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<slf4j.version>2.0.17</slf4j.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${slf4j.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.tinkerpop</groupId>
<artifactId>gremlin-driver</artifactId>
<version>3.8.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>${slf4j.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.aliyun.lindorm.demo.LindormGraphExampleDemo</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
编写 LindormGraphExampleDemo.java
在 src/main/java/com/example/ 目录下新建 LindormGraphExampleDemo.java:
package com.aliyun.lindorm.demo;
import org.apache.tinkerpop.gremlin.driver.Client;
import org.apache.tinkerpop.gremlin.driver.Cluster;
import org.apache.tinkerpop.gremlin.driver.Result;
import org.apache.tinkerpop.gremlin.driver.ResultSet;
import org.apache.tinkerpop.gremlin.driver.RequestOptions;
import org.apache.tinkerpop.gremlin.util.ser.Serializers;
import org.yaml.snakeyaml.Yaml;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
/**
* 图数据库 Java 客户端示例(对应 example_client.py)
* 1. 调用 /schema/mgmt/apply 初始化 Schema(含向量属性)
* 2. 插入顶点(person 含128维向量、software)和边(knows、created)
* 3. 查询顶点、图遍历、向量检索
*/
public class LindormGraphExampleDemo {
private static final int REQUEST_TIMEOUT_MS = 30000;
private static final int VECTOR_DIM = 128;
private static final Random RANDOM = new Random();
// ========== Schema JSON ==========
// 如果不使用向量,请去掉 embedding 向量列
private static final String SCHEMA_JSON =
"{\"vertexLabels\":["
+ "{\"label\":\"person\",\"properties\":["
+ "{\"name\":\"name\",\"dataType\":\"STRING\"},"
+ "{\"name\":\"age\",\"dataType\":\"INT\"},"
+ "{\"name\":\"city\",\"dataType\":\"STRING\"},"
+ "{\"name\":\"embedding\",\"dataType\":\"VECTOR_FLOAT\","
+ "\"vectorMeta\":{\"dimension\":128,\"distanceMethod\":\"EUCLIDEAN\","
+ "\"indexType\":\"HNSW\",\"indexParams\":{\"M\":\"24\",\"EF_CONSTRUCT\":\"200\"}}}"
+ "]},"
+ "{\"label\":\"software\",\"properties\":["
+ "{\"name\":\"name\",\"dataType\":\"STRING\"},"
+ "{\"name\":\"lang\",\"dataType\":\"STRING\"},"
+ "{\"name\":\"price\",\"dataType\":\"INT\"}"
+ "]}"
+ "],\"edgeLabels\":["
+ "{\"label\":\"knows\",\"properties\":["
+ "{\"name\":\"date\",\"dataType\":\"STRING\"},"
+ "{\"name\":\"weight\",\"dataType\":\"DOUBLE\"}"
+ "]},"
+ "{\"label\":\"created\",\"properties\":["
+ "{\"name\":\"date\",\"dataType\":\"STRING\"},"
+ "{\"name\":\"weight\",\"dataType\":\"DOUBLE\"}"
+ "]}"
+ "],\"connections\":["
+ "{\"edgeLabel\":\"knows\",\"outVertex\":\"person\",\"inVertex\":\"person\"},"
+ "{\"edgeLabel\":\"created\",\"outVertex\":\"person\",\"inVertex\":\"software\"}"
+ "]}";
// ========== Gremlin DSL ==========
private static final String ADD_PERSON_DSL =
"g.addV(G___label).property(id, G___id)"
+ ".property('name', G___name)"
+ ".property('age', G___age)"
+ ".property('city', G___city)"
+ ".property('embedding', G___embedding)";
private static final String ADD_SOFTWARE_DSL =
"g.addV(G___label).property(id, G___id)"
+ ".property('name', G___name)"
+ ".property('lang', G___lang)"
+ ".property('price', G___price)";
private static final String ADD_EDGE_DSL =
"g.V(G___fromId).hasLabel(G___fromLabel).addE(G___edgeLabel).to(__.V(G___toId).hasLabel(G___toLabel))"
+ ".property('date', G___date)"
+ ".property('weight', G___weight)";
private static final String QUERY_VERTEX_DSL =
"g.V(G___id).hasLabel(G___label).valueMap(true)";
private static final String QUERY_NEIGHBORS_DSL =
"g.V(G___id).hasLabel(G___label).out(G___edgeLabel).valueMap(true)";
private static final String VECTOR_SEARCH_DSL =
"g.V().hasLabel(G___label).hasVector(G___prop, G___vector, G___topK).valueMap(true)";
// ========== main ==========
public static void main(String[] args) throws Exception {
String yamlPath = args.length > 0 ? args[0] : "config/remote.yaml";
Config config = loadConfig(yamlPath);
// 1. 初始化 Schema
printSection("1. 初始化 Schema");
applySchema(config);
// 2. 创建 Gremlin 客户端
Cluster cluster = null;
Client client = null;
try {
// 客户端包含连接池,线程安全,支持多线程并发
cluster = Cluster.build(new File(yamlPath)).create();
// cluster = Cluster.build("ld-xx-proxy-graph-pub.lindorm.aliyuncs.com")
// .port(16032)
// .serializer(Serializers.GRAPHBINARY_V1)
// .maxConnectionPoolSize(32)
// .minConnectionPoolSize(32)
// .credentials("root", "xx")
// .path("/gremlin/default")
// .create();
client = cluster.connect().init();
// 3. 插入数据
// 等待 Schema(含向量索引)在服务端构建完成,避免后续插入/检索时索引尚未就绪
Thread.sleep(10000);
printSection("2. 插入顶点和边");
System.out.println("\n--- 添加 person 顶点 ---");
addPersons(client);
System.out.println("\n--- 添加 software 顶点 ---");
addSoftwares(client);
System.out.println("\n--- 添加边 ---");
addEdges(client);
// 4. 查询
printSection("3. 查询");
System.out.println("\n--- 查询顶点 marko ---");
queryVertex(client, "marko", "person");
System.out.println("\n--- marko 认识的人 ---");
queryNeighbors(client, "marko", "person", "knows");
System.out.println("\n--- marko 创建的软件 ---");
queryNeighbors(client, "marko", "person", "created");
System.out.println("\n--- 向量检索 top 3 ---");
queryVectorSearch(client, "person", "embedding", 3);
} finally {
if (client != null) client.close();
if (cluster != null) cluster.close();
}
System.out.println("\n完成!");
}
// ========== 1. Schema 初始化 ==========
private static void applySchema(Config config) throws Exception {
String url = config.schemaBaseUrl()
+ "/schema/mgmt/apply?db=" + URLEncoder.encode(config.dbName, "UTF-8");
String result = httpPost(url, SCHEMA_JSON, config);
System.out.println("Schema 初始化: " + result);
}
// ========== 2. 插入数据 ==========
private static void addPersons(Client client) {
String[][] persons = {
{"marko", "marko", "29", "Beijing"},
{"vadas", "vadas", "27", "Hongkong"},
{"josh", "josh", "32", "Beijing"},
{"peter", "peter", "35", "Shanghai"},
};
for (String[] p : persons) {
Map<String, Object> bindings = new HashMap<>();
bindings.put("G___label", "person");
bindings.put("G___id", p[0]);
bindings.put("G___name", p[1]);
bindings.put("G___age", Integer.parseInt(p[2]));
bindings.put("G___city", p[3]);
bindings.put("G___embedding", randomVector(VECTOR_DIM));
List<Result> results = execute(client, ADD_PERSON_DSL, bindings);
System.out.println("添加顶点: " + p[0] + " -> " + results);
}
}
private static void addSoftwares(Client client) {
String[][] softwares = {
{"lop", "lop", "java", "328"},
{"ripple", "ripple", "java", "199"},
};
for (String[] s : softwares) {
Map<String, Object> bindings = new HashMap<>();
bindings.put("G___label", "software");
bindings.put("G___id", s[0]);
bindings.put("G___name", s[1]);
bindings.put("G___lang", s[2]);
bindings.put("G___price", Integer.parseInt(s[3]));
List<Result> results = execute(client, ADD_SOFTWARE_DSL, bindings);
System.out.println("添加顶点: " + s[0] + " -> " + results);
}
}
private static void addEdges(Client client) {
// {from, fromLabel, to, toLabel, edgeLabel, date, weight}
String[][] edges = {
{"marko", "person", "vadas", "person", "knows", "20160110", "0.5"},
{"marko", "person", "josh", "person", "knows", "20130220", "1.0"},
{"marko", "person", "lop", "software", "created", "20171210", "0.4"},
{"josh", "person", "lop", "software", "created", "20091111", "0.4"},
{"josh", "person", "ripple", "software", "created", "20171210", "1.0"},
{"peter", "person", "lop", "software", "created", "20170324", "0.2"},
};
for (String[] e : edges) {
Map<String, Object> bindings = new HashMap<>();
bindings.put("G___fromId", e[0]);
bindings.put("G___fromLabel", e[1]);
bindings.put("G___toId", e[2]);
bindings.put("G___toLabel", e[3]);
bindings.put("G___edgeLabel", e[4]);
bindings.put("G___date", e[5]);
bindings.put("G___weight", Double.parseDouble(e[6]));
List<Result> results = execute(client, ADD_EDGE_DSL, bindings);
System.out.println("添加边: " + e[0] + " -[" + e[4] + "]-> " + e[2] + " -> " + results);
}
}
// ========== 3. 查询 ==========
private static void queryVertex(Client client, String vertexId, String label) {
Map<String, Object> bindings = new HashMap<>();
bindings.put("G___id", vertexId);
bindings.put("G___label", label);
List<Result> results = execute(client, QUERY_VERTEX_DSL, bindings);
for (Result r : results) {
System.out.println(r.getObject());
}
}
private static void queryNeighbors(Client client, String vertexId, String label, String edgeLabel) {
Map<String, Object> bindings = new HashMap<>();
bindings.put("G___id", vertexId);
bindings.put("G___label", label);
bindings.put("G___edgeLabel", edgeLabel);
List<Result> results = execute(client, QUERY_NEIGHBORS_DSL, bindings);
for (Result r : results) {
System.out.println(r.getObject());
}
}
private static void queryVectorSearch(Client client, String label, String prop, int topK) {
List<Float> queryVector = randomVector(VECTOR_DIM);
Map<String, Object> bindings = new HashMap<>();
bindings.put("G___label", label);
bindings.put("G___prop", prop);
bindings.put("G___vector", queryVector);
bindings.put("G___topK", topK);
List<Result> results = execute(client, VECTOR_SEARCH_DSL, bindings);
for (Result r : results) {
System.out.println(r.getObject());
}
}
// ========== 工具方法 ==========
/** 执行 Gremlin DSL,通过 RequestOptions 传递 bindings 参数。 */
private static List<Result> execute(Client client, String dsl, Map<String, Object> bindings) {
RequestOptions.Builder options = RequestOptions.build().timeout(REQUEST_TIMEOUT_MS);
for (Map.Entry<String, Object> entry : bindings.entrySet()) {
options.addParameter(entry.getKey(), entry.getValue());
}
ResultSet resultSet = client.submit(dsl, options.create());
return resultSet.all().join();
}
/** 生成随机浮点向量。 */
private static List<Float> randomVector(int dim) {
List<Float> vec = new ArrayList<>(dim);
for (int i = 0; i < dim; i++) {
vec.add(Math.round((RANDOM.nextFloat() * 2 - 1) * 1000000f) / 1000000f);
}
return vec;
}
/** 发送带 Basic Auth 的 JSON POST 请求。 */
private static String httpPost(String urlValue, String jsonBody, Config config) throws Exception {
HttpURLConnection conn = (HttpURLConnection) new URL(urlValue).openConnection();
conn.setRequestMethod("POST");
setBasicAuth(conn, config);
conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
conn.setConnectTimeout(10000);
conn.setReadTimeout(30000);
conn.setDoOutput(true);
try (OutputStream out = conn.getOutputStream()) {
out.write(jsonBody.getBytes(StandardCharsets.UTF_8));
}
int code = conn.getResponseCode();
InputStream input = code >= 400 ? conn.getErrorStream() : conn.getInputStream();
if (input == null) {
return "HTTP " + code;
}
StringBuilder body = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
body.append(line);
}
}
return "HTTP " + code + ": " + body;
}
/** 设置 HTTP Basic Auth 请求头。 */
private static void setBasicAuth(HttpURLConnection conn, Config config) {
conn.setRequestProperty("Authorization", "Basic " + Base64.getEncoder()
.encodeToString((config.username + ":" + config.password).getBytes(StandardCharsets.UTF_8)));
}
/** 从 Gremlin remote.yaml 文件加载连接配置。 */
private static Config loadConfig(String path) throws Exception {
Map<?, ?> yaml;
try (InputStream in = new FileInputStream(path)) {
yaml = (Map<?, ?>) new Yaml().load(in);
}
Config config = new Config();
config.remoteYamlPath = path;
if (yaml.get("hosts") != null) {
Object hosts = yaml.get("hosts");
config.host = hosts instanceof List && !((List<?>) hosts).isEmpty()
? String.valueOf(((List<?>) hosts).get(0))
: String.valueOf(hosts);
}
if (yaml.get("port") != null) {
config.gremlinPort = Integer.parseInt(String.valueOf(yaml.get("port")));
}
if (yaml.get("username") != null) {
config.username = String.valueOf(yaml.get("username"));
}
if (yaml.get("password") != null) {
config.password = String.valueOf(yaml.get("password"));
}
if (yaml.get("path") != null) {
String value = String.valueOf(yaml.get("path"));
config.dbName = value.substring(value.lastIndexOf('/') + 1);
}
return config;
}
private static void printSection(String title) {
String line = new String(new char[50]).replace('\0', '=');
System.out.println("\n" + line);
System.out.println(title);
System.out.println(line);
}
/** 运行配置。 */
private static class Config {
String remoteYamlPath;
String host = "127.0.0.1";
int gremlinPort = 8182;
String dbName = "default";
String username = "admin";
String password = "admin";
String schemaBaseUrl() {
return "http://" + host + ":" + gremlinPort;
}
}
}
编译并运行
在项目根目录下执行以下命令打包并运行:
mvn clean package -DskipTests
java -jar target/lindorm-graph-java-demo-1.0.0.jar config/remote.yaml