零基础学习性能测试第十一章:影子数据库-构建影子
·
目录
以下是 零基础构建影子数据库/影子表的实战指南,包含从原理到落地的完整方案,附带可即用的代码片段和避坑策略:
一、影子库/表构建核心流程
二、4种企业级构建方案(附代码)
方案1:独立影子库(最高隔离性)
# application.yml
spring:
datasource:
url: jdbc:mysql://prod-db:3306/order_db
username: prod_user
password: ******
shadow:
datasource: # 影子库配置
url: jdbc:mysql://shadow-db:3306/order_db
username: shadow_user
password: ******
路由逻辑(AOP实现):
@Aspect
@Component
public class ShadowDataSourceAspect {
@Around("execution(* com..service.*.*(..))")
public Object switchDataSource(ProceedingJoinPoint pjp) {
// 从请求上下文获取压测标记
boolean isShadow = RequestContextHolder.getRequest()
.getHeader("X-SHADOW-TEST") != null;
if (isShadow) {
DataSourceContext.setDataSource("shadowDB"); // 切换数据源
}
try {
return pjp.proceed();
} finally {
DataSourceContext.clear(); // 恢复默认
}
}
}
方案2:同库影子表(低成本)
表结构克隆:
-- 创建影子表(与生产表结构一致)
CREATE TABLE orders_shadow LIKE orders;
SQL重写拦截器:
public class ShadowTableInterceptor implements Interceptor {
private static final Pattern TABLE_REGEX = Pattern.compile("\\b(orders|inventory)\\b");
@Override
public Object intercept(Invocation invocation) throws Throwable {
BoundSql boundSql = ... // 获取原始SQL
String sql = boundSql.getSql();
if (isShadowRequest()) {
// 关键:正则替换表名
sql = TABLE_REGEX.matcher(sql)
.replaceAll("$1_shadow");
}
// 反射修改SQL
Field sqlField = boundSql.getClass().getDeclaredField("sql");
sqlField.setAccessible(true);
sqlField.set(boundSql, sql);
return invocation.proceed();
}
private boolean isShadowRequest() {
// 检测压测标记(ThreadLocal/请求头)
return ShadowContext.isActive();
}
}
方案3:数据标记法(无需建表)
ALTER TABLE orders ADD COLUMN is_shadow TINYINT DEFAULT 0 COMMENT '0:生产 1:压测';
数据操作拦截:
// MyBatis拦截器追加条件
public void beforeSelect(StatementHandler handler) {
if (isShadowRequest()) {
String originSql = handler.getBoundSql().getSql();
String newSql = originSql + (originSql.contains("WHERE") ? " AND " : " WHERE ")
+ " is_shadow = 1";
// 重写SQL...
}
}
// 插入时自动标记
@Insert("INSERT INTO orders(...) VALUES(..., #{isShadow})")
void insertOrder(Order order, @Param("isShadow") int isShadow);
方案4:中间件方案(推荐-ShardingSphere)
# 配置shadow规则
rules:
- !SHADOW
dataSources:
primary_ds:
sourceDataSourceName: ds_prod
shadowDataSourceName: ds_shadow
tables:
orders:
dataSourceNames: [primary_ds]
shadowAlgorithmNames: [simple_hint]
shadowAlgorithms:
simple_hint:
type: SIMPLE_HINT
props:
shadow: true
流量标记:
/* SHARDINGSPHERE_HINT: SHADOW=true */
SELECT * FROM orders WHERE user_id=1001;
三、关键实施步骤
步骤1:数据准备(影子库/表初始化)
# 克隆生产表结构到影子表
mysqldump -d -u prod_user -p prod_db orders > orders_schema.sql
mysql -u shadow_user -p shadow_db < orders_schema.sql
# 初始化压测数据(10万条)
INSERT INTO orders_shadow
SELECT NULL, user_id, ... , NOW()
FROM orders
WHERE id <= 100000;
步骤2:流量标记传递(全链路透传)
| 组件 | 实现方式 |
|---|---|
| HTTP请求 | 添加Header: X-SHADOW-TEST: true |
| Dubbo调用 | 使用RpcContext: RpcContext.getContext().setAttachment("isShadow", "true") |
| MQ消息 | 消息头添加属性: properties.put("X-SHADOW-TEST", "true") |
步骤3:验证数据隔离性
-- 压测前
SELECT COUNT(*) FROM orders; -- 生产: 15230
SELECT COUNT(*) FROM orders_shadow; -- 影子: 100000
-- 执行压测脚本(模拟下单)
-- 压测后
SELECT COUNT(*) FROM orders; -- 生产: 15230(未变化)
SELECT COUNT(*) FROM orders_shadow; -- 影子: 108920(新增8920条)
四、企业级最佳实践
1. 自动化数据管理
# 每日凌晨同步基础数据
def sync_shadow_data():
# 1. 清空影子表
execute_sql("TRUNCATE orders_shadow")
# 2. 复制基础数据
execute_sql("""
INSERT INTO orders_shadow
SELECT *
FROM orders
WHERE created_at < CURDATE() - INTERVAL 30 DAY
""")
# 3. 生成压测数据
generate_test_data(scale=10) # 10倍扩容
2. 防误操作安全机制
-- 影子表添加防删除标记
ALTER TABLE orders_shadow
ADD COLUMN __is_test_data TINYINT DEFAULT 1;
-- 创建拦截触发器
DELIMITER $$
CREATE TRIGGER prevent_prod_delete
BEFORE DELETE ON orders
FOR EACH ROW
BEGIN
IF OLD.__is_test_data = 0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '禁止删除生产数据!';
END IF;
END$$
DELIMITER ;
3. 压测流量染色监控
// 全局埋点记录压测流量
@Around("execution(* com..controller.*.*(..))")
public Object logShadowTraffic(ProceedingJoinPoint pjp) {
boolean isShadow = request.getHeader("X-SHADOW-TEST") != null;
Metrics.counter("shadow_requests", "type", isShadow?"shadow":"prod")
.increment();
return pjp.proceed();
}
五、各方案选型对照表
| 维度 | 影子库 | 影子表 | 数据标记法 | ShardingSphere |
|---|---|---|---|---|
| 隔离性 | ⭐️⭐️⭐️⭐️⭐️ | ⭐️⭐️⭐️ | ⭐️⭐️ | ⭐️⭐️⭐️⭐️ |
| 改造成本 | ⭐️⭐️ (需双写) | ⭐️⭐️⭐️⭐️ | ⭐️⭐️⭐️⭐️ | ⭐️⭐️ (配置驱动) |
| 数据一致性 | 完全隔离 | 表级隔离 | 行级混合 | 库/表级隔离 |
| 适用场景 | 金融/支付核心链路 | 电商/零售业务 | 读写分离架构 | 云原生环境 |
| 运维成本 | 高 (独立实例) | 低 | 中 | 低 |
六、避坑指南
-
缓存污染
- 影子查询跳过Redis:
if(isShadow) cache.disable()
- 影子查询跳过Redis:
-
外键约束
- 影子表移除外键:
ALTER TABLE orders_shadow DROP FOREIGN KEY fk_user_id
- 影子表移除外键:
-
自增ID冲突
-- 修改影子表自增起点 ALTER TABLE orders_shadow AUTO_INCREMENT=10000000; -
账单类业务
- 隔离支付渠道:压测时自动切换至沙箱支付接口
终极安全建议:
在影子库/表名前缀shadow_标识(如shadow_orders),执行所有SQL前强制检查:if(sql.contains("DROP") && !sql.contains("shadow_")) { throw new SecurityException("禁止操作生产表!"); }
更多推荐
所有评论(0)