Eureka V2版
最近在做微服务升级,目标是Spring cloud 升级至 v2022.0.4 ,Spring boot升级至 3.1.5。当然注册中心也需要同步升级 v2.0.1。这些版本下都已经支持jdk17+。
一、Eureka地址
打开Eureka注册中心的开源地址:https://github.com/Netflix/Eureka
我们发现Eureka又开始更新了,最近也发布了最新版本v2.0.1。具体更新了哪些信息呢?
我们来看一下他的新特性:
Eureka Server HTTP API和有线数据格式在2.0.1版本中没有更改。Eureka服务器和客户端应该在网络上前后兼容。此版本主要是为了与Spring Framework 6.0和Spring Boot 3.0的兼容性以及对Jakarta EE 9的更改。
- Eureka 2.0.1 Java客户端API与1.x不向后兼容。
- Eureka 1.x包括对Jersey 1的支持和对Jersey 2的可选支持。在许多情况下,Jersey 1是默认的,并被烘焙到eureka核心和eureka客户端模块中。具有默认客户端已被删除。如果没有提供其他实现(例如Spring Cloud对RestTemplate实现的支持),那么现在应该使用新的eureka-client-jersey3和eureka-core-jersey3。
- DiscoveryClient构造函数已更新为具有新的必需TransportClientFactories参数。Eureka 2.0.1 Java客户端API的大部分保持不变。所有对javax.类的引用都已更改为jakarta.类,并添加了相应的模块。
- Eureka服务器管理器模块已删除。目前不支持JakartaEE版本的@Inject。
- Eureka服务器模块当前未构建功能性WAR文件。建议使用Spring Cloud Netflix作为Spring Boot应用程序来创建Eureka 2.0.1服务器。
现在构建需要JDK 11,因为有些测试需要Jetty,而支持Jakarta EE 9的Jetty版本需要Java 11。该版本仍然以Java 8为目标。
二、Eureka配置使用
2.1 maven引入
<!--使用它之后项目必须增加一个bootstrap.yml文件进行配置-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
2.2 主方法引入
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
2.3 配置
# 注册中心的配置
eureka:
environment: test
client:
serviceUrl:
defaultZone: http://localhost:8719/eureka/
register-with-eureka: true
fetch-registry: true
registry-fetch-interval-seconds: 5
instance:
instance-id: ${spring.cloud.client.ip-address}:${server.port}
# 只有prefer-ip-address=true时才会生效
ip-address: ${spring.cloud.client.ip-address}
# 心跳时间,即服务续约间隔时间(缺省为30s)
lease-renewal-interval-in-seconds: 5
# 发呆时间,即服务续约到期时间(缺省为90s)
lease-expiration-duration-in-seconds: 15
# 设置微服务调用地址为IP优先(缺省为false)
prefer-ip-address: true
server:
enable-self-preservation: true
eviction-interval-timer-in-ms: 3000
renewal-percent-threshold: 0.55
三、填坑信息
我们会遇到一下错误信息:
客户端报错信息:
2023-12-14T13:32:25.126 +0800|[DiscoveryClient-HeartbeatExecutor-22raceId][DiscoveryClient-HeartbeatExecutor-22]|WARN|RetryableEurekaHttpClient.execute():130|Request execution failed with message: com.fasterxml.jackson.databind.exc.MismatchedInputException: Root name 'timestamp' does not match expected ('instance') for type [simple type, class com.netflix.appinfo.InstanceInfo] at [Source: (com.sun.jersey.client.apache4.ApacheHttpClient4Handler$HttpClientResponseInputStream); line: 1, column: 2] (through reference chain: com.netflix.appinfo.InstanceInfo["timestamp"]) 2023-12-14T13:32:25.127 +0800|[DiscoveryClient-HeartbeatExecutor-22raceId][DiscoveryClient-HeartbeatExecutor-22]|ERROR|DiscoveryClient.renew():907|DiscoveryClient_服务名称/ip:端口 - was unable to send heartbeat! com.netflix.discovery.shared.transport.TransportException: Cannot execute request on any known server
服务器报错信息
2023-12-14T09:41:40.883+08:00 INFO 17872 --- [a-EvictionTimer] c.n.e.registry.AbstractInstanceRegistry : Evicting 1 items (expired=1, evictionLimit=1) 2023-12-14T09:41:40.883+08:00 WARN 17872 --- [a-EvictionTimer] c.n.e.registry.AbstractInstanceRegistry : DS: Registry: expired lease for JDK-17-TEST/ip:端口 2023-12-14T09:41:40.883+08:00 INFO 17872 --- [a-EvictionTimer] c.n.e.registry.AbstractInstanceRegistry : Cancelled instance JDK-17-TEST/ip:端口 (replication=false)
遇到以上信息,一般说明老版本的client的心跳信息上报给v2版本的Server时报的错误。
此错误信息是因为,当我们出现非200状态码时Spring Boot会触发默认的/error 导致的。
error信息如下:
{
"timestamp": "2023-12-14T07:51:14.055+00:00",
"status": 404,
"error": "Not Found",
"path": "/eureka/apps/JDK-17-TEST/ip:端口"
}
遇到这种情况我们如何处理呢,只需要继承ErrorController即可。
/**
* Copyright 2020-2023 The Great Wall Motor Company Limited.
* All rights reserved
* <p>
* Filename : GwmErrorController
* CLR version: 2023
* Description: gwm-hr-framework:com.gwm.hr.framework.eureka.error:GwmErrorController
* <p>
* Created by suchao at 2023/12/14 2:42 PM
* http://www.gwm.cn
*/
package com.gwm.hr.framework.eureka.error;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
*
* GwmErrorController
* @author 作者
* @date 2023/12/14 2:42 PM
* @version 12
* @since 2023
*/
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class GwmErrorController implements ErrorController {
int initialCapacity = 16;
private static final AntPathMatcher ANT_PATH_MATCHER = new AntPathMatcher();
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
Map<String, Object> model = new HashMap<>(initialCapacity);
model.put("timestamp", LocalDateTime.now());
response.setStatus(response.getStatus());
return new ModelAndView("error", model);
}
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
HttpStatus status = getStatus(request);
if (matched(request)){
return new ResponseEntity<>(null, status);
}
if (status == HttpStatus.NO_CONTENT) {
return new ResponseEntity<>(status);
}
Map<String, Object> body = new HashMap<>(initialCapacity);
body.put("timestamp", LocalDateTime.now());
body.put("error","这是一个自定义错误");
return new ResponseEntity<>(body, status);
}
@ExceptionHandler(HttpMediaTypeNotAcceptableException.class)
public ResponseEntity<String> mediaTypeNotAcceptable(HttpServletRequest request) {
HttpStatus status = getStatus(request);
if (matched(request)){
return new ResponseEntity<>(null, status);
}
return ResponseEntity.status(status).build();
}
protected HttpStatus getStatus(HttpServletRequest request) {
Integer statusCode = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
if (statusCode == null) {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
try {
return HttpStatus.valueOf(statusCode);
}
catch (Exception ex) {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
}
/**
* 是否匹配上Eureka信息
* @return 匹配上返回true,匹配失败返回false
*/
protected boolean matched(HttpServletRequest request){
Object pathObj = request.getAttribute(RequestDispatcher.ERROR_REQUEST_URI);
if (Objects.nonNull(pathObj)){
return ANT_PATH_MATCHER.match("/eureka/**",pathObj.toString());
}
return false;
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。