zero downtime deployment
架构前提
服务集群
Cluster Server
节点间负载均衡
Load Balance
服务无状态
Stateless
可以使用redis
、 mysql
等中间件保存会话状态。
向后兼容 Backward Compatibility
场景一:新增字段
有一张User表,在v1.1有name、age两个字段。在v1.2中,新增了一个SQL脚本。
1
2alter table user add address varchar(1024);
update user set address = 'shanghai' where address is null;我们必须保证,在发布v1.2之前,先执行sql脚本,让User表中已经存在address字段。此时v1.1对address字段无感知,因为v1.1不会操作address。
场景二:重命名字段
有一张User表,在v1.1有name字段。
在v1.2中,我们想把name字段改成nickname。为了保证数据库向后兼容,我们新增了一个SQL脚本。
1
alter table user add nickname varchar(1024);
不能直接在脚本中把name改成nickname,因为在v1.1的服务中,用的还是name。
v1.2的code中,也需要做到向后兼容。
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
26class User {
/**
* 在v1.2中,name属性不对外暴露。
*/
@Getter(access = private)
@Setter
private String name;
@Setter
private String nickname;
/**
* 在v1.2中,数据库同时存在name和nickname,旧数据name有值,新数据nickname有值。
* 所以,这里的get方法,需要考虑从两个字段取值。
*/
public String getNickname {
return name == null ? name : nickname;
}
public static List<User> selectAll() {
String sql = "select * from user;";
// setName(); setNickname(); ...
}
}可以看到,在整个v1.2的生命周期中,不管是数据库还是服务端,同时支持name和nickname两个版本。
只有在v1.3中,我们才可以彻底抛弃name。
1
2update user set nickname = name where nickname is null;
alter table user drop name;1
2
3
4
5
6
7class User {
@Getter
@Setter
private String nickname;
}在实际开发中,向后兼容往往比上面这两个场景更加复杂。