项目背景
在生产环境中,我们为了验证新版本的发布,一般会将部分新版本的服务交付生产环境,让它们与上个版本的服务共同为上层请求方服务。此时由于还未经过生产验证,所以可以通过部分引流将请求拿过来消费,如下图
- 普通用户
Pro链路
- 灰度测试用户
Gray+Pro链路
- QA
Pre+Pro链路rpc流量染色
如下图,我们在k8s中部署了srv三个实例,两个当前正服务的实例srv-old(v0.0.1),此时我们想发布一个灰度实例(v0.0.2)
我们可以在api这个上级应用的配置中心里添加热配置,如下:gray.switch = true #灰度发布开关gray.service = srv #灰度发布的服务gray.version = v0.0.2gray.option = mod #操作 对某个值取模mod , 或 判断包含gray.target = 100 #option为mod时:target标示mod基数,比如*mod10 ,option为contain时 ,target标示[pre,endWith,equal]gray.key = userId #分流的关键字段gray.result = 1,2,3 #操作对比的结果集
micro客户端插件实现:
//灰度发布-流量染色
type grayscaleWrapper struct {
client.Client
}
func (w *grayscaleWrapper) Call(ctx context.Context, req client.Request, rsp interface{}, opts ...client.CallOption) error {
//判断当前是否是需要灰度发布的请求
if apollo.C().GetByDefaultNamespace("gray.switch") == "true" && apollo.C().GetByDefaultNamespace("gray.service") == req.Service() {
nOpts := append(opts, client.WithSelectOption(
selector.WithFilter(w.filterChain(req)),
))
return w.Client.Call(ctx, req, rsp, nOpts...)
}
return w.Client.Call(ctx, req, rsp, opts...)
}
func (w *grayscaleWrapper) filterChain(req client.Request) selector.Filter {
return func(old []*registry.Service) []*registry.Service {
defer func() {
if r := recover(); r != nil {
log.Error(r)
return
}
}()
var services []*registry.Service
switch apollo.C().GetByDefaultNamespace("gray.option") {
case "mod":
{
key := apollo.C().GetByDefaultNamespace("gray.key")
if len(key) == 0 {
return old
}
gTarget := apollo.C().GetByDefaultNamespace("gray.target")
if len(gTarget) == 0 {
return old
}
gTargetInt, err := strconv.Atoi(gTarget)
if err != nil {
log.Error(err)
return old
}
value, ok := req.Body().(map[string]interface{})[key]
if !ok {
log.Error("gray.key : " + key + " non-existent")
return old
}
vInt, err := strconv.Atoi(fmt.Sprintf("%d", value))
if err != nil {
log.Error(err)
return old
}
t := vInt % gTargetInt
//判断是否满足条件
gResult := apollo.C().GetByDefaultNamespace("gray.result")
if len(gResult) == 0 {
return old
}
gResultArr := strings.Split(gResult, ",")
tV := strconv.Itoa(t)
ok = false
for _, v := range gResultArr {
if tV == v {
ok = true
break
}
}
if !ok {
return old
}
//挑选灰度版本
gVersion := apollo.C().GetByDefaultNamespace("gray.version")
for _, srv := range old {
if srv.Version == gVersion {
services = append(services, srv)
}
}
}
break
case "contain":
{
key := apollo.C().GetByDefaultNamespace("gray.key")
if len(key) == 0 {
return old
}
gTarget := apollo.C().GetByDefaultNamespace("gray.target") //pre endWith equal
if len(gTarget) == 0 {
return old
}
vPara, ok := req.Body().(map[string]interface{})[key]
if !ok {
log.Error("gray.key : " + key + " non-existent")
return old
}
value:=fmt.Sprintf("%s",vPara)
//判断是否满足条件
gResult := apollo.C().GetByDefaultNamespace("gray.result")
if len(gResult) == 0 {
return old
}
gResultArr := strings.Split(gResult, ",")
ok = false
switch gTarget {
case "pre":
{
for _, v := range gResultArr {
if strings.HasPrefix(value,v) {
ok = true
break
}
}
}
break
case "endWith":
{
for _, v := range gResultArr {
if strings.HasSuffix(value,v) {
ok = true
break
}
}
}
break
default:
{
for _, v := range gResultArr {
if value==v {
ok = true
break
}
}
}
}
if !ok {
return old
}
//挑选灰度版本
gVersion := apollo.C().GetByDefaultNamespace("gray.version")
for _, srv := range old {
if srv.Version == gVersion {
services = append(services, srv)
}
}
}
break
default:
return old
}
if len(services) == 0 {
return old
}
return services
}
}
func NewClientWrapper() client.Wrapper {
return func(c client.Client) client.Client {
return &grayscaleWrapper{
Client: c,
}
}
}
客户端api添加
