package com.jvector.test.unit;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;
import static org.junit.jupiter.api.Assertions.*;
/**
* 向量操作单元测试
*/
public class VectorTest {
private Vector vector;
private float[] testData;
@BeforeEach
void setUp() {
testData = new float[]{1.0f, 2.0f, 3.0f, 4.0f};
vector = new Vector(testData);
}
@Test
void testVectorCreation() {
assertNotNull(vector);
assertEquals(4, vector.getDimension());
assertArrayEquals(testData, vector.getData());
}
@Test
void testVectorNormalization() {
Vector normalized = vector.normalize();
// 计算预期的L2范数
double norm = Math.sqrt(1 + 4 + 9 + 16);
float[] expected = {
(float)(1.0 / norm),
(float)(2.0 / norm),
(float)(3.0 / norm),
(float)(4.0 / norm)
};
assertArrayEquals(expected, normalized.getData(), 1e-6f);
}
@Test
void testInvalidVectorCreation() {
assertThrows(IllegalArgumentException.class, () -> new Vector(null));
assertThrows(IllegalArgumentException.class, () -> new Vector(new float[]{}));
}
}
/**
* 距离度量单元测试
*/
public class DistanceMetricTest {
private EuclideanDistance euclideanDistance;
private CosineSimilarity cosineSimilarity;
@BeforeEach
void setUp() {
euclideanDistance = new EuclideanDistance();
cosineSimilarity = new CosineSimilarity();
}
@Test
void testEuclideanDistance() {
float[] v1 = {1.0f, 2.0f, 3.0f};
float[] v2 = {4.0f, 5.0f, 6.0f};
float distance = euclideanDistance.distance(v1, v2);
float expected = (float) Math.sqrt(9 + 9 + 9); // sqrt(27)
assertEquals(expected, distance, 1e-6f);
}
@Test
void testCosineSimilarity() {
float[] v1 = {1.0f, 0.0f, 0.0f};
float[] v2 = {0.0f, 1.0f, 0.0f};
float similarity = cosineSimilarity.similarity(v1, v2);
assertEquals(0.0f, similarity, 1e-6f); // 正交向量
float[] v3 = {1.0f, 0.0f, 0.0f};
float[] v4 = {2.0f, 0.0f, 0.0f};
similarity = cosineSimilarity.similarity(v3, v4);
assertEquals(1.0f, similarity, 1e-6f); // 平行向量
}
}
/**
* HNSW索引单元测试
*/
public class HnswIndexTest {
private HnswIndex index;
private HnswConfig config;
@BeforeEach
void setUp() {
config = HnswConfig.builder()
.m(16)
.efConstruction(200)
.maxLevel(5)
.build();
index = new HnswIndex(config, new EuclideanDistance());
}
@Test
void testIndexCreation() {
assertNotNull(index);
assertEquals(0, index.size());
}
@Test
void testAddVector() {
Vector vector = new Vector(new float[]{1.0f, 2.0f, 3.0f});
long id = index.add(vector);
assertTrue(id > 0);
assertEquals(1, index.size());
assertTrue(index.contains(id));
}
@Test
void testSearch() {
// 添加测试向量
List<Vector> vectors = generateTestVectors(100);
for (Vector vector : vectors) {
index.add(vector);
}
// 搜索
Vector query = vectors.get(0);
List<SearchResult> results = index.search(query, 10);
assertNotNull(results);
assertTrue(results.size() <= 10);
// 第一个结果应该是查询向量本身(距离为0)
assertEquals(0.0f, results.get(0).getDistance(), 1e-6f);
}
private List<Vector> generateTestVectors(int count) {
Random random = new Random(42);
List<Vector> vectors = new ArrayList<>();
for (int i = 0; i < count; i++) {
float[] data = new float[10];
for (int j = 0; j < 10; j++) {
data[j] = random.nextFloat();
}
vectors.add(new Vector(data));
}
return vectors;
}
}
/**
* 向量数据库集成测试
*/
@SpringBootTest
@TestPropertySource(properties = {
"jvector.storage.path=/tmp/test-vector-db",
"jvector.index.ef-construction=100"
})
public class VectorDatabaseIntegrationTest {
@Autowired
private VectorDatabaseManager databaseManager;
@Autowired
private TestDataGenerator dataGenerator;
private VectorDatabase database;
@BeforeEach
void setUp() {
CreateDatabaseRequest request = CreateDatabaseRequest.builder()
.name("test-db")
.dimensions(128)
.distanceMetric("euclidean")
.user("test-user")
.build();
database = databaseManager.createDatabase(request).join();
}
@AfterEach
void tearDown() {
if (database != null) {
databaseManager.deleteDatabase("test-db", "test-user").join();
}
}
@Test
void testCrudOperations() {
// 添加向量
Vector vector = dataGenerator.generateRandomVector(128);
Long id = database.addVector(vector).join();
assertNotNull(id);
// 搜索向量
List<SearchResult> results = database.search(vector, 1, SearchOptions.defaultOptions()).join();
assertEquals(1, results.size());
assertEquals(id, results.get(0).getId());
// 删除向量
boolean deleted = database.deleteVector(id).join();
assertTrue(deleted);
// 验证删除
results = database.search(vector, 1, SearchOptions.defaultOptions()).join();
assertTrue(results.isEmpty());
}
@Test
void testBatchOperations() {
List<Vector> vectors = dataGenerator.generateRandomVectors(1000, 128);
// 批量添加
List<Long> ids = database.addVectorsBatch(vectors).join();
assertEquals(1000, ids.size());
// 批量搜索
Vector query = vectors.get(0);
List<SearchResult> results = database.search(query, 10, SearchOptions.defaultOptions()).join();
assertEquals(10, results.size());
// 验证结果排序
for (int i = 1; i < results.size(); i++) {
assertTrue(results.get(i-1).getDistance() <= results.get(i).getDistance());
}
}
@Test
void testPersistence() {
// 添加数据
List<Vector> vectors = dataGenerator.generateRandomVectors(100, 128);
database.addVectorsBatch(vectors).join();
// 保存索引
database.saveIndex().join();
// 重新加载
database.loadIndex().join();
// 验证数据完整性
Vector query = vectors.get(0);
List<SearchResult> results = database.search(query, 5, SearchOptions.defaultOptions()).join();
assertFalse(results.isEmpty());
}
}
/**
* 跨数据库操作集成测试
*/
@SpringBootTest
public class CrossDatabaseIntegrationTest {
@Autowired
private VectorDatabaseManager databaseManager;
@Autowired
private CrossDatabaseSearchService searchService;
private List<String> databaseNames;
@BeforeEach
void setUp() {
databaseNames = Arrays.asList("db1", "db2", "db3");
for (String dbName : databaseNames) {
CreateDatabaseRequest request = CreateDatabaseRequest.builder()
.name(dbName)
.dimensions(64)
.distanceMetric("cosine")
.user("test-user")
.build();
databaseManager.createDatabase(request).join();
}
// 填充测试数据
populateTestData();
}
@Test
void testCrossDatabaseSearch() {
Vector query = new Vector(generateRandomData(64));
CrossSearchRequest request = CrossSearchRequest.builder()
.databases(databaseNames)
.query(query)
.k(20)
.options(SearchOptions.defaultOptions())
.build();
CrossSearchResponse response = searchService.search(request, "test-user").join();
assertNotNull(response);
assertEquals(20, response.getResults().size());
// 验证结果来自多个数据库
Set<String> sourceDatabases = response.getDatabaseResults().stream()
.map(DatabaseSearchResult::getDatabaseName)
.collect(Collectors.toSet());
assertTrue(sourceDatabases.size() > 1);
}
private void populateTestData() {
for (String dbName : databaseNames) {
Optional<VectorDatabase> db = databaseManager.getDatabase(dbName, "test-user");
if (db.isPresent()) {
List<Vector> vectors = generateTestVectors(50);
db.get().addVectorsBatch(vectors).join();
}
}
}
}
/**
* 性能基准测试
*/
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Benchmark)
public class VectorSearchBenchmark {
private HnswIndex index;
private List<Vector> queries;
@Setup(Level.Trial)
public void setUp() {
// 创建索引
HnswConfig config = HnswConfig.builder()
.m(16)
.efConstruction(200)
.build();
index = new HnswIndex(config, new EuclideanDistance());
// 添加100万个向量
Random random = new Random(42);
for (int i = 0; i < 1_000_000; i++) {
float[] data = new float[128];
for (int j = 0; j < 128; j++) {
data[j] = random.nextGaussian();
}
index.add(new Vector(data));
}
// 生成查询向量
queries = generateQueries(1000);
}
@Benchmark
public List<SearchResult> searchK10() {
Vector query = queries.get(ThreadLocalRandom.current().nextInt(queries.size()));
return index.search(query, 10);
}
@Benchmark
public List<SearchResult> searchK100() {
Vector query = queries.get(ThreadLocalRandom.current().nextInt(queries.size()));
return index.search(query, 100);
}
@Benchmark
@Group("concurrent")
@GroupThreads(4)
public List<SearchResult> concurrentSearch() {
Vector query = queries.get(ThreadLocalRandom.current().nextInt(queries.size()));
return index.search(query, 10);
}
private List<Vector> generateQueries(int count) {
Random random = new Random(123);
List<Vector> queries = new ArrayList<>();
for (int i = 0; i < count; i++) {
float[] data = new float[128];
for (int j = 0; j < 128; j++) {
data[j] = (float) random.nextGaussian();
}
queries.add(new Vector(data));
}
return queries;
}
}
/**
* 内存使用测试
*/
public class MemoryUsageTest {
@Test
void testIndexMemoryUsage() {
Runtime runtime = Runtime.getRuntime();
// 记录初始内存
long initialMemory = runtime.totalMemory() - runtime.freeMemory();
// 创建索引并添加向量
HnswIndex index = new HnswIndex(HnswConfig.defaultConfig(), new EuclideanDistance());
Random random = new Random(42);
int vectorCount = 100_000;
for (int i = 0; i < vectorCount; i++) {
float[] data = new float[256];
for (int j = 0; j < 256; j++) {
data[j] = random.nextFloat();
}
index.add(new Vector(data));
}
// 强制GC并测量内存
System.gc();
Thread.yield();
long finalMemory = runtime.totalMemory() - runtime.freeMemory();
long memoryUsed = finalMemory - initialMemory;
// 计算平均每个向量的内存开销
double memoryPerVector = (double) memoryUsed / vectorCount;
logger.info("Memory used: {} bytes", memoryUsed);
logger.info("Memory per vector: {:.2f} bytes", memoryPerVector);
// 验证内存使用在合理范围内
assertTrue(memoryPerVector < 2048, "Memory usage per vector is too high");
}
}
# Dockerfile
FROM openjdk:11-jre-slim
LABEL maintainer="JVector Team"
LABEL version="1.0.0"
# 安装必要工具
RUN apt-get update && apt-get install -y \
curl \
&& rm -rf /var/lib/apt/lists/*
# 创建应用目录
WORKDIR /app
# 复制应用文件
COPY target/jvector-*.jar app.jar
COPY config/ config/
COPY scripts/ scripts/
# 设置环境变量
ENV JAVA_OPTS="-Xmx4g -Xms2g -XX:+UseG1GC"
ENV JVECTOR_CONFIG_PATH="/app/config"
ENV JVECTOR_DATA_PATH="/data"
# 创建数据目录
RUN mkdir -p /data && \
chmod +x scripts/*.sh
# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
# 暴露端口
EXPOSE 8080
# 启动应用
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]
# docker-compose.yml
version: '3.8'
services:
jvector:
build: .
ports:
- "8080:8080"
environment:
- JAVA_OPTS=-Xmx4g -Xms2g
- JVECTOR_STORAGE_TYPE=file
- JVECTOR_METRICS_ENABLED=true
volumes:
- jvector_data:/data
- jvector_logs:/app/logs
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
restart: unless-stopped
prometheus:
image: prom/prometheus:latest
ports:
- "9090:9090"
volumes:
- ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
restart: unless-stopped
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin123
volumes:
- grafana_data:/var/lib/grafana
- ./monitoring/grafana/dashboards:/etc/grafana/provisioning/dashboards
restart: unless-stopped
volumes:
jvector_data:
jvector_logs:
prometheus_data:
grafana_data:
# k8s-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: jvector
labels:
app: jvector
spec:
replicas: 3
selector:
matchLabels:
app: jvector
template:
metadata:
labels:
app: jvector
spec:
containers:
- name: jvector
image: jvector:latest
ports:
- containerPort: 8080
env:
- name: JAVA_OPTS
value: "-Xmx4g -Xms2g -XX:+UseG1GC"
- name: JVECTOR_CLUSTER_MODE
value: "true"
- name: JVECTOR_NODE_ID
valueFrom:
fieldRef:
fieldPath: metadata.name
resources:
requests:
memory: "2Gi"
cpu: "1000m"
limits:
memory: "6Gi"
cpu: "4000m"
volumeMounts:
- name: data-storage
mountPath: /data
- name: config
mountPath: /app/config
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 60
periodSeconds: 30
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
volumes:
- name: data-storage
persistentVolumeClaim:
claimName: jvector-data
- name: config
configMap:
name: jvector-config
---
apiVersion: v1
kind: Service
metadata:
name: jvector-service
spec:
selector:
app: jvector
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: LoadBalancer
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: jvector-data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 100Gi
storageClassName: fast-ssd
---
apiVersion: v1
kind: ConfigMap
metadata:
name: jvector-config
data:
application.yml: |
jvector:
storage:
type: distributed
path: /data
cluster:
enabled: true
discovery:
type: kubernetes
namespace: default
monitoring:
metrics:
enabled: true
prometheus:
enabled: true
/**
* 指标收集器
*/
@Component
public class MetricsCollector {
private final MeterRegistry meterRegistry;
private final Timer searchTimer;
private final Counter addCounter;
private final Gauge indexSizeGauge;
public MetricsCollector(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
this.searchTimer = Timer.builder("jvector.search.duration")
.description("Vector search operation duration")
.register(meterRegistry);
this.addCounter = Counter.builder("jvector.add.total")
.description("Total number of vectors added")
.register(meterRegistry);
this.indexSizeGauge = Gauge.builder("jvector.index.size")
.description("Current index size")
.register(meterRegistry, this, MetricsCollector::getCurrentIndexSize);
}
/**
* 记录搜索操作
*/
public void recordSearch(long duration, int resultCount, String database) {
searchTimer.record(duration, TimeUnit.NANOSECONDS);
meterRegistry.counter("jvector.search.total",
"database", database,
"result_count", String.valueOf(resultCount))
.increment();
}
/**
* 记录添加操作
*/
public void recordAdd(String database) {
addCounter.increment(Tags.of("database", database));
}
/**
* 记录错误
*/
public void recordError(String operation, String errorType) {
meterRegistry.counter("jvector.errors.total",
"operation", operation,
"error_type", errorType)
.increment();
}
private double getCurrentIndexSize() {
// 实现获取当前索引大小的逻辑
return 0.0; // 简化实现
}
}
/**
* 应用健康检查
*/
@Component
public class JVectorHealthIndicator implements HealthIndicator {
private final VectorDatabaseManager databaseManager;
private final MetricsCollector metricsCollector;
@Override
public Health health() {
try {
// 检查数据库管理器状态
if (!databaseManager.isHealthy()) {
return Health.down()
.withDetail("database_manager", "unhealthy")
.build();
}
// 检查内存使用情况
MemoryUsage heapUsage = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
double memoryUsagePercentage = (double) heapUsage.getUsed() / heapUsage.getMax() * 100;
if (memoryUsagePercentage > 90) {
return Health.down()
.withDetail("memory_usage", memoryUsagePercentage + "%")
.withDetail("status", "high memory usage")
.build();
}
return Health.up()
.withDetail("databases", databaseManager.getDatabaseCount())
.withDetail("memory_usage", String.format("%.2f%%", memoryUsagePercentage))
.build();
} catch (Exception e) {
return Health.down()
.withDetail("error", e.getMessage())
.build();
}
}
}
# prometheus.yml
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'jvector'
static_configs:
- targets: ['jvector:8080']
metrics_path: '/actuator/prometheus'
scrape_interval: 10s
rule_files:
- "jvector_alerts.yml"
alerting:
alertmanagers:
- static_configs:
- targets:
- alertmanager:9093
# jvector_alerts.yml
groups:
- name: jvector
rules:
- alert: HighMemoryUsage
expr: jvm_memory_used_bytes{area="heap"} / jvm_memory_max_bytes{area="heap"} > 0.9
for: 5m
labels:
severity: warning
annotations:
summary: "JVector high memory usage"
description: "Memory usage is above 90% for more than 5 minutes"
- alert: SearchLatencyHigh
expr: rate(jvector_search_duration_seconds_sum[5m]) / rate(jvector_search_duration_seconds_count[5m]) > 1
for: 2m
labels:
severity: critical
annotations:
summary: "JVector search latency is high"
description: "Average search latency is above 1 second"
- alert: ErrorRateHigh
expr: rate(jvector_errors_total[5m]) > 0.1
for: 1m
labels:
severity: critical
annotations:
summary: "JVector error rate is high"
description: "Error rate is above 0.1 errors per second"
- alert: DatabaseDown
expr: up{job="jvector"} == 0
for: 30s
labels:
severity: critical
annotations:
summary: "JVector database is down"
description: "JVector instance is not responding"
/**
* 基于向量搜索的推荐系统
*/
@Service
public class VectorRecommendationService {
private final VectorDatabase userEmbeddingDB;
private final VectorDatabase itemEmbeddingDB;
private final EmbeddingGenerator embeddingGenerator;
/**
* 获取用户推荐
*/
public List<RecommendationResult> getRecommendations(String userId, int count) {
// 获取用户向量
Vector userVector = getUserEmbedding(userId);
if (userVector == null) {
return generateColdStartRecommendations(count);
}
// 搜索相似物品
SearchOptions options = SearchOptions.builder()
.enableCache(true)
.excludeIds(getUserInteractedItems(userId))
.build();
List<SearchResult> results = itemEmbeddingDB.search(userVector, count * 2, options).join();
// 过滤和排序
return results.stream()
.filter(this::isValidRecommendation)
.map(this::convertToRecommendation)
.limit(count)
.collect(Collectors.toList());
}
/**
* 实时更新用户偏好
*/
public void updateUserPreference(String userId, String itemId, double rating) {
// 获取当前用户向量
Vector currentUserVector = getUserEmbedding(userId);
Vector itemVector = getItemEmbedding(itemId);
if (currentUserVector != null && itemVector != null) {
// 基于评分更新用户向量
Vector updatedVector = updateUserVector(currentUserVector, itemVector, rating);
// 异步更新数据库
CompletableFuture.runAsync(() -> {
userEmbeddingDB.updateVector(getUserId(userId), updatedVector);
});
}
}
private Vector updateUserVector(Vector userVector, Vector itemVector, double rating) {
float[] userData = userVector.getData().clone();
float[] itemData = itemVector.getData();
// 简单的线性更新策略
float learningRate = 0.01f;
float influence = (float) (rating - 3.0) * learningRate; // 假设评分范围1-5
for (int i = 0; i < userData.length; i++) {
userData[i] += influence * itemData[i];
}
return new Vector(userData).normalize();
}
}
/**
* 语义搜索服务
*/
@Service
public class SemanticSearchService {
private final VectorDatabase documentDB;
private final TextEmbeddingModel embeddingModel;
/**
* 语义搜索文档
*/
public List<SearchResult> semanticSearch(String query, int limit) {
// 生成查询向量
Vector queryVector = embeddingModel.encode(query);
// 执行向量搜索
SearchOptions options = SearchOptions.builder()
.enableCache(true)
.deduplication(true)
.build();
List<SearchResult> results = documentDB.search(queryVector, limit, options).join();
// 重排序(可选)
return rerankResults(query, results);
}
/**
* 添加文档
*/
public void addDocument(Document document) {
// 生成文档向量
Vector documentVector = embeddingModel.encode(document.getContent());
// 添加到向量数据库
documentDB.addVector(document.getId(), documentVector);
// 更新倒排索引(如果需要)
updateInvertedIndex(document);
}
private List<SearchResult> rerankResults(String query, List<SearchResult> results) {
// 可以集成更复杂的重排序模型
return results;
}
}
/**
* 性能优化配置
*/
@Configuration
public class PerformanceOptimizationConfig {
/**
* JVM调优参数
*/
public static final String[] RECOMMENDED_JVM_ARGS = {
"-Xmx8g", // 堆内存大小
"-Xms4g", // 初始堆大小
"-XX:+UseG1GC", // 使用G1垃圾收集器
"-XX:MaxGCPauseMillis=200", // 最大GC暂停时间
"-XX:+UnlockExperimentalVMOptions",
"-XX:+UseJVMCICompiler", // 启用JVMCI编译器
"-XX:+UseCompressedOops", // 压缩对象指针
"-XX:+AlwaysPreTouch" // 预分配内存页
};
/**
* 索引优化配置
*/
@Bean
public HnswConfig optimizedHnswConfig() {
return HnswConfig.builder()
.m(16) // 连接数,影响搜索质量和内存使用
.efConstruction(200) // 构建时的搜索深度
.maxLevel(5) // 最大层数
.levelMultiplier(1.0 / Math.log(2.0)) // 层级倍数
.build();
}
/**
* 线程池配置
*/
@Bean
public ThreadPoolTaskExecutor vectorOperationExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(Runtime.getRuntime().availableProcessors());
executor.setMaxPoolSize(Runtime.getRuntime().availableProcessors() * 2);
executor.setQueueCapacity(1000);
executor.setThreadNamePrefix("vector-op-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
/**
* 监控和诊断工具
*/
@Component
public class PerformanceDiagnosticTool {
/**
* 分析搜索性能
*/
public SearchPerformanceReport analyzeSearchPerformance(HnswIndex index) {
SearchPerformanceReport report = new SearchPerformanceReport();
// 分析索引结构
report.setIndexSize(index.size());
report.setAverageConnections(calculateAverageConnections(index));
report.setLevelDistribution(calculateLevelDistribution(index));
// 性能测试
List<Long> searchTimes = performSearchBenchmark(index);
report.setAverageSearchTime(calculateAverage(searchTimes));
report.setPercentile95(calculatePercentile(searchTimes, 0.95));
report.setPercentile99(calculatePercentile(searchTimes, 0.99));
return report;
}
/**
* 内存使用分析
*/
public MemoryUsageReport analyzeMemoryUsage() {
MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
MemoryUsage heapUsage = memoryBean.getHeapMemoryUsage();
MemoryUsage nonHeapUsage = memoryBean.getNonHeapMemoryUsage();
return MemoryUsageReport.builder()
.heapUsed(heapUsage.getUsed())
.heapMax(heapUsage.getMax())
.nonHeapUsed(nonHeapUsage.getUsed())
.directMemoryUsed(getDirectMemoryUsage())
.build();
}
}
#!/bin/bash
# 部署脚本
set -e
echo "Starting JVector deployment..."
# 环境检查
check_environment() {
echo "Checking environment..."
if ! command -v java &> /dev/null; then
echo "Java not found. Please install Java 11+"
exit 1
fi
if ! command -v docker &> /dev/null; then
echo "Docker not found. Please install Docker"
exit 1
fi
echo "Environment check passed."
}
# 构建应用
build_application() {
echo "Building application..."
mvn clean package -DskipTests
docker build -t jvector:latest .
echo "Build completed."
}
# 部署到生产环境
deploy_production() {
echo "Deploying to production..."
# 停止旧容器
docker-compose down
# 备份数据
backup_data
# 启动新容器
docker-compose up -d
# 健康检查
wait_for_health_check
echo "Deployment completed."
}
# 数据备份
backup_data() {
echo "Backing up data..."
timestamp=$(date +%Y%m%d_%H%M%S)
docker exec jvector_db tar czf /backup/jvector_backup_$timestamp.tar.gz /data
echo "Backup completed: jvector_backup_$timestamp.tar.gz"
}
# 健康检查
wait_for_health_check() {
echo "Waiting for service to be healthy..."
for i in {1..30}; do
if curl -f http://localhost:8080/health > /dev/null 2>&1; then
echo "Service is healthy!"
return 0
fi
echo "Waiting... ($i/30)"
sleep 10
done
echo "Health check failed!"
exit 1
}
# 执行部署
main() {
check_environment
build_application
deploy_production
}
main "$@"
本章完整介绍了向量搜索引擎的测试、部署和实际应用:
这套完整的解决方案为向量搜索引擎的生产部署和运维提供了全面的指导。
思考题: