概述
灰度方案是一种让开发在黑白之间平滑过度的一种发布方案,让部分用户使用稳定的旧方案,让另一部分用户使用新方案,如果对于新方案没有问题,那么就把灰度开关开到最大使用新逻辑。灰度方案可以整体系统的稳定性,并且可以在灰度期间发现问题、解决问题、调整问题,降低出现问题的损失,保证上线进度。
实现思路
大体实现逻辑
本方案灰度逻辑实现在代码层面
具体实现
废话不多说 直接贴代码
DB 设计
表主要涵盖灰度id、灰度名称、灰度开关、白名单、黑名单、灰度区间几个字段
- 灰度名称 声明该灰度是干嘛地
- 灰度开关 可以直接关闭灰度开关走旧逻辑,也可以直接关闭灰度走新逻辑
- 白名单 用‘,’ 分割的字符串,直接走新逻辑的一部分id
- 黑名单 用‘.’ 分割的字符串,直接走就逻辑的一部分id
- 灰度区间 用‘,’ 分割的字符串, 类似 ‘20,80’ 意味着id尾数落在该区间内就是走新逻辑
具体的表结构如下
-- auto-generated definition
create table gray_feature
(
id int auto_increment comment '主键id'
primary key,
gray_id int null comment '灰度id',
gray_name varchar(32) default '' not null comment '灰度名称',
gray_switch tinyint default 1 null comment '0 灰度关闭 直接返回false 走老逻辑
1 灰度开启 进入灰度判断
2 灰度关闭 直接返回true 走新逻辑',
white_list varchar(64) default '' not null comment '白名单',
black_list varchar(64) default '' not null comment '黑名单 优先级高于白名单',
gray_interval varchar(64) default '0,1' not null comment '灰度区间 左闭右开',
create_time datetime default CURRENT_TIMESTAMP null comment '创建时间',
constraint gray_feature_gray_id_uindex
unique (gray_id)
)
comment '灰度特性';
代码逻辑设计
因为是个简单的灰度逻辑,主要用于后台开发一些重构、迁移项目刚上线防止出问题的简易策略,因此无论越简单越好,响应越快越好。 灰度判断的核心就是从DB拿到灰度配置,然后去校验对应的id,为了响应足够快,这里把灰度配置缓存在内存中一段时间。。。直接贴代码吧。。。
/**
* 灰度相关
*
* @author zhi
* @date 2020-02-28 12:50
*/
@Service
public class GrayServiceImpl implements GrayService {
@Resource
ApplicationContext context;
/**
* 获取灰度信息
*
* @param grayId 灰度id
* @return 灰度信息
*/
private GrayFeatureDO getGrayFeature(Long grayId) {
ILoadingCacheService<Long, GrayFeatureDO> grayFeatureCache = context.getBean("GRAY_FEATURE_CACHE", ILoadingCacheService.class);
return grayFeatureCache.getValue(grayId);
}
@Override
public boolean checkInGray(GrayCheckRequestVO requestVO) {
if (null == requestVO || null == requestVO.getGrayId() || null == requestVO.getUid()) {
return false;
}
GrayFeatureDO grayFeature = getGrayFeature(requestVO.getGrayId());
if (grayFeature == null) {
return false;
}
GrayFeatureDetail detail = convertGrayFeatureDetail(grayFeature);
return checkGrayFeatureDetail(detail, requestVO.getUid());
}
private static final Function<String, List<String>> SPLIT_TO_LIST = str -> Splitter.on(",").omitEmptyStrings().splitToList(str);
/**
* 灰度开关关闭
*/
private static final int CLOSE_SWITCH = 0;
/**
* 灰度关闭 全量true
*/
private static final int TRUE_SWITCH = 2;
private GrayFeatureDetail convertGrayFeatureDetail(GrayFeatureDO grayFeature) {
GrayFeatureDetail grayFeatureDetail = new GrayFeatureDetail();
grayFeatureDetail.setGrayId(grayFeature.getGrayId());
grayFeatureDetail.setGrayName(grayFeature.getGrayName());
grayFeatureDetail.setGraySwitch(grayFeature.getGraySwitch());
List<Long> whiteList = SPLIT_TO_LIST.apply(grayFeature.getWhiteList()).stream().map(Long::new).collect(Collectors.toList());
grayFeatureDetail.setWhiteList(whiteList);
List<Long> blackList = SPLIT_TO_LIST.apply(grayFeature.getBlackList()).stream().map(Long::new).collect(Collectors.toList());
grayFeatureDetail.setBlackList(blackList);
String[] grayInterval = grayFeature.getGrayInterval().split(",");
grayFeatureDetail.setGrayIntervalFloor(Integer.valueOf(grayInterval[0]));
grayFeatureDetail.setGrayIntervalCell(Integer.valueOf(grayInterval[1]));
grayFeatureDetail.setCreateTime(grayFeature.getCreateTime());
return grayFeatureDetail;
}
private boolean checkGrayFeatureDetail(GrayFeatureDetail detail, Long uid) {
if (uid == null) {
return false;
}
// 灰度开关关闭
if (detail.getGraySwitch() == CLOSE_SWITCH) {
return false;
}
// 灰度全量
if (detail.getGraySwitch() == TRUE_SWITCH) {
return true;
}
// 黑名单
if (!CollectionUtils.isEmpty(detail.getBlackList()) && detail.getBlackList().contains(uid)) {
return false;
}
// 白名单
if (!CollectionUtils.isEmpty(detail.getWhiteList()) && detail.getWhiteList().contains(uid)) {
return true;
}
if (detail.getGrayIntervalCell() != null && detail.getGrayIntervalFloor() != null) {
long mod = uid % 100;
return detail.getGrayIntervalFloor() <= mod && mod < detail.getGrayIntervalCell();
}
return false;
}
}
缓存这块的逻辑
/**
* 缓存管理
*
* @author zhi
* @date 2020-02-28 14:17
*/
@Configuration
@Slf4j
public class CacheConfig {
@Bean(name = "grayFeatureCache")
public Cache<Long, GrayFeatureDO> grayFeatureCache() {
// 这里用的是 caffine 缓存他个10分钟 缓存久了 就么得意义了
Cache<Long, GrayFeatureDO> grayFeatureCache = Caffeine.newBuilder().expireAfterWrite(600, TimeUnit.SECONDS).build();
log.debug("载入 grayFeatureCache");
return grayFeatureCache;
}
}
/**
* 缓存的实现类
* ILoadingCacheService 就是个接口 我就不列出来了
*
* @author zhi
* @date 2020-02-28 14:24
*/
@Service("GRAY_FEATURE_CACHE")
@Slf4j
public class GrayFeatureCacheImpl implements ILoadingCacheService<Long, GrayFeatureDO> {
@Resource
@Qualifier("grayFeatureCache")
Cache<Long, GrayFeatureDO> grayFeatureCache;
@Resource
GrayDAO grayDAO;
@Override
public GrayFeatureDO getValue(Long grayId) {
return grayFeatureCache.get(grayId, key -> grayDAO.getGrayFeatureByGrayId(key));
}
@Override
public void clear() {
}
@Override
public void saveValue(Long integer, GrayFeatureDO grayFeatureDO) {
}
@Override
public long size() {
return 0;
}
}
就此就完成了所有灰度的逻辑 简单易懂
https://github.com/YinglishZhi/something/tree/master