1. 项目概述
在构建智能知识管理系统时,我们经常需要处理来自不同业务模块的文档变更。传统做法是为每种文档类型编写独立的处理逻辑,这不仅导致代码重复,还会随着业务扩展变得难以维护。本文将分享一个基于Spring AI的实战方案,通过工厂模式实现按消息类型动态派发向量化处理的能力。
这个方案的核心价值在于:
- 解耦消息接收与业务处理
- 支持运行时动态扩展新文档类型
- 统一异常处理和监控点
- 简化新业务类型的接入成本
2. 核心设计思路
2.1 问题域分析
系统需要处理来自后台的各种文档变更消息,包括:
- 公告(notice)的增删改
- 学习资料(materials)的更新
- 其他业务文档的变更
每种文档类型有特定的:
- 数据存储位置(不同的数据库表)
- 内容处理逻辑(如分段策略)
- 向量化参数(如嵌入模型选择)
2.2 工厂模式选型
采用简单工厂模式而非抽象工厂,因为:
- 产品类型(IVectorService)单一且稳定
- 创建逻辑简单直接(type→实现映射)
- 不需要支持产品族的扩展
工厂类VectorServiceFactory的关键职责:
- 启动时自动注册所有IVectorService实现
- 运行时根据type快速查找对应服务
- 处理重复type定义的冲突检测
3. 关键技术实现
3.1 接口设计
IVectorService接口定义四个核心方法:
java复制public interface IVectorService {
// 处理新增文档
void add(String sourceId);
// 处理更新文档(默认delete+add实现)
default void update(String sourceId) {
delete(sourceId);
add(sourceId);
}
// 处理删除文档
void delete(String sourceId);
// 返回处理的文档类型标识
String type();
}
接口设计的考量:
- 使用default方法提供update默认实现
- sourceId统一使用String类型避免类型转换问题
- 单一职责原则:每个方法只做一件事
3.2 工厂实现
VectorServiceFactory的核心逻辑:
java复制@Component
public class VectorServiceFactory {
private final Map<String, IVectorService> registry = new ConcurrentHashMap<>();
@Autowired
public VectorServiceFactory(List<IVectorService> services) {
services.forEach(service -> {
String type = service.type();
if (registry.containsKey(type)) {
throw new IllegalStateException("重复的向量服务类型: " + type);
}
registry.put(type, service);
});
}
public IVectorService getService(String type) {
return Optional.ofNullable(registry.get(type))
.orElseThrow(() -> new IllegalArgumentException("未知的服务类型: " + type));
}
}
工厂实现的优化点:
- 使用ConcurrentHashMap保证线程安全
- 采用Optional优雅处理null值
- 启动时立即完成注册,运行时无锁访问
3.3 消息接收器
CampusaiMessageReceiver的关键改进:
java复制@Component
public class CampusaiMessageReceiver {
private static final Logger logger = LoggerFactory.getLogger(CampusaiMessageReceiver.class);
@Autowired
private VectorServiceFactory factory;
@RabbitListener(queues = "${queue.vector}")
public void handleMessage(String message) {
try {
MessageDto dto = parseMessage(message);
processMessage(dto);
} catch (Exception e) {
logger.error("消息处理失败: {}", message, e);
// 可添加重试或死信队列逻辑
}
}
private MessageDto parseMessage(String json) {
// 使用Jackson进行JSON解析
}
private void processMessage(MessageDto dto) {
IVectorService service = factory.getService(dto.getType());
dto.getIds().forEach(id -> dispatchOperation(service, dto.getOperation(), id));
}
private void dispatchOperation(IVectorService service, String op, String id) {
switch (op.toUpperCase()) {
case "ADD": service.add(id); break;
case "UPDATE": service.update(id); break;
case "DELETE": service.delete(id); break;
default: throw new UnsupportedOperationException(op);
}
}
}
接收器的增强点:
- 添加完善的日志记录
- 分离消息解析与处理逻辑
- 支持批量ID处理
- 明确的异常分类处理
4. 业务实现示例
以Notice处理为例的完整实现:
java复制@Service
public class NoticeVectorService implements IVectorService {
private final NoticeMapper noticeMapper;
private final DocumentIdsMapper docIdMapper;
private final VectorStoreClient vectorStore;
private final DocumentSplitter splitter;
public NoticeVectorService(NoticeMapper noticeMapper,
DocumentIdsMapper docIdMapper,
VectorStoreClient vectorStore,
DocumentSplitter splitter) {
this.noticeMapper = noticeMapper;
this.docIdMapper = docIdMapper;
this.vectorStore = vectorStore;
this.splitter = splitter;
}
@Override
public void add(String noticeId) {
Notice notice = noticeMapper.selectById(noticeId);
if (notice == null || StringUtils.isBlank(notice.getContent())) {
return;
}
List<Document> chunks = splitter.split(notice.getContent());
List<String> docIds = vectorStore.add(chunks);
docIds.forEach(docId ->
docIdMapper.insert(new DocumentId(noticeId, docId))
);
}
@Override
public void delete(String noticeId) {
List<String> docIds = docIdMapper.findDocIdsBySource(noticeId);
if (!docIds.isEmpty()) {
vectorStore.delete(docIds);
docIdMapper.deleteBySource(noticeId);
}
}
@Override
public String type() {
return "notice";
}
}
实现细节说明:
- 使用构造函数注入替代字段注入
- 添加空内容检查
- 引入DocumentSplitter处理文档分块
- 使用DTO封装关联关系
5. 生产环境注意事项
5.1 性能优化
- 批量处理:
java复制// 在VectorStoreClient中添加批量接口
void addBatch(List<Document> documents);
void deleteBatch(List<String> docIds);
- 异步处理:
java复制@Async("vectorTaskExecutor")
public void addAsync(String sourceId) {
// 异步实现
}
- 限流控制:
java复制// 使用RateLimiter控制QPS
private final RateLimiter limiter = RateLimiter.create(100); // 100 QPS
public void add(String sourceId) {
limiter.acquire();
// 业务逻辑
}
5.2 事务一致性
处理方案:
- 本地事务:
java复制@Transactional
public void add(String sourceId) {
// 业务操作
}
- 最终一致性:
java复制public void addWithCompensation(String sourceId) {
try {
// 主业务逻辑
} catch (Exception e) {
// 记录补偿日志
compensationLogRepository.save(new CompensationLog(sourceId, "ADD"));
throw e;
}
}
@Scheduled(fixedDelay = 60000)
public void retryCompensation() {
// 查询补偿日志并重试
}
5.3 监控指标
建议监控:
- 消息处理耗时
- 各类型文档处理量
- 向量库操作成功率
- 异常发生频率
使用Micrometer示例:
java复制@Autowired
private MeterRegistry registry;
public void add(String sourceId) {
Timer.Sample sample = Timer.start(registry);
try {
// 业务逻辑
sample.stop(registry.timer("vector.process", "type", "notice", "op", "add"));
} catch (Exception e) {
sample.stop(registry.timer("vector.process", "type", "notice", "op", "add", "status", "error"));
throw e;
}
}
6. 扩展设计
6.1 动态策略配置
通过配置中心支持运行时调整:
java复制@RefreshScope
@Service
public class ConfigurableVectorService implements IVectorService {
@Value("${vector.strategy.notice.splitter:default}")
private String splitterType;
// 根据配置选择不同处理策略
}
6.2 多向量库支持
工厂模式扩展:
java复制public interface VectorStoreFactory {
VectorStoreClient getStore(String storeType);
}
@Service
public class MultiVectorService implements IVectorService {
private final VectorStoreFactory storeFactory;
public void add(String sourceId) {
VectorStoreClient store = storeFactory.getStore("redis");
// 使用特定向量库
}
}
6.3 版本化管理
支持向量版本追溯:
java复制public void add(String sourceId) {
String version = "v" + System.currentTimeMillis();
vectorStore.add(documents, version);
docIdMapper.insert(sourceId, docId, version);
}
7. 测试策略
7.1 单元测试
工厂测试示例:
java复制@Test
void shouldThrowWhenDuplicateType() {
List<IVectorService> services = List.of(
new MockService("notice"),
new MockService("notice") // 重复类型
);
assertThrows(IllegalStateException.class,
() -> new VectorServiceFactory(services));
}
7.2 集成测试
测试场景设计:
- 发送MQ消息 → 验证向量库更新
- 模拟异常 → 验证重试机制
- 压力测试 → 验证性能指标
7.3 契约测试
使用Pact验证消息契约:
java复制@Pact(consumer = "frontend")
public MessagePact createPact(PactDslWithProvider builder) {
return builder
.given("notice exists")
.expectsToReceive("notice update")
.withContent({
"ids": ["123"],
"operation": "UPDATE",
"type": "notice"
})
.toPact();
}
8. 部署架构
推荐的生产环境部署方案:
code复制[若依后台] → [RabbitMQ集群] ← [Spring AI应用集群]
|
→ [死信队列] ← [告警服务]
关键配置:
- RabbitMQ镜像队列保证高可用
- Spring应用多实例无状态部署
- 向量库(Redis)集群模式
- 独立的监控和日志收集
9. 经验总结
在实际落地过程中,我们获得了以下宝贵经验:
-
版本兼容性:当向量模型升级时,需要同时维护新旧版本的向量库,建议:
- 在document_ids表中增加model_version字段
- 查询时根据问题创建时间选择对应版本
-
批量处理优化:对于大规模数据迁移:
java复制@Scheduled(cron = "0 2 * * *") public void batchImport() { // 分页查询源数据 // 批量生成向量 // 批量写入向量库 } -
缓存策略:高频访问的文档建议添加本地缓存:
java复制@Cacheable(cacheNames = "vectorCache", key = "#sourceId") public List<Float> getVector(String sourceId) { // 查询向量库 } -
故障演练:定期测试以下场景:
- RabbitMQ宕机时消息堆积处理
- 向量库超时时的降级策略
- 网络分区时的数据一致性
这个方案目前已在生产环境稳定运行半年,每天处理超过10万次文档变更。其核心价值在于通过清晰的架构边界,使团队能够快速响应各种新的文档类型处理需求,同时保持系统的可维护性和稳定性。