使用 ASM Hash Tagging 插件进行按比例灰度发布

简介: ASM Hash Tagging插件基于请求头哈希实现精准灰度发布,支持金丝雀、A/B测试等场景。通过FNV-1a算法将用户流量按比例稳定路由至不同服务版本,确保会话一致性,助力多应用独立灰度、渐进式发布。

使用 ASM Hash Tagging 插件进行按比例灰度发布

概述

ASM Hash Tagging 插件是一个 WebAssembly (Wasm) 插件,专为基于请求头哈希的流量路由而设计。它支持复杂的流量管理场景,如金丝雀发布、A/B 测试和基于用户的路由,确保同一用户始终收到相同的服务版本。

核心概念

工作原理

插件在入口网关级别运行,执行以下操作:

  1. 从传入请求中提取指定的头部值(例如 x-user-id
  2. 使用 FNV-1a 算法执行哈希操作
  3. 计算哈希值对配置值(通常为 100,用于百分比路由)的模
  4. 根据配置的范围确定要分配的标签值
  5. 向请求中添加带有分配标签值的新头部
  6. 此标签头部随后被 Istio VirtualService 用于路由到适当的服务子集

关键组件

  • Header(头部): 用于哈希的请求头部(例如 x-user-id
  • Modulo(模数): 哈希结果的最大值(通常为 100,用于基于百分比的路由)
  • Tag Header(标签头部): 要添加到请求的头部名称(例如 app-version
  • Policies(策略): 定义范围和相应的标签值
  • Partitioned Policies(分区策略): 使用权重而非范围定义策略的替代方式

使用场景

1. 独立金丝雀发布

多个应用程序可以同时执行独立的灰度发布。每个应用程序团队都可以控制自己的发布百分比,而不会影响其他团队。例如:

  • 应用 A:10% 的用户到 v2,90% 到 v1
  • 应用 B:30% 的用户到 v2,70% 到 v1
  • 应用 C:50% 的用户到 v3,50% 到 v1

2. 基于用户的流量路由

确保来自同一用户的所有请求始终路由到相同的服务版本,提供稳定的用户体验。这对于有用户会话或有状态交互的应用程序尤其重要。

3. A/B 测试

将不同的用户段路由到不同的应用程序版本以进行功能测试,可以精确控制分配给每个变体的用户百分比。

4. 渐进式发布

将新功能或服务版本逐渐引入到受控百分比的用户中,允许在全面部署之前进行监控和验证。

配置结构

type HashTaggingConfig struct {
   
    Debug *HashTaggingDebugConfig `json:"debug,omitempty"`
    Rules []TaggingRule           `json:"rules"`
}

type TaggingRule struct {
   
    Name      *string           `json:"name,omitempty"`  // 可选的规则名称,用于调试
    Match     *TaggingRuleMatch `json:"match,omitempty"` // 可选的主机匹配
    Header    string            `json:"header"`          // 要进行哈希的头部
    Modulo    uint32            `json:"modulo"`          // 模数值
    TagHeader string            `json:"tagHeader"`       // 要添加到请求的头部
    Policies  []TaggingPolicy   `json:"policies,omitempty"` // 基于范围的策略
    PartitionedPolicies []PartitionedTaggingPolicy `json:"partitionedPolicies,omitempty"` // 基于权重的策略
}

type TaggingPolicy struct {
   
    Range    uint32 `json:"range"`    // 此策略的上限
    TagValue string `json:"tagValue"` // 当哈希值落在范围内时分配的值
}

type PartitionedTaggingPolicy struct {
   
    PartitionSize uint32 `json:"partitionSize"` // 权重/分区大小
    TagValue      string `json:"tagValue"`      // 此分区分配的值
}

type TaggingRuleMatch struct {
   
    Host *string `json:"host,omitempty"` // 要匹配的主机模式
}

高级功能

主机匹配

使用通配符匹配将规则应用于特定主机:

match:
  host: "*.example.com"

调试配置

启用详细日志记录并为调试指定请求 ID 头部:

debug:
  requestIdHeader: x-request-id
  detailLogEnabled: true

范围策略与分区策略

  • 范围策略: 定义上限(例如,范围 33、66、100 对应 33%、33%、34%)
  • 分区策略: 定义分区大小(例如,大小 30、70 对应 30%、70%)

注意:每个规则应仅配置一种类型的策略。

流量分配算法

插件使用以下算法:

  1. 使用 FNV-1a 算法计算头部值的哈希值
  2. 计算 slot = hash % modulo
  3. 查找第一个 slot < policy.Range 的策略
  4. 向请求中添加相应的标签头部

这确保了相同的头部值的一致路由,同时实现精确的基于百分比的分配。

代码示例

基本配置示例

此示例根据用户的用户 ID 路由流量,将 33% 路由到 v1,33% 到 v2,34% 到 v3:

apiVersion: extensions.istio.io/v1alpha1
kind: WasmPlugin
metadata:
  name: hash-tagging
  namespace: istio-system
spec:
  imagePullPolicy: Always
  selector:
    matchLabels:
      istio: ingressgateway
  url: registry-cn-hangzhou.ack.aliyuncs.com/dev/asm-wasm-hash-tagging:v1.22.6.2-g8d22c57-aliyun
  phase: AUTHN
  pluginConfig:
    rules:
      - header: x-user-id
        modulo: 100
        tagHeader: app-version
        policies:
          - range: 33
            tagValue: v1
          - range: 66
            tagValue: v2
          - range: 100
            tagValue: v3

分区策略示例

此示例使用分区策略而非基于范围的策略:

apiVersion: extensions.istio.io/v1alpha1
kind: WasmPlugin
metadata:
  name: hash-tagging
  namespace: istio-system
spec:
  imagePullPolicy: Always
  selector:
    matchLabels:
      istio: ingressgateway
  url: registry-cn-hangzhou.ack.aliyuncs.com/dev/asm-wasm-hash-tagging:v1.22.6.2-g8d22c57-aliyun
  phase: AUTHN
  pluginConfig:
    rules:
      - header: x-user-id
        modulo: 100
        tagHeader: app-version
        partitionedPolicies:
          - partitionSize: 30
            tagValue: v1
          - partitionSize: 50
            tagValue: v2
          - partitionSize: 20
            tagValue: v3

多规则示例

此示例演示针对不同应用程序的多个规则:

apiVersion: extensions.istio.io/v1alpha1
kind: WasmPlugin
metadata:
  name: hash-tagging
  namespace: istio-system
spec:
  imagePullPolicy: Always
  selector:
    matchLabels:
      istio: ingressgateway
  url: registry-cn-hangzhou.ack.aliyuncs.com/dev/asm-wasm-hash-tagging:v1.22.6.2-g8d22c57-aliyun
  phase: AUTHN
  pluginConfig:
    rules:
      # 应用 A 规则:10% 到 v2,90% 到 v1
      - name: "app-a-routing"
        header: x-user-id
        modulo: 100
        tagHeader: app-a-version
        policies:
          - range: 10
            tagValue: v2
          - range: 100
            tagValue: v1
      # 应用 B 规则:30% 到 v2,70% 到 v1
      - name: "app-b-routing"
        header: x-user-id
        modulo: 100
        tagHeader: app-b-version
        policies:
          - range: 30
            tagValue: v2
          - range: 100
            tagValue: v1
      # 应用 C 规则:50% 到 v3,50% 到 v1
      - name: "app-c-routing"
        header: x-user-id
        modulo: 100
        tagHeader: app-c-version
        policies:
          - range: 50
            tagValue: v3
          - range: 100
            tagValue: v1

特定主机规则示例

此示例仅将规则应用于特定主机:

apiVersion: extensions.istio.io/v1alpha1
kind: WasmPlugin
metadata:
  name: hash-tagging
  namespace: istio-system
spec:
  imagePullPolicy: Always
  selector:
    matchLabels:
      istio: ingressgateway
  url: registry-cn-hangzhou.ack.aliyuncs.com/dev/asm-wasm-hash-tagging:v1.22.6.2-g8d22c57-aliyun
  phase: AUTHN
  pluginConfig:
    rules:
      - name: "api-app-routing"
        match:
          host: "api.example.com"
        header: x-user-id
        modulo: 100
        tagHeader: api-version
        policies:
          - range: 25
            tagValue: v2-beta
          - range: 100
            tagValue: v1-stable

完整的服务网格设置

要完成流量路由设置,您还需要基于插件添加的标签配置 VirtualService:

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: app-a-virtualservice
  namespace: app-namespace
spec:
  hosts:
  - app-a
  http:
  - match:
    - headers:
        app-a-version:
          exact: v2
    route:
    - destination:
        host: app-a
        subset: v2
  - route:
    - destination:
        host: app-a
        subset: v1
---
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: app-a-destinationrule
  namespace: app-namespace
spec:
  host: app-a
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2

源码示例

主程序入口 (main.go)

package main

import (
    "encoding/json"

    "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm"
    "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types"
    _ "github.com/wasilibs/nottinygc"
    "istio.alibabacloud.com/hashtagging/pkg/config"
    "istio.alibabacloud.com/hashtagging/pkg/proxy"
)

func main() {
   
    proxywasm.SetVMContext(&vmContext{
   })
}

type vmContext struct {
   
    // Embed the default VM context here,
    // so that we don't need to reimplement all the methods.
    types.DefaultVMContext
}

// Override types.DefaultVMContext.
func (*vmContext) NewPluginContext(contextID uint32) types.PluginContext {
   
    return &pluginContext{
   }
}

type pluginContext struct {
   
    // Embed the default plugin context here,
    // so that we don't need to reimplement all the methods.
    types.DefaultPluginContext
    config        *config.HashTaggingConfig
    runtimeConfig *proxy.HashTaggingConfigRuntime
}

// Override types.DefaultPluginContext.
func (ctx *pluginContext) OnPluginStart(pluginConfigurationSize int) types.OnPluginStartStatus {
   
    proxywasm.LogDebug("loading hash tagging plugin config")
    data, err := proxywasm.GetPluginConfiguration()
    if err != nil {
   
        proxywasm.LogErrorf("error in GetPluginConfiguration: %v", err)
        return types.OnPluginStartStatusFailed
    }
    if data == nil {
   
        proxywasm.LogError("empty config, pluginStartFailed")
        return types.OnPluginStartStatusFailed
    }
    config, err := config.NewHashTaggingConfig(data)
    if err != nil {
   
        proxywasm.LogErrorf("error in NewHashTaggingConfig: %v", err)
        return types.OnPluginStartStatusFailed
    }
    ctx.config = config
    ctx.runtimeConfig = &proxy.HashTaggingConfigRuntime{
   }
    ctx.runtimeConfig.FromConfig(config)
    marshaledConfig, _ := json.Marshal(config)

    marshaledRuntimeConfig, _ := json.Marshal(ctx.runtimeConfig)
    proxywasm.LogDebugf("raw config: %s", string(data))
    proxywasm.LogDebugf("marshaled config: %s", string(marshaledConfig))
    proxywasm.LogDebugf("marshaled runtime config: %s", string(marshaledRuntimeConfig))
    return types.OnPluginStartStatusOK
}

// Override types.DefaultPluginContext.
func (ctx *pluginContext) NewHttpContext(contextID uint32) types.HttpContext {
   
    return proxy.NewHashTaggingContext(ctx.runtimeConfig)
}

配置处理 (pkg/config/config.go)

package config

import (
    "encoding/json"
    "fmt"
    "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm"
    "hash/fnv"
)

// TaggingPolicy represents a policy for tagging based on hash ranges.
type TaggingPolicy struct {
   
    Range    uint32 `json:"range"`
    TagValue string `json:"tagValue"`
}

// PartitionedTaggingPolicy represents a policy for tagging based on partitions.
type PartitionedTaggingPolicy struct {
   
    PartitionSize uint32 `json:"partitionSize"`
    TagValue      string `json:"tagValue"`
}

// TaggingRuleMatch specifies matching criteria for rules.
type TaggingRuleMatch struct {
   
    Host *string `json:"host,omitempty"`
}

// DeepCopy creates a deep copy of TaggingRuleMatch.
func (t *TaggingRuleMatch) DeepCopy() *TaggingRuleMatch {
   
    result := &TaggingRuleMatch{
   }
    if t.Host != nil {
   
        tempHost := *t.Host
        result.Host = &tempHost
    }
    return result
}

// TaggingRule represents a rule for hash tagging.
type TaggingRule struct {
   
    Name      *string           `json:"name,omitempty"`
    Match     *TaggingRuleMatch `json:"match,omitempty"`
    Header    string            `json:"header"`
    Modulo    uint32            `json:"modulo"`
    TagHeader string            `json:"tagHeader"`
    Policies  []TaggingPolicy   `json:"policies,omitempty"`
    // configure each policy's weight is friendlier than range
    PartitionedPolicies []PartitionedTaggingPolicy `json:"partitionedPolicies,omitempty"`
}

// DeepCopy creates a deep copy of TaggingRule.
func (t *TaggingRule) DeepCopy() *TaggingRule {
   
    result := &TaggingRule{
   
        Header:    t.Header,
        Modulo:    t.Modulo,
        TagHeader: t.TagHeader,
        Policies:  make([]TaggingPolicy, len(t.Policies)),
    }
    copy(result.Policies, t.Policies)

    if t.Name != nil {
   
        tempName := *t.Name
        result.Name = &tempName
    }
    if t.Match != nil {
   
        result.Match = t.Match.DeepCopy()
    }

    for _, policy := range t.PartitionedPolicies {
   
        result.PartitionedPolicies = append(result.PartitionedPolicies, policy)
    }
    return result
}

// HeaderGetter is a function type for getting header values.
type HeaderGetter func(headerName string) (string, error)

// MatchPolicy matches a policy based on header value hashing.
func (t *TaggingRule) MatchPolicy(headerGetter HeaderGetter) (*TaggingPolicy, error) {
   
    hashHeaderValue, err := headerGetter(t.Header)
    if err != nil {
   
        return nil, fmt.Errorf("failed to match policy since failed to get header value, err: %s", err.Error())
    }
    hash := fnv.New32a()
    _, err = hash.Write([]byte(hashHeaderValue))
    if err != nil {
   
        return nil, fmt.Errorf("failed to match policy since failed to write hash, err: %s", err.Error())
    }
    hashNumber := hash.Sum32()
    slot := hashNumber % t.Modulo
    for _, policy := range t.Policies {
   
        if slot >= policy.Range {
   
            continue
        }
        return &policy, nil
    }
    return nil, nil
}

// GetName returns the name of the rule.
func (t *TaggingRule) GetName() string {
   
    if t.Name == nil {
   
        return "unknown-rule"
    }
    return *t.Name
}

// HashTaggingDebugConfig holds debug configuration options.
type HashTaggingDebugConfig struct {
   
    // user can specify a header to be treated as request id in log for debug use.
    RequestIdHeader  *string `json:"requestIdHeader,omitempty"`
    DetailLogEnabled *bool   `json:"detailLogEnabled,omitempty"`
}

// HashTaggingConfig is the main configuration structure.
type HashTaggingConfig struct {
   
    Debug *HashTaggingDebugConfig `json:"debug,omitempty"`
    Rules []TaggingRule           `json:"rules"`
}

// validateConfig validates the configuration.
func validateConfig(config *HashTaggingConfig) error {
   
    ruleRepeatDetectMap := map[string]struct{
   }{
   }
    for _, rule := range config.Rules {
   
        if _, found := ruleRepeatDetectMap[rule.TagHeader]; found {
   
            return fmt.Errorf("HashTagging plugin found repeated rule, key %s", rule.TagHeader)
        }
        ruleRepeatDetectMap[rule.TagHeader] = struct{
   }{
   }
        rangeOfPreviousPolicy := uint32(0)
        tagValueRepeatedDetectMap := map[string]struct{
   }{
   }
        if len(rule.Policies) > 0 && len(rule.PartitionedPolicies) > 0 {
   
            return fmt.Errorf("one one of policies and weightedPolicies should be configured")
        }

        for _, policy := range rule.Policies {
   
            if policy.Range == 0 {
   
                return fmt.Errorf("HashTagging plugin, invalid policy of rule[%s], range must greater than 0", rule.TagHeader)
            }
            if policy.Range <= rangeOfPreviousPolicy {
   
                return fmt.Errorf("HashTagging plugin, invalid policy of rule [%s], policy will never hit since range[%d] is not greater than previous one[%d]", rule.TagHeader, policy.Range, rangeOfPreviousPolicy)
            }
            rangeOfPreviousPolicy = policy.Range
            if policy.Range > rule.Modulo {
   
                return fmt.Errorf("HashTagging plugin, invalid policy of rule [%s], range[%d] greater than modulo[%d]", rule.TagHeader, policy.Range, rule.Modulo)
            }
            if _, found := tagValueRepeatedDetectMap[policy.TagValue]; found {
   
                return fmt.Errorf("HashTagging plugin, found repeated tag value [%s] in rule [%s]", policy.TagValue, rule.TagHeader)
            }
        }

        // validate weighted policies with rules, sum of weight must equals to modulo
        if len(rule.PartitionedPolicies) > 0 {
   
            sumOfWeight := uint32(0)
            for _, weightedPolicy := range rule.PartitionedPolicies {
   
                sumOfWeight += weightedPolicy.PartitionSize
            }
            if sumOfWeight > rule.Modulo {
   
                return fmt.Errorf("HashTagging plugin, invalid weighted policies of rule [%s], sum of weight[%d] is greater than modulo[%d]", rule.TagHeader, sumOfWeight, rule.Modulo)
            }
        }
    }
    return nil
}

// NewHashTaggingConfig creates a new configuration from JSON.
func NewHashTaggingConfig(jsonStr []byte) (*HashTaggingConfig, error) {
   
    config := &HashTaggingConfig{
   }
    err := json.Unmarshal(jsonStr, config)
    if err != nil {
   
        proxywasm.LogErrorf("error in unmarshal HashTaggingConfig: %v", err)
        return config, err
    }

    return config, validateConfig(config)
}

哈希处理逻辑 (pkg/proxy/hash-tagging.go)

package proxy

import (
    "fmt"
    "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm"
    "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types"
    "istio.alibabacloud.com/hashtagging/pkg/config"
    wildmatch "github.com/becheran/wildmatch-go"
)

var PROPERTY_REQ_HOST = []string{
   "request", "host"}

const (
    WILDCARD = "*"
)

// TaggingRuleList is a list of tagging rules.
type TaggingRuleList []*config.TaggingRule

// Add adds a rule to the list.
func (t *TaggingRuleList) Add(rule *config.TaggingRule) {
   
    *t = append(*t, rule)
}

// HashTaggingConfigRuntime is the runtime configuration structure.
type HashTaggingConfigRuntime struct {
   
    Debug               *config.HashTaggingDebugConfig
    RulesForAllHost     TaggingRuleList
    RulesForCertainHost map[string]TaggingRuleList
}

// FromConfig converts the raw config to runtime config.
func (h *HashTaggingConfigRuntime) FromConfig(config *config.HashTaggingConfig) {
   
    h.Debug = config.Debug
    for _, rule := range config.Rules {
   
        h.add(&rule)
    }
}

// convertPartitionedPoliciesToRangePolicies converts partitioned policies to range policies.
func convertPartitionedPoliciesToRangePolicies(rule *config.TaggingRule) {
   
    totalParitionSize := uint32(0)
    for _, weightedPolicy := range rule.PartitionedPolicies {
   
        totalParitionSize += weightedPolicy.PartitionSize
        rangePolicy := config.TaggingPolicy{
   
            Range:    totalParitionSize,
            TagValue: weightedPolicy.TagValue,
        }
        rule.Policies = append(rule.Policies, rangePolicy)
    }
    if totalParitionSize > rule.Modulo {
   
        proxywasm.LogErrorf("failed to convert partitioned policy to range policy, total partitionSize MUST <= modulo")
    }
}

// add adds a rule to the runtime configuration.
func (h *HashTaggingConfigRuntime) add(rule *config.TaggingRule) {
   
    ruleCopy := rule.DeepCopy()
    convertPartitionedPoliciesToRangePolicies(ruleCopy)

    isForAllHost := ruleCopy.Match == nil || ruleCopy.Match.Host == nil ||
        *ruleCopy.Match.Host == WILDCARD || *ruleCopy.Match.Host == ""
    if isForAllHost {
   
        h.RulesForAllHost.Add(ruleCopy)
    } else {
   
        if h.RulesForCertainHost == nil {
   
            h.RulesForCertainHost = map[string]TaggingRuleList{
   }
        }
        rules, found := h.RulesForCertainHost[*ruleCopy.Match.Host]
        if found {
   
            rules.Add(ruleCopy)
        } else {
   
            temp := TaggingRuleList{
   }
            temp.Add(ruleCopy)
            h.RulesForCertainHost[*ruleCopy.Match.Host] = temp
        }
    }
}

// Match returns matching rules for the given host.
func (h *HashTaggingConfigRuntime) Match(reqHost string) TaggingRuleList {
   
    result := TaggingRuleList{
   }
    // add all host rules into result
    result = append(result, h.RulesForAllHost...)

    for host, rules := range h.RulesForCertainHost {
   
        wm := wildmatch.NewWildMatch(host)
        if !wm.IsMatch(reqHost) {
   
            continue
        }
        result = append(result, rules...)
    }
    return result
}

// HashTaggingContext is the context for hash tagging operations.
type HashTaggingContext struct {
   
    // Embed the default http context here,
    // so that we don't need to reimplement all the methods.
    types.DefaultHttpContext
    config    *HashTaggingConfigRuntime
    enabled   bool
    requestId string
}

// NewHashTaggingContext creates a new hash tagging context.
func NewHashTaggingContext(cfg *HashTaggingConfigRuntime) *HashTaggingContext {
   
    return &HashTaggingContext{
   
        config: cfg,
    }
}

// matchRules matches rules for the current request.
func (h *HashTaggingContext) matchRules() (TaggingRuleList, error) {
   
    reqHost, err := proxywasm.GetProperty(PROPERTY_REQ_HOST)
    if err != nil {
   
        return nil, fmt.Errorf("failed to get host, err: %s", err.Error())
    }
    reqHostStr := string(reqHost)

    result := h.config.Match(reqHostStr)

    if h.config.Debug != nil && h.config.Debug.DetailLogEnabled != nil && *h.config.Debug.DetailLogEnabled {
   
        for idx, rule := range result {
   
            h.LogDebugf("matched rule %d: %s", idx, rule.Name)
        }
    }
    return result, nil
}

// OnHttpRequestHeaders handles HTTP request headers.
func (h *HashTaggingContext) OnHttpRequestHeaders(numHeaders int, endOfStream bool) types.Action {
   
    h.LogDebugf("HashTagging entered")
    rules, err := h.matchRules()
    if err != nil {
   
        h.LogDebugf("failed to match rules")
        return types.ActionContinue
    }
    h.LogDebugf("HashTagging rules num: %d", len(rules))

    for _, rule := range rules {
   
        matchedPolicy, err := rule.MatchPolicy(proxywasm.GetHttpRequestHeader)
        if err != nil {
   
            h.LogErrorf("error when match policy of rule %s, err: %s", rule.GetName(), err.Error())
            continue
        }
        if matchedPolicy == nil {
   
            h.LogDebugf("hash tagging plugin rule %s not matched", rule.GetName())
            continue
        }
        h.LogDebugf("hash tagging plugin rule %s matched, add header %s=%s", rule.GetName(), rule.TagHeader, matchedPolicy.TagValue)
        err = proxywasm.AddHttpRequestHeader(rule.TagHeader, matchedPolicy.TagValue)
        if err != nil {
   
            h.LogErrorf("error when add header %s=%s for request", rule.TagHeader, matchedPolicy.TagValue)
            continue
        }
    }
    return types.ActionContinue
}

// OnHttpRequestBody handles HTTP request body.
func (h *HashTaggingContext) OnHttpRequestBody(bodySize int, endOfStream bool) types.Action {
   
    return types.ActionContinue
}

// OnHttpResponseHeaders handles HTTP response headers.
func (h *HashTaggingContext) OnHttpResponseHeaders(numHeaders int, endOfStream bool) types.Action {
   
    return types.ActionContinue
}

// OnHttpResponseBody handles HTTP response body.
func (h *HashTaggingContext) OnHttpResponseBody(bodySize int, endOfStream bool) types.Action {
   
    return types.ActionContinue
}

// initRequestId initializes the request ID.
func (h *HashTaggingContext) initRequestId() {
   
    if h.requestId == "" {
   
        if h.config.Debug != nil && h.config.Debug.RequestIdHeader != nil {
   
            requestId, err := proxywasm.GetHttpRequestHeader(*h.config.Debug.RequestIdHeader)
            if err != nil {
   
                h.requestId = "unknown"
            } else {
   
                h.requestId = requestId
            }
        } else {
   
            h.requestId = "unknown"
        }
    }
}

// LogDebugf logs a debug message.
func (h *HashTaggingContext) LogDebugf(format string, args ...interface{
   }) {
   
    h.initRequestId()
    newArgs := append([]interface{
   }{
   h.requestId}, args...)
    proxywasm.LogDebugf("[HashTaggingPlugin][%s] "+format, newArgs...)
}

// LogTracef logs a trace message.
func (h *HashTaggingContext) LogTracef(format string, args ...interface{
   }) {
   
    h.initRequestId()
    newArgs := append([]interface{
   }{
   h.requestId}, args...)
    proxywasm.LogTracef("[HashTaggingPlugin][%s] "+format, newArgs...)
}

// LogErrorf logs an error message.
func (h *HashTaggingContext) LogErrorf(format string, args ...interface{
   }) {
   
    h.initRequestId()
    newArgs := append([]interface{
   }{
   h.requestId}, args...)
    proxywasm.LogErrorf("[HashTaggingPlugin][%s] "+format, newArgs...)
}

如何编译构建?

tinygo build -o build/plugin.wasm -gc=custom -tags='custommalloc nottinygc_envoy'  -target=wasi -scheduler=none main.go

结论

Hash Tagging 插件为在服务网格中实施基于用户的流量路由提供了一个强大的机制。通过利用基于哈希的算法,它实现了精确的基于百分比的分布,同时在服务版本之间保持用户会话的一致性。

这种方法对于以下场景特别有价值:

  • 跨多个应用程序的独立金丝雀发布
  • 具有一致用户体验的 A/B 测试
  • 带有细粒度控制的渐进式发布
  • 具有不同功能集的多租户应用程序

插件的灵活配置允许在常见用例中保持简单的同时处理复杂的路由场景。结合 Istio 的 VirtualService 和 DestinationRule 资源,它支持支持现代部署实践的复杂流量管理策略。

更多关于本插件的使用说明,参见 基于哈希打标插件的多标签路由实现按用户比例进行灰度发布

相关文章
|
1月前
|
人工智能 安全 前端开发
AgentScope Java v1.0 发布,让 Java 开发者轻松构建企业级 Agentic 应用
AgentScope 重磅发布 Java 版本,拥抱企业开发主流技术栈。
2061 30
|
24天前
|
人工智能 监控 Cloud Native
云原生AI赋能文旅数智化转型:玄晶引擎AI数字员工落地长白山康养项目全解析
本文以长白山大健康企业为例,介绍其通过玄晶引擎云原生AI数字员工实现“养生+文旅”模式智能化升级的实践。涵盖技术架构、运营适配、营销创新与落地经验,展现AI在内容生产、客服转化、B端获客等环节的全链路赋能,助力企业收益率提升47%、团队扩张35%,为文旅产业数智化转型提供可复用范本。
146 12
|
19天前
|
供应链 容器
什么是code128码?
Code 128码是一种高密度条形码,支持全ASCII字符,广泛用于物流、运输和供应链管理。它分为A、B、C三个子集,可编码字母、数字及控制符,具有高密度、小空间优势,适用于复杂数据编码需求。
380 3
|
30天前
|
Java 中间件 Nacos
开箱即用的 GoWind Admin|风行,企业级前后端一体中后台框架:kratos-bootstrap 入门教程(类比 Spring Boot)
kratos-bootstrap 是 GoWind Admin 的核心引导框架,类比 Spring Boot,提供应用初始化、配置管理、组件集成等一站式解决方案。通过标准化流程与多源配置支持,开发者可快速构建企业级中后台服务,专注业务开发,降低微服务复杂度。
146 2
|
1月前
|
Java 关系型数据库 MySQL
基于springboot的二手物品交易系统
本研究聚焦二手交易平台的网络化转型,探讨其在社会经济快速发展背景下的必要性与意义。结合SpringBoot、Java、MySQL等技术,分析系统设计与实现路径,旨在提升平台管理效率、降低成本,推动二手交易向规范化、信息化发展,助力现代化服务体系建设。
|
1月前
|
人工智能 关系型数据库 API
AI数字员工哪个好?2025十大品牌云原生适配实测:玄晶引擎/百度/阿里全链路方案
本文基于阿里云生态实测,解析AI数字员工从“可视化”到“业务落地”的转型趋势,揭露选型两大陷阱,结合玄晶引擎等50+案例与API性能数据,发布十大品牌榜单。聚焦云原生架构、API对接效率、开发友好度与全链路闭环四大维度,提供中小微企业至中大型企业的优选方案及开发者专属选型工具包,助力低成本高效落地。
317 8
|
1月前
|
JavaScript Java 关系型数据库
基于springboot的社区垃圾分类管理系统
本系统基于Spring Boot与MySQL,结合物联网、大数据等技术,构建社区智能垃圾管理平台。实现垃圾投放监控、自动分类识别、积分激励及数据统计分析,提升管理效率与居民参与度,推动绿色社区可持续发展。
|
1月前
|
人工智能 运维 安全
一文看懂函数计算 AgentRun,让 Agentic AI 加速进入企业生产环境
AgentRun 的愿景很简单:让 AI Agent 从 Demo 到生产级部署,变得前所未有的简单。通过 Serverless 架构持续优化成本并解放运维负担,通过企业级 Runtime 提供生产级的执行环境和安全保障,通过开源生态集成避免框架锁定,通过全链路可观测让每个环节都清晰可控——这就是 AgentRun 要为企业提供的完整解决方案。
|
8天前
|
消息中间件 人工智能 Kubernetes
阿里云云原生应用平台岗位急招,加入我们,打造 AI 最强基础设施
云原生应用平台作为中国最大云计算公司的基石,现全面转向 AI,打造 AI 时代最强基础设施。寻找热爱技术、具备工程极致追求的架构师、极客与算法专家,共同重构计算、定义未来。杭州、北京、深圳、上海热招中,让我们一起在云端,重构 AI 的未来。