4、流量路由基础:基于权重的路由、基于标签的路由、基于用户ID/策略ID的哈希路由
在量化策略灰度发布体系中,流量路由是连接“策略版本”与“真实用户/订单”的核心桥梁。灰度发布并非简单地将流量切分,而是需要根据不同的业务场景,将流量精确地导向不同的策略版本。本章将深入剖析三种最基础、最常用的路由模式:基于权重、基于标签和基于哈希的路由。
4.1 路由模式总览
三种路由模式分别解决不同维度的流量调度问题。下表从核心原理、适用场景和典型配置三个维度进行对比:
| 路由模式 | 核心原理 | 适用场景 | 典型配置 |
|---|---|---|---|
| 基于权重 | 按比例随机分配流量 | A/B测试、新策略小流量验证、性能压测 | v1: 90%, v2: 10% |
| 基于标签 | 根据请求携带的元数据(标签)匹配 | 内部员工灰度、VIP用户优先体验、地域灰度 | env=internal → v2 |
| 基于哈希 | 对用户ID/策略ID进行一致性哈希 | 会话保持、用户粒度灰度、策略版本绑定 | hash(user_id) % 100 |
架构选型建议:在实际生产环境中,这三种模式通常不是互斥的,而是组合使用。例如:先通过标签路由将内部员工流量导入新版本,再对普通用户使用权重路由进行小流量验证,最后通过哈希路由实现用户粒度的稳定灰度。
4.2 基于权重的路由
基于权重的路由是最直观的流量分配方式。灰度发布平台为每个策略版本分配一个权重值(通常为百分比),路由引擎根据权重比例随机地将请求转发到对应版本。
4.2.1 权重路由模型
下图展示了权重路由的核心逻辑:
4.2.2 代码实现示例
以下是一个简化的权重路由实现,使用 Go 语言编写:
// WeightedRouter 基于权重的路由选择器
type WeightedRouter struct {
versions []VersionWeight
totalWeight int
}
type VersionWeight struct {
Version string
Weight int // 权重值,如 70
}
func (r *WeightedRouter) SelectVersion() string {
randNum := rand.Intn(r.totalWeight) // 生成 [0, totalWeight) 随机数
cumulative := 0
for _, vw := range r.versions {
cumulative += vw.Weight
if randNum < cumulative {
return vw.Version
}
}
return r.versions[len(r.versions)-1].Version
}
// 初始化:v1=70%, v2=30%
router := &WeightedRouter{
versions: []VersionWeight{
{"v1", 70},
{"v2", 30},
},
totalWeight: 100,
}
注意:权重路由的随机性可能导致流量分布不均匀,尤其是在短时间窗口内。对于需要严格按比例分配的场景,建议使用令牌桶或滑动窗口算法进行平滑处理。
4.3 基于标签的路由
基于标签的路由允许灰度发布平台根据请求中携带的元数据(如用户等级、设备类型、地域、渠道等)进行精细化路由。这种模式常用于内部灰度验证和定向放量。
4.3.1 标签路由流程
下图展示了标签路由的完整执行流程:
4.3.2 标签路由配置表
在实际系统中,标签路由规则通常以配置表的形式存储,支持动态更新:
| 优先级 | 标签键 | 标签值 | 目标版本 | 说明 |
|---|---|---|---|---|
| 1 | role | admin | v2 (灰度版) | 内部管理员优先体验 |
| 2 | user_level | vip | v2 (灰度版) | VIP用户定向灰度 |
| 3 | region | cn-east | v1 (稳定版) | 特定地域保持稳定 |
| 4 | * | * | v1 (稳定版) | 默认路由到稳定版 |
最佳实践:标签路由规则应支持优先级和默认兜底。当请求携带多个标签时,按优先级最高的规则执行;若所有规则均不匹配,则路由到默认版本,确保系统不会出现“无版本可用”的情况。
4.4 基于用户ID/策略ID的哈希路由
哈希路由通过计算用户ID或策略ID的哈希值,将流量稳定地映射到特定版本。其核心优势在于会话保持:同一个用户或同一个策略ID的请求始终路由到同一个版本,避免因版本切换导致的状态不一致问题。
4.4.1 一致性哈希路由模型
下图展示了基于用户ID的哈希路由模型:
4.4.2 哈希路由代码实现
// HashRouter 基于用户ID的哈希路由
type HashRouter struct {
versionRanges map[string][2]int // 版本 -> [起始, 结束) 哈希区间
}
func (r *HashRouter) SelectVersion(userID string) string {
hashVal := int(crc32.ChecksumIEEE([]byte(userID))) % 100
for version, rng := range r.versionRanges {
if hashVal >= rng[0] && hashVal < rng[1] {
return version
}
}
return "v1" // 默认版本
}
// 初始化:v1 占 70%,v2 占 30%
router := &HashRouter{
versionRanges: map[string][2]int{
"v1": {0, 70},
"v2": {70, 100},
},
}
// 使用示例
version := router.SelectVersion("user_12345") // 始终返回 v1 或 v2
4.4.3 哈希路由 vs 权重路由
| 对比维度 | 哈希路由 | 权重路由 |
|---|---|---|
| 确定性 | 确定:同一ID始终路由到同一版本 | 随机:同一请求可能路由到不同版本 |
| 会话保持 | 天然支持,无需额外状态 | 不支持,需要外部存储记录 |
| 流量分布 | 取决于ID分布,可能不均匀 | 严格按比例分布 |
| 版本扩缩容 | 需要重新哈希,可能影响大量用户 | 直接调整权重,影响平滑 |
| 适用场景 | 用户粒度灰度、策略版本绑定 | A/B测试、小流量验证 |
工程实践:在量化策略灰度中,推荐使用策略ID作为哈希键。因为策略ID通常与交易标的或策略逻辑绑定,同一个策略ID的订单路由到同一个版本,可以避免因版本切换导致的策略信号不一致问题。
4.5 三种路由模式的组合策略
在实际的量化策略灰度发布系统中,单一路由模式往往无法满足所有需求。以下是一个典型的组合路由策略示例:
- 第一层:标签路由 — 检查请求是否携带内部灰度标签(如
env=staging),若是则直接路由到最新灰度版本。 - 第二层:哈希路由 — 对普通用户,根据用户ID进行哈希路由,确保用户粒度的会话保持。
- 第三层:权重路由 — 在哈希路由的基础上,通过调整哈希区间(即权重)来控制灰度比例。
这种组合策略既保证了灰度验证的灵活性,又实现了用户粒度的稳定性,是生产环境中最推荐的架构模式。