🔥🔥🔥提高服务器使用效率:合并微服务优化实践

前言

现在各个公司都在进行降本增效,其中的一个方面就是提高服务器的使用效率,由于在前期,错误的预估了业务的规模,导致了微服务拆分过细,很多服务器的使用在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的扩展足够支持,否则,只能让前端同事大量的修改调用的路径,测试同事进行大规模的回归测试了。大家要是有更好的方案也可以讨论一下,我的目的只是提供一些思路和引发讨论,以期能够共同进步。

相关推荐
Nejosi_念旧4 小时前
解读 Go 中的 constraints包
后端·golang·go
风无雨4 小时前
GO 启动 简单服务
开发语言·后端·golang
小明的小名叫小明4 小时前
Go从入门到精通(19)-协程(goroutine)与通道(channel)
后端·golang
斯普信专业组5 小时前
Go语言包管理完全指南:从基础到最佳实践
开发语言·后端·golang
一只叫煤球的猫6 小时前
【🤣离谱整活】我写了一篇程序员掉进 Java 异世界的短篇小说
java·后端·程序员
你的人类朋友7 小时前
🫏光速入门cURL
前端·后端·程序员
aramae9 小时前
C++ -- STL -- vector
开发语言·c++·笔记·后端·visual studio
洁辉9 小时前
Spring Cloud 全栈指南:构建云原生微服务的终极武器
spring cloud·微服务·云原生
lifallen9 小时前
Paimon 原子提交实现
java·大数据·数据结构·数据库·后端·算法
怀揣小梦想10 小时前
微服务项目远程调用时的负载均衡是如何实现的?
微服务·架构·负载均衡