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

前言

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

相关推荐
Victor3562 小时前
https://editor.csdn.net/md/?articleId=139321571&spm=1011.2415.3001.9698
后端
Victor3562 小时前
Hibernate(89)如何在压力测试中使用Hibernate?
后端
灰子学技术4 小时前
go response.Body.close()导致连接异常处理
开发语言·后端·golang
Gogo8165 小时前
BigInt 与 Number 的爱恨情仇,为何大佬都劝你“能用 Number 就别用 BigInt”?
后端
fuquxiaoguang5 小时前
深入浅出:使用MDC构建SpringBoot全链路请求追踪系统
java·spring boot·后端·调用链分析
毕设源码_廖学姐5 小时前
计算机毕业设计springboot招聘系统网站 基于SpringBoot的在线人才对接平台 SpringBoot驱动的智能求职与招聘服务网
spring boot·后端·课程设计
野犬寒鸦7 小时前
从零起步学习并发编程 || 第六章:ReentrantLock与synchronized 的辨析及运用
java·服务器·数据库·后端·学习·算法
indexsunny7 小时前
互联网大厂Java面试实战:Spring Boot微服务在电商场景中的应用与挑战
java·spring boot·redis·微服务·kafka·spring security·电商
逍遥德8 小时前
如何学编程之01.理论篇.如何通过阅读代码来提高自己的编程能力?
前端·后端·程序人生·重构·软件构建·代码规范
MX_93598 小时前
Spring的bean工厂后处理器和Bean后处理器
java·后端·spring