Spring Cloud Eureka 服务治理

Spring Cloud Eureka 服务治理

Scroll Down

什么是服务治理

在传统的RPC远程调用中,服务与服务之间的依赖关系比较复杂,所以需要使用服务治理来管理服务之间的关系,可以实现服务调用、负载均衡、容错等。实现服务的注册和发现。

Eureka 是什么

Eureka是由Netflix开发的一款服务治理开源框架,Spring-cloud对其进行了集成。Eureka既包含了服务端也包含了客户端,Eureka服务端是一个服务注册中心(Eureka Server),提供服务的注册和发现,即当前有哪些服务注册进来可供使用;Eureka客户端为服务提供者(Server Provider),它将自己提供的服务注册到Eureka服务端,并周期性地发送心跳来更新它的服务租约,同时也能从服务端查询当前注册的服务信息并把它们缓存到本地并周期性地刷新服务状态。这样服务消费者(Server Consumer)便可以从服务注册中心获取服务名称,并消费服务。三者关系如下:
687474703a2f2f6e6f74652e796f7564616f2e636f6d2f7977732f6170692f706572736f6e616c2f66696c652f33363341343841303839344634334330394143433739433042443237383335443f6d6574686f643d646f776e6c6f61642673686172654b65793d61.png

搭建Eureka-Server服务注册中心

创建一个Spring Boot项目,artifactId填Eureka-Server,引入eureka-server包:

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.RELEASE</spring-cloud.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

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

这里使用的Spring Cloud的版本是Hoxton.RELEASE,对应的SpringBoot版本是2.2.0.RELEASE版本。
spring-cloud-starter-netflix-eureka-server 是spring Cloud更加细化的表现,之前版本引入的是:spring-cloud-starter-eureka-server

在启动类上添加 @EnableEurekaServer 注解,表明这是一个Eureka服务端:

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {

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

}

在application.yml 中添加配置:


server:
  port: 8080

spring:
  application:
    name: eureka-server


eureka:
  instance:
    hostname: localhost
  client:
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
      register-with-eureka: false
      fetch-registry: false
  • 配置服务的端口是8080,服务名称为eureka-server,
  • eureka.client.register-with-eureka表示是否将服务注册到Eureka服务端,由于自身就是Eureka服务端,所以设置为false;
  • eureka.client.fetch-registry表示是否从Eureka服务端获取服务信息,因为这里只搭建了一个Eureka服务端,并不需要从别的Eureka服务端同步服务信息,所以这里设置为false;
  • eureka.client.serviceUrl.defaultZone指定Eureka服务端的地址,默认值为http://localhost:8761/eureka。
    配置完成后,启动项目,访问localhost:8080,出现以下界面:
    4.PNG
    因为没有Eureka客户端注册进来,所以Instances currently registered with Eureka列表是空的。
    下面创建Eureka客户端提供服务。

搭建Eureka-Client 服务提供者

新建一个Spring Boot项目,artifactId填Service-Provider,然后引入以下依赖:

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.RELEASE</spring-cloud.version>
    </properties>

   <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

在启动类上添加 @EnableDiscoveryClient 或者 @EnableEurekaServer注解,表明这是一个Eureka 服务端:

@SpringBootApplication
@EnableDiscoveryClient
public class EurekaClientApplication {

    public static void main(String[] args) {
        SpringApplication.run(EurekaClientApplication.class, args);
    }

}

application.yml添加如下配置:


server:
  port: 8088

spring:
  application:
    name: server-provider


eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:8080/eureka/

配置了服务的端口为8080,名称为server-provider,剩下的是Eureka的配置:

  • eureka.instance.hostname指定了Eureka服务端的IP;

  • eureka.client.register-with-eureka表示是否将服务注册到Eureka服务端,由于自身就是Eureka服务端,所以设置为false;

  • eureka.client.fetch-registry表示是否从Eureka服务端获取服务信息,因为这里只搭建了一个Eureka服务端,并不需要从别的Eureka服务端同步服务信息,所以这里设置为false;

  • eureka.client.serviceUrl.defaultZone指定Eureka服务端的地址,默认值为http://localhost:8761/eureka。

配置之后启动项目,在控制台可以看见如下:

Registered Applications size is zero : true
Application version is -1: true
Getting all instance registry info from the eureka server
The response status is 200
Starting heartbeat executor: renew interval is: 30
InstanceInfoReplicator onDemand update allowed rate per min is 4
Discovery Client initialized at timestamp 1578485835257 with initial instances count: 1
Registering application SERVICE-PROVIDER with eureka with status UP
Saw local status change event StatusChangeEvent [timestamp=1578485835262, current=UP, previous=STARTING]
DiscoveryClient_SERVICE-PROVIDER/DESKTOP-4K4RMBI:service-provider:8088: registering service...
DiscoveryClient_SERVICE-PROVIDER/DESKTOP-4K4RMBI:service-provider:8088 - registration status: 204
Tomcat started on port(s): 8088 (http) with context path ''
Updating port to 8088
Started EurekaClientApplication in 11.753 seconds (JVM running for 13.834)

第3,4行输出表示已经成功从Eureka服务端获取到了服务;
第5行表示发送心跳给Eureka服务端,续约(renew)服务;
第8到11行表示已经成功将服务注册到了Eureka服务端。

再次访问localhost:8080, 可以看到服务列表已经出现了名字为SERVICE-PROVIDER的服务:
5.PNG
UP表示在线的意思(如果Eureka客户端正常关闭,那么这里的状态将变为DOWN)

当关闭客户端,再次刷新localhost:8080 页面时,会有红色文字提示:

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已经进入了保护模式。微服务在部署之后可能由于网络问题造成Eureka客户端无法成功的发送心跳给Eureka服务端,这时候Eureka服务端认定Eureka客户端已经挂掉了,虽然实际上Eureka客户端还在正常的运行着。而保护模式就是为了解决这个问题,即当Eureka服务端在短时间内同时丢失了过多的Eureka客户端时,Eureka服务端会进入保护模式,不去剔除这些客户端。因为我们这里只部署了一个Eureka客户端服务,所以关闭客户端后满足“短时间内丢失过多Eureka客户端”的条件。

因此,我们一般在开发时将保护模式关闭。但是在生产环境中一定开启。关闭保护模式需要加如下配置:

eureka:
  server:
    enable-self-preservation: false

Eureka-Server 集群

我们以上的配置是单机配置,只有一个注册中心。这里有一个问题:所有Eureka客户端将自己注册到注册中心,如果这个注册中心宕机,那个所以的服务将无法正常访问,这必将是灾难性的。为了提高Eureka服务端的高可用性,一般会对其集群部署,同时部署多个Eureka服务端,并且相互间同步服务。

在以上搭建Eureka-Server 时,我们将一下两个配置关闭:

eureka:
  client:
    register-with-eureka: false
    fetch-registry: false

在Eureka集群模式中,开启这两个参数可以让当前Eureka服务端将自己也作为服务注册到别的Eureka服务端,并且从别的Eureka服务端获取服务进行同步。所以这里我们将这两个参数置为true(默认就是true),下面开始搭建Eureka服务端集群,为了简单起见这里只搭建两个节点的Eureka服务端集群。

Eureka高可用实际上将自己作为服务向其他服务注册中心注册自己,这样就可以形成一组相互注册的服务注册中心,从而实现服务清单的互相同步,达到高可用效果。

在resources 目录下新建 application-peer1.yml,配置如下:

server:
  port: 8080

spring:
  application:
    name: Eureka-Server

eureka:
  instance:
    hostname: peer1
  client:
    serviceUrl:
      defaultZone: http://peer2:8081/eureka/
  server:
    enable-self-preservation: false

创建另外一个Eureka服务端peer2的yml配置application-peer2.yml:

server:
  port: 8081

spring:
  application:
    name: Eureka-Server

eureka:
  instance:
    hostname: peer2
  client:
    serviceUrl:
      defaultZone: http://peer1:8080/eureka/
  server:
    enable-self-preservation: false

peer2中的serviceUrl我们指向Eureka服务端peer1。

为了让这种在一台机器上配置两个hostname的方式生效,我们需要修改下hosts文件(位置C:\Windows\System32\drivers\etc):

127.0.0.1       peer1
127.0.0.1       peer2

通过设置Idea启动SpringBoot,分别通过peer1 peer2配置文件启动项目:
6.PNG

启动后,访问peer1: http://localhost:8081:
7.PNG

8.PNG

可见DS Replicase指向了peer2,
registered-replicas和available-replicas都指向了http://peer2:8081/eureka/。

访问peer2,可以看到类似的信息。

Eureka服务端做了集群处理,所以Eureka客户端指定的服务端地址也要进行修改:

eureka:
  client:
    serviceUrl:
      defaultZone: http://peer1:8080/eureka/,http://peer2:8081/eureka/

利用同样的方法,在Eureka客户端新建一个application-con1.yml,端口号为8090,其他配置一样。同时启动 application.yml , application-con1.yml配置文件。访问http://localhost:8081/:
9.PNG

发现两个服务已经注册进来了。

搭建Server-Consumer服务消费者

在实际项目中,Eureka客户端即是服务提供者,也是服务消费者,即自身的接口可能被别的服务访问,同时也可能调用别的服务接口。这里为了更好的演示,我们把服务消费者单独的分开来演示。

新建一个Spring Boot项目,artifactId填Server-Consumer,其主要的任务就是将自身的服务注册到Eureka服务端,并且获取Eureka服务端提供的服务并进行消费。这里服务的消费我们用Ribbon来完成,Ribbon是一款实现服务负载均衡的开源软件。
引入Eureka客户端和Ribbon依赖:

 <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.RELEASE</spring-cloud.version>
    </properties>

    <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.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

在入口类中加入@EnableDiscoveryClient注解用于发现服务和注册服务,并配置一个RestTemplate Bean,然后加上@LoadBalanced注解来开启负载均衡:

@SpringBootApplication
@EnableDiscoveryClient
public class ServerConsumerApplication {

    @Bean
    @LoadBalanced
    RestTemplate restTemplate(){
        return new RestTemplate();
    }
    public static void main(String[] args) {
        SpringApplication.run(ServerConsumerApplication.class, args);
    }

}

编写一个TestController,用于消费服务:

@RestController
public class TestController {
    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/info")
    public String getInfo() {
        return this.restTemplate.getForEntity("http://SERVICE-PROVIDER/info", String.class).getBody();
    }
}

上面代码注入了RestTemplate,getInfo中使用RestTemplate对象均衡的去获取服务并消费。可以看到我们使用服务名称(Server-Provider)去获取服务的,而不是使用传统的IP加端口的形式。这就体现了使用Eureka去获取服务的好处,只要保证这个服务名称不变即可。

编写application.yml 文件:

server:
  port: 9000

spring:
  application:
    name: Server-Consumer

eureka:
  client:
    serviceUrl:
      defaultZone: http://peer1:8081/eureka/,http://peer2:8082/eureka/

端口为9000,服务名称为Server-Consumer并指定了Eureka服务端的地址。

在Eureka-Client中添加接口供消费,编写TestController:

@RestController
public class TestController {
    @Value("server.port")
    String port;
    @RequestMapping("/info")
    public String info(){
       return "本方法被调用,端口号为:" + port;
    }
}

启动项目,访问localhost:9000/info:
10.PNG
11.PNG

可以看到,通过9000端口访问,获取到了8089、8090两个端口的服务,说明从9000去获取服务是均衡的。
这时候我们关闭一个Eureka服务端,再次访问http://localhost:9000/info,还是可以成功获取到信息,这就是Eureka服务端集群的好处。

Eureka-Server添加认证

我们会发现,当我们输入注册中心的端口,就可获取到注册中心的信息,这显然是不安全的,因此要对Eureka-Server 注册中心添加用户认证额功能。
在Eureka-Server引入Spring-Security依赖:

 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

然后在application.yml中配置用户名和密码:

security.user.name=user
security.user.password=123456

Eureka服务端配置了密码之后,所有eureka.client.serviceUrl.defaultZone的配置也必须配置上用户名和密码,格式为:eureka.client.serviceUrl.defaultZone=http://$:$@$:$/eureka/,如:

eureka:
  client:
    serviceUrl:
      defaultZone: http://user:123456@peer2:8082/eureka/

重新运行后,访问http://localhost:8081/,页面将弹出验证窗口,输入用户名和密码后即可访问。

##Eureka配置
Eureka服务端常用配置
|配置|含义|默认值|
|-------|-------|-------|
|eureka.server.enable-self-preservation|注册中心自我保护机制开启或关闭|true|

Eureka客户端常用配置:

配置含义默认值
eureka.client.enabled启用Eureka客户端true
eureka.client.registry-fetch-interval-seconds从Eureka服务端获取注册信息的间隔时间30s
eureka.client.eureka-service-url-poll-interval-seconds到Eureka服务端拉取更改信息的频率0
eureka.client.serviceUrl.defaultZone指定服务注册中心的地址
eureka.client.register-with-eureka是否要将自身的实例消息注册到Eureka服务端true
eureka.client.fetch-registry是否从Eureka服务端获取注册信息true

Eureka服务实例常用配置:

配置含义默认值
eureka.instance.hostname服务实例的主机名操作系统设置的主机名
eureka.instance.instance-id设置该服务实例的唯一标识符,用来注册到eureka中
eureka.instance.prefer-ip-address设置服务实例偏好使用ip地址作为主机名false
eureka.instance.metadata-map设置服务实例的name/value键值对的元数据,该数据会被发送到Eureka服务端并且其它的实例可以使用
eureka.instance.lease-renewal-interval-in-seconds定义服务续约任务的调用间隔时间30s
eureka.instance.lease-expiration-duration-in-seconds定义服务失效的时间90s