搭建单节点Eureka注册中心

  1. <!--
  2. spring-boot版本:2.3.7.RELEASE
  3. spring-cloud版本:Hoxton.SR9
  4. -->
  5. <dependencies>
  6. <dependency>
  7. <groupId>org.springframework.boot</groupId>
  8. <artifactId>spring-boot-starter-web</artifactId>
  9. </dependency>
  10. <dependency>
  11. <groupId>org.springframework.cloud</groupId>
  12. <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
  13. </dependency>
  14. </dependencies>
spring:
  application:
    name: eureka-server1
server:
  port: 7000
eureka:
  client:
    service-url:
      defaultZone: http://192.168.0.103:7000/eureka-server1 #设置服务注册中心的URL,用于client和server端交流
    register-with-eureka: false  #否将自己注册到Eureka Server,默认为true,由于当前就是server,故而设置成false,表明该服务不会向eureka注册自己的信息
    fetch-registry: false  #是否从eureka server获取注册信息,由于单节点,不需要同步其他节点数据,用false
  instance:
    appname: eureka-server1 # 服务实例名称,可覆盖spring定义名称
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class, args);
    }
}

启动成功访问,结果如下:
image.png

Eureka客户端

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  </dependency>
</dependencies>
server:
  port: 8000
eureka:
  instance:
    appname: eurka-client
  client:
    service-url:
      defaultZone: http://127.0.0.1:7000/eureka
@SpringBootApplication
@EnableEurekaClient
public class EurekaClientApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaClientApplication.class, args);
    }
}

注册到了Eureka服务端
image.png

Eureka整体介绍

注册中心实现服务的注册与发现,包含服务器端与客户端两部分。https://github.com/Netflix/Eureka

  • 服务器端 Server:为所有客户端提供服务注册与服务发现功能,维护注册到自身的服务客户端的相关信息,同时提供接口给客户端获取注册列表中其他服务的信息,使得客户端之间能够相互调用。
    • Server注册中心功能
      1. 服务注册表:记录各个微服务信息,例如服务名称,ip,端口等。注册表提供 查询API(查询可用的微服务实例)和管理API(用于服务的注册和注销)。
      2. 服务注册与发现:
      • 注册:将微服务信息注册到注册中心。
      • 发现:查询可用微服务列表及其网络地址。
      1. 服务检查:定时检测已注册的服务,如发现某实例长时间无法访问,就从注册表中移除。
  • 服务客户端 Client:客户端将自己的服务通过一定方式登记在服务端,并在正常范围维护自己信息的一致性,方便其他服务发现自己。同时可以通过服务端拉取到自己依赖的其他服务的信息,完成服务调用。客户端还内置了负载均衡,用于实现基本的负载均衡。
    • Client功能
      1. 注册:每个微服务启动时,将自己的网络地址等信息注册到注册中心,注册中心会存储(内存中)这些信息。
      2. 获取服务注册表:服务消费者从注册中心,查询服务提供者的网络地址,并使用该地址调用服务提供者,为了避免每次都查注册表信息,所以client会定时去server拉取注册表信息到缓存到client本地。
      3. 心跳:各个微服务与注册中心通过某种机制(心跳)通信,若注册中心长时间和服务间没有通信,就会注销该实例。
      4. 调用:实际的服务调用,通过注册表,解析服务名和具体地址的对应关系,找到具体服务的地址,进行实际调用。

Eureka Server与Eureka Client之间主要通过心跳方式联系,心跳(HeartBeat)是Eureka Client 定时向Eureka Server汇报本服务实例当前的状态,维护本服务在注册表中租约的有消息。EurekaClient将定时从Eureka Server中拉取注册表中的信息,并将这些信息缓存到本地,用于服务发现。

Eureka高可用

  1. 方式1:通过运行多个EurekaServr实例并相互注册的方式实现。Server节点之间会彼此增量地同步信息,从而保证节点中数据一致。(需要注意,多个EurekaServer实例名称需要相同)

02 Eureka 注册中心—服务注册与发现 - 图3

  1. 方式2:运行多个Eureka Server实例,Server节点之间没有关系,Eureka Client同时向多个Server注册信息。

采用方式1搭建Eureka集群方案:

  1. 本地搭建Eureka服务集群,直接使用Ip会有问题,故采用“主机名:端口号的方式”。

修改hosts文件:
127.0.0.1 eureka1
127.0.0.1 eureka2

  1. 节点1 eureka-server1 ```java spring: application: name: eureka

server: port: 7001

eureka: client: service-url: defaultZone: http://eureka2:7002/eureka #设置服务注册中心的URL,用于client和server端交流 instance: hostname: eureka1 appname: eureka


3.  节点2 eureka-server2
```java
spring:
  application:
    name: eureka

server:
  port: 7002

eureka:
  client:
    service-url:
      defaultZone: http://eureka1:7001/eureka #设置服务注册中心的URL,用于client和server端交流
  instance:
    hostname: eureka2
    appname: eureka

添加启动项,使用2个配置文件
image.png
启动后分别查看2个Server,可以看到 available-replicas 项(可用的复制节点)
image.png
image.png

Eureka原理

Register 服务注册

想要进行服务注册的实例,需向Eureka Server注册信息。注册在第一次心跳(HeartBeat)时提交。

Renew 续租

续租、更新状态,Eureka客户端通过向Eureka客户端发送心跳来表示当前服务是活动可用的,30秒内发送一次心跳,如果90秒(3个周期)Eureka Client没有想Eureka Server发送信息,Eureka Server会认为该服务不可用,会从服务注册表中将其删除。如果每分钟client续约数小于总数的85%,那么Eureka Server 会有自我保护机制,认为是网络问题,不会删除服务)

Fetch Registry 拉取注册表

Eureka Client从Eureka Server获取注册表并将其缓存在本地,之后通过这些信息来查找其他服务。
通过获取上一个获取周期和当前获取周期之间的增量进行更新,可以定期(30s)更新此信息。
节点信息在服务器中保存的时间更长(大约3分钟),因此获取节点信息时可能会再次返回相同的实例。Eureka客户端自动处理重复的信息。
在获得增量之后,Eureka客户机通过比较服务器返回的实例计数来与服务器协调信息,如果由于某种原因信息不匹配,则再次获取整个注册表信息。

Cancel 取消请求

Eureka客户端在关闭时向Eureka服务器发送取消请求。这将从服务器的实例注册表中删除实例,从而有效地将实例从通信量中取出。

Time Lag 同步延迟

同步延迟,来自Eureka客户端的所有操作可能需要一段时间才能反映到Eureka服务器上,然后反映到其他Eureka客户端上。这是因为eureka服务器上的有效负载缓存,它会定期刷新以反映新信息。Eureka客户端还定期地获取增量。因此,更改传播到所有Eureka客户机可能需要2分钟。

communication mechanism 通讯机制

Http协议下的Rest请求,默认情况下Eureka使用Jersey和Jackson以及JSON完成节点间的通讯

Eureka 单独使用

RESTful服务调用

官方API文档:https://github.com/Netflix/eureka/wiki/Eureka-REST-operations

Operation HTTP action Description
注册一个新的服务 POST
/eureka/v2/apps/appID
Input: JSON/XML payload HTTP Code: 204 on success
移除一个服务 DELETE /eureka/v2/apps/appID/instanceID HTTP Code: 200 on success
发送renew心跳 PUT /eureka/v2/apps/appID/instanceID HTTP Code:
200 on success
404 if instanceID doesn’t exist
查看全部服务信息 GET
/eureka/v2/apps
HTTP Code: 200 on success Output: JSON/XML
通过appID 查看服务全部实例信息 GET
/eureka/v2/apps/appID
HTTP Code: 200 on success Output: JSON/XML
通过appID/instanceID查看具体服务实例信息 GET /eureka/v2/apps/appID/instanceID HTTP Code: 200 on success Output: JSON/XML
通过instanceID查看实例信息 GET
/eureka/v2/instances/instanceID
HTTP Code: 200 on success Output: JSON/XML
使实例退出服务 PUT /eureka/v2/apps/appID/instanceID/status?value=OUT_OF_SERVICE HTTP Code:
200 on success
500 on failure
将服务移回服务 DELETE /eureka/v2/apps/appID/instanceID/status?value=UP (The value=UP is optional, it is used as a suggestion for the fallback status due to removal of the override) HTTP Code:
200 on success
500 on failure
更新元数据 PUT /eureka/v2/apps/appID/instanceID/metadata?key=value HTTP Code:
200 on success
500 on failure
查询特定vipAddress下的所有实例 GET
/eureka/v2/vips/vipAddress

HTTP Code: 200 on success Output: JSON/XML
404 if the vipAddress does not exist.
查询特定svipAddress下的所有实例 GET
/eureka/v2/svips/svipAddress

HTTP Code: 200 on success Output: JSON/XML
404 if the svipAddress does not exist.

查看服务状态

get: /eureka/status 使用浏览器请求url会返回服务器状态信息

查看注册到eureka中的服务信息

get: {ip:port}/eureka/apps

查看注册到eureka的具体的服务

get: {ip:port}/eureka/apps/{appname}/{id}

服务续约

put:{ip:port}/eureka/apps/{appname}/{id}?lastDirtyTimestamp={}&status=up

更改服务状态

put:{ip:port}/eureka/apps/{appname}/{id}/status?lastDirtyTimestamp={}&value={UP/DOWN}
对应eureka源码的:InstanceResource#statusUpdate

删除状态更新

delete:{ip:port}/eureka/apps/{appname}/{id}/status?lastDirtyTimestamp={}&value={UP/DOWN}

删除服务

delete: {ip:port}/eureka/apps/{appname}/{id}

元数据 Metadata

Eureka元数据有2种,标准元数据,用户自定义元数据

  • 标准元数据:主机名、IP地址、端口号、状态页和健康检查等信息,这些信息都会被发布在服务注册表中,用于服务之间的调用。
  • 自定义元数据:可以使用 e ureka.instance.metadata-map 配置,这些元数据可以在远程客户端中访问,但是一般不改变客户端行为,除非客户端知道该元数据的含义。

image.png

Eureka Client

EurekaClient 可以在客户端获取eureka服务器上的注册列表信息
org.springframework.cloud.client.DiscoveryClient与com.netflix.discovery.DiscoveryClient
org.springframework.cloud.client.DiscoveryClient是SpringCloud对注册中心client的抽象封装,提供公用功能
org.springframework.cloud.client.DiscoveryClient定义用来服务发现的客户端接口,是客户端进行服务发现的核心接口,是spring cloud用来进行服务发现的顶级接口,在common中可以看到其地位。在Netflix Eureka和Consul中都有具体的实现类。

public String test() {
    List<ServiceInstance> instances = new ArrayList<>();

    // getService,获取Eureka注册中心全部服务名
    for (String service : discoveryClient.getServices()) {
        if ("provider".equals(service)) {
            // 根据具体的服务名获取全部实例
            instances = discoveryClient.getInstances(service);
        }
    }
    System.out.println(instances);
    String response = "远程访问出错啦";
    if (instances != null && instances.size() > 0) {
        // 随机获取一个实例进行访问
        int i = (int) (Math.random() * instances.size());
        ServiceInstance serviceInstance = instances.get(i);
        URI uri = serviceInstance.getUri();
        String path = null;
        try {
            path = uri.toURL().toString().concat("/provider/test");
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        System.out.println();
        RestTemplate restTemplate = new RestTemplate();
        response = restTemplate.getForObject(path, String.class);
    }
    return response;
}

多网卡选择

  1. ip注册 ```yaml eureka: instance:

    表示将自己的ip注册到EurekaServer上。不配置或false,表示将操作系统的hostname注册到server

    prefer-ip-address: true ip-address: 127.0.0.1 # 实际能够访问到的ip

2.  服务器有多个网卡,eh0,eh1,eh2,只有eh0可以让外部其他服务访问进来,而Eureka client将eh1和eh2注册到Eureka server上,这样其他服务就无法访问该微服务了。
<a name="s3QYv"></a>
## Actuator 监控应用
若需使用Actuator监控,需在Eureka Server端导入依赖
```xml
<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

通过EurekaServer 地址+”/actuator” 访问:http://localhost:7000/actuator
actuator默认开启info和health端点,如需开启所有端点,需配置management.endpointswebexposure.include

management:
  endpoints:
    web:
    exposure:
      include: '*'

配置后壳查看所有端点

{"_links":
 {"self":{"href":"http://localhost:7000/actuator","templated":false},
  "archaius":{"href":"http://localhost:7000/actuator/archaius","templated":false},
  "beans":{"href":"http://localhost:7000/actuator/beans","templated":false},
  "caches-cache":{"href":"http://localhost:7000/actuator/caches/{cache}","templated":true},
  "caches":{"href":"http://localhost:7000/actuator/caches","templated":false}
  ,"health":{"href":"http://localhost:7000/actuator/health","templated":false},
  "health-path":{"href":"http://localhost:7000/actuator/health/{*path}","templated":true},
  "info":{"href":"http://localhost:7000/actuator/info","templated":false},
  "conditions":{"href":"http://localhost:7000/actuator/conditions","templated":false},
  "configprops":{"href":"http://localhost:7000/actuator/configprops","templated":false},
  "env":{"href":"http://localhost:7000/actuator/env","templated":false},
  "env-toMatch":{"href":"http://localhost:7000/actuator/env/{toMatch}","templated":true},
  "loggers":{"href":"http://localhost:7000/actuator/loggers","templated":false},
  "loggers-name":{"href":"http://localhost:7000/actuator/loggers/{name}","templated":true},
  "heapdump":{"href":"http://localhost:7000/actuator/heapdump","templated":false},
  "threaddump":{"href":"http://localhost:7000/actuator/threaddump","templated":false},
  "metrics-requiredMetricName":{"href":"http://localhost:7000/actuator/metrics/{requiredMetricName}","templated":true},
  "metrics":{"href":"http://localhost:7000/actuator/metrics","templated":false},
  "scheduledtasks":{"href":"http://localhost:7000/actuator/scheduledtasks","templated":false},
  "mappings":{"href":"http://localhost:7000/actuator/mappings","templated":false},
  "refresh":{"href":"http://localhost:7000/actuator/refresh","templated":false},
  "features":{"href":"http://localhost:7000/actuator/features","templated":false},
  "service-registry":{"href":"http://localhost:7000/actuator/service-registry","templated":false}
 }
}

api端点功能

health

会显示系统状态:{“status”:”UP”}

shutdown

用来关闭节点
开启远程关闭功能

management.endpoint.shutdown.enabled=true

使用Post方式请求端点 shutdown,远程关闭当前服务
返回:{“message”: “Shutting down, bye…”}

beans

获取应用上下文中创建的所有Bean

configprops

获取应用中配置的属性信息报告

env

获取应用所有可用的环境属性报告

mappings

获取应用所有Spring Web的控制器映射关系报告

info

获取应用自定义的信息

metrics

返回应用的各类重要度量指标信息
metrics节点并没有返回全量信息,我们可以通过不同的key去加载我们想要的值
例:metrics/jvm.memory.max

threaddump

返回程序运行中的线程信息

Eureka健康检查

Eureka Client 通过发送心跳与Eureka Server保持通信Renew,但是远程调用一个服务,需要该服务同时是”UP”状态才表示服务可用。

开启服务健康手动控制

  client:
    service-url:
      defaultZone: http://127.0.0.1:7000/eureka
    registry-fetch-interval-seconds: 5

    healthcheck:
      enabled: true
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
</dependencies>
@Service
public class HealthStatusService implements HealthIndicator {

    private boolean health = true;

    @Override
    public Health getHealth(boolean includeDetails) {
        return HealthIndicator.super.getHealth(includeDetails);
    }

    @Override
    public Health health() {
        if (health) {
            return new Health.Builder().up().build();
        } else {
            return new Health.Builder().down().build();
        }
    }

    public void setHealth(boolean health) {
        this.health = health;
    }
}
    @PutMapping("/status/{status}")
    public Health changeStatus(@PathVariable("status") Boolean status) {
        healthStatusService.setHealth(status);
        return healthStatusService.health();
    }

安全配置

Eureka Server端

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</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>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
  </dependency>
</dependencies>
spring:
  application:
    name: eureka
  security:
    user:
      name: ixiaoyu2
      password: ixiaoyu2
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        super.configure(http);
    }
}

Eureka Client

server:
  port: 8000
spring:
  application:
    name: provider
eureka:
  instance:
    appname: provider
    lease-renewal-interval-in-seconds: 5
    lease-expiration-duration-in-seconds: 60
    metadata-map:
      microserviceId: 9
    prefer-ip-address: true
    ip-address: 127.0.0.1
  client:
    service-url:
      defaultZone: http://ixiaoyu2:ixiaoyu2@127.0.0.1:7000/eureka
    registry-fetch-interval-seconds: 5

    healthcheck:
      enabled: true

management:
  endpoint:
    shutdown:
      enabled: true #开启远程服务下线management:
  endpoints:
    web:
      exposure:
        include: '*'

Eureka 的优化

  • 自我保护机制

    • 当服务多时,开启自我保护;当服务少时,关闭自我保护
  • 清除服务的间隔时间(快速下线)

    • timer:多线程并行处理定时任务时,Timer运行多个TimeTask时,只要其中之一没有捕获抛出的异常,其它任务便会自动终止运行,使用ScheduledExecutorService则没有这个问题
  • 关闭从readOnly缓存度注册表
  • 优化readWrite和readOnly缓存的同步时间间隔
    server:
        # 自我保护,看服务多少。
      enable-self-preservation: false
      # 自我保护阈值
      renewal-percent-threshold: 0.85
      # 剔除服务时间间隔
      eviction-interval-timer-in-ms: 1000
      # 关闭从readOnly读注册表
      use-read-only-response-cache: false
      # readWrite 和 readOnly 同步时间间隔。
      response-cache-update-interval-ms: 1000
    

    Eureka实现AP,是弱一致性

Eureka Regin 和 zone配置

参考:https://juejin.cn/post/6844903928178409479
02 Eureka 注册中心—服务注册与发现 - 图8
如图所示,有一个 region:华北地区,下面有两个机房,机房A 和机房B
每个机房内有一个 Eureka Server 集群 和两个服务提供者 ServiceA 和 ServerB
现在假设 serverA 需要调用 ServerB 服务,按照就近原则,serverA 会优先调用同一个 zone 内的 ServiceB,当 ServiceB 不可用时,才会去调用另一个 zone 内的 ServiceB

  • 服务注册:要保证服务注册到同一个zone内的注册中心,因为如果注册到别zone的注册中心的话,网络延时比较大,心跳检测很可能出问题。
  • 服务调用:要保证优先调用同一个zone内的服务,只有在同一个zone内的服务不可用时,才去调用别zone的服务。

    server 配置:

    ```yaml

    应用名称

    spring: application: name: eureka-service

eureka 配置

eureka: client: fetch-registry: true register-with-eureka: true prefer-same-zone-eureka: true region: chengdu # 成都区域 availability-zones: chengdu: z1,z2 # 成都区域有两个可用空间 service-url:

# z1 空间 集群
  z1: http://127.0.0.1:8011/eureka,http://127.0.0.1:8012/eureka
# z2 空间 集群
  z2: http://127.0.0.1:8021/eureka,http://127.0.0.1:8022/eureka

server: enable-self-preservation: false # 关闭自我保护,默认true开启 response-cache-update-interval-ms: 1000 # 缓存更新时间 eviction-interval-timer-in-ms: 1000 # 服务剔除间隔时间 use-read-only-response-cache: false # 关闭only read缓存使用

server: port: 8011

<a name="FAoME"></a>
### client-provider配置
```yaml

server:
  port: 8088

spring:
  application:
    name: api-passenger-zone

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    prefer-same-zone-eureka: true
    region: chengdu
    availability-zones:
      chengdu: z1,z2  # z1空间 z1放前面
    service-url:
      z1: http://127.0.0.1:8011/eureka,http://127.0.0.1:8012/eureka
      z2: http://127.0.0.1:8021/eureka,http://127.0.0.1:8022/eureka
  instance:
    lease-renewal-interval-in-seconds: 30
    # 设置服务空间
    metadata-map:
      zone: z1
    appname: api-passenger-zone
    prefer-ip-address: true
    ip-address: 127.0.0.1


zone:
  name: cd-z1

server:
  port: 8089

spring:
  application:
    name: api-passenger-zone

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    prefer-same-zone-eureka: true
    region: chengdu
    availability-zones:
      chengdu: z2,z1   # z2空间优先 z2放前面
    service-url:
      z2: http://127.0.0.1:8021/eureka,http://127.0.0.1:8022/eureka
      z1: http://127.0.0.1:8011/eureka,http://127.0.0.1:8012/eureka
  instance:
    lease-renewal-interval-in-seconds: 30
    # 空间 z2
    metadata-map:
      zone: z2
    appname: api-passenger-zone
    prefer-ip-address: true
    ip-address: 127.0.0.1


zone:
  name: cd-z2

client-consumer配置

# 应用名称
spring:
  application:
    name: eureka-client
server:
  port: 8000

eureka:
  client:
    region: chengdu
    availability-zones:
      chengdu: z1,z2 # z1空间优先,z1放前面
    service-url:
      z1: http://127.0.0.1:8011/eureka,http://127.0.0.1:8012/eureka
      z2: http://127.0.0.1:8021/eureka,http://127.0.0.1:8022/eureka
  instance:
    lease-renewal-interval-in-seconds: 30
  # z1空间
    metadata-map:
      zone: z1
# 应用名称
spring:
  application:
    name: eureka-client
server:
  port: 9000

eureka:
  client:
    region: chengdu
    availability-zones:
      chengdu: z2,z1 #z2优先,z2放前面
    service-url:
      z2: http://127.0.0.1:8021/eureka,http://127.0.0.1:8022/eureka
      z1: http://127.0.0.1:8011/eureka,http://127.0.0.1:8012/eureka
  instance:
    lease-renewal-interval-in-seconds: 30
    # z2空间
    metadata-map:
      zone: z2

EurekaClient

只会向第一个url注册,只会从第一个url拉取,为避免第一个url压力过大,所以向Eureka Clien 要把后面的url随机打乱

Eurek注册只会重试3次(可配置)