Design and Implementation of a Lightweight Gray Release Platform
The article presents a lightweight gray‑release platform for e‑commerce, detailing its four‑module architecture, unified data‑type and predicate rule model, matcher implementations, stable bucket‑based traffic allocation, developer SDK and configuration APIs, as well as whitelist handling and non‑functional considerations for rapid, fine‑grained deployments.
This article introduces a lightweight gray‑release platform developed for a fast‑growing e‑commerce business. It explains why traditional gray‑release services are overly customized, lack flexibility, and cannot meet evolving business needs.
Overall Architecture
The platform consists of four main modules: a gray‑operation console, a gray‑service backend, a configuration store (Nacos/Ark), and a gray‑SDK for developers. The console provides CRUD operations and visual management of gray rules.
Gray Data Types
Four data types are defined to describe gray‑release conditions: VERSION, STRING, SEGMENT, NUMBER, plus a NONE type for illegal rules.
/**
* 版本号
*/
VERSION("version"),
/**
* 字符串类型
*/
STRING("string"),
/**
* 集合类型
*/
SEGMENT("segment"),
/**
* 数字类型
*/
NUMBER("number"),
/**
* 非法规则
*/
NONE("none");Gray Rule Design
Based on the data type, different predicates are supported (e.g., IN, NOT_IN, REGEX, EQ, NEQ, GREATER_THAN, etc.). The rule model is a hierarchy of Toggle → Rule → Condition, where each rule is an OR of conditions and each condition is an AND of predicates.
@Data
@NoArgsConstructor
public class Toggle implements Serializable {
private Integer fullGray = 0; // 1 = full‑gray, skip version check
private Integer enabled = 1; // 1 = active, 0 = inactive
private List
rules;
@Data
public static class Rule implements Serializable {
private List
conditions;
}
@Data
public static class Condition implements Serializable {
private String type; // ConditionType
private String subject; // GrayFields
private String predicate; // PredicateType
private List
objects;
}
}Matcher Interface
Each data type implements a Matcher that provides the ConditionType and a map of PredicateType to concrete PredicateMatcher implementations.
public interface Matcher {
ConditionType getConditionType();
Map
getPredicateMatcher();
}Sample Matcher Implementations
NumberMatch, StringMatch and VersionMatch demonstrate how predicates are evaluated.
@Component
public class NumberMatch implements Matcher {
@Override
public ConditionType getConditionType() { return ConditionType.NUMBER; }
@Override
public Map
getPredicateMatcher() {
EnumMap
map = new EnumMap<>(PredicateType.class);
map.put(PredicateType.EQUAL_TO, (target, objects) ->
objects.stream()
.filter(Objects::nonNull)
.map(Object::toString)
.filter(StringUtils::isNotBlank)
.map(Long::valueOf)
.anyMatch(v -> Objects.equals(Long.valueOf(String.valueOf(target)), v)));
return map;
}
} @Component
public class StringMatch implements Matcher {
@Override
public ConditionType getConditionType() { return ConditionType.STRING; }
@Override
public Map
getPredicateMatcher() {
EnumMap
map = new EnumMap<>(PredicateType.class);
map.put(PredicateType.EQ, (target, objects) ->
objects.stream()
.filter(Objects::nonNull)
.map(Object::toString)
.filter(StringUtils::isNotBlank)
.anyMatch(v -> String.valueOf(target).equalsIgnoreCase(v))));
return map;
}
} @Component
public class VersionMatch implements Matcher {
@Override
public ConditionType getConditionType() { return ConditionType.VERSION; }
@Override
public Map
getPredicateMatcher() {
EnumMap
map = new EnumMap<>(PredicateType.class);
map.put(PredicateType.EQUAL_TO, (target, objects) ->
objects.stream()
.filter(Objects::nonNull)
.map(Object::toString)
.filter(StringUtils::isNotBlank)
.anyMatch(v -> VersionUtils.compareMajorVersion(String.valueOf(target), v) == 0));
return map;
}
}Version Comparison Utility
/**
* Compare two version strings up to a given length.
*/
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 = i < majorV1.length ? Integer.parseInt(majorV1[i]) : 0;
int y = i < majorV2.length ? Integer.parseInt(majorV2[i]) : 0;
if (x > y) return 1;
if (x < y) return -1;
}
} catch (NumberFormatException e) { /* ignore */ }
return 0;
}Stable Percentage Traffic Control
A 10,000‑bucket consistent‑hash algorithm (Murmur3) guarantees stable traffic allocation per session.
private static final HashFunction murmur3 = Hashing.murmur3_32();
private static final int bucket = 100_00;
public boolean hitBucket(Map
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 limit = (rate != null && rate >= 0 && rate <= bucket) ? rate : bucket;
return hash <= limit;
}Gray Service SDK Interface
public interface GrayService {
boolean hitGray(String sceneKey, Map
attrs);
boolean hitGray(Toggle toggle, Map
attrs);
HitResult hitExperimentGroup(String sceneKey, Map
attrs);
HitResult hitExperimentGroup(String sceneKey, String experimentGroupKey, Map
attrs);
}Configuration Service API
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 getConfigObject(String sceneKey, Class
targetClass);
}Whitelist and Non‑Functional Design
Whitelist rules are modeled as a highest‑priority list that can bypass all other conditions. Additional non‑functional concerns such as data‑pipeline integration, permission control, release‑pipeline governance, lifecycle management, and AB‑testing capabilities are mentioned.
Conclusion
The platform provides a high‑performance, extensible SDK and a visual configuration console, enabling rapid gray‑release deployments with fine‑grained traffic control. It has become a core stability guarantee for the organization and is already patented and integrated into production operations.
DeWu Technology
A platform for sharing and discussing tech knowledge, guiding you toward the cloud of technology.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.