Merge pull request #1126 from XCXCXCXCX/ISSUE_#359
[#359] provide a way for user to implement their health checkers.
This commit is contained in:
commit
1f1b047984
@ -16,6 +16,7 @@
|
||||
package com.alibaba.nacos.api.naming.pojo;
|
||||
|
||||
import com.alibaba.fastjson.annotation.JSONField;
|
||||
import com.alibaba.fastjson.serializer.SerializeWriter;
|
||||
import com.alibaba.nacos.api.common.Constants;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
@ -45,8 +46,16 @@ public abstract class AbstractHealthChecker implements Cloneable {
|
||||
* @return Another instance with exactly the same fields.
|
||||
* @throws CloneNotSupportedException
|
||||
*/
|
||||
@Override
|
||||
public abstract AbstractHealthChecker clone() throws CloneNotSupportedException;
|
||||
|
||||
/**
|
||||
* used to JsonAdapter
|
||||
*/
|
||||
public void jsonAdapterCallback(SerializeWriter writer){
|
||||
// do nothing
|
||||
}
|
||||
|
||||
public static class None extends AbstractHealthChecker {
|
||||
|
||||
public static final String TYPE = "NONE";
|
||||
@ -116,6 +125,17 @@ public abstract class AbstractHealthChecker implements Cloneable {
|
||||
return headerMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* used to JsonAdapter
|
||||
*
|
||||
* @param writer
|
||||
*/
|
||||
@Override
|
||||
public void jsonAdapterCallback(SerializeWriter writer) {
|
||||
writer.writeFieldValue(',', "path", getPath());
|
||||
writer.writeFieldValue(',', "headers", getHeaders());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(path, headers, expectedResponseCode);
|
||||
@ -215,6 +235,18 @@ public abstract class AbstractHealthChecker implements Cloneable {
|
||||
this.pwd = pwd;
|
||||
}
|
||||
|
||||
/**
|
||||
* used to JsonAdapter
|
||||
*
|
||||
* @param writer
|
||||
*/
|
||||
@Override
|
||||
public void jsonAdapterCallback(SerializeWriter writer) {
|
||||
writer.writeFieldValue(',', "user", getUser());
|
||||
writer.writeFieldValue(',', "pwd", getPwd());
|
||||
writer.writeFieldValue(',', "cmd", getCmd());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(user, pwd, cmd);
|
||||
|
@ -104,6 +104,7 @@
|
||||
<version>2.1.1.RELEASE</version>
|
||||
<configuration>
|
||||
<mainClass>com.alibaba.nacos.Nacos</mainClass>
|
||||
<layout>ZIP</layout>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
|
@ -35,9 +35,8 @@ if not "%2" == "cluster" (
|
||||
set "JAVA_OPT=%JAVA_OPT% -XX:-UseLargePages"
|
||||
)
|
||||
|
||||
set "JAVA_OPT=%JAVA_OPT% -Xbootclasspath/a:%BASE_DIR%\plugins\cmdb"
|
||||
set "JAVA_OPT=%JAVA_OPT% -Dnacos.home=%BASE_DIR%"
|
||||
set "JAVA_OPT=%JAVA_OPT% -jar %BASE_DIR%\target\nacos-server.jar"
|
||||
set "JAVA_OPT=%JAVA_OPT% -Dloader.path=%BASE_DIR%/plugins/health -jar %BASE_DIR%\target\nacos-server.jar"
|
||||
set "JAVA_OPT=%JAVA_OPT% --spring.config.location=%CUSTOM_SEARCH_LOCATIONS%"
|
||||
set "JAVA_OPT=%JAVA_OPT% --logging.config=%BASE_DIR%/conf/nacos-logback.xml"
|
||||
|
||||
|
@ -102,7 +102,7 @@ else
|
||||
fi
|
||||
|
||||
JAVA_OPT="${JAVA_OPT} -Dnacos.home=${BASE_DIR}"
|
||||
JAVA_OPT="${JAVA_OPT} -jar ${BASE_DIR}/target/nacos-server.jar"
|
||||
JAVA_OPT="${JAVA_OPT} -Dloader.path=${BASE_DIR}/plugins/health -jar ${BASE_DIR}/target/nacos-server.jar"
|
||||
JAVA_OPT="${JAVA_OPT} ${JAVA_OPT_EXT}"
|
||||
JAVA_OPT="${JAVA_OPT} --spring.config.location=${CUSTOM_SEARCH_LOCATIONS}"
|
||||
JAVA_OPT="${JAVA_OPT} --logging.config=${BASE_DIR}/conf/nacos-logback.xml"
|
||||
|
BIN
distribution/plugins/health/nacos-health-plugin-example-1.0.jar
Normal file
BIN
distribution/plugins/health/nacos-health-plugin-example-1.0.jar
Normal file
Binary file not shown.
@ -25,6 +25,7 @@ import com.alibaba.nacos.naming.core.Cluster;
|
||||
import com.alibaba.nacos.naming.core.Service;
|
||||
import com.alibaba.nacos.naming.core.ServiceManager;
|
||||
import com.alibaba.nacos.naming.exception.NacosException;
|
||||
import com.alibaba.nacos.naming.healthcheck.HealthCheckType;
|
||||
import com.alibaba.nacos.naming.misc.Loggers;
|
||||
import com.alibaba.nacos.naming.misc.UtilsAndCommons;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
@ -75,23 +76,12 @@ public class ClusterController {
|
||||
|
||||
JSONObject healthCheckObj = JSON.parseObject(healthChecker);
|
||||
AbstractHealthChecker abstractHealthChecker;
|
||||
|
||||
switch (healthCheckObj.getString("type")) {
|
||||
case AbstractHealthChecker.Tcp.TYPE:
|
||||
abstractHealthChecker = JSON.parseObject(healthChecker, AbstractHealthChecker.Tcp.class);
|
||||
break;
|
||||
case AbstractHealthChecker.Http.TYPE:
|
||||
abstractHealthChecker = JSON.parseObject(healthChecker, AbstractHealthChecker.Http.class);
|
||||
break;
|
||||
case AbstractHealthChecker.Mysql.TYPE:
|
||||
abstractHealthChecker = JSON.parseObject(healthChecker, AbstractHealthChecker.Mysql.class);
|
||||
break;
|
||||
case AbstractHealthChecker.None.TYPE:
|
||||
abstractHealthChecker = JSON.parseObject(healthChecker, AbstractHealthChecker.None.class);
|
||||
break;
|
||||
default:
|
||||
throw new NacosException(NacosException.INVALID_PARAM, "unknown health check type:" + healthChecker);
|
||||
String type = healthCheckObj.getString("type");
|
||||
Class<AbstractHealthChecker> healthCheckClass = HealthCheckType.ofHealthCheckerClass(type);
|
||||
if(healthCheckClass == null){
|
||||
throw new NacosException(NacosException.INVALID_PARAM, "unknown health check type:" + healthChecker);
|
||||
}
|
||||
abstractHealthChecker = JSON.parseObject(healthChecker, healthCheckClass);
|
||||
|
||||
cluster.setHealthChecker(abstractHealthChecker);
|
||||
cluster.setMetadata(UtilsAndCommons.parseMetadata(metadata));
|
||||
@ -105,4 +95,5 @@ public class ClusterController {
|
||||
|
||||
return "ok";
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ package com.alibaba.nacos.naming.controllers;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.alibaba.nacos.api.common.Constants;
|
||||
import com.alibaba.nacos.api.naming.CommonParams;
|
||||
import com.alibaba.nacos.api.naming.pojo.AbstractHealthChecker;
|
||||
import com.alibaba.nacos.core.utils.WebUtils;
|
||||
import com.alibaba.nacos.naming.boot.RunningConfig;
|
||||
import com.alibaba.nacos.naming.core.DistroMapper;
|
||||
@ -32,11 +33,16 @@ import com.alibaba.nacos.naming.web.CanDistro;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Health status related operation controller
|
||||
@ -110,4 +116,20 @@ public class HealthController {
|
||||
|
||||
return "ok";
|
||||
}
|
||||
|
||||
@ResponseBody
|
||||
@RequestMapping(value = "checkers", method = RequestMethod.GET)
|
||||
public ResponseEntity checkers() {
|
||||
List<Class> classes = HealthCheckType.getLoadedHealthCheckerClasses();
|
||||
Map<String, AbstractHealthChecker> checkerMap = new HashMap<>(8);
|
||||
for (Class clazz : classes) {
|
||||
try {
|
||||
AbstractHealthChecker checker = (AbstractHealthChecker) clazz.newInstance();
|
||||
checkerMap.put(checker.getType(), checker);
|
||||
} catch (InstantiationException | IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return ResponseEntity.ok(checkerMap);
|
||||
}
|
||||
}
|
||||
|
@ -15,48 +15,45 @@
|
||||
*/
|
||||
package com.alibaba.nacos.naming.healthcheck;
|
||||
|
||||
import com.alibaba.nacos.naming.healthcheck.extend.HealthCheckExtendProvider;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author nacos
|
||||
*/
|
||||
@Component("healthCheckDelegate")
|
||||
public class HealthCheckProcessorDelegate implements HealthCheckProcessor {
|
||||
|
||||
@Autowired
|
||||
private HttpHealthCheckProcessor httpProcessor;
|
||||
private Map<String, HealthCheckProcessor> healthCheckProcessorMap
|
||||
= new HashMap<>();
|
||||
|
||||
public HealthCheckProcessorDelegate(HealthCheckExtendProvider provider) {
|
||||
provider.init();
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private TcpSuperSenseProcessor tcpProcessor;
|
||||
|
||||
@Autowired
|
||||
private MysqlHealthCheckProcessor mysqlProcessor;
|
||||
|
||||
@Autowired
|
||||
private NoneHealthCheckProcessor noneProcessor;
|
||||
public void addProcessor(Collection<HealthCheckProcessor> processors){
|
||||
healthCheckProcessorMap.putAll(processors.stream()
|
||||
.filter(processor -> processor.getType() != null)
|
||||
.collect(Collectors.toMap(HealthCheckProcessor::getType, processor -> processor)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(HealthCheckTask task) {
|
||||
|
||||
String type = task.getCluster().getHealthChecker().getType();
|
||||
|
||||
if (type.equals(httpProcessor.getType())) {
|
||||
httpProcessor.process(task);
|
||||
return;
|
||||
HealthCheckProcessor processor = healthCheckProcessorMap.get(type);
|
||||
if(processor == null){
|
||||
processor = healthCheckProcessorMap.get("none");
|
||||
}
|
||||
|
||||
if (type.equals(tcpProcessor.getType())) {
|
||||
tcpProcessor.process(task);
|
||||
return;
|
||||
}
|
||||
|
||||
if (type.equals(mysqlProcessor.getType())) {
|
||||
mysqlProcessor.process(task);
|
||||
return;
|
||||
}
|
||||
|
||||
noneProcessor.process(task);
|
||||
processor.process(task);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -15,6 +15,13 @@
|
||||
*/
|
||||
package com.alibaba.nacos.naming.healthcheck;
|
||||
|
||||
import com.alibaba.nacos.api.naming.pojo.AbstractHealthChecker;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* @author nkorange
|
||||
*/
|
||||
@ -22,17 +29,54 @@ public enum HealthCheckType {
|
||||
/**
|
||||
* TCP type
|
||||
*/
|
||||
TCP,
|
||||
TCP("tcp", AbstractHealthChecker.Tcp.class),
|
||||
/**
|
||||
* HTTP type
|
||||
*/
|
||||
HTTP,
|
||||
HTTP("http", AbstractHealthChecker.Http.class),
|
||||
/**
|
||||
* MySQL type
|
||||
*/
|
||||
MYSQL,
|
||||
MYSQL("mysql", AbstractHealthChecker.Mysql.class),
|
||||
/**
|
||||
* No check
|
||||
*/
|
||||
NONE
|
||||
NONE("none", AbstractHealthChecker.None.class);
|
||||
|
||||
private String name;
|
||||
|
||||
private Class healthCheckerClass;
|
||||
|
||||
private static Map<String, Class> EXTEND =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
HealthCheckType(String name, Class healthCheckerClass) {
|
||||
this.name = name;
|
||||
this.healthCheckerClass = healthCheckerClass;
|
||||
}
|
||||
|
||||
public static void registerHealthChecker(String type, Class healthCheckerClass){
|
||||
EXTEND.putIfAbsent(type, healthCheckerClass);
|
||||
}
|
||||
|
||||
public static Class ofHealthCheckerClass(String type){
|
||||
HealthCheckType enumType;
|
||||
try {
|
||||
enumType = valueOf(type);
|
||||
}catch (Exception e){
|
||||
return EXTEND.get(type);
|
||||
}
|
||||
return enumType.healthCheckerClass;
|
||||
}
|
||||
|
||||
public static List<Class> getLoadedHealthCheckerClasses(){
|
||||
List<Class> all = new ArrayList<>();
|
||||
for(HealthCheckType type : values()){
|
||||
all.add(type.healthCheckerClass);
|
||||
}
|
||||
for(Map.Entry<String, Class> entry : EXTEND.entrySet()){
|
||||
all.add(entry.getValue());
|
||||
}
|
||||
return all;
|
||||
}
|
||||
}
|
||||
|
@ -42,25 +42,16 @@ public class JsonAdapter implements ObjectDeserializer, ObjectSerializer {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName) {
|
||||
JSONObject jsonObj = (JSONObject) parser.parse();
|
||||
String checkType = jsonObj.getString("type");
|
||||
|
||||
if (StringUtils.equals(checkType, AbstractHealthChecker.Http.TYPE)) {
|
||||
return (T) JSON.parseObject(jsonObj.toJSONString(), AbstractHealthChecker.Http.class);
|
||||
}
|
||||
Class target = HealthCheckType.ofHealthCheckerClass(checkType);
|
||||
|
||||
if (StringUtils.equals(checkType, AbstractHealthChecker.Tcp.TYPE)) {
|
||||
return (T) JSON.parseObject(jsonObj.toJSONString(), AbstractHealthChecker.Tcp.class);
|
||||
}
|
||||
|
||||
if (StringUtils.equals(checkType, AbstractHealthChecker.None.TYPE)) {
|
||||
return (T) JSON.parseObject(jsonObj.toJSONString(), AbstractHealthChecker.None.class);
|
||||
}
|
||||
|
||||
if (StringUtils.equals(checkType, AbstractHealthChecker.Mysql.TYPE)) {
|
||||
return (T) JSON.parseObject(jsonObj.toJSONString(), AbstractHealthChecker.Mysql.class);
|
||||
if(target != null){
|
||||
return (T) JSON.parseObject(jsonObj.toJSONString(), target);
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -83,21 +74,6 @@ public class JsonAdapter implements ObjectDeserializer, ObjectSerializer {
|
||||
|
||||
writer.writeFieldValue(',', "type", config.getType());
|
||||
|
||||
if (StringUtils.equals(config.getType(), HealthCheckType.HTTP.name())) {
|
||||
AbstractHealthChecker.Http httpCheckConfig = (AbstractHealthChecker.Http) config;
|
||||
writer.writeFieldValue(',', "path", httpCheckConfig.getPath());
|
||||
writer.writeFieldValue(',', "headers", httpCheckConfig.getHeaders());
|
||||
}
|
||||
|
||||
if (StringUtils.equals(config.getType(), HealthCheckType.TCP.name())) {
|
||||
// nothing sepcial to handle
|
||||
}
|
||||
|
||||
if (StringUtils.equals(config.getType(), HealthCheckType.MYSQL.name())) {
|
||||
AbstractHealthChecker.Mysql mysqlCheckConfig = (AbstractHealthChecker.Mysql) config;
|
||||
writer.writeFieldValue(',', "user", mysqlCheckConfig.getUser());
|
||||
writer.writeFieldValue(',', "pwd", mysqlCheckConfig.getPwd());
|
||||
writer.writeFieldValue(',', "cmd", mysqlCheckConfig.getCmd());
|
||||
}
|
||||
config.jsonAdapterCallback(writer);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.alibaba.nacos.naming.healthcheck.extend;
|
||||
|
||||
import com.alibaba.nacos.api.naming.pojo.AbstractHealthChecker;
|
||||
import com.alibaba.nacos.naming.healthcheck.HealthCheckProcessor;
|
||||
import com.alibaba.nacos.naming.healthcheck.HealthCheckType;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.config.SingletonBeanRegistry;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author XCXCXCXCX
|
||||
*/
|
||||
@Component
|
||||
public class HealthCheckExtendProvider implements BeanFactoryAware {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(HealthCheckExtendProvider.class);
|
||||
|
||||
private ServiceLoader<HealthCheckProcessor> processorLoader
|
||||
= ServiceLoader.load(HealthCheckProcessor.class);
|
||||
|
||||
private ServiceLoader<AbstractHealthChecker> checkerLoader
|
||||
= ServiceLoader.load(AbstractHealthChecker.class);
|
||||
|
||||
private SingletonBeanRegistry registry;
|
||||
|
||||
public void init(){
|
||||
loadExtend();
|
||||
}
|
||||
|
||||
private void loadExtend() {
|
||||
Iterator<HealthCheckProcessor> processorIt = processorLoader.iterator();
|
||||
Iterator<AbstractHealthChecker> healthCheckerIt = checkerLoader.iterator();
|
||||
|
||||
Set<String> origin = new HashSet<>();
|
||||
for(HealthCheckType type : HealthCheckType.values()){
|
||||
origin.add(type.name());
|
||||
}
|
||||
Set<String> processorType = new HashSet<>();
|
||||
Set<String> healthCheckerType = new HashSet<>();
|
||||
processorType.addAll(origin);
|
||||
healthCheckerType.addAll(origin);
|
||||
|
||||
while(processorIt.hasNext()){
|
||||
HealthCheckProcessor processor = processorIt.next();
|
||||
String type = processor.getType();
|
||||
if(processorType.contains(type)){
|
||||
throw new RuntimeException("More than one processor of the same type was found : [type=\"" + type + "\"]");
|
||||
}
|
||||
processorType.add(type);
|
||||
registry.registerSingleton(lowerFirstChar(processor.getClass().getSimpleName()), processor);
|
||||
}
|
||||
|
||||
while(healthCheckerIt.hasNext()){
|
||||
AbstractHealthChecker checker = healthCheckerIt.next();
|
||||
String type = checker.getType();
|
||||
if(healthCheckerType.contains(type)){
|
||||
throw new RuntimeException("More than one healthChecker of the same type was found : [type=\"" + type + "\"]");
|
||||
}
|
||||
healthCheckerType.add(type);
|
||||
HealthCheckType.registerHealthChecker(checker.getType(), checker.getClass());
|
||||
}
|
||||
if(!processorType.equals(healthCheckerType)){
|
||||
throw new RuntimeException("An unmatched processor and healthChecker are detected in the extension package.");
|
||||
}
|
||||
if(processorType.size() > origin.size()){
|
||||
processorType.removeAll(origin);
|
||||
LOGGER.debug("init health plugin : types=" + processorType);
|
||||
}
|
||||
}
|
||||
|
||||
private String lowerFirstChar(String simpleName) {
|
||||
if(StringUtils.isBlank(simpleName)){
|
||||
throw new IllegalArgumentException("can't find extend processor class name");
|
||||
}
|
||||
return String.valueOf(simpleName.charAt(0)).toLowerCase() + simpleName.substring(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
if(beanFactory instanceof SingletonBeanRegistry){
|
||||
this.registry = (SingletonBeanRegistry) beanFactory;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user