什么是灰度发布?灰度发布几种类型

Posted Jet相信过程

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了什么是灰度发布?灰度发布几种类型相关的知识,希望对你有一定的参考价值。

一、方案背景介绍

1. 什么是灰度发布

灰度发布是指在 黑和白(0和1)之间,能够平滑过渡的一种发布方式。

AB test就是一种灰度发布方式,指为产品已发布A版本,在发布B版本时,在同一时间维度,
让一部分用户继续用A版本,一部分用户开始用B版本,如果用户对B版本没有什么反对意见,那么逐步扩大范围,把所有用户都迁移到B版本上面来。灰度发布可以保证整体系统的稳定,在初始灰度发布时就可以发现及调整问题,以保证其影响度。

1.2 灰度发布好处

  • 降低发布影响面: 就算出问题,也只会影响部分用户,从而可以提前发现新版本中的 bug,然后在下一次发布前提前修复,避免影响更多用户;
  • 提升用户体验: 除了能发现 bug,还能很好的收集新版本的用户使用反馈,从而提前调整系统,提升用户体验,也能给后续的产品演进带来参考价值。
  • 可以做到不停机的热迁移,版本回滚便捷(速度快)

1.3 背景

针对我们当前现状,使用灰度发布的背景:
在目前app完成测试,准备上线发布时候,就需要运维支持了,其中的挑战点在于如何不影响当前在线业务的情况下来进行升级。
系统升级就会有风险,系统宕机风险,用户使用习惯改变而造成用户流失的风险,服务错误不可用等等风险。
利用灰度发布, 降低发布带来的影响,虽然功能都在test环境测过,但毕竟没发布到prod环境,如果先让少部分用户先使用新版本,提前发现bug,或者性能问题,提前做好修复,就可以降低新版本带来的影响。
其主要思想就是把影响集中到一个点,然后再发散到一个面,出现意外情况后很容易就回退,即使影响也是可控的。

二、灰度发布几种类型

灰度发布的主要分类:

  • 金丝雀发布
  • 滚动发布
  • 蓝绿发布

1)金丝雀发布
金丝雀发布成本较低,只需要一个实例即可降低新版本存在的风险,适合缺乏足够的发布工具研发能力及成长型的小公司。但是,金丝雀发布也有缺点,当升级全部剩余实例时,如果流量过多,可能会导致服务中断。

2)滚动发布
滚动发布则是在金丝雀发布的基础上进行的改进和优化,第一次也是使用金丝雀发布,后续则使用多批次的形式发布剩余实例,每次批次之间会进行观察,如果有问题,再进行回滚。

3)蓝绿发布

蓝绿发布比较简单,只是对全量发布的一种优化而已,发布前不用全部停机,而是另外部署新版本全部实例,然后再把流量全部再切换到新版本。

三、选型

全量发布:不建议使用

蓝绿发布:适合于对于资源预算比较充足的业务,或者是比较简单的单体应用,可以快速实现系统的整体变更

金丝雀和全链路灰度:适合需要针对特定用户或者人群进行现网请求验证的业务,可以显著减低风险

综上,建议选择 金丝雀或者全链路灰度 进行服务的升级发布。

四、灰度发布流程及实现思路

用户请求————> 网关----->服务a----->服务b
1、用户会发送请求
2、经过网关分发请求到具体的服务A
3、服务A 调用服务B
[图片]
灰度发布的核心就是路由转发,如果我们能够自定义网关到 服务A 及 服务A到服务B中间的路由策略,就可以实现用户引流,灰度发布。

五、几种实现方案

1)SpringCloud+Redis 实现灰度发布

通过网关寻找下层服务之前,通过拦截器处理请求头的参数信息,通过判断Redis数据当前请求是否符合灰度的要求,如果符合,走灰度服务;否则走正常服务。

2)nginx +Lua + Redis 实现灰度发布

原理:使用nginx做负载均衡和反向代理,nginx内嵌lua模块,解析并执行lua编写的脚本逻辑,可以通过lua解析cookie以及访问redis,而一些灰度发布分流的策略就是放在redis里通过cookie关联

执行过程:

  • 当用户请求到达前段代理服务nginx,內嵌的lua模块解析nginx配置文件中的lua脚本代码
  • lua变量获取到客户端的ip地址,去查询redis缓存内是否有该建值,如果有返回值执行@client_test,否则执行@client
  • location @client_test把请求转发给灰度发布服务,location@client把请求转发给正常服务,服务器返回结果。

3)Openresty+Lua+Redis灰度发布

OpenResty 是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项,用于方便地搭建能够处理高并发、高扩展性的动态 Web 应用、Web 服务和动态网关。
Http协议的灰度功能主要基于ngx_http_lua_module模块实现,Tcp协议的灰度功能主要基于ngx_stream_lua_module模块实现。

本系统实现了对Http协议和Tcp协议的灰度功能,并且提供后台管理系统对灰度白名单进行管理。
三种方式灰度实现:
Http协议灰度实现如下功能:

  • 通过IP或者IP段验证客户端IP是否灰度白名单;
  • 通过获取header头中的userid,验证是否灰度白名单;
  • 通过获取header头中的device-id,验证是否灰度白名单。
    TCP协议灰度实现如下功能:
  • 通过IP或者IP段验证客户端IP是否灰度白名单;
    后台管理系统实现如下功能:
  • 支持对设备deviceId灰度白名单新增、删除和查询功能;
  • 支持对客户端IP灰度白名单新增、删除和查询功能;
  • 支持对用户userId灰度白名单新增、删除和查询功能,同时可以根据配置灰度策略,将满足条件的用户自动导入灰度白名单中。

说明:
1、当用户请求到达前端web(代理)服务器Openresty,内嵌的lua模块解析Nginx配置文件中的lua脚本代码;
2、Lua获取客户端IP地址、userId和设备ID,去查询Redis中是否有该键值,如果有则转发到灰度环境,否则转发到生产环境中;
3、后端管理系统提供可视化界面,可以管理redis中的白名单信息,包括:客户端IP白名单、userId白名单、设备Id白名单。

Vue 灰度发布新功能的那些事

前言

什么是灰度发布?百度百科的解释如下。

从上可以看出,灰度发布的主要作用有以下几点:

  1. 降低直接全量发布带来的影响,让少部分用户先使用新版本,如发现问题则及时做好修复,验证无重大问题则全量发布新功能
  2. 通过新老版本的数据对比,决定新版本是否需要全量发布

概述

灰度发布的方式有很多,按端可以区分服务端,客户端,Web前端都可以做,没有最好,只有更适合自己的业务场景。

如上可以看到常见的几种灰度发布的方式,都有各自的优缺点,由于我们公司有完善的大数据AB test方案,所以前端只需关注接口返回的字段标识,来做具体的页面加载逻辑,今天重点讲述在前端中使用Vue框架中如何做灰度发布。在Vue中主要可以分为以下两种情况:

组件级别:

  1. 组件级别动态控制只需后端回传对应方案标识即可。

页面级别:

  1. 前端页面访问地址不变,同后端人员约定好AB test 标记字段,前端根据字段返回不同的内容加载对应的页面。
  2. 新旧功能区分两个页面地址,跳转页面地址由后端控制,此方案前端不需要太多改动,此文就不多说明。

先来看看日常处理的方式,一个页面可能会存在多个地方判断AB test 逻辑,或者是更多的AB test同时进行,这样的页面代码逻辑复杂度相对比较高,也不够整洁易懂,当有新的AB test加入或者有AB test需要决策的时候,修改代码的成本较高,降低了代码维护的效率。

<template>
    ...
    <test-a v-if="testA" />
    <test-b v-else-if="testB" />
    ...
    <div v-if="testA">
        ...
    </div>
    <div v-else-if="testB">
        ...
    </div>
    ...
</template>
<script>
    ...
    if (testA) 
        ...     else if (testB) 
        ...
    
</script>

接下来就开始我们今天文章的正题,看看有哪些方式可以解决以上的问题。

组件级别

如只是简单的两个小组件功能的灰度则可以直接用 v-if 处理

<testA v-if="testA" />
<testB v-else />

如有多个功能同时测试,可以通过 Vue 的<component>元素加一个特殊的 isattribute 来实现,currentTabComponent 可基于接口获取或其他前端计算得出。

<component :is="currentTabComponent"></component>

页面级别

方案一 新增入口页面分发

新增入口页面,将新旧版本页面升级为组件的方式引入,入口页面增加接口查询,通过 v-if 或通过 Vue 的&lt;component&gt; 元素加一个特殊的 is 属性来加载页面组件。如下是通过接口查询代码示例,通过接口前置查询会带来一定的界面延迟加载,取决于接口的响应速度,我们也可以通过在URL增加参数获取,这时的URL由后端拼接好参数再返回,这样就可以避免一次接口查询。

<template>
   <component :is="testId"></component>
</template>
<script>
  import IndexA from ./index-a
  import IndexB from ./index-b
  import 
    getTestID
   from @/api/getTestID

  export default 
    name: index,
    components: 
      index-a: IndexA,
      index-b: IndexB
    ,
    data() 
      return 
        testId: 
      
    ,
    created() 
      this.getTestID()
    ,
    methods: 
      async getTestID() 
        const  testId  = await getTestID(
          xxx: xxx
        )
        this.testId = testId
      
    
  
</script>

这里直接这样加载页面级组件会导致此文件体积加大,可以将页面组件的加载方式改为异步组件,提升页面加载速度。

components: 
    index-a: () => import(/* webpackChunkName: "index-a" */ ./index-a),
    index-b: () => import(/* webpackChunkName: "index-b" */ ./index-b)

方案二 高阶组件方案

在路由配置中从接口获取灰度标识数据,进行路由分发。如果不想额外增加接口查询的开销,也可以将标识数据从URL参数返回,此方式需要提前拼接好参数。

高阶组件的好处是所有需要灰度的加载逻辑都在路由配置文件中,统一维护,组件也可复用,不需要每个需要灰度的页面都增加一个入口文件。

组件代码

<template>
  <component :is="com" />
</template>
<script>
export default 
  name: DynamicLoadComponent,
  props: 
    renderComponent: 
      type: Promise
    
  ,
  data() 
    return 
      com: () => this.renderComponent
    
  
;
</script>

router.js 配置


  path: originPath,
  component: () => import(@/views/components/DynamicLoadComponent),
  name: originPath,
  props: (route) => (
    renderComponent: new Promise((resolve, reject) => 
      // 根据 route 拼接参数获取加载页面
      if (route.query.testA) 
          resolve(import(@/views/testA));
       else 
          resolve(import(@/views/testB));
      
      // OR 根据接口返回标识动态加载页面
      getAPIData()
        .then((response) => 
          if (response.testA) 
            resolve(import(@/views/testA));
           else 
            resolve(import(@/views/testB));
          
        )
        .catch(reject);
    ),
  )

方案三 动态Router.js引入

如果是有大面积的页面替换,可采用这种方式。例如,后端开发语言更换导致接口地址及返回的字段内容都发生变化,这样会有一段时间的过渡使用,开发完一个页面上线一个页面,就可能会有5个页面使用新的方案,5个页面还是保留原始方案的情况。

改造router.js,将原始路由配置抽离到default.js中,再新建java.js将新方案路由配置写入,基于前端计算或接口返回标识动态加载路由配置文件。

import Vue from vue
import Router from vue-router
import  isHitJavaAPI  from @/config

Vue.use(Router)

const router = new Router(
  mode: history
)

const computedRouterDirectory = (routeFile) => 
   let routerConfig;
   const requireRouter = require.context(., false, /\\.js$/);
   routerConfig = requireRouter.keys().filter(file => file === `./$routeFile.js`)[0];
   if (routerConfig) 
     routerConfig = requireRouter(routerConfig)
     routerConfig.default && router.addRoutes(routerConfig.default);
  


if (isHitJavaAPI()) 
   computedRouterDirectory(java)
  else 
   computedRouterDirectory(default)

isHitJavaAPI方法中是命中灰度的逻辑,如果这里是前端做灰度,可基于deviceID或UA等计算。如果这里是调用接口获取方案则需改为同步调用。

总结

本文主要介绍了页面级别的几个灰度方案,每个方案的试用场景都有各自的优缺点,如新增入口文件,主要是针对页面变动较大且当前项目只会有一个在进行中的灰度测试;高阶组件适用于当前项目有多个进行中的灰度测试,则可复用组件;动态加载路由配置文件主要针对于当前项目有大规模的页面UI或逻辑更换灰度测试;通过以上几种方案都可极大的提升代码的可维护性以及解耦灰度逻辑和业务代码逻辑,当灰度测试没有问题需全量上线时,我们只需修改入口逻辑即可,无需在业务代码中去逐个修改灰度逻辑。

除开本文所介绍的几种方式,也还有其他的加载方式,如路由钩子函数拦截后做动态跳转,或者请求到后端,后端做重定向处理等。每个方式都有各自的优缺点,就看是不是你当前场景最合适的方案。如果你有其他的方案,欢迎留言和我们交流~

参考

Vue Router根据后台数据加载不同的组件
components-dynamic-async

以上是关于什么是灰度发布?灰度发布几种类型的主要内容,如果未能解决你的问题,请参考以下文章

什么是灰度发布,以及灰度发布A/B测试

Dubbo路由功能实现灰度发布及源码分析

什么是灰度发布,以及灰度发布A/B测试

几种灰度发布方案

几种灰度发布方案

Istio灰度发布 --《云原生服务网格Istio》书摘03