一、灰度发布
灰度发布是指在系统迭代的时候一种平滑过度上线发布方式。灰度发布是在原有的系统的基础上面,额外增加一个新版本,这个新版本包含新上线的需要验证的功能,通过负载均衡引入部分流量到新版本的应用上,如果在这个过程中没有出现问题,便可以平滑地把线上的应用一步步替换成新的版本,这样就完成了一次灰度发布。通过灰度发布的方式可以在用户无感的情况下完成系统发版升级。
这里使用一个开源的实现:ribbon-discovery-filter-spring-cloud-starter
在Eureka中有两种metadata:
- 标准元数据:主要是服务的各种信息。如服务IP、端口、服务健康状态、续约信息等。
- 自定义的元数据:往Eureka注册的服务可以通过
eureka.instance.metadata-map.<key>=<value>
来配置,内部就是一个map来保存的。可以配置在远程的服务,也可以随着服务的注册保存到Eureka注册表中。
基于Eureka的元数据实现完成灰度发布的原理是:通过获取Eureka的元数据信息,根据元数据信息的识别,最后在路由规则进行负载均衡。
2.1 新建cloud-service-usercenter
依赖:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- springboot web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-tomcat</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<!--不用Tomcat,使用undertow -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
<dependency>
<groupId>io.undertow</groupId>
<artifactId>undertow-servlet</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
|
配置文件:这里通过配置文件配置三份配置,-1随机端口,node1和node2是v1版本,node3是v2版本。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
server:
port: 8877
spring:
profiles: node1
application:
name: usercenter
eureka:
client:
serviceUrl:
defaultZone: http://${eureka.host:127.0.0.1}:${eureka.port:8671}/eureka/
instance:
prefer-ip-address: true
metadata-map:
version: release
---
server:
port: 8899
spring:
profiles: node2
application:
name: usercenter
eureka:
client:
serviceUrl:
defaultZone: http://${eureka.host:127.0.0.1}:${eureka.port:8671}/eureka/
instance:
prefer-ip-address: true
metadata-map:
version: release
---
server:
port: 8866
spring:
profiles: node3
application:
name: usercenter
eureka:
client:
serviceUrl:
defaultZone: http://${eureka.host:127.0.0.1}:${eureka.port:8671}/eureka/
instance:
prefer-ip-address: true
metadata-map:
version: gray
|
Controller:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@RestController
@RequestMapping("user")
public class UsercenterController {
@Value("${spring.profiles}")
private String profile;
@Value("${server.port}")
private Integer port;
@GetMapping("list")
public String list() {
return "active " + profile + " port " + port;
}
}
|
启动类:
1
2
3
4
5
6
7
8
|
@SpringBootApplication
@EnableDiscoveryClient
public class UsercenterApplication {
public static void main(String[] args) {
SpringApplication.run(UsercenterApplication.class, args);
}
}
|
2.2 Zuul网关添加路由和依赖
配置文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
server:
port: 88
spring:
application:
name: zuul-server
eureka:
client:
serviceUrl:
defaultZone: http://${eureka.host:127.0.0.1}:${eureka.port:8671}/eureka/
instance:
prefer-ip-address: true
logging:
level:
org.springframework.cloud.netflix: debug
zuul:
routes:
service-a:
path: /usercenter/**
serviceId: usercenter
|
依赖:
1
2
3
4
5
|
<dependency>
<groupId>io.jmnarloch</groupId>
<artifactId>ribbon-discovery-filter-spring-cloud-starter</artifactId>
<version>2.1.0</version>
</dependency>
|
编写灰度发布过滤器:当携带请求gray_switch
并且值为open
时就会去请求灰度版本的应用。否则就是请求release
版本的应用。
现实中我们可以通过使用Nginx,然后将部分流量添加请求头,这样就可以切一部分的流量去做灰度了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
@Component
public class GrayFilter extends ZuulFilter {
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder() {
return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;
}
@Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
return !ctx.containsKey(FORWARD_TO_KEY) && !ctx.containsKey(SERVICE_ID_KEY);
}
@Override
public Object run() throws ZuulException {
HttpServletRequest request = RequestContext.getCurrentContext().getRequest();
String mark = request.getHeader("gray_switch");
if (StringUtils.isNotBlank(mark) && "open".equals(mark)) {
RibbonFilterContextHolder.getCurrentContext().add("version", "gray");
} else {
RibbonFilterContextHolder.getCurrentContext().add("version", "release");
}
return null;
}
}
|
2.3 启动测试
分别启动Eureka、Zuul和UsercenterApplication。
在启动UsercenterApplication的使用指定active profile
:
访问接口时
http://localhost:88/usercenter/user/list