外卖霸王餐灰度开关:基于Spring Cloud Config+Bus动态刷新踩坑
业务场景与技术选型
"吃喝不愁"App需对新上线的"霸王餐"功能进行城市级灰度发布,例如仅对北京、上海用户开放。系统采用 Spring Cloud Config 作为配置中心,结合 Spring Cloud Bus + RabbitMQ 实现配置变更广播,目标是:修改 Git 中的 feature-toggle.yml 后,所有服务实例自动刷新灰度开关状态,无需重启。
Config Server 配置
bootstrap.yml(Config Server):
yaml
server:
port: 8888
spring:
application:
name: config-server
cloud:
config:
server:
git:
uri: https://github.com/juwatech/eatfree-config.git
default-label: main
search-paths: '{application}'
确保仓库中存在 eatfree-service/feature-toggle.yml:
yaml
feature:
free-meal:
enabled-cities: ["beijing", "shanghai"]
global-switch: true

客户端依赖与配置
服务模块(如 order-service)引入:
xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
bootstrap.yml(客户端):
yaml
spring:
application:
name: eatfree-service
cloud:
config:
uri: http://localhost:8888
profile: default
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
management:
endpoints:
web:
exposure:
include: busrefresh
灰度开关配置类
java
package juwatech.cn.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
@RefreshScope
@ConfigurationProperties(prefix = "feature.free-meal")
public class FreeMealToggle {
private boolean globalSwitch = false;
private List<String> enabledCities;
public boolean isGlobalSwitch() {
return globalSwitch;
}
public void setGlobalSwitch(boolean globalSwitch) {
this.globalSwitch = globalSwitch;
}
public List<String> getEnabledCities() {
return enabledCities;
}
public void setEnabledCities(List<String> enabledCities) {
this.enabledCities = enabledCities;
}
public boolean isEnabledForCity(String cityCode) {
return globalSwitch && enabledCities != null && enabledCities.contains(cityCode);
}
}
关键注解:@RefreshScope ------ 使 Bean 在 /actuator/busrefresh 触发时重建。
使用灰度开关的业务代码
java
package juwatech.cn.service;
import juwatech.cn.config.FreeMealToggle;
import org.springframework.stereotype.Service;
@Service
public class OrderEligibilityService {
private final FreeMealToggle freeMealToggle;
public OrderEligibilityService(FreeMealToggle freeMealToggle) {
this.freeMealToggle = freeMealToggle;
}
public boolean canClaimFreeMeal(String userId, String cityCode) {
// 其他逻辑略
return freeMealToggle.isEnabledForCity(cityCode);
}
}
踩坑1:@RefreshScope 不生效
现象:调用 POST /actuator/busrefresh 后,FreeMealToggle 的字段值未更新。
原因 :@ConfigurationProperties 类若同时被 @Component 和 @RefreshScope 注解,需确保其被 Spring 正确代理。更稳妥的做法是分离配置类与使用类:
java
// 配置类(无 @Component)
@ConfigurationProperties(prefix = "feature.free-meal")
public class FreeMealProperties {
// fields + getters/setters
}
// 使用类
@Component
@RefreshScope
public class FreeMealToggle {
private final FreeMealProperties props;
public FreeMealToggle(FreeMealProperties props) {
this.props = props;
}
public boolean isEnabledForCity(String cityCode) {
return props.getGlobalSwitch() && props.getEnabledCities().contains(cityCode);
}
}
// 主启动类启用 @EnableConfigurationProperties
@SpringBootApplication
@EnableConfigurationProperties(FreeMealProperties.class)
public class EatfreeApplication {
public static void main(String[] args) {
SpringApplication.run(EatfreeApplication.class, args);
}
}
踩坑2:Bus 广播未触发
现象:Config Server 收到 Webhook,但客户端未收到刷新事件。
排查步骤:
- 确认 RabbitMQ 中存在
springCloudBusexchange; - 客户端日志是否包含
Received remote refresh request; - 检查 Config Server 是否也引入了
spring-cloud-starter-bus-amqp------ 必须引入,否则无法转发事件。
Config Server 也需添加:
xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
并配置相同 RabbitMQ 连接。
踩坑3:List 类型反序列化失败
Git 中配置为:
yaml
enabled-cities: beijing,shanghai
导致 List<String> 解析为单个字符串 "beijing,shanghai"。
正确写法(YAML 数组):
yaml
enabled-cities:
- beijing
- shanghai
或使用方括号:
yaml
enabled-cities: ["beijing", "shanghai"]
验证流程
-
修改 Git 仓库,添加
"guangzhou"到enabled-cities; -
向 Config Server 发送 POST 请求(模拟 Webhook):
bashcurl -X POST http://config-server:8888/monitor -H "Content-Type: application/json" -d '{"destination":"eatfree-service:**"}' -
观察客户端日志:
Refresh scope refresh requested; -
调用业务接口,验证广州用户 now 可参与活动。
本文著作权归吃喝不愁app开发者团队,转载请注明出处!