包阅导读总结
1. 关键词:得物技术、灰度平台、配置服务、SDK 接口、流量调控
2. 总结:本文介绍了得物的轻量级灰度&配置平台,包括总体架构设计、灰度服务与配置服务关键设计、流量调控、分组和白名单设计等,指出其解决了传统灰度服务的问题,带来创新和优势。
3. 主要内容:
– 前言
– 得物业务和技术发展,传统灰度发布服务存在问题
– 总体架构设计
– 包含灰度运营平台、服务端、Nacos&Ark、灰度SDK
– 灰度服务关键设计
– 灰度数据类型设计与规则
– 简单灰度触发器模型
– 灰度触发器模型和规则实现
– 客户端版本号比较算法
– 灰度服务SDK接口设计
– 配置服务关键设计
– 配置可视化设计集成
– 灰度服务的接口抽象
– 稳定的百分比流量调控
– 分桶算法与比例调控
– 灰度分组设计
– 多灰度分组条件命中与指定分组规则
– 白名单设计
– 其他非功能设计
– 展望和总结
– 介绍平台创新和优势,成为稳定性保障之一
思维导图:
文章地址:https://mp.weixin.qq.com/s/BJ6C9vlBF-l3OveIF6FWLA
文章来源:mp.weixin.qq.com
作者:feel
发布时间:2024/8/9 7:08
语言:中文
总字数:5779字
预计阅读时间:24分钟
评分:87分
标签:灰度发布,配置管理,技术架构,得物技术,业务稳定性
以下为原文内容
本内容来源于用户推荐转载,旨在分享知识与观点,如有侵权请联系删除 联系邮箱 media@ilingban.com
一、前言
二、总体架构设计
三、灰度服务关键设计
1. 灰度数据类型设计
2. 灰度规则设计
3. 简单灰度触发器模型
4. 灰度触发器模型和规则实现
5. 客户端版本号比较算法
6. 灰度服务SDK接口设计
四、配置服务关键设计
1. 配置可视化设计集成
2. 灰度服务的接口抽象
五、稳定的百分比流量调控
六、灰度分组设计
七、白名单设计
八、其他非功能设计
九、展望和总结
-
灰度运营平台:为用户提供增删查改的灰度发布管理和UI界面; -
灰度服务端:为灰度运营平台提供标准的增删查改功能、权限控制、灰度场景管理和应用接入命名空间; -
Nacos&Ark: 提供高性能的灰度配置读取和存储服务; -
灰度SDK: 为研发提供轻量级高性能的灰度判断API和配置服务。
VERSION("version"),
STRING("string"),
SEGMENT("segment"),
NUMBER("number"),
NONE("none"),
;
IS_IN("in"),
IS_NOT_IN("notIn"),
REGEX("regex"),
NREGEX("nregex"),
EQ("eq"),
NEQ("neq"),
EQUAL_TO("="),
NOT_EQUAL_TO("!="),
GREATER_THAN(">"),
GREATER_OR_EQUAL(">="),
LESS_THAN("<"),
LESS_OR_EQUAL("<="),
NONE("none"),
@Data
@NoArgsConstructor
public class Toggle implements Serializable {
private Integer fullGray = 0;
private Integer enabled = 1;
private List<Rule> rules;
@Data
public static class Rule implements Serializable {
private List<Condition> conditions;
}
@Data
public static class Condition implements Serializable {
private String type;
private String subject;
private String predicate;
private List<Object> objects;
}
}
public interface Matcher {
ConditionType getConditionType();
Map<PredicateType, PredicateMatcher> getPredicateMatcher();
}
@Component
public class NumberMatch implements Matcher {
@Override
public ConditionType getConditionType() {
return ConditionType.NUMBER;
}
@Override
public Map<PredicateType, PredicateMatcher> getPredicateMatcher() {
EnumMap<PredicateType, PredicateMatcher> matcherEnumMap = new EnumMap<>(PredicateType.class);
matcherEnumMap.put(PredicateType.EQUAL_TO, ((target, objects) -> objects.stream()
.filter(Objects::nonNull)
.map(Objects::toString)
.filter(StringUtils::isNotBlank)
.map(Long::valueOf)
.anyMatch(t -> Objects.equals(Long.valueOf(String.valueOf(target)), t))));
return matcherEnumMap;
}
}
@Component
public class StringMatch implements Matcher {
@Override
public ConditionType getConditionType() {
return ConditionType.STRING;
}
@Override
public Map<PredicateType, PredicateMatcher> getPredicateMatcher() {
EnumMap<PredicateType, PredicateMatcher> matcherEnumMap = new EnumMap<>(PredicateType.class);
matcherEnumMap.put(PredicateType.EQ, ((target, objects) -> objects.stream()
.filter(Objects::nonNull)
.map(Objects::toString)
.filter(StringUtils::isNotBlank)
.anyMatch(t -> String.valueOf(target).equalsIgnoreCase(t))));
return matcherEnumMap;
}
}
@Component
public class VersionMatch implements Matcher {
@Override
public ConditionType getConditionType() {
return ConditionType.VERSION;
}
@Override
public Map<PredicateType, PredicateMatcher> getPredicateMatcher() {
EnumMap<PredicateType, PredicateMatcher> matcherEnumMap = new EnumMap<>(PredicateType.class);
matcherEnumMap.put(PredicateType.EQUAL_TO, ((target, objects) -> objects.stream()
.filter(Objects::nonNull)
.map(Objects::toString)
.filter(StringUtils::isNotBlank)
.anyMatch(t -> VersionUtils.compareMajorVersion(String.valueOf(target), t) == 0)));
return matcherEnumMap;
}
}
@Component
public class SegmentMatch implements Matcher {
@Override
public ConditionType getConditionType() {
return ConditionType.SEGMENT;
}
@Override
public Map<PredicateType, PredicateMatcher> getPredicateMatcher() {
EnumMap<PredicateType, PredicateMatcher> matcherEnumMap = new EnumMap<>(PredicateType.class);
matcherEnumMap.put(PredicateType.IS_IN, ((target, objects) -> objects.stream()
.filter(Objects::nonNull)
.map(Objects::toString)
.filter(StringUtils::isNotBlank)
.anyMatch(t -> {
if (target instanceof List || target instanceof Set) {
return ((Collection<Object>) target).stream()
.filter(Objects::nonNull)
.map(Objects::toString)
.anyMatch(v -> String.valueOf(v).equalsIgnoreCase(t));
}
return String.valueOf(target).equalsIgnoreCase(t);
})));
return matcherEnumMap;
}
}
public static int compareVersion(String v1, String v2, Integer limit) {
if (v1 == null || v2 == null) {
return 0;
}
try {
String[] majorV1 = parserPinkAppVersion(v1).split("\\.");
String[] majorV2 = parserPinkAppVersion(v2).split("\\.");
int len = Math.min(Optional.ofNullable(limit)
.orElse(Integer.MAX_VALUE), Math.max(majorV1.length, majorV2.length));
for (int i = 0; i < len; ++i) {
int x = 0, y = 0;
if (i < majorV1.length) {
x = Integer.parseInt(majorV1[i]);
}
if (i < majorV2.length) {
y = Integer.parseInt(majorV2[i]);
}
if (x > y) {
return 1;
}
if (x < y) {
return -1;
}
}
} catch (NumberFormatException e) {
}
return 0;
}
public interface GrayService {
boolean hitGray(String sceneKey, Map<String, Object> attrs);
boolean hitGray(Toggle toggle, Map<String, Object> attrs);
HitResult hitExperimentGroup(String sceneKey, Map<String, Object> attrs);
HitResult hitExperimentGroup(String sceneKey, String experimentGroupKey, Map<String, Object> attrs);
}
public interface GrayConfigService {
String getConfigValue(String sceneKey);
String getStringValue(String sceneKey, String pathKey, String defaultValue);
boolean getBooleanValue(String sceneKey, String pathKey, boolean defaultValue);
Integer getIntegerValue(String sceneKey, String pathKey, Integer defaultValue);
Long getLongValue(String sceneKey, String pathKey, Long defaultValue);
Double getDoubleValue(String sceneKey, String pathKey, Double defaultValue);
BigDecimal getBigDecimalValue(String sceneKey, String pathKey, int scale);
JSONArray getJSONArrayValue(String sceneKey, String pathKey);
JSONObject getConfigJSONObject(String sceneKey);
<T> T getConfigObject(String sceneKey, Class<T> targetClass);
}
private static final HashFunction murmur3 = Hashing.murmur3_32();
private static final int bucket = 100_00;
public boolean hitBucket(Map<String, Object> attrsContext, String bucketKey, Integer rate) {
if (Objects.equals(Optional.ofNullable(rate).orElse(bucket), bucket)) {
return true;
}
if (Objects.equals(Optional.ofNullable(rate).orElse(bucket), 0)) {
return false;
}
String bucketValue = Optional.ofNullable(attrsContext)
.map(v -> v.get(bucketKey))
.filter(Objects::nonNull)
.map(String::valueOf)
.filter(StringUtils::isNotBlank)
.orElse(String.valueOf(System.currentTimeMillis()));
int hash = Hashing.consistentHash(murmur3.hashString(bucketValue, Charsets.UTF_8), bucket);
int rateLimit = bucket;
if (rate >= 0 && rate <= bucket) {
rateLimit = rate;
}
return hash <= rateLimit;
}
public class Toggle implements Serializable {
private List<Rule> rules;
@Data
public static class Rule implements Serializable {
..............
}
@Data
public static class Condition implements Serializable {
.......
}
}
public class Toggle implements Serializable {
private List<WhiteList> whiteLists;
@Data
public static class WhiteList implements Serializable {
private String subject;
private List<Object> values;
}
}
-
灰度数据大数据埋点服务集成,提供可视化的数据分析能力
文 / feel
关注得物技术,每周一、三、五更新技术干货
要是觉得文章对你有帮助的话,欢迎评论转发点赞~
未经得物技术许可严禁转载,否则依法追究法律责任。
如有任何疑问,或想要了解更多技术资讯,请添加小助手微信: