CAT客户端上报消息时,是开启了一个sender线程从消息队列里面获取消息后给服务器上报消息。那么当服务器是集群时,CAT客户端是如何选择服务器的呢?本文着重分析一下CAT客户端路由的实现算法。
源码分析
TcpSocketSender
CAT客户端上报消息线程TcpSocketSender#initialize初始化方法,该方法由DefaultTransportManager#initialize调用,是在应用启动中由框架调用的初始化方法。
public void initialize() {
Integer queueSize = SIZE;
if (System.getProperty(COMMAND_LINE_QUEUE_CAPACITY) != null){
try{
queueSize = Integer.parseInt(System.getProperty(COMMAND_LINE_QUEUE_CAPACITY));@1
}catch (Exception e){
m_logger.error(String.format("Command Line Param[%s] is not valid, use default queue size %d !", COMMAND_LINE_QUEUE_CAPACITY, queueSize), e);
}
}
m_queue = new DefaultMessageQueue(queueSize);@2
Integer atomicQueueSize = queueSize;
if (System.getProperty(COMMAND_LINE_ATOMIC_QUEUE_CAPACITY) != null){
try{
atomicQueueSize = Integer.parseInt(System.getProperty(COMMAND_LINE_ATOMIC_QUEUE_CAPACITY));
}catch (Exception e){
m_logger.error(String.format("Command Line Param[%s] is not valid, use default queue size %d !", COMMAND_LINE_ATOMIC_QUEUE_CAPACITY, atomicQueueSize), e);
}
}
m_atomicTrees = new DefaultMessageQueue(atomicQueueSize);@3
m_manager = new ChannelManager(m_logger, m_serverAddresses, m_queue, m_configManager, m_factory);@4
Threads.forGroup("cat").start(this);@5
Threads.forGroup("cat").start(m_manager);@6
Threads.forGroup("cat").start(new MergeAtomicTask());@7
}
代码@1:获取queue大小。
代码@2:创建MessageQueue,用来存储非原子消息。
代码@3:创建MessageQueue,用来存储原子消息。
代码@4:创建一个ChannelManager,管理跟服务端通信的Channel。
代码@5:开启TcpSocketSender任务线程。
代码@6:开启ChannelManager任务线程。
代码@7:开启合并原子消息任务线程。
TcpSocketSender#run方法会从MessageQueue队列获取上报的消息,然后通过sendInternal方法上报消息。
private void sendInternal(MessageTree tree) {
ChannelFuture future = m_manager.channel();@1
ByteBuf buf = PooledByteBufAllocator.DEFAULT.buffer(10 * 1024); // 10K
m_codec.encode(tree, buf);@2
int size = buf.readableBytes();
Channel channel = future.channel();
channel.writeAndFlush(buf);@3
if (m_statistics != null) {
m_statistics.onBytes(size);
}
}
代码@1:从ChannelManager获取ChannelFuture对象。在ChannelManager构造方法中会初始化一个可用的Channel。
代码@2:编码上报的消息。
代码@3:发送消息。
ChannelManager
构造方法
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();@1
if (StringUtils.isNotEmpty(serverConfig)) {
List<InetSocketAddress> configedAddresses = parseSocketAddress(serverConfig);
ChannelHolder holder = initChannel(configedAddresses, serverConfig);@2
if (holder != null) {
m_activeChannelHolder = holder; @3
} 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");
}
}
}
代码@1:从server拉取配置的路由列表。具体实现可见com.dianping.cat.system.page.router.Handler类。
代码@2:如果服务器端配置了路由列表,则用该路由列表初始化Channel。该方法从server返回的路由列表中选择第一个可用的ChannelHolder,然后赋值给m_activeChannelHolder。客户端上报数据时,会从ChannelManager中获取activeChannelHolder对象,然后上报数据。
代码@3:将获取到的有效的ChannelHolder赋值给m_activeChannelHolder,以后可以通过getActiveFuture方法获取ChannelFuture。
客户端设计
1、Cat客户端生产消息,放入到Message Queue。
2、TcpSocketSender从MessageQueue获取消息,上报给Cat服务器。