点评CAT是灵活性非常高的RPM项目,但是实际使用的时候,我们希望可以弹性地添加Server并让客户端可以动态的发现。
因此想到了使用注册发现服务,比如Consul。
git上开源的版本,使用的是三个配置文件:client.xml,server.xml和datasources.xml,并要求放在/data/appdatas/cat目录下,client.xml和server.xml都配置了可用的Server服务,现在目标就是将其自动注册和发现。
修改清单如下:
我的操作步骤如下:
为了尽可能少地代码侵入,我创建了一个子项目,将client端有关的代码改动都放到这个项目下,根目录下的pom.xml修改如下:
1、在modules下添加(放在第一个位置,作为第一个编译的项目):
cat-consul-client
2、依赖添加dependencyManagement->dependency下:
你自己的groupid
cat-consul-client
${project.version}
3、我用了com.ecwid.consul包(和spring-cloud-starter-consul-discovery相同),它的json版本依赖比原CAT使用的版本要高,因此改其版本:
com.google.code.gson
gson
2.8.2
cat-consul-client的pom.xml文件:
parent
com.dianping.cat
2.0.0
4.0.0
你的groupid
cat-consul-client
cat-consul-client
jar
org.unidal.framework
web-framework
com.ecwid.consul
consul-api
1.4.0
javax.servlet
javax.servlet-api
provided
org.unidal.maven.plugins
plexus-maven-plugin
generate plexus component descriptor
process-classes
plexus
你的包名.build.ComponentsConfigurator
utf-8
CAT应用了plexus来组织模块关系,ComponentsConfigurator后面介绍。
创建一个类来存储consul相关的配置信息,这里我把server的job和alert配置暂时也放在了这里,并将部分配置挪到了/META-INF/app.properties文件中(本身CAT的client也需要这个文件来注册app name),具体代码如下:
package 你的包名;
import com.dianping.cat.Cat;
import com.dianping.cat.configuration.client.entity.ClientConfig;
import com.dianping.cat.configuration.client.entity.Server;
import com.ecwid.consul.v1.ConsulClient;
import com.ecwid.consul.v1.QueryParams;
import com.ecwid.consul.v1.Response;
import com.ecwid.consul.v1.agent.model.NewService;
import com.ecwid.consul.v1.health.model.HealthService;
import org.unidal.helper.Files;
import org.unidal.lookup.extension.Initializable;
import org.unidal.lookup.extension.InitializationException;
import org.unidal.lookup.logging.LogEnabled;
import org.unidal.lookup.logging.Logger;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.List;
import java.util.Properties;
import java.util.UUID;
/**
* 保存了部分Consul必须的参数,转存配置到文件
* registerServer(): 注册到Consul
*
*/
public class CatConsul implements LogEnabled, Initializable {
private static final String PROPERTIES_XML = "/META-INF/app.properties";
private String consulUrl = "localhost";
private int consulPort = 8500;
private String serviceName = "cat-consul-service";
private String serviceId;
private String serviceAddress;
private String jobMachine = "true";
private String alertMachine = "true";
private String appName;
private Logger m_logger;
private boolean isInitialized = false;
public String getConsulUrl() {
return consulUrl;
}
public boolean isInitialized() {
return isInitialized;
}
public String getAppName(){
return appName;
}
public void enableLogging(Logger logger) {
m_logger = logger;
}
public String getServiceName() {
return serviceName;
}
public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}
public String getServiceId() {
if(serviceId == null) {
File uuidFile = new File(Cat.getCatHome(), "config/uuid");
if(uuidFile.isFile()) {
try {
serviceId = Files.forIO().readFrom(uuidFile, "utf-8");
} catch (IOException e) {
e.printStackTrace();
}
}
else {
serviceId = this.serviceName + "-" + UUID.randomUUID();
try {
Files.forIO().writeTo(uuidFile, serviceId);
} catch (IOException e) {
e.printStackTrace();
}
}
}
return serviceId;
}
public void setServiceId(String serviceId) {
this.serviceId = serviceId;
}
public void setServiceAddress(String serviceAddress) {
this.serviceAddress = serviceAddress;
}
public CatConsul() {
}
public CatConsul(String consulUrl, int consulPort) {
this.consulUrl = consulUrl;
this.consulPort = consulPort;
}
public ClientConfig getClientConfig() {
ClientConfig config = new ClientConfig().setMode("client");
List services = getServers();
for (HealthService healthService : services) {
HealthService.Service service = healthService.getService();
Server server = new Server(service.getAddress());
server.setPort(service.getPort());
config.addServer(server);
}
// no remote server, add local
if(services.size() == 0) {
Server server = new Server(getServiceAddress());
// TODO default port
server.setPort(2280);
config.addServer(server);
}
return config;
}
public File getClientFile() {
return getFile(getClientConfig().toString(), "client");
}
public File getServerFile() {
return getFile(getServerConfig(), "server");
}
public File getFile(String content, String fileName) {
File configFile = null;
try {
configFile = new File(Cat.getCatHome(), fileName + ".xml");
Files.forIO().writeTo(configFile, content);
} catch (IOException e) {
m_logger.warn("generate file error " + configFile);
}
return configFile;
}
public List getServers() {
ConsulClient client = new ConsulClient(consulUrl, consulPort);
Response> healthyServices = client.getHealthServices("cat-consul-service", true, QueryParams.DEFAULT);
return healthyServices.getValue();
}
public String getServiceAddress() {
if(this.serviceAddress == null) {
try {
this.serviceAddress = InetAddress.getLocalHost().getHostAddress().toString();
} catch (UnknownHostException e) {
m_logger.warn("can't load default host ...");
}
}
return this.serviceAddress;
}
public void registerServer() {
NewService newService = new NewService();
newService.setId(getServiceId());
newService.setName(getServiceName());
newService.setAddress(getServiceAddress());
// TODO default port
newService.setPort(2280);
// set check
NewService.Check serviceCheck = new NewService.Check();
serviceCheck.setHttp("http://" + getServiceAddress() + ":8080/cat/actuator/health");
serviceCheck.setTimeout("5s");
serviceCheck.setInterval("10s");
serviceCheck.setDeregisterCriticalServiceAfter("30m");
serviceCheck.setTlsSkipVerify(false);
newService.setCheck(serviceCheck);
ConsulClient client = new ConsulClient(this.consulUrl, this.consulPort);
client.agentServiceRegister(newService);
m_logger.info(String.format("register this service(name:%s, address: %s, port: %s, health check: %s) to consul, consul url is %s, port is %s", serviceName, serviceAddress, 2280, serviceCheck.getHttp(), consulUrl, consulPort));
}
public String getRemoteServers() {
List services = getServers();
// no more server, write local
if(services.size() == 0) {
return getServiceAddress() + ":8080";
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < services.size(); i ++) {
HealthService.Service service = services.get(i).getService();
// TODO CAT http port 8080
sb.append(service.getAddress()).append(":").append("8080");
if(i < services.size() - 1) {
sb.append(",");
}
}
return sb.toString();
}
/**
* need call after register, to load itself.
*
* @return
*/
public String getServerConfig() {
return String.format(serverFormat, jobMachine, alertMachine, getRemoteServers());
}
private String serverFormat = "\n" +
"\n" +
"\t\n" +
"\t\n" +
"\t\t\n" +
"\t\t\n" +
"\t \n" +
"\t\n" +
"\t\t%s \n" +
"\t \n" +
" ";
public void initialize() throws InitializationException {
InputStream in = null;
try {
in = Thread.currentThread().getContextClassLoader().getResourceAsStream(PROPERTIES_XML);
if (in == null) {
in = Cat.class.getResourceAsStream(PROPERTIES_XML);
}
if (in != null) {
Properties prop = new Properties();
prop.load(in);
String _appName = prop.getProperty("app.name");
if (_appName != null) {
m_logger.info(String.format("Find domain name %s from app.properties.", _appName));
this.appName = _appName;
} else {
m_logger.info(String.format("Can't find app.name from app.properties."));
}
String _consulUrl = prop.getProperty("consul.url");
if (_consulUrl != null) {
m_logger.info(String.format("Find consul url %s from app.properties.", _consulUrl));
this.consulUrl = _consulUrl;
}
int _consulport = Integer.decode(prop.getProperty("consul.port", "0"));
if (_consulport != 0) {
m_logger.info(String.format("Find consul port %s from app.properties.", _consulport));
this.consulPort = _consulport;
}
String _alertMachine = prop.getProperty("alert.machine");
if (_alertMachine != null) {
m_logger.info(String.format("Find alert machine %s from app.properties.", _alertMachine));
this.alertMachine = _alertMachine;
}
String _jobMachine = prop.getProperty("job.machine");
if (_jobMachine != null) {
m_logger.info(String.format("Find job machine %s from app.properties.", _jobMachine));
this.jobMachine = _jobMachine;
}
String _localAddress = prop.getProperty("service.address");
if (_localAddress != null) {
m_logger.info(String.format("Find local service address %s from app.properties.", _localAddress));
this.serviceAddress = _localAddress;
}
this.isInitialized = true;
} else {
m_logger.info(String.format("Can't find app.properties in %s", PROPERTIES_XML));
}
} catch (Exception e) {
m_logger.error("cat consul initialize error", e);
throw new InitializationException(e.getMessage(), e);
} finally {
if (in != null) {
try {
in.close();
} catch (Exception e) {
}
}
}
}
}
再创建一个类替代原先的DefaultClientConfigManager类,添加这个类主要是为了改写原来的初始化和后续连接检查时获取的更新服务端信息:
package 你的包名;
import com.dianping.cat.configuration.*;
import com.dianping.cat.configuration.client.entity.ClientConfig;
import com.dianping.cat.configuration.client.entity.Domain;
import com.dianping.cat.configuration.client.entity.Server;
import com.dianping.cat.configuration.client.transform.DefaultSaxParser;
import com.ecwid.consul.v1.health.model.HealthService;
import org.unidal.helper.Files;
import org.unidal.lookup.annotation.Inject;
import org.unidal.lookup.extension.Initializable;
import org.unidal.lookup.extension.InitializationException;
import org.unidal.lookup.logging.LogEnabled;
import org.unidal.lookup.logging.Logger;
import java.io.File;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class ConsulClientConfigManager implements LogEnabled, ClientConfigManager, Initializable {
private Logger m_logger;
private ClientConfig m_config;
@Inject
private CatConsul catConsul;
public void enableLogging(Logger logger) {
m_logger = logger;
}
public Domain getDomain() {
Domain domain = null;
if (m_config != null) {
Map domains = m_config.getDomains();
domain = domains.isEmpty() ? null : domains.values().iterator().next();
}
if (domain != null) {
return domain;
} else {
return new Domain("UNKNOWN").setEnabled(false);
}
}
public int getMaxMessageLength() {
if (m_config == null) {
return 5000;
} else {
return getDomain().getMaxMessageSize();
}
}
//this is for get now server's ip
//TODO change code with ChannelManager#loadServerConfig
public String getServerConfigUrl() {
List healthServiceList = catConsul.getServers();
StringBuilder sb = new StringBuilder();
for(HealthService healthService: healthServiceList){
HealthService.Service service = healthService.getService();
// TODO CAT http port 8080
sb.append(service.getAddress()).append(":").append("8080");
sb.append(";");
}
return "{\"kvs\":{\"routers\":\"" + sb.toString() + "\",\"sample\":\"1.0\"}}";
}
public List getServers() {
if (m_config == null) {
return Collections.emptyList();
} else {
return m_config.getServers();
}
}
public int getTaggedTransactionCacheSize() {
return 1024;
}
public boolean isCatEnabled() {
if (m_config == null) {
return false;
} else {
return m_config.isEnabled();
}
}
public boolean isDumpLocked() {
if (m_config == null) {
return false;
} else {
return m_config.isDumpLocked();
}
}
public void initialize() throws InitializationException {
initialize(null);
}
public void initialize(File configFile) throws InitializationException {
try {
if(!catConsul.isInitialized()) {
catConsul.initialize();
}
ClientConfig globalConfig = null;
ClientConfig clientConfig = new ClientConfig();
clientConfig.addDomain(new Domain(catConsul.getAppName()));
configFile = catConsul.getClientFile();
String xml = Files.forIO().readFrom(configFile, "utf-8");
globalConfig = DefaultSaxParser.parse(xml);
m_logger.info(String.format("Global config file(%s) found.", configFile));
// merge the two configures together to make it effected
if (globalConfig != null && clientConfig != null) {
globalConfig.accept(new ClientConfigMerger(clientConfig));
}
if (clientConfig != null) {
clientConfig.accept(new ClientConfigValidator());
}
m_config = clientConfig;
m_logger.info("consol client config initialized.");
} catch (Exception e) {
throw new InitializationException(e.getMessage(), e);
}
}
}
修改cat-client项目中com.dianping.cat.build.ComponentsConfigurator类,将ClientConfigManager的实现替换为上述实现:
all.add(C(ClientConfigManager.class, ConsulClientConfigManager.class).req(CatConsul.class));
接着修改cat-client项目中com.dianping.cat.message.io.ChannelManager,这个类的修改是唯一侵入的,没办法,它是关键而且源码中是new出来的,不过这里只修改了其中一个的方法:
private String loadServerConfig() {
try {
// String url = m_configManager.getServerConfigUrl();
// InputStream inputstream = Urls.forIO().readTimeout(2000).connectTimeout(1000).openStream(url);
// String content = Files.forIO().readFrom(inputstream, "utf-8");
String content = m_configManager.getServerConfigUrl();
KVConfig routerConfig = (KVConfig) m_jsonBuilder.parse(content.trim(), KVConfig.class);
String current = routerConfig.getValue("routers");
m_sample = Double.valueOf(routerConfig.getValue("sample").trim());
return current.trim();
} catch (Exception e) {
// ignore
}
return null;
}
最后修改cat-client的pom.xml文件,加上项目依赖:
com.ucmed.cat
cat-consul-client
cat-consul-client的ComponentsConfigurator类:
package 你的包名.build;
import 你的包名.CatConsul;
import org.unidal.lookup.configuration.AbstractResourceConfigurator;
import org.unidal.lookup.configuration.Component;
import java.util.ArrayList;
import java.util.List;
public class ComponentsConfigurator extends AbstractResourceConfigurator {
public static void main(String[] args) {
generatePlexusComponentsXmlFile(new ComponentsConfigurator());
}
public List defineComponents() {
List all = new ArrayList();
all.add(C(CatConsul.class));
return all;
}
}
依然先修改根目录下的pom.xml:
1、modules,建议放在倒数第二位:
cat-consul-home
2、dependencyManagement->dependencies:
你的groupid
cat-consul-home
${project.version}
cat-consul-home的pom.xml:
parent
com.dianping.cat
2.0.0
4.0.0
你的groupid
cat-consul-home
cat-consul-home
jar
你的groupid
cat-consul-client
com.dianping.cat
cat-core
org.unidal.framework
web-framework
com.ecwid.consul
consul-api
1.4.0
javax.servlet
javax.servlet-api
provided
org.quartz-scheduler
quartz
2.2.1
utf-8
添加CatServlet类,server端初始化用:
package 你的包名;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.EnumSet;
import javax.servlet.DispatcherType;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import 你的包名.CatConsul;
import org.unidal.initialization.DefaultModuleContext;
import org.unidal.initialization.ModuleContext;
import org.unidal.initialization.ModuleInitializer;
import org.unidal.web.AbstractContainerServlet;
public class CatServlet extends AbstractContainerServlet {
private static final long serialVersionUID = 1L;
private Exception m_exception;
@Override
protected void initComponents(ServletConfig servletConfig) throws ServletException {
try {
ModuleContext ctx = new DefaultModuleContext(getContainer());
ModuleInitializer initializer = ctx.lookup(ModuleInitializer.class);
CatConsul catConsul = ctx.lookup(CatConsul.class);
catConsul.initialize();
catConsul.registerServer();
File clientXmlFile = catConsul.getClientFile();
File serverXmlFile = catConsul.getServerFile();
servletConfig.getServletContext().setAttribute("cat-consul-config-module", catConsul);
ctx.setAttribute("cat-client-config-file", clientXmlFile);
ctx.setAttribute("cat-server-config-file", serverXmlFile);
initializer.execute(ctx);
} catch (Exception e) {
m_exception = e;
System.err.println(e);
throw new ServletException(e);
}
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
res.setCharacterEncoding("utf-8");
res.setContentType("text/plain");
PrintWriter writer = res.getWriter();
if (m_exception != null) {
writer.write("Server has NOT been initialized successfully!\r\n\r\n");
m_exception.printStackTrace(writer);
} else {
writer.write("Not implemented yet!");
}
}
}
添加CatConsulHealthFilter,这里consul是通过http方式注册的,所以添加供consul健康检查的filter:
package 你的包名;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* 给consul返回健康状态
*
*/
public class CatConsulHealthFilter implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;
PrintWriter writer = httpResponse.getWriter();
writer.write("{\"status\":\"UP\"}");
writer.flush();
}
public void destroy() {
}
}
修改cat-home项目的pom.xml,添加项目依赖:
你的groupid
cat-consul-home
修改cat-home项目webapp/WEB-INF/web.xml,替换原有的CatServlet(原有的需要移除),并添加健康检查filter配置:
health-filter
com.ucmed.cat.consul.home.CatConsulHealthFilter
cat-servlet
com.ucmed.cat.consul.home.CatServlet
1
health-filter
/actuator/health
REQUEST
修改resources/META-INF/app.properties:
app.name=cat
# default this two is localhost:8500
consul.url=consul的地址
consul.port=consul的端口
# default this two is false
alert.machine=false
job.machine=false
# default with InetAddress.getLocalHost().getHostAddress().toString()
#service.address=本机服务的ip,用于特殊环境
最后mvn clean install -DskipTests 编译安装,其他参考原项目文档,enjoy~