背景
在日常开发中,当修改了一些核心代码,或遇到测试环境难以完全覆盖的场景时,我们希望在上线时能通过灰度逐步放量来验证新逻辑。这样一来,如果发现问题,也可以快速切换回旧逻辑。
下面我就介绍下,一个简单的基于用户维度的灰度的方式。
整体流程
简单的流程如下:

处理逻辑:
接收到用户请求后,系统会根据用户标识和灰度配置规则进行处理,判断该用户是否被灰度。如果用户被灰度,则执行新的代码逻辑;如果未被灰度,则执行旧的代码逻辑。
灰度配置规则的存储
- 配置中心,使用的时候直接读取配置中心的配置,修改灰度的配置的时候,配置中心也可以快速的推到应用中,快速生效配置。
- 数据库+缓存,使用的时候从数据库中将配置加载到缓存中,读取缓存的灰度配置的数据,使用数据库+缓存的方式,修改灰度配置的时候要复杂一点,先去修改数据库里面的配置,再去删掉缓存中的数据。
灰度配置规则的处理
灰度配置处理逻辑
java
public boolean isGray(Long userId) {
if (userId == null) {
return false;
}
try {
// 灰度配置规则可以从 apollo 或者从缓存中取,根据实际情况 获取gray_config
String rule = config.getProperty("gray_config", "");
if (StringUtils.isEmpty(rule)) {
return false;
}
JSONArray objects = JSON.parseArray(rule);
if (CollectionUtils.isEmpty(objects)) {
return false;
}
for (Object object : objects) {
JSONObject jsonObject = (JSONObject) object;
String type = jsonObject.getString("type");
if (StringUtils.isEmpty(type)) {
continue;
}
if ("direct".equals(type)) {
String userIds = jsonObject.getString("data");
Set<String> userIdSet = StringUtils.commaDelimitedListToSet(userIds);
if (userIdSet.contains(String.valueof(userId))) {
return true;
}
}
if ("mod".equals(type)) {
Integer scale = jsonObject.getInteger("data");
if (scale < 0) {
continue;
}
if (scale >= 100) {
return true;
}
Integer remainder = (int) (userId % 100);
if (remainder <= scale) {
return true;
}
}
}
}
catch (Exception ex) {
logger.error("处理灰度失败", ex);
}
return false;
}
用户配置规则gray_config可以如下配置
java
[
{
"type": "mod",
"data": "10"
},
{
"type": "direct",
"data": "10001,10002"
}
]
上述灰度配置表示的含义如下:
第一个配置:使用用户 ID 与 100(可自定义)取模。当取模后的值大于等于 0 且小于 10 时,表示该用户被灰度。如需扩大灰度范围,可以将 data 值调大,直到大于等于 100 时实现全量放开。当 data 值小于 0(如 -1)时,则为全量关闭。
第二个配置:将用户 ID 与指定的用户 ID 列表进行对比。如果用户 ID 存在于该列表中,则表示该用户被灰度;反之则未被灰度。
mod 和 direct 均可配置多条规则,只要满足其中任一规则,即视为通过灰度校验。
总结
以上介绍了一个简单的用户维度的灰度方案,可以灵活指定用户灰度或者按照比例对用户进行灰度。
这个用户比例计算也是一个大致的范围,可能有数据倾斜,不过总体还是能反应用户的分布的。
最后当灰度完成后,需要将旧代码删除。