06.CAT-集群&机制
CAT集群,不同以往的ZK集群,但是类似于Redis的Cluster集群。
CAT服务定位:
1.无中心节点(目的是避开集群的选举);
2.永久性的提供服务,前提是集群只有有活的节点
6.01 集群&机制
CAT的集群本身既是服务端,又是客户端。
Client 定时向 Server 发送请求,汇报自己的状态。
Server 收集 Client 进行汇总数据,监控 Client 的健康状态 ,如果出现异常,进行 告警通知项目负责人
6.02 获取集群信息
部署服务端代码,cat-2.0.0.war,本身的war包是含有客户端的cat-client-2.0.0.jar
cat-client模块通过读取 /data/appdatas/cat/client.xml
ClientConfigManager 组件初始化
public class DefaultClientConfigManager implements LogEnabled, ClientConfigManager, Initializable {
private static final String CAT_CLIENT_XML = "/META-INF/cat/client.xml";
private static final String PROPERTIES_CLIENT_XML = "/META-INF/app.properties";
private static final String XML = "/data/appdatas/cat/client.xml";
....
@Override
public void initialize() throws InitializationException {
File configFile = new File(XML);
initialize(configFile);
}
@Override
public void initialize(File configFile) throws InitializationException {
try {
ClientConfig globalConfig = null;
ClientConfig clientConfig = null;
if (configFile != null) {
if (configFile.exists()) {
String xml = Files.forIO().readFrom(configFile.getCanonicalFile(), "utf-8");
globalConfig = DefaultSaxParser.parse(xml);
m_logger.info(String.format("Global config file(%s) found.", configFile));
} else {
m_logger.warn(String.format("Global config file(%s) not found, IGNORED.", configFile));
}
}
.....
ClientConfigManager
服务在被lookup时,进行initialize
方法调用initialize
初始化,首先加载配置文件/data/appdatas/cat/client.xml
/data/appdatas/cat/client.xml 配置
<?xml version="1.0" encoding="utf-8"?>
<config mode="client" xmlns:xsi="http://www.w3.org/2001/XMLSchema" xsi:noNamespaceSchemaLocation="config.xsd">
<servers>
<!-- Local mode for development -->
<server ip="127.0.0.1" port="2280" http-port="8080" />
<!-- If under production environment, put actual server address as list. -->
<!--
<server ip="192.168.7.71" port="2280" />
<server ip="192.168.7.72" port="2280" />
-->
</servers>
</config>
xml
可以配置servers
,即集群中所有节点的配置(ip、tcp和http-port
)通过
cat-client
模块的加载配置获取所有的集群节点信息
6.03 持有自身客户端信息
客户代码信息读取
public class DefaultClientConfigManager implements LogEnabled, ClientConfigManager, Initializable {
private static final String CAT_CLIENT_XML = "/META-INF/cat/client.xml";
private static final String PROPERTIES_CLIENT_XML = "/META-INF/app.properties";
@Override
public void initialize() throws InitializationException {
.....
initialize(configFile);
}
@Override
public void initialize(File configFile) throws InitializationException {
try {
ClientConfig globalConfig = null;
ClientConfig clientConfig = null;
......
// load the client configure from Java class-path
clientConfig = loadConfigFromEnviroment();
if (clientConfig == null) {
clientConfig = loadConfigFromXml();
}
// 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;
} catch (Exception e) {
throw new InitializationException(e.getMessage(), e);
}
}
}
private ClientConfig loadConfigFromEnviroment() {
String appName = loadProjectName();
if (appName != null) {
ClientConfig config = new ClientConfig();
config.addDomain(new Domain(appName));
return config;
}
return null;
}
private String loadProjectName() {
String appName = null;
InputStream in = null;
try {
in = Thread.currentThread().getContextClassLoader().getResourceAsStream(PROPERTIES_CLIENT_XML);
if (in == null) {
in = Cat.class.getResourceAsStream(PROPERTIES_CLIENT_XML);
}
....
}
- 读取配置
/META-INF/app.properties
app.properties
app.name=cat
6.04 client->Server 发送消息
client的message需要发送
public class DefaultMessageManager extends ContainerHolder implements MessageManager, Initializable, LogEnabled {
@Inject
private TransportManager m_transportManager;
public void flush(MessageTree tree) {
if (tree.getMessageId() == null) {
tree.setMessageId(nextMessageId());
}
MessageSender sender = m_transportManager.getSender();
if (sender != null && isMessageEnabled()) {
sender.send(tree);
reset();
} else {
m_throttleTimes++;
if (m_throttleTimes % 10000 == 0 || m_throttleTimes == 1) {
m_logger.info("Cat Message is throttled! Times:" + m_throttleTimes);
}
}
}
......
通过 MessageSender 进行消息的 send 发送
public class TcpSocketSender implements Task, MessageSender, LogEnabled {
private transient boolean m_active;
@Override
public void initialize() {
int len = getQueueSize();
m_queue = new DefaultMessageQueue(len);
m_atomicTrees = new DefaultMessageQueue(len);
m_manager = new ChannelManager(m_logger, m_serverAddresses, m_queue, m_configManager, m_factory);
Threads.forGroup("cat").start(this);
Threads.forGroup("cat").start(m_manager);
Threads.forGroup("cat").start(new MergeAtomicTask());
}
@Override
public void run() {
m_active = true;
try {
while (m_active) {
ChannelFuture channel = m_manager.channel();
if (channel != null && checkWritable(channel)) {
try {
MessageTree tree = m_queue.poll();
if (tree != null) {
sendInternal(tree);
tree.setMessage(null);
}
} catch (Throwable t) {
m_logger.error("Error when sending message over TCP socket!", t);
}
} else {
long current = System.currentTimeMillis();
long oldTimestamp = current - HOUR;
while (true) {
try {
MessageTree tree = m_queue.peek();
if (tree != null && tree.getMessage().getTimestamp() < oldTimestamp) {
MessageTree discradTree = m_queue.poll();
if (discradTree != null) {
m_statistics.onOverflowed(discradTree);
}
} else {
break;
}
} catch (Exception e) {
m_logger.error(e.getMessage(), e);
break;
}
}
TimeUnit.MILLISECONDS.sleep(5);
}
}
} catch (InterruptedException e) {
// ignore it
m_active = false;
}
}
@Override
public void send(MessageTree tree) {
if (isAtomicMessage(tree)) {
boolean result = m_atomicTrees.offer(tree, m_manager.getSample());
if (!result) {
logQueueFullInfo(tree);
}
} else {
boolean result = m_queue.offer(tree, m_manager.getSample());
if (!result) {
logQueueFullInfo(tree);
}
}
}
client与server通讯建立
public class ChannelManager implements Task {
public ChannelManager(Logger logger, List<InetSocketAddress> serverAddresses, MessageQueue queue,
ClientConfigManager configManager, MessageIdFactory idFactory) {
m_logger = logger;
m_queue = queue;
m_configManager = configManager;
m_idfactory = idFactory;
EventLoopGroup group = new NioEventLoopGroup(1, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);
return t;
}
});
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group).channel(NioSocketChannel.class);
bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
bootstrap.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
}
});
m_bootstrap = bootstrap;
String serverConfig = loadServerConfig();
if (StringUtils.isNotEmpty(serverConfig)) {
List<InetSocketAddress> configedAddresses = parseSocketAddress(serverConfig);
ChannelHolder holder = initChannel(configedAddresses, serverConfig);
if (holder != null) {
m_activeChannelHolder = holder;
} else {
m_activeChannelHolder = new ChannelHolder();
m_activeChannelHolder.setServerAddresses(configedAddresses);
}
} else {
ChannelHolder holder = initChannel(serverAddresses, null);
if (holder != null) {
m_activeChannelHolder = holder;
} else {
m_activeChannelHolder = new ChannelHolder();
m_activeChannelHolder.setServerAddresses(serverAddresses);
m_logger.error("error when init cat module due to error config xml in /data/appdatas/cat/client.xml");
}
}
}
@Override
public void run() {
while (m_active) {
// make save message id index asyc
m_idfactory.saveMark();
checkServerChanged();
ChannelFuture activeFuture = m_activeChannelHolder.getActiveFuture();
List<InetSocketAddress> serverAddresses = m_activeChannelHolder.getServerAddresses();
doubleCheckActiveServer(activeFuture);
reconnectDefaultServer(activeFuture, serverAddresses);
try {
Thread.sleep(10 * 1000L); // check every 10 seconds
} catch (InterruptedException e) {
// ignore
}
}
}
ChannelManager
被TcpSocketSender
在initialize
实例化ChannelManager
的 线程run
方法
6.05 server 处理消息
server
只是对埋点消息统一处理
6.02 小结
- 原理很简单,
client
找到需要通讯的server
,建立Netty
连接,发送请求 - 服务端,解析请求,监控频率下的请求次数