ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

不重启不降温:Java应用在Azure上的热迁移实战

不重启不降温:Java应用在Azure上的热迁移实战 先把盘子铺开说一句在Java后端这块摸爬滚打这么多年服务器迁移最让人头疼的从来不是“搬代码”而是“搬状态”。尤其是业务跑起来之后你发现迁移就意味着要重启、要停服、要凌晨三点点着外卖干活。所以当我真正在Azure上把一套Java应用从旧环境热迁移到新环境、全程没有重启、用户毫无感知的时候最大的感受就是——这事儿终于从“玄学”变成了“工程”。今天这篇就来聊聊怎么给Java应用做热迁移重点放在Azure体系下的可落地方案。我会把原理、步骤、坑、以及我实际踩过的雷全部交代清楚。适合正在做上云迁移、跨区域容灾、或者单纯想摆脱“重启恐惧症”的Java工程师和架构师参考。1. 热迁移到底在“迁移”什么别只盯着流量状态才是命门1.1 重启一次代价远不止“几秒钟不可用”很多同学对“重启”这件事比较麻木觉得无非就是服务断几十秒Liveness探针一拉又能跑起来。但实际上对Java应用来说重启的杀伤力是滞后的、隐藏的。先说JIT。Java应用跑久了HotSpot会把热点代码编译成机器码这部分编译优化是运行时逐步积累的。一重启这些编译产物全部作废应用进入冷启动状态CPU飙高、RT变长要花很长时间才能回到之前的性能水平。你以为重启只要1分钟实际上业务要“软趴”十几分钟甚至更久。再就是状态丢失。本地缓存、内存中的Session、Socket长连接、定时任务的状态、分布式锁的持有者……这些都会随进程消失而全部重置。尤其是那些用了本地缓存比如Caffeine、Guava Cache做热点数据挡板的服务重启一次相当于把缓存的“命中率”清零数据库压力瞬间上来运气不好直接拖垮下游。所以热迁移的核心价值不只是“不中断”而是“不降温”——让流量、状态、数据平滑地从一个节点挪到另一个节点整个过程服务保持温热状态用户无感、JIT不丢、缓存不冷。1.2 把“状态”分类才能知道要迁移什么我在做热迁移方案时习惯先把状态拆成三类流量状态客户端连到哪个节点、负载均衡怎么分流的这套由网关和注册中心管。会话状态用户的登录态、购物车、临时业务数据这类必须外置不能留在JVM内存里。数据状态数据库中的业务数据、文件、消息队列中的积压消息。这类是迁移的重头戏。热迁移的本质就是先把数据状态做同步把会话状态外置最后再切换流量状态。顺序错了后面全是事故。很多人在迁移时一上来就切流量结果数据没跟上、Session没外置流量切过去新节点跑了几分钟就开始报错只能仓促回滚。这种问题我见得太多根源就是没有分清楚”流量切换”和“状态迁移”的先后关系。2. 方案选型Azure体系下热迁移的几条路线和取舍2.1 三条主流路径按场景对号入座做热迁移不是一个固定的动作而是一套按场景组合的方案。我自己归纳为三条路线路线A虚拟机/物理机级别的热迁移如果你用的是Azure的IaaS虚拟机或者从本地物理机迁到Azure最直接的工具是Azure Migrate。它支持Agentless和Agent-based两种模式。Agentless意思是Azure通过Hyper-V或VMware的API做内存级复制业务无感知Agent-based则需要每台机器装个agent把磁盘变化增量同步到Azure。好处是粒度粗、覆盖面广连操作系统一起搬应用层完全不用改。代价是它对网络稳定性要求高而且迁移完成后一般还是要做一次快速切换相当于一次小暂停做不到绝对的“不重启”。路线B容器化 滚动发布如果应用已经容器化Docker AKS那热迁移其实变成了“滚动更新”。把新节点加入后端池确认健康之后逐步摘除旧节点。这种方式非常丝滑但前提是应用本身要无状态或者状态全部外置。不然新Pod起来了Session还在旧Pod的JVM里切过去就是一大波报错。路线C应用级手工灰度老系统没法容器化、又不想动底层的时候走的就是纯应用层的灰度迁移。新环境并行部署一套通过负载均衡/网关逐步切流量。这路线的优势是灵活、可控性强回滚只要切流量就行和底层基础设施解耦。缺点是比较考验手工操作和监控能力。2.2 为什么我在Azure上偏向应用级灰度 托管负载均衡组合很多人一听到热迁移就奔着Azure Migrate去觉得“官方的肯定最省心”。实践下来Azure Migrate对整机搬迁确实好用但Java应用往往有自己复杂的拓扑中间件多、依赖多、连接串多整机搬过去容易产生“跑得起来但业务不对”的尴尬局面。我更喜欢的方式是新环境先按目标架构重新部署应用数据层用Azure Database Migration Service或双写同步流量层用Azure Load Balancer或Application Gateway做后端池切换。选这个组合的理由很简单可控性强。每一批流量切换都是显式的发现问题随时切回来。和源代码、CI/CD天然打通。迁移的同时还能顺便规范部署流程。Azure的托管LB和App Gateway的健康探测很成熟切换时可以用探测来控制“流量是否进入新节点”。说白了整机迁移适合基础设施层面的“搬家”而应用级灰度迁移更适合“已经知道新环境长什么样、想按计划切流量”的场景。我做Java应用迁移时90%的case都走后者。3. 动手前的架构体检不解决这四个问题别谈热迁移3.1 会话状态必须外置这是铁律热迁移的大前提是“新节点和旧节点要能无缝互换”。如果Session存在JVM里那新旧节点天然无法互换——因为状态在各自的肚子里。所以第一件事就是把Session搬到Redis这类外部存储里去。Spring Session Redis Data的集成方式很成熟改动成本很低。一般也就加个依赖、加几个配置项、把EnableRedisHttpSession打开。这里有个容易被忽视的细节Redis的序列化方式要统一。默认的JDK序列化在跨版本升级时容易出幺蛾子建议用JSON序列化比如GenericJackson2JsonRedisSerializer不然新老实例读取Session时可能字段对不上。3.2 本地缓存要降级为“可重建的优化层”本地缓存不是不能留但你必须清楚它只是性能优化手段不是数据正确性的依赖。迁移时就算新节点缓存是空的也不能影响业务正确性。所以体检项目是查一遍代码里有没有直接用本地缓存扛业务逻辑的。比如用Caffeine做幂等判断、用本地Map做分布式锁这是重灾区。一旦发现必须改成Redis或数据库实现。我自己项目里就踩过类似坑有个服务用本地Cache记录“用户是否已领取优惠券”平时没问题迁移时新节点缓存是空的用户又领了一次线上事故直接变成资损。后来全面改成Redis Lua脚本才彻底解决。3.3 数据层要有“同步中继”而不是一次性拷贝数据库迁移是热迁移里最需要提前量的一环。你不能在切换流量那分钟才开始导数据必须提前让新库和旧库保持准实时同步。Azure上做这件事首选Azure Database Migration Service支持从自建/其他云/本地数据库在线迁移到Azure SQL Database或Azure Database for MySQL/PostgreSQL。它会先做全量迁移然后做持续的增量同步直到你选择“Cutover”那一刻才把读写切换到新库。如果是自建MySQL/PostgreSQL也可以用原生的主从复制来搭同步链路但会多一层运维工作。关键点是数据同步必须提前跑起来让“追赶延迟”趋近于零再启动流量切换。3.4 文件与对象存储要“迁完再切”Java应用如果用了本地磁盘存文件迁移时一定出问题。上传的图片、生成的报表、导出的Excel这些一旦被请求访问本地路径就失效了。体检时凡是涉及文件读写的地方统一改成对象存储Azure Blob Storage或S3兼容存储。迁移阶段用Blob的异步复制或双写策略先把存量文件同步完再在流量切换时点确认增量追赶完毕。这一步很多人不重视觉得“文件量不大拷一下就行”实际上在线业务文件是持续增长的你必须在切换前就建立好增量同步管道。我在一次迁移中就有过教训存量文件拷完了切换后用户上传的新文件写到了新存储但旧存储里还有一批用户刚上传的文件没过来最后只能写脚本补救非常狼狈。4. 一套可复现的Azure热迁移实操流程4.1 前置准备备份、配置核对、基线监控先做备份。数据库做一次完整备份配置中心导出全量配置应用发布包和镜像打上本次迁移的标签。不要嫌麻烦万一切换后要回滚这些都是救命稻草。然后是配置核对。重点查这几个地方数据库连接串新环境必须指向新库或同步中继库。Redis地址新旧环境是否共用一套Redis如果不共用Session迁移要提前做双读双写。注册中心/配置中心Nacos、Eureka、Apollo或Azure App Configuration的地址是否已更新。JVM参数堆大小、GC策略、元空间大小新旧环境要尽量保持一致否则性能行为会漂移。上线前最好把监控基线打出来QPS、RT、GC频率、CPU、内存、数据库连接数。没有基线切换后出了问题你都不知道“变化是不是迁移引起的”。4.2 数据双写与缓存预热在流量还没切之前先把数据层和新环境的缓存准备好。数据双写我常用的方式是业务代码里通过一个开关控制“双写”行为——主库照常写同时异步写到新库。或者更轻量的做法是用DMS/Canal这类工具监听binlog做增量同步业务零侵入。缓存预热这个特别重要但很多人会忘。新应用节点刚起来Redis缓存是空的如果新环境用的是独立Redis大量请求打过来就会穿透到数据库。我的做法是写一个预热脚本从旧Redis把热点key批量读出来写入新Redis。热点key怎么定义可以用旧Redis的INFO keyspace和SCAN统计结合业务特征来圈也可以简单点把过去24小时被访问过的key都拉一遍。4.3 流量切换借Azure负载均衡做“后端池换血”这一步是整个热迁移的高潮。以Azure Application Gateway为例流程是在Azure Application Gateway里新建一个后端池Backend Pool把新环境的应用节点加进去。配置健康探测Health Probe路径指向你应用的/actuator/health或自定义健康检查接口。调整监听器规则把部分流量切到新后端池建议第一阶段切5%~10%。观察监控指标确认新节点无异常后把比例逐步提升到50%、100%。这里要特别注意健康探测的配置。Azure的探测器和我们常见的K8s探针逻辑不太一样它的Interval、Timeout、Unhealthy Threshold要按应用实际启动时间调好。我曾经遇到一个情况应用启动需要40秒但探测器的超时时间只有10秒结果新节点每次都被误判为不健康永远进不了后端池。常规参数参考如下参数推荐值说明Interval30s别太频繁避免压垮应用Timeout5s超过5秒没响应视为失败Unhealthy Threshold3连续3次失败才摘除节点Healthy Threshold2连续2次成功才恢复在池切流量时我还会刻意打开CGIConnection Draining或类似机制的选项。它允许旧节点在断开前继续处理已建立的请求一段时间避免“正在下单的用户突然连接被掐”。Azure Application Gateway里有Connection Draining设置建议开启超时时间设30~60秒。4.4 流量切完之后旧节点先别急着杀100%流量已经切到新环境不代表旧节点可以立刻销毁。IAAS迁移也罢、应用迁移也罢都有一个“观察期”陷阱你以为切完了实际上还有很多存量连接、延迟请求、报表任务还在旧节点上跑。我一般会给旧节点保留至少24~48小时的“只出不进”状态负载均衡不再分配新流量但节点别关机让它自然把内存中的存量任务跑完。有些长任务跑几个小时很正常你强行关机会导致任务中断、数据不完整。这也是很多人“切完就释放旧资源”然后被业务方投诉的原因。热迁移讲究“软着陆”旧节点慢一点退场比什么都重要。5. 热迁移中最容易翻车的五个场景5.1 JIT冷启动导致的性能“假性劣化”新节点刚上线时因为缺少JIT编译预热性能往往比旧节点差一截。如果你在低流量阶段没发现等切到50%、100%流量时才发现RT飙升那就很难受了。解决办法在服务真正接流量之前主动发送预热请求。写一个预热脚本把线上常见的请求路径循环打一遍让JIT把热点方法编译成机器码。有些人会用-XX:CompileThreshold调低编译阈值来加速预热但生产环境我不建议瞎调这个参数容易引发额外的编译线程开销。最稳的方式还是“真实流量多打几分钟再说”。5.2 WebSocket和长连接被负载均衡“掐断”Java后端很多IM、消息推送场景离不开WebSocket。这类连接一旦建立就绑定在某台机器上。流量切换时新连接会走新节点但旧连接还挂在旧节点上。如果你图的省事直接关旧节点那所有WebSocket连接瞬间断开客户端如果没有重连机制就是大规模掉线事故。我的经验是网关层处理连接迁移或至少确保客户端有指数退避重连机制。服务端也可以做“优雅下线”先通知客户端重连再关闭连接。Spring WebSocket里有SessionDisconnectEvent可以用它来触发“服务端主动踢下线并告知原因”的逻辑让客户端感知到之后秒级重连到新节点。5.3 定时任务在新旧环境“双跑”没做过分布式任务框架如XXL-JOB、ElasticJob统一管理的定时任务迁移时特别容易踩坑新老环境同时跑同一个定时任务数据被重复处理发券、推送、账单这些业务直接出事。原则是定时任务必须加分布式锁或者通过任务调度平台统一控制“新旧环境只有一个能跑”。如果是自研任务最简单的方式是借助Redis的分布式锁锁的key包含任务名和环境标记。切流量前把旧节点上的定时任务全部停掉确认新节点接管后再逐步放开。还可以用ShedLock这类轻量锁配合数据库或Redis只让一个实例执行任务。这个方案对Spring Boot项目侵入极小。5.4 数据库连接池的“预热欠账”数据库连接池HikariCP、Druid不会在应用启动瞬间把连接全部建好而是按需创建。新节点刚切进去时接到的请求多、连接池还没热起来就会频繁创建新连接加上MySQL/PostgreSQL服务端的资源开销看起来就像“数据库被打挂了”。经验做法发布前先用脚本并发请求几次把连接池“暖”起来或者把HikariCP的minimumIdle和maximumPoolSize设成一致让连接池启动时就建好足够连接。这样能显著减少切换瞬间的抖动。5.5 日志和链路追踪不在一个“频道”新环境如果没有和旧环境打通日志采集和链路追踪出了问题你会发现用户报障了但你不知道请求最终落到哪台机器、哪个方法两眼一抹黑。迁移前必须把日志采集、TraceId透传、Metrics监控对新环境就绪。Azure侧可以接Application InsightsJava那边用OpenTelemetry SDK埋点日志统一打到Azure Log Analytics或者自建的ELK。没有可观测性的热迁移就像蒙着眼睛换轮胎。6. 常见问题速查与实操心得6.1 故障速查表现象可能原因解决思路切换后RT飙升JIT冷启动 / 连接池未预热预热脚本、连接池预建用户登录态丢失Session还在旧节点JVM外置到Redis、统一序列化新节点一直被判定不健康探针路径不对/超时太短调整健康探测配置数据出现重复定时任务双跑分布式锁/调度平台统一文件404文件还在旧存储对象存储双写迁移完再切流量WebSocket大面积掉线网关关闭存量连接客户端重连、服务端优雅下线切回旧环境后数据不一致双写补偿没做好建立反向同步、回滚前数据校验6.2 我总结的几条实操原则第一流量可以灰度数据必须强一致。流量切错了能切回来数据错了很难洗白。所以数据同步、校验、补偿这些环节一定要做到位。第二先慢后快敢于停手观察。5%流量阶段多观察一段时间不要急着往上加。我甚至建议切到20%的时候停一个小时看看有没有慢SQL、连接池水位异常、缓存命中率下降等信号。第三每一次切换都要有回滚预案。如果新节点有问题能不能一分钟内把所有流量切回旧节点我见过很多团队说要回滚结果发现旧节点已经被释放了、数据库已经被覆盖了回滚变成了“重新部署”那就不叫热迁移了。第四动用Azure的托管服务别什么都自己扛。负载均衡、数据库迁移、对象存储复制、监控告警这些Azure都提供了成熟的托管方案能省多少事就省多少事。自己搭一套Erlang级别的脚本去复制数据最后大概率被一两个边界case按在地上摩擦。聊到这里热迁移的整体思路和步骤应该比较完整了。我个人的体会是做Java应用的Azure热迁移最大的门槛不是技术方案选型而是你有没有把“状态迁移”和“流量切换”这两件事彻底分开处理。只要状态外置了、数据同步准实时了、流量能按比例灰度剩下的大多是执行层面的细心问题。最后再分享一个小技巧迁移演练一定要做挑业务低峰期把整套流程完整走一遍包括回滚动作都演练到位。真正做迁移那天你会感谢自己提前“彩排”过几次。
返回列表