Spring Cloud Netflix Eureka服务同步及高可用

      Spring Cloud Netflix Eureka是服务注册和发现组件,在分布式系统中,为客户端提供服务注册信息,类似于Zookeeper注册中心,首先服务提供者在启动时,将服务信息注册到注册中心,服务消费者在启动时,从注册中心获取服务提供者信息列表,根据特定的规则,选取其中一个服务提供者来发起调用。

     默认情况下,Eureka是服务器同时也是客户端,自己给自己注册,先验证下默认注册到服务器是什么。

server:
  port: 8771

spring:
    application:
      name: eurekaserver
    profiles: node1
eureka:
  instance:
    hostname: node1
    preferIpAddress: true

#  client:
    #自身是否注册到eureka服务器
#    registerWithEureka: false
#    是否从eureka服务器获取注册信息
#    fetchRegistry: false
    #8761这个机器上的实例同步到8771,想相当于备份冗余,实现高可用
#    serviceUrl:
#      defaultZone: http://node2:8772/eureka/

registerWithEureka: false,这里注释掉了,默认是true,它会注册到本身,我们先启动看下。

com.netflix.discovery.shared.transport.TransportException: Cannot execute request on any known server

找不到服务器,为什么呢,不应该是注册到自己8771端口吗,因为我们上面serviceUrl没有指定,那么它默认注册到本机的8761端口,很自然,找不到要注册的服务器,后台会报错,那是因为服务器和客户端间维持着一个心跳线程,当服务器存在了,就不会报错了,但不影响程序的启动和访问。


这里得出的结论是,serviceUrl就是要注册到的服务注册中心的地址,默认是本机的8761端口,当然也可以指定其他机器。

接下来,我配置了3个注册中心node1,node2,node3,他们之间相互注册。


nod1和node2是相互注册,那么node1上有node2服务,node2上有node1服务,由于相互注册的Eureka server,他们之间的服务可以同步,所以node1上有node1服务和node2服务;node2上也有node1服务和node2服务。官方说法是,同伴意识,我的理解是,服务共享,哈哈。

node3服务注册到node2,则node2上有nod1,node2,node3服务了,node2上所有服务也会同步到node1。而node3因为没有和node2相互注册,也没有服务注册到它上面来,所以node3没有服务,node3可以看成是Eureka客户端了。看界面结果(图片在文章显示不正常,欲哭无泪)。

node1:http://node1:8771/


node2:http://node2:8772/


node3:http://node3:8773/


启动类:

@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class, args);
    }
}

application.yml:

#客户端默认连接8761这个端口,如果不指定,则默认启动是8080端口,则客户端也得改为8080端口。
server:
  port: 8771

spring:
    application:
      name: eurekaserver
    profiles: node1
eureka:
  instance:
    hostname: node1
    preferIpAddress: true

  client:
    #自身是否注册到eureka服务器
#    registerWithEureka: false
#    是否从eureka服务器获取注册信息
    fetchRegistry: false
    #8771这个机器上的服务同步到8772,注册中心上的服务信息是相互同步的
    serviceUrl:
      defaultZone: http://node2:8772/eureka/

---
server:
  port: 8772

spring:
    application:
      name: eurekaserver
    profiles: node2
eureka:
  instance:
    hostname: node2
#    preferIpAddress: true

  client:
    #自身是否注册到eureka服务器serviceUrl
#    registerWithEureka: false
#    是否从eureka服务器获取注册信息
    fetchRegistry: false
    #8772这个机器上的服务同步到8771,注册中心上的服务信息是相互同步的
    serviceUrl:
      defaultZone: http://node1:8771/eureka/

---
server:
  port: 8773

spring:
    application:
      name: eurekaserver
    profiles: node3
eureka:
  instance:
    hostname: node3
#    preferIpAddress: true

  client:
    #自身是否注册到eureka服务器serviceUrl
#    registerWithEureka: false
#    是否从eureka服务器获取注册信息
    fetchRegistry: false
    serviceUrl:
      defaultZone: http://node2:8772/eureka/

hosts:

127.0.0.1 node1
127.0.0.1 node2
127.0.0.1 node3

这里配置hosts,只是为了再界面上做区分,也可以不配置,直接用localhost。

pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <modelVersion>4.0.0</modelVersion>

    <groupId>com.cj</groupId>
    <artifactId>eurekaserver</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>eurekaserver</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <!--eureka server -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka-server</artifactId>
        </dependency>

        <!-- spring boot test-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Dalston.RC1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>



</project>

在项目跟目录执行:mvn clean package.

然后进入target目录。

打开3个cmd窗口,分别执行:

java -jar eurekaserver-0.0.1-SNAPSHOT.jar --spring.profiles.active=node1

java -jar eurekaserver-0.0.1-SNAPSHOT.jar --spring.profiles.active=node2

java -jar eurekaserver-0.0.1-SNAPSHOT.jar --spring.profiles.active=node3

启动3个eurekaserver,启动过程报的错,不用管,因为它注册到其他服务器还没启动,心跳线程一直在检测,等都启动了,就不会报错了。在浏览器分别访问:

http://node1:8771/,http://node2:8772/,http://node3:8773/。查看结果,如上。


上面讲的是eureka服务器之间的服务同步。那如何保证高可用呢。


配置如下:

#客户端默认连接8761这个端口,如果不指定,则默认启动是8080端口,则客户端也得改为8080端口。
server:
  port: 8771

spring:
    application:
      name: eurekaserver
    profiles: node1
eureka:
  instance:
    hostname: node1
#    preferIpAddress: true //集群是,不要加这个。

  client:
    #自身是否注册到eureka服务器
    registerWithEureka: true
#    是否从eureka服务器获取注册信息
    fetchRegistry: true
    #8771这个机器上的服务同步到8772,注册中心上的服务信息是相互同步的
    serviceUrl:
      defaultZone: http://node2:8772/eureka/,http://node3:8773/eureka/

---
server:
  port: 8772

spring:
    application:
      name: eurekaserver
    profiles: node2
eureka:
  instance:
    hostname: node2
#    preferIpAddress: true

  client:
    #自身是否注册到eureka服务器serviceUrl
    registerWithEureka: true
#    是否从eureka服务器获取注册信息
    fetchRegistry: true
    #8772这个机器上的服务同步到8771,注册中心上的服务信息是相互同步的
    serviceUrl:
      defaultZone: http://node1:8771/eureka/,http://node3:8773/eureka/

---
server:
  port: 8773

spring:
    application:
      name: eurekaserver
    profiles: node3
eureka:
  instance:
    hostname: node3
#    preferIpAddress: true

  client:
    #自身是否注册到eureka服务器serviceUrl
    registerWithEureka: true
#    是否从eureka服务器获取注册信息
    fetchRegistry: true
    serviceUrl:
      defaultZone: http://node1:8771/eureka/,http://node2:8772/eureka/

启动过程跟上面一样。看结果。

http://node1:8771/


http://node2:8772/


http://node3:8773/


注意:prefer-ip-address不要设置为true,或者不加,直接用默认值,要不然,available-replicas为空

ok,那如何测试呢,我先启动一个客户端,只注册连接到node1,从node1获取服务,然后把node1干掉,看还能不能获取服务。

客户端启动类:

package com.cj;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.Set;

/**
 * @author wb-cj189958
 * @date 2018/5/26 20:56
 */
@SpringBootApplication
@EnableEurekaClient
@RestController
public class EurekaClientApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaClientApplication.class, args);
    }

    @Autowired
    private DiscoveryClient discoveryClient;

    @RequestMapping("/{applicationName}")
    public List<ServiceInstance> serviceInstance(
            @PathVariable String applicationName) {
        return discoveryClient.getInstances(applicationName);
    }
}

application.yml:

#默认注册地址
#eureka:
#  client:
#    serviceUrl:
#      defaultZone: http://localhost:8761/eureka/

#management:
#  contextPath: /eurekaclient1

#传给注册中心的实例信息
eureka:
  instance:
    hostname: localhost
#    preferIpAddress: true
    #心跳时间,客户端注册服务元数据注册中心
    leaseRenewalIntervalInSeconds: 1
    homePageUrlPath: ${server.servletPath}
    statusPageUrlPath: ${server.servletPath}/info
    healthCheckUrlPath: ${server.servletPath}/health
    #对应"metadata":{"instanceId":"eurekaclient:56929cfd113d3fe33d3d3df34715d780"
    metadataMap:
          instanceId: ${spring.application.name}:${random.value}
  client:
      serviceUrl:
        defaultZone: http://node1:8771/eureka/

server:
  port: 8762
  servletPath: /eurekaclient
spring:
  application:
    name: eurekaclient

pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.2.RELEASE</version>
        <relativePath/>
        <!-- lookup parent from repository -->
    </parent>

    <modelVersion>4.0.0</modelVersion>

    <groupId>com.cj</groupId>
    <artifactId>eurekaclient</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>eurekaclient</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Dalston.RC1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>


    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>
</project>

http://node1:8771


http://node2:8772


http://node3:8773



虽然我只是报服务注册到了node1,但由于相互注册的服务器上的服务会进行同步,所以,node1,node2,node3都有,我们客户端访问注册中心服务。

访问地址:http://localhost:8762/eurekaclient/eurekaserver

eurekaclient是客户端应用的上下文,我自己加的,默认是/

eurekaserver是eurekaserver集群的应用名,不区分打小写,源代码会自动转换为大写

返回结果:eurekaserver集群中服务名为eurekaserver的服务信息,也就是node1,node2,node3服务信息。


现在,我们把node1干掉,看下结果。

实际结果是,注册中心改服务还在,页面警告:

EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.

查了下,是Eureka Server的自我保护机制,将注册信息保存起来了。所以node1服务还在。

修改node1,node2,node3节点如下配置:

eureka:
  server:
      enableSelfPreservation: false  #关闭自我保护机制,将停掉的服务移除

      eviction-interval-timer-in-ms: 5000 #每5秒清除一次

OK,上面修改完了,继续,当我们把node1干掉后,node2肯定访问不了。看看node2和node3吧。

http://node2:8772/


http://node3:8773/

上面的页面要多等下,多刷新几次,才能看到正常的结果,因为 eurekaserver要花费一定的时间检测和更新服务的状态。

ok,这里是正常的。因为node1挂掉了,看看客户端向node1请求服务信息正常不,

http://localhost:8762/eurekaclient/eurekaserver


node1不是挂了吗,node2,node3上也没有啊,为什么,客户端还是能拿到这个信息,难道客户端有缓存,还没有清除,好吧,在等等。

还是不行,因为我们client和eureka server的通信地址serviceUrl为node1的,现在node1挂了,那么client和eureka server就不能通信了,客户端本地缓存的服务也就不能更新了。

修改client的配置如下:

原来:

eureka.client.serviceUrl.defaultZone=http://node1:8771/eureka/

现在:

eureka.client.serviceUrl.defaultZone=http://node1:8771/eureka/,http://node2:8772/eureka/,http://node3:8773/eureka/

也就是说,只要eureka server集群中有一个节点是正常的,那么client就能正常和注册中心通信。


再接下来,我们把node2干掉,会发生什么。

node1,node2肯定访问不了,我们访问node3



我们客户获取eurekaserver服务


结果是正常的。

你可能会问,node3不是还在吗,为什么node3上没有eurekaserver服务。

还是之前说的,node3的服务是注册到node1,node2上的,node1,node2再把node3服务信息同步给node3,现在node1,node2都挂了,也就同步不过来了。

总结:

1.eureka server要保证高可用,集群中服务器节点间就要相互注册。

2.客户端的注册中心地址,要配置eureka server集群中所有服务地址。


demo地址:

Euraka demo源码

猜你喜欢

转载自blog.csdn.net/bawcwchen/article/details/80504721