Spring Cloud Eureka 基于 Netflix Eureka 进行了封装,增加了Spring Boot 特有的自动化配置风格,主要负责微服务中的服务治理功能,包括服务注册
和服务发现
。
我们希望有一个模块可以自动注册存在的微服务,而不是手动配置。在调用的时候,在这个模块中查找被注册的服务的位置,然后直接调用,这就是服务治理。
Eureka 主要用于服务治理,主要包含两大部分,服务
注册中心
和客户端
。客户端又可以根据需要分为服务提供者和服务消费者。
服务治理机制
服务注册中心
- 失效剔除:
在正常情况下,服务提供者下线时,将会提醒服务注册中心下线,然后服务注册中心会将服务的实例从服务列表剔除。
如果由于网络故障,没有将下线的请求信息发送给注册中心,服务中心不知道具体的服务提供者已经下线,在Eureka Server 启动的时候,一个定时任务将会被创建,每60s(默认)便去清单中剔除超过90s(默认) 的服务实例。 - 自我保护:
Eureka Server 会维持一个心跳,如果运行时,统计心跳的比例在15分钟内低于85%,则服务中心将会自动启动自我保护机制。
在自我保护机制中,当前的注册信息会被保护起来,使得这些信息不会过期。在实际情况中可能由于网络不稳定导致心跳失败,所以自我保护机制还是很有必要。在调试中会调用到不存在的服务实例,需要将自我保护机制关掉。
//自我保护机制
private boolean enableSelfPreservation = true;
private double renewalPercentThreshold = 0.85;
eureka:
server:
# 关掉自我保护
enable-self-preservation: false
服务提供者
- 服务注册:
只要加上注解就可以使得客户端到服务注册中心注册。客户端启动之后,会马上发送请求,并按照配置配置信息注册到服务注册中心,在请求中会带上服务的元数据信息。 - 服务续约:
在服务注册之后,服务提供者需要使用心跳来维护注册的服务。为了防止剔除,服务提供者使用心跳进行维护,这个过程称为服务续约。
euewka:
instance:
# 续约时间,也是心跳时间
lease-renewal-interval-in-seconds: 30
# 服务时效时间 超时时间
lease-expiration-duration-in-seconds: 90
服务消费者
- 服务下线
当服务进行关闭,客户端就会触发一个服务消息给Eureka Server, 提醒这是一个正常的服务关闭操作,然后服务注册中心就可以把服务实例从列表删除。 - 获取服务:
启动服务消费者,将会触发一个请求给服务注册中心,将服务注册中心的服务清单获取下来,并在客户端维护。也可以定期更新本地维护的清单,默认30s。
eureka:
client:
# 定期更新本地维护的清单
registry-fetch-interval-seconds: 30
查看自动配置类
服务注册中心配置
org.springframework.cloud.netflix.eureka.server.EurekaServerConfigBean
@ConfigurationProperties(EurekaServerConfigBean.PREFIX)
public class EurekaServerConfigBean implements EurekaServerConfig {
//服务注册中心的配置都是以 eureka.server 开头
public static final String PREFIX = "eureka.server";
private static final int MINUTES = 60 * 1000;
@Autowired(required = false)
PropertyResolver propertyResolver;
private String aWSAccessId;
private String aWSSecretKey;
private int eIPBindRebindRetries = 3;
private int eIPBindingRetryIntervalMs = 5 * MINUTES;
private int eIPBindingRetryIntervalMsWhenUnbound = 1 * MINUTES;
//自我保护机制
private boolean enableSelfPreservation = true;
//心跳失败比例
private double renewalPercentThreshold = 0.85;
//续约数阀值更新频率
private int renewalThresholdUpdateIntervalMs = 15 * MINUTES;
..........
服务注册类的配置
org.springframework.cloud.netflix.eureka.EurekaClientConfigBean
@ConfigurationProperties(EurekaClientConfigBean.PREFIX)
public class EurekaClientConfigBean implements EurekaClientConfig, Ordered {
//客户端配置都是以 eureka.client 开头
public static final String PREFIX = "eureka.client";
/**
* Default Eureka URL.
*/
public static final String DEFAULT_URL = "http://localhost:8761" + DEFAULT_PREFIX + "/";
//默认可用分区。
public static final String DEFAULT_ZONE = "defaultZone";
private static final int MINUTES = 60;
@Autowired(required = false)
PropertyResolver propertyResolver;
// 是否启动客户端
private boolean enabled = true;
// 获取服务的时间间隔
private int registryFetchIntervalSeconds = 30;
//更新实例到 Server 的时间
private int instanceInfoReplicationIntervalSeconds = 30;
//初始化实例到Server 的时间
private int initialInstanceInfoReplicationIntervalSeconds = 40;
//轮询Server地址更改时间
private int eurekaServiceUrlPollIntervalSeconds = 5 * MINUTES;
//读取 Server 超时时间
private int eurekaServerReadTimeoutSeconds = 8;
// 连接 Server 时间
private int eurekaServerConnectTimeoutSeconds = 5;
//Client 到Server 的连接总数
private int eurekaServerTotalConnections = 200;
//Client 到每个主机的连接总数
private int eurekaServerTotalConnectionsPerHost = 50;
........
服务实例类的配置
org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean
//实力类的配置以 eureka.instance 开头
@ConfigurationProperties("eureka.instance")
public class EurekaInstanceConfigBean implements CloudEurekaInstanceConfig, EnvironmentAware {
private static final String UNKNOWN = "unknown";
//配置前缀
private String actuatorPrefix = "/actuator";
// 应用名
private String appname = UNKNOWN;
// 应用组名
private String appGroupName;
// 实例注册之后,是否马上开启通信,默认 false
private boolean instanceEnabledOnit;
// 非安全端口
private int nonSecurePort = 80;
// 安全端口
private int securePort = 443;
..............
服务注册中心仪表盘配置
服务注册中心的仪表盘用于服务注册中心的可视化展示。org.springframework.cloud.netflix.eureka.server.EurekaDashboardProperties
@ConfigurationProperties("eureka.dashboard")
public class EurekaDashboardProperties {
// 仪表盘访问路径
private String path = "/";
// 是否启用仪表盘
private boolean enabled = true;
如
eureka:
dashboard:
path: /dashboard
# 查看源码
DiscoveryClient 实例
/**
* Convenience annotation for clients to enable Eureka discovery configuration
* (specifically). Use this (optionally) in case you want discovery and know for sure that
* it is Eureka you want. All it does is turn on discovery and let the autoconfiguration
* find the eureka classes if they are available (i.e. you need Eureka on the classpath as
* well).
*
* @author Dave Syer
* @author Spencer Gibb
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface EnableEurekaClient {
}
通过注释发现,@EnableEurekaClient的作用就是启动Eureka Discovery configuration.
public class EurekaDiscoveryClient implements DiscoveryClient {
EurekaDiscoveryClient类的作用就是与Eureka Server 相互协作/
Eureka Client 负责向Server 注册服务实例、向Server 租约续期、向Server取消租约、查询Server中的服务实例列表,Eureka Client 还需要配置一个Eureka Server 的URL列表。
服务发现
从要与eureka客户机对话的属性文件中获取所有eureka服务url的列表。
public static List<String> getServiceUrlsFromConfig(EurekaClientConfig clientConfig, String instanceZone, boolean preferSameZone) {
List<String> orderedUrls = new ArrayList<String>();
// 读取region 并返回,每个微服务对应一个 region
String region = getRegion(clientConfig);
String[] availZones = clientConfig.getAvailabilityZones(clientConfig.getRegion());
if (availZones == null || availZones.length == 0) {
availZones = new String[1];
availZones[0] = DEFAULT_ZONE;
}
logger.debug("The availability zone for the given region {} are {}", region, availZones);
int myZoneOffset = getZoneOffset(instanceZone, preferSameZone, availZones);
List<String> serviceUrls = clientConfig.getEurekaServerServiceUrls(availZones[myZoneOffset]);
if (serviceUrls != null) {
orderedUrls.addAll(serviceUrls);
}
int currentOffset = myZoneOffset == (availZones.length - 1) ? 0 : (myZoneOffset + 1);
while (currentOffset != myZoneOffset) {
serviceUrls = clientConfig.getEurekaServerServiceUrls(availZones[currentOffset]);
if (serviceUrls != null) {
orderedUrls.addAll(serviceUrls);
}
if (currentOffset == (availZones.length - 1)) {
currentOffset = 0;
} else {
currentOffset++;
}
}
if (orderedUrls.size() < 1) {
throw new IllegalArgumentException("DiscoveryClient: invalid serviceUrl specified!");
}
return orderedUrls;
}
获取实例
@Override
public List<ServiceInstance> getInstances(String serviceId) {
List<InstanceInfo> infos = this.eurekaClient.getInstancesByVipAddress(serviceId, false);
List<ServiceInstance> instances = new ArrayList<>();
for (InstanceInfo info : infos) {
instances.add(new EurekaServiceInstance(info));
}
return instances;
}