V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
CRH
V2EX  ›  程序员

一个简单的灰度发布思路,求指正

  •  
  •   CRH · 2020-06-02 13:18:39 +08:00 · 4263 次点击
    这是一个创建于 1417 天前的主题,其中的信息可能已经有所发展或是发生改变。

    背景

    • 小公司,技术力量一般
    • to B 服务,可用性要求高
    • 服务仅提供 APP 端
    • 可以强制客户升级 APP 版本

    目标

    • 旧版本的 APP 连接至新版本的后端,可能会导致不可预知的 bug,没有人手做这方面的兼容性测试
    • APP 和后端同步升级,可能会导致未测出的 bug 大面积出现
    • 以前都是半夜上线,测试小哥连夜验证,如有问题再连夜回滚版本,累
    • 不想修改太多代码

    实现

    环境

    • 分生产( prod )、预发布( pre )环境
    • 两个环境的 API 入口分别为 prod.xxx.compre.xxx.com
    • prod 和 pre 分别部署在两组不同的应用服务器上(组 1 & 组 2 ),连接至同一组数据库服务器

    新增接口

    接口 A

    • APP 每次启动(登录)时,访问 prod 环境的接口 A
    • 接口 A 会根据用户 ID 返回应安装的 APP 版本
    • 如接口 A 返回的版本与本地版本不符,会强制客户更新 APP

    接口 B

    • APP 每次启动时,访问 prod 环境的接口 B
    • 接口 B 根据 APP 版本号返回应该连接的环境
    • 比如生产环境是 v1.0,新发布了 v1.1,那么 v1.0 访问接口 B 会返回 server:prod.xxx.com ,v1.1 会返回 server:pre.xxx.com

    上线过程

    • 假如生产环境在运行 v1.0 版 APP,新发布了 v1.1 版 APP
    • 新版本上线时,将 v1.1 版的后端代码发布至 pre 组(组 2 )应用服务器,此时 pre 组没有流量
    • 配置 prod 环境的接口 A,给部分友好用户返回 version:v1.1,大多数客户仍然返回 version:v1.0
    • 友好用户会被强制更新到 v1.1 版 APP,这部分流量由 pre 组服务器承担
    • 友好用户经过一段时间的试用,如果发现问题:
      - 配置接口 A 全部返回 version:v1.0
      - 这些用户就会被强制更新回 v1.0
    • 如果试用没有问题:
      - 逐日依据客户重要程度,分批配置接口 A 返回的数据,返回 version:v1.1,并相应增加第 2 组服务器资源
      - 在接口 A 对全部客户均返回新版本号后,等待数日(保证全部客户已更新),配置接口 B,对所有版本均返回 server:prod.xxx.com
      - 修改负载均衡设置,将 prod.xxx.com 指向第 2 组服务器
    • 将第 1 组服务器(原 prod 组)下线

    补充说明

    • 其实也不是什么新思路,只是比较容易实现
    • 使用推送代替接口 A,可以更好地控制客户使用的版本,避免版本分裂
    • 新版本只能在数据库上新增字段,不能删除或修改已有字段
    • 其实安全起见,pre 环境应该使用独立数据库 & 双写至 prod 数据库的,不过感觉比较复杂,打算先不做

    问题

    • 有现成的轮子么?
    • 这个做法有啥隐患?

    请指教,谢谢!

    25 条回复    2020-06-03 21:50:20 +08:00
    Aruforce
        1
    Aruforce  
       2020-06-02 13:37:03 +08:00
    目标这个应该改成存在的问题吧...

    想做到数据安全升级的话,就不能略过,需要完成老 APP 在新 API 下的兼容测试;
    略过之后如果存在兼容 bug 那肯定存在数据错误需要修复;

    你这种操作是啥?拿一部分用户献祭?脏数据修复搞死你。。。
    而且不同代码使用同一套数据环境。。。确认表结构是兼容的?
    lovedebug
        2
    lovedebug  
       2020-06-02 13:42:44 +08:00
    我们一般都会用特性开关,打开部分用户测试。或者用旁路引流部分到 duplicator 环境测试
    scukmh
        3
    scukmh  
       2020-06-02 13:48:39 +08:00 via iPhone
    @Aruforce 楼主有提到只允许新增字段,不允许删除字段呀。
    firefox12
        4
    firefox12  
       2020-06-02 13:49:54 +08:00
    既然可以强制前端升级,那么 协议一般都是这么搞。

    客户端第一个命令 都是 告诉服务器 我的版本号 客户 id 。
    服务器给的结果都是 1. 可以继续 你去连这个服务器 pre
    2. 可以继续 你去练这个服务器 pro

    服务器 可以根据这个客户 id 做灰度,简单的 比如 id <10000 的 是你们测试的 id, 那么你们就可以用自己的 id
    在 pre 服务上测,大部分人都继续用 pro 的。
    等测试好了 把 pro 升级,把服务器的的 灰度关掉, 服务器的结果就变成
    1. 如果不是新版本 就必须升级。
    2. 已经是新版本 请走 pro.
    pmispig
        5
    pmispig  
       2020-06-02 13:54:25 +08:00
    你这个我可以给你提供一个更简单的思路。
    在 app 所有的 get/post 等请求里面,带一个 version=1.0 这样的头部,然后用 openresty 根据头部来做路由就行了。
    想让部分用户升级的话,就是另一个问题了,这个很简单。
    CRH
        6
    CRH  
    OP
       2020-06-02 14:03:41 +08:00
    @Aruforce 对……应该是存在的问题。
    曾经出现过测试阶段没有测出的 bug,在实际使用中出现了,就要紧急回滚版本 + 被很多客户骂。
    少部分“友好”客户,就是和我们关系比较好的客户,上线初期可能只有一两家。
    我们甚至可以派客服远程一直看着他们的屏幕,有问题就及时协助处理。
    脏数据修复确实是个问题
    CRH
        7
    CRH  
    OP
       2020-06-02 14:07:52 +08:00
    @pmispig 谢谢,是说放在 request header 里面对吧
    pmispig
        8
    pmispig  
       2020-06-02 14:14:07 +08:00
    @CRH 是的
    aut0man
        9
    aut0man  
       2020-06-02 14:52:28 +08:00
    @pmispig 抱歉哈…提问下(为我的菜而先行道歉,代码能力弱)你这个 version=1.0 的头部能解决什么问题呢?这样大家可以直接访问到 1.0 版本的 prod,而不是 pre ?…
    @CRH 楼主想解决的是,不想每次更新后彻底测试彻底上线,想整个类似热更新一样的东西?大家更新了,就是新 feature,不更新 旧版的也能用 是这样吗?
    whileFalse
        10
    whileFalse  
       2020-06-02 15:22:47 +08:00
    用得着这么复杂?
    启动的时候请求后端:
    get api.xxx.com/boot?client_version=1.0.1&channel=alpha&user_id=12345678
    返回:
    {
    "latest_version": "1.0.2",
    "force_upgrade": false,
    "api_base": "https://prod.api.com/develop"
    }
    whileFalse
        11
    whileFalse  
       2020-06-02 15:23:24 +08:00
    后续请求都用 api_base 拼 url 就行了。
    pmispig
        12
    pmispig  
       2020-06-02 16:55:28 +08:00
    @aut0man 解决的问题是简化你这个切来切去,把简单问题复杂化了
    CRH
        13
    CRH  
    OP
       2020-06-02 17:19:03 +08:00
    @aut0man 我们都会在测试环境做比较彻底的测试的
    出发点是:
    - bug 总有漏网之鱼,如果是严重 bug 会影响所有客户
    - 先让小部分和我们关系比较好的客户用新版本,使用一段时间没有问题了,再让所有客户更新到新版本
    guyskk0x0
        14
    guyskk0x0  
       2020-06-02 19:51:31 +08:00 via Android
    服务端兼容两个版本 App 不就好了,越简单越安全。

    假设当前是 app v1 + server v1,server 升级到 v2 时完全可以兼容一下 v1,新功能一律加开关,出问题立即切回 v1 实现。

    然后 app 可以灰度更新到 v2,有问题就卸载安装回 v1 。

    下一次 server 升级 v3 之前,再要求所有 app 都升级到 v2 。
    shuangya
        15
    shuangya  
       2020-06-02 20:30:01 +08:00 via Android
    首先,虽然说有强制升级 App 的能力,但这个给用户的体验太差了,没有必要还是不要使用最好。
    楼主的方式改造是可行的,但是需要考虑一些问题,一是强制升级体验会很差,二是如果不强制升级,就有维护多个版本的成本。
    说说我们的大概做法:如果接口更改是向下兼容的,只是加了一些字段什么的,一般不会改 URL 。因为迭代频繁的时候,每次都改 URL 成本太高。这种情况下,一般是正式一批服务器,灰度一批服务器,根据特定条件(比如 ID 范围、按所属企业啥的),统一代理把它转发到正式 /灰度服务器。如果出现问题,只需要把转发重新改回线上就可以快速回滚。
    如果是不向下兼容的,那一般会发布为一个新的接口,可以加上版本标志什么的。这个时候灰度其实就不需要后端做什么了,只需要客户端分批升级就行。
    shuangya
        16
    shuangya  
       2020-06-02 20:41:55 +08:00 via Android
    举个例子,为什么说强制更新尽量少用。
    你们的客户公司,老板正在出差,网络状况不太好。这个时候,他需要审核一点资料啥的。然后他点开了 App,很不巧,他被灰度到了,如果不更新就不能使用。然而他的网络情况让他下了好几遍新版本也没有下载成功。
    很显然,他不一定要新版才能完成的工作,却因为强制升级而不能完成了。
    对于 to B 来说,需要更多考虑用户体验,而不需要额外的营销手段留住客户。所以需要尽可能考虑 B 端客户的各种情况,能让客户正常使用是最重要的。
    比如弱网,异地 /跨国漫游……这类 to C 产品不用太关心的因素。
    Foxkeh
        17
    Foxkeh  
       2020-06-02 21:37:56 +08:00 via iPhone
    不敢搞灰发,因为——
    化肥会挥发。黑化肥发灰,灰化肥发黑。黑化肥发灰会挥发;灰化肥挥发会发黑。黑化肥挥发发灰会花飞;灰化肥挥发发黑会飞花。黑灰化肥会挥发发灰黑讳为花飞;灰黑化肥会挥发发黑灰为讳飞花。黑灰化肥灰会挥发发灰黑讳为黑灰花会飞;灰黑化肥会会挥发发黑灰为讳飞花化为灰。黑化黑灰化肥灰会挥发发灰黑讳为黑灰花会回飞;灰化灰黑化肥会会挥发发黑灰为讳飞花回化为灰。
    tookbra
        18
    tookbra  
       2020-06-02 21:46:34 +08:00
    @Foxkeh 大兄弟,笑死我了
    chihiro2014
        19
    chihiro2014  
       2020-06-02 21:50:46 +08:00
    先分 10%的流量给新版本,剩下 90%给旧版本,稳定了再上线
    ica10888
        20
    ica10888  
       2020-06-03 09:26:37 +08:00
    这个叫金丝雀发布吧...
    ica10888
        21
    ica10888  
       2020-06-03 09:38:40 +08:00
    我的,金丝雀发布是灰度发布的别名,感觉金丝雀这个比喻很合适...
    aut0man
        22
    aut0man  
       2020-06-03 09:50:09 +08:00
    @guyskk0x0 我读懂你这个了 浏览下来觉得你这个是实现比较方便且靠谱的:D thanx 学到了
    Ianchen
        23
    Ianchen  
       2020-06-03 09:51:51 +08:00
    Istio 了解下,或者实在不行就按用户来走流量分发啊,就像内测用户与公测用户进的服务器不同,但是这个实际上是后端类似网关一类的服务去分配的。至于 App 是否升级这与灰度无关吧
    CRH
        24
    CRH  
    OP
       2020-06-03 17:18:47 +08:00
    @shuangya 谢谢哈,前面对我们的产品描述得不够仔细,我的锅

    我们的产品是类似于超市里自助结账的设备,给服务员 /顾客操作的,也就是说设置在固定的地点,有固定的营业时间
    强制更新可以放在半夜没有人用的时候,正常情况下可以做到无感
    shuangya
        25
    shuangya  
       2020-06-03 21:50:20 +08:00
    @CRH 如果是这样的话,强制更新也是可行的,但是还是要考虑回滚、更新失败的降级措施(至少保证旧版本可以用)
    总的来说,在服务端进行灰度比较可控一些,如果是推送到端上再发现问题,回滚起来很麻烦(比方说你们推送了一个新版本,白天开始用之后才发现后端的新代码没考虑到一些 case,这个时候靠强制推送来回滚比较麻烦)
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2982 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 11:12 · PVG 19:12 · LAX 04:12 · JFK 07:12
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.