线上开关真正解决的,不是配置问题,而是变更风险问题

很多能力到了线上以后,真正的上线方式已经不是“发版打开”,而是:

1、代码先上线

2、默认关闭或小范围开启

3、通过配置逐步放量

4、出问题时快速回滚

所以开关如果只被写成一个简单 if,通常还是不够。

我更习惯先把开关按用途分层

如果所有开关都混成一堆布尔值,后面非常难维护。

我更常见的分法是:

1、保护性开关:出问题时立即止血,比如关闭某个消费者入口

2、策略开关:控制某段业务逻辑是否生效,比如签名校验、代理缓存、规则代理

3、参数配置:灰度比例、超时、缓存时间、批量大小

4、白名单配置:只对部分活动、渠道、用户放量

只有先把用途分开,后面灰度和回滚才好做。

保护性开关最重要的是默认值和关闭态

一个保护性开关如果没有默认值,环境切换时就容易出问题。

我更喜欢这种写法:

1
2
3
4
if (omsOrderConfig.isConsumerClose()) {
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
return;
}

这个开关的价值很直接:

1、入口是否继续消费可以被立即控制

2、异常时先止血

3、恢复后再逐步打开

关键不是这段代码多复杂,而是它对应的是不是一条可回滚链路。

策略开关更适合包住新逻辑,而不是包住整条主流程

像签名校验、代理缓存、规则代理这种能力,我更倾向于只把“新增逻辑”包在开关里,而不是把整条主链路都包住。

1
2
3
4
5
6
private void checkSendPrizeSign(QASendPrizeRequest req) {
if (!constantConfig.getSendPrizeSignSwitch()) {
return;
}
// 新增校验逻辑
}

这样做的好处是:

1、旧逻辑一直还活着

2、问题出现时可以快速退回旧路径

3、变更影响面更容易被控制

灰度放量如果不做范围控制,就只是“慢一点全量”

真正有用的灰度,至少要能回答:

1、先对谁开

2、放到多少比例

3、观察哪些指标

4、什么条件下回滚

比较常见的灰度范围有:

1、按活动

2、按渠道

3、按用户白名单

4、按固定 hash 比例

1
2
3
4
public boolean hitGray(String userId, int percent) {
int hash = Math.abs(userId.hashCode()) % 100;
return hash < percent;
}

没有灰度范围控制时,“先开 10%”这句话本身就落不了地。

配置读取稳定性,和配置内容本身一样重要

很多配置不是本地常量,而是从配置中心或配置服务动态拿。

这时我会一起关注:

1、默认值有没有

2、取配置失败怎么办

3、有没有本地缓存

4、值解析失败怎么兜底

1
2
3
4
5
Config configObject = configService.getConfigObject(key);
int expireTime = 30;
if (configObject != null && StringUtils.isNotBlank(configObject.getCodeValue())) {
expireTime = Integer.parseInt(configObject.getCodeValue());
}

配置读取如果没有兜底,线上问题经常会从“业务开关异常”升级成“配置服务异常影响主流程”。

快速回滚最重要的是旧链路没有死

很多人理解回滚,只想到“把开关关掉”。

但真正线上有效的回滚要满足一个前提:旧链路还在、还能跑、数据还能接得住。

如果新逻辑已经:

1、改写关键状态

2、依赖新字段

3、切换了外部依赖

那开关就算关掉,也不一定真能回退。

所以我现在会更早问这个问题:如果 5 分钟后线上有问题,这条能力能不能不发版直接退回旧逻辑?

哪些地方最值得先埋开关

从经验看,最值得先埋开关的地方一般是:

1、新下游调用

2、新消费者入口

3、新签名或拦截逻辑

4、代理缓存、路由代理、规则代理

5、大流量批处理任务

这些点一旦出问题,优先级往往不是定位根因,而是先把影响面控住。

我更认可的上线顺序

如果一个新能力准备进线上,我现在更倾向按这个顺序:

1、先补默认关闭态和配置兜底

2、再补灰度范围控制

3、再补监控和告警

4、最后逐步放量到全量

这个顺序的关键是,任何一步出问题都能快速退回,而不是只能再发一版救火。

小结

配置开关真正的价值,不是多写一个布尔判断,而是把线上变更拆成“可控风险”。

保护性开关负责止血,策略开关负责包住新增逻辑,灰度配置负责放量路径,默认值和旧链路负责回滚兜底。把这几层想清楚,开关才真正有用。