问题:consul作为注册中心和eureka的机制不同。
当微服务启动后首先向注册中心发注册请求,这点两者一致。之后consul在维护可用服务列表时,采用的是主动向微服务发健康检查的接口(也可以配置成微服务主动向consul发心跳,但是我看完官网和各类文章都没说清楚具体怎么搞)。如果微服务正常返回,那么就认为服务正常。eureka是等待微服务主动向eureka发心跳,eureka收到心跳后,就给这个服务续约,认为服务是正常的。
正是因为这点差异就产生了大问题!
场景一:consul和微服务间的网络不稳定,断开了几分钟。
在此情况下,consul向微服务发的健康检查请求发不通,认为微服务挂了,就从服务列表里剔除他。
当网络恢复后,注意,已经剔除的服务,consul是不会主动再发健康检查的。那么服务列表里没有他,
也就不正常了。网关的转发都是需要获取可用服务列表,才能做转发的!
这时候,你想让微服务注册上consul就只能重启微服务了。这在生产环境意味着什么就不用多说了吧。
是不是很坑?
场景二:consul因为某种情况需要重启。(可能是consul也需要投产,或者其他原因,总之就是需要重启consul)
这个场景和上面的类似。当consul重启后,之前已经注册上来的,状态正常的服务,consul忘记了。
因为他重启了,他不记得前世的服务列表。
所以这种情况下,也需要重启我们的微服务,才能使状态正常。
综上,因为consul的健康检查机制是consul主动发,跟eureka不同才导致这种问题。
问题很清楚了,要怎么解决呢?
我查了很多资料,除了consul可以用ttl的方式,让微服务主动发心跳来续约外,好像没有想成的方案。
但是这个ttl的方案,官网没仔细说,网上的文章也是一大抄,没人说的明白。
我经过研究后,发现consul提供了好多rest接口。可以通过接口注册,注销,查询等。
那么我们就可以仿照eureka的方式,主动查consul的服务列表,类似心跳。
如果没查到,或者发不通(consul挂了)那么久开始发注册的接口,把自己注册上去。
不停的发,直至成功。
我用2个服务来模拟,一个是springcloudgateway网关gate,一个是微服务one
在应用启动后起一个定时任务:
package com.example.one.init;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
//CommandLineRunner ApplicationRunner
@Component
public class InitRunner implements ApplicationRunner {
private static final Logger logger = LoggerFactory.getLogger(InitRunner.class);
@Value("${spring.cloud.consul.host}")
private String consulip;
@Value("${spring.cloud.consul.port}")
private String consulport;
@Value("${spring.cloud.client.ip-address}")
private String ip;
@Value("${spring.application.name}")
private String servername;
@Value("${server.port}")
private String port;
@Value("${my.consul.check.register:false}")
private String registerFlag;//检查服务在consul列表不存在,或者consul访问不通,就不停的注册自己。保证consul因为网络不稳定或者重启,服务不正常。(微服务只有在启动是会注册自己,这样做是为了不重启服务)
@Value("${my.consul.check.interval:10}")
private String interval;//检查服务在consul中是否正常的频率,每interval秒检查一次
private long intervalNum=10;//interval转成long类型
public long getIntervalNum() {
if(interval!=null) {
try {
intervalNum = Long.valueOf(interval.trim());
} catch (NumberFormatException e) {
intervalNum=10;
e.printStackTrace();
}
}
return intervalNum;
}
public void setIntervalNum(long intervalNum) {
this.intervalNum = intervalNum;
}
public String getRegisterFlag() {
//保证只有2个值
if(registerFlag!=null && registerFlag.trim().equals("true")) {
return "true";
}
return "false";
}
public void setRegisterFlag(String registerFlag) {
this.registerFlag = registerFlag;
}
@Autowired
private RestTemplate restTemplate;
// @Autowired
// private DbRouteDefinitionRepository dbRouteDefinitionRepository;
public RestTemplate getRestTemplate() {
return restTemplate;
}
public void setRestTemplate(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@Override
public void run(ApplicationArguments args) throws Exception {
DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String dateStr=LocalDateTime.now().format(df);
String serverInfo="["+servername+":"+ip+":"+port+"]";
System.out.println(serverInfo+",InitRunner初始化逻辑。。。");
/
// LocalDateTime startTime = LocalDateTime.now();
String message=serverInfo;
//gate-192-168-124-17-8888
String defalutConsulId=servername+"-"+ip.replace(".", "-")+"-"+port;
// long interVal=10;
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
scheduledThreadPool.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
if(getRegisterFlag().equals("false")) {
logger.info("InitRunner consul检查纠正服务开关未开启,不执行检查纠错服务。。。。。");
return;
}
try {
logger.info("InitRunner 执行consul检查纠正服务"+serverInfo+"。。。delay "+getIntervalNum()+" seconds...");
//查询consul的已经注册的服务列表
String consulQueryUrl="http://"+consulip+":"+consulport+"/v1/agent/services";
String consulRegisterUrl="http://"+consulip+":"+consulport+"/v1/agent/service/register";
// getRestTemplate().getForObject(url, responseType, uriVariables)
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
HttpEntity<String> entity = new HttpEntity<String>(headers);
// String strbody=restTemplate.exchange(consulUrl, HttpMethod.GET, entity,String.class).getBody();
// Map responseData=restTemplate.exchange(consulUrl, HttpMethod.GET, entity,Map.class).getBody();
ResponseEntity<Map> responseEntity=restTemplate.exchange(consulQueryUrl, HttpMethod.GET, entity,Map.class);
Map responseData = responseEntity.getBody();
boolean isExistSelf=false;//是否能从consul查到自己的信息。查不到说明自己没注册上或者被注销了,需要主动注册。
if(responseData!=null) {
logger.info("InitRunner 执行consul检查纠正服务。。。返回 :\r\n"+responseData.toString());
Iterator it= responseData.keySet().iterator();
while(it.hasNext()) {
String key= (String) it.next();
Map instance = (Map) responseData.get(key);
if(instance!=null) {
String Service =(String) instance.get("Service");
String Address =(String) instance.get("Address");
String Port =null;
if(instance.get("Port")!=null) {
Port =""+ instance.get("Port");
}
String ID =(String) instance.get("ID");
if(Service.equalsIgnoreCase(servername) && Address.equals(ip) && Port.equals(port)) {
logger.info("InitRunner 执行consul检查纠正服务,找到本实例的ID==="+