Posted in

轻量级的灰度&配置平台|得物技术_AI阅读总结 — 包阅AI

包阅导读总结

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. 灰度服务的接口抽象

五、稳定的百分比流量调控

六、灰度分组设计

七、白名单设计

八、其他非功能设计

九、展望和总结

随着近几年得物的业务和技术的快速发展,我们不管是在面向C端场景还是B端供应链;业务版本的迭代更新,技术架构的不断升级;不管是业务稳定性还是架构稳定性,业务灰度的能力对我们来说都是一项重要的技术保障,越来越受到我们业务研发的关注。然而,传统的灰度发布服务往往过于定制化,缺乏灵活性和通用性,无法满足不断变化的业务需求,往往灰度的场景可能通过代码硬编码或者简单的配置中心配置。在这样的背景下,本文将介绍一种全新的、轻量级的灰度平台,它将为大家的业务带来全新的灰度体验。
  • 灰度运营平台:为用户提供增删查改的灰度发布管理和UI界面;
  • 灰度服务端:为灰度运营平台提供标准的增删查改功能、权限控制、灰度场景管理和应用接入命名空间;
  • Nacos&Ark: 提供高性能的灰度配置读取和存储服务;
  • 灰度SDK: 为研发提供轻量级高性能的灰度判断API和配置服务。
我们参考上面的总体架构设计可知,灰度配置的存储不是关键,复用已有的Nacos等或者自研的配置服务中心即可,重点是在SDK和灰度数据结构的设计上。
首先我们针对不同的场景可能会遇到不同的数据类型灰度,不同的数据类型在规则处理中可能也有特点场景的实现,所以先定义灰度数据类型参考如下:
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@NoArgsConstructorpublic 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();}
@Componentpublic 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; }}
@Componentpublic 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; }}
@Componentpublic 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; }
}
@Componentpublic 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; }}
目前客户端的版本号存在特殊的灰度版本号,且安卓和iOS由于历史原因存在2套不兼容的情况,我们也做了定制的算法实现。
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;}
根据上面的设计,我们也就很容易抽象出一个通用标准的灰度服务接口,打包成SDK供业务研发使用。
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);}
我们很多业务类型场景自定义的配置,早期的设计都是存放在Nacos或者Ark配置平台上。这种配置存在只能面向研发等局限性,尤其在配置复杂时,修改过程需要特别谨慎。为了解决这个问题,我们设计了面向研发和业务的可视化动态表单的配置方式,集成现成的页面表单搭建技术平台或动态表单技术平台,使得配置可以以可视化的形式展示。
这是我们内部页面表单搭建技术,通过简单配置或者拖拽的方式进行自定义表单。
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);}
我们在场景使用过程中需要实现精确到万分之一的比例调控,session会话稳定,同样的上下文多次请求,命中的结果不变。设计开1万个分桶,分桶算法的实现使用了murmur3_32。
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; }}
还有一些非功能性的设计更多是和现有系统能力集成,对接成本和每个公司的技术栈有差异,在此就不展开描述,有兴趣的同学可以自行设计。
  • 灰度数据大数据埋点服务集成,提供可视化的数据分析能力
本文介绍了我们团队所开发的轻量级灰度及配置平台,以及它所带来的创新和优势。通过对业务灰度发布的需求和挑战进行深入剖析,我们意识到传统的灰度服务存在着一系列问题,诸如定制化程度高、缺乏灵活性、无法应对自定义场景等。为解决这些问题,我们研发出了一个高性能轻量级的SDK集成工具,它具有强大的功能,利用表单化的方式实现了灰度服务的产品化能力,使得灰度规则的配置和管理变得简单而直观,大大降低了研发和业务使用的门槛。这个集成工具已经成为我们研发过程中稳定性的三大保障之一,不仅得到了团队的认可,也申请通过了相关专利,目前已被集成到了日常的运营平台中。它的存在使得研发人员和业务使用者能够更轻松地享受到灰度服务的益处。
通过本文,我们希望这个轻量级的灰度配置平台的设计思路对你有所帮助。整体的设计思路非常轻量级,还有很多用户体验和非功能的设计没有在本文中展开描述,这些也并不是这个平台的核心设计内容。如果您有兴趣,可以自行补充;如需进一步的帮助或有其他需求,欢迎随时联系我们。

文 / feel

关注得物技术,每周一、三、五更新技术干货

要是觉得文章对你有帮助的话,欢迎评论转发点赞~

未经得物技术许可严禁转载,否则依法追究法律责任。

如有任何疑问,或想要了解更多技术资讯,请添加小助手微信: