为了账号安全,请及时绑定邮箱和手机立即绑定

SpringCloud-Eureka服务注册与发现组件

标签:
SpringBoot

Eureka和Zookeeper很类似,它是SpringCloud框架中的服务注册及发现组件。所有的微服务在使用过程中会向Eureka进行注册,而后客户端利用Eureka获取服务的信息(即服务的发现)。虽然SpringCloud支持Zookeeper,不过官方并不建议使用Zookeeper,而是推荐使用Eureka。

为什么要使用Eureka

对于这个问题其实可以引申为:在RPC框架或服务治理框架中,为什么要使用服务发现组件?
在没有使用服务注册和发现组件情况中,客户端如果想要调用服务存在以下缺点

  1. 需要记录大量的真实服务地址;

  2. 客户端需要实现负载;

  3. 无法确认某一服务是否可用;

而服务注册和发现组件可以帮助客户端解决这些问题。


Eureka服务注册和发现

创建Eureka服务

和Zookeeper提供了单独的安装包不同,目前还没发现Eureka官方提供单独的安装包来运行。我们可以将Eureka的依赖引入单独的工程中,然后部署运行该工程即可将Eureka的服务启动起来。另外Eureka即能创建单机版又能创建集群版,下面分别介绍一下单机版Eureka和集群版Eureka如何搭建。

搭建单机版Eureka

  1. 引入相关依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka-server</artifactId></dependency><dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId></dependency><!-- SpringCloud是基于SpringBoot的,所以要引入SpringBoot的依赖 --><dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId></dependency>
  1. 在配置文件中加入Eureka相关的配置

server:  port: 7001eureka:  instance: # eureak实例定义    hostname: 127.0.0.1 # 定义Eureka实例所在的主机名称
  1. 在启动类中加入@EnableEurekaServer注解

import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;@SpringBootApplication@EnableEurekaServer   // 启动Eureka服务public class Eureka_7001_StartSpringCloudApplication {    public static void main(String[] args) {
        SpringApplication.run(Eureka_7001_StartSpringCloudApplication.class,args);
    }
}
  1. 启动应用,打开浏览器访问http://127.0.0.1:7001/


    Eureka控台


    此时可以看到Eureka已经启动,但是此时如果观察后台会发现有如下ERROR日志

2018-05-24 21:25:52.500 ERROR 10360 --- [tbeatExecutor-0] com.netflix.discovery.DiscoveryClient    : DiscoveryClient_UNKNOWN/localhost:7001 - was unable to send heartbeat!

com.netflix.discovery.shared.transport.TransportException: Cannot execute request on any known server
    at com.netflix.discovery.shared.transport.decorator.RetryableEurekaHttpClient.execute(RetryableEurekaHttpClient.java:111) ~[eureka-client-1.6.2.jar:1.6.2]
    at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator.sendHeartBeat(EurekaHttpClientDecorator.java:89) ~[eureka-client-1.6.2.jar:1.6.2]
    at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator$3.execute(EurekaHttpClientDecorator.java:92) ~[eureka-client-1.6.2.jar:1.6.2]
    at com.netflix.discovery.shared.transport.decorator.SessionedEurekaHttpClient.execute(SessionedEurekaHttpClient.java:77) ~[eureka-client-1.6.2.jar:1.6.2]
    at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator.sendHeartBeat(EurekaHttpClientDecorator.java:89) ~[eureka-client-1.6.2.jar:1.6.2]
    at com.netflix.discovery.DiscoveryClient.renew(DiscoveryClient.java:815) ~[eureka-client-1.6.2.jar:1.6.2]
    at com.netflix.discovery.DiscoveryClient$HeartbeatThread.run(DiscoveryClient.java:1379) [eureka-client-1.6.2.jar:1.6.2]
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471) [na:1.7.0_79]
    at java.util.concurrent.FutureTask.run(FutureTask.java:262) [na:1.7.0_79]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) [na:1.7.0_79]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) [na:1.7.0_79]
    at java.lang.Thread.run(Thread.java:745) [na:1.7.0_79]

2018-05-24 21:25:53.890  INFO 10360 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient    : DiscoveryClient_UNKNOWN/localhost:7001: registering service...
2018-05-24 21:25:55.903 ERROR 10360 --- [nfoReplicator-0] c.n.d.s.t.d.RedirectingEurekaHttpClient  : Request execution error

com.sun.jersey.api.client.ClientHandlerException: java.net.ConnectException: Connection refused: connect
    at com.sun.jersey.client.apache4.ApacheHttpClient4Handler.handle(ApacheHttpClient4Handler.java:187) ~[jersey-apache-client4-1.19.1.jar:1.19.1]
    at com.sun.jersey.api.client.filter.GZIPContentEncodingFilter.handle(GZIPContentEncodingFilter.java:123) ~[jersey-client-1.19.1.jar:1.19.1]
    at com.netflix.discovery.EurekaIdentityHeaderFilter.handle(EurekaIdentityHeaderFilter.java:27) ~[eureka-client-1.6.2.jar:1.6.2]
    at com.sun.jersey.api.client.Client.handle(Client.java:652) ~[jersey-client-1.19.1.jar:1.19.1]
    at com.sun.jersey.api.client.WebResource.handle(WebResource.java:682) ~[jersey-client-1.19.1.jar:1.19.1]
    at com.sun.jersey.api.client.WebResource.access$200(WebResource.java:74) ~[jersey-client-1.19.1.jar:1.19.1]
    at com.sun.jersey.api.client.WebResource$Builder.post(WebResource.java:570) ~[jersey-client-1.19.1.jar:1.19.1]
    at com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient.register(AbstractJerseyEurekaHttpClient.java:56) ~[eureka-client-1.6.2.jar:1.6.2]
    at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator$1.execute(EurekaHttpClientDecorator.java:59) [eureka-client-1.6.2.jar:1.6.2]
    at com.netflix.discovery.shared.transport.decorator.MetricsCollectingEurekaHttpClient.execute(MetricsCollectingEurekaHttpClient.java:73) ~[eureka-client-1.6.2.jar:1.6.2]
    at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator.register(EurekaHttpClientDecorator.java:56) [eureka-client-1.6.2.jar:1.6.2]
    at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator$1.execute(EurekaHttpClientDecorator.java:59) [eureka-client-1.6.2.jar:1.6.2]
    at com.netflix.discovery.shared.transport.decorator.RedirectingEurekaHttpClient.executeOnNewServer(RedirectingEurekaHttpClient.java:118) ~[eureka-client-1.6.2.jar:1.6.2]
    at com.netflix.discovery.shared.transport.decorator.RedirectingEurekaHttpClient.execute(RedirectingEurekaHttpClient.java:79) ~[eureka-client-1.6.2.jar:1.6.2]
    at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator.register(EurekaHttpClientDecorator.java:56) [eureka-client-1.6.2.jar:1.6.2]
    at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator$1.execute(EurekaHttpClientDecorator.java:59) [eureka-client-1.6.2.jar:1.6.2]
    at com.netflix.discovery.shared.transport.decorator.RetryableEurekaHttpClient.execute(RetryableEurekaHttpClient.java:119) [eureka-client-1.6.2.jar:1.6.2]
    at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator.register(EurekaHttpClientDecorator.java:56) [eureka-client-1.6.2.jar:1.6.2]
    at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator$1.execute(EurekaHttpClientDecorator.java:59) [eureka-client-1.6.2.jar:1.6.2]
    at com.netflix.discovery.shared.transport.decorator.SessionedEurekaHttpClient.execute(SessionedEurekaHttpClient.java:77) [eureka-client-1.6.2.jar:1.6.2]
    at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator.register(EurekaHttpClientDecorator.java:56) [eureka-client-1.6.2.jar:1.6.2]
    at com.netflix.discovery.DiscoveryClient.register(DiscoveryClient.java:798) [eureka-client-1.6.2.jar:1.6.2]
    at com.netflix.discovery.InstanceInfoReplicator.run(InstanceInfoReplicator.java:104) [eureka-client-1.6.2.jar:1.6.2]
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471) [na:1.7.0_79]
    at java.util.concurrent.FutureTask.run(FutureTask.java:262) [na:1.7.0_79]
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:178) [na:1.7.0_79]
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:292) [na:1.7.0_79]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) [na:1.7.0_79]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) [na:1.7.0_79]
    at java.lang.Thread.run(Thread.java:745) [na:1.7.0_79]
Caused by: java.net.ConnectException: Connection refused: connect
    at java.net.DualStackPlainSocketImpl.waitForConnect(Native Method) ~[na:1.7.0_79]
    at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:85) ~[na:1.7.0_79]
    at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:339) ~[na:1.7.0_79]
    at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:200) ~[na:1.7.0_79]
    at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:182) ~[na:1.7.0_79]
    at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172) ~[na:1.7.0_79]
    at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392) ~[na:1.7.0_79]
    at java.net.Socket.connect(Socket.java:579) ~[na:1.7.0_79]
    at org.apache.http.conn.scheme.PlainSocketFactory.connectSocket(PlainSocketFactory.java:121) ~[httpclient-4.5.3.jar:4.5.3]
    at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:180) ~[httpclient-4.5.3.jar:4.5.3]
    at org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:144) ~[httpclient-4.5.3.jar:4.5.3]
    at org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:134) ~[httpclient-4.5.3.jar:4.5.3]
    at org.apache.http.impl.client.DefaultRequestDirector.tryConnect(DefaultRequestDirector.java:610) ~[httpclient-4.5.3.jar:4.5.3]
    at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:445) ~[httpclient-4.5.3.jar:4.5.3]
    at org.apache.http.impl.client.AbstractHttpClient.doExecute(AbstractHttpClient.java:835) ~[httpclient-4.5.3.jar:4.5.3]
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:118) ~[httpclient-4.5.3.jar:4.5.3]
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:56) ~[httpclient-4.5.3.jar:4.5.3]
    at com.sun.jersey.client.apache4.ApacheHttpClient4Handler.handle(ApacheHttpClient4Handler.java:173) ~[jersey-apache-client4-1.19.1.jar:1.19.1]
    ... 29 common frames omitted
  1. 去除Error日志
    当然你可以忽略该错误日志,继续使用Eureka。但是作为有洁癖的程序员看到报错信息当然是无法忍受的,那么如何干掉这些错误日志中,我们需要将配置文件改成如下内容即可

server:
  port: 7001eureka:
  client: # 客户端进行Eureka注册的配置
    service-url:
      defaultZone: http://127.0.0.1:7001/eureka
    register-with-eureka: false    # 当前的微服务不注册到eureka之中
    fetch-registry: false     # 不通过eureka获取注册信息
  instance: # eureak实例定义
    hostname: 127.0.0.1 # 定义Eureka实例所在的主机名称

搭建集群版Eureka

在实际应用中,任何服务都不会选择单实例部署,因为存在单点故障问题,线上的服务要想高可用必须要搭建集群。在Eureka当中,任何服务都可以作为服务提供者去Eureka进行注册,这当然包括Eureka服务端本身。Eureka集群的搭建正是利用了这个特性。


Eureka集群示意图

  • Eureka-server-1做为服务提供者向Eureka-server-2和Eureka-server-3中进行注册;

  • Eureka-server-2做为服务提供者向Eureka-server-1和Eureka-server-3中进行注册;

  • Eureka-server-3做为服务提供者向Eureka-server-1和Eureka-server-2中进行注册;

我们用3个节点搭建集群,这3个节点的内容除配置文件之外其余的都和单机版相同。下面我们来看看这3个节点的具体配置。
Eureka-server-1节点的配置

server:
  port: 7001eureka:  #server:
  client: # 客户端进行Eureka注册的配置
    service-url:
      defaultZone: http://eureka-7002.com:7002/eureka,http://eureka-7003.com:7003/eureka
    register-with-eureka: false    # 当前的微服务不注册到eureka之中
    fetch-registry: false     # 不通过eureka获取注册信息
  instance: # eureak实例定义
    hostname: eureka-7001.com  # 定义Eureka实例所在的主机名称spring:
  application:
    name: eureka-7001.com

Eureka-server-2节点的配置

server:
  port: 7002eureka:
  client: # 客户端进行Eureka注册的配置
      service-url:
        defaultZone: http://eureka-7001.com:7001/eureka,http://eureka-7003.com:7003/eureka
      register-with-eureka: false    # 当前的微服务不注册到eureka之中
      fetch-registry: false     # 不通过eureka获取注册信息
  instance:
    hostname: eureka-7002.com
spring:
  application:
    name: eureka-7002.com

Eureka-server-3节点的配置

server:
  port: 7003eureka:
  client:
    defalutZone: http://eureka-7001.com:7001/eureka,http://eureka-7002.com:7002/eureka
    register-with-eureka: false
    fetch-registry: false
  instance:
    hostname: eureka-7003.com
spring:
  application:
    name: eureka-7003.com

启动3个节点的Eureka服务,登录http://eureka-7001.com:7001/查看7001的Eureka控台,在集群信息中可以看到集群中的另外两个节点信息。

eureka-7001.com:7001


Eureka服务详解

Eureka可以分为Eureka服务端和Eureka客户端,Eureka服务端即服务注册中心,Eureka客户端包含两个角色:服务提供者和服务消费者。Eureka的主要功能是服务治理


图片源自《SpringCloud微服务实战》


服务提供者的功能

  • 注册服务

  • 续约

  • 服务下线通知

服务消费者的功能

  • 获取服务列表

  • 调用服务

服务注册中心的功能

  • 服务提供者信息同步

  • 失效剔除

  • 自我保护

这里我们先重点看看服务注册中心的功能。

自我保护



在Eureka的控台中,我们可能会经常看到以下信息,该信息表明Eureka的自我保护机制被触发了。

触发了Eureka自我保护的提示信息

默认情况下,如果Eureka Server在一定时间内没有接收到某个微服务实例的心跳,Eureka Server将会注销该实例(默认90秒)。但是当网络分区故障发生时,微服务与Eureka Server之间无法正常通信,以上行为可能变得非常危险了——因为微服务本身其实是健康的,此时本不应该注销这个微服务。

Eureka通过“自我保护模式”来解决这个问题——当Eureka Server节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。一旦进入该模式,Eureka Server就会保护服务注册表中的信息,不再删除服务注册表中的数据(也就是不会注销任何微服务)。当网络故障恢复后,该Eureka Server节点会自动退出自我保护模式。

可以通过eureka: server:enable-self-preservation: false将自我保护机制关闭,但一般不建议将其关闭。

失效剔除

正常下线时,服务提供者会发送下线通知给注册中心,注册中心能正常处理这种情况。如果服务非正常下线的话,注册中心又该如何处理呢?Eureka Server在启动的时候会创建一个定时任务每分钟扫描一篇服务清单,如果发现有服务超过90秒没有发送过心跳就将该服务信息剔除出去。

服务信息同步

当服务提供者将自己的信息注册给某个注册中心,该注册中心就会将此服务信息同步到集群中的其他注册中心上,从而实现注册中心间的服务同步。

服务提供者

我们知道服务提供者有三个主要功能

  • 注册服务

  • 续约

  • 服务下线通知

注册服务

  1. 引入相关依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka</artifactId></dependency><dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId></dependency>
  1. 在application.yml文件中对Eureka客户端进行配置

eureka:
 client: # 客户端进行Eureka注册的配置
   service-url:
     defaultZone: http://127.0.0.1:7001/eureka
  1. 在启动类上追加Eureka客户端启用的注解@EnableEurekaClient

@SpringBootApplication@EnableEurekaClientpublic class Dept_8001_StartSpringCloudApplication {    public static void main(String[] args) {
        SpringApplication.run(Dept_8001_StartSpringCloudApplication.class, args);
    }
}
  1. 启动服务提供者,由于加入了@EnableEurekaClient注解及配置了Eureka服务的连接地址,所以会自动向Eureka注册中心进行服务的注册。此时访问Eureka的控台,可以看到以下信息

    注册的服务


    最左侧的Application表示服务的标记符,服务的调用方正是通过该标识符对服务发起调用。此时它的值为UNKNOWN这显然不符合我们的要求,该值的内容取自application.yml文件中的spring.application.name,所以我们可以通过设置spring.application.name对其进行修改。最右边UP表示当前服务是活着的(DOWN表示服务不可用),UP边上有个超链接,点击这个链接我们可以看到该服务提供者的详细信息。这个详细信息是在服务提供者的配置文件中进行配置的。

  2. 配置详细的服务信息

server:
  port: 8001eureka:
  client:
    service-url:
      defaultZone: http://eureka-7001.com:7001/eureka
  instance:
    instance-id: dept-8001.com    # 在信息列表时显示主机名称
    prefer-ip-address: true      # 访问的路径变为 IP 地址info:
  app.name: spring-cloud-demo
  company.name: zgc
  build.artifactId: $project.artifactId$
  build.version: $project.verson$

如果现在要想查看所有的微服务详细信息,还需要修改 pom.xml 文件,追加监控配置:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot- starter-actuator</artifactId></dependency><plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven- resources-plugin</artifactId>
  <configuration>
    <delimiters>
      <delimiter>$</delimiter>
    </delimiters>
  </configuration></plugin>

此时再点击查看服务详情的info信息,可以看到如下信息


续约

服务注册完之后,服务提供者和Eureka注册中心之间会维持心跳来告知注册中心,服务还活着。我们把该操作称为服务续约(Renew),下列两个配置和续约有关

eureka:
  instance:
    lease-renewal-interval-in-seconds: 30     #每30秒会向Eureka Server发起Renew操作
    lease-expiration-duration-in-seconds: 90  #服务失效时间。默认是90秒,也就是如果Eureka Server在90秒内没有接收到来自Service Provider的Renew操作,就会把Service Provider剔除。

服务下线

当服务提供者进行正常的关闭操作时,会触发一个服务下线的REST请求给Eureka注册中心。Eureka服务端在收到请求之后,将该服务状态设置为下线(DOWN),并把该线下通知广播出去。

服务消费者

服务消费者的主要功能

  • 获取服务列表

  • 调用服务

服务发现

服务发现不仅能应用在服务消费者中还能应用在服务提供者中。

  1. 在Eureka的客户端程序中注入DiscoveryClient类,借助该类可以帮助我们自动获取服务的列表信息

    @Autowired
    private DiscoveryClient client ;    // 进行Eureka的发现服务
    @RequestMapping("/discover")    public Object discover() {  // 直接返回发现服务信息
        return this.client ;
    }
  1. 在启动类中加入@EnableDiscoveryClient注解。(貌似不加入也能够生效)

调用服务

服务消费者通过服务标识符获取具体的服务提供者信息,由服务消费者自己决定具体调用哪个服务提供者。所以服务消费者通常要维护负载均衡算法,在SpringCloud中提供了Ribbon组件进行客户端的负载调度。

参考连接
Eureka 客户端和服务端间的交互



作者:Coding小聪
链接:https://www.jianshu.com/p/89f372f54614

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 1
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消