目录
学习是一件需要实践和思考的事情,很多东西学过一遍没有动手操作很容易就忘记,而且你以为你理解和掌握了,一到面试和工作中用到就会懵。最近才发现要改变自己的学习习惯了,学习就是理论为辅实践为主。我工作中第一次接触的就是Eureka,确实在企业中搭建好了也没多去了解一些细节的东西,现在是时候了解一下里面的细节和用法了。
1. 初识Eureka
1.1 Eureka是什么
Eureka最早是由Netflix公司开发的一款提供服务注册和发现的开源产品,本身是一个基于REST的服务,主要用于定位运行在AWS域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的。SpringCloud项目组对Eureka进行二次封装,将它集成在自己的子项目spring-cloud-netflix中,用于实现微服务架构下的注册中心的功能。
1.2 什么是注册中心
注册中心是指实现了服务的注册和发现以及服务的续约、剔除、下线等功能的服务端应用。注册中心是微服务架构下实现服务治理的最核心模块。注册中心保存了注册服务的IP和端口号等注册信息,使服务与服务之间进行RPC远程调用和实现负载均衡变得简单,同时注册中心还会采取心跳检测机制对服务实例进行续约、剔除,下线等维护。目前主流的注册中心产品有eureka、nacos、zookeeper等。
1.3 Eureka的原理
基本的概念:
Eureka实现注册中心采用C-S架构,分为Eureka的服务端(server)和客户端(client ),并且客户端包含了服务提供者和服务消费者两种角色。
- 服务注册中心:Eureka的服务端,提供服务的注册与发现功能,接收客户端的register、renew、cancel 请求对服务进行处理,采取客户端定时发送心跳到服务端的方式剔除没有按时续约(renew)的服务。在集群集群模式下Eureka Server之间需要互相注册,在集群中随意选择一个节点注册,并通过对等复制的机制完成数据的同步。
- 服务提供者:Eureka的客户端,运行后作为客户端向注册中心发起注册(register)的请求,注册成功后会将自己的IP地址、端口号、service ID等信息注册到注册表中。Eureka Client默认以30秒为周期向Eureka Server发送心跳以完成续约。
- 服务消费者:Eureka的客户端,运行后将自身注册到注册中心,同时默认以30秒为周期从注册中心获取(fetch)服务的注册表信息缓存到客户端,方便后续服务消费者对服务提供者进行服务选择和远程调用。
2. Eureka的快速入门
2.1 搭建eureka的单机服务
- 创建springboot项目eureka-server01,添加eureka作为服务端的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
<version>2.0.3.RELEASE</version>
</dependency>
- 在application.yml配置文件配置eureka服务
server:
port: 7001
spring:
application:
name: eureka-server # 服务的应用名称,部署集群需保证应用名称一样
eureka:
instance:
hostname: localhost # euraka服务实例的主机名或域名
instance-id: eureka-server01 # euraka服务实例在注册中心的唯一实例ID
client:
# 当前服务作为服务端不注册到eureka, 默认为true,单机可为false,集群必须设为true
register-with-eureka: false
# 是否获取eureka服务器注册表上的注册信息,默认为true ,单机版可设为false
fetch-registry: false
service-url:
defaultZone: http://localhost:7001/eureka/ #客户端可注册的eureka服务地址
- 应用主启动类中添加@EnableEurekaServer注解,表示作为eureka服务端
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerMainApp {
public static void main(String[] args) {
SpringApplication.run(EurekaServerMainApp.class,args);
}
}
- 在浏览器中输入localhost:7001即可访问到Eureka的注册中心页面,我们发现Renews threshold的值为1,后面会介绍怎么计算出来的。
2.2 注册服务的消费者
- 创建springboot项目order-service,添加eureka作为客户端的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- 在application.yml文件中配置服务信息
server:
port: 8080
spring:
application:
name: order-service # 服务的应用名称,部署集群需保证应用名称一样
eureka:
instance:
hostname: localhost # euraka服务实例的主机名或域名
instance-id: order8080 # euraka服务实例的id,需唯一
lease-renewal-interval-in-seconds: 30 # 客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
lease-expiration-duration-in-seconds: 90 # 服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
client:
# 客户端服务需要将自己作为服务注册到eureka, 默认为true
register-with-eureka: true
# 客户端服务需要获取eureka服务器注册表上的注册信息,默认为true
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka/ #客户端可注册的eureka服务地址
- 应用主启动类中添加@EnableEurekaClient注解,表示作为eurek客户端
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaClient
public class ConsumerOrderMainApp {
public static void main(String[] args) {
SpringApplication.run(ConsumerOrderMainApp.class, args);
}
}
2.3 注册服务的提供者
- 创建springboot项目payment-service,添加eureka作为客户端的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- 在application.yml文件中配置服务信息
server:
port: 8080
spring:
application:
name: payment-service # 服务的应用名称,部署集群需保证应用名称一样
eureka:
instance:
hostname: localhost # euraka服务实例的主机名或域名
instance-id: payment8001 # euraka服务实例的id,需唯一
lease-renewal-interval-in-seconds: 30 # 客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
lease-expiration-duration-in-seconds: 90 # 服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
client:
# 客户端服务需要将自己作为服务注册到eureka, 默认为true
register-with-eureka: true
# 客户端服务需要获取eureka服务器注册表上的注册信息,默认为true
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka/ #客户端可注册的eureka服务地址
- 应用主启动类中添加@EnableEurekaClient注解,表示作为eurek客户端
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaClient
public class ProviderPaymentMainApp {
public static void main(String[] args) {
SpringApplication.run(ProviderPaymentMainApp.class, args);
}
}
- 启动服务后在Eureka注册中心列表能看到注册成功的两个服务order-service和payment-service,从页面中我们发现了Renews theshold值为5,Renews值为4,同时还有一行红色的提示说明(出现存在一分钟延迟),这文字表示触发了eureka的自我保护机制。两个服务明明都正常发送了心跳为什么还会触发eureka的自我保护机制呢?这是eureka服务端设置了eureka.client.register-with-eureka = false引发的问题。
3. Eureka的特性
3.1 自我保护机制
1. 什么是自我保护机制?
自我保护机制指当eureka server节点在短时间内丢失过多客户端的心跳时,应该优先认定为是发生了网络分区故障(延时、卡顿、拥挤),而不是认为客户端服务真正不可用了,避免因eureka server节点无法正常通信而删除健康可用服务实例的一种保护策略。
2. 为什么要有自我保护机制?
我们前面了解到如果eureka server在一定时间(默认90s)内没有接收到某个微服务实例的心跳,eureka server将会注销该实例。这种方式存在缺点,如果eureka server节点在某一时刻与注册在该节点的所有客户端服务都无法进行正常的心跳通信了,在90s过后eureka server节点把这些客户端服务都注销了,而这些客户端服务又都是正常能提供服务的,不就发生了错误吗?eureka为了避免这种错误引入了自我保护机制,eureka server在触发自我保护机制后不再对未完成心跳续约的服务实例进行剔除,保证了服务的高可用。
3. 自我保护机制的触发条件?
eureka的自我保护机制默认是开启的,而触发条件是: eureka server每分钟收到的总续约次数(Renews)低于或等于续约阈值(Renews threshold)就会触发。当它一分钟收到的续约总数重新恢复到阈值以上时,该eureka server结点才会自动退出自我保护模式。阈值计算有坑,不建议设置eureka-server的eureka.client.register-with-eureka = false,集群模式为true不会有影响。
4. 续约阈值和续约次数如何计算?
默认需续约的阈值百分比:eureka.server.renewal-percent-threshold = 0.85
默认向服务端发送心跳的时间间隔:eureka.instance.lease-renewal-interval-in-seconds = 30
注意:阈值计算是取默认心跳时间间隔30s
阈值计算公式:Renews threshold = 服务实例总数(server+client)* 每分钟默认需要续约次数(60s/30s)* 0.85
上面案例:1台eureka server 时,Renews threshold = 1*(60/30)*0.85 = 1.7 取整为1
1台eureka server,2台eureka client 时,Renews threshold = (1+2)*(60/30)*0.85 = 5.1 取整为5
最后,我们要知道续约阈值什么时候更新,这个涉及源码级别的分析,我们只简单了解续约阈值更新可以通过 eureka.server.renewal-threshold-update-interval-ms = xxx 来配置,默认是15分钟更新一次。
注意:续约总次数计算是取每个服务实例各自的心跳时间间隔
一分钟续约总次数计算:Renews = 全部服务实例一分钟续约次数相加
上面案例: 2台eureka client 发送心跳间隔为30s,则一分钟续约总次数 = 2*(60/30)= 4次,比续约阈值5还少,所以触发了自我保护机制。
思考:那么是什么原因导致的呢?
我们试着设置eureka server的eureka.client.register-with-eureka = true重启服务试试,结果完美解决所有问题,Renews变成了6,大于续约阈值5了,不再错误触发自我保护机制了。默认eureka server是希望把自己也注册进去的,也会给自己发心跳续约,对于集群模式来说这样是合理的,一般eureka server也不会是单机部署的。
5. 对自我保护机制的总结?
触发自我保护机制条件(Renews<=Renews threshold)后Lease expiration enabled变为false,表示续约不会过期,所以不再对服务实例进行注销。阈值计算是取默认心跳时间间隔30s,配置服务实例心跳间隔不建议大于30s,否则容易触发自我保护机制。自我保护机制可以设置eureka.server.enable-self-preservation = false关闭。
3.2 集群支持AP特性
CAP理论,就是指在一个分布式系统中,Consistency(一致性)、Availability(可用性)、Partition tolerance(分区容错性),三者不能同时满足,只能满足其中的任意两项。
1. Consistency(一致性):指的是数据的一致性,即分布式系统中的任意结点服务被用户访问都能拿到相同的数据,即服务结点之间数据是没有差别的,这种一致性是严格要求的,指的是强一致性。
2. Availability(可用性):指的是分布式系统在出现某一节点宕机或数据不一致时,也要保证系统的正常可用。可用性强调的是系统的高可用,可以接受一部分数据不是最新的要求。
3. Partition tolerance(分区容错性): 只要是分布式系统就可能会发生网络分区,这里的分区是指某一个或多个结点因为网络问题暂时与集群结点无法通信,就形成了分区。分区容错性是指分布式系统要能够容忍网络分区带来的AC不能同时满足的问题,保证系统能正常运行,这通常是必须的。如果一个系统既要满足A(可用性)和P(一致性),那么这个系统一定是不会发生网络分区和存在分区容错性问题的。
为什么Eureka的集群是支持AP特性的呢?这是因为eureka的集群结点设计是Peer to Peer对等的,没有主从之分。Eureka的客户端服务在向某个Eureka服务结点注册时如果发现注册失败,则会继续选择其它节点进行注册,只要有一台Eureka服务结点还在,就能保证集群服务的可用(保证了可用性)
4. Eureka的集群
4.1 不分区集群模式
- eureka的集群设计是Peer to Peer,即集群结点与结点之间是平等的没有主从之分,结点之间采用对等复制的方式同步数据,所以集群搭建就是eureka server结点能够互相注册,而不分区集群是让所有服务结点都在defaultZone默认分区中。
# 集群模式下eureka server也需要作为客户端注册到eureka中
eureka.client.register-with-eureka = true
- 创建一个springboot项目eureka-server02与前面的eureka-server01共同搭建集群,修改本机路径下 C:\Windows\System32\drivers\etc 的hosts文件。
# 添加ip地址与主机名的映射
127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com
- eureka注册中心结合springsecurity实现密码安全验证,先在pom.xml文件中引入依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
- 再创建springsecurity的配置类,对/eureka/** 下的所有请求过滤CSRF。
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 使CSRF忽略 /eureka/**的所有请求
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
//忽略所有 /eureka/** 的请求
http.csrf().ignoringAntMatchers("/eureka/**");
}
}
- 配置eureka-server01的application.yml
server:
port: 7001
spring:
application:
name: eureka-server # 服务的应用名称,部署集群需保证应用名称一样
security: # security用户认证的账号密码
user:
name: admin
password: admin12
eureka:
instance:
hostname: eureka7001.com # euraka服务实例的主机名或域名
instance-id: eureka-server01 # euraka服务实例在注册中心的唯一实例ID
client:
register-with-eureka: true # 集群模式eureka需要相互注册发现彼此,必须设为true
fetch-registry: true # 是否获取eureka的注册表信息,默认为true
service-url:
# 客户端可注册的eureka服务地址,集群需要写多个,但最终只会选择一个可用的注册
defaultZone: http://admin:admin12@eureka7002.com:7002/eureka/,http://admin:admin12@eureka7001.com:7001/eureka/
server:
eviction-interval-timer-in-ms: 300000 # 剔除eureka上未正常心跳续约服务实例的时间间隔
renewal-threshold-update-interval-ms: 900000 # 更新续约阈值的时间间隔(默认15分钟)
- 配置eureka-server02的application.yml
server:
port: 7002
spring:
application:
name: eureka-server # 服务的应用名称,部署集群需保证应用名称一样
security: # security用户认证的账号密码
user:
name: admin
password: admin12
eureka:
instance:
hostname: eureka7002.com # euraka服务实例的主机名或域名
instance-id: eureka-server02 # euraka服务实例在注册中心的唯一实例ID
client:
register-with-eureka: true # 集群模式erueaka需要相互注册发现彼此,必须设为true
fetch-registry: true # 是否获取eureka的注册表信息,默认为true
service-url:
# 客户端可注册的eureka服务地址,集群需要写多个,但最终只会选择一个可用的注册
defaultZone: http://admin:admin12@eureka7001.com:7001/eureka/,http://admin:admin12@eureka7002.com:7002/eureka/
server:
eviction-interval-timer-in-ms: 300000 # 剔除eureka上未正常心跳续约服务实例的时间间隔
renewal-threshold-update-interval-ms: 900000 # 更新续约阈值的时间间隔(默认15分钟)
- order-service服务和payment-service注册到eureka集群中,要在注册地址上加上认证的用户和密码才能成功注册。
eureka:
client:
service-url:
defaultZone: http://admin:admin12@eureka7002.com:7002/eureka/,http://admin:admin12@eureka7001.com:7001/eureka/
- 访问 eureka7001.com:7001或 eureka7002.com:7002会进入springsecurity默认登录页面,输入自己配置的用户名和密码登录成功就表明eureka实现了安全认证功能,我们可以在注册中心页面查看集群信息和注册的服务实例。
- 在注册中心页面我们看到了order-service服务和payment-service服务被成功注册到集群中,同时也包括eureka-server的所有服务实例,这便于让我们知道eureka-server有几个结点,以及结点的健康状态。
4.2 分区集群模式
eureka集群提供了分区的功能, 它的分区集群是通过配置region和zone(或者availability-zones)来实现的,这些均是来源于AWS的概念。region和zone的英文单词都可以翻译为地区,在eureka中一个region可以包含多个zone,所以我们可以把region认为是一个大的区域,zone是region下面的小的分区。上图中的us-east-1c、us-east-1d、us-east-1e就是三个不同的zone,都属于region为us-east的这个地区。
- 配置eureka-server01的application.yml文件,让eureka-server01注册到广州(guangzhou)分区。
server:
port: 7001
spring:
application:
name: eureka-server # 服务的应用名称,部署集群需保证应用名称一样
security: # security用户认证的账号密码
user:
name: admin
password: admin12
eureka:
instance:
hostname: eureka7001.com # euraka服务实例的主机名或域名
instance-id: eureka-server01 # euraka服务实例在注册中心的唯一实例ID
client:
register-with-eureka: true # 集群模式eureka需要相互注册发现彼此,必须设为true
fetch-registry: true # 是否获取eureka的注册表信息,默认为true
prefer-same-zone-eureka: true # 尽量向同一区域的eureka注册,默认为true
# 集群所属的区域为华南地区
region: huanan
# 可用的分区,广州分区和深圳分区
availability-zones:
huanan: shenzhen,guangzhou # 按照顺序从可用分区中选择一个可用地址注册
service-url:
guangzhou: http://admin:admin12@eureka7001.com:7001/eureka/
shenzhen: http://admin:admin12@eureka7002.com:7002/eureka/
server:
enable-self-preservation: false # 关闭自我保护机制,保证不可用服务被及时删除
eviction-interval-timer-in-ms: 30000 # 剔除eureka上未正常心跳续约服务实例的时间间隔
renewal-threshold-update-interval-ms: 900000 # 更新续约阈值的时间间隔(默认15分钟)
- 配置eureka-server02的application.yml文件,让eureka-server02注册到深圳(shenzhen)分区。
server:
port: 7002
spring:
application:
name: eureka-server # 服务的应用名称,部署集群需保证应用名称一样
security: # security用户认证的账号密码
user:
name: admin
password: admin12
eureka:
instance:
hostname: eureka7002.com # euraka服务实例的主机名或域名
instance-id: eureka-server02 # euraka服务实例在注册中心的唯一实例ID
client:
register-with-eureka: true # 集群模式eureka需要相互注册发现彼此,必须设为true
fetch-registry: true # 是否获取eureka的注册表信息,默认为true
prefer-same-zone-eureka: true # 尽量向同一区域的eureka注册,默认为true
# 集群所属的区域为华南地区
region: huanan
# 可用的分区,广州分区和深圳分区
availability-zones:
huanan: guangzhou,shenzhen # 按照顺序从可用分区中选择一个可用地址注册
service-url:
guangzhou: http://admin:admin12@eureka7001.com:7001/eureka/
shenzhen: http://admin:admin12@eureka7002.com:7002/eureka/
server:
enable-self-preservation: false # 关闭自我保护机制,保证不可用服务被及时删除
eviction-interval-timer-in-ms: 30000 # 剔除eureka上未正常心跳续约服务实例的时间间隔
renewal-threshold-update-interval-ms: 900000 # 更新续约阈值的时间间隔(默认15分钟)
- 消费者order-service服务注册进集群中,order-service服务所属zone为guangzhou,并会优先调用同一zone下的服务提供者,降低服务延迟。当同一zone下的服务提供者不可用时,才会调用其它zone下的服务。
server:
port: 8080
spring:
application:
name: order-service
eureka:
instance:
instance-id: order8080
lease-renewal-interval-in-seconds: 30 # Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
lease-expiration-duration-in-seconds: 90 # Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
metadata-map:
zone: guangzhou # 当前服务所属的zone
client:
register-with-eureka: true # 表示是否将自己注册进EurekaServer默认为true
fetch-registry: true # 是否获取eureka服务器注册表上的注册信息,默认为true
registry-fetch-interval-seconds: 30 # 指示从eureka服务器获取注册表信息的频率
prefer-same-zone-eureka: true # 尽量向同一区域的 eureka 注册,默认为true
region: huanan # 集群所属的区域为华南地区
availability-zones: # 可用的分区
huanan: guangzhou,shenzhen
service-url:
guangzhou: http://admin:admin12@eureka7001.com:7001/eureka/
shenzhen: http://admin:admin12@eureka7002.com:7002/eureka/
- 提供者payment-service服务注册进集群中,配置该payment-service服务端口号为8001,所属zone为guangzhou。
server:
port: 8001
spring:
application:
name: payment-service
eureka:
instance:
instance-id: payment8001
lease-renewal-interval-in-seconds: 30 # Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
lease-expiration-duration-in-seconds: 90 # Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
metadata-map:
zone: guangzhou # 当前服务所属的zone为guangzhou
client:
register-with-eureka: true # 表示是否将自己注册进EurekaServer默认为true
fetch-registry: true # 是否获取eureka服务器注册表上的注册信息,默认为true
registry-fetch-interval-seconds: 30 # 指示从eureka服务器获取注册表信息的频率
prefer-same-zone-eureka: true # 尽量向同一区域的 eureka 注册,默认为true
region: huanan # 集群所属的区域为华南地区
availability-zones: # 可用的分区
huanan: guangzhou,shenzhen
service-url:
guangzhou: http://admin:admin12@eureka7001.com:7001/eureka/
shenzhen: http://admin:admin12@eureka7002.com:7002/eureka/
- 提供者payment-service服务注册进集群中,配置该payment-service服务端口号为8002,所属zone为shenzhen。
server:
port: 8002
spring:
application:
name: payment-service
eureka:
instance:
instance-id: payment8002
lease-renewal-interval-in-seconds: 30 # Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
lease-expiration-duration-in-seconds: 90 # Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
metadata-map:
zone: shenzhen # 当前服务所属的zone为shenzhen
client:
register-with-eureka: true # 表示是否将自己注册进EurekaServer默认为true
fetch-registry: true # 是否获取eureka服务器注册表上的注册信息,默认为true
registry-fetch-interval-seconds: 30 # 指示从eureka服务器获取注册表信息的频率
prefer-same-zone-eureka: true # 尽量向同一区域的 eureka 注册,默认为true
region: huanan # 集群所属的区域为华南地区
availability-zones: # 可用的分区
huanan: shenzhen,guangzhou
service-url:
guangzhou: http://admin:admin12@eureka7001.com:7001/eureka/
shenzhen: http://admin:admin12@eureka7002.com:7002/eureka/
- 分区集群搭建后启动项目查看eureka管理界面查看服务结点列表,查看分区集群搭建成功,但同时也看到了关闭了自我保护机制的红色文字提醒,关闭自我保护机制能够保证服务结点不可用时能够被及时下线处理。
- 测试服务消费者order-service调用服务提供者payment-service,我们发现order-service调用服务提供者接口时只调用端口为8001的payment-service服务实例,这是因为分区集群模式下服务消费者优先调用同一zone下的服务提供者,当同一zone下的服务提供者不可用时,才会去调用不同zone下的服务提供者。