概要
You’ll setup a Netflix Eureka service registry and then build a client that both registers itself with the registry and uses it to resolve its own host. A service registry is useful because it enables client-side load-balancing and decouples service providers from consumers without the need for DNS.
您将设置一个Netflix Eureka服务注册表,然后构建一个客户端,该客户端都将自己注册到注册表并使用它来解析自己的主机。 服务注册表很有用,因为它支持客户端负载平衡,并且无需DNS即可将服务提供者与消费者分离
关键字:client-side load-balancing(客户端负载均衡)
、decouples(解耦)
eureka-server
pom.xml
<!-- springboot版本和cloud版本最好还是按文档来,已经踩过了不少坑 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<!-- 服务注册和发现的库 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<!-- springcloud version Finchley.SR2 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.SR2</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>
配置:
server:
port: 1025
eureka:
instance:
# 实例host
hostname: localhost
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
server:
# 是否开启自我保护(默认true)|| 是否剔除异常服务
enable-self-preservation: true
logging:
level:
com.netflix.eureka: OFF
com.netflix.discovery: OFF
启动类:
- 启动日志会有一些,生产环境使用多节点配置,警告消失。
- 默认情况下,注册表将还尝试将其自身注册,需要禁用
- 本地使用它时,将此注册表放在单独的端口上
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
// use Spring Cloud’s @EnableEurekaServer to standup a registry
@EnableEurekaServer
@SpringBootApplication
public class EurekaServiceApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServiceApplication.class, args);
}
}
eureka-client
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- 客户端:eureka-client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.SR2</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>
配置(bootstrap.yml):
默认配置:
- 服务端口为8080
- 注册地址为http://localhost:8761/eureka
- 通常在服务配置的最早阶段使用。此属性用于服务引导程序(bootstrap.yml)
spring:
application:
name: eureka-client-example
代码:
- @EnableDiscoveryClient:开启DiscoveryClient
- 其他服务注册和发现的实现,同样可以使用@EnableDiscoveryClient
- 服务
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
// @EnableDiscoveryClient activates the Netflix Eureka DiscoveryClient implementation.
@EnableDiscoveryClient
@SpringBootApplication
public class EurekaClientApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaClientApplication.class, args);
}
}
@RestController
class ServiceInstanceRestController {
@Autowired
private DiscoveryClient discoveryClient;
/**
* 返回所有的枚举ServiceInstance在注册表中注册的实
*/
@RequestMapping("/service-instances/{applicationName}")
public List<ServiceInstance> serviceInstancesByApplicationName(
@PathVariable String applicationName) {
return this.discoveryClient.getInstances(applicationName);
}
}
测试:
访问:http://127.0.0.1:8080/service-instances/eureka-client-example,得到如下响应
[
{
"host":"192.168.0.107",
"port":8080,
"secure":false,
"instanceInfo":{
"instanceId":"192.168.0.107:eureka-client-example",
"app":"EUREKA-CLIENT-EXAMPLE",
"appGroupName":null,
"ipAddr":"192.168.0.107",
"sid":"na",
"homePageUrl":"http://192.168.0.107:8080/",
"statusPageUrl":"http://192.168.0.107:8080/actuator/info",
"healthCheckUrl":"http://192.168.0.107:8080/actuator/health",
"secureHealthCheckUrl":null,
"vipAddress":"eureka-client-example",
"secureVipAddress":"eureka-client-example",
"countryId":1,
"dataCenterInfo":{
"@class":"com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo",
"name":"MyOwn"
},
"hostName":"192.168.0.107",
"status":"UP",
"overriddenStatus":"UNKNOWN",
"leaseInfo":{
"renewalIntervalInSecs":30,
"durationInSecs":90,
"registrationTimestamp":1555866795740,
"lastRenewalTimestamp":1555866945689,
"evictionTimestamp":0,
"serviceUpTimestamp":1555866795741
},
"isCoordinatingDiscoveryServer":false,
"metadata":{
"management.port":"8080",
"jmx.port":"61062"
},
"lastUpdatedTimestamp":1555866795741,
"lastDirtyTimestamp":1555866795618,
"actionType":"ADDED",
"asgName":null
},
"serviceId":"EUREKA-CLIENT-EXAMPLE",
"metadata":{
"management.port":"8080",
"jmx.port":"61062"
},
"uri":"http://192.168.0.107:8080",
"scheme":null
}
]
详细配置
**eureka.instance.*: Eureka:实例注册配置项**
org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean
**eureka.server.*: Eureka:Eureka Server配置项**
org.springframework.cloud.netflix.eureka.EurekaClientConfigBean
**eureka.client.*: Eureka:Eureka Client配置的项**
org.springframework.cloud.netflix.eureka.server.EurekaServerConfigBean
注册相关(eureka-server):
- 服务提供者在启动会向Eureka Server注册信息(包括服务名(Eureka Service Id)、service id、ip、port、心跳周期等),EurekaServer获取注册请求,将服务实例信息存入到读写缓存中
- 服务消费者根据Eureka service id向Eureka Server获取要访问服务的注册信息,第一次请求会一次性拉取对应Eureka service id的全部服务实例信息
- Eureka Server收到请求后,会首先在只读缓存查找,如果找到则直接返回,否则再查找读写缓存,如果找到则将再存入到只读缓存中,然后返回查找结果
- 服务提供者获取信息的,将服务实例信息缓存到本地,在使用时根据算法从N个服务中中选取一个服务并向这个服务发送请求
spring:
application:
# 本服务注册到注册到服务器的名称, 这个名称就是后面调用服务时的服务标识符,即Eureka Service Id
name: eureka-server
eureka:
client:
# 该实例是否向 Eureka Server注册自己
register-with-eureka: false
# 该实例是否向 Eureka 服务器获取所有的注册信息表
fetch-registry: true
# 配置 Eureka-Server 地址
serviceUrl:
defaultZone: http://127.0.0.1:8761/eureka/
续约相关(eureka-client):
- 服务提供者会定时向Eureka Server发送心跳,默认30s
- Eureka Server收到心跳后,会更新对应的服务实例信息,如果服务的状态有变化则将实例的变化加入到”最近租约变更记录队列”中
- Eureka Server有个实例过期清理定时器,如果在指定时间内没有收到心跳(默认90s),则认为服务已经下线,会从读写缓存中移除此实例,将并此变化更新“最近租约变更记录队列”。通常建议将存活的最长时间设置为3个心跳
- 服务消费者也有个定时任务会定时去更新服务实例信息(默认30s),第一次全量拉取服务实例,会读取所有的实例信息; 之后使用增量拉取服务实例信息,Eureka Server根据”最近租约变更记录队列”,告诉请求方哪些服务实例有变化,只返回变化的实例信息。客户端根据返回的增量信息更新本地缓存。我们也可以禁用增量获取服务实例实例,每次使用全量获取服务实例信息
- 服务下线
- 主动下线,在服务实例结束前,主动通知Eureka Server,在默认配置下,此时服务消费者最长在30s左右知道此服务已经下线
- 被动下线,服务进度崩溃/网络异常,此时服务消费者最长在(3次心跳+一次刷新频率30s)(共约120s左右)内知道此服务已经下线
eureka:
client:
# 是否从 Eureka-Server 拉取服务实例注册信息,默认值为true
fetch-registry: true
# 从 Eureka-Server 拉取注册服务实例信息频率,默认:30 秒
registry-fetch-interval-seconds: 30
# 是否禁用增量获取服务实例注册信息
disableDelta: false
instance:
# 心跳,租约续约频率,单位:秒
lease-renewal-interval-in-seconds: 30
# eureka server多久没有收到心跳,则表示对应的实例过期,单位:秒
lease-expiration-duration-in-seconds: : 90s
# Eureka Server端服务实例租约过期定时任务执行频率
eviction-interval-timer-in-ms: 60s
缓存配置(eureka-server):
- 读写缓存:每次有服务实例状态变化(如注册服务、下线等)只会更新读写缓存
- 只读缓存永不过期,但是可能存在过期数据。此时为了保证只读数据的准确性,会有个定时器定时同步两个缓存,然后将状态变化的服务实例添加到”最近租约变更记录队列”。执行频率默认30s
“最近租约变更记录队列”里的数据也有有效期,默认为180s。
- 当有注册、状态变更、下线时,会将创建最近租约变更记录加入此队列中
- 用于注册信息增量获取
- 后台任务定时顺序扫描队列,当数据最后更新时间超过一定时长后进行移除
eureka:
server:
# 是否开启只读请求响应缓存。响应缓存 ( ResponseCache ) 机制目前使用两层缓存策略。
# 优先读取只读缓存,读取不到后读取固定过期的读写缓存。
use-read-only-response-cache: true
# 只读缓存更新频率,单位:毫秒。只读缓存定时更新任务只更新读取过请求 ,
# 因此虽然永不过期,也会存在读取不到的情况。
response-cache-update-interval-ms: 30s
# 读写缓存写入后过期时间,单位:秒。
response-cache-auto-expiration-in-seconds: 180s
# 移除队列里过期的租约变更记录的定时任务执行频率,单位:毫秒。默认值 :30 * 1000 毫秒。
delta-retention-timer-interval-in-ms: 30s
# 租约变更记录过期时长,单位:毫秒。默认值 : 3 * 60 * 1000 毫秒。
retention-time-in-m-s-in-delta-queue: 180s
自我保护机制(eureka-server):
- Renews threshold: Eureka Server 期望每分钟最少收到服务实例心跳的总数
- Renews threshold = 当前注册的服务实例数 x 2 * 自我保护系数( eureka.renewalPercentThreshold )
- 之所以 x 2,是因为Eureka 硬编码认为每个实例每 30 秒 1 个心跳,即1分钟2个心跳
- Renews (last min): Eureka Server 最近 1 分钟收到服务实例心跳的总数
eureka:
server:
# 是否开启自我保护模式。
enable-self-preservation: false
# 开启自我保护模式比例,超过该比例后开启自我保护模式。
# 是否开启自我保护的差别,在于是否执行清理过期租约逻辑。
# 如果想关闭分批逐步过期,设置 renewalPercentThreshold = 0 。
renewal-percent-threshold: 0.85
# 自我保护模式比例更新定时任务执行频率。
renewal-threshold-update-interval-ms: 900s
集群:
多个Eureka Client进程向注册中心注册的Eureka Service Id相同,则被认为是一个集群
多个Eureka Server相互注册,组成一个集群
为了保证集群里所有Eureka Server节点的状态同步,所有以下操作都会同步到集群的所有服务上:服务注册(Registers)、服务更新(Renewals)、服务取消(Cancels),服务超时(Expirations)和服务状态变更(Status Changes)
- syncUp:在Eureka Server重启或新的Eureka Server节点加进来的,会执行初始化,从而能够正常提供服务。当Eureka server启动时,他会从其它节点获取所有的注册信息。如果获取同步失败,它在一定时间(此值由决定)内拒绝服务
- replicateToPeers: 复印所有的eureka操作到集群中其他节点,请求再次转发到其它的Eureka Server,调用同样的接口,传入同样的参数,除了会在header中标记isReplication=true,从而避免重复的replicate
- register: 注册登录的实例,并且复印此实例的信息到所有的eureka server的节点。如果其它Eureka server调用此节点,只在本节点更新实例信息,避免通知其他节点执行更新
- renew:心跳
已有的Eureka Server:在运行过程中,Eureka Server之间会定时同步实例的注册信息。这样即使新的Application Service只向集群中一台注册服务,则经过一段时间会集群中所有的Eureka Server都会有这个实例的信息。那么Eureka Server节点之间如何相互发现,各个节点之间定时(时间由eureka.server.peer-eureka-nodes-update-interval-ms决定)更新节点信息,进行相互发现。
Service Consumer:Service Consumer刚启动时,它会从配置文件读取Eureka Server的地址信息。当集群中新增一个Eureka Server中时,那么Service Provider如何发现这个Eureka Server?Service Consumer会定时(此值由eureka.client.eureka-service-url-poll-interval-seconds决定)调用Eureka Server集群接口,获取所有的Eureka Server信息的并更新本地配置。
eureka:
server:
# Eureka-Server 启动时,从远程 Eureka-Server 读取不到注册信息时,多长时间不允许 Eureka-Client 访问
wait-time-in-ms-when-sync-empty: 5
# Eureka-Server 集群节点更新频率,单位:毫秒
eureka.server.peer-eureka-nodes-update-interval-ms:
# 初始化实例信息到Eureka服务端的间隔时间,单位为秒
initial-instance-info-replication-interval-seconds: 40
# 更新实例信息的变化到Eureka服务端的间隔时间,单位为秒
instance-info-replication-interval-seconds: 30