写这篇文章的初衷是为了给sentinel社区提交一个关于动态规则源的pull request,如下图
这里的动态规则源的意思是指,在sentinel添加限流规则后,能实时推送到我们指定的注册中心。使注册中心上的服务实例及时注册相应的规则,根据DisCoverClient进行服务发现并启用规则
这里就需要对eureka的启动流程展开说明一下:
这里的InstanceInfo就是每个服务的实例,也是动态规则源需要监听的对象,从注释中可以看出,这个对象就是暴露给其他服务的
* The class that holds information required for registration with
* <tt>Eureka Server</tt> and to be discovered by other components.
* <p>
* <code>@Auto</code> annotated fields are serialized as is; Other fields are
* serialized as specified by the <code>@Serializer</code>.
* </p>
*
Eureka-Client 发起注册
- 应用实例信息复制器
// DiscoveryClient.java
public class DiscoveryClient implements EurekaClient {
/**
* 应用实例状态变更监听器
*/
private ApplicationInfoManager.StatusChangeListener statusChangeListener;
/**
* 应用实例信息副本
*/
private InstanceInfoReplicator instanceInfoReplicator;
private void initScheduledTasks() {
// ... 省略无关代码
if (clientConfig.shouldRegisterWithEureka()) {
// ... 省略无关代码
// 创建 应用实例信息复制器
// InstanceInfo replicator
instanceInfoReplicator = new InstanceInfoReplicator(
this,
instanceInfo,
clientConfig.getInstanceInfoReplicationIntervalSeconds(),
2); // burstSize
// 创建 应用实例状态变更监听器
statusChangeListener = new ApplicationInfoManager.StatusChangeListener() {
@Override
public String getId() {
return "statusChangeListener";
}
@Override
public void notify(StatusChangeEvent statusChangeEvent) {
if (InstanceStatus.DOWN == statusChangeEvent.getStatus() ||
InstanceStatus.DOWN == statusChangeEvent.getPreviousStatus()) {
// log at warn level if DOWN was involved
logger.warn("Saw local status change event {}", statusChangeEvent);
} else {
logger.info("Saw local status change event {}", statusChangeEvent);
}
instanceInfoReplicator.onDemandUpdate();
}
};
// 注册 应用实例状态变更监听器
if (clientConfig.shouldOnDemandUpdateStatusChange()) {
applicationInfoManager.registerStatusChangeListener(statusChangeListener);
}
// 开启 应用实例信息复制器
instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
}
}
}
其中InstanceInfoReplicator就是将这个实例复制一个副本。为了避免线程安全问题,用于在一个线程的run方法内复制执行
代码如下:
// InstanceInfoReplicator.java
class InstanceInfoReplicator implements Runnable {
private static final Logger logger = LoggerFactory.getLogger(InstanceInfoReplicator.class);
private final DiscoveryClient discoveryClient;
/**
* 应用实例信息
*/
private final InstanceInfo instanceInfo;
/**
* 定时执行频率,单位:秒
*/
private final int replicationIntervalSeconds;
/**
* 定时执行器
*/
private final ScheduledExecutorService scheduler;
/**
* 定时执行任务的 Future
*/
private final AtomicReference<Future> scheduledPeriodicRef;
/**
* 是否开启调度
*/
private final AtomicBoolean started;
private final RateLimiter rateLimiter; // 限流相关,跳过
private final int burstSize; // 限流相关,跳过
private final int allowedRatePerMinute; // 限流相关,跳过
InstanceInfoReplicator(DiscoveryClient discoveryClient, InstanceInfo instanceInfo, int replicationIntervalSeconds, int burstSize) {
this.discoveryClient = discoveryClient;
this.instanceInfo = instanceInfo;
this.scheduler = Executors.newScheduledThreadPool(1,
new ThreadFactoryBuilder()
.setNameFormat("DiscoveryClient-InstanceInfoReplicator-%d")
.setDaemon(true)
.build());
this.scheduledPeriodicRef = new AtomicReference<Future>();
this.started = new AtomicBoolean(false);
this.rateLimiter = new RateLimiter(TimeUnit.MINUTES);
this.replicationIntervalSeconds = replicationIntervalSeconds;
this.burstSize = burstSize;
this.allowedRatePerMinute = 60 * this.burstSize / this.replicationIntervalSeconds;
logger.info("InstanceInfoReplicator onDemand update allowed rate per min is {}", allowedRatePerMinute);
}
public void start(int initialDelayMs) {
if (started.compareAndSet(false, true)) {
// 设置 应用实例信息 数据不一致
instanceInfo.setIsDirty(); // for initial register
// 提交任务,并设置该任务的 Future
Future next = scheduler.schedule(this, initialDelayMs, TimeUnit.SECONDS);
scheduledPeriodicRef.set(next);
}
}
// ... 省略无关方法
}
// InstanceInfo.java
private volatile boolean isInstanceInfoDirty = false;
private volatile Long lastDirtyTimestamp;
public synchronized void setIsDirty() {
isInstanceInfoDirty = true;
lastDirtyTimestamp = System.currentTimeMillis();
}
刷新应用实例信息
实现代码如下:
void refreshInstanceInfo() {
// 刷新 数据中心信息
applicationInfoManager.refreshDataCenterInfoIfRequired();
// 刷新 租约信息
applicationInfoManager.refreshLeaseInfoIfRequired();
// 健康检查
InstanceStatus status;
try {
status = getHealthCheckHandler().getStatus(instanceInfo.getStatus());
} catch (Exception e) {
logger.warn("Exception from healthcheckHandler.getStatus, setting status to DOWN", e);
status = InstanceStatus.DOWN;
}
if (null != status) {
applicationInfoManager.setInstanceStatus(status);
}
}
这里的refreshxxx的方法,就是用来更新实例信息用的。到此为止,服务实例更新完了
发起注册应用实例 实现代码如下:
// DiscoveryClient.java
boolean register() throws Throwable {
logger.info(PREFIX + appPathIdentifier + ": registering service...");
EurekaHttpResponse<Void> httpResponse;
try {
httpResponse = eurekaTransport.registrationClient.register(instanceInfo);
} catch (Exception e) {
logger.warn("{} - registration failed {}", PREFIX + appPathIdentifier, e.getMessage(), e);
throw e;
}
if (logger.isInfoEnabled()) {
logger.info("{} - registration status: {}", PREFIX + appPathIdentifier, httpResponse.getStatusCode());
}
return httpResponse.getStatusCode() == 204;
}
// AbstractJerseyEurekaHttpClient.java
@Override
public EurekaHttpResponse<Void> register(InstanceInfo info) {
String urlPath = "apps/" + info.getAppName();
ClientResponse response = null;
try {
Builder resourceBuilder = jerseyClient.resource(serviceUrl).path(urlPath).getRequestBuilder();
addExtraHeaders(resourceBuilder);
response = resourceBuilder
.header("Accept-Encoding", "gzip")
.type(MediaType.APPLICATION_JSON_TYPE)
.accept(MediaType.APPLICATION_JSON)
.post(ClientResponse.class, info);
return anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();
} finally {
if (logger.isDebugEnabled()) {
logger.debug("Jersey HTTP POST {}/{} with instance {}; statusCode={}", serviceUrl, urlPath, info.getId(),
response == null ? "N/A" : response.getStatus());
}
if (response != null) {
response.close();
}
}
}
可以看到作为注册信息的监听者是ApplicationManager,被监听的服务实例是InstanceConfig。这样就先确立了观察者与被观察者的关系