智能字幕校准系统实战(一):微服务架构设计与技术栈选型
系列文章:《智能字幕校准系统实战:从架构到算法的全栈技术解析》
本文为第1篇:微服务架构设计与技术栈选型
阅读时间:15分钟
难度:★★★(中级)
标签:微服务架构Spring BootPython系统设计技术选型
前情回顾
在系列开篇中,我介绍了这个智能字幕校准系统的业务背景:
业务痛点:
- 人工校准字幕耗时长(10分钟视频需30-60分钟)
- 人力成本高($15-25/小时)
- 难以规模化(5种语言需要5个熟练工人)
我们的目标:
- 自动化率90%+
- 时间节省80%
- 成本降低70%
- 支持5种语言(英、西、葡、日、法)
今天这篇文章,我将详细讲解如何设计这个系统的架构,以及为什么做出这些技术选型决策。
架构设计的核心问题
在开始编码之前,我们需要回答几个关键问题:
问题1:单体应用 vs 微服务?
单体应用的诱惑:
- 开发简单,一个项目搞定
- 部署方便,一个war包解决
- 调试容易,本地就能运行
但我们选择了微服务,为什么?
让我们看看这个系统的特点:
核心功能模块:
1. 业务逻辑层(Java擅长)
- 用户认证、权限管理
- 任务流程编排
- 文件管理
- API接口
2. AI算法层(Python擅长)
- NLP文本处理
- 智能匹配算法
- 数据分析
3. 翻译爬取层(特殊环境)
- Selenium WebDriver
- 需要Chrome浏览器
- 高内存消耗
4. 外部服务集成层
- Azure STT API
- Sonix API
- DeepL API
问题显而易见:
- 技术栈异构:Java和Python各有所长,强行统一会降低效率
- 部署环境不同:Selenium需要浏览器环境,AI服务需要大内存和GPU
- 扩展需求不同:AI计算密集,需要独立扩展;业务逻辑IO密集,扩展需求不同
- 团队技能不同:后端工程师写Java,算法工程师写Python
结论:微服务是更合适的选择。
问题2:如何拆分微服务?
微服务拆分有很多原则,常见的有:
- 按业务领域拆分(DDD)
- 按技术栈拆分
- 按团队拆分
- 按数据边界拆分
我们采用的是混合策略:
| 服务 | 拆分依据 | 理由 |
|---|---|---|
| chronos-web-after | 业务领域 | 核心业务逻辑,是系统的大脑 |
| chronos-python | 技术栈 | Python擅长AI算法 |
| chronos-translate | 技术栈+部署环境 | Selenium需要独立环境 |
| chronos-web-front | 技术栈 | 前后端分离 |
拆分原则:
- 高内聚:每个服务职责单一明确
- 低耦合:服务间通过定义良好的接口通信
- 独立部署:各服务可以独立发布、扩展
- 技术自由:每个服务可以选择最适合的技术栈
问题3:微服务如何通信?
常见方案对比:
| 方案 | 优点 | 缺点 | 是否采用 |
|---|---|---|---|
| Spring Cloud全家桶 | 生态完善,功能强大 | 复杂度高,学习成本大 | [NO] 杀鸡用牛刀 |
| gRPC | 性能高,类型安全 | 需要proto定义,调试不便 | [NO] 过度设计 |
| HTTP REST | 简单直接,易于调试 | 性能一般 | [OK] 适合中小规模 |
| 消息队列(MQ) | 解耦彻底,削峰填谷 | 增加运维复杂度 | [NO] 当前规模不需要 |
| Redis队列 | 轻量级,易于运维 | 功能简单 | [OK] 满足需求 |
我们的选择:
- 同步调用:HTTP REST(用于实时响应,如用户登录)
- 异步调用:Redis任务队列(用于耗时任务,如字幕校准)
为什么不用Spring Cloud?
- 我们的服务数量只有4个,不需要复杂的服务治理
- 部署规模小(单机或2-3台服务器),不需要分布式追踪
- 团队规模小,维护成本要可控
简单就是美!
系统架构设计
整体架构图
┌─────────────────────────────────────────────────────────────┐
│ 用户层 │
│ │
│ ┌──────────────────────────────────────────────┐ │
│ │ chronos-web-front (Vue.js 2.6) │ │
│ │ - Element UI 组件库 │ │
│ │ - Vuex 状态管理 │ │
│ │ - Axios HTTP客户端 │ │
│ │ - WebSocket 实时推送 │ │
│ └──────────────────────────────────────────────┘ │
│ │ HTTP/WS │
└──────────────────────────┼──────────────────────────────────┘
│
┌──────────────────────────┼──────────────────────────────────┐
│ 核心业务层 │
│ │ │
│ ┌──────────────────────────────────────────────┐ │
│ │ chronos-web-after (Spring Boot 2.7.9) │ │
│ │ │ │
│ │ Controller层: │ │
│ │ ├─ CalibrationController - 20+ REST API │ │
│ │ ├─ TranscriptionController │ │
│ │ ├─ TranslationController │ │
│ │ └─ UserController │ │
│ │ │ │
│ │ Service层: │ │
│ │ ├─ CalibrationService (@Scheduled) │ │
│ │ ├─ TaskQueue (Redis操作) │ │
│ │ ├─ AwsS3Service (文件管理) │ │
│ │ └─ UserService (认证授权) │ │
│ │ │ │
│ │ Task层: │ │
│ │ ├─ CalibrationTask (任务执行) │ │
│ │ ├─ TranscriptionTask │ │
│ │ └─ TranslationTask │ │
│ └──────────────────────────────────────────────┘ │
│ │ │ │ │
│ HTTP │ Redis │ HTTP │ │
│ │ Queue │ │ │
└────────────────┼──────────────┼──────────────┼───────────────┘
│ │ │
┌───────┴─────┐ ┌────┴─────┐ ┌───┴──────────┐
│ │ │ │ │ │
┌────────┴─────┐ ┌─────┴───┴────┐ ┌──┴───┴──────┐ ┌─────┴─────┐
│ AI算法服务 │ │ 翻译爬取服务 │ │ 外部STT服务 │ │ 外部翻译 │
│ │ │ │ │ │ │ 服务 │
│ chronos- │ │ chronos- │ │ - Azure │ │ - DeepL │
│ python │ │ translate │ │ Speech │ │ - AWS │
│ │ │ │ │ - Sonix │ │ Trans │
│ - Spacy NLP │ │ - Selenium │ │ │ │ │
│ - 6级算法 │ │ WebDriver │ │ │ │ │
│ - 质量评估 │ │ - Chrome │ │ │ │ │
│ │ │ Headless │ │ │ │ │
└──────────────┘ └──────────────┘ └─────────────┘ └───────────┘
│ │
└──────┬───────┘
│
┌───────────────┼──────────────────────────────────────────────┐
│ 存储层 │
│ │
│ ┌──────────┐ ┌───────────┐ ┌──────────────┐ │
│ │ MySQL │ │ Redis │ │ AWS S3 │ │
│ │ │ │ │ │ │ │
│ │ - 任务表 │ │ - 任务队列 │ │ - 音频文件 │ │
│ │ - 用户表 │ │ - Session │ │ - 字幕文件 │ │
│ │ - 字幕表 │ │ - 缓存 │ │ - 结果文件 │ │
│ └──────────┘ └───────────┘ └──────────────┘ │
└──────────────────────────────────────────────────────────────┘
架构特点
- 分层清晰:用户层、业务层、服务层、存储层
- 职责分离:Java做业务编排,Python做AI计算,Selenium做爬取
- 异步解耦:Redis队列实现任务异步处理
- 云存储:S3解决大文件存储和传输问题
四大微服务详解
1. chronos-web-after:核心业务服务
角色定位:系统的大脑,负责业务流程编排和任务调度
技术栈:
<dependencies>
<!-- Spring Boot 核心 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.9</version>
</dependency>
<!-- Spring Security + JWT 认证 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Spring Data JPA 数据访问 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- Redis 队列和缓存 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- AWS SDK (S3存储) -->
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
<version>2.20.26</version>
</dependency>
</dependencies>
核心功能模块:
1.1 用户认证与权限管理
@RestController
@RequestMapping("/api/auth")
public class AuthController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtTokenProvider tokenProvider;
/**
* 用户登录
*/
@PostMapping("/login")
public Result login(@RequestBody LoginRequest request) {
// 验证用户名密码
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
request.getUsername(),
request.getPassword()
)
);
SecurityContextHolder.getContext().setAuthentication(authentication);
// 生成JWT Token
String token = tokenProvider.generateToken(authentication);
return Result.success(new AuthResponse(token));
}
}
1.2 任务流程编排
@Service
public class CalibrationService {
@Autowired
private TaskRepository taskRepository;
@Autowired
private TaskQueue taskQueue;
@Autowired
private AwsS3Service s3Service;
/**
* 创建校准任务
*/
@Transactional
public Task createCalibrationTask(CalibrationRequest request) {
// 1. 创建任务记录
Task task = new Task();
task.setUserId(getCurrentUserId());
task.setTaskType(TaskType.CALIBRATION);
task.setStatus(TaskStatus.PENDING);
task.setLanguage(request.getLanguage());
task.setCreatedTime(LocalDateTime.now());
taskRepository.save(task);
// 2. 生成S3预签名上传URL
String refSrtUploadUrl = s3Service.generateUploadUrl(
"subtitles/" + task.getId() + "/ref.srt",
Duration.ofMinutes(30)
);
String audioUploadUrl = s3Service.generateUploadUrl(
"audio/" + task.getId() + "/audio.mp3",
Duration.ofMinutes(30)
);
task.setRefSrtUploadUrl(refSrtUploadUrl);
task.setAudioUploadUrl(audioUploadUrl);
taskRepository.save(task);
return task;
}
/**
* 开始执行任务
*/
public void startTask(String taskId) {
// 1. 验证文件已上传
Task task = taskRepository.findById(taskId)
.orElseThrow(() -> new TaskNotFoundException(taskId));
if (!s3Service.exists(task.getRefSrtUrl()) ||
!s3Service.exists(task.getAudioUrl())) {
throw new FilesNotUploadedException();
}
// 2. 加入Redis任务队列
taskQueue.push(taskId);
// 3. 更新状态
task.setStatus(TaskStatus.PENDING);
taskRepository.save(task);
}
/**
* 定时任务调度器:每5秒扫描一次队列
*/
@Scheduled(fixedDelay = 5000)
public void scheduleTasks() {
// 从队列中取出任务
String taskId = taskQueue.poll(1, TimeUnit.SECONDS);
if (taskId != null) {
// 提交到线程池执行
calibrationExecutor.submit(() -> {
try {
processCalibrationTask(taskId);
} catch (Exception e) {
handleTaskFailure(taskId, e);
}
});
}
}
}
1.3 状态机管理
@Service
public class TaskStateMachine {
/**
* 状态转换规则
*/
private static final Map<TaskStatus, List<TaskStatus>> ALLOWED_TRANSITIONS = Map.of(
PENDING, List.of(DOWNLOADING, FAILED),
DOWNLOADING, List.of(DOWNLOADED, FAILED),
DOWNLOADED, List.of(TRANSCRIBING, FAILED),
TRANSCRIBING, List.of(TRANSCRIBED, FAILED),
TRANSCRIBED, List.of(CALIBRATING, FAILED),
CALIBRATING, List.of(CALIBRATED, FAILED),
CALIBRATED, List.of(UPLOADING, FAILED),
UPLOADING, List.of(COMPLETED, FAILED)
);
/**
* 状态转换
*/
@Transactional
public void transition(String taskId, TaskStatus newStatus, String message) {
Task task = taskRepository.findById(taskId).orElseThrow();
// 验证转换合法性
if (!canTransition(task.getStatus(), newStatus)) {
throw new IllegalStateTransitionException(
String.format("Cannot transition from %s to %s",
task.getStatus(), newStatus)
);
}
// 更新状态
task.setStatus(newStatus);
task.setProgress(newStatus.getProgress());
task.setMessage(message);
task.setUpdatedTime(LocalDateTime.now());
taskRepository.save(task);
// WebSocket推送给前端
webSocketService.sendTaskUpdate(taskId, task);
log.info("Task {} transitioned to {}: {}", taskId, newStatus, message);
}
private boolean canTransition(TaskStatus from, TaskStatus to) {
List<TaskStatus> allowed = ALLOWED_TRANSITIONS.get(from);
return allowed != null && allowed.contains(to);
}
}
设计亮点:
- [OK] 状态机保证流程一致性:防止非法状态转换
- [OK] 定时调度器自动处理任务:用户无需等待
- [OK] ThreadPoolExecutor控制并发:避免系统过载
- [OK] WebSocket实时推送进度:良好的用户体验
2. chronos-python:AI校准服务
角色定位:系统的智慧核心,负责NLP处理和智能匹配算法
技术栈:
# requirements.txt
spacy==3.5.0
numpy==1.24.2
pandas==1.5.3
scikit-learn==1.2.2
# 语言模型
en_core_web_md==3.5.0
es_core_news_md==3.5.0
pt_core_news_md==3.5.0
ja_core_news_md==3.5.0
fr_core_news_md==3.5.0
# 日语分词
mecab-python3==1.0.5
核心模块:
2.1 服务入口
# chronos.py
class ChronosService:
"""校准服务主类"""
def __init__(self):
self.train_set = {} # 存储已加载的语言模型
def load_train_set_by_lang(self, lang):
"""
按需加载Spacy语言模型
避免启动时加载所有模型(耗时+内存)
"""
if lang not in self.train_set:
model_map = {
'en': 'en_core_web_md',
'es': 'es_core_news_md',
'pt': 'pt_core_news_md',
'ja': 'ja_core_news_md',
'fr': 'fr_core_news_md'
}
model_name = model_map.get(lang, 'en_core_web_md')
print(f"Loading Spacy model: {model_name}...")
self.train_set[lang] = spacy.load(model_name)
print(f"Model loaded successfully.")
return self.train_set[lang]
def start_calibration(self, task_id, ref_srt_path, stt_json_path, lang):
"""
开始校准流程
Args:
task_id: 任务ID
ref_srt_path: 参考字幕路径
stt_json_path: STT结果路径
lang: 语言代码
Returns:
校准后的SRT文件路径
"""
# 1. 加载语言模型
nlp = self.load_train_set_by_lang(lang)
# 2. 解析参考字幕
sub_segs = self.parse_subtitle(ref_srt_path, lang, nlp)
# 3. 解析STT词级时间戳
to_match_words = self.parse_stt_result(stt_json_path)
# 4. 执行6级匹配算法(核心)
sync = DirectSync()
result = sync.subtitle_time_sync(sub_segs, to_match_words, nlp)
# 5. 生成校准后的SRT
output_path = f"/tmp/calibration/{task_id}/result.srt"
self.write_srt(result, output_path)
return output_path
def start_preprocessing_list(self, task_id, ref_srt_path, stt_json_list, lang):
"""
批量预处理:处理多个STT结果,选取最佳匹配
适用场景:
- 同一音频使用不同STT服务(Azure vs Sonix)
- 需要选择质量最好的结果
"""
nlp = self.load_train_set_by_lang(lang)
sub_segs = self.parse_subtitle(ref_srt_path, lang, nlp)
best_result = None
best_score = 0
for stt_json_path in stt_json_list:
to_match_words = self.parse_stt_result(stt_json_path)
# 执行校准
sync = DirectSync()
result = sync.subtitle_time_sync(sub_segs, to_match_words, nlp)
# 计算质量分数
score = self.calculate_quality_score(result)
if score > best_score:
best_score = score
best_result = result
# 返回最佳结果
output_path = f"/tmp/calibration/{task_id}/result_best.srt"
self.write_srt(best_result, output_path)
return output_path
def calculate_quality_score(self, result):
"""
计算校准质量分数
综合评分 = 锚点覆盖率 × 0.6 + (1 - 时间交叉率) × 0.4
"""
anchor_coverage = result.get_anchor_coverage() # 匹配成功的字幕占比
time_crossing_rate = result.get_time_crossing_rate() # 时间交叉占比
return anchor_coverage * 0.6 + (1 - time_crossing_rate) * 0.4
设计亮点:
- [OK] 按需加载语言模型:节省内存和启动时间
- [OK] 批量处理+质量评分:自动选择最佳结果
- [OK] 质量指标量化:可追踪优化效果
3. chronos-translate:翻译爬取服务
角色定位:通过Selenium爬取Google翻译,作为免费翻译备选方案
技术栈:
<dependencies>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.5.5</version>
</dependency>
<!-- Selenium WebDriver -->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.7.2</version>
</dependency>
</dependencies>
核心实现:
@Service
public class GoogleTranslateService {
private WebDriver driver;
@PostConstruct
public void initDriver() {
ChromeOptions options = new ChromeOptions();
options.addArguments("--headless"); // 无头模式
options.addArguments("--no-sandbox");
options.addArguments("--disable-dev-shm-usage");
options.addArguments("--disable-gpu");
driver = new ChromeDriver(options);
}
/**
* 翻译文本
*/
public String translate(String text, String sourceLang, String targetLang) {
// 1. 智能分段(Google限制5000字符)
List<String> segments = splitText(text, 5000);
List<String> translatedSegments = new ArrayList<>();
for (String segment : segments) {
// 2. 构造URL
String url = String.format(
"https://translate.google.com/?sl=%s&tl=%s&text=%s",
sourceLang,
targetLang,
URLEncoder.encode(segment, StandardCharsets.UTF_8)
);
driver.get(url);
// 3. 等待翻译结果
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(30));
WebElement resultElement = wait.until(
ExpectedConditions.presenceOfElementLocated(
By.cssSelector("span[data-language-for-alternatives='" + targetLang + "']")
)
);
String translated = resultElement.getText();
translatedSegments.add(translated);
// 4. 随机延迟(反爬虫)
Thread.sleep(1000 + new Random().nextInt(2000));
}
return String.join("", translatedSegments);
}
/**
* 智能分段:在标点符号处分割
*/
private List<String> splitText(String text, int maxLength) {
List<String> segments = new ArrayList<>();
int start = 0;
while (start < text.length()) {
int end = Math.min(start + maxLength, text.length());
// 尽量在句号处分割
if (end < text.length()) {
int lastPeriod = text.lastIndexOf('.', end);
if (lastPeriod > start) {
end = lastPeriod + 1;
}
}
segments.add(text.substring(start, end));
start = end;
}
return segments;
}
@PreDestroy
public void cleanup() {
if (driver != null) {
driver.quit();
}
}
}
设计亮点:
- [OK] Headless模式:无需图形界面,节省资源
- [OK] 智能分段:处理长文本,在句号处分割保证语义完整
- [OK] 反爬虫策略:随机延迟,避免被封IP
为什么需要这个服务?
- DeepL免费额度:500K字符/月
- AWS Translate:按量付费
- Google翻译爬取:免费备选方案,降低成本
4. chronos-web-front:前端UI服务
角色定位:用户交互界面
技术栈:
{
"dependencies": {
"vue": "^2.6.11",
"element-ui": "^2.15.6",
"vuex": "^3.6.2",
"vue-router": "^3.5.1",
"axios": "^0.26.1"
}
}
核心功能:
// 任务创建流程
async createCalibrationTask() {
try {
// 1. 创建任务,获取上传URL
const { data } = await axios.post('/api/calibration/create', {
language: this.selectedLanguage
});
const { taskId, refSrtUploadUrl, audioUploadUrl } = data;
// 2. 直接上传文件到S3(不经过服务器)
await this.uploadToS3(refSrtUploadUrl, this.refSrtFile);
await this.uploadToS3(audioUploadUrl, this.audioFile);
// 3. 通知服务器开始处理
await axios.post(`/api/calibration/${taskId}/start`);
// 4. 建立WebSocket连接,监听进度
this.connectWebSocket(taskId);
this.$message.success('任务创建成功!');
} catch (error) {
this.$message.error('任务创建失败:' + error.message);
}
},
uploadToS3(presignedUrl, file) {
return axios.put(presignedUrl, file, {
headers: {
'Content-Type': 'application/octet-stream'
},
onUploadProgress: (progressEvent) => {
const percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
this.uploadProgress = percentCompleted;
}
});
},
connectWebSocket(taskId) {
const ws = new WebSocket(`ws://localhost:8080/ws/task/${taskId}`);
ws.onmessage = (event) => {
const update = JSON.parse(event.data);
// 更新UI
this.taskStatus = update.status;
this.taskProgress = update.progress;
this.taskMessage = update.message;
// 任务完成
if (update.status === 'COMPLETED') {
this.$message.success('任务完成!');
this.downloadUrl = update.downloadUrl;
ws.close();
}
// 任务失败
if (update.status === 'FAILED') {
this.$message.error('任务失败:' + update.message);
ws.close();
}
};
}
服务间通信设计
Redis任务队列
为什么选择Redis而非RabbitMQ?
| 维度 | Redis | RabbitMQ | 我们的选择 |
|---|---|---|---|
| 轻量级 | [OK] 内存占用小 | [NO] 较重 | [OK] |
| 易于运维 | [OK] 配置简单 | [NO] 配置复杂 | [OK] |
| 学习成本 | [OK] 低 | [NO] 高 | [OK] |
| 消息确认 | [WARN] 需手动实现 | [OK] 内置ACK | 可接受 |
| 任务规模 | [WARN] <1000 QPS | [OK] >10000 QPS | [OK] 我们<100 QPS |
| 持久化 | [OK] AOF | [OK] | [OK] |
结论:对于中小规模任务(<1000 QPS),Redis是更简单、更合适的选择。
队列设计:
@Component
public class TaskQueue {
@Autowired
private RedisTemplate<String, String> redisTemplate;
private static final String PENDING_QUEUE = "calibration:pending";
private static final String PROCESSING_HASH = "calibration:processing";
/**
* 加入队列
*/
public void push(String taskId) {
redisTemplate.opsForList().rightPush(PENDING_QUEUE, taskId);
log.info("Task {} pushed to queue", taskId);
}
/**
* 取出任务(阻塞式)
*/
public String poll(long timeout, TimeUnit unit) {
return redisTemplate.opsForList().leftPop(PENDING_QUEUE, timeout, unit);
}
/**
* 标记为处理中
*/
public void markProcessing(String taskId, String workerId) {
redisTemplate.opsForHash().put(
PROCESSING_HASH,
taskId,
workerId + ":" + System.currentTimeMillis()
);
}
/**
* 标记为完成
*/
public void markCompleted(String taskId) {
redisTemplate.opsForHash().delete(PROCESSING_HASH, taskId);
}
}
数据库设计
核心表结构
1. tbl_task(任务表)
CREATE TABLE tbl_task (
id VARCHAR(36) PRIMARY KEY COMMENT '任务ID(UUID)',
user_id VARCHAR(36) NOT NULL COMMENT '用户ID',
task_type VARCHAR(20) NOT NULL COMMENT '任务类型:CALIBRATION/TRANSCRIPTION/TRANSLATION',
status VARCHAR(20) NOT NULL COMMENT '状态:PENDING/DOWNLOADING/.../COMPLETED/FAILED',
progress INT DEFAULT 0 COMMENT '进度:0-100',
message TEXT COMMENT '状态消息',
-- 文件路径
ref_srt_url VARCHAR(500) COMMENT '参考字幕S3路径',
audio_url VARCHAR(500) COMMENT '音频文件S3路径',
result_url VARCHAR(500) COMMENT '结果文件S3路径',
-- 任务配置
language VARCHAR(10) COMMENT '语言:en/es/pt/ja/fr',
source_lang VARCHAR(10) COMMENT '源语言',
target_lang VARCHAR(10) COMMENT '目标语言',
-- 质量指标
anchor_coverage DECIMAL(5,2) COMMENT '锚点覆盖率',
time_crossing_rate DECIMAL(5,2) COMMENT '时间交叉率',
quality_score DECIMAL(5,2) COMMENT '质量分数',
-- 时间戳
created_time DATETIME NOT NULL COMMENT '创建时间',
updated_time DATETIME COMMENT '更新时间',
completed_time DATETIME COMMENT '完成时间',
INDEX idx_user_status (user_id, status),
INDEX idx_created_time (created_time),
INDEX idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='任务表';
索引设计思考:
idx_user_status:用户查询自己的任务(WHERE user_id = ? AND status = ?)idx_created_time:按时间排序(ORDER BY created_time DESC)idx_status:调度器查询待处理任务(WHERE status = 'PENDING')
2. tbl_user(用户表)
CREATE TABLE tbl_user (
id VARCHAR(36) PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
password VARCHAR(100) NOT NULL COMMENT 'BCrypt加密',
email VARCHAR(100),
role VARCHAR(20) DEFAULT 'USER' COMMENT 'USER/ADMIN',
created_time DATETIME NOT NULL,
last_login_time DATETIME,
INDEX idx_username (username)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3. tbl_subtitle(字幕表)
CREATE TABLE tbl_subtitle (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
task_id VARCHAR(36) NOT NULL,
subtitle_index INT NOT NULL COMMENT '字幕序号',
original_text TEXT COMMENT '原始文本',
calibrated_text TEXT COMMENT '校准后文本',
start_time DECIMAL(10,3) COMMENT '开始时间(秒)',
end_time DECIMAL(10,3) COMMENT '结束时间(秒)',
match_level INT COMMENT '匹配级别:1-6',
INDEX idx_task_id (task_id),
INDEX idx_task_index (task_id, subtitle_index)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
技术选型的思考
为什么选择Spring Boot?
优势:
- [OK] 生态成熟:Spring Security、Spring Data JPA开箱即用
- [OK] 开发效率高:注解驱动,减少样板代码
- [OK] 易于测试:依赖注入,便于Mock
- [OK] 社区活跃:问题容易找到解决方案
劣势(我们可以接受):
- [WARN] 启动速度慢(15-20秒)- 我们是长期运行的服务,不频繁重启
- [WARN] 内存占用大(500MB+)- 服务器资源充足
为什么选择Python做AI服务?
优势:
- [OK] Spacy是NLP领域最成熟的库
- [OK] NumPy/Pandas数据处理强大
- [OK] 算法工程师熟悉Python
- [OK] 与Java解耦,独立迭代
如果用Java实现NLP:
- [NO] 需要引入deeplearning4j等库,生态不如Python成熟
- [NO] 团队学习成本高
为什么选择AWS S3?
对比方案:
| 方案 | 优点 | 缺点 | 选择 |
|---|---|---|---|
| 本地文件系统 | 免费,简单 | 无法扩展,单点故障 | [NO] |
| 自建文件服务器 | 可控性强 | 运维成本高 | [NO] |
| 阿里云OSS | 国内速度快 | 客户在美国 | [NO] |
| AWS S3 | 稳定可靠,CDN加速 | 按量付费 | [OK] |
成本计算:
- 存储:$0.023/GB/月
- 请求:$0.0004/1000次GET请求
- 传输:前100GB免费
预估:每月处理1000个任务(每个100MB)= 100GB = $2.3/月
相比自建文件服务器:
- 自建:服务器$50/月 + 运维时间$200/月 = $250/月
- S3:$2.3/月
- 节省:$247.7/月(99%)
架构的扩展性
当前架构(单机部署)
┌─────────────────────────┐
│ 服务器 (4核8G) │
│ ├─ chronos-web-after │
│ ├─ chronos-web-front │
│ ├─ MySQL │
│ └─ Redis │
└─────────────────────────┘
┌─────────────────────────┐
│ 服务器 (8核16G) │
│ ├─ chronos-python │
│ └─ chronos-translate │
└─────────────────────────┘
支持规模:2000任务/天
扩展方案(水平扩展)
┌─── Nginx 负载均衡 ───┐
│ │
┌────┴────┐ ┌──────┴────┐
│ Web服务1 │ │ Web服务2 │
└─────────┘ └───────────┘
│ │
└──────┬───────────────┘
│
┌──────┴───────┐
│ Redis集群 │
└──────────────┘
│
┌───────────┼───────────┐
│ │ │
┌───┴──┐ ┌───┴──┐ ┌───┴──┐
│Python1│ │Python2│ │Python3│ ← 可独立扩展
└───────┘ └───────┘ └───────┘
支持规模:1000任务/天
扩展成本:
- 增加1台Python服务器:$100/月
- 处理能力提升:3倍
总结
架构设计的核心思想
-
简单优于复杂
- 不盲目追求微服务"正统"(Spring Cloud全家桶)
- 用Redis而非RabbitMQ
- HTTP REST而非gRPC
-
技术服务于业务
- Java做业务编排(擅长)
- Python做AI算法(擅长)
- 各取所长,不强行统一
-
按需扩展
- 当前规模用单机
- 未来可以水平扩展
- 架构有弹性
-
成本可控
- S3比自建文件服务器便宜99%
- Redis比RabbitMQ运维成本低
- Selenium爬取降低翻译成本
架构的不足与改进方向
当前不足:
- ⚠️ 服务间无服务发现(硬编码IP)
- ⚠️ 无分布式追踪(调试困难)
- ⚠️ 无熔断降级(雪崩风险)
改进方向(当规模扩大时):
- 引入Nacos做配置中心和服务发现
- 引入SkyWalking做分布式追踪
- 引入Sentinel做熔断降级
但现在不需要,因为:
- 服务数量少(4个)
- 部署规模小(2台服务器)
- 团队规模小(3人)
过度设计就是浪费!
下期
下一篇文章,我将深入讲解系统的核心算法:
《智能字幕校准系统实战(二):6级智能校准算法深度解析》
内容包括:
- 字幕时间轴漂移的本质原因
- Level 1-6匹配算法的详细实现
- Spacy语义相似度计算原理
- 箱线图异常检测算法
- 算法参数调优经验
这是整个系列中技术难度最高、最有价值的一篇,敬请期待!
系列导航:
- 第0篇:系列开篇
- 第1篇:微服务架构设计(当前)
- 第2篇:6级智能校准算法深度解析
- 第3篇:基于Spacy的多语言NLP处理实践
- 第4篇:Spring Boot异步任务处理架构
- 第5篇:多家STT/翻译服务集成方案
- 第6篇:大文件处理与性能优化实战
原创不易,如果觉得有收获,请给个三连支持!
标签:# #Spring Boot #系统设计 #技术选型 #实战项目

浙公网安备 33010602011771号