docs(knowledge): 添加测试知识库文档
新增 6 个知识库文档,用于测试 L0+L1 混合检索功能: API 类: - payment-errors.md - 支付网关错误码定义 领域知识类: - spring-ai-tool-best-practices.md - Spring AI 工具定义最佳实践 基础设施类: - redis-config.md - Redis 缓存配置指南 - mysql-connection-pool.md - MySQL 连接池配置 - flyway-best-practices.md - Flyway 数据库迁移最佳实践 故障排查类: - fault-diagnosis-process.md - 故障诊断流程规范 所有文档均包含: - 标准 frontmatter 元数据 (title, keywords, summary, category) - 实用配置示例和代码片段 - 支持 L0 精确匹配的关键词
This commit is contained in:
@@ -0,0 +1,259 @@
|
|||||||
|
---
|
||||||
|
title: Spring AI 工具定义最佳实践
|
||||||
|
keywords: [Spring AI, @Tool, 工具定义, Agent, 函数调用]
|
||||||
|
summary: 如何为 Spring AI Agent 定义高质量的工具(Tool),包括命名、描述、参数设计和错误处理
|
||||||
|
category: domain
|
||||||
|
---
|
||||||
|
|
||||||
|
# Spring AI 工具定义最佳实践
|
||||||
|
|
||||||
|
## 工具定义基础
|
||||||
|
|
||||||
|
### 基本注解
|
||||||
|
```java
|
||||||
|
@Component
|
||||||
|
public class MyTools {
|
||||||
|
|
||||||
|
@Tool(description = "查询用户信息。参数 userId: 用户ID(必填)")
|
||||||
|
public UserInfo getUserInfo(String userId) {
|
||||||
|
// 实现
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 关键要素
|
||||||
|
1. **@Component** - 让 Spring 扫描到
|
||||||
|
2. **@Tool** - 标记为 Agent 可调用的工具
|
||||||
|
3. **description** - 告诉 Agent 这个工具做什么
|
||||||
|
|
||||||
|
## 描述(Description)编写规范
|
||||||
|
|
||||||
|
### 好的描述
|
||||||
|
```java
|
||||||
|
@Tool(description = "查询知识库文档。优先精确匹配关键词,未命中或多个匹配时自动补充语义相关片段。" +
|
||||||
|
"参数 query: 查询关键词,例如 'ERR_TIMEOUT'、'支付网关超时'")
|
||||||
|
public LookupResult lookupKnowledge(String query) { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
**要点**:
|
||||||
|
- ✅ 说明工具用途(查询知识库)
|
||||||
|
- ✅ 说明工作机制(精确匹配 → 语义补充)
|
||||||
|
- ✅ 说明参数含义和示例
|
||||||
|
|
||||||
|
### 差的描述
|
||||||
|
```java
|
||||||
|
@Tool(description = "查询文档") // ❌ 太简略
|
||||||
|
public LookupResult lookup(String q) { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
## 参数设计
|
||||||
|
|
||||||
|
### 参数命名
|
||||||
|
```java
|
||||||
|
// ✅ 好的命名 - 语义清晰
|
||||||
|
public Result search(String query, int maxResults, String category)
|
||||||
|
|
||||||
|
// ❌ 差的命名 - 缩写难懂
|
||||||
|
public Result search(String q, int max, String cat)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 参数类型
|
||||||
|
```java
|
||||||
|
// ✅ 使用明确的类型
|
||||||
|
public UserInfo getUser(String userId)
|
||||||
|
public List<Order> getOrders(LocalDate startDate, LocalDate endDate)
|
||||||
|
|
||||||
|
// ❌ 使用 Object 或 Map
|
||||||
|
public Object getUser(Map<String, Object> params) // Agent 不知道传什么
|
||||||
|
```
|
||||||
|
|
||||||
|
### 可选参数处理
|
||||||
|
```java
|
||||||
|
@Tool(description = "查询订单。参数 status: 订单状态(可选,不传则查所有)")
|
||||||
|
public List<Order> getOrders(
|
||||||
|
@Nullable String status // 使用 @Nullable 标注
|
||||||
|
) {
|
||||||
|
if (status == null) {
|
||||||
|
return orderRepository.findAll();
|
||||||
|
}
|
||||||
|
return orderRepository.findByStatus(status);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 返回值设计
|
||||||
|
|
||||||
|
### 使用明确的返回类型
|
||||||
|
```java
|
||||||
|
// ✅ 好的返回类型
|
||||||
|
public class LookupResult {
|
||||||
|
private boolean found;
|
||||||
|
private PrimaryResult primary;
|
||||||
|
private SupplementResult supplement;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ❌ 返回 String - Agent 难以解析
|
||||||
|
public String lookup(String query) {
|
||||||
|
return "找到文档: xxx"; // 非结构化
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 返回错误信息
|
||||||
|
```java
|
||||||
|
public LookupResult lookup(String query) {
|
||||||
|
if (query == null || query.isEmpty()) {
|
||||||
|
return LookupResult.builder()
|
||||||
|
.found(false)
|
||||||
|
.error("查询关键词不能为空")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
// 正常逻辑
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 错误处理
|
||||||
|
|
||||||
|
### 优雅降级
|
||||||
|
```java
|
||||||
|
@Tool(description = "查询用户信息")
|
||||||
|
public UserInfo getUser(String userId) {
|
||||||
|
try {
|
||||||
|
return userService.findById(userId);
|
||||||
|
} catch (UserNotFoundException e) {
|
||||||
|
log.warn("用户不存在: userId={}", userId);
|
||||||
|
return UserInfo.notFound(userId); // 返回特殊对象,不抛异常
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("查询用户失败: userId={}", userId, e);
|
||||||
|
return UserInfo.error("系统错误,请稍后重试");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 不要抛出未捕获的异常
|
||||||
|
```java
|
||||||
|
// ❌ 不要这样做
|
||||||
|
@Tool(description = "查询用户")
|
||||||
|
public UserInfo getUser(String userId) {
|
||||||
|
return userService.findById(userId); // 可能抛出异常,Agent 无法处理
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 可观测性
|
||||||
|
|
||||||
|
### 日志规范
|
||||||
|
```java
|
||||||
|
@Tool(description = "查询订单")
|
||||||
|
public List<Order> getOrders(String userId) {
|
||||||
|
String requestId = UUID.randomUUID().toString().substring(0, 8);
|
||||||
|
long startTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
log.info("[{}] 收到订单查询请求: userId={}", requestId, userId);
|
||||||
|
|
||||||
|
try {
|
||||||
|
List<Order> orders = orderService.findByUserId(userId);
|
||||||
|
long elapsed = System.currentTimeMillis() - startTime;
|
||||||
|
log.info("[{}] 查询完成: count={}, time={}ms", requestId, orders.size(), elapsed);
|
||||||
|
return orders;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("[{}] 查询失败: userId={}", requestId, userId, e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 性能优化
|
||||||
|
|
||||||
|
### 设置合理的超时
|
||||||
|
```java
|
||||||
|
@Tool(description = "查询大数据集")
|
||||||
|
public DataResult queryBigData(String query) {
|
||||||
|
// 设置超时保护
|
||||||
|
return CompletableFuture
|
||||||
|
.supplyAsync(() -> heavyQuery(query))
|
||||||
|
.orTimeout(5, TimeUnit.SECONDS)
|
||||||
|
.exceptionally(ex -> DataResult.timeout())
|
||||||
|
.join();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 避免返回超大数据
|
||||||
|
```java
|
||||||
|
// ✅ 分页或限制数量
|
||||||
|
@Tool(description = "查询用户列表(最多返回 100 条)")
|
||||||
|
public List<User> listUsers(int page, int size) {
|
||||||
|
size = Math.min(size, 100); // 强制上限
|
||||||
|
return userService.findAll(PageRequest.of(page, size));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ❌ 返回全量数据
|
||||||
|
public List<User> listAllUsers() {
|
||||||
|
return userService.findAll(); // 可能几万条
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 工具组合示例
|
||||||
|
|
||||||
|
### 查询 + 操作的组合
|
||||||
|
```java
|
||||||
|
@Component
|
||||||
|
public class OrderTools {
|
||||||
|
|
||||||
|
@Tool(description = "查询订单详情")
|
||||||
|
public OrderDetail getOrder(String orderId) { ... }
|
||||||
|
|
||||||
|
@Tool(description = "取消订单")
|
||||||
|
public CancelResult cancelOrder(String orderId, String reason) { ... }
|
||||||
|
|
||||||
|
@Tool(description = "申请退款")
|
||||||
|
public RefundResult refund(String orderId, Double amount) { ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Agent 使用场景**:
|
||||||
|
1. 用户:"帮我查一下订单 12345"
|
||||||
|
2. Agent 调用 `getOrder("12345")`
|
||||||
|
3. 用户:"帮我取消这个订单"
|
||||||
|
4. Agent 调用 `cancelOrder("12345", "用户主动取消")`
|
||||||
|
|
||||||
|
## 常见陷阱
|
||||||
|
|
||||||
|
### ❌ 工具做太多事
|
||||||
|
```java
|
||||||
|
// 不要把整个业务流程塞进一个工具
|
||||||
|
@Tool(description = "处理订单")
|
||||||
|
public void processOrder(String orderId) {
|
||||||
|
// 查询订单
|
||||||
|
// 验证库存
|
||||||
|
// 扣减库存
|
||||||
|
// 创建物流单
|
||||||
|
// 发送通知
|
||||||
|
// ... 太多步骤,Agent 无法介入
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### ✅ 拆分成多个工具
|
||||||
|
```java
|
||||||
|
@Tool(description = "查询订单")
|
||||||
|
public Order getOrder(String orderId) { ... }
|
||||||
|
|
||||||
|
@Tool(description = "验证库存")
|
||||||
|
public StockResult checkStock(String productId, int quantity) { ... }
|
||||||
|
|
||||||
|
@Tool(description = "创建物流单")
|
||||||
|
public ShipmentResult createShipment(String orderId) { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
### ❌ 描述不准确
|
||||||
|
```java
|
||||||
|
@Tool(description = "查询用户")
|
||||||
|
public UserInfo getUser(String query) {
|
||||||
|
// 实际上支持按 userId、email、手机号查询
|
||||||
|
// 但描述没说清楚,Agent 不知道
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### ✅ 描述完整
|
||||||
|
```java
|
||||||
|
@Tool(description = "查询用户信息。支持按 userId、email 或手机号查询。" +
|
||||||
|
"参数 query: 用户ID、邮箱或手机号")
|
||||||
|
public UserInfo getUser(String query) { ... }
|
||||||
|
```
|
||||||
@@ -0,0 +1,310 @@
|
|||||||
|
---
|
||||||
|
title: Flyway 数据库迁移最佳实践
|
||||||
|
keywords: [Flyway, 数据库迁移, 版本管理, schema, migration]
|
||||||
|
summary: Flyway 数据库迁移的命名规范、编写技巧、回滚策略和常见问题处理
|
||||||
|
category: infrastructure
|
||||||
|
---
|
||||||
|
|
||||||
|
# Flyway 数据库迁移最佳实践
|
||||||
|
|
||||||
|
## 命名规范
|
||||||
|
|
||||||
|
### 标准格式
|
||||||
|
```
|
||||||
|
V{version}__{description}.sql
|
||||||
|
|
||||||
|
示例:
|
||||||
|
V001__create_user_table.sql
|
||||||
|
V002__add_email_to_user.sql
|
||||||
|
V003__create_order_table.sql
|
||||||
|
V004__add_metadata_to_api_document.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
**规则**:
|
||||||
|
- `V` 大写,表示 Versioned migration
|
||||||
|
- 版本号用 3 位数字(001, 002...)
|
||||||
|
- 两个下划线 `__` 分隔版本号和描述
|
||||||
|
- 描述用小写字母和下划线
|
||||||
|
|
||||||
|
### 版本号管理
|
||||||
|
```
|
||||||
|
V001 - 初始表结构
|
||||||
|
V002 - 添加字段
|
||||||
|
V003 - 创建索引
|
||||||
|
V004 - 修改字段类型
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
**建议**:
|
||||||
|
- 预留版本号空间(001, 010, 020...)
|
||||||
|
- 紧急修复用中间号(V005_hotfix__...)
|
||||||
|
|
||||||
|
## SQL 编写规范
|
||||||
|
|
||||||
|
### 添加列
|
||||||
|
```sql
|
||||||
|
-- ✅ 好的写法 - 包含默认值和注释
|
||||||
|
ALTER TABLE user
|
||||||
|
ADD COLUMN email VARCHAR(100) DEFAULT '' COMMENT '用户邮箱';
|
||||||
|
|
||||||
|
-- ❌ 不好的写法 - 缺少默认值
|
||||||
|
ALTER TABLE user
|
||||||
|
ADD COLUMN email VARCHAR(100); -- 已有数据会是 NULL
|
||||||
|
```
|
||||||
|
|
||||||
|
### 修改列
|
||||||
|
```sql
|
||||||
|
-- ✅ 先添加新列,再迁移数据,最后删除旧列
|
||||||
|
ALTER TABLE user ADD COLUMN new_status VARCHAR(20) DEFAULT 'active';
|
||||||
|
UPDATE user SET new_status = old_status WHERE old_status IS NOT NULL;
|
||||||
|
ALTER TABLE user DROP COLUMN old_status;
|
||||||
|
ALTER TABLE user CHANGE COLUMN new_status status VARCHAR(20);
|
||||||
|
|
||||||
|
-- ❌ 直接修改 - 可能导致数据丢失
|
||||||
|
ALTER TABLE user MODIFY COLUMN status INT;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 创建索引
|
||||||
|
```sql
|
||||||
|
-- ✅ 指定索引名称
|
||||||
|
CREATE INDEX idx_user_email ON user(email);
|
||||||
|
CREATE INDEX idx_order_user_id ON `order`(user_id);
|
||||||
|
|
||||||
|
-- ❌ 不指定名称 - 自动生成的名称难以管理
|
||||||
|
CREATE INDEX ON user(email);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 外键约束
|
||||||
|
```sql
|
||||||
|
-- ✅ 命名规范
|
||||||
|
ALTER TABLE `order`
|
||||||
|
ADD CONSTRAINT fk_order_user_id
|
||||||
|
FOREIGN KEY (user_id) REFERENCES user(id)
|
||||||
|
ON DELETE CASCADE;
|
||||||
|
|
||||||
|
-- ❌ 不指定名称
|
||||||
|
ALTER TABLE `order`
|
||||||
|
ADD FOREIGN KEY (user_id) REFERENCES user(id);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 幂等性保证
|
||||||
|
|
||||||
|
### 检查表是否存在
|
||||||
|
```sql
|
||||||
|
-- 创建表前检查
|
||||||
|
CREATE TABLE IF NOT EXISTS user (
|
||||||
|
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||||
|
username VARCHAR(50) NOT NULL
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 检查列是否存在
|
||||||
|
```sql
|
||||||
|
-- 添加列前检查
|
||||||
|
ALTER TABLE user
|
||||||
|
ADD COLUMN IF NOT EXISTS email VARCHAR(100);
|
||||||
|
|
||||||
|
-- 或使用存储过程(MySQL < 8.0)
|
||||||
|
SET @col_exists = (
|
||||||
|
SELECT COUNT(*) FROM information_schema.columns
|
||||||
|
WHERE table_name = 'user' AND column_name = 'email'
|
||||||
|
);
|
||||||
|
|
||||||
|
SET @query = IF(@col_exists = 0,
|
||||||
|
'ALTER TABLE user ADD COLUMN email VARCHAR(100)',
|
||||||
|
'SELECT "Column exists" AS msg'
|
||||||
|
);
|
||||||
|
|
||||||
|
PREPARE stmt FROM @query;
|
||||||
|
EXECUTE stmt;
|
||||||
|
DEALLOCATE PREPARE stmt;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 检查索引是否存在
|
||||||
|
```sql
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_user_email ON user(email);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 数据迁移
|
||||||
|
|
||||||
|
### 分批处理大表
|
||||||
|
```sql
|
||||||
|
-- ❌ 一次更新全部 - 可能锁表很久
|
||||||
|
UPDATE large_table SET status = 'active' WHERE status IS NULL;
|
||||||
|
|
||||||
|
-- ✅ 分批更新
|
||||||
|
UPDATE large_table
|
||||||
|
SET status = 'active'
|
||||||
|
WHERE status IS NULL
|
||||||
|
LIMIT 1000;
|
||||||
|
|
||||||
|
-- 重复执行直到影响行数为 0
|
||||||
|
```
|
||||||
|
|
||||||
|
### 使用事务(DDL 语句除外)
|
||||||
|
```sql
|
||||||
|
START TRANSACTION;
|
||||||
|
|
||||||
|
UPDATE user SET status = 'active' WHERE status = 'enabled';
|
||||||
|
UPDATE user SET status = 'inactive' WHERE status = 'disabled';
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
|
```
|
||||||
|
|
||||||
|
## 回滚策略
|
||||||
|
|
||||||
|
### 不支持自动回滚
|
||||||
|
Flyway 社区版不支持自动回滚,需要手动编写撤销脚本:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- V005__add_email_to_user.sql
|
||||||
|
ALTER TABLE user ADD COLUMN email VARCHAR(100);
|
||||||
|
|
||||||
|
-- V005__add_email_to_user.undo.sql (手动执行)
|
||||||
|
ALTER TABLE user DROP COLUMN email;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 建议使用新版本修复
|
||||||
|
```sql
|
||||||
|
-- V005 出错了,不要回滚
|
||||||
|
-- 而是创建 V006 修复
|
||||||
|
|
||||||
|
-- V006__fix_user_email.sql
|
||||||
|
ALTER TABLE user MODIFY COLUMN email VARCHAR(200);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### 问题 1: 迁移失败后状态卡住
|
||||||
|
|
||||||
|
**症状**:
|
||||||
|
```
|
||||||
|
FlywayException: Migration failed!
|
||||||
|
Schema history table shows failed migration.
|
||||||
|
```
|
||||||
|
|
||||||
|
**解决**:
|
||||||
|
```sql
|
||||||
|
-- 查看迁移历史
|
||||||
|
SELECT * FROM flyway_schema_history ORDER BY installed_rank DESC;
|
||||||
|
|
||||||
|
-- 删除失败记录
|
||||||
|
DELETE FROM flyway_schema_history WHERE version = '005' AND success = 0;
|
||||||
|
|
||||||
|
-- 修复 SQL 脚本后重新启动
|
||||||
|
```
|
||||||
|
|
||||||
|
### 问题 2: Checksum 不匹配
|
||||||
|
|
||||||
|
**症状**:
|
||||||
|
```
|
||||||
|
FlywayException: Checksum mismatch for migration version 005
|
||||||
|
```
|
||||||
|
|
||||||
|
**原因**:迁移脚本被修改了
|
||||||
|
|
||||||
|
**解决**:
|
||||||
|
```sql
|
||||||
|
-- 方案 1: 修复 checksum(仅开发环境)
|
||||||
|
UPDATE flyway_schema_history
|
||||||
|
SET checksum = NULL
|
||||||
|
WHERE version = '005';
|
||||||
|
|
||||||
|
-- 方案 2: 创建新版本(推荐)
|
||||||
|
-- 不要修改已执行的迁移脚本,创建 V006
|
||||||
|
```
|
||||||
|
|
||||||
|
### 问题 3: 多个开发者同时创建迁移
|
||||||
|
|
||||||
|
**场景**:
|
||||||
|
- 开发者 A 创建 V005
|
||||||
|
- 开发者 B 创建 V005
|
||||||
|
- 冲突!
|
||||||
|
|
||||||
|
**预防**:
|
||||||
|
```
|
||||||
|
使用时间戳版本号:
|
||||||
|
V20260624001__add_user_email.sql
|
||||||
|
V20260624002__add_order_index.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
## 生产环境最佳实践
|
||||||
|
|
||||||
|
### 1. 先验证后应用
|
||||||
|
```bash
|
||||||
|
# 开发环境测试
|
||||||
|
mvn flyway:migrate
|
||||||
|
|
||||||
|
# 预生产环境验证
|
||||||
|
mvn flyway:migrate -Dflyway.url=jdbc:mysql://pre-prod-db:3306/db
|
||||||
|
|
||||||
|
# 生产环境应用
|
||||||
|
mvn flyway:migrate -Dflyway.url=jdbc:mysql://prod-db:3306/db
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 备份数据库
|
||||||
|
```bash
|
||||||
|
# 应用迁移前备份
|
||||||
|
mysqldump -u root -p superbiz_agent > backup_before_v005.sql
|
||||||
|
|
||||||
|
# 应用迁移
|
||||||
|
mvn spring-boot:run
|
||||||
|
|
||||||
|
# 出问题时恢复
|
||||||
|
mysql -u root -p superbiz_agent < backup_before_v005.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 限制自动迁移
|
||||||
|
```yaml
|
||||||
|
# 生产环境配置
|
||||||
|
spring:
|
||||||
|
flyway:
|
||||||
|
enabled: false # 禁用自动迁移
|
||||||
|
|
||||||
|
# 手动触发
|
||||||
|
mvn flyway:migrate -Dspring.profiles.active=prod
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 监控迁移时间
|
||||||
|
```sql
|
||||||
|
SELECT version, description, type, installed_on, execution_time
|
||||||
|
FROM flyway_schema_history
|
||||||
|
ORDER BY installed_rank DESC
|
||||||
|
LIMIT 10;
|
||||||
|
```
|
||||||
|
|
||||||
|
## 工具和命令
|
||||||
|
|
||||||
|
### Maven 命令
|
||||||
|
```bash
|
||||||
|
# 查看迁移信息
|
||||||
|
mvn flyway:info
|
||||||
|
|
||||||
|
# 执行迁移
|
||||||
|
mvn flyway:migrate
|
||||||
|
|
||||||
|
# 验证迁移
|
||||||
|
mvn flyway:validate
|
||||||
|
|
||||||
|
# 清空数据库(危险!仅开发环境)
|
||||||
|
mvn flyway:clean
|
||||||
|
```
|
||||||
|
|
||||||
|
### 配置文件
|
||||||
|
```yaml
|
||||||
|
spring:
|
||||||
|
flyway:
|
||||||
|
enabled: true
|
||||||
|
baseline-on-migrate: true # 已有数据库时从当前版本开始
|
||||||
|
locations: classpath:db/migration
|
||||||
|
table: flyway_schema_history
|
||||||
|
validate-on-migrate: true
|
||||||
|
```
|
||||||
|
|
||||||
|
## 团队协作规范
|
||||||
|
|
||||||
|
1. **迁移脚本不可修改**:已合并的脚本禁止修改
|
||||||
|
2. **版本号递增**:新脚本必须比最新版本号大
|
||||||
|
3. **命名规范统一**:遵循 `V{version}__{description}.sql`
|
||||||
|
4. **Code Review**:迁移脚本必须经过审查
|
||||||
|
5. **测试覆盖**:每个迁移都要测试(空库 + 有数据)
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
---
|
||||||
|
title: MySQL 数据库连接池配置
|
||||||
|
keywords: [MySQL, HikariCP, 连接池, 数据库, 性能优化]
|
||||||
|
summary: MySQL 连接池的配置参数、性能调优和故障排查指南
|
||||||
|
category: infrastructure
|
||||||
|
---
|
||||||
|
|
||||||
|
# MySQL 数据库连接池配置
|
||||||
|
|
||||||
|
## HikariCP 配置
|
||||||
|
|
||||||
|
### 基础配置
|
||||||
|
```yaml
|
||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
url: jdbc:mysql://localhost:3306/superbiz_agent?useSSL=false&serverTimezone=Asia/Shanghai
|
||||||
|
username: root
|
||||||
|
password: password
|
||||||
|
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||||
|
hikari:
|
||||||
|
maximum-pool-size: 10
|
||||||
|
minimum-idle: 5
|
||||||
|
connection-timeout: 30000
|
||||||
|
idle-timeout: 600000
|
||||||
|
max-lifetime: 1800000
|
||||||
|
```
|
||||||
|
|
||||||
|
## 关键参数说明
|
||||||
|
|
||||||
|
### maximum-pool-size
|
||||||
|
- **默认值**:10
|
||||||
|
- **建议值**:根据并发量调整
|
||||||
|
- **公式**:connections = ((core_count * 2) + effective_spindle_count)
|
||||||
|
- **注意**:不是越大越好,过大会增加数据库负担
|
||||||
|
|
||||||
|
### connection-timeout
|
||||||
|
- **默认值**:30000ms (30秒)
|
||||||
|
- **说明**:等待连接的最大时间
|
||||||
|
- **建议**:根据业务超时要求调整
|
||||||
|
|
||||||
|
### idle-timeout
|
||||||
|
- **默认值**:600000ms (10分钟)
|
||||||
|
- **说明**:连接空闲多久后被释放
|
||||||
|
- **建议**:小于 MySQL wait_timeout
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### 连接泄漏
|
||||||
|
|
||||||
|
**症状**:
|
||||||
|
- 应用无法获取数据库连接
|
||||||
|
- 日志显示 "Connection is not available"
|
||||||
|
|
||||||
|
**排查**:
|
||||||
|
```java
|
||||||
|
// 检查是否有未关闭的连接
|
||||||
|
try (Connection conn = dataSource.getConnection()) {
|
||||||
|
// 使用连接
|
||||||
|
} // 自动关闭
|
||||||
|
```
|
||||||
|
|
||||||
|
**解决**:
|
||||||
|
- 使用 try-with-resources
|
||||||
|
- 检查事务是否正常提交/回滚
|
||||||
|
|
||||||
|
### wait_timeout 超时
|
||||||
|
|
||||||
|
**症状**:MySQL 错误 "The last packet successfully received from the server was X milliseconds ago"
|
||||||
|
|
||||||
|
**排查**:
|
||||||
|
```sql
|
||||||
|
SHOW VARIABLES LIKE 'wait_timeout';
|
||||||
|
```
|
||||||
|
|
||||||
|
**解决**:
|
||||||
|
```yaml
|
||||||
|
hikari:
|
||||||
|
max-lifetime: 1800000 # 小于 MySQL wait_timeout
|
||||||
|
```
|
||||||
|
|
||||||
|
## 性能监控
|
||||||
|
|
||||||
|
### HikariCP 指标
|
||||||
|
```java
|
||||||
|
HikariPoolMXBean poolMXBean = hikariDataSource.getHikariPoolMXBean();
|
||||||
|
int active = poolMXBean.getActiveConnections();
|
||||||
|
int idle = poolMXBean.getIdleConnections();
|
||||||
|
int total = poolMXBean.getTotalConnections();
|
||||||
|
```
|
||||||
|
|
||||||
|
### 慢查询监控
|
||||||
|
```sql
|
||||||
|
-- 开启慢查询日志
|
||||||
|
SET GLOBAL slow_query_log = 'ON';
|
||||||
|
SET GLOBAL long_query_time = 2;
|
||||||
|
|
||||||
|
-- 查看慢查询
|
||||||
|
SELECT * FROM mysql.slow_log ORDER BY start_time DESC LIMIT 10;
|
||||||
|
```
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
---
|
||||||
|
title: Redis 缓存配置指南
|
||||||
|
keywords: [Redis, 缓存, 配置, 连接池, 超时]
|
||||||
|
summary: Redis 缓存的配置参数说明、连接池设置和常见问题排查
|
||||||
|
category: infrastructure
|
||||||
|
---
|
||||||
|
|
||||||
|
# Redis 缓存配置指南
|
||||||
|
|
||||||
|
## 基础配置
|
||||||
|
|
||||||
|
### 连接参数
|
||||||
|
```yaml
|
||||||
|
spring:
|
||||||
|
redis:
|
||||||
|
host: localhost
|
||||||
|
port: 6379
|
||||||
|
password: your_password
|
||||||
|
database: 0
|
||||||
|
timeout: 3000ms
|
||||||
|
```
|
||||||
|
|
||||||
|
### 连接池配置
|
||||||
|
```yaml
|
||||||
|
spring:
|
||||||
|
redis:
|
||||||
|
lettuce:
|
||||||
|
pool:
|
||||||
|
max-active: 8
|
||||||
|
max-idle: 8
|
||||||
|
min-idle: 0
|
||||||
|
max-wait: -1ms
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### 超时问题排查
|
||||||
|
|
||||||
|
**症状**:Redis 操作超时
|
||||||
|
|
||||||
|
**排查步骤**:
|
||||||
|
1. 检查网络连接:`ping redis_host`
|
||||||
|
2. 检查 Redis 服务状态:`redis-cli ping`
|
||||||
|
3. 查看慢查询日志:`redis-cli slowlog get 10`
|
||||||
|
4. 检查连接池状态
|
||||||
|
|
||||||
|
**解决方案**:
|
||||||
|
- 增加超时时间
|
||||||
|
- 优化慢查询
|
||||||
|
- 调整连接池大小
|
||||||
|
|
||||||
|
### 连接数过多
|
||||||
|
|
||||||
|
**症状**:达到 Redis 最大连接数限制
|
||||||
|
|
||||||
|
**排查**:
|
||||||
|
```bash
|
||||||
|
redis-cli info clients
|
||||||
|
```
|
||||||
|
|
||||||
|
**解决**:
|
||||||
|
- 调整 `maxclients` 参数
|
||||||
|
- 检查连接泄漏
|
||||||
|
- 启用连接池复用
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
---
|
||||||
|
title: 故障诊断流程规范
|
||||||
|
keywords: [故障诊断, 排查, 根因分析, RCA, 应急响应]
|
||||||
|
summary: 生产环境故障的标准诊断流程、根因分析方法和文档规范
|
||||||
|
category: troubleshooting
|
||||||
|
---
|
||||||
|
|
||||||
|
# 故障诊断流程规范
|
||||||
|
|
||||||
|
## 应急响应流程
|
||||||
|
|
||||||
|
### 1. 初步评估(5 分钟内)
|
||||||
|
|
||||||
|
**关键问题**:
|
||||||
|
- 影响范围:多少用户受影响?
|
||||||
|
- 严重程度:P0(全站挂)/ P1(核心功能)/ P2(次要功能)
|
||||||
|
- 开始时间:什么时候开始的?
|
||||||
|
|
||||||
|
**立即行动**:
|
||||||
|
- 通知相关人员
|
||||||
|
- 开启故障战室
|
||||||
|
- 记录时间线
|
||||||
|
|
||||||
|
### 2. 快速止血(15-30 分钟)
|
||||||
|
|
||||||
|
**优先级**:恢复服务 > 找根因
|
||||||
|
|
||||||
|
**常见止血手段**:
|
||||||
|
- 回滚最近部署
|
||||||
|
- 重启服务
|
||||||
|
- 流量切换
|
||||||
|
- 降级非核心功能
|
||||||
|
|
||||||
|
**验证止血**:
|
||||||
|
- 检查监控指标恢复
|
||||||
|
- 抽样验证用户功能
|
||||||
|
- 确认错误日志减少
|
||||||
|
|
||||||
|
### 3. 根因分析
|
||||||
|
|
||||||
|
**信息收集**:
|
||||||
|
- 错误日志(ELK/Kibana)
|
||||||
|
- 监控指标(Grafana)
|
||||||
|
- 慢查询日志
|
||||||
|
- 堆栈信息
|
||||||
|
- 最近变更记录
|
||||||
|
|
||||||
|
**分析方法**:
|
||||||
|
- 5-Why 分析法
|
||||||
|
- 时间线对比(问题前后变化)
|
||||||
|
- 相关性分析(哪些指标同时异常)
|
||||||
|
|
||||||
|
## 5-Why 分析法
|
||||||
|
|
||||||
|
**示例:API 超时故障**
|
||||||
|
|
||||||
|
1. **为什么 API 超时?**
|
||||||
|
- 数据库查询慢
|
||||||
|
|
||||||
|
2. **为什么数据库查询慢?**
|
||||||
|
- 索引失效
|
||||||
|
|
||||||
|
3. **为什么索引失效?**
|
||||||
|
- 表数据量暴增,执行计划变更
|
||||||
|
|
||||||
|
4. **为什么表数据量暴增?**
|
||||||
|
- 定时清理任务失败
|
||||||
|
|
||||||
|
5. **为什么清理任务失败?**
|
||||||
|
- 磁盘空间不足,任务异常退出
|
||||||
|
|
||||||
|
**根因**:磁盘空间监控未配置告警
|
||||||
|
|
||||||
|
## 故障报告模板
|
||||||
|
|
||||||
|
### 1. 故障概要
|
||||||
|
- 发生时间:
|
||||||
|
- 影响时长:
|
||||||
|
- 影响范围:
|
||||||
|
- 严重程度:
|
||||||
|
|
||||||
|
### 2. 故障现象
|
||||||
|
- 用户反馈:
|
||||||
|
- 错误日志:
|
||||||
|
- 监控截图:
|
||||||
|
|
||||||
|
### 3. 根本原因
|
||||||
|
- 直接原因:
|
||||||
|
- 根本原因:(5-Why 分析)
|
||||||
|
- 相关变更:
|
||||||
|
|
||||||
|
### 4. 解决方案
|
||||||
|
- 临时方案:
|
||||||
|
- 长期方案:
|
||||||
|
- 预防措施:
|
||||||
|
|
||||||
|
### 5. 时间线
|
||||||
|
```
|
||||||
|
10:00 - 用户反馈 API 超时
|
||||||
|
10:05 - 确认影响范围,通知团队
|
||||||
|
10:10 - 发现数据库慢查询
|
||||||
|
10:15 - 执行索引优化,服务恢复
|
||||||
|
10:30 - 根因分析完成
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. 改进措施
|
||||||
|
- 技术改进:
|
||||||
|
- 流程改进:
|
||||||
|
- 监控增强:
|
||||||
|
|
||||||
|
## 常见故障分类
|
||||||
|
|
||||||
|
### 性能类
|
||||||
|
- 慢查询
|
||||||
|
- 内存溢出
|
||||||
|
- CPU 飙高
|
||||||
|
- 线程池耗尽
|
||||||
|
|
||||||
|
### 可用性类
|
||||||
|
- 服务宕机
|
||||||
|
- 网络故障
|
||||||
|
- 依赖服务挂
|
||||||
|
- 数据库连接池满
|
||||||
|
|
||||||
|
### 数据类
|
||||||
|
- 数据不一致
|
||||||
|
- 数据丢失
|
||||||
|
- 重复数据
|
||||||
|
|
||||||
|
### 安全类
|
||||||
|
- 认证失败
|
||||||
|
- 权限绕过
|
||||||
|
- SQL 注入
|
||||||
|
- DDoS 攻击
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
### 日志规范
|
||||||
|
```java
|
||||||
|
// 关键操作记录请求 ID
|
||||||
|
log.info("[{}] 开始处理支付请求: userId={}, amount={}",
|
||||||
|
requestId, userId, amount);
|
||||||
|
|
||||||
|
// 异常必须记录完整堆栈
|
||||||
|
log.error("[{}] 支付失败", requestId, e);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 监控指标
|
||||||
|
- **Golden Signals**:延迟、流量、错误率、饱和度
|
||||||
|
- **业务指标**:订单量、支付成功率
|
||||||
|
- **资源指标**:CPU、内存、磁盘、网络
|
||||||
|
|
||||||
|
### 告警阈值
|
||||||
|
- 错误率 > 1%
|
||||||
|
- P99 延迟 > 2s
|
||||||
|
- 数据库连接池使用率 > 80%
|
||||||
|
- 内存使用率 > 85%
|
||||||
Reference in New Issue
Block a user