前言
现在各个公司都在进行降本增效,其中的一个方面就是提高服务器的使用效率,由于在前期,错误的预估了业务的规模,导致了微服务拆分过细,很多服务器的使用在10%以下,为了解决这个情况,笔者根据eureka的注册原理,合并了多个服务,提高了服务的使用率。
目标
合并微服务势必会对前端的调用产生影响,所以此次我们的目的主要是降低对前端的影响。在不影响依赖方的情况下,将符合精简条件的server服务迁移至新服务中
调用流程 目前各服务启动后各自注册自己的实例信息到注册中心,调用方通过feign客户端信息到注册中心拉取目标服务端的路由信息进行接口调用
此处就不合大家讲具体的业务服务合并细节,我们先看一下eureka是如何支持一个服务注册多个服务名的。
原理分析
服务注册触发路径
java
SpringApplication.run() ->
this.refreshContext(context) ->
this.refresh(context) ->
ServletWebServerApplicationContext.refresh() ->
this.finishRefresh() ->
AbstractApplicationContext.finishRefresh ->
DefaultLifecycleProcessor.onRefresh() ->
this.startBeans ->
this.start() ->
this.doStart()
eureka的服务注册初始化加载是通过spring的SPI机制实现的
spring-cloud-starter-netflix-eureka-client -> spring.factories
java
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaClientConfigServerAutoConfiguration,\
org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceAutoConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration,\
org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration
Eureka Client 的配置是通过 EurekaClientAutoConfiguration 和 EurekaDiscoveryClientConfiguration 进行处理的
java
EurekaClientAutoConfiguration
public EurekaDiscoveryClient discoveryClient(EurekaClient client,
EurekaClientConfig clientConfig) {
return new EurekaDiscoveryClient(client, clientConfig);
}
eureka服务注册核心的类为 : EurekaServiceRegistry,通过这个类中提供的方法实现注册与下线,此外EurekaAutoServiceRegistration类实现了SmartLifecycle接口,会在spring容器加载和初始化完后调用start()方法
java
public void start() {
// only set the port if the nonSecurePort or securePort is 0 and this.port != 0
if (this.port.get() != 0) {
if (this.registration.getNonSecurePort() == 0) {
this.registration.setNonSecurePort(this.port.get());
}
if (this.registration.getSecurePort() == 0 && this.registration.isSecure()) {
this.registration.setSecurePort(this.port.get());
}
}
// only initialize if nonSecurePort is greater than 0 and it isn't already running
// because of containerPortInitializer below
if (!this.running.get() && this.registration.getNonSecurePort() > 0) {
this.serviceRegistry.register(this.registration);
this.context.publishEvent(
new InstanceRegisteredEvent<>(this, this.registration.getInstanceConfig()));
this.runnin
eureka的注册信息全部保存在ApplicationInfoManager中
创建ApplicationInfoManager类的时候,创建了一个InstanceInfo类 ApplicationInfoManager通过EurekaInstanceConfigBean、InstanceInfo进行创建
java
InstanceInfoReplicator
public void run() {
try {
discoveryClient.refreshInstanceInfo();
Long dirtyTimestamp = instanceInfo.isDirtyWithTime();
if (dirtyTimestamp != null) {
discoveryClient.register();
instanceInfo.unsetIsDirty(dirtyTimestamp);
}
} catch (Throwable t) {
logger.warn("There was a problem with the instance info replicator", t);
} finally {
Future next = scheduler.schedule(this, replicationIntervalSeconds, TimeUnit.SECONDS);
scheduledPeriodicRef.set(next);
}
}
以上就是Eureka服务注册的主要流程,在最后附上配置多服务的配置类
java
@Configuration
public class RegistryConfig implements SmartLifecycle {
private static String appName = "server-name";
public Map<String, CloudEurekaClient> applicationNameClientMap = new ConcurrentHashMap();
@Autowired(required = false)
private ObjectProvider<HealthCheckHandler> healthCheckHandler;
@Autowired
private InetUtils inetUtils;
@Autowired
private EurekaInstanceConfigBean eurekaInstanceConfigBean;
@Autowired
private EurekaClientConfig eurekaClientConfig;
@Autowired
private AbstractDiscoveryClientOptionalArgs<?> optionalArgs;
@Autowired
private ApplicationContext context;
@Autowired
private EurekaServiceRegistry serviceRegistr;
private AtomicBoolean running = new AtomicBoolean(false);
@Override
public void start() {
EurekaInstanceConfigBean eurekaInstanceConfigBean = new EurekaInstanceConfigBean(inetUtils);
BeanUtils.copyProperties(this.eurekaInstanceConfigBean, eurekaInstanceConfigBean);
eurekaInstanceConfigBean.setAppname(appName);
eurekaInstanceConfigBean.setVirtualHostName(appName);
eurekaInstanceConfigBean.setSecureVirtualHostName(appName);
InstanceInfo instanceInfo = (new InstanceInfoFactory()).create(eurekaInstanceConfigBean);
ApplicationInfoManager manager = new ApplicationInfoManager(eurekaInstanceConfigBean, instanceInfo);
CloudEurekaClient cloudEurekaClient =
new CloudEurekaClient(manager, eurekaClientConfig, this.optionalArgs, this.context);
EurekaRegistration registration =
EurekaRegistration.builder(eurekaInstanceConfigBean).with(manager).with(cloudEurekaClient)
.with(this.healthCheckHandler).build();
EurekaAutoServiceRegistration eurekaAutoServiceRegistration =
new EurekaAutoServiceRegistration(this.context, this.serviceRegistr, registration);
CloudEurekaClient mapResult = this.applicationNameClientMap.putIfAbsent(appName, cloudEurekaClient);
if (mapResult == null) {
eurekaAutoServiceRegistration.start();
}
this.running.set(true);
}
@Override
public void stop() {
CloudEurekaClient cloudEurekaClient = this.applicationNameClientMap.get(appName);
if (cloudEurekaClient != null) {
cloudEurekaClient.shutdown();
}
this.applicationNameClientMap.remove(appName);
this.running.set(false);
}
@Override
public boolean isRunning() {
return this.running.get();
}
}
尾声
eureka的技术栈已经有很多年了,很多公司已经在逐渐的淘汰,但其中还有很多设计的思想值得我们学习,在一开始,笔者也只是抱着试试看的心态来尝试一下,还好eureka的扩展足够支持,否则,只能让前端同事大量的修改调用的路径,测试同事进行大规模的回归测试了。大家要是有更好的方案也可以讨论一下,我的目的只是提供一些思路和引发讨论,以期能够共同进步。