前言
- 前后花了两周多个时间完成了
Spring Boot 1.5.6.RELEASE & Spring Cloud Dalston.SR4
升级到 Spring Boot 2.0.6.RELEASE & Spring Cloud Finchley.SR2 & spring-cloud-netflix 2.0.2.RELEASE
的工作。
- 总结一下遇到的一些问题
一些问题的总结
- 首先 1.x和2.x的所有的服务注册,服务发现,灰度调用,服务调用,zuul网关等等组件核心都是兼容的。so大胆的升级吧。
- 其次maven pom变化较大,主要是netifilx的artifactId变化比较多,其余的变化都不是太大,这都可以通过
spring-cloud-netflix-dependencies
pom中找到。
- 然后是feign的变化比较大,整个包名发生了变化。
- NotBlank,NotEmpty 现在已经纳入了JSR303了,不需要在使用hibernate提供的注解了。
一些建议
- 建议统一抽象出一个业务服务使用pom依赖项目,并打包发布维护起来,比如我们这里就叫
my-server-dependencies
的这么一个pom项目。这样做有几个好处:
- 第一个是通过将版本统一管理起来了,方便对所有的基础服务jar包进行升级,而且能够完成版本的基础依赖管理。
- 第二个是能够避免掉由于未付项目过多之后导致的依赖版本混乱。
- 第三个是能够将maven插件统一的配置,比如说
docker打包插件
,fatjar插件
,compiler插件
,能统一的对他们进行控制和配置,并通过properties暴露出集体的调优指标。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
<!--比如说我我在 dependencies 中定义了很多 props,这些是通用的服务配置 -->
<properties>
<prod.jvm.Xms>1G</prod.jvm.Xms>
<prod.jvm.Xmx>1G</prod.jvm.Xmx>
<prod.jvm.g1.newp>5</prod.jvm.g1.newp>
<prod.jvm.g1.maxp>60</prod.jvm.g1.maxp>
</properties>
<!--
比如说我们通过可视化监控发现,某个业务服务访问比较多对象生成的比较快,由于默认配置的堆太小,导致GC触发的比较频繁
由于默认的G1MaxNewSizePercent为60%,我们通过可视化监控发现odl区分配的40%堆空间的利用率不到10%。
那么这时候可以通过调节G1的G1MaxNewSizePercent和增大一些JVM的最大内存,以此来减少GC触发频率和更高的资源利用率
那么根据以上结论我们就需要修改一下相关配置。由于我们把这些关键配置都通过props暴露出来了,业务项目只需要如下几个props修改,就完成了我们想要的。
-->
<properties>
<prod.jvm.Xms>4G</prod.jvm.Xms>
<prod.jvm.Xmx>4G</prod.jvm.Xmx>
<prod.jvm.g1.newp>40</prod.jvm.g1.newp>
<prod.jvm.g1.maxp>80</prod.jvm.g1.maxp>
</properties>
|
- 第四个是能够将一些必带的包默认激活,比如说
lombok
spring-boot-starter-actuator
micrometer-registry-prometheus
等。
- 第五个建议是将profiles 统一定义在parent中,这样方便gitlab-ci.yml 文件的统一处理。
POM Change
- parent 目前最新版是 2.0.6.RELEASE 点击这里获取最新版
- 这里建议使用2.0.5+的spring boot 不然会有个DataSource的bug导致无法启动
1
2
3
4
5
|
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.6.RELEASE</version>
</parent>
|
application.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
spring:
multipart:
max-file-size: 10Mb # 旧
servlet:
multipart:
max-request-size: 10MB
max-file-size: 10MB # 新配置
# 开启新的指标
management:
endpoints:
web:
exposure:
include: "*"
|
JSR303
org.hibernate.validator.constraints.NotBlank
==> javax.validation.constraints.NotBlank
2.org.hibernate.validator.constraints.NotEmpty
==> javax.validation.constraints.NotEmpty
ErrorController
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
|
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<!-- Import dependency management from Spring Boot -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.0.6.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-dependencies</artifactId>
<version>2.0.2.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
|
Eureka Server
- eureka server 需要将artifactId更新
- 旧的artifactId 是
spring-cloud-starter-eureka-server
- 新的artifactId
spring-cloud-starter-netflix-eureka-server
- 更新完成之后的完成eurake-server配置如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
<!-- 为方便prometheus拉取数据的一个eurake到consul的适配器-->
<dependency>
<groupId>at.twinformatics</groupId>
<artifactId>eureka-consul-adapter</artifactId>
<version>${eureka-consul-adapter.version}</version>
</dependency>
</dependencies>
|
Eureka Client
1
2
3
4
5
6
7
8
9
10
11
12
|
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
|
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
|
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<!--这里排除掉jersey的依赖 spring cloud 会默认构建一个resttpml替代-->
<exclusions>
<exclusion>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-client</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-core</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jersey.contribs</groupId>
<artifactId>jersey-apache-client4</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
|
Config client
1
2
3
4
5
6
7
8
9
10
11
|
<!--旧的依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<!--更新之后的依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
|
Feign
Feign的POM更新
- 上面提到说feign的变化是最大的,如下是具体变化
1
2
3
4
5
6
7
8
9
10
|
<!-- 旧的 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
<!-- 新的 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
|
Feign 中的注解类
org.springframework.cloud.netflix.feign.FeignClient
==> org.springframework.cloud.openfeign.FeignClient
org.springframework.cloud.netflix.feign.EnableFeignClients
==> org.springframework.cloud.openfeign.EnableFeignClients
feign 默认的jackson配置会导致服务直接调用的抛出如下异常
1
|
Can not deserialize value of type java.util.Date from String "2018-11-02T04:14:56.761+0000": not a valid representation (error: Failed to parse Date value '2018-11-02T04:14:56.761+0000': Unparseable date: "2018-11-02T04:14:56.761+0000")
|
- 原因是因为默认的jackson日期格式无法解析成
yyyy-MM-dd HH:mm:ss
,
- 默认的jackson配置也会把null值的属性序列化,这样会导致无用的字符串开销。所以这里我们需要配置一下fegin默认的Encoder
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
@Bean
public feign.codec.Encoder feignEncoder() {
return new SpringEncoder(() -> httpMessageConverters());
}
private HttpMessageConverters httpMessageConverters() {
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
mapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper.enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL);
mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
mapper.setTimeZone(TimeZone.getTimeZone("GMT+8:00"));
// 由于我们的feign只用于对内的请求,所以这里我们只需要使用Jackson的converter,所以new的时候第一个参数填false,排除掉默认的
return new HttpMessageConverters(false,Arrays.asList(new MappingJackson2HttpMessageConverter(mapper)));
}
|
#ZUUL
The default HTTP client used by Zuul is now backed by the Apache HTTP Client instead of the deprecated Ribbon RestClient. To use RestClient or okhttp3.OkHttpClient, set ribbon.restclient.enabled=true or ribbon.okhttp.enabled=true, respectively. If you would like to customize the Apache HTTP client or the OK HTTP client, provide a bean of type ClosableHttpClient or OkHttpClient.
Ribbon 灰度调用
- 之前有提到过通过扩展ribbon支持灰度,由于2.0的spring boot 默认在eurake的matedata重携带了一些稀奇古怪的东西,导致我们之前的代码不能用了,修改后的结果如下
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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
|
public class MetadataAwareRule extends ZoneAvoidanceRule {
@Override
public Server choose(Object key) {
final RibbonFilterContext context = RibbonFilterContextHolder.getCurrentContext();
ILoadBalancer lb = getLoadBalancer();
final List<Server> allServers = lb.getAllServers();
// 存放已打标签但不满足标签的server
final List<Server> metaServers = new ArrayList<>();
// 存放未标签的server
final List<Server> noMetaServers = new ArrayList<>();
// 匹配成功的server
final List<Server> matchedMetaServers = new ArrayList<>();
final Map<String, String> attributes = context.getAttributes();
// 取得接口端传入的参数
final String inputDeveloper = attributes.get("developer");
for (Server server : allServers) {
if (server instanceof DiscoveryEnabledServer) {
final DiscoveryEnabledServer discoveryEnabledServer = (DiscoveryEnabledServer) server;
final Map<String, String> metadata = discoveryEnabledServer.getInstanceInfo().getMetadata();
final String developer = metadata.get("developer");
// 如果没有meta数据 表示是测试服务上的地址
if (developer == null || developer.equals("")) {
// 存放并没有打标签的server
noMetaServers.add(server);
} else {
// 如果匹配成功开发者直接调用
if (inputDeveloper != null && (!"".equals(inputDeveloper)) && developer.equals(inputDeveloper)) {
matchedMetaServers.add(server);
} else {
// 存入server有标签但是不匹配的server
metaServers.add(server);
}
}
}
}
//优先走自定义路由。即满足灰度要求的server
if (!matchedMetaServers.isEmpty()) {
com.google.common.base.Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(matchedMetaServers, key);
if (server.isPresent()) {
return server.get();
} else {
return null;
}
}
// 如果没有匹配成功的则走
else {
if (!noMetaServers.isEmpty()) {
com.google.common.base.Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(noMetaServers, key);
if (server.isPresent()) {
return server.get();
} else {
return null;
}
} else {
// 似情况打开
return null;
// com.google.common.base.Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(metaServers, key);
// if (server.isPresent()) {
// return server.get();
// } else {
// return null;
// }
}
}
}
}
|