本文目录导读:

这是一个非常经典的Java部署问题,蓝绿部署的核心思想是通过维护两套完全相同的生产环境(蓝环境和绿环境),利用负载均衡器瞬间切换流量,以实现零停机发布和快速回滚。
以下我将从架构原理、核心实现步骤、以及一个简化版的Java代码/配置示例(基于Spring Cloud Gateway + Nacos)来详细说明如何落地。
核心架构与原理
- 两套环境:
- 蓝环境:当前正在运行旧版本(v1)的生产环境。
- 绿环境:部署新版本(v2)的全新环境,与蓝环境完全隔离(不同的服务器/容器组)。
- 单一入口(负载均衡器/网关):所有用户流量都先经过一个网关组件。
- 流量切换:
- 新版本在绿环境部署并验证通过后,修改网关/负载均衡配置,将100%的流量从蓝环境切到绿环境。
- 蓝环境保留作为备用,一旦绿环境出现问题,可立即将流量切回蓝环境。
基于Java技术的典型实现方案
这里以 Spring Cloud Gateway + Nacos(注册中心/配置中心) + K8s/Docker 为例。
关键组件
- Spring Cloud Gateway:充当流量入口和路由决策者。
- Nacos:作为动态配置中心,存储“当前活跃环境”的标识(如:
current.active.env = green)。 - Kubernetes / Docker Compose:用于管理两套完全隔离的Java应用实例(蓝和绿)。
- Java应用:你的Spring Boot应用,使用
spring.profiles.active区分环境。
详细实现步骤(代码与配置)
步骤1:准备两套Java应用实例
假设你的Spring Boot应用启动时需要指定env参数。
- 蓝环境实例: 启动命令:
java -jar app.jar --spring.profiles.active=blue - 绿环境实例: 启动命令:
java -jar app.jar --spring.profiles.active=green
在K8s中,这通过两个独立的Deployment实现,分别有不同的Labels(如env: blue和env: green)。
步骤2:配置服务注册与发现(Nacos)
两个环境的服务都注册到同一个Nacos集群,但通过元数据(metadata.env: blue)或Service ID的不同Group来区分。
假设我们使用相同的Service ID,但不同元数据:
// application-blue.yml
spring:
application:
name: user-service
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
metadata:
env: blue
// application-green.yml
spring:
application:
name: user-service
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
metadata:
env: green
步骤3:创建负载均衡策略(关键)
在Spring Cloud Gateway中,自定义ReactiveLoadBalancerClientFilter,使其根据Nacos中的动态配置(current.active.env=green),只路由到对应元数据的实例。
-
Nacos配置项:
Data ID:blue-green-configGroup:DEFAULT_GROUPcurrent.active.env=blue
-
自定义负载均衡器(核心代码):
@Component
@Slf4j
public class BlueGreenLoadBalancer implements ReactorServiceInstanceLoadBalancer {
private final NacosDiscoveryProperties nacosDiscoveryProperties;
private final NacosServiceManager nacosServiceManager;
private final ConfigService configService; // Nacos Config Service
// 注入配置服务,用于动态读取 current.active.env
public BlueGreenLoadBalancer(NacosDiscoveryProperties nacosDiscoveryProperties,
NacosServiceManager nacosServiceManager,
ConfigService configService) {
this.nacosDiscoveryProperties = nacosDiscoveryProperties;
this.nacosServiceManager = nacosServiceManager;
this.configService = configService;
}
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
// 1. 从请求中获取服务名
ServiceInstanceListSupplier supplier = (ServiceInstanceListSupplier) request.getContext();
return supplier.get().next().flatMap(instances -> {
// 2. 从Nacos配置中心获取当前应激活的环境(blue 或 green)
String activeEnv = getActiveEnvFromNacosConfig(); // 读取配置项 current.active.env
log.info("当前蓝绿环境配置为:{}", activeEnv);
// 3. 过滤出符合当前活跃环境的实例
List<ServiceInstance> filteredInstances = instances.stream()
.filter(instance -> activeEnv.equals(
((NacosServiceInstance) instance).getMetadata().get("env")))
.collect(Collectors.toList());
// 4. 使用轮询从过滤后的实例中选择一个
if (filteredInstances.isEmpty()) {
log.warn("未找到活跃环境 {} 的实例,降级为所有实例", activeEnv);
// 降级逻辑:如果没有对应环境的实例,就随便选一个(容错)
return Mono.just(new DefaultResponse(instances.get(ThreadLocalRandom.current().nextInt(instances.size()))));
}
int index = ThreadLocalRandom.current().nextInt(filteredInstances.size());
return Mono.just(new DefaultResponse(filteredInstances.get(index)));
});
}
private String getActiveEnvFromNacosConfig() {
try {
// 获取配置,默认值为 blue
return configService.getConfig("blue-green-config", "DEFAULT_GROUP", 5000);
} catch (NacosException e) {
log.error("读取Nacos配置失败,默认使用blue环境", e);
return "blue";
}
}
}
步骤4:配置Gateway路由(无需改)
Gateway配置保持常规服务发现路由即可:
# application-gateway.yml
spring:
cloud:
gateway:
routes:
- id: user-service-route
uri: lb://user-service # 使用负载均衡
predicates:
- Path=/api/user/**
步骤5:执行蓝绿切换
- 部署绿环境:在K8s中启动新的
user-service-greenDeployment,Nacos中会出现两个环境的实例。 - 验证绿环境:通过内网IP或临时域名访问绿环境,进行冒烟测试。
- 切换流量:修改Nacos配置中心的
blue-green-config配置项:- 将值从
blue改为green。 - Gateway中的
BlueGreenLoadBalancer会实时感知到(通过Nacos Config Listener),开始将流量全部路由到env=green的实例。
- 将值从
- 回滚:如果出问题,将配置改回
blue,流量瞬间切回蓝环境。
更简化的方案(不依赖Spring Cloud)
如果你的项目没有使用Spring Cloud,可以使用Nginx + 脚本来实现,Java应用本身只需要保持两套相同代码、不同端口/IP即可:
方案特点
原理:Nginx的upstream指向两个不同的服务器组。
切换:通过修改Nginx配置文件(或使用nginx-plus的API),将proxy_pass从upstream_blue切换到upstream_green。
# nginx.conf
upstream backend_blue {
server 10.0.0.1:8080; # 蓝环境
server 10.0.0.2:8080; # 蓝环境
}
upstream backend_green {
server 10.0.0.3:8080; # 绿环境
server 10.0.0.4:8080; # 绿环境
}
server {
listen 80;
# 初始指向蓝色
location / {
proxy_pass http://backend_blue;
}
}
切换到绿色只需要执行:
# 使用 sed 替换,reload sed -i 's/backend_blue/backend_green/g' /etc/nginx/conf.d/default.conf nginx -s reload
关键注意事项
- 数据库兼容性问题:蓝绿部署最大的坑在数据库,蓝环境和绿环境通常共享同一个数据库(否则成本翻倍),因此新版本的数据库变更(Schema、迁移脚本)必须做到向前兼容(即:新代码可以读写旧Schema,旧代码可以读写新Schema),这通常需要分两步发版:
- 先执行数据库兼容性变更(允许新旧代码共存)。
- 再切换流量。
- 会话状态:如果Java应用依赖Session,需要将Session存储到外部存储(Redis, GemFire),否则切换后用户会掉线。
- 环境隔离成本:蓝绿部署意味着你需要两倍的机器资源(CPU、内存),在云原生时代,这一点容易通过K8s的命名空间或资源配额来管理,但仍然需要预算规划。
- 自动化脚本:建议将切换过程(修改Nacos配置、调用K8s API)编写成一个Shell脚本或Java工具类,通过Jenkins/CI触发,避免手动操作失误。
- 内存/缓存预热:新启动的绿环境(JVM)是冷缓存,切换前,可以先通过健康检查或预热的HTTP请求进行缓存预热,避免切换后瞬间的响应延迟。
| 组件 | 角色 | Java相关实现 |
|---|---|---|
| 负载均衡 | 流量开关 | Nginx、Spring Cloud Gateway、Nacos |
| 配置中心 | 动态切换信号 | Nacos、Apollo |
| 服务实例 | 待切换的主体 | Spring Boot应用,通过env元数据标识 |
| 基础设施 | 环境隔离 | K8s(两个独立的Deployment/Service)或不同IP的服务器 |
核心一句话:蓝绿部署在Java中的实现,本质上是负载均衡器+动态配置,将流量从一组标注为“蓝”的实例,瞬间切到另一组标注为“绿”的实例。