在生产环境,进行新旧流量的切换时,需要按一定规则逐步迁移,防止一次性迁移如果出现问题导致后端服务奔溃影响用户,比如按用户id切换访问、按设备标识进行逐步升级等等
注意:一个策略下,多个规则之间是或,每个规则中条件之间是且
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)
}
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 删除。