一.简介
Eureka是Netflix中的一个开源框架,由SpringCloud整合的一个的一个基于服务发现及注册的微服务框架,Eureka主要包含Eureka Server(服务端)及Eureka Client(客户端)两个组件。Eureka Server端提供服务注册、服务续约、服务下线、服务健康检查等功能;Eureka client需要在启动时根据配置的Server端地址去注册自己,并定时向服务端发送心跳并维护本地服务列表。
Eureka满足AP,高可用与可伸缩的Service发现服务,突出可用性。
(CAP原理
P:Partition tolerance,网络分区容错。类似多机房部署,保证服务稳定性。
A: Availability,可用性。
C:Consistency ,一致性。)
二.Eureka Server端
服务端示例代码开发及配置
- 引入POM
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
- 添加配置
spring:
profiles:
active: dev
application:
name: eurekaserver01
eureka:
instance:
#注册名默认是“IP名:应用名:应用端口名”,即${spring.cloud.client.ipAddress}:${spring.application.name}:${spring.application.instance_id:${server.port}}
instance-id: eurekaserver01
client:
#表示是否从Eureka Server获取注册的服务信息
fetch-registry: false
# 续约间隔时间 单位为秒
#registry-fetch-interval-seconds: 30
#表示是否将自己注册到Eureka Server
register-with-eureka: false
service-url:
# 服务地址 可配置多个中间按照逗号分割
defaultZone: http://eurekaserver01:7001/eureka/
server:
# 关闭自我保护机制
enable-self-preservation: false
# 清理间隔(单位:毫秒,默认是60*1000),当服务心跳失效后多久,删除服务
eviction-interval-timer-in-ms: 3000
- 在启动类中添加@EnableEurekaServer
@SpringBootApplication
@EnableEurekaServer
public class Eurekaserver01Application {
public static void main(String[] args) {
SpringApplication.run(Eurekaserver01Application.class, args);
}
}
Eureka Server 源码解析
Eureka Server端的代码不多在org.springframework.cloud.netflix.eureka.server中
服务注册
在这个方法中com.netflix.eureka.registry.AbstractInstanceRegistry#register(com.netflix.appinfo.InstanceInfo, int, boolean),主要做了以下事情:
- 发布服务注册监听事件;
- 如果是新注册的服务则更新续订客户端数量(在原有的数量上加1)
- 重新计算期望收到的续订数(=续订客户端数量*(60/多久续订一次)*自我保护机制阀值)向下取整;
- 如果实例状态不等于UNKNOWN,则更新实例状态
- 将当前实例添加到注册列表(ConcurrentHashMap<String, Map<String, Lease>> registry)
源码如下:
@Override
/**
*info :表示注册的势力信息
*/
public void register(InstanceInfo info, int leaseDuration, boolean isReplication) {
//添加服务注册监听事件
handleRegistration(info, leaseDuration, isReplication);
//服务注册
super.register(info, leaseDuration, isReplication);
}
/**
* Registers a new instance with a given duration.
*
* @see com.netflix.eureka.lease.LeaseManager#register(java.lang.Object, int, boolean)
*/
public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
read.lock();
try {
//根据服务名称在注册列表中获取服务租约信息
Map<String, Lease<InstanceInfo>> gMap = registry.get(registrant.getAppName());
REGISTER.increment(isReplication);
if (gMap == null) {
final ConcurrentHashMap<String, Lease<InstanceInfo>> gNewMap = new ConcurrentHashMap<String, Lease<InstanceInfo>>();
gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap);
if (gMap == null) {
gMap = gNewMap;
}
}
Lease<InstanceInfo> existingLease = gMap.get(registrant.getId());
// 当前租约存在
if (existingLease != null && (existingLease.getHolder() != null)) {
//现有的租约时间戳
Long existingLastDirtyTimestamp = existingLease.getHolder().getLastDirtyTimestamp();
//本次注册的租约时间
Long registrationLastDirtyTimestamp = registrant.getLastDirtyTimestamp();
logger.debug("Existing lease found (existing={}, provided={}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);
if (existingLastDirtyTimestamp > registrationLastDirtyTimestamp) {
logger.warn("There is an existing lease and the existing lease's dirty timestamp {} is greater" +
" than the one that is being registered {}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);
logger.warn("Using the existing instanceInfo instead of the new instanceInfo as the registrant");
registrant = existingLease.getHolder();
}
} else {
// 租约不存在,因此是新的注册
synchronized (lock) {
if (this.expectedNumberOfClientsSendingRenews > 0) {
// 注册的客户端数量加1
this.expectedNumberOfClientsSendingRenews = this.expectedNumberOfClientsSendingRenews + 1;
updateRenewsPerMinThreshold();
}
}
logger.debug("No previous lease information found; it is new registration");
}
Lease<InstanceInfo> lease = new Lease<>(registrant, leaseDuration);
if (existingLease != null) {
lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp());
}
gMap.put(registrant.getId(), lease);
recentRegisteredQueue.add(new Pair<Long, String>(
System.currentTimeMillis(),
registrant.getAppName() + "(" + registrant.getId() + ")"));
// 如果服务状态不为UNKNOWN,则设置服务状态
if (!InstanceStatus.UNKNOWN.equals(registrant.getOverriddenStatus())) {
logger.debug("Found overridden status {} for instance {}. Checking to see if needs to be add to the "
+ "overrides", registrant.getOverriddenStatus(), registrant.getId());
if (!overriddenInstanceStatusMap.containsKey(registrant.getId())) {
logger.info("Not found overridden id {} and hence adding it", registrant.getId());
overriddenInstanceStatusMap.put(registrant.getId(), registrant.getOverriddenStatus());
}
}
InstanceStatus overriddenStatusFromMap = overriddenInstanceStatusMap.get(registrant.getId());
if (overriddenStatusFromMap != null) {
logger.info("Storing overridden status {} from map", overriddenStatusFromMap);
registrant.setOverriddenStatus(overriddenStatusFromMap);
}
// 根据覆盖的状态规则设置状态
InstanceStatus overriddenInstanceStatus = getOverriddenInstanceStatus(registrant, existingLease, isReplication);
registrant.setStatusWithoutDirty(overriddenInstanceStatus);
// 如果租约已注册为UP状态,设置租约服务UP时间戳
if (InstanceStatus.UP.equals(registrant.getStatus())) {
lease.serviceUp();
}
registrant.setActionType(ActionType.ADDED);
recentlyChangedQueue.add(new RecentlyChangedItem(lease));
registrant.setLastUpdatedTimestamp();
invalidateCache(registrant.getAppName(), registrant.getVIPAddress(), registrant.getSecureVipAddress());
logger.info("Registered instance {}/{} with status {} (replication={})",
registrant.getAppName(), registrant.getId(), registrant.getStatus(), isReplication);
} finally {
read.unlock();
}
}
服务下线
com.netflix.eureka.registry.AbstractInstanceRegistry#cancel
主要做了以下事情:
- 发布服务下线事件
- 根据实例ID将当前服务在注册列表中删除
- 根据实例ID删除状态列表
- 设置实例的 actionType为DELETED
- 如果是新注册的服务则更新续订客户端数量(expectedNumberOfClientsSendingRenews 在原有的数量上减1)
- 重新计算期望收到的续订数(=续订客户端数量*(60/多久续订一次)*自我保护机制阀值)向下取整;
源码如下:
@Override
public boolean cancel(String appName, String serverId, boolean isReplication) {
handleCancelation(appName, serverId, isReplication);
return super.cancel(appName, serverId, isReplication);
}
@Override
public boolean cancel(final String appName, final String id,
final boolean isReplication) {
if (super.cancel(appName, id, isReplication)) {
replicateToPeers(Action.Cancel, appName, id, null, null, isReplication);
return true;
}
return false;
}
/**
* {@link #cancel(String, String, boolean)} method is overridden by {@link PeerAwareInstanceRegistry}, so each
* cancel request is replicated to the peers. This is however not desired for expires which would be counted
* in the remote peers as valid cancellations, so self preservation mode would not kick-in.
*/
protected boolean internalCancel(String appName, String id, boolean isReplication) {
read.lock();
try {
CANCEL.increment(isReplication);
Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
Lease<InstanceInfo> leaseToCancel = null;
if (gMap != null) {
leaseToCancel = gMap.remove(id);
}
recentCanceledQueue.add(new Pair<Long, String>(System.currentTimeMillis(), appName + "(" + id + ")"));
InstanceStatus instanceStatus = overriddenInstanceStatusMap.remove(id);
if (instanceStatus != null) {
logger.debug("Removed instance id {} from the overridden map which has value {}", id, instanceStatus.name());
}
if (leaseToCancel == null) {
CANCEL_NOT_FOUND.increment(isReplication);
logger.warn("DS: Registry: cancel failed because Lease is not registered for: {}/{}", appName, id);
return false;
} else {
leaseToCancel.cancel();
InstanceInfo instanceInfo = leaseToCancel.getHolder();
String vip = null;
String svip = null;
if (instanceInfo != null) {
instanceInfo.setActionType(ActionType.DELETED);
recentlyChangedQueue.add(new RecentlyChangedItem(leaseToCancel));
instanceInfo.setLastUpdatedTimestamp();
vip = instanceInfo.getVIPAddress();
svip = instanceInfo.getSecureVipAddress();
}
invalidateCache(appName, vip, svip);
logger.info("Cancelled instance {}/{} (replication={})", appName, id, isReplication);
}
} finally {
read.unlock();
}
synchronized (lock) {
if (this.expectedNumberOfClientsSendingRenews > 0) {
// Since the client wants to cancel it, reduce the number of clients to send renews.
this.expectedNumberOfClientsSendingRenews = this.expectedNumberOfClientsSendingRenews - 1;
updateRenewsPerMinThreshold();
}
}
return true;
}
服务续约
com.netflix.eureka.registry.AbstractInstanceRegistry#renew
//服务续约 appName 服务名称 serverId 服务ID isReplication 是否是服务端的复制 还是客户端的续约操作
public boolean renew(String appName, String id, boolean isReplication) {
//计数器加1 ,如果当前是服务器副本备份 则counter加1 否则counter(当前服务名对应的区域计数器)加1且myZoneCounter(当前服务自己的计数器)加1
RENEW.increment(isReplication);
//根据服务名称从注册列表中取出对应的的服务列表
Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
Lease<InstanceInfo> leaseToRenew = null;
if (gMap != null) {
//根据服务ID取出服务实例信息
leaseToRenew = gMap.get(id);
}
if (leaseToRenew == null) {
//续约失败加1
RENEW_NOT_FOUND.increment(isReplication);
logger.warn("DS: Registry: lease doesn't exist, registering resource: {} - {}", appName, id);
return false;
} else {
//服务实例信息
InstanceInfo instanceInfo = leaseToRenew.getHolder();
if (instanceInfo != null) {
// touchASGCache(instanceInfo.getASGName());
//匹配当前的实例状态 并返回匹配后的实例状态
InstanceStatus overriddenInstanceStatus = this.getOverriddenInstanceStatus(
instanceInfo, leaseToRenew, isReplication);
//如果匹配后的实例状态为已下线 则续约失败加1
if (overriddenInstanceStatus == InstanceStatus.UNKNOWN) {
logger.info("Instance status UNKNOWN possibly due to deleted override for instance {}"
+ "; re-register required", instanceInfo.getId());
RENEW_NOT_FOUND.increment(isReplication);
return false;
}
//如果当前实例状态与匹配后的实例状态补相等 则修改实例状态为匹配后的实例状态
if (!instanceInfo.getStatus().equals(overriddenInstanceStatus)) {
logger.info(
"The instance status {} is different from overridden instance status {} for instance {}. "
+ "Hence setting the status to overridden status", instanceInfo.getStatus().name(),
overriddenInstanceStatus.name(),
instanceInfo.getId());
instanceInfo.setStatusWithoutDirty(overriddenInstanceStatus);
}
}
//最后一分钟的实例续约计数器加1
renewsLastMin.increment();
//修改最后一次续约时间
leaseToRenew.renew();
return true;
}
}