springcloud 配置服务间启动顺序

springcloud 配置服务间启动顺序

springcloud 微服务是由多个可独立运行的springboot服务组成,服务间可互相调用。但是如果在服务启动的时候,A服务依赖B服务的一些接口,此时B服务未启动完成,则A服务需等待B服务启动完成后才能启动。本文通过EUREKA服务注册与发现功能实现自定义服务启动顺序。
eureka服务注册与发现的机制原理此处不再叙述,本文主要通过EurekaDiscoveryClient获取注册中心注册的服务列表,轮询检查各服务的状态,根据状态(UP)以及调用服务的接口测试判断某个服务是否可以启动。

1.在EUREKA启动类上加注解@EnableDiscoveryClient在这里插入图片描述

eureka状态改变监听器

import com.netflix.appinfo.InstanceInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.netflix.eureka.server.event.*;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

import java.util.Objects;

/**
 * eureka状态改变监听器
 *
 */
@Component
@Slf4j
public class EurekaStateChangeListener {

    /**
     * 服务下线事件
     * @param eurekaInstanceCanceledEvent
     */
    @EventListener
    public void listen(EurekaInstanceCanceledEvent eurekaInstanceCanceledEvent) {
        // 服务断线事件
        String appName = eurekaInstanceCanceledEvent.getAppName();
        String serverId = eurekaInstanceCanceledEvent.getServerId();
        Objects.requireNonNull(appName, "服务名不能为空!");
        log.info(">>>>>>> 失效服务:{},已被剔除!", serverId);
    }

    /**
     * 服务注册事件
     * @param event
     */
    @EventListener
    public void listen(EurekaInstanceRegisteredEvent event) {
        // 服务注册
        InstanceInfo instanceInfo = event.getInstanceInfo();
        String appName = instanceInfo.getAppName();
        Objects.requireNonNull(appName, "服务名不能为空!");
        log.info(">>>>>>> 服务名:{},端口号:{}", appName, instanceInfo.getPort());
    }

    /**
     * 服务续约事件(即心跳事件)
     * @param event
     */
    /*@EventListener(condition = "#event.replication==false")
    public void listen(EurekaInstanceRenewedEvent event) {
        //服务续约事件
        event.getAppName();
        event.getServerId();
        log.info(">>>>>>>>>>>>>>>Server续约:" + event.getServerId());
    }*/

    /**
     * 注册中心启动
     * @param event
     */
    @EventListener
    public void listen(EurekaRegistryAvailableEvent event) {
        log.info(">>>>>>>>>>>>>>>Server注册中心:" + event);
    }

    /**
     * Server启动
     * @param event
     */
    @EventListener
    public void listen(EurekaServerStartedEvent event) {
        log.info(">>>>>>>>>>>>>>>Server启动:" + event);
        //Server启动
    }
}

eureka配置需禁用readOnlyCacheMap

eureka.client.refresh.enable = true
# 默认开启
eureka.client.fetch-registry = true 
## 禁用readOnlyCacheMap
eureka.server.useReadOnlyResponseCache=false
eureka.client.registry-fetch-interval-seconds = 30
# 默认开启增量更新注册表缓存,即不禁用增量
#eureka.client.disable-delta = false

2.在A服务启动之前判断其他服务的状态以及接口是否可以调用成功

@PostConstruct:@PostConstruct注解好多人以为是Spring提供的。其实是Java自己的注解。

Java中该注解的说明:@PostConstruct该注解被用来修饰一个非静态的void()方法。被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。PostConstruct在构造函数之后执行,init()方法之前执行。

通常我们会是在Spring框架中使用到@PostConstruct注解 该注解的方法在整个Bean初始化中的执行顺序:

Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法)

/**
   * A服务启动前判断其他服务是否注册成功且状态为UP
   * @throws InterruptedException
   */
  @PostConstruct
  void started() throws InterruptedException {
    while(!isProcessRun()){
      // 服务未完全启动成功,等待1秒
      Thread.sleep(1000);
    }
    log.info("B服务启动成功,开始启动A服务!!!!!!!!");
  }
/**
   * 判断服务是否注册成功且状态为UP,以及调用接口是否成功
   * @return
   */
  private boolean isProcessRun(){
    boolean flag = true;
    //获取注册中心注册的服务列表。对应的就是Application
    List<String> applicationList = discoveryClient.getServices();
    System.out.println("applicationList:"+applicationList);
    if(!applicationList.contains("B服务名")){
      log.error("B服务未注册!!!!!!!!");
      flag = false;
      return flag;
    }
    
    for(String applicationName : applicationList) {
      //获取每个服务的提供者。对应的就是Application的status
      List<ServiceInstance> instanceList = discoveryClient.getInstances(applicationName);
//      System.out.println(instanceList.toString());
      // 判断服务状态是否为UP
        for (ServiceInstance serviceInstance : instanceList) {
          String instanceInfoStr = serviceInstance.toString();
          if (StringUtil.isNotBlank(instanceInfoStr)) {
            // 实例状态(通过正则获取status的属性值)
            String status = RegExUtils.matchGroup0("(\\bstatus = [A-Z]+\\w)", instanceInfoStr.trim());
            
            // 判断B服务状态是否为UP
            if (StringUtil.isNotBlank(instanceInfoStr)) {
              status = status.substring(status.indexOf("= ") + 1).trim();
//              System.out.println(status);
              if (!"UP".equals(status)) {
                flag = false;
                log.error(serviceInstance.getServiceId()+"服务未启动成功,等待中!!!!!!!!");
                break;
              }
            }
            // 测试接口调用
            //第一种方式 通过IP地址访问B服务接口测试。
              RestTemplate restTemplate = new RestTemplate();
              String response = restTemplate.getForObject(serviceInstance.getUri()+"/connection",String.class);
              System.out.println("服务URI:" + serviceInstance.getUri()+"/connection" + ";服务接口调用状态:"+response);
              if(StringUtil.isBlank(response) || !"success".equals(response)){
                flag = false;
                log.error(serviceInstance.getServiceId()+"服务调用接口失败,等待中!!!!!!!!");
                break;
              }
          }
        }
    }

    return flag;
  }

3.在B服务控制层添加测试接口调用的接口

/**
 * 功能简述:测试接口是否可以调用成功
 *
 * @author aa
 * @date 2021/10/22
 * @since 1.0.0
 */
@RestController
public class ServiceController {

    @GetMapping("/connection")
    public String connection(){
        return "success";
    }
}

版权声明:本文为weixin_42123075原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。