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

请问你们是如何实现服务不中断的代码发布更新的

  •  
  •   brader · 2023-03-10 15:38:38 +08:00 · 5828 次点击
    这是一个创建于 624 天前的主题,其中的信息可能已经有所发展或是发生改变。
    目前我知道的一个自动化的方式就是 k8s 的滚动更新。

    大家实际生产中,应该不是每个人都有使用 k8s 吧?
    像你们 java 、go 这些语言,发布新版本的时候,如果不用 k8s ,你们是怎么实现平滑更新过度的,还是直接暴力重启?
    59 条回复    2023-03-14 09:51:04 +08:00
    lincanbin
        1
    lincanbin  
       2023-03-10 15:40:42 +08:00
    从名字服务里摘掉发布节点,然后等一会儿这个节点就没流量了,就可以发布了。
    brader
        2
    brader  
    OP
       2023-03-10 15:44:36 +08:00
    @lincanbin 额,有点没明白,你用的是代码框架,或者代码自实现的平滑重启效果吗
    seers
        3
    seers  
       2023-03-10 15:45:03 +08:00 via Android
    方法多了去了,Nginx 调整下集群权重
    LeeReamond
        4
    LeeReamond  
       2023-03-10 15:45:03 +08:00
    @lincanbin 名字服务,nameserver ?现代汉语属实是让人摸不到头脑了
    moshiyeap100
        5
    moshiyeap100  
       2023-03-10 15:45:32 +08:00
    比如一个服务有 3 个实例。

    step1: 从注册中心下线 A ,A 没流量了开始更新 A

    更新后,观察 A 是否正常。 如果正常继续更新下一个服务,不正常就下线回滚。


    我们就是这么原始。
    ZSeptember
        6
    ZSeptember  
       2023-03-10 15:47:08 +08:00
    首先看你有几个实例,至少两个实例才能做到
    lincanbin
        7
    lincanbin  
       2023-03-10 15:49:33 +08:00
    @LeeReamond 也可以叫注册中心吧,反正叫法挺多的,就是维护一个服务的实例列表的一个服务
    tmtstudio
        8
    tmtstudio  
       2023-03-10 15:50:38 +08:00   ❤️ 5
    php 无所畏惧🤗
    SanYuan
        9
    SanYuan  
       2023-03-10 15:50:40 +08:00
    多套环境,A\B 互为蓝绿环境,用户全拉到 A 然后发布 B 完事用户切到 B
    LeegoYih
        10
    LeegoYih  
       2023-03-10 15:51:49 +08:00
    新实例启动完成后,Nginx 配置改为新实例的地址 reload 配置,然后销毁旧实例
    lincanbin
        11
    lincanbin  
       2023-03-10 15:52:47 +08:00
    除了名字服务里来剔除发布节点流量的方法外,reuseport 也是一个很常见的方式。
    libook
        12
    libook  
       2023-03-10 15:53:11 +08:00
    一般正式环境业务不会但节点跑的,多节点就需要负载均衡,那么只需要手动调整负载均衡权重,让一台机器没有入站流量了,再将这台停机升级,恢复流量再处理下一台。

    可以用脚本或程序自动化,持续部署方案比 k8s 出来早很多年就很成熟了。
    daoyu
        13
    daoyu  
       2023-03-10 15:53:52 +08:00
    蓝绿部署、金丝雀部署
    photon006
        14
    photon006  
       2023-03-10 15:55:08 +08:00
    node.js ,小脚本用 pm2 reload ,跟 nginx reload or kill -HUP 类似

    常规项目用 docker service ,参考:

    https://linuxhandbook.com/update-docker-container-zero-downtime
    https://www.tines.com/blog/simple-zero-downtime-deploys-with-nginx-and-docker-compose
    brader
        15
    brader  
    OP
       2023-03-10 16:04:09 +08:00
    @seers 这种很难实现自动化吧?
    brader
        16
    brader  
    OP
       2023-03-10 16:05:34 +08:00
    @moshiyeap100 你们有弄了注册中心,还是蛮复杂的,然后你说的过程,你们是实现了自动化构建发布过程,还是手动更新啊
    brader
        17
    brader  
    OP
       2023-03-10 16:06:07 +08:00
    @ZSeptember 多个实例可以很容易实现自动化更新吗?
    brader
        18
    brader  
    OP
       2023-03-10 16:06:40 +08:00
    @tmtstudio 确实 PHP 更新比较方便,就简单场景,我们直接 git pull 也很少出问题
    brader
        19
    brader  
    OP
       2023-03-10 16:07:27 +08:00
    @SanYuan 就是这个切的过程,不用 k8s 的话,你们实现自动化的架构是怎么实现的?还是单纯手动更新的
    brader
        20
    brader  
    OP
       2023-03-10 16:09:02 +08:00
    @libook 好像网上比较少看到这种架构的教程或者脚本实现啊,自己实现的话,感觉又不那么容易
    brader
        21
    brader  
    OP
       2023-03-10 16:10:42 +08:00
    @LeegoYih 我想到的比较简单的处理方式也是这样的,但是这个方式手动好操作,要做成自动化,不容易吧?
    LeegoYih
        22
    LeegoYih  
       2023-03-10 16:13:19 +08:00
    @brader 不知道你有没有使用类似 Jenkins 之类的工具,结合这些工具还是挺方便的,手写脚本确实比较麻烦
    brader
        23
    brader  
    OP
       2023-03-10 16:15:44 +08:00
    @LeegoYih Jenkins 我用过,但是它的流水线部署本质上,和你写 shell 或者代码,没有很大区别吧,只是它结合了比较多的插件和功能。 还是要自己实现自动化不中断的流程啊
    libook
        24
    libook  
       2023-03-10 16:17:52 +08:00
    @brader #20 运维写几个 shell 脚本通常就可以搞定了。有些 CI/CD 工具,比如 jenkins ,也可以看看。
    sujin190
        25
    sujin190  
       2023-03-10 16:21:48 +08:00
    之前我们的做法是自己再外面套了层守护进程,有守护进程来打开端口,然后创建子进程继承过去,然后重启的时候守护进程先创建新的进程,然后给旧的进程发送停止信号,旧进程会先关闭监听端口,然后等待所有请求处理完后慢慢退出,这样就可以实现完全平滑无异常重启更新了,这个好处是和 nginx 负责均衡啥的都不相干,应用自己就能完成,相对简单

    不过很可惜像 supervisor 啥都不支持这个功能,其实 supervisor 完全可以打开端口后放在固定 fd 上,比如 3 ,或者可以通过参数传递 fd 的值,然后启动子进程后,子进程直接通过 fd 创建 socket 就好了
    brader
        26
    brader  
    OP
       2023-03-10 16:23:54 +08:00
    @sujin190 这种方案就相当于项目代码自实现的 reload 效果了吧,麻烦点就是每个项目都要自己实现这样一个功能
    Alliot
        27
    Alliot  
       2023-03-10 16:31:23 +08:00
    Nginx upstream + healthcheck

    假设 有 a b 2 个节点,发布时先将 A 置为 unhealth 状态,流量全部调度到 b 发布完成后置为 health ,同理发布 b 即可。
    sujin190
        28
    sujin190  
       2023-03-10 16:33:35 +08:00
    @brader 对,不是啥大服务,单节点又没注册中心啥的,网关负载均衡啥的也不在本机,流量不高不低,想随时随意可以重启没啥异常,搞啥都感觉费劲。。
    getoffworkontime
        29
    getoffworkontime  
       2023-03-10 16:37:14 +08:00
    摘除旧服务的时候, 怎么保证旧服务中的任务已经执行完?
    vincent7245
        30
    vincent7245  
       2023-03-10 16:43:36 +08:00
    服务器是分布式的,滚动升级
    brader
        31
    brader  
    OP
       2023-03-10 16:44:52 +08:00
    @Alliot 我手动操作也是采用类似这样的方案,就是自动化好像不好实现
    anonymous2351d00
        32
    anonymous2351d00  
       2023-03-10 17:01:45 +08:00
    做一个 流量转发的中间层 成为 A ,你的服务 v1 版本称为 B1
    假如 A -> B1 现在要发 B2 版本
    保证 A -> B1 的流量控制 部署 B2 ,流量情况 nil -> B2
    部署 B2
    以非常非常快速的手速,光一样的敲动命令来 调整流量转发层 A -> B2
    此时 nil -> B1
    这个版本就好了
    malusama
        33
    malusama  
       2023-03-10 17:12:48 +08:00
    @getoffworkontime 优雅退出
    awanganddong
        34
    awanganddong  
       2023-03-10 17:33:03 +08:00
    这个问题我昨天还在思考。
    比如我们公司用的腾讯云的 lbs

    首先第一步发送 curl ,将 clb 中配置的后端服务的权重置为 0 ,这时候不存在新增请求,只存在存量请求。这个过程中服务器 clb 还可以转发响应。

    接下来就开始关闭 go 进程,这时候 go 代码可以配置优雅退出。等原来 go 进程退出后,就开始启用新的 go 进程。
    cyningxu
        35
    cyningxu  
       2023-03-10 17:45:17 +08:00
    多实例+注册中心?
    ccagml
        36
    ccagml  
       2023-03-10 19:07:08 +08:00 via Android
    好奇,如果是类似数据库表结构变了,可以这样滚动更新吗
    pengtdyd
        37
    pengtdyd  
       2023-03-10 19:27:27 +08:00
    半夜 3 点钟,断线 30 分钟没什么大不了,影响不了啥业务,不要把公司的业务想的那么重要,就像你穿了一件新衣服走在大街上,你以为别人都是注视你,其实别人压根不关心你穿的啥,别把自己想的太重要。
    brader
        38
    brader  
    OP
       2023-03-10 20:54:13 +08:00 via Android   ❤️ 1
    @ccagml 你说的这个问题,和实现平滑更新没很大关系。你这个问题,更多的应该在代码层面实现,做一下版本控制,我这几年一直是这么干的,没遇到啥问题
    samun
        39
    samun  
       2023-03-10 20:59:17 +08:00
    @lincanbin 如果有耗时比较耗时的操作在跑呢 下线了也得等一段时间才能关
    dnsjia
        40
    dnsjia  
       2023-03-10 21:04:18 +08:00
    Features
        41
    Features  
       2023-03-10 21:17:58 +08:00
    @tmtstudio PHP 是会受到影响的,高并发场景一堆的 502
    whileFalse
        42
    whileFalse  
       2023-03-10 21:22:11 +08:00 via iPhone
    你有负载均衡器吧!
    Ericcccccccc
        43
    Ericcccccccc  
       2023-03-10 22:55:56 +08:00
    我估计你想找的是 服务发现, 搞明白了这个以及相关问题滚动发布就差不多解决了.
    cdlnls
        44
    cdlnls  
       2023-03-10 23:03:55 +08:00
    用 docker swarm / service ,比 k8s 简单,方便管理多个节点。发布也是滚动更新。
    lincanbin
        45
    lincanbin  
       2023-03-10 23:30:28 +08:00 via Android
    @samun 那就等他跑完了再发布啊,这种发布工具很容易设计的。
    runze
        46
    runze  
       2023-03-11 00:05:46 +08:00
    @brader #31 APISIX 之类的网关适合这个场景
    daysv
        47
    daysv  
       2023-03-11 02:08:03 +08:00
    问题在于你们运行 DDL DML 怎么实现两个服务无缝切换的? 是搞了两套库吗?
    xiaowoniukai
        48
    xiaowoniukai  
       2023-03-11 07:30:12 +08:00 via iPhone
    apisix
    samun
        49
    samun  
       2023-03-11 10:19:55 +08:00
    @lincanbin 要搞成自动化这就是一个不确定因素了
    hhjswf
        50
    hhjswf  
       2023-03-11 10:33:11 +08:00 via Android
    @pengtdyd 搞不中断部署就是为了不在半夜部署啊。。
    hhjswf
        51
    hhjswf  
       2023-03-11 11:04:10 +08:00 via Android
    @brader 你们业务量不够吧,单表数据亿为单位,你修改表结构会锁表很长时间,这段时间怎么代码层怎么处理这张表 crud ?还是要通过数据库集群做
    dreamusername
        52
    dreamusername  
       2023-03-11 12:21:56 +08:00
    微服务间调用有服务发现,不论是 nacos 类的还是 kubernetes service 类的,这样保证服务间调用可以访问到可用服务;
    服务需要有优雅退出,这样保证任务执行完毕后退出,防止任务中断。
    对外服务,一般为 API 类,有负载均衡,服务在异常状态不路由流量(未就绪、退出等状态)保证用户访问到可用节点。
    chrosing
        53
    chrosing  
       2023-03-11 12:39:49 +08:00
    我们是搞到预发 也和楼上说的两个环境类似。 预发和正式都是同一个库 先更新到预发 然后引导流量慢慢迁移过来 测试没有问题后 在同步服务到正式 然后进行更新 再之后服务完全没问题 流量再引导过去
    bushenx
        54
    bushenx  
       2023-03-12 03:29:16 +08:00 via Android
    项目背景 基于 grpc 的后端业务
    1. 必不可少的就是服务需要支持优雅退出,现在项目用到得 grpc 框架流支持。
    2. 对于无状态服务直接发布一个新节点,将旧节点从注册中心移除就可以了。
    3. 有状态服务我们采用主备机的方式发布新版本。
    lincanbin
        55
    lincanbin  
       2023-03-12 11:15:28 +08:00 via Android
    @samun 所以说服务开发要同意框架,框架要跟运维系统打通。
    这种耗时操作在框架里挂钩子,执行完框架通知运维系统就完全打通了。
    brader
        56
    brader  
    OP
       2023-03-13 09:49:01 +08:00
    @hhjswf 这个就要具体情况具体分析了,即使你的项目非常庞大、访问量非常多,那么也不太可能出现你一个项目的所有表都是很大数据量的情况,所以只是偶尔会接到需要更新这种表的需求,以下两种方案我都在生产中实践过并取得成功:
    方案一:在低峰期更新表结构(看自己的项目情况,像我们在 22 点-05 点都是低峰期)
    方案二:copy 出一张新表并改好新表结构,然后 insert 新表,( insert 新表,rename 旧表,rename 新表),注意前面有 2 个 insert 过程,第一次 insert 完看下最大 id ,然后这就是第二次的条件,分 2 次 insert 是因为第一次 insert 数据量大,耗时久,后续又产生了很多增量数据。
    brader
        57
    brader  
    OP
       2023-03-13 09:53:53 +08:00
    @daysv 我日常的做法是分 2 步走,代码发布前,先更新表结构,思考做前后兼容的变动(实在实现不了的另做打算,很少这种情况吧),新代码里也做版本控制判断,这样我新旧版本都不影响
    hhjswf
        58
    hhjswf  
       2023-03-13 23:45:02 +08:00 via Android
    @brader 方案二你只考虑 insert 吗?有没有可能第二次 insert 期间,系统对第一次 insert 的记录进行删改操作,能同步过去吗
    brader
        59
    brader  
    OP
       2023-03-14 09:51:04 +08:00
    @hhjswf 首先不排除这个可能,因为每人的业务情况不同,对于我来说,几乎没有这样的烦恼,因为第一次 insert 的一般是时间比较靠前的历史数据,不会变动,第二次的近期数据,因为操作时间非常短暂,出现概率就低。
    如果你的业务对历史操作频繁,或者其他不适用情况,就建议你晚上更新,要求强一致就和领导申请锁写。
    没有一套能解决所有问题的一成不变的方案,在于你怎么灵活应用和变通。

    还有你们有使用阿里云的 RDS 、收费版 DMS 的话,建议把他们的工具利用起来,他们有个无锁变更功能还是很强大的,阿里介绍的实现原理大概就是 建新表->追全量->追期间变动的 binlog ,全程自动化透明,我们只要递交变更任务就好
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2802 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 75ms · UTC 13:59 · PVG 21:59 · LAX 05:59 · JFK 08:59
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.