祈盟SDK 代码禁区
这些写法在本项目历史中出过事。
AI 生成代码时,必须避免以下 8 种模式。
最后更新:2026-06-30
禁区 1:OR 条件拼 SQL
错误写法(永远不要这么写)
// 错误:直接拼 OR 条件,2w 条条件打 MySQL
String sql = "SELECT * FROM t WHERE " +
conditions.stream()
.map(c -> "(a=" + c.a + " AND b='" + c.b + "')")
.collect(Collectors.joining(" OR "));
事故记录
- 5/26 MySQL CPU 爆掉事故:2w 条 OR 条件,CPU 90%+ 持续 8 分钟
正确写法(3 种方案)
// 方案 A: IN 语法(推荐,最简单)
String sql = "SELECT * FROM t WHERE a IN (...) AND b = ?";
// 方案 B: 分批 IN(数据量超大时)
List<List<String>> batches = Lists.partition(userIds, 1000);
for (List<String> batch : batches) {
userDao.findByIds(batch);
}
// 方案 C: 临时表 JOIN(性能最优)
"INSERT INTO tmp_query (a, b) VALUES (?, ?), (?, ?), ...";
"SELECT t.* FROM t JOIN tmp_query USING (a, b)";
禁区 2:无退避重试
错误写法(永远不要这么写)
// 错误:无退避连续重试
for (int i = 0; i < 5; i++) {
try { fetch(); break; }
catch (Exception e) { log.error("retry " + i); }
}
# Python 错误写法
for i in range(5):
try:
fetch_data()
break
except Exception as e:
log.error(f"retry {i}")
事故记录
- 5/26 MySQL 二次伤害:连续 5 次重试,每次都打 DB
正确写法
// Resilience4j
RetryConfig config = RetryConfig.custom()
.maxAttempts(5)
.intervalFunction(IntervalFunction.ofExponentialBackoff(30000L, 2.0, 300000L))
.retryOnException(e -> e instanceof TransientException)
.build();
# Python 指数退避
import time
import random
def retry_with_backoff(func, max_attempts=5):
delays = [30, 60, 120, 240, 300]
for attempt in range(max_attempts):
try:
return func()
except TransientException:
if attempt == max_attempts - 1:
return get_cached_or_default() # 熔断
delay = delays[attempt] + random.uniform(0, 5)
time.sleep(delay)
except PermanentException:
raise # 持续故障不重试
禁区 3:客户端缓存 > 服务端会话
错误写法(永远不要这么写)
# 错误:客户端缓存时长 > 服务端会话有效期
SDK_SESSION_HOURS = 14 # 服务端
SCRIPT_CACHE_HOURS = 24 # 客户端
# 结果:第 14h 静默中断,无任何报错
事故记录
正确写法
# 正确:客户端缓存 < 服务端会话,预留 10%-20% 余量
SDK_SESSION_HOURS = 14
SCRIPT_CACHE_HOURS = SDK_SESSION_HOURS - 2 # = 12h
SCRIPT_CACHE_HOURS = 12
# 正确:每次使用前校验缓存有效性
def fetch_orders():
if not is_cookie_valid():
re_login()
return do_fetch()
禁区 4:用 DOUBLE 存金额
错误 DDL(永远不要这么写)
-- 错误:DOUBLE 精度溢出
amount DOUBLE(11,2)
amount FLOAT
-- 0.1 + 0.2 在 double 下不是精确的 0.3
-- 累计计算后误差放大
事故记录
- SDK 金额存储 double 精度问题(长期未根治,周报标"架构治理最低分")
正确 DDL
-- 方案 A: DECIMAL(适合报表展示)
amount DECIMAL(15,2) NOT NULL DEFAULT 0 COMMENT '金额(元)'
-- 方案 B: BIGINT(适合高频计算,避免小数运算)
amount_cents BIGINT NOT NULL DEFAULT 0 COMMENT '金额(分)'
正确应用层写法
// 错误:double 累加
double total = 0.0;
for (Order order : orders) {
total += order.getAmount();
}
// 正确:BigDecimal
BigDecimal total = BigDecimal.ZERO;
for (Order order : orders) {
total = total.add(order.getAmount());
}
禁区 5:实时查明细做统计
错误 SQL(永远不要这么写)
-- 错误:直播平台查"昨日活跃",每次全表扫描
SELECT COUNT(DISTINCT user_id) FROM active_log
WHERE active_time >= ? AND active_time < ?
-- 直播平台每秒调一次 → DB 爆炸
事故记录
正确 SQL
-- 正确:读预聚合表(凌晨任务算好的)
SELECT active_count FROM stat_account_summary
WHERE stat_date = ? AND game_id = ? AND promoter_id = ?
-- 配合 Redis 缓存(5 分钟 TTL)
适用边界
- 统计类数据(昨日/近 30 天/留存/LTV)→ 走预聚合表
- 实时数据(最近 30 分钟活跃)→ 才允许读明细表
- 高频查询(> 10 QPS)→ 必须走预聚合 + 缓存
禁区 6:相信上游会传正确的数据
错误写法(永远不要这么写)
// 错误:直接用入参,不校验
public Result query(List<String> userIds) {
return userDao.findByIds(userIds);
}
事故记录
正确写法
// 正确:所有入参必须校验
public Result query(List<String> userIds) {
// 1. 非空校验
if (userIds == null || userIds.isEmpty()) {
return Result.fail("INVALID_PARAMS", "参数不能为空");
}
// 2. 上限校验
if (userIds.size() > 2000) {
return Result.fail("BATCH_SIZE_EXCEEDED", "单次最多2000条");
}
// 3. 格式校验
for (String id : userIds) {
if (id == null || id.length() > 64) {
return Result.fail("INVALID_PARAMS", "参数格式错误");
}
}
// 4. 去重
List<String> dedupIds = userIds.stream()
.distinct()
.collect(Collectors.toList());
return userDao.findByIds(dedupIds);
}
禁区 7:群里通知一下就算"跨部门同步"
错误做法(永远不要这么做)
错误:在群里 @ 一下就算通知完事
"@产品 @发行 接口切了,你们看下"
事故记录
- 6/16:5/26 上线新接口,5/26 晚用户反馈数据缺失,临时切换为爬虫
只群里通知,没告知产品/发行,跨部门信息不同步
正确做法(4 步走)
步骤 1: 邮件
- 收件人:产品部 + 发行部 + 技术部 + 客服部
- 主题:【变更通知】[具体变更内容]
- 正文:变更原因 + 时间 + 内容 + 影响 + 回滚 + 负责人
步骤 2: 工单
- 在 PMO / 飞书项目里建变更单
- 关联到具体需求/事故
步骤 3: 文档更新
- 在 Confluence / 飞书更新变更记录
- 写明前后差异
步骤 4: 72h 跟踪
- 变更后 72h 内确认无问题
- 出问题立即回滚并通知
禁区 8:出事后才补监控
错误流程(永远不要这么做)
错误流程:
5/12 出事 -> 5/13 加 Redis 监控
5/26 出事 -> 5/27 加 MySQL 监控
事故记录
- 5/12 事故后才补 Redis 监控
- 5/26 事故后才补 MySQL 监控
- 6/16 事故前没有任何抓取任务监控
正确流程(上线即有)
所有新接口上线 = 必须有监控(QPS / 错误率 / 延迟)
所有新数据源接入 = 必须有告警(可用性 / 数据量 / 时延)
所有批量任务 = 必须有完成/失败告警
所有外部依赖 = 必须有可用性监控(DB / Redis / 第三方接口)
推荐监控清单
| 监控项 |
阈值 |
告警级别 |
| 接口 QPS |
> 1000 |
P2 |
| 接口错误率 |
> 1% |
P1 |
| 接口 P99 延迟 |
> 3s |
P1 |
| MySQL CPU |
> 70% |
P1 |
| MySQL 慢查询 |
> 100 条/min |
P1 |
| Redis 带宽 |
> 800Mbps |
P1 |
| 抓取任务执行时长 |
> 1h |
P2 |
| 抓取任务连续失败 |
> 3 次 |
P0 |
| 跨部门数据同步延迟 |
> 1h |
P1 |
附录:禁区速查表
| 禁区 |
一句话 |
事故 |
| 1 OR 拼 SQL |
用 IN / 临时表 JOIN 替代 |
5/26 |
| 2 无退避重试 |
30s -> 1min -> 2min -> 4min -> 5min |
5/26 |
| 3 缓存 > 会话 |
客户端 < 服务端,每次校验 |
6/16 |
| 4 DOUBLE 存金额 |
用 DECIMAL(15,2) 或 BIGINT |
长期隐患 |
| 5 实时查明细 |
走预聚合表,不查明细 |
v4.24 之前 |
| 6 相信上游 |
入参必校验:非空 + 上限 + 格式 |
5/12、5/26 |
| 7 群里通知 |
邮件 + 工单 + 文档 + 72h 跟踪 |
6/16 |
| 8 出事后补监控 |
上线即有监控和告警 |
5/12、5/26 |