精华内容
下载资源
问答
  • 整合nacos(Euraka 类似)实现灰度发布 在一般情况下,升级服务器端应用,需要将应用源码或程序包上传到服务器,然后停止掉老版本服务,再启动新版本。但是这种简单的发布方式存在两个问题,一方面,在新版本升级...
  • 主要介绍了Nginx与Lua灰度发布的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
  • dubbo重写loadbalance实现灰度发布demo 项目背景 由于项目中使用的是dubbo,2B多租户系统的业务中,客户需要分多版本的需求,以及dubbo服务部署需要灰度的小白鼠,在此背景下故需要dubbo服务的灰度发布机制,而dubbo...
  • 主要介绍了Nginx 实现灰度发布的三种方法总结的相关资料,需要的朋友可以参考下
  • 《基于Spring Cloud 的灰度发布实践 》
  • 此种发布方式,按照中国特色的叫法被冠以“灰度发布”、“灰度放量”、“分流发布”。 关于“灰度发布”叫法的来源无从考察。只不过按照中国传统哲学的说法来看,很符合中国人中庸的思维模式:自然界所有的事物总是...
  • 灰度发布经验分享

    2018-07-04 14:32:10
    灰度发布(又名金丝雀发布)是指在黑与白之间,能够平滑过渡的一种发布方式。在其上可以进行A/B testing,即让一部分用户继续用产品特性A,一部分用户开始用产品特性B,如果用户对B没有什么反对意见,那么逐步扩大...
  • 在互联网产品的发布过程中也较多采用此种发布方式:产品的发布过程不是一蹴而就,而是逐步扩大使用用户的范围,从公司内部用户-...此种发布方式,按照中国特色的叫法被冠 以”灰度发布“、”灰度放量“、”分流发布“。
  • 灰度发布

    千次阅读 2017-05-25 11:16:02
    1. 前言  互联网产品有一个特点,就是不停的...为了避免这些风险,很多产品都采用了灰度发布的策略,其主要思想就是把影响集中到一个点,然后再发散到一个面,出现意外情况后很容易就回退。 很长时间,我们都一


    1. 前言

     互联网产品有一个特点,就是不停的升级,升级,再升级。一般采用敏捷开发的团队,基本上保持每周一次的发布频率,系统升级总是伴随着风险,新旧版本兼容的风险,用户使用习惯突然改变而造成用户流失的风险,系统down机的风险.....为了避免这些风险,很多产品都采用了灰度发布的策略,其主要思想就是把影响集中到一个点,然后再发散到一个面,出现意外情况后很容易就回退。

    很长时间,我们都一直在改进搜索引擎的排序算法,尽量让最好的商品出现在 搜索结果的第一屏。我们尝试了很多种算法,不断调整各个排序因子所占的比重。但是我们无法确信我们的排序结果能满足所有用户的需求。所以我们采用了灰度发 布,选取几个一级商品类目,在其中应用不同的排序算法,比如在女装类目中,我们把卖家信用所占的比率调整到60%,在珠宝类目中,我们把销售量所占的比率 调整到60%.. 然后发布出去,收集用户反馈,最终选择一种大部分人认为好的算法。

         在传统软件产品发布过程中(例如微软的Windows 7的发布过程中),一般都会经历Pre-Alpha、Alpha、Beta、Release candidate(RC)、RTM、General availability or General Acceptance (GA)等几个阶段(参考Software release life cycle)。可以看出传统软件的发布阶段是从公司内部->外部小范围测试>外部大范围测试->正式发布,涉及的用户数也是逐步放量的过程。

       在互联网产品的发布过程中也较多采用此种发布方式:产品的发布过程不是一蹴而就,而是逐步扩大使用用户的范围,从公司内部用户->忠诚度较高的种子 用户->更大范围的活跃用户->所有用户。在此过程中,产品团队根据用户的反馈及时完善产品相关功能。此种发布方式,按照中国特色的叫法被冠 以”灰度发布“、”灰度放量“、”分流发布“。

      关于“灰度发布”叫法的来源无从考察。只不过按照中国传统哲学的说法来看,很符合中国人中庸的思维模式:自然界所有的事物总是以对称、互补、和谐的形式存 在,例如黑与白、阴与阳、正与负、福与祸。在二元对立的元素间存在相互过渡的阶段,所谓”祸兮福所倚,福兮祸所伏“。具体到黑与白,在非黑即白中间还有中 间色——灰色。于是出现了很多关于灰色的说法:灰盒测试,灰色管理(极力推荐 任正非:管理的灰度),灰色收入,灰色地带等等。因此对于灰度发布实际上就是从不发布,然后逐渐过渡到正式发布的一个过程。

    2. 灰度发布定义

    灰度发布是指在黑与白之间,能够平滑过渡的一种发布方式。AB test就是一种灰度发布方式,让一部分用户继续用A,一部分用户开始用B,如果用户对B没有什么反对意见,那么逐步扩大范围,把所有用户都迁移到B上面 来。灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以发现、调整问题,以保证其影响度。

    3. 灰度发布作用

    a.及早获得用户的意见反馈,完善产品功能,提升产品质量

    b.让用户参与产品测试,加强与用户互动

    c.降低产品升级所影响的用户范围

    d.规避一定的发布风险

    e.避免停服发布给用户带来不便

    f.具有容灾能力

    4. 灰度发布步骤

      1)、定义目标

      2)、选定策略:包括用户规模、发布频率、功能覆盖度、回滚策略、运营策略、新旧系统部署策略等

      3)、筛选用户:包括用户特征、用户数量、用户常用功能、用户范围等

      4)、部署系统:部署新系统、部署用户行为分析系统(web analytics)、设定分流规则、运营数据分析、分流规则微调

      5)、发布总结:用户行为分析报告、用户问卷调查、社会化媒体意见收集、形成产品功能改进列表

      6)、产品完善

      7)、新一轮灰度发布或完整发布

     

    5. 灰度发布测试方法

         灰度发布于互联网公司常用A/B测试似乎比较类似,老外似乎并没有所谓的灰度发布的概念。按照wikipedia中对A/B测试的定义,A/B测试又叫:A/B/N Testing、Multivariate Testing,因此本质上灰度测试可以算作A/B测试的一种特例。只不过为了术语上不至于等同搞混淆,谈谈自己理解的两者的差异。

        灰度发布是对某一产品的发布逐步扩大使用群体范围,也叫灰度放量

        A/B测试重点是在几种方案中选择最优方案

       关于A/B测试可以参考这篇文章:A/B测试终极指南

     

    6. 灰度发布引擎

         对于一般的小系统并不需要单独的灰度发布引擎,可以参考A/B测试中做法,在页面JavaScript或服务器端实现分流的规则即可。但对于大型的互联网应用而言,单独的用于管理用户分流的发布引擎就很有必要了。“钱掌柜”分流发布模式 提到了原来阿里软件所使用的灰度发布引擎,设计思路具有普遍性,可以供参考


    下面是一个灰度发布的架构示意图:

     

    7. 灰度发布常见问题

    7.1. 以偏概全

    7.1.1. 问题特征:

    a选择的样本不具有代表性;

    b样本具有代表性,但选择样本用户使用习惯并没有涵盖所有核心功能

    7.1.2. 解决方案:

    样本选择要多样化,样本的组合涵盖大部分核心功能

    7.2. 知识的诅咒

    “知识的诅咒”的说法来自《粘住》中实验,具体可以自己搜索一下。我们自己对于自己开发的产品极为熟悉,于是乎想当然认为用户也应当能够理解产品的设计思路、产品的功能使用。

    7.2.1. 问题特征:

    a结果没有量化手段;

    b只依赖于用户问卷调查;

    c没有web analytics系统;

    d运营数据不全面,只有核心业务指标(例如交易量),没有用户体验指标

    e对结果分析,只选择对发布有利的信息,对其他视而不见

    7.2.2. 解决方案:

    a产品设计考虑产品量化指标

    b结果分析依据量化指标而不是感觉

    7.3. 发布没有回头路可走

    7.3.1. 问题特征:

    a新旧系统用户使用习惯差异太大,没有兼容原有功能

    b新旧系统由于功能差异太大,无法并行运行,只能强制升级

    c新系统只是实现了旧系统部分功能,用户要完整使用所有功能,要在 在新旧系统切换

    d新旧系统数据库数据结构差异太大,无法并行运行

    7.3.2. 解决方案:

        前期产品策划重点考虑这些问题,包括:回滚方案、 新旧系统兼容方案、用户体验的一致性、用户使用习惯的延续性、新旧系统数据模型兼容性

    7.4. 用户参与度不够

    7.4.1. 问题特征:

    a指望用户自己去挖掘所有功能。对于一个产品,大部分用户经常只使用部分功能,用户大部分也很懒惰,不会主动去挖掘产品功能

    b互动渠道单一

    c陷入“知识的诅咒”,不尊重参与用户意见

    7.4.2. 解决方案:

    a善待吃螃蟹的样本用户,包括给予参与测试的用户小奖励(例如MS给参与Win7测试用户正版License)、给用户冠以title

    b通过邮件、论坛、社区、Blog、Twitter等新媒体与用户形成互动

    c提供产品功能向导。在hotmail最近的升级后的功能tip,gmail的tip都有类似的产品功能导向。在产品中会提示类似于:你知道吗,xx还提供xx功能,通过它你可以xx 。

    8. 让产品具备灰度发布能力

    8.1. 灰度机制的七个维度

    8.1.1. 需求度

    用户需求是产品核心,产品对需求的体现程度,就是企业被生态所需要的程度;

    8.1.2. 速度

    快速实现单点突破,角度、锐度尤其是速度,是产品在生态中存在发展的根本;

    8.1.3. 灵活度

    敏捷企业、快速迭代产品的关键是主动变化,主动变化比应变能力更重要;

    8.1.4. 冗余度

    容忍失败,允许适度浪费,鼓励内部竞争内部试错,不尝试失败就没有成功;

    8.1.5. 开放协作度

    最大程度地扩展协作,互联网很多恶性竞争都可以转向协作型创新;

    8.1.6. 进化度

    构建生物型组织,让企业组织本身在无控过程中拥有自进化、自组织能力;

    8.1.7. 创新度

    创新并非刻意为之,而是充满可能性、多样性的生物型组织的必然产物。

     

    8.2. 灰度发布的策略要素

    8.2.1. 易于发布到云平台

        一般采用灰度发布都是具有自主产品的平台模式发布,而不是在客户服务器端进行发布,具备自主研发产品和有一定硬件部署能力的企业可以考虑灰度发布。

    灰度发布一般是基于云的需要,如负载均衡,用户隔离等机制。如大型的电商网站等都是采用的分布式部署方式,利用负载均衡实现服务器分发,将用户访问分配到不同的地区服务器访问,确保用户访问效率,提升用户体验。

    之所以强调易于发布,就是公司要具备自己可操作的服务器设备(云服务设备),这样可以实现在用户不知情的情况下实现灰度发布。即,在用户无感知的情况下实现最优配置的测试部署,提升产品质量,实现产品快速迭代——频繁发布,实现具有意义的‘实时发布’策略。

    注:需要开通云服务模式(有一定硬件和经济实力的公司可以考虑)。

     

    8.2.2. 设置用户标识策略

    用于区分用户,辅助数据统计,保证灰度发布过程中用户体验的连贯性(避免用户在新旧版本中跳变,匿名Web应用比较容易有这个问题)。匿名Web应用可采用IP、Cookie等,需登录的应用可直接采用应用的帐号体系。

    8.2.3. 目标用户选取策略

    即选取哪些用户先行体验新版本,是强制升级还是让用户自主选择等。可考虑的因素很多,包括但不限于地理位置、用户终端特性(如分辨率、性能)、用户自身特点(性别、年龄、忠诚度等)。对于细微修改(如文案、少量控件位置调整)可直接强制升级,对于类似新浪微博改版这样的大型升级,应让用户自主选择,最好能够提供让用户自主回滚至旧版本的渠道。

    对于客户端应用,可以考虑类似Chrome的多channel升级策略,让用户自主选择采用stable、beta、unstable channel的版本。在用户有明确预期的情况下自行承担试用风险。

    8.2.4. 提供数据反馈入口

         用户数据反馈:在得到用户允许的前提下,收集用户的使用新版本应用的情况。如客户端性能、客户端稳定性、使用次数、使用频率等。用于与旧版本进行对比,决策后续是继续扩大新版本投放范围还是回滚。
         服务端数据反馈:新版本服务端性能、服务端稳定性等,作用与用户数据反馈类似。

    8.2.5. 新版本回滚策略

    当新版本灰度发布表现不佳时,应回滚至旧版本。对于纯粹的Web应用而言,回滚相对简单。主要难点在于用户数据的无缝切换。对于客户端应用,如果期待用户自行卸载新版本另行安装旧版本,成本和流失率都太高。可以考虑通过快速另行发布新版本,利用升级来“回滚”,覆盖上次灰度发布的修改。

    对于移动客户端,新版本发布成本较高,需要Appstore、Market审核。本人没有移动客户端产品的经验,不太确定移动客户端产品如何处理灰度发布及回滚。但尽量将客户端打造成Web App,会更有利于升级和回滚。(不过苹果对纯Web App类的App有较强的限制,好像已经不允许在Appstore上发布这类应用了?)

    8.2.6. 新版本公关运营支持

    对于改版级别的大型升级,需要配合公关运营支持,用于及时处理用户在微博、博客等渠道给出的“显式反馈”。对比通过隐式数据反馈得到的结论后,综合考虑应对策略。

    8.3. 灰度发布的方案

    灰度发布一般有三种方式 nginx+lua,nginx根据cookie分流,nginx 根据权重来分配:
         nginx+lua根据来访者ip地址区分,由于公司出口是一个ip地址,会出现访问网站要么都是老版,要么都是新版,采用这种方式并不适合nginx 根据权重来分配,实现很简单,也可以尝试nginx根据cookie分流,灰度发布基于用户才更合理。

     

    Nginx+lua配置可以参考如下文章进行实践:

    利用nginx+lua+memcache实现灰度发布

     

    Nginx+Lua+Redis实例

     

    nginx灰度方案---基于ip或者基于cookies

     

    8.3.1. 方案一:代码逻辑控制

    实现:

    在代码中埋开关,做if-else判断,对于需要灰度的机器,设置开关为on,否则为off。每次版本发布都是有两个版本。

    优点

    · 快速回滚,不需要重新发布和重启系统。

    缺点

    · 对代码有倾入性。

    · 分支逻辑,带来复杂性。

    这种方式笔者曾经应用过,就是在阿里的时候把商品的数据库从Oracle切换到MySQL,使用了一个状态变量进行控制。从而打到平滑迁移的效果。

     

    8.3.2. 方案二:Alibaba预发机制

        其实这个不是真正意义上的灰度。因为这个预先发布机器是内部IP,没有对外服务的。需要绑定域名进行验证。但是数据是完全的线上。所以本质上是灰度 某些特定用户(可以访问灰度机器的用户,内部测试用户)的一种简单做法。其实API这边也有类似的做法,就是我们的Gamma环境,而且我们还提供了 Gamma机器的域名,方便外部合作用户配合测试。

    优点

    · 简单

    缺点

    · 浪费一台机器(这个可以预先发布完成之后投入正式环境,预发布的时候从nginx摘除,不过需要运维支持。)

    · 不够灵活

    · 只能针对接入层机器,IDL服务灰度需要另外考虑。

    8.3.3. 方案三:SET部署

    8.3.3.1. 按照业务隔离部署

        比如现在API Container的做法,部署的粒度可以到API级别,前端根据nginx进行转发。比如:

    · 微购物 API Container: api.weigou.qq.com

    · 拍拍 API Container:api.paipai.com

    · 易迅 API Container: api.yixun.com

    · 网购 API Container:api.buy.qq.com

        上面是大业务级别的隔离部署。还可以进一步细化到模块级别,比如虚拟服务电商的API,是挂在拍拍下面的一个子业务模块,但是由于他们接入微信之 后,访问量大增,为了避免影响拍拍其他业务,也为了避免受其他业务影响,API这里是给他们单独部署了两台机器,nginx配置一下就可以将针对虚拟的 API访问引流过来了:

    虚拟API Container:http://api.paipai.com/v2/virbiz

    这样,我们在发布一个版本的时候,可以先选择业务量最小的易迅进行发布,观察没有问题再全量其他平台。

    8.3.3.2. 按照用户隔离部署

        这个对于开放平台来说不是很适合,不过对于SNS这种应用场景就很合适了。比如QQ系统,按照用户号码段分为若干个set,每个set包含连续1亿 个号码的用户。假设现在最新的QQ号码接近10亿,则总共有10个set(Set 1到Set 10)。这样每次可以选择其中一个SET进行发布,而且高位QQ往往是不是很重要的用户,所以会先发布SET10。

    优点

    · 隔离部署,各个业务线影响最小。自动支持灰度发布。

    缺点

    · 灰度的粒度取决于隔离部署的粒度,一般会偏大。

    · 相对于集中部署比较浪费机器。

    · 各个业务线版本可能不一致,不利于统一管理。

    · 有一定的实现和部署成本

     

     

    8.3.4. 方案四:动态路由

       采用一个可以灵活配置的灰度策略,影响Load Balance的行为,让其根据灰度策略,返回灰度服务的IP和端口。

    适合与后台IDL的服务灰度。

    优点

    · 灵活,可控。

    缺点

    · 现在的配置中心和L5本身没有考虑指定路由策略,且不具有扩展性,需要在其外边开发。

    · API的元数据来源比较分散,目前 API和IDL元数据,API等级和频率限制 分布在不同的数据源,现在需要增加一个 灰度路由 数据源。

     

     

    9. 采用灰度发布的案例

    9.1. 谷歌Gmail Labs

    Gmail Labs是一个新特性橱窗,用户可以自己选择一些未正式发布的新特性进行体验,不喜欢可以关闭,在这个过程中,吃了螃蟹,也当了Google的小白鼠。

    这个做法比传统的灰度要高明很多,更加尊重用户:

    1、它没有强加用户,用户是否愿意当小白鼠完全自愿

    2、新特性不是打包在一起的一个大版本,可以选择某几个喜欢的螃蟹尝尝

    3、螃蟹不好吃可以扔掉,不用硬吃进肚子里引发肠胃炎

    当然这些好处也是有代价的:

    1、要开发一个labs平台实现新特性上架、独立尝试的功能,这可能要改动Gmail的前后台架构

    2、新特性要按照一定规范来写,才能发布到这个平台上,可能会增加一些工作量

    3、小白鼠用户增多之后,对系统的压力可能会有一定提升,因为每一位用户调用的界面都不一样了

    既然Gmail Labs能够顺利发布,那么说明对Google来说,以上这些问题都不算问题。另外,现在展示的新特性,都注明了开发者的名字,那么,Gmail Labs可能会开放这个平台让外部开发者也能提交特性?这倒是很open的一种开发模式,非常适合Google的web app产品线。

    9.2. 腾讯QZone

    QZone是另外一个采用灰度发布的例子。大家都知道,QZone的改进是巨大的,从以前慢悠悠的老爷爷变成了一个充满青春活力的小伙子。其中经历了大小无数次的发布,他们的发布也都是采用了灰度发布的策略,用户数据的升级并不是大 面积的一次性升级,而是通过一个用户升级标志服务器,如果用户数据没有升级,后台会把此用户的数据逐步迁移到新版本上,然后将升级标志位置1,升级过程 中,用户仍然可以访问旧的数据,升级完成后的访问都将转发给新的版本。

    QQ的很多产品发布都采用灰度发布,有些是抽取部分QQ号段升级成新系统,然后根据用户反馈再大范围升级。

    9.3. 微信wechat

    灰度、灰度、再灰度

        在变更后的部署方式上,微信在一些规则会限定不能一次把所有的逻辑变更上去,每一次变更一小点观察到每一个环节没有问题的时候,才能布局到全网上去。微信后台每一天可以支撑超过20个后台变更,在业界来说,通常做到5个已经是比较快了,但是微信可以做到快4倍。


    腾讯内部的上线系统

    而所谓灰度发布,是指在黑与白之间,能够平滑过渡的一种发布方式。AB test就是一种灰度发布方式,让一部用户继续用A,一部分用户开始用B,如果用户对B没有什么反对意见,那么逐步扩大范围,把所有用户都迁移到B上面 来。灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以发现、调整问题,以保证其影响度。(在腾讯,灰度发布是最常采用的发布方式之一)

    孙子兵法:古之所谓善战者,胜于易胜者也

    常识上,解决一个复杂问题的时候,会用高明的技巧解决复杂的问题,这个不是微信团队的目标,他们追求的要做到让所有问题很自然和简单的方式解决掉。在周颢看来,微信架构的技术复杂点在四个要点:协议、容灾、轻重、监控。

    微信架构

    · 协议。手机终端跟后台服务器之间的交互协议,这个协议的设计是整个系统的骨架,在这一点做好设计可以使得系统的复杂度大大降低。

    · 容灾。当系统出现了若干服务器或若干支架(宕机的时候),仍然需要让系统尽可能的提供正常的服务。

    · 轻重。如何在系统架构中分布功能,在哪一个点实现哪一个功能,代表系统中间的功能配置。

    · 监控。为系统提供一个智能仪表盘。

    在协议设计上,移动互联网和常规互联网有很大的区别。首先有CMWAP和CMNET的不同,在中国现在有相当多的手机用户使用WMWAP连接,还有 就是在线和离线的概念,当QQ下线的时候叫离线,当你登录的时候叫在线。但是在移动互联网这两个概念比较模糊。从微信的设计中,不管在线还是离线系统表现 都应该是一致的。还有一个是连接不稳定的问题,由于手机信号强弱的变化,当时信号很好,5秒钟走到信号不好的地区,连接就必须断掉。这个中间带来不稳定的 因素为协议设计带来较大困难。此外就是资费敏感的问题,因为移动互联网是按照流量计费的,这个计费会使得在协议设计中如何最小化传输的问题。最后就是高延 迟的问题。

    对此,业界标准的解决方案:Messaging And Presence Protocol:1)XMPP;2)SIP/SIMPLE。它的优点是简单,大量开源实现。而缺点同样明显:1)流量大:状态初始化;2)消息不可靠。

    微信在系统中做了特殊设计,叫SYNC协议,是参考Activesyec来实现的。特点首先是基于状态同步的协 议,假定说收发消息本身是状态同步的过程,假定终端和服务器状态已经被迟了,在服务器端收到最新的消息,当客户端、终端向服务器对接的时候,收取消息的过 程实际上可以简单的归纳为状态同步的过程,收消息以及收取你好友状态更新都是相同的。在这样的模式之下,我们会也许会把交互的模式统一化,只需要推送一个 消息到达的通知就可以了,终端收到这个通知就来做消息的同步。在这样的简化模式之下,安卓和塞班都可以得到统一。这样的系统本身的实现是更为复杂的,但是 获得很多额外的好处。

    让剩下系统实现的部分更加简单,简化了交互模式,状态同步可以通过状态同步的差值获得最小的数据变更,通过增量的传输得到最小的数据传输量。通过这 样的协议设计,微信可以确保消息是稳定到达的,而且是按序到达。引用一句俗话:比它炫的没它简单,比它简单的没它快,没谁比他更快,哪怕在GPRS下,微 信也能把进度条轻易推到底。

    9.4. Ucloud高可用架构实践

    DevOps成都站|架构与运维峰会活动总结地址:

    http://mp.weixin.qq.com/s?__biz=MjM5NDE0MjI4MA==&mid=2656298704&idx=2&sn=68d5d42a9c26640a21eebd3253ca81c3&scene=1&srcid=0519IBq6Q2k77kYAQmXuofuV&from=groupmessage&isappinstalled=0#wechat_redirect

    此处主要截取账户计费系统架构演进过程的六个阶段进行整理。

    服务架构的演进过程


    UCloud服务架构的演进主要经历了以下六个阶段:

    a.单体模式;

    b.具有灰度发布能力;

    c.前后端分离;

    d.服务化改造;

    e.按SET部署;

    f.分机房按SET部署,按SET进行跨机房热备容灾。

     

    1. 单体模式架构上线业务系统

    UCloud服务初期上线时的架构主要分三部分:

    · 

    PHP Web Conosle,负责所有前端展现交互、后台服务间逻辑组装;

    · 

    · 

    平台类服务,账户、计费、监控、名字服务等公共服务;

    · 

    · 

    各业务系统分数据中心后台服务的接入层。

    · 

    PHP Web Console、业务系统分数据中心的服务、平台类服务组合上线,Web Console 通过Protobuf与所有后端服务进行通信。

     

    2. 具备灰度发布能力

    要解决前面面临的问题,我们首先需要支持Web层灰度发布包含以下的灰度方式:

    · 

    无用户态特性按照 单IP -> IP段(地区) -> 到IP取模逐步灰度控制影响范围;

    · 

    · 

    有用户态特性按照 单内部用户(开发账号) -> 内部测试账号 -> 用户分级逐步灰度发布控制影响范围。

    · 

     

    3. 前后端分离

    · 

    开发API Gateway 层用来管理后端 API 注册和管理、权限验证管理、流量控制;

    · 

    · 

    开发API层,解决前台交互层,需要整合跨系统逻辑调用问题,前端只专注产品交互和用户体验;

    · 

    · 

    开发统一的单点登陆Token,系统方便前端实现跨域API调用让前端代码可以完全静态化。

    · 

    在此阶段,完成前端展现可以独立控制发布,彻底实现了前后端解耦,API协议保证向前兼容,Web端可以随意重构交互优化前端架构,实现了跨域独立部署,独立的灰度策略互相之间不受影响,极大的提高了前端团队开发效率和稳定性。

     

    4. 服务化改造

     对业务端API开发效率优化:

    · 

    按照业务模块化,所有业务API由后台产品研发部门独立部署发布上线;

    · 

    · 

    抽象通用平台类特性例如:子账号特性,权限体系,计费等特性抽象公共能力让业务端在API中组装。

    · 

    总体目标:让业务API开发效率提升并单独部署维护,提高产品特性的研发迭代效率并提高稳定性。

     

    5. 按SET部署


    基础架构优化完毕,各个业务系统单独部署发布,开始对系统进行容量和容灾方面的考虑,从部分平台类系统开始考虑按SET部署架构测底解决容量和容灾问题,每个SET只服务一部分用户,保证遇到物理服务器宕机等故障情况下只影响部分用户或业务。

    例如图上所示, SET 1 服务1 ~ 服务50000000 用户,SET 2 服务50000001 ~ 100000000 的用户,一个SET 出现问题只影响一个部分用户,不同的业务根据自身情况进行SET切分,规模大小也视情况而定,按SET部署后合理的划分方式下不同SET之间数据还可以互相迁移,来平衡搞负载或高容量的SET,极大的提高了可运维性。

     

    6. 分机房部署SET


    按SET部署架构改造完毕后还没有达到最理想的状态,如果所有服务部署在单机房还是可能会出现问题,机房整体出现断电、断网等故障还是会出现大面积影响。

    · 

    对SET架构进行分机房部署,让不同的用户运行在不同的机房中,这依赖一些基础设施比如跨机房光线专线。

    · 

    · 

    跨地域SET在相邻节点部署热备,以便出现机房故障时能具备异地快速恢复服务的能力。

    · 

    总体介绍了UCloud在不同的阶段架构演进的一些过程和经验,架构没有最好的,只有最合适当前业务发展的架构。

     

     

    10. 参考资料

    什么是灰度发布

     

    从腾讯的“灰度机制”到产品的“灰度上线”,你了解多少?

     

    “钱掌柜”分流发布模式

    百度百科:灰度发布

    A/B testing

    A/B测试终极指南

    互联网产品的灰度发布

    聊聊灰度发布

    一亿用户增长背后的架构秘密-腾讯微信技术总监周颢

     

    马化腾谈互联网产品:灰度法则的七个维度

    展开全文
  • 复杂系统灰度发布工程效率实践 汪洪恩_myslide.cn_.pdf
  • 灰度发布 技术实现 报告 可行性研究报告 很好的资源 采用lua+nginx方式
  • 基于apollo实现配置灰度发布

    万次阅读 2021-10-10 10:47:44
    在上一篇,通过dubbo的版本号控制,我们实现了一个服务的简单的灰度发布过程,在真实的项目环境中,灰度发布的应用场景是很多的,服务接口存在灰度的需求,本篇再介绍另一种比较常见的灰度需求场景,即配置文件的...

    前言

    在上一篇,通过dubbo的版本号控制,我们实现了一个服务的简单的灰度发布过程,在真实的项目环境中,灰度发布的应用场景是很多的,服务接口存在灰度的需求,本篇再介绍另一种比较常见的灰度需求场景,即配置文件的灰度发布

    配置文件简介

    如今,微服务架构盛行下,不管是互联网大厂,还是规模较小的团队,都有一套自己的中央配置文件管理中心,各个微服务模块都能接入中央配置文件,进行统一的使用,现如今,配置中心的技术选择也是非常丰富的,比如springcloud技术栈下的springcloud-config,springcloud-alibaba下自研的nacos,传统的配置中心disconf,携程出品的apollo等,都是不错的选择

    不管外观如何变化,他们的作用都是相同的,那就是统一管理微服务的配置,起到统一管理,统一维护,统一调度的作用

    在这种形势下,实际的业务中就出现了这么一种情况,配置管理中心可能存在针对不同环境,比如 dev,test,prod等环境各自的一套配置文件,但实际上,这还是无法满足某些场景下的要求的

    举例来说,像上一篇介绍的,服务接口需要做到灰度使用,利用不同的版本号进行控制,从而达到新功能和之前稳定版本功能做到动态切换的效果,而配置文件是控制程序中某些业务的关键信息,同样需要做到类似的功能,这种情况下该如何实现呢?

    在一些开源的配置管理技术框架中,它们为了满足这样的需求就实现了类似的灰度发布的功能,本篇将介绍如何利用apollo实现配置文件的灰度发布

    apollo简介与快速搭建

    关于apollo的介绍,网上有大量的参考和学习资料,主要想说的是,apollo是一款非常好用的配置管理工具,应用于各个大中小型互联网公司,可以无缝整合到很多java框架下,代码侵入性低,而配置文件修改后实时生效是它的一大亮点

    快速搭建过程

    本篇基于centos7或者阿里云

    1、安装java环境
    安装JDK1.8及以上(省略)

    2、安装mysql
    安装mysql5.7及以上(省略)

    3、github下载源码包
    下载Release版本:https://github.com/ctripcorp/apollo/releases
    在这里插入图片描述
    其主要的目录文件如上图所示,首先,在mysql中执行下sql目录下的两个sql脚本,apollo的运行依赖数据库
    在这里插入图片描述
    4、将解压后的文件上传至服务器

    修改demo.sh,主要修改连接数据库信息
    在这里插入图片描述
    修改内容如下,主要将数据库连接信息配置成自己的数据库IP即可
    在这里插入图片描述
    修改完毕后,启动apollo,只需要执行命令: sh demo.sh start 即可,等待服务启动完毕之后,浏览器访问:http://IP:8070,然后输入默认登录用户名和密码 apollo/admin 即可进入apollo主页面

    在这里插入图片描述
    关于apollo的具体使用本文不做过多赘述,有兴趣的同学可以深入学习

    5、apollo创建一个项目
    为了后文使用演示,这里快速创建一个app,和一个namespace,创建也非常简单,只需要点击 "创建应用"即可
    在这里插入图片描述
    默认是application应用,当然可以通过创建新的namespace的方式新建一个新的namespace,这个是为了各自的项目管理自身的配置文件进行使用,为了方便测试,这里我们在application应用下,创建了一个叫做dubbo的namespace,随机添加几个配置文件,即key/value的数据

    友情提醒:注意appId,工程中后面有使用

    以上的准备工作到此结束

    演示工程步骤

    提前准备一个springboot工程

    1、添加核心依赖

    主要包括springboot和apollo客户端连接依赖

     		<dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
                <version>2.2.1.RELEASE</version>
            </dependency>
    
            <dependency>
                <groupId>com.ctrip.framework.apollo</groupId>
                <artifactId>apollo-client</artifactId>
                <version>1.5.1</version>
            </dependency>
    

    2、配置文件

    server:
      port: 8001
    
    #连接apollo的配置
    app:
      id: provider
    apollo:
      meta: http://IP:8080
      bootstrap:
        enabled: true
        namespaces: dubbo
    

    3、apollo连接测试
    在工程中写一个接口,测试一下是否能从apollo上面读取到配置,测试读取一下下面这个配置
    在这里插入图片描述

        @Value("${uyun:default}")
        private String uyunname;
    
        @GetMapping("/getUyunName")
        public String getUyunName(){
            return uyunname;
        }
    

    启动项目,浏览器访问一下该接口,可以看到能成功访问到apollo,说明与apollo的整合完毕
    在这里插入图片描述

    如何解决本文开篇谈到的使用apollo实现配置文件的灰度发布呢?回到apollo,我们注意到这里有个“灰度”按钮
    在这里插入图片描述
    这是什么意思呢?简单理解就是,使用这个灰度按钮的功能,可以对当前的配置文件进行一份拷贝,类似备份,同时我们可以对灰度的配置进行添加,修改,删除等操作,而灰度文件的修改不会影响到主配置文件,下面我们对 "dubbo"这个配置文件做一下灰度的配置吧

    点击“灰度”
    在这里插入图片描述
    在这里插入图片描述
    可以看到,灰度创建的内容是对主配置文件的全量拷贝,当然灰度配置也需要像主配置文件那样进行发布之后才能使用的,点击发布,弹出一个需要我们设置灰度规则的框
    在这里插入图片描述
    在这里插入图片描述
    简单解释下,既然是灰度发布的配置文件,自然不能让所有的客户端访问到,这里apollo提供了几种配置规则方式,这里我选择了直接配置IP的方式,将本机的IP填进去,然后保存,再次发布即可

    这个规则的含义就是,当前dubbo的这个灰度配置文件,只能允许我的本机IP进行访问,接下来就让我们做一下验证吧

    4、灰度配置测试

    在工程中新增一个接口,同时给灰度配置文件中新增2个配置
    在这里插入图片描述

    	@Value("${name:default}")
        private String uname;
    
        @Value("${age:default}")
        private String age;
    
        @GetMapping("/getUyunName")
        public String getUyunName(){
            return uyunname;
        }
    

    将本机的其中一个项目启动,访问新增的这个接口,可以发现,能够成功访问到灰度文件中的配置信息
    在这里插入图片描述
    为了验证灰度的功能,我们将项目打成jar包,部署到服务器上,服务器的IP不在灰度配置列表中,理论上是无法访问到 age 这个新增的配置的

    在这里插入图片描述
    成功启动后,浏览器直接访问云服务器上面的这个接口,这时,可以发现,同样的接口,由于IP未配置在灰度规则中,将无法读取到age这个配置的值
    在这里插入图片描述

    在这里插入图片描述

    踩坑提醒
    如果使用的是阿里云或者其他云服务器部署apollo的时候,本地项目连接apollo的时候会一直报apollo的连接超时问题,这个问题的解决办法是,在启动的配置vm中,添加如下参数即可

    -Dapollo.configService=http://apollo部署的主机IP:8080
    

    在这里插入图片描述

    以上就是本篇的全部内容,希望对看到的小伙伴有用哦,最后感谢观看!

    展开全文
  • 互联网应用灰度发布建设实践.pdf
  • Spring Cloud 优雅下线以及灰度发布

    千次阅读 多人点赞 2020-10-20 10:43:55
    文章目录前言优雅下线常见的下线方式优雅的下线方式灰度发布蓝绿部署滚动部署金丝雀部署 前言 在生产环境中,如何保证在服务升级的时候,不影响用户的体验,这个是一个非常重要的问题。如果在我们升级服务的时候,会...

    前言

    在生产环境中,如何保证在服务升级的时候,不影响用户的体验,这个是一个非常重要的问题。如果在我们升级服务的时候,会造成一段时间内的服务不可用,这就是不够优雅的。那什么是优雅的呢?主要就是指在服务升级的时候,不中断整个服务,让用户无感知,进而不会影响用户的体验,这就是优雅的。

    实际上,优雅下线是目标,而不是手段,它是一个相对的概念,例如kill PIDkill -9 PID都是暴力杀死服务,相对于kill -9 PID来说,kill PID就是优雅的。但如果单独拿kill PID出来说,我们能说它是优雅的下线策略吗?肯定不是啊,就是这个道理。

    因此,本文讲述的优雅下线仅能称之为“相对的优雅下线”,但相对于暴力的杀死服务,已经足够优雅了。常见的优雅解决方案,主要包括优雅下线和灰度发布。而实际上,灰度发布的范围就已经包含优雅下线了。最后,在本文中,我们主要讲述基于 Spring Cloud 和 Euraka 的优雅下线以及灰度发布。

    优雅下线

    常见的下线方式

    方式一:kill PID

    使用方式:kill java进程ID

    该方式借助的是 Spring Boot 应用的 Shutdown hook,应用本身的下线也是优雅的,但如果你的服务发现组件使用的是 Eureka,那么默认最长会有 90 秒的延迟,其他应用才会感知到该服务下线,这意味着:该实例下线后的 90 秒内,其他服务仍然可能调用到这个已下线的实例。因此,该方式是不够优雅的 。

    方式二:/shutdown端点

    Spring Boot 提供了/shutdown端点,可以借助它实现优雅停机。

    使用方式:在想下线应用的applicationyml中添加如下配置,从而启用并暴露/shutdown端点:

    management:
      endpoint:
        shutdown:
          enabled: true
      endpoints:
        web:
          exposure:
            include: shutdown
    

    发送 POST 请求到/shutdown端点

    • curl -X http://你想停止的服务地址/actuator/shutdown

    该方式本质和方式一是一样的,也是借助 Spring Boot 应用的 Shutdown hook 去实现的。

    方式三:/pause端点

    Spring Boot 应用提供了/pause端点,利用该端点可实现优雅下线。

    使用方式:在想下线应用的application.yml中添加配置,从而启用并暴露/pause端点:

    management:
      endpoint:
        # 启用pause端点
        pause:
          enabled: true
        # 启用restart端点,之所以要启用restart端点,是因为pause端点的启用依赖restart端点的启用
        restart:
          enabled: true
      endpoints:
        web:
          exposure:
            include: pause,restart
    

    发送 POST 请求到/actuator/pause端点:

    • curl -X POST http://你想停止的服务实例地址/actuator/pause

    执行后的效果类似下图:

    down
    如图所示,该应用在 Eureka Server 上的状已被标记为DOWN,但是应用本身其实依然是可以正常对外服务的。在 Spring Cloud 中,Ribbon 做负载均衡时,只会负载到标记为UP的实例上。利用这两点,你可以:先用/pause端点,将要下线的应用标记为DOWN,但不去真正停止应用;然后过一定的时间(例如 90 秒,或者自己做个监控,看当前实例的流量变成 0 后)再去停止应用,例如kill应用。

    • 缺点 & 局限
    缺点描述
    不同的版本配置不大一样早期的 Spring Cloud 版本中,pause端点是不依赖restart端点的
    无法和 Eureka 的健康检查配合使用如果你的服务发现组件用的是 Eureka,并且你的应用开启了健康检查eureka.client.healthcheck.enabled = true,那么/pause端点无效

    方式四:/service-registry端点

    使用方式:在想下线应用的application.yml中添加配置,从而暴露/service-registry端点:

    management:
      endpoints:
        web:
          exposure:
            include: service-registry
    

    发送 POST 请求到/actuator/service-registry端点:

    curl -X "POST" "http://localhost:8000/actuator/service-registry?status=DOWN" \
       -H "Content-Type: application/vnd.spring-boot.actuator.v2+json;charset=UTF-8"
    

    实行后的效果类似如下图:
    down

    优雅的下线方式

    在上文中,我们讲述了四种常见的下线方式,对比来看,方式四 是一种比较优雅的下线方式。

    在实际项目中,我们可以先使用/service-registry端点,将服务标记为DOWN,然后监控服务的流量,当流量为 0 时,即可升级该服务。当然,这里假设我们部署了多个服务实例,当一个服务实例DOWN掉之后,其他服务实例仍然是可以提供服务的,如果就部署一台服务的话,那么讨论优不优雅就没那么重要了。

    除了上述的下线方式之外,还有一种利用EurekaAutoServiceRegistration对象达到优雅下线的目标。

    • 执行eurekaAutoServiceRegistration.start()方法时,当前服务向 Eureka 注册中心注册服务;
    • 执行eurekaAutoServiceRegistration.stop()方法时,当前服务会向 Eureka 注册中心进行反注册,注册中心收到请求后,会将此服务从注册列表中删除。

    示例代码如下:

    @RestController
    @RequestMapping(value = "/graceful/registry-service")
    public class GracefulOffline {
    
        @Autowired
        private EurekaAutoServiceRegistration eurekaAutoServiceRegistration;
    
        @RequestMapping("/online")
        public String online() {
            this.eurekaAutoServiceRegistration.start();
            return "execute online method, online success.";
        }
    
        @RequestMapping("/offline")
        public String offline() {
            this.eurekaAutoServiceRegistration.stop();
            return "execute offline method, offline success.";
        }
    }
    

    到这里,我们已经介绍了两种相对优雅的下线方式了。具体如何操作,我们可以根据实际上情况进行包装,或者利用自动化的脚本来实现更加优雅的下线方式。

    灰度发布

    蓝绿部署

    蓝绿部署,英文名为 Blue Green Deployment,是一种可以保证系统在不间断提供服务的情况下上线的部署方式。

    如何保证系统不间断提供服务呢?那就是同时部署两个集群,但仅对外提供一个集群的服务,当需要升级时,切换集群进行升级。蓝绿部署无需停机,并且风险较小。其大致步骤为:

    1. 部署集群 1 的应用(初始状态),将所有外部请求的流量都打到这个集群上
    2. 部署集群 2 的应用,集群 2 的代码与集群 1 不同,如新功能或者 Bug 修复等
    3. 将流量从集群 1 切换到集群 2
    4. 如集群 2 测试正常,就删除集群 1 正在使用的资源(例如实例),使用集群 2 对外提供服务

    因为在使用蓝绿部署的方式时,我们需要控制流量,所以我们需要借助路由服务,如 Nginx 等。

    滚动部署

    滚动部署,英文名为 Rolling Update,同样是一种可以保证系统在不间断提供服务的情况下上线的部署方式。和蓝绿部署不同的是,滚动部署对外提供服务的版本并不是非此即彼,而是在更细的粒度下平滑完成版本的升级。

    如何做到细粒度平滑升级版本呢?滚动部署只需要一个集群,集群下的不同节点可以独立进行版本升级。比如在一个 12 节点的集群中,我们每次升级 4 个节点,并将升级后的节点重新投入使用,周而复始,直到集群中所有的节点都更新为新版本。

    这种部署方式相对于蓝绿部署,更加节约资源,因为它不需要运行两个集群。但这种方式也有很多缺点,例如:

    1. 没有一个确定 OK 的环境。使用蓝绿部署,我们能够清晰地知道老版本是 OK 的,而使用滚动发布,我们无法确定。
    2. 修改了现有的环境。
    3. 如果需要回滚,很困难。举个例子,在某一次发布中,我们需要更新 100 个实例,每次更新 10 个实例,每次部署需要 5 分钟。当滚动发布到第 80 个实例时,发现了问题,需要回滚。这时,我们估计就要疯了。
    4. 有的时候,我们还可能对系统进行动态伸缩,如果部署期间,系统自动扩容/缩容了,我们还需判断到底哪个节点使用的是哪个代码。尽管有一些自动化的运维工具,但是依然令人心惊胆战。

    并不是说滚动发布不好,滚动发布也有它非常合适的场景。

    金丝雀部署

    金丝雀部署又称灰度部署(或者,灰度发布),英文名为 Canary Deployment,是指在黑与白之间,能够平滑过渡的一种发布方式。

    金丝雀的名称来源于「矿井中的金丝雀」,早在 17 世纪,英国矿井工人发现,金丝雀对瓦斯这种气体十分敏感,空气中哪怕有极其微量的瓦斯,金丝雀也会停止歌唱;而当瓦斯含量超过一定限度时,虽然鲁钝的人类毫无察觉,金丝雀却早已毒发身亡。当时在采矿设备相对简陋的条件下,工人们每次下井都会带上一只金丝雀作为“瓦斯检测指标”,以便在危险状况下紧急撤离。

    我们来看一下金丝雀部署的步骤:

    1. 准备好部署各个阶段的工件,包括:构建工件,测试脚本,配置文件和部署清单文件
    2. 从负载均衡列表中移除掉“金丝雀”服务器
    3. 升级“金丝雀”应用(切断原有流量并进行部署)
    4. 对应用进行自动化测试
    5. 将“金丝雀”服务器重新添加到负载均衡列表中(连通性和健康检查)
    6. 如果“金丝雀”在线使用测试成功,升级剩余的其他服务器(否则就回滚)

    在金丝雀部署中,常常按照用户量设置路由权重,例如 90% 的用户维持使用老版本,10% 的用户尝鲜新版本。不同版本应用共存,经常与 A/B 测试一起使用,用于测试选择多种方案。金丝雀部署比较典型的例子,就是我们在使用某个应用的时候,该应用邀请我们进行“内测”或者“新版本体验”,如果我们同意了,那么我们就成了金丝雀。


    参考资料

    展开全文
  • 灰度发布的核心就是路由转发,如果我们能够自定义网关==>服务a、服务a==>服务b中间的路由策略,就可以实现用户引流,灰度发布

    Spring Cloud灰度发布方案----自定义路由规则

    一、简介

    1.1 不停机部署服务策略介绍

    • 蓝绿部署
      蓝绿部署的模型中包含两个集群A和B
      1、在没有上线的正常情况下,集群A和集群B的代码版本是一致的,并且同时对外提供服务。
      2、在系统升级的时候下,我们首先把一个集群(比如集群A)从负载列表中摘除,进行新版本的部署。集群B仍然继续提供服务。
      3、当集群A升级完毕,我们把负载均衡重新指向集群A,再把集群B从负载列表中摘除,进行新版本的部署。集群A重新提供服务。
      4、最后,当集群B也升级完成,我们把集群B也恢复到负载列表当中。这个时候,两个集群的版本都已经升级,并且对外的服务几乎没有间断过。
      详细介绍请参考:https://www.cnblogs.com/aaron911/p/11299422.html

    • 滚动部署
      和蓝绿部署不同的是,滚动部署对外提供服务的版本并不是非此即彼,而是在更细的粒度下平滑完成版本的升级。
      滚动部署只需要一个集群,集群下的不同节点可以独立进行版本升级。比如在一个16节点的集群中,我们选择每次升级4个节点,过程如下图:
      在这里插入图片描述

    • 灰度发布(金丝雀发布)
      金丝雀发布,与蓝绿部署不同的是,它不是非黑即白的部署方式,所以又称为灰度发布。它能够缓慢的将修改推广到一小部分用户,验证没有问题后,再推广到全部用户,以降低生产环境引入新功能带来的风险。
      灰度发布的重点就是制定引流策略,将请求分发到不同版本服务中。比如内部测试人员的请求分发到金丝雀服务,其他用户分发到旧服务中。测试通过之后在推广到全部用户。

    部署方式优势劣势描述
    蓝绿部署同一时间对外服务的只有一个版本,容易定位问题。升级和回滚一集群为粒度,操作相对简单需要维护两个集群,机器成本要求高两套环境交替升级,旧版本保留一定时间便于回滚。
    滚动部署只需维护一个集群,成本低上线过程中,两个版本同时对外服务,不易定位问题,且容易造成数据错乱。升级和回滚操作相对复杂按批次停止老版本实例,启动新版本实例。
    灰度发布新版本出现问题影响范围很小,允许失败,风险较小只能适用于兼容迭代的方式,如果是大版本不兼容的场景,就没办法使用这种方式了根据比例将老版本升级,例如80%用户访问是老版本,20%用户访问是新版本。

    1.2 eureka RestFul接口

    请求名称请求方式HTTP地址请求描述
    注册新服务POST/eureka/apps/{appID}传递JSON或者XML格式参数内容,HTTP code为204时表示成功
    删除注册服务DELETE/eureka/apps/{appID}/{instanceID}
    发送服务心跳PUT/eureka/apps/{appID}/{instanceID}
    查询所有服务GET/eureka/apps
    查询指定appID的服务列表GET/eureka/apps/{appID}
    查询指定appID&instanceIDGET/eureka/apps/{appID}/{instanceID}获取指定appID以及InstanceId的服务信息
    查询指定instanceID服务列表GET/eureka/apps/instances/{instanceID}获取指定instanceID的服务列表
    变更服务状态PUT/eureka/apps/{appID}/{instanceID}/status?value=DOWN服务上线、服务下线等状态变动
    变更元数据PUT/eureka/apps/{appID}/{instanceID}/metadata?key=value更新eurekametadata元数据

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

    2.1 调用链分析

    • 用户请求==>zuul网关==>服务a==>服务b
      1、首先用户发送请求
      2、经过网关分发请求到具体服务a
      3、服务a 调用服务b接口
      在这里插入图片描述

    灰度发布的核心就是路由转发,如果我们能够自定义网关==>服务a、服务a==>服务b中间的路由策略,就可以实现用户引流,灰度发布。

    2.2 实现思路、流程

    在这里插入图片描述

    • 网关层设计思路
    1. 用户请求首先到达Nginx然后转发到网关zuul,此时zuul拦截器会根据用户携带请求token解析出对应的userId,然后从路由规则表中获取路由转发规则。
    
    2. 如果该用户配置了路由策略,则该用户是灰度用户,转发用户请求到配置的灰度服务。否则转发到正常服务。
    
    • 服务间调用设计思路
    3. zuul网关将请求转发到服务a后,可能还会通过fegin调用其他服务。所以需要拦截请求,将请求头version=xxx给带上,然后存入线程变量。
    此处不能用Threadlocal存储线程变量,因为SpringCloud用hystrix做线程池隔离,而线程池是无法获取到ThreadLocal中的信息的! 
    所以这个时候我们可以参考Sleuth做分布式链路追踪的思路或者使用阿里开源的TransmittableThreadLocal方案。
    此处使用HystrixRequestVariableDefault实现跨线程池传递线程变量。
    
    4. 服务间调用时会经过ribbon组件从服务实例列表中获取一个实例选择转发。Ribbon默认的IRule规则为ZoneAvoidanceRule`。而此处我们继承该类,重写了其父类选择服务实例的方法。
    
    5. 根据自定义IRule规则将灰度用户请求路由到灰度服务,非灰度用户请求路由到正常服务。
    
    

    2.3 资源准备

    • spring cloud微服务准备
      调用链路:用户==>zuul-server==>abTest==> provider-server
    服务名端口eureka元数据描述
    zuul-server9000网关服务
    abTest8083version: v1新版本金丝雀服务
    abTest8084老版本服务
    abTest8085老版本旧服务
    provider-server8093version: v1新版本金丝雀服务
    provider-server8094老版本服务
    provider-server8095老版本旧服务
    • 路由规则库表
    # 用户表
    CREATE TABLE `t_user`  (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `nickname` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '用户昵称',
      `head_image` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 'head_image',
      `city` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '城市',
      `gender` int(2) DEFAULT NULL COMMENT '性别  0:男 1:女',
      `user_type` int(2) DEFAULT 0 COMMENT '用户类型(0:普通用户 1:vip)',
      `mobile` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '用户手机号',
      `status` int(2) DEFAULT 1 COMMENT '用户状态 0:冻结  1:正常',
      `token` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '登录token',
      `token_expires_time` datetime(0) DEFAULT NULL COMMENT 'token过期时间',
      `create_time` datetime(0) DEFAULT NULL COMMENT '创建时间',
      `update_time` datetime(0) DEFAULT NULL COMMENT '更新时间',
      PRIMARY KEY (`id`) USING BTREE
    ) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户表' ROW_FORMAT = Dynamic;
    
    INSERT INTO `t_user` VALUES (1, 'hld', NULL, NULL, 1, 0, 'xxxx', 1, 'nm4p2ouy9ckl20bnnd62acev3bnasdmb', '2021-12-01 15:31:09', '2021-08-31 15:31:18', '2021-09-01 16:15:25');
    INSERT INTO `t_user` VALUES (2, 'xxx', NULL, NULL, 1, 0, 'xxxxx', 1, 'lskeu9s8df7sdsue7re890er343rtolzospw', '2021-12-01 15:31:09', '2021-08-31 15:31:18', '2021-09-01 16:15:25');
    INSERT INTO `t_user` VALUES (3, 'www', NULL, NULL, 1, 0, 'wwww', 1, 'pamsnxs917823skshwienmal2m3n45mz', '2021-12-01 15:31:09', '2021-08-31 15:31:18', '2021-09-01 16:15:25');
    
    # 灰度路由规则配置表
    CREATE TABLE `ab_test`  (
      `id` int(11) NOT NULL,
      `application_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '服务名',
      `version` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '版本',
      `userId` int(11) DEFAULT NULL COMMENT '用户id',
      PRIMARY KEY (`id`) USING BTREE
    ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
    
    INSERT INTO `ab_test` VALUES (1, 'abTest', 'v1', 1);
    INSERT INTO `ab_test` VALUES (2, 'abTest', 'v2', 3);
    

    三、 代码实现

    灰度服务eureka.instance.metadata-map元数据信息添加version: v1。 正常服务设置元数据信息
    自定义路由规则IRule时可以根据version来区分是否灰度服务,从而实现不同用户路由到不同的服务中。

    3.1 网关路由(zuul-server服务)

    本demo使用zuul作为网关层,自定义网关层IRule路由规则实现网关层灰度。

    • 自定义IRule规则
    package com.hanergy.out.config;
    
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.hanergy.out.entity.AbTest;
    import com.hanergy.out.entity.TUser;
    import com.hanergy.out.service.AbTestService;
    import com.hanergy.out.service.TUserService;
    import com.netflix.client.config.IClientConfig;
    import com.netflix.loadbalancer.ILoadBalancer;
    import com.netflix.loadbalancer.Server;
    import com.netflix.loadbalancer.ZoneAvoidanceRule;
    import com.netflix.niws.loadbalancer.DiscoveryEnabledServer;
    import com.netflix.zuul.context.RequestContext;
    import io.jmnarloch.spring.cloud.ribbon.rule.MetadataAwareRule;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.http.HttpStatus;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.http.HttpServletRequest;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map;
    import java.util.Random;
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * @description: 此处轮询调用对应服务
     * @author: Han LiDong
     * @create: 2021/11/18 16:12
     * @update: 2021/11/18 16:12
     */
    // ZoneAvoidanceRule   AbstractLoadBalancerRule
    @Component
    public class GrayRule extends MetadataAwareRule {
    
        private AtomicInteger nextServerCyclicCounter;
        private static final boolean AVAILABLE_ONLY_SERVERS = true;
        private static final boolean ALL_SERVERS = false;
    
        private static Logger log = LoggerFactory.getLogger(GrayRule.class);
    
        public GrayRule() {
            nextServerCyclicCounter = new AtomicInteger(0);
        }
        private Random random = new Random();
    
        @Autowired
        private AbTestService abTestService;	//灰度规则配置表
        @Autowired
        private TUserService userService;		//用户表
    
        @Override
        public void initWithNiwsConfig(IClientConfig iClientConfig) {
    
        }
    
        /**
         * 根据请求头token获取用户信息,然后去ab_test表获取灰度规则。
         * @param lb
         * @param o
         * @return
         */
        @Override
        public Server choose(Object o) {
            return choose(getLoadBalancer(),o);
        }
    
        public Server choose(ILoadBalancer lb, Object o){
            if (lb == null) {
                log.warn("no load balancer");
                return null;
            }
            RequestContext requestContext =  RequestContext.getCurrentContext();
            HttpServletRequest request = requestContext.getRequest();
            //请求请求头token信息
            String token = request.getHeader("token");
            // 根据token获取用户信息
            TUser user = userService.getOne(new QueryWrapper<TUser>()
                    .lambda()
                    .eq(TUser::getToken, token));
            // token异常
            if (user == null){
                requestContext.setSendZuulResponse(false);
                requestContext.setResponseStatusCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
            }
            // 查询灰度发布配置表,判断此用户是否灰度用户
            AbTest abTest = abTestService.getOne(new QueryWrapper<AbTest>()
                    .lambda()
                    .eq(AbTest::getUserid, user.getId()));
            String version = null;
            if(abTest != null){
                version = abTest.getVersion();
            }
            //该用户可选择的服务列表(灰度用户:灰度服务列表   非灰度用户:非灰度服务列表)
            List<Server> allServers = new ArrayList<>();
    
    
            //1.从线程变量获取version信息
            //String version = GrayHolder.getGray();
            //获取所有可达服务
            List<Server> reachableServers = lb.getReachableServers();
            for (Server server : reachableServers){
                Map<String, String> metadata = ((DiscoveryEnabledServer) server).getInstanceInfo().getMetadata();
                String metaVersion = metadata.get("version");
                if (version != null && !version.isEmpty() && version.equals(metaVersion)){   //是灰度用户并且当前server是灰度服务
                    allServers.add(server);
                } else if ((version == null || version.isEmpty()) && metaVersion == null){    //非灰度用户并且当前server非灰度服务
                    allServers.add(server);
                }
            }
            // 轮询选择其中一个服务
            Server choosedServer = choose(lb, o, allServers);
    
            return choosedServer;
        }
        /**
         * 轮询策略选择一个服务
         * @param lb
         * @param o
         * @param allServers
         * @return
         */
        public Server choose(ILoadBalancer lb, Object o, List<Server> allServers){
            Server server = null;
            int count = 0;
            while (server == null && count++ < 10) {
                int upCount = allServers.size();
    
                if (upCount == 0) {
                    log.warn("No up servers available from load balancer: " + lb);
                    return null;
                }
                // 轮询服务下标
                int nextServerIndex = incrementAndGetModulo(upCount);
                server = allServers.get(nextServerIndex);
    
                if (server == null) {
                    /* Transient. */
                    Thread.yield();
                    continue;
                }
    
                if (server.isAlive() && (server.isReadyToServe())) {
                    return (server);
                }
    
                // Next.
                server = null;
            }
    
            if (count >= 10) {
                log.warn("No available alive servers after 10 tries from load balancer: "
                        + lb);
            }
            return server;
        }
    
        /**
         * Inspired by the implementation of {@link AtomicInteger#incrementAndGet()}.
         *
         * @param modulo The modulo to bound the value of the counter.
         * @return The next value.
         */
        private int incrementAndGetModulo(int modulo) {
            for (;;) {
                int current = nextServerCyclicCounter.get();
                int next = (current + 1) % modulo;
                if (nextServerCyclicCounter.compareAndSet(current, next))
                    return next;
            }
        }
    }
    
    • 自定义规则加入Spring容器(zuul-server服务)
      1、编写config配置类
    package com.hanergy.out.config;
    
    import com.netflix.loadbalancer.IRule;
    import org.springframework.context.annotation.Bean;
    
    /**
     * @description: 此处无需@Configuration注解,启动类增加@RibbonClient注解注入配置类
     * @author: Han LiDong
     * @create: 2021/11/18 16:53
     * @update: 2021/11/18 16:53
     */
    //@Configuration
    public class GrayRibbonConfiguration {
    
        @Bean
        public IRule ribbonRule(){
            return new GrayRule();
        }
    }
    

    2、启动类增加@RibbonClient注解,扫描IRule配置

    package com.hanergy.out;
    
    import com.hanergy.out.config.GrayRibbonConfiguration;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
    import org.springframework.cloud.netflix.ribbon.RibbonClient;
    import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
    
    // 网关
    @SpringBootApplication
    @EnableZuulProxy
    // name为微服务名称,必须和服务提供者的微服务名称一致,configuration配置自定义的负载均衡规则
    @RibbonClient(name = "zuul-server",configuration = GrayRibbonConfiguration.class)
    public class ZuulServiceApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(ZuulServiceApplication.class, args);
        }
    }
    

    3.2 服务间调用路由策略(abTest服务)

    由于Hystrix有2个隔离策略:THREAD以及SEMAPHORE,当隔离策略为THREAD时,由于线程切换,会导致无法获取到原线程中的缓存数据。默认就是THREAD策略,所以服务间调用时无法获取到request对象。所以就需要我们提前将灰度信息提前存储起来。
    此处不能用Threadlocal存储线程变量,因为SpringCloud用hystrix做线程池隔离,而线程池是无法获取到ThreadLocal中的信息的!
    所以这个时候我们可以参考Sleuth做分布式链路追踪的思路或者使用阿里开源的TransmittableThreadLocal方案。
    此处使用HystrixRequestVariableDefault实现跨线程池传递线程变量。

    • HystrixRequestVariableDefault实现跨线程池工具类
    package com.hanergy.out.config;
    
    import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext;
    import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariableDefault;
    
    /**
     * @description:
     * @author: Han LiDong
     * @create: 2021/11/19 09:43
     * @update: 2021/11/19 09:43
     */
    public class GrayHolder {
    
        private  static HystrixRequestVariableDefault<String> gray ;
       /* static {
            System.out.println("init holder");
        }*/
    
    
        public static String getGray(){
            return  gray.get();
        }
    
        public static void setGray(String token){
            HystrixRequestContext.initializeContext();
            gray =  new HystrixRequestVariableDefault<>();
            gray.set(token);
        }
    
    
    }
    
    
    • aop拦截请求,获取灰度信息
    package com.hanergy.out.config;
    
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.hanergy.out.entity.AbTest;
    import com.hanergy.out.entity.TUser;
    import com.hanergy.out.service.AbTestService;
    import com.hanergy.out.service.TUserService;
    import com.hanergy.out.utils.RibbonParam;
    import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariableDefault;
    import org.aopalliance.intercept.Joinpoint;
    import org.apache.commons.lang3.StringUtils;
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.http.HttpRequest;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.client.support.HttpRequestWrapper;
    import org.springframework.stereotype.Component;
    import org.springframework.web.context.request.RequestContextHolder;
    import org.springframework.web.context.request.ServletRequestAttributes;
    import org.springframework.web.servlet.support.RequestContext;
    
    import javax.servlet.http.HttpServletRequest;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * @description:
     * @author: Han LiDong
     * @create: 2021/11/18 16:31
     * @update: 2021/11/18 16:31
     */
    @Aspect
    @Component
    public class ReqestAspect {
    
        @Autowired
        private TUserService userService;
        @Autowired
        private AbTestService abTestService;
        
        @Before("execution(* com.hanergy.out.controller.*.*(..))")
        public void before(){
            HttpServletRequest request =  ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
            String token = request.getHeader("token");
            // 根据token获取用户信息
            TUser user = userService.getOne(new QueryWrapper<TUser>()
                    .lambda()
                    .eq(TUser::getToken, token));
            if (user == null){
                throw new RuntimeException("token异常");
            }
            // 查询灰度发布配置表,判断此用户是否灰度用户
            AbTest abTest = abTestService.getOne(new QueryWrapper<AbTest>()
                    .lambda()
                    .eq(AbTest::getUserid, user.getId()));
    
            Map<String,String> map = new HashMap<>();
            map.put("userId",user.getId().toString());
            RibbonParam.set(map);
    		// 存储是否灰度用户信息
            GrayHolder.setGray(abTest == null ? "" : abTest.getVersion());
    
        }
    
    }
    
    
    • 自定义ribbon路由规则
    package com.hanergy.out.config;
    
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.hanergy.out.entity.AbTest;
    import com.hanergy.out.entity.TUser;
    import com.hanergy.out.service.AbTestService;
    import com.hanergy.out.service.TUserService;
    import com.hanergy.out.utils.RibbonParam;
    import com.netflix.client.config.IClientConfig;
    import com.netflix.hystrix.strategy.concurrency.HystrixLifecycleForwardingRequestVariable;
    import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariableDefault;
    import com.netflix.loadbalancer.*;
    import com.netflix.niws.loadbalancer.DiscoveryEnabledServer;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import org.springframework.web.context.request.RequestContextHolder;
    import org.springframework.web.context.request.ServletRequestAttributes;
    import org.springframework.web.filter.RequestContextFilter;
    import org.springframework.web.servlet.support.RequestContextUtils;
    import org.springframework.web.servlet.tags.RequestContextAwareTag;
    import org.w3c.dom.stylesheets.LinkStyle;
    import springfox.documentation.RequestHandler;
    
    import javax.servlet.http.HttpServletRequest;
    import java.util.*;
    import java.util.concurrent.atomic.AtomicInteger;
    import java.util.stream.Collectors;
    
    /**
     * @description:
     * @author: Han LiDong
     * @create: 2021/11/18 16:12
     * @update: 2021/11/18 16:12
     */
    // ZoneAvoidanceRule   AbstractLoadBalancerRule
    @Component
    public class GrayRule extends ZoneAvoidanceRule {
    
        private AtomicInteger nextServerCyclicCounter;
        private static final boolean AVAILABLE_ONLY_SERVERS = true;
        private static final boolean ALL_SERVERS = false;
    
        private static Logger log = LoggerFactory.getLogger(GrayRule.class);
    
        public GrayRule() {
            nextServerCyclicCounter = new AtomicInteger(0);
        }
        private Random random = new Random();
    
        @Autowired
        private AbTestService abTestService;
        @Autowired
        private TUserService userService;
    
        @Override
        public void initWithNiwsConfig(IClientConfig iClientConfig) {
    
        }
    
        @Override
        public Server choose(Object o) {
            return choose(getLoadBalancer(),o);
        }
        /**
         * 根据请求头token获取用户信息,然后去ab_test表获取灰度规则。
         * @param lb
         * @param o
         * @return
         */
        public Server choose(ILoadBalancer lb, Object o){
            if (lb == null) {
                log.warn("no load balancer");
                return null;
            }
            //该用户可选择的服务列表(灰度用户:灰度服务列表   非灰度用户:非灰度服务列表)
            List<Server> allServers = new ArrayList<>();
    
            //1.从线程变量获取version信息
            String version = GrayHolder.getGray();
            //获取所有可达服务
            List<Server> reachableServers = lb.getReachableServers();
            for (Server server : reachableServers){
                Map<String, String> metadata = ((DiscoveryEnabledServer) server).getInstanceInfo().getMetadata();
                String metaVersion = metadata.get("version");
                if (version != null && !version.isEmpty() && version.equals(metaVersion)){   //是灰度用户并且当前server是灰度服务
                    allServers.add(server);
                } else if ((version == null || version.isEmpty()) && metaVersion == null){    //非灰度用户并且当前server非灰度服务
                    allServers.add(server);
                }
            }
            // 轮询选择其中一个服务
            Server choosedServer = choose(lb, o, allServers);
    
            return choosedServer;
        }
    
        /**
         * 轮询策略选择一个服务
         * @param lb
         * @param o
         * @param allServers
         * @return
         */
        public Server choose(ILoadBalancer lb, Object o, List<Server> allServers){
            Server server = null;
            int count = 0;
            while (server == null && count++ < 10) {
                int upCount = allServers.size();
    
                if (upCount == 0) {
                    log.warn("No up servers available from load balancer: " + lb);
                    return null;
                }
                int nextServerIndex = incrementAndGetModulo(upCount);
                server = allServers.get(nextServerIndex);
    
                if (server == null) {
                    /* Transient. */
                    Thread.yield();
                    continue;
                }
    
                if (server.isAlive() && (server.isReadyToServe())) {
                    return (server);
                }
    
                // Next.
                server = null;
            }
    
            if (count >= 10) {
                log.warn("No available alive servers after 10 tries from load balancer: "
                        + lb);
            }
            return server;
        }
    
        /**
         * Inspired by the implementation of {@link AtomicInteger#incrementAndGet()}.
         *
         * @param modulo The modulo to bound the value of the counter.
         * @return The next value.
         */
        private int incrementAndGetModulo(int modulo) {
            for (;;) {
                int current = nextServerCyclicCounter.get();
                int next = (current + 1) % modulo;
                if (nextServerCyclicCounter.compareAndSet(current, next))
                    return next;
            }
        }
    }
    
    • 自定义路由规则生效
      1、config配置类
    package com.hanergy.out.config;
    
    import com.netflix.loadbalancer.IRule;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    /**
     * @description:
     * @author: Han LiDong
     * @create: 2021/11/18 16:53
     * @update: 2021/11/18 16:53
     */
    //@Configuration
    public class GrayRibbonConfiguration {
    
        @Bean
        public IRule ribbonRule(){
            return new GrayRule();
        }
    }
    

    2、启动类增加@RibbonClient注解

    package com.hanergy.out;
    
    import com.hanergy.out.config.GrayRibbonConfiguration;
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    import org.springframework.cloud.netflix.ribbon.RibbonClient;
    import org.springframework.cloud.openfeign.EnableFeignClients;
    import org.springframework.context.annotation.Bean;
    import org.springframework.web.cors.CorsConfiguration;
    import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
    import org.springframework.web.filter.CorsFilter;
    import springfox.documentation.swagger2.annotations.EnableSwagger2;
    
    @SpringBootApplication
    @EnableSwagger2
    @EnableFeignClients          //feign
    @EnableDiscoveryClient
    @EnableCircuitBreaker       // 熔断器注解
    //name为微服务名称,必须和服务提供者的微服务名称一致,configuration配置自定义的负载均衡规则
    @RibbonClient(name = "abTest",configuration = GrayRibbonConfiguration.class)
    @MapperScan(basePackages = {"com.hanergy.out.dao"})
    public class HanergyOutApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(HanergyOutApplication.class, args);
        }
    }
    

    四、灰度接口测试

    调用链:用户==》zuul网关==>abTest服务==>provider-server服务

    4.1 provider-server服务提供测试接口

    @Slf4j
    @RestController
    @RequestMapping("/v1/test")
    public class TestController {
    
        @Value("${server.port}")
        private Integer port;
    
        @ApiOperation(value="获取端口号",notes="获取端口号")
        @GetMapping("/getPort")
        public HttpResult<Integer> getPort(){
            return HttpResult.successResult(port);
        }
    }
    
    

    4.2 abTest服务提供测试接口

    • feign服务间调用
    @FeignClient(value = "provider-server",fallback = ManagerPreFallbackImpl.class)
    public interface RemoteManagerPreService {
    
        @ApiOperation(value="获取端口号",notes="获取端口号")
        @GetMapping("/v1/test/getPort")
        public HttpResult<Integer> getPort();
    
    }
    
    • hystrix断路器
    @Slf4j
    @Component
    public class ManagerPreFallbackImpl implements RemoteManagerPreService {
    
        @Override
        public HttpResult<Integer> getPort() {
            log.error("获取provider服务端口异常");
            return null;
        }
    }
    
    • 服务间调用
    @Slf4j
    @RestController
    @RequestMapping("/v1/test")
    public class TestController {
    
        @Value("${server.port}")
        private Integer port;
    
        @ApiOperation(value="获取provider服务端口号",notes="获取provider服务端口号")
        @GetMapping("/getProviderPort")
        public HttpResult<Integer> getProviderPort(){
        	// feign服务间调用
            HttpResult<Integer> res = remoteManagerPreService.getPort();
            Integer providerPort = res.getData();
    
            return HttpResult.successResult("port: "+ port + ",providerPort:" + providerPort);
        }
    }
    

    五、验证

    abTest分别使用8083、8084、8085端口启动,其中8083端口设置元数据信息为 version: v1
    provider-server分别使用8093、8094、8095端口启动,其中8093端口设置元数据信息为 version: v1
    那么灰度用户的接口请求路由为:zuul==》8083端口服务==》8093端口服务
    正常用户接口请求路由为:zuul==》8084/8085端口服务==》8094/8095端口服务

    • 启动所需服务
      启动eureka注册中心、zuul网关、abtest(8083、8084、8085)、provider-server(8093、8094、8095)
    • 调用eureka RestFul接口修改元数据信息
      通过此种方法更改server的元数据后,由于ribbon会缓存实例列表,所以在测试改变服务信息时,ribbon并不会马上从eureka拉去最新信息,需等待一段时间。
    //修改8083端口abTest服务元数据信息
    PUT  182.92.xxx.xxx:8761/eureka/apps/ABTEST/192.168.199.1:abTest:8083/metadata?version=v1
    //修改8093端口provider-server服务元数据信息
    PUT  182.92.219.202:8761/eureka/apps/PROVIDER-SERVER/192.168.199.1:provider-server:8093/metadata?version=v1
    
    • 验证eureka元数据信息是否已添加
      在这里插入图片描述

    • 灰度用户调用测试
      在这里插入图片描述

    • 正常用户请求测试

    在这里插入图片描述
    在这里插入图片描述
    至此灰度发布验证完成,

    展开全文
  • 灰度发布方案

    2021-11-24 15:22:43
    对于互联网产品来说,上线和未上线就是黑与白之分,而实现未上线功能平稳过渡的一种方式就叫做灰度发布。不少大厂在产品上线前都会进行灰度测试,本文作者为大家总结了大厂常用的几种灰度发布方案。 什么是灰度...
  • gateway简介和灰度发布实现方案 gateway介绍 官方文档:https://docs.spring.io/spring-cloud-gateway/docs/2.2.8.RELEASE/reference/html/#gateway-starter 网关请求处理过程 客户端向Spring Cloud Gateway发出...
  • 灰度发布是金丝雀发布的延伸,是将发布分成不同的阶段/批次,每个阶段/批次的用户数量逐级增加。如果新版本在当前阶段没有发现问题,就再增加用户数量进入下一个阶段,直至扩展到全部用户。 灰度发布可以减小发布...
  • versionSwitchTool 通过apache,nginx,iis rewrite机制进行项目灰度发布
  • 什么是灰度发布,其要点有哪些? 最近跟几个聊的来的同行来了一次说聚就聚的晚餐,聊了一下最近的工作情况如何以及未来规划等等,酒足饭饱后我们聊了一个话题“灰度发布”。 因为笔者所负责的产品还没有达到...
  • service-common(基础服务)、service-zuul(网关服务)、service-A(灰度服务1)、service-B(灰度服务2) 外部调用:请求==>service-zuul==>service-A 内部调用:请求==>service-zuul==>service-A
  • 大厂技术高级前端Node进阶点击上方程序员成长指北,关注公众号回复1,加入高级Node交流群一个大型的前端项目在发布时,都会采取灰度策略。第一次听同事说灰度的时候,还是停留在疑惑...
  • 灰度发布和灰度测试

    万次阅读 多人点赞 2019-08-27 10:30:06
    灰度测试是什么意思?如果您对互联网软件开发行业了解不多,您可能对这个词不太熟悉。...灰度测试又名金丝雀发布、灰度发布,一种在黑白之间发布平滑过渡的方式。可以对其执行A/B测试,也就是说,一些...
  • 基于动态策略的灰度发布系统,基于ngx_lua的一次性实践,手机微博、微博头条等产品线采用此方案
  • 什么是灰度发布

    2021-04-20 10:13:42
    什么是灰度发布,以及灰度发布A/B测试 在一般情况下,升级服务器端应用,需要将应用源码或程序包上传到服务器,然后停止掉老版本服务,再启动新版本。但是这种简单的发布方式存在两个问题,一方面,在新版本升级过程...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 30,400
精华内容 12,160
关键字:

灰度发布