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。具体更新了哪些信息呢?
image.png
我们来看一下他的新特性:

Eureka Server HTTP API和有线数据格式在2.0.1版本中没有更改。Eureka服务器和客户端应该在网络上前后兼容。此版本主要是为了与Spring Framework 6.0和Spring Boot 3.0的兼容性以及对Jakarta EE 9的更改。
  1. Eureka 2.0.1 Java客户端API与1.x不向后兼容。
  2. Eureka 1.x包括对Jersey 1的支持和对Jersey 2的可选支持。在许多情况下,Jersey 1是默认的,并被烘焙到eureka核心和eureka客户端模块中。具有默认客户端已被删除。如果没有提供其他实现(例如Spring Cloud对RestTemplate实现的支持),那么现在应该使用新的eureka-client-jersey3和eureka-core-jersey3。
  3. DiscoveryClient构造函数已更新为具有新的必需TransportClientFactories参数。Eureka 2.0.1 Java客户端API的大部分保持不变。所有对javax.类的引用都已更改为jakarta.类,并添加了相应的模块。
  4. Eureka服务器管理器模块已删除。目前不支持JakartaEE版本的@Inject。
  5. 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

三、填坑信息

我们会遇到一下错误信息:

  1. 客户端报错信息:

    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
  2. 服务器报错信息

    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;
    }

}

cafebabe
130 声望14 粉丝

spring cloud dubbo openresty kong gin beego