Eureka和Zookeeper很类似,它是SpringCloud框架中的服务注册及发现组件。所有的微服务在使用过程中会向Eureka进行注册,而后客户端利用Eureka获取服务的信息(即服务的发现)。虽然SpringCloud支持Zookeeper,不过官方并不建议使用Zookeeper,而是推荐使用Eureka。
为什么要使用Eureka
对于这个问题其实可以引申为:在RPC框架或服务治理框架中,为什么要使用服务发现组件?
在没有使用服务注册和发现组件情况中,客户端如果想要调用服务存在以下缺点
- 需要记录大量的真实服务地址;
- 客户端需要实现负载;
- 无法确认某一服务是否可用;
而服务注册和发现组件可以帮助客户端解决这些问题。
创建Eureka服务
和Zookeeper提供了单独的安装包不同,目前还没发现Eureka官方提供单独的安装包来运行。我们可以将Eureka的依赖引入单独的工程中,然后部署运行该工程即可将Eureka的服务启动起来。另外Eureka即能创建单机版又能创建集群版,下面分别介绍一下单机版Eureka和集群版Eureka如何搭建。
搭建单机版Eureka
- 引入相关依赖
<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>
- 在配置文件中加入Eureka相关的配置
server:
port: 7001
eureka:
instance: # eureak实例定义
hostname: 127.0.0.1 # 定义Eureka实例所在的主机名称
- 在启动类中加入
@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);
}
}
-
启动应用,打开浏览器访问http://127.0.0.1:7001/
此时可以看到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
- 去除Error日志
当然你可以忽略该错误日志,继续使用Eureka。但是作为有洁癖的程序员看到报错信息当然是无法忍受的,那么如何干掉这些错误日志中,我们需要将配置文件改成如下内容即可
server:
port: 7001
eureka:
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-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: 7001
eureka:
#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: 7002
eureka:
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: 7003
eureka:
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服务详解
Eureka可以分为Eureka服务端和Eureka客户端,Eureka服务端即服务注册中心,Eureka客户端包含两个角色:服务提供者和服务消费者。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秒没有发送过心跳就将该服务信息剔除出去。
服务信息同步
当服务提供者将自己的信息注册给某个注册中心,该注册中心就会将此服务信息同步到集群中的其他注册中心上,从而实现注册中心间的服务同步。
服务提供者
我们知道服务提供者有三个主要功能
- 注册服务
- 续约
- 服务下线通知
注册服务
- 引入相关依赖
<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>
- 在application.yml文件中对Eureka客户端进行配置
eureka:
client: # 客户端进行Eureka注册的配置
service-url:
defaultZone: http://127.0.0.1:7001/eureka
- 在启动类上追加Eureka客户端启用的注解
@EnableEurekaClient
@SpringBootApplication
@EnableEurekaClient
public class Dept_8001_StartSpringCloudApplication {
public static void main(String[] args) {
SpringApplication.run(Dept_8001_StartSpringCloudApplication.class, args);
}
}
- 启动服务提供者,由于加入了
@EnableEurekaClient
注解及配置了Eureka服务的连接地址,所以会自动向Eureka注册中心进行服务的注册。此时访问Eureka的控台,可以看到以下信息
最左侧的Application表示服务的标记符,服务的调用方正是通过该标识符对服务发起调用。此时它的值为UNKNOWN这显然不符合我们的要求,该值的内容取自application.yml文件中的spring.application.name,所以我们可以通过设置spring.application.name对其进行修改。最右边UP表示当前服务是活着的(DOWN表示服务不可用),UP边上有个超链接,点击这个链接我们可以看到该服务提供者的详细信息。这个详细信息是在服务提供者的配置文件中进行配置的。 - 配置详细的服务信息
server:
port: 8001
eureka:
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),并把该线下通知广播出去。
服务消费者
服务消费者的主要功能
- 获取服务列表
- 调用服务
服务发现
服务发现不仅能应用在服务消费者中还能应用在服务提供者中。
- 在Eureka的客户端程序中注入
DiscoveryClient
类,借助该类可以帮助我们自动获取服务的列表信息
@Autowired
private DiscoveryClient client ; // 进行Eureka的发现服务
@RequestMapping("/discover")
public Object discover() { // 直接返回发现服务信息
return this.client ;
}
- 在启动类中加入
@EnableDiscoveryClient
注解。(貌似不加入也能够生效)
调用服务
服务消费者通过服务标识符获取具体的服务提供者信息,由服务消费者自己决定具体调用哪个服务提供者。所以服务消费者通常要维护负载均衡算法,在SpringCloud中提供了Ribbon组件进行客户端的负载调度。
参考连接
Eureka 客户端和服务端间的交互