springcloud 微服务是由多个可独立运行的springboot服务组成,服务间可互相调用。但是如果在服务启动的时候,A服务依赖B服务的一些接口,此时B服务未启动完成,则A服务需等待B服务启动完成后才能启动。本文通过EUREKA服务注册与发现功能实现自定义服务启动顺序。
eureka服务注册与发现的机制原理此处不再叙述,本文主要通过EurekaDiscoveryClient获取注册中心注册的服务列表,轮询检查各服务的状态,根据状态(UP)以及调用服务的接口测试判断某个服务是否可以启动。
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;
@Component
@Slf4j
public class EurekaStateChangeListener {
@EventListener
public void listen(EurekaInstanceCanceledEvent eurekaInstanceCanceledEvent) {
// 服务断线事件
String appName = eurekaInstanceCanceledEvent.getAppName();
String serverId = eurekaInstanceCanceledEvent.getServerId();
Objects.requireNonNull(appName, "服务名不能为空!");
log.info(">>>>>>> 失效服务:{},已被剔除!", serverId);
}
@EventListener
public void listen(EurekaInstanceRegisteredEvent event) {
// 服务注册
InstanceInfo instanceInfo = event.getInstanceInfo();
String appName = instanceInfo.getAppName();
Objects.requireNonNull(appName, "服务名不能为空!");
log.info(">>>>>>> 服务名:{},端口号:{}", appName, instanceInfo.getPort());
}
@EventListener
public void listen(EurekaRegistryAvailableEvent event) {
log.info(">>>>>>>>>>>>>>>Server注册中心:" + 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 = false2.在A服务启动之前判断其他服务的状态以及接口是否可以调用成功
@PostConstruct:@PostConstruct注解好多人以为是Spring提供的。其实是Java自己的注解。
Java中该注解的说明:@PostConstruct该注解被用来修饰一个非静态的void()方法。被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。PostConstruct在构造函数之后执行,init()方法之前执行。
通常我们会是在Spring框架中使用到@PostConstruct注解 该注解的方法在整个Bean初始化中的执行顺序:
Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法)
@PostConstruct
void started() throws InterruptedException {
while(!isProcessRun()){
// 服务未完全启动成功,等待1秒
Thread.sleep(1000);
}
log.info("B服务启动成功,开始启动A服务!!!!!!!!");
}
private boolean isProcessRun(){
boolean flag = true;
//获取注册中心注册的服务列表。对应的就是Application
List 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 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服务控制层添加测试接口调用的接口
@RestController
public class ServiceController {
@GetMapping("/connection")
public String connection(){
return "success";
}
}



