1. 背景
1.1. 注册中心是什么
注册中心可以说是微服务架构中的”通讯录“,它记录了服务和服务地址的映射关系。在分布式架构中,服务会注册到这里,当服务需要调用其它服务时,就到这里找到服务的地址,进行调用。
1.2. 为什么需要注册中心
在分布式系统中,服务可能有上千个,然后每个服务都有好几个实例,如果通过 ip + port 进行服务之间通信则会使系统变得难维护,并且还需要考虑其他复杂的问题:
- 服务注册后,如何被及时发现
- 服务宕机后,如何及时下线
- 服务如何有效的水平扩展
- 如何获取服务列表
- 注册中心如何实现自身的高可用
2. Eureka
2.1. 世面上的流行的注册中心
组件名称 | 组件简介 |
---|---|
Zookeeper | zookeeper是一个分布式协调工具,可以实现注册中心功能 |
Eureka | springcloud的注册中心 |
Consul | Consul 简化了分布式环境中的服务的注册和发现流程,国外比较流行 |
Nacos | Nacos 致力于帮助您发现、配置和管理微服务。SpringCloudAlibaba |
2.2. Eureka
https://github.com/Netflix/eureka
服务注册中心(可以是一个集群),对外暴露自己的地址
注册中心有 Eureka Service, Eureka Client,Eureka Client又分为提供者和消费者;
(某一个服务既可以是提供者也可以是消费者)
服务提供者
- 服务注册: 启动的时候会通过发送REST请求的方式将自己注册到Eureka Server上,同时带上了自身服务的一些元数据信息。
- 服务续约: 在注册完服务之后,服务提供者会维护一个心跳(默认30S) 用来持续告诉Eureka Server: "我还活着 ”
- 服务下线: 当服务实例进行正常的关闭操作时,它会触发一个服务下线的REST请求 给Eureka Server, 告诉服务注册中心:“我要下线了 ”。
服务消费者
- 获取服务: 服务消费者(Eureka Client)在启动的时候,会发送一个REST请求给Eureka Server,获 取上面注册的服务清单,并且缓存在Eureka Client本地,默认缓存30秒 (eureka.client.registryFetchIntervalSeconds)。同时,为了性能考虑,Eureka Server也会维护一份只读的服务清单缓存,该缓存每隔30秒更新一次。
- 服务调用: 服务消费者在获取服务清单后,通过服务名可以获得具体提供服务的实例名和该实例的元数据信息。在进行服务调用的时候,优先访问同处一个Zone中的服务提供方。
Eureka Server(服务注册中心)
-
失效剔除:【在关闭自我保护才有效】 默认每隔一段时间(默认为60秒) 将当前清单中超时(默认为90秒)没有续约的服务剔除出去。
-
自我保护: EurekaServer 在运行期间,如果在15分钟内超过85%的客户端节点都没有正常的心跳(通常由于网络不稳定导致)。 Eureka Server会将当前的实例注册信息保护起来, 让这些实例不会过期,尽可能保护这些注册信息。此时会出现以下几种情况:
- Eureka Server不再从注册列表中移除因为长时间没收到心跳而应该过期的服务。
- Eureka Server仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上,保证当前节点依然可用。
- 当网络稳定时,当前Eureka Server新的注册信息会被同步到其它节点中。
因此Eureka Server可以很好的应对因网络故障导致部分节点失联的情况,而不会像ZK那样如果有一半不可用的情况会导 致整个集群不可用而变成瘫痪。
3.1. 服务注册
3.1.1. Eureka-Client
啥时候会被注册
- 当客户端刚刚启动的时候
- 当客户端的instance信息发生改动
当我们的客户端引入了Eureka-Client,当主方法启动时,@SpringBootApplication会扫描所有的META-INF/spring.factories文件下的 xxxAutoConfiguration。这时候 EurekaClientAutoConfiguration 也会被加载。
上面这段代码,很简单,就是实例化了一个Bean,主要是这个Bean实现了SmartLifecycle, 当重写方法 isAutoStartup() 返回值为true,会启动start()方法。
下面可以详细看看这个代码。
EurekaClientAutoConfiguration.java
@Bean
public EurekaAutoServiceRegistration eurekaAutoServiceRegistration(ApplicationContext context, EurekaServiceRegistry registry, EurekaRegistration registration) {
// 重点代码
return new EurekaAutoServiceRegistration(context, registry, registration);
}
EurekaAutoServiceRegistration.java
public class EurekaAutoServiceRegistration implements AutoServiceRegistration, SmartLifecycle, Ordered {
@Override
public void start() {
// ...
// 该实例还未启动
if (!this.running.get() && this.registration.getNonSecurePort() > 0) {
// 重点;自动去注册服务
this.serviceRegistry.register(this.registration);
// 发布 节点注册事件
this.context.publishEvent(
new InstanceRegisteredEvent<>(this, this.registration.getInstanceConfig()));
this.running.set(true);
}
}
@Override
public boolean isAutoStartup() {
return true;
}
}
InstanceInfoReplicator.java
public boolean onDemandUpdate() {
if (rateLimiter.acquire(burstSize, allowedRatePerMinute)) {
if (!scheduler.isShutdown()) {
scheduler.submit(new Runnable() {
@Override
public void run() {
// ...
// 调用run方法
InstanceInfoReplicator.this.run();
}
});
return true;
} else {
return false;
}
} else {
return false;
}
}
public void run() {
try {
// 刷新实例信息。
discoveryClient.refreshInstanceInfo();
Long dirtyTimestamp = instanceInfo.isDirtyWithTime();
if (dirtyTimestamp != null) {
// 注册自己的服务
discoveryClient.register();
instanceInfo.unsetIsDirty(dirtyTimestamp);
}
} catch (Throwable t) {
} finally {
Future next = scheduler.schedule(this, replicationIntervalSeconds, TimeUnit.SECONDS);
scheduledPeriodicRef.set(next);
}
}
第二种是当我们客户端instance信息发生变化
private void initScheduledTasks() {
//省略, 刷新缓存的定时器
// 监听instance的状态变更
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) {
// ...
// 调用方法
instanceInfoReplicator.onDemandUpdate();
}
};
if (clientConfig.shouldOnDemandUpdateStatusChange()) {
applicationInfoManager.registerStatusChangeListener(statusChangeListener);
}
instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
}
总结 :服务注册分为2种。
第一种: 当应用启动的时候,如果应用开启了自动注册(默认开启), 那么在自动配置类加载的时候,会通过EurekaAutoServiceRegistration实例化的时候,去改变instance的status,然后调用注册。
第二种: 主要应用于启动之后,当应用的信息发生改变之后,每40每秒执行一次的线程,检测到了,也会自动去注册一次。
DiscoveryClient.register()
DiscoveryClient.java
boolean register() throws Throwable {
EurekaHttpResponse<Void> httpResponse;
try {
//发起HTTP请求
httpResponse = eurekaTransport.registrationClient.register(instanceInfo);
} catch (Exception e) {
}
return httpResponse.getStatusCode() == 204;
}
使用的Jersey框架来完成http的请求调用
AbstractJerseyEurekaHttpClient.java
@Override
public EurekaHttpResponse<Void> register(InstanceInfo info) {
// 请求url
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请求;请求参数
.post(ClientResponse.class, info);
return anEurekaHttpResponse(response.getStatus()).headers(