1. 项目概述:多格式文档向量化的微服务架构设计
在当今企业级应用开发中,文档向量化处理已成为构建智能系统的核心需求。我最近完成了一个基于Spring AI的微服务项目,通过工厂模式+模板模式的组合架构,实现了对PDF、Word、Excel等多种格式文档的统一向量化处理。这个设计不仅解决了多格式兼容性问题,还大幅提升了系统的扩展性和维护性。
传统文档处理方案通常面临三个痛点:格式解析逻辑分散、向量化流程不统一、新增格式成本高。我们的架构通过在Spring Boot微服务中引入设计模式,将文档解析与向量化过程标准化。实测表明,该方案能使新格式接入时间缩短70%,同时保持核心业务逻辑的稳定性。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心架构设计解析
2.1 工厂模式实现文档解析器动态装配
文档解析是向量化的第一步,我们通过工厂模式实现多格式的灵活支持:
java复制public interface DocumentParser {
List<TextSegment> parse(InputStream input);
}
@Service
public class ParserFactory {
private final Map<DocumentType, DocumentParser> parsers;
@Autowired
public ParserFactory(List<DocumentParser> parserImpls) {
this.parsers = parserImpls.stream()
.collect(Collectors.toMap(
DocumentParser::getSupportedType,
Function.identity()
));
}
public DocumentParser getParser(DocumentType type) {
return Optional.ofNullable(parsers.get(type))
.orElseThrow(() -> new UnsupportedFormatException(type));
}
}
每个具体解析器(如PdfParser、DocxParser)只需关注自身格式的解析逻辑。工厂类通过Spring的依赖注入自动收集所有解析器实现,建立类型与实现的映射关系。这种设计带来三个优势:
- 新格式接入只需新增Parser实现类,无需修改现有代码
- 解析器实现可独立测试和替换
- 运行时根据文档类型自动选择合适解析器
2.2 模板模式标准化向量化流程
无论何种文档格式,向量化的核心流程是固定的:解析文本→分块→向量化→存储。我们使用模板方法模式固化这一流程:
java复制public abstract class VectorizationTemplate {
public final VectorizationResult process(InputStream input) {
// 1. 文档解析
List<TextSegment> segments = parse(input);
// 2. 文本分块
List<TextChunk> chunks = chunking(segments);
// 3. 向量化处理
List<Vector> vectors = embedding(chunks);
// 4. 结果存储
return store(vectors);
}
protected abstract List<TextSegment> parse(InputStream input);
protected List<TextChunk> chunking(List<TextSegment> segments) {
// 默认分块实现
}
protected abstract List<Vector> embedding(List<TextChunk> chunks);
protected abstract VectorizationResult store(List<Vector> vectors);
}
具体子类只需实现特定步骤的差异化逻辑。例如PDF向量化子类可能重写分块方法实现基于PDF书签的智能分块,而Word子类可能采用段落分块策略。
3. Spring AI集成实现
3.1 向量化服务配置
Spring AI提供了简洁的嵌入模型集成方式。我们在应用中配置OpenAI的text-embedding-3-large模型:
yaml复制spring:
ai:
openai:
api-key: ${OPENAI_KEY}
embedding:
model: text-embedding-3-large
dimensions: 1536
服务类通过自动注入使用嵌入功能:
java复制@Service
public class OpenAiEmbeddingService implements EmbeddingService {
private final OpenAiEmbeddingClient embeddingClient;
public OpenAiEmbeddingService(OpenAiEmbeddingClient embeddingClient) {
this.embeddingClient = embeddingClient;
}
@Override
public List<Vector> embed(List<TextChunk> chunks) {
List<String> texts = chunks.stream()
.map(TextChunk::getText)
.toList();
return embeddingClient.embed(texts).stream()
.map(Vector::new)
.toList();
}
}
3.2 微服务API设计
对外暴露的REST接口保持极简设计:
java复制@RestController
@RequestMapping("/api/v1/vectorize")
public class VectorizationController {
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<VectorizationResult> vectorizeDocument(
@RequestParam("file") MultipartFile file,
@RequestParam(value = "chunkSize", defaultValue = "1000") int chunkSize) {
DocumentType type = detectDocumentType(file.getOriginalFilename());
VectorizationProcessor processor = processorFactory.getProcessor(type);
return ResponseEntity.ok(
processor.process(file.getInputStream(), chunkSize)
);
}
}
4. 性能优化实践
4.1 异步批处理实现
为提高吞吐量,我们实现了异步批处理机制:
java复制@Async
@Transactional
public CompletableFuture<VectorizationResult> processAsync(InputStream input) {
return CompletableFuture.completedFuture(process(input));
}
// 批量接口
public List<VectorizationResult> batchProcess(List<DocumentInput> inputs) {
return inputs.parallelStream()
.map(input -> processorFactory.getProcessor(input.type())
.process(input.stream(), input.chunkSize()))
.toList();
}
4.2 向量存储优化
针对大规模向量数据,我们采用分片存储策略:
java复制public class VectorStoreService {
private final int SHARD_SIZE = 10_000;
public void storeVectors(String docId, List<Vector> vectors) {
List<List<Vector>> shards = Lists.partition(vectors, SHARD_SIZE);
shards.forEach(shard -> {
int shardId = shards.indexOf(shard);
vectorRepository.save(new VectorShard(docId, shardId, shard));
});
}
}
5. 异常处理与监控
5.1 自定义异常体系
java复制public class VectorizationException extends RuntimeException {
private final ErrorCode code;
public enum ErrorCode {
UNSUPPORTED_FORMAT,
PARSING_FAILURE,
EMBEDDING_FAILURE,
STORAGE_FAILURE
}
}
@ControllerAdvice
public class VectorizationExceptionHandler {
@ExceptionHandler(VectorizationException.class)
public ResponseEntity<ErrorResponse> handleException(VectorizationException ex) {
return ResponseEntity
.status(resolveHttpStatus(ex.getCode()))
.body(new ErrorResponse(ex.getCode(), ex.getMessage()));
}
}
5.2 Prometheus监控集成
通过Micrometer暴露关键指标:
java复制@Bean
public MeterRegistryCustomizer<PrometheusMeterRegistry> metricsConfig() {
return registry -> {
registry.config().commonTags("application", "vectorization-service");
Counter.builder("vectorization.requests")
.description("Total vectorization requests")
.register(registry);
Timer.builder("vectorization.process.time")
.description("Vectorization processing time")
.publishPercentiles(0.5, 0.95, 0.99)
.register(registry);
};
}
6. 部署与扩展方案
6.1 Kubernetes部署配置
典型的Deployment配置示例:
yaml复制apiVersion: apps/v1
kind: Deployment
metadata:
name: vectorization-service
spec:
replicas: 3
selector:
matchLabels:
app: vectorization
template:
spec:
containers:
- name: vectorization
image: registry.example.com/vector-service:1.0.0
resources:
limits:
cpu: 2
memory: 2Gi
envFrom:
- configMapRef:
name: vectorization-config
6.2 水平扩展策略
基于KEDA的自动扩缩容配置:
yaml复制apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: vectorization-scaler
spec:
scaleTargetRef:
name: vectorization-service
triggers:
- type: prometheus
metadata:
serverAddress: http://prometheus-server
metricName: vectorization_queue_length
threshold: "100"
query: sum(rate(vectorization_requests_total[1m]))
7. 实际应用中的经验总结
在半年多的生产运行中,我们积累了几个关键经验:
-
分块策略决定质量:发现采用语义分块(而非固定长度)能提升后续检索效果20%以上。我们最终集成了LLM辅助的智能分块算法。
-
元数据至关重要:除了文本内容,存储文档结构信息(如章节关系)极大增强了向量检索的上下文理解能力。
-
失败重试机制:对OpenAI API调用实现指数退避重试,将失败率从5%降至0.2%。
-
内存控制:处理大文档时采用流式解析,避免OOM错误。我们为PDF解析设置了100MB的单文档内存限制。
这个架构目前已稳定处理日均50万+文档,支持10+格式的向量化需求。最令人满意的是其扩展性——新增Markdown支持仅需2人日的工作量,且完全不影响现有功能。
