本文已参与「新人创作礼」活动,一起开启掘金创作之路。
Netflix Eureka 目录汇总
-
eureka server启动以及初始化
-
eureka client启动以及初始化
-
服务注册
3.1 可重入读写锁-读锁
-
服务发现
4.1. 全量抓取注册表 4.2. 注册表多级缓存机制 4.3. 注册表多级缓存过期机制(主动、定时、被动) 4.4. 增量抓取注册表
4.4.1. 一致性Hash对比机制 4.4.2. 可重入读写锁-写锁 -
服务续约
-
服务下线
-
服务故障自动感知及服务实例自动摘除
-
自我保护机制
-
Eureka Server集群注册表同步及高可用
-
Eureka Server集群之间注册表同步使用的三层队列机制
Netflix Eureka 时间间隔简要
服务注册(延迟40秒执行注册逻辑)
服务发现-
读写缓存-定时过期(180秒)
只读缓存-被动过期(30秒(整30秒))
定时抓取增量注册表(30秒)
定时删除超过3分钟的服务实例变更记录(30秒)
服务续约(30秒)
服务下线
服务故障自动感知及服务实例自动摘除(感知:90秒(BUG:90 * 2秒);摘除:60秒)
自我保护机制
每分钟期望的续约次数 & 每分钟期望的续约次数阈值(15分钟)
MeasuredRate renewsLastMin(60秒)
Eureka Server集群信息更新(10分钟)
注册表同步使用的三层队列机制
注册表同步的3层队列任务批处理机制
组件简要 - 实例
batchingDispatcher:
- 构造AcceptorExecutor实例。
- 构造TaskExecutors实例,通过TaskExecutors.batchExecutors()方法。包含
AcceptorExecutor,ReplicationTaskProcessor实例。 - 构造TaskDispatcher实例,通过TaskDispatchers.createBatchingTaskDispatcher()方法。
batchingDispatcher.process():
- 封装实例变化信息,通过
acceptorExecutor.process放入acceptorQueue(LinkedBlockingQueue)
AcceptorRunner:
- 构造AcceptorExecutor实例时,启动AcceptorRunner任务。
- 将
acceptorQueue队列数据全部放入processingOrder队列中(processingOrder只是存放taskId集合的链表,pendingTasks才是真正存放acceptorQueue队列里的任务)
acceptorQueue:接收服务实例状态变更任务的队列
TaskExecutors:构造器内通过WorkerRunnableFactory工厂获取BatchWorkerRunnable,并启动BatchWorkerRunnable任务的线程。
ReplicationTaskProcessor:
ProcessingResult :处理任务结果。
-
Success:成功。
-
Congestion:拥挤错误,任务将会被重试。例如,请求被限流。
-
TransientError:瞬时错误,任务将会被重试。例如,网络请求超时。
-
PermanentError:永久错误,任务将会被丢弃。例如,执行时发生程序异常。
组件简要 - 类
任务分发器:分发器在收到任务执行请求后,提交到接收队列,任务实际未执行。
TaskDispatcher:
批量任务分发器:
单任务分发器:
任务接收器
AcceptorExecutor:
AcceptorRunner:
任务执行器
TaskExecutors:
WorkerRunnableFactory:
WorkerRunnable:
BatchWorkerRunnable:
SingleTaskWorkerRunnable:
任务处理器
TaskProcessor:
ReplicationTaskProcessor:
任务持有者
TaskHolder:
三层队列批处理
第一层队列:接收队列(acceptorQueue),避免处理任务的阻塞等待;重处理队列(reprocessQueue),存放任务提交失败的请求。
/**
* 存放服务实例状态变更命令对象的TaskHolder结构体
*/
private final BlockingQueue<TaskHolder<ID, T>> acceptorQueue = new LinkedBlockingQueue<>();
/**
* 存放请求提交失败的任务,随着acceptorQueue一起,重新放入处理队列(processingQueue)
*/
private final BlockingDeque<TaskHolder<ID, T>> reprocessQueue = new LinkedBlockingDeque<>();
第二层队列:处理队列,接收线程( AcceptorRunner )合并任务,将相同任务编号的任务合并,只执行一次。
/**
* 转存从acceptorQueue队列移过来的命令对象
*/
private final Map<ID, TaskHolder<ID, T>> pendingTasks = new HashMap<>();
/**
* 顺序存放acceptorQueue队列移过来的命令对象taskId。关联pendingTasks,作为索引使用。
* 为啥不使用LinkedHashMap???
*/
private final Deque<ID> processingOrder = new LinkedList<>();
第三个队列:存放批处理任务,等批量处理任务poll处理
/**
* 根据数量及时间打包批量任务
*/
private final BlockingQueue<List<TaskHolder<ID, T>>> batchWorkQueue =
new LinkedBlockingQueue<>();
数据流
第一层队列:acceptorQueue
// PeerEurekaNode.java
/**
* Sends the registration information of {@link InstanceInfo} receiving by
* this node to the peer node represented by this class.
*
* @param info the instance information {@link InstanceInfo} of any instance
* that is send to this instance.
* @throws Exception
*/
public void register(final InstanceInfo info) throws Exception {
long expiryTime = System.currentTimeMillis() + getLeaseRenewalOf(info);
// 调用批处理分发器,封装注册、下线、心跳、故障等行为到命令对象中
batchingDispatcher.process(
taskId("register", info),
// 封装命令(实例同步任务)
new InstanceReplicationTask(targetHost, Action.Register, info, null, true) {
@Override
public EurekaHttpResponse<Void> execute() {
return replicationClient.register(info);
}
},
expiryTime
);
}
// TaskDispatchers.java
/**
* 任务分发器
*/
return new TaskDispatcher<ID, T>() {
@Override
public void process(ID id, T task, long expiryTime) {
acceptorExecutor.process(id, task, expiryTime);
}
@Override
public void shutdown() {
acceptorExecutor.shutdown();
taskExecutor.shutdown();
}
};
// AcceptorExecutor.java
void process(ID id, T task, long expiryTime) {
// 1. 接收任务放到接收队列(acceptorQueue)
acceptorQueue.add(new TaskHolder<ID, T>(id, task, expiryTime));
acceptedTasks++;
}
第一层队列:reprocessQueue
// TaskExecutors.java
// 提交批处理任务
ProcessingResult result = processor.process(tasks);
switch (result) {
case Success:
break;
case Congestion:
case TransientError:
// 批处理任务返回 瞬断错误(网络)时,需要放入reprocessQueue队列中,重新发送
taskDispatcher.reprocess(holders, result);
break;
case PermanentError:
logger.warn("Discarding {} tasks of {} due to permanent error", holders.size(), workerName);
}
// AcceptorExecutor.java
void reprocess(List<TaskHolder<ID, T>> holders, ProcessingResult processingResult) {
// 4(1). 接收请求失败的任务放入重处理队列(reprocessQueue)
reprocessQueue.addAll(holders);
replayedTasks += holders.size();
trafficShaper.registerFailure(processingResult);
}
第二层队列:processingOrder & pendingTasks
注意:
batchWorkRequests信号量初始是没有权限的,需要等待TaskExecutors$BatchWorkerRunnable
任务线程赋予权限,assignBatchWork才会真正的分配【批处理任务】
// AcceptorExecutor.java
/**
* 1. 合并【第一层队列】,迁移到【第二层队列】
* 2. 合并【任务】打包为【批处理任务】
*/
class AcceptorRunner implements Runnable {
@Override
public void run() {
long scheduleTime = 0;
while (!isShutdown.get()) {
try {
// 2. 将acceptorQueue与reprocessQueue队列的任务迁移到pendingTasks中
drainInputQueues();
int totalItems = processingOrder.size();
long now = System.currentTimeMillis();
if (scheduleTime < now) {
scheduleTime = now + trafficShaper.transmissionDelay();
}
if (scheduleTime <= now) {
// 3. 将任务(holder)合并成批量任务(holders),提交到工作队列(batchWorkQueue)
assignBatchWork();
assignSingleItemWork();
}
// If no worker is requesting data or there is a delay injected by the
// traffic shaper,
// sleep for some time to avoid tight loop.
if (totalItems == processingOrder.size()) {
Thread.sleep(10);
}
} catch (InterruptedException ex) {
// Ignore
} catch (Throwable e) {
// Safe-guard, so we never exit this loop in an uncontrolled way.
logger.warn("Discovery AcceptorThread error", e);
}
}
}
第三层队列:batchWorkRequests
// TaskExecutors$BatchWorkerRunnable.java
/**
* 获取批处理任务,并提交任务
*/
@Override
public void run() {
try {
while (!isShutdown.get()) {
// 给予信号量【batchWorkRequests】权限,并获取【批处理任务】
List<TaskHolder<ID, T>> holders = getWork();
metrics.registerExpiryTimes(holders);
List<T> tasks = getTasksOf(holders);
// 提交批处理任务
ProcessingResult result = processor.process(tasks);
switch (result) {
case Success:
break;
case Congestion:
case TransientError:
// 批处理任务返回 瞬断错误(网络)时,需要放入reprocessQueue队列中,重新发送
taskDispatcher.reprocess(holders, result);
break;
case PermanentError:
logger.warn("Discarding {} tasks of {} due to permanent error", holders.size(), workerName);
}
metrics.registerTaskResult(result, tasks.size());
}
} catch (InterruptedException e) {
// Ignore
} catch (Throwable e) {
// Safe-guard, so we never exit this loop in an uncontrolled way.
logger.warn("Discovery WorkerThread error", e);
}
}
// TaskExecutors$BatchWorkerRunnable.java
/**
* 给予信号量【batchWorkRequests】权限,并获取【批处理任务】
*
* @return
* @throws InterruptedException
*/
private List<TaskHolder<ID, T>> getWork() throws InterruptedException {
// 给予信号量【batchWorkRequests】权限
BlockingQueue<List<TaskHolder<ID, T>>> workQueue = taskDispatcher.requestWorkItems();
List<TaskHolder<ID, T>> result;
do {
// 获取【批处理任务】
result = workQueue.poll(1, TimeUnit.SECONDS);
} while (!isShutdown.get() && result == null);
return result;
}
// AcceptorExecutor.java
/**
* 给予信号量【batchWorkRequests】权限,并返回【批处理任务队列】的引用
* @return
*/
BlockingQueue<List<TaskHolder<ID, T>>> requestWorkItems() {
batchWorkRequests.release();
return batchWorkQueue;
}
// ReplicationTaskProcessor.java
/**
* 同步任务处理器,处理批处理任务
*
* @param tasks
* @return
*/
@Override
public ProcessingResult process(List<ReplicationTask> tasks) {
ReplicationList list = createReplicationListOf(tasks);
try {
// 提交批处理任务
EurekaHttpResponse<ReplicationListResponse> response =
replicationClient.submitBatchUpdates(list);
int statusCode = response.getStatusCode();
if (!isSuccess(statusCode)) {
if (statusCode == 503) {
logger.warn("Server busy (503) HTTP status code received from the peer {}; " +
"rescheduling tasks after delay", peerId);
return ProcessingResult.Congestion;
} else {
// Unexpected error returned from the server. This should ideally never happen.
logger.error("Batch update failure with HTTP status code {}; discarding {} " +
"replication tasks", statusCode, tasks.size());
return ProcessingResult.PermanentError;
}
} else {
handleBatchResponse(tasks, response.getEntity().getResponseList());
}
} catch (Throwable e) {
if (isNetworkConnectException(e)) {
logNetworkErrorSample(null, e);
return ProcessingResult.TransientError;
} else {
logger.error("Not re-trying this exception because it does not seem to be a " +
"network exception", e);
return ProcessingResult.PermanentError;
}
}
return ProcessingResult.Success;
}