前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >灰度策略-简易实现

灰度策略-简易实现

原创
作者头像
王宝
发布2024-11-24 16:23:00
发布2024-11-24 16:23:00
950
举报

背景

在生产环境,进行新旧流量的切换时,需要按一定规则逐步迁移,防止一次性迁移如果出现问题导致后端服务奔溃影响用户,比如按用户id切换访问、按设备标识进行逐步升级等等

专业术语

策略:切换方式有很多种类,每一种为一种策略,有唯一词典标识,需程序员自行定义

规则:一种策略下面有多个规则,规则之间定义为或的关系,即满足一条规则,则该灰度策略为true

条件:一个规则由多个条件组成,条件之间为且的关系,条件都满足,则该条规则满足

示列:定义一种策略标识【myteststrategy】,该策略下有两个规则,一是对用户uid取10000的余数,落在【1-5000】范围内,则该规则满足灰度策略,一是指定uid在白名单范围内,则该条规则满足灰度条件,以上任意一条规则满足条件,则该灰度策略满足。

注意:一个策略下,多个规则之间是或,每个规则中条件之间是且

测试示例

以下添加一种灰度策略,输入值进行检测

代码语言:txt
复制
package graytool

import (
	"context"
	"fmt"
	"testing"
)

const grayKey = "deviceFwVersionUpgrade"

// 1、设置灰度规则 2、获取灰度规则 3、检验设备型号和设备uuid是否满足灰度条件
func TestSetGrayList(t *testing.T) {
	var ctx = context.Background()
	grayLogic := NewGrayLogic()

	uuidRatio := 10
	req := SetGaryListReq{
		GaryKey: grayKey,
		RuleItem: []*GrayRuleItemInfo{
			{
				DeviceType: &[]string{"mss110"},
				UuidRatio:  &uuidRatio,
			},
			&GrayRuleItemInfo{
				DeviceType: &[]string{"mss110"},
			},
		},
	}
	result, err := grayLogic.SetGrayList(ctx, req)
	fmt.Println(result, err)
	//获取设置的灰度规则
	TestGetGrayList(t)
	//检测灰度结果
	TestGrayResult(t)
}

/*获取灰度规则*/
func TestGetGrayList(t *testing.T) {
	var ctx = context.Background()
	grayLogic := NewGrayLogic()

	req := GetGaryListReq{
		GaryKey: grayKey,
	}
	result, err := grayLogic.GetGrayList(ctx, req)
	fmt.Println(len(result.List), result, err)
}

/*获取灰度结果*/
func TestGrayResult(t *testing.T) {
	var ctx = context.Background()
	grayLogic := NewGrayLogic()

	req := GetGaryStatusReq{
		GaryKey: grayKey,
		Data: map[string]string{
			"deviceType": "mss110",
			"uuid":       "myUuid",
		},
	}
	result, err := grayLogic.GetGrayStatus(ctx, req)
	fmt.Println(result, err)
}

策略支持规则条件核心代码

代码语言:txt
复制
package graytool

import (
	"context"
	"errors"
	"hash/crc32"
	"strconv"
	"strings"
	"sync"
	"time"
)

var grayListRWLock sync.Mutex       //包里面放全局的排斥 便于所有协程都能访问同一个锁
const grayRuleListExpire = 600      // 规则信息的本地内存缓存时间
const grayRuleListOffsetExpire = 10 // 检测规则信息的补偿时间,避免检测时临界缓存过期的情况
var grayList = make(map[string]*grayRuleList)

// 灰度规则列表
type grayRuleList struct {
	List   []*GrayRuleItemInfo //列表的值
	Expire int64               //过期时间戳
}

type GrayRuleItemInfo struct {
	AppVendor     *[]string //app厂商
	UidRatio      *int      //根据uid的万分比
	DeviceType    *[]string //支持的设备型号
	NotDeviceType *[]string //不支持的设备型号
	UuidRatio     *int      //uuid灰度的百分比
	UuidWhiteList *[]string //uuid白名单
	ClusterRegion *[]string //集群区域
	UidWhiteList  *[]int64  //uid白名单
	IdWhiteList   *[]int64  //表的id白名单
}

type CheckGaryKeyReq struct {
	GaryKey string
}

type GetGaryListReq struct {
	GaryKey string
}

type SetGaryListReq struct {
	GaryKey  string
	RuleItem []*GrayRuleItemInfo
}

type GetGaryStatusReq struct {
	GaryKey string
	Data    map[string]string
}

type GrayLogic struct {
}

func NewGrayLogic() *GrayLogic {
	return &GrayLogic{}
}

// 判断grayKey对应规则是否存在,且是否过期 true表示存在,false表示不存在
func (g *GrayLogic) CheckGrayList(ctx context.Context, req CheckGaryKeyReq) (bool, error) {
	if req.GaryKey == "" {
		return false, errors.New("gray key is not empty")
	}
	if value, ok := grayList[req.GaryKey]; ok {
		if value.Expire != 0 && (value.Expire-grayRuleListOffsetExpire) > time.Now().Unix() {
			return true, nil
		}
	}
	return false, nil
}

// 根据grayKey设置对应规则及过期时间
func (g *GrayLogic) SetGrayList(ctx context.Context, req SetGaryListReq) (bool, error) {
	if req.GaryKey == "" {
		return false, errors.New("gray key is not empty")
	}
	grayListRWLock.Lock()
	grayList[req.GaryKey] = &grayRuleList{
		List:   req.RuleItem,
		Expire: time.Now().Unix() + grayRuleListExpire,
	}
	grayListRWLock.Unlock()
	return true, nil
}

// 根据grayKey获取对应规则及过期时间
func (g *GrayLogic) GetGrayList(ctx context.Context, req GetGaryListReq) (*grayRuleList, error) {
	grayRuleList := &grayRuleList{}
	if req.GaryKey == "" {
		return grayRuleList, errors.New("gray key is not empty")
	}
	if _, ok := grayList[req.GaryKey]; !ok {
		return grayRuleList, nil
	}
	return grayList[req.GaryKey], nil
}

// 获取是否满足灰度
func (g *GrayLogic) GetGrayStatus(ctx context.Context, req GetGaryStatusReq) (bool, error) {

	if req.GaryKey == "" {
		return false, errors.New("gray key is not empty")
	}

	if _, ok := grayList[req.GaryKey]; !ok {
		return false, errors.New("gray scale list not initialized")
	}

	ruleItems := grayList[req.GaryKey].List
	if len(ruleItems) == 0 {
		return false, nil
	}
	//灰度key不存在,或者灰度子策略不存在
	flag := false
	//遍历所有的规则条件进行处理(存在多条规则时,有一个rule验证通过则认为是验证通过的)
	for _, rule := range ruleItems {
		if flag {
			break
		}

		//循环每条rule规则中的所有限制条件(所有条件全部满足才认为是验证通过)
		ruleSuccess := true
		if rule.UidRatio != nil {
			if !g.handleUidRatio(req, rule.UidRatio) {
				ruleSuccess = false
				continue
			}
		}
		if rule.AppVendor != nil && ruleSuccess {
			if !g.handleAppVendor(req, rule.AppVendor) {
				ruleSuccess = false
				continue
			}
		}
		if rule.DeviceType != nil && ruleSuccess {
			if !g.handleDeviceType(req, rule.DeviceType) {
				ruleSuccess = false
				continue
			}
		}
		if rule.NotDeviceType != nil && ruleSuccess {
			if !g.handleNotDeviceType(req, rule.NotDeviceType) {
				ruleSuccess = false
				continue
			}
		}
		if rule.UuidRatio != nil && ruleSuccess {
			if !g.handleUuidRatio(req, rule.UuidRatio) {
				ruleSuccess = false
				continue
			}
		}
		if rule.UuidWhiteList != nil && ruleSuccess {
			if !g.handleUuidWhiteList(req, rule.UuidWhiteList) {
				ruleSuccess = false
				continue
			}
		}
		if rule.ClusterRegion != nil && ruleSuccess {
			if !g.handleClusterRegion(req, rule.ClusterRegion) {
				ruleSuccess = false
				continue
			}
		}
		if rule.UidWhiteList != nil && ruleSuccess {
			if !g.handleUidWhiteList(req, rule.UidWhiteList) {
				ruleSuccess = false
				continue
			}
		}
		if rule.IdWhiteList != nil && ruleSuccess {
			if !g.handleIdWhiteList(req, rule.IdWhiteList) {
				ruleSuccess = false
				continue
			}
		}
		//TODO 若有新增的KEY则需要新增判断逻辑 ↑
		//fmt.Println("====>",ruleSuccess)

		if ruleSuccess {
			flag = true
		}
	}
	return flag, nil
}

// 处理uid的万分比灰度
func (g *GrayLogic) handleUidRatio(req GetGaryStatusReq, itemRule *int) bool {
	if _, ok := req.Data["uid"]; !ok {
		return false
	}

	uid, err := strconv.ParseInt(req.Data["uid"], 10, 64)
	if err != nil || uid <= 0 {
		return false
	}

	//除以10000取余的范围是 0~9999, 万分之一百表示的是0~99
	if int(uid%10000) >= *itemRule {
		return false
	}

	return true
}

// 处理根据设备的deviceType进行灰度发布的策略(支持的型号)
func (g *GrayLogic) handleDeviceType(req GetGaryStatusReq, itemRule *[]string) bool {
	//设备型号不存在 或者 设备型号为空,不灰度
	reqDeviceType, ok := req.Data["deviceType"]
	if !ok || reqDeviceType == "" {
		return false
	}
	reqDeviceType = strings.ToLower(reqDeviceType)

	//规则中型号的值为空数组,也表示不灰度
	if len(*itemRule) == 0 {
		return false
	}

	for _, deviceType := range *itemRule {
		//判断是否在灰度范围内(all表示灰度所有设备型号)
		if ("all" == deviceType) || (reqDeviceType == strings.ToLower(deviceType)) {
			return true
		}
	}
	return false
}

// 处理根据app的vendor进行灰度发布的策略
func (g *GrayLogic) handleAppVendor(req GetGaryStatusReq, itemRule *[]string) bool {
	//vendor 不存在或者值为空 不灰度
	vendor, ok := req.Data["vendor"]
	if !ok || vendor == "" {
		return false
	}

	vendor = strings.ToLower(vendor)

	for _, value := range *itemRule {
		//判断是否在灰度范围内 (all表示灰度所有vendor)
		if ("all" == value) || (vendor == strings.ToLower(value)) {
			return true
		}
	}
	return false
}

// 处理根据设备的deviceType进行灰度发布的策略 (不支持的型号)
func (g *GrayLogic) handleNotDeviceType(req GetGaryStatusReq, itemRule *[]string) bool {
	//设备型号不存在 或者 设备型号为空,不灰度
	deviceType, ok := req.Data["deviceType"]
	if !ok || deviceType == "" {
		return false
	}

	deviceType = strings.ToLower(deviceType)
	// 规则中型号的值为空数组,表示全部灰度
	if len(*itemRule) == 0 {
		return true
	}

	for _, value := range *itemRule {
		// all表示所有型号都不灰度
		if "all" == value || deviceType == strings.ToLower(value) {
			return false
		}
	}
	return true
}

// 处理根据设备的uuid进行百分比灰度的策略
func (g *GrayLogic) handleUuidRatio(req GetGaryStatusReq, itemRule *int) bool {
	//设备uuid不存在 或者 uuid为空,不灰度
	uuid, ok := req.Data["uuid"]
	if !ok || uuid == "" {
		return false
	}
	//将字符串转换为int
	uuidUint32 := crc32.ChecksumIEEE([]byte(uuid))

	//除以100取余的范围是 0~99, 百分之五十表示的是0~49
	if int(uuidUint32%uint32(100)) >= *itemRule {
		return false
	}
	return true
}

// 处理根据uuid设置白名单策略
func (g *GrayLogic) handleUuidWhiteList(req GetGaryStatusReq, itemRule *[]string) bool {
	//uuid 不存在或者值为空 不灰度
	uuid, ok := req.Data["uuid"]
	if !ok || uuid == "" {
		return false
	}

	uuid = strings.ToLower(uuid)

	for _, value := range *itemRule {
		//判断是否在灰度范围内
		if uuid == strings.ToLower(value) {
			return true
		}
	}
	return false
}

// 处理根据集群区域策略
func (g *GrayLogic) handleClusterRegion(req GetGaryStatusReq, itemRule *[]string) bool {
	//clusterRegion 不存在或者值为空 不灰度
	region, ok := req.Data["clusterRegion"]
	if !ok || region == "" {
		return false
	}

	region = strings.ToLower(region)

	for _, value := range *itemRule {
		//判断是否在灰度范围内
		if region == strings.ToLower(value) {
			return true
		}
	}
	return false
}

// 处理根据uid设置白名单策略
func (g *GrayLogic) handleUidWhiteList(req GetGaryStatusReq, itemRule *[]int64) bool {
	//uid 不存在或者值为空 不灰度
	uid, ok := req.Data["uid"]
	if !ok || uid == "" {
		return false
	}

	resultId, _ := strconv.ParseInt(uid, 10, 64)

	for _, value := range *itemRule {
		//判断是否在灰度范围内
		if resultId == value {
			return true
		}
	}
	return false
}

// 处理根据表的id白名单策略
func (g *GrayLogic) handleIdWhiteList(req GetGaryStatusReq, itemRule *[]int64) bool {
	//表的id字段
	whiteId, ok := req.Data["whiteId"]
	if !ok || whiteId == "" {
		return false
	}

	resultId, _ := strconv.ParseInt(whiteId, 10, 64)

	for _, value := range *itemRule {
		//判断是否在灰度范围内
		if resultId == value {
			return true
		}
	}
	return false
}

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • 专业术语
    • 策略:切换方式有很多种类,每一种为一种策略,有唯一词典标识,需程序员自行定义
    • 规则:一种策略下面有多个规则,规则之间定义为或的关系,即满足一条规则,则该灰度策略为true
    • 条件:一个规则由多个条件组成,条件之间为且的关系,条件都满足,则该条规则满足
    • 示列:定义一种策略标识【myteststrategy】,该策略下有两个规则,一是对用户uid取10000的余数,落在【1-5000】范围内,则该规则满足灰度策略,一是指定uid在白名单范围内,则该条规则满足灰度条件,以上任意一条规则满足条件,则该灰度策略满足。
  • 测试示例
    • 以下添加一种灰度策略,输入值进行检测
    • 策略支持规则条件核心代码
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档