深入理解監控系統——CAT Client端源碼解析

前言

**Server端 **

(Cat-consumer 用於實時分析從客戶端提供的數據\Cat-home 作爲用戶給用戶提供展示的控制端
,並且Cat-home做展示時,通過對Cat-Consumer的調用獲取其他節點的數據,將所有數據彙總展示
consumer、home以及路由中心都是部署在一起的,每個服務端節點都可以充當任何一個角色)

**Client端 **

(Cat-client 提供給業務以及中間層埋點的底層SDK)

相關文章:
[分佈式監控CAT] Server端源碼解析——初始化
[分佈式監控CAT] Client端—定製化SDK\自動埋點實現
[分佈式監控CAT] Client端源碼解析
[分佈式監控CAT] Server端源碼解析——消息消費\報表處理

Client端

cat-client模塊的包結構

└─com
    ├─dianping
    │  └─cat
    │      ├─build
    │      ├─configuration
    │      ├─log4j
    │      ├─message
    │      │  ├─internal
    │      │  ├─io
    │      │  └─spi
    │      │      ├─codec
    │      │      └─internal
    │      ├─servlet
    │      └─status
    └─site
        ├─helper
        └─lookup
            └─util

Client模塊架構圖

architecture

類圖

UML

源碼閱讀

閱讀思路:

我們通過一個測試用例,debug來理解源碼。

		//靜態方法獲取Transaction對象
        Transaction t=Cat.newTransaction("logTransaction", "logTransaction");

		TimeUnit.SECONDS.sleep(30);
		t.setStatus("0");
		t.complete();

接着我們着重看看關鍵代碼:

Cat.java

	private static Cat s_instance = new Cat();
	private static volatile boolean s_init = false;
	
	private static void checkAndInitialize() {
		if (!s_init) {
			synchronized (s_instance) {
				if (!s_init) {
					initialize(new File(getCatHome(), "client.xml"));
					log("WARN", "Cat is lazy initialized!");
					s_init = true;
				}
			}
		}
	}
	private Cat() {
	}

	public static MessageProducer getProducer() {
		checkAndInitialize();

		return s_instance.m_producer;
	}

Cat lazy Init

可以看到類加載時已經完成了Cat對象的初始化,內存中有且僅有一個Cat Object(static Cat s_instance = new Cat();),但是包含配置信息的完整的Cat對象並沒有完全初始化完成。調用Cat時會先嚐試獲取producer對象,並在獲取之前檢查客戶端配置是否加載完畢(checkAndInitialize)。

checkAndInitialize()通過使用doublecheck來對Cat相關配置填充的單次初始化加載。

1.cat-client首先會使用plexus(一個比較老的IOC容器)加載配置文件/META-INF/plexus/plexus.xml,完成IOC容器的初始化。

2.接着使用…/…/client.xml文件完成cat對象的配置信息填充初始化。並且啓動這四個daemon線程,後文詳細說明:

cat-StatusUpdateTask 用來每秒鐘上報客戶端基本信息(JVM等信息)

cat-merge-atomic-task(消息合併檢查)

cat-TcpSocketSender-ChannelManager(NIO 連接服務端檢查)

cat-TcpSocketSender(消息發送服務端)

CatClientModule

public class CatClientModule extends AbstractModule {
	public static final String ID = "cat-client";

	@Override
	protected void execute(final ModuleContext ctx) throws Exception {
		ctx.info("Current working directory is " + System.getProperty("user.dir"));

		// initialize milli-second resolution level timer
		MilliSecondTimer.initialize();

		// tracking thread start/stop,此處增加經典的hook,用於線程池關閉的清理工作。
		Threads.addListener(new CatThreadListener(ctx));

		// warm up Cat
		Cat.getInstance().setContainer(((DefaultModuleContext) ctx).getContainer());

		// bring up TransportManager
		ctx.lookup(TransportManager.class);

		ClientConfigManager clientConfigManager = ctx.lookup(ClientConfigManager.class);
		
		if (clientConfigManager.isCatEnabled()) {
			// start status update task
			StatusUpdateTask statusUpdateTask = ctx.lookup(StatusUpdateTask.class);

			Threads.forGroup("cat").start(statusUpdateTask);
			LockSupport.parkNanos(10 * 1000 * 1000L); // wait 10 ms

			// MmapConsumerTask mmapReaderTask = ctx.lookup(MmapConsumerTask.class);
			// Threads.forGroup("cat").start(mmapReaderTask);
		}
	}

這裏plexusIOC的具體的初始化加載邏輯在org\unidal\framework\foundation-service\2.5.0\foundation-service-2.5.0.jar中,有興趣可以仔細查看。
當準備工作做完之後,會執行具體的消息構造:

消息構造

DefaultMessageProducer.newTransaction(String type, String name)

@Override
	public Transaction newTransaction(String type, String name) {
		// this enable CAT client logging cat message without explicit setup
		if (!m_manager.hasContext()) {
			//詳細可見下文源碼,此處就是用ThreadLocal存儲一個Context對象:ctx = new Context(m_domain.getId(), m_hostName, m_domain.getIp());
			m_manager.setup();

		}

		if (m_manager.isMessageEnabled()) {
			DefaultTransaction transaction = new DefaultTransaction(type, name, m_manager);

//向Context中填充構造的消息體:Context.m_tree;Context.m_stack;稍後看看Context這個對象
			m_manager.start(transaction, false);
			return transaction;
		} else {
			return NullMessage.TRANSACTION;
		}
	}

DefaultMessageManager.start(Transaction transaction, boolean forked)

@Override
	public void start(Transaction transaction, boolean forked) {
		Context ctx = getContext();//這裏獲取上文中說到的ThreadLocal中構造的Context對象

		if (ctx != null) {
			ctx.start(transaction, forked);

			if (transaction instanceof TaggedTransaction) {
				TaggedTransaction tt = (TaggedTransaction) transaction;

				m_taggedTransactions.put(tt.getTag(), tt);
			}
		} else if (m_firstMessage) {
			m_firstMessage = false;
			m_logger.warn("CAT client is not enabled because it's not initialized yet");
		}
	}

DefaultMessageManager.Context.start(Transaction transaction, boolean forked)

		public void start(Transaction transaction, boolean forked) {
			if (!m_stack.isEmpty()) {//
				 {
					Transaction parent = m_stack.peek();
					addTransactionChild(transaction, parent);
				}
			} else {
				m_tree.setMessage(transaction);//在這裏把返回的transaction放在tree上,如果有嵌套結構,後邊繼續在tree上添枝加葉
			}

			if (!forked) {
				m_stack.push(transaction);
			}
		}

這部分代碼可以看出,
通過ThreadLocal<.Context.>,使Context中實際的消息的構造保證了線程安全。
如果當前Context的棧m_stack不爲空,那麼接着之前的消息後邊,將當前消息構造爲一個孩子結點。如果當前消息之前沒有其他消息,放入m_stack中,並setMessage.也就是當前消息時父節點。

至此,消息體構造完畢。
這裏需要看一下Context類,是DefaultMessageManager包私有的內部類。

Context.java

	class Context {
		private MessageTree m_tree;//初始化的時候構建一個MessageTree

		private Stack<Transaction> m_stack;

		private int m_length;

		private boolean m_traceMode;

		private long m_totalDurationInMicros; // for truncate message

		private Set<Throwable> m_knownExceptions;

		public Context(String domain, String hostName, String ipAddress) {
			m_tree = new DefaultMessageTree();
			m_stack = new Stack<Transaction>();

			Thread thread = Thread.currentThread();
			String groupName = thread.getThreadGroup().getName();

			m_tree.setThreadGroupName(groupName);
			m_tree.setThreadId(String.valueOf(thread.getId()));
			m_tree.setThreadName(thread.getName());

			m_tree.setDomain(domain);
			m_tree.setHostName(hostName);
			m_tree.setIpAddress(ipAddress);
			m_length = 1;
			m_knownExceptions = new HashSet<Throwable>();
		}

每個線程通過使用ThreadLocal構造一個Context對象並存儲。Context主要包含當前的消息體m_tree,和多個嵌套消息體填充的棧:m_stack 。

這裏寫圖片描述

再回到我們原來的UnitTest代碼,
Transaction t=Cat.newTransaction("logTransaction", "logTransaction");
這行代碼完成了客戶端plexusIOC容器的初始化,cat-client的加載初始化、啓動了四個daemon線程,並返回了Transaction對象。

		t.setStatus("0");//很簡單,就是這是一個屬性值
		t.complete();



消息完成後,將消息放入一個隊列中,從而保證異步上報。
transaction.complete();

complete的具體代碼如下:

........
	public void complete() {
		try {
			if (isCompleted()) {
				// complete() was called more than once
				DefaultEvent event = new DefaultEvent("cat", "BadInstrument");

				event.setStatus("TransactionAlreadyCompleted");
				event.complete();
				addChild(event);
			} else {
				m_durationInMicro = (System.nanoTime() - m_durationStart) / 1000L;

				setCompleted(true);

				if (m_manager != null) {
					m_manager.end(this);
				}
			}
		} catch (Exception e) {
			// ignore
		}
	}
........
	@Override
	public void end(Transaction transaction) {
		Context ctx = getContext();

		if (ctx != null && transaction.isStandalone()) {
			if (ctx.end(this, transaction)) {
				m_context.remove();
			}
		}
	}
........

		public boolean end(DefaultMessageManager manager, Transaction transaction) {
			if (!m_stack.isEmpty()) {
				Transaction current = m_stack.pop();//Context的成員變量m_stack彈出棧頂元素,LIFO當然是最新的current元素。

				if (transaction == current) {
					m_validator.validate(m_stack.isEmpty() ? null : m_stack.peek(), current);
				} else {
					while (transaction != current && !m_stack.empty()) {
						m_validator.validate(m_stack.peek(), current);

						current = m_stack.pop();
					}
				}

				if (m_stack.isEmpty()) {//如果當前線程存儲的Context中m_stack無元素
					MessageTree tree = m_tree.copy();

					m_tree.setMessageId(null);//清理m_tree
					m_tree.setMessage(null);

					if (m_totalDurationInMicros > 0) {
						adjustForTruncatedTransaction((Transaction) tree.getMessage());
					}

					manager.flush(tree);//將消息放入消費隊列中
					return true;
				}
			}

			return false;
		}
........
	public void flush(MessageTree tree) {
		if (tree.getMessageId() == null) {
			tree.setMessageId(nextMessageId());//爲消息體生產全局唯一ID,詳見snowflate算法
		}

		MessageSender sender = m_transportManager.getSender();

		if (sender != null && isMessageEnabled()) {
			sender.send(tree);

			reset();//ThreadLocal中存儲的Context清理
		} else {
			m_throttleTimes++;

			if (m_throttleTimes % 10000 == 0 || m_throttleTimes == 1) {
				m_logger.info("Cat Message is throttled! Times:" + m_throttleTimes);
			}
		}
	}
........
	private Context getContext() {
		if (Cat.isInitialized()) {
			Context ctx = m_context.get();//ThreadLocal存儲一個Context對象

			if (ctx != null) {
				return ctx;
			} else {
				if (m_domain != null) {
					ctx = new Context(m_domain.getId(), m_hostName, m_domain.getIp());
				} else {
					ctx = new Context("Unknown", m_hostName, "");
				}

				m_context.set(ctx);
				return ctx;
			}
		}

		return null;
	}
	
//TcpSocketSender.send(MessageTree tree)

	private MessageQueue m_queue = new DefaultMessageQueue(SIZE);

	private MessageQueue m_atomicTrees = new DefaultMessageQueue(SIZE);
	
	@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);
			}
		}
	}

至此,構造的消息體放入了阻塞隊列中等待上傳。如圖左邊。
CAT客戶端在收集端數據方面使用ThreadLocal(線程局部變量),是線程本地變量。保證了線程安全。
爲什麼這樣設計,基於ThreadLocal收集消息?

業務方在處理業務邏輯時基本都是在一個線程內部調用後端服務、數據庫、緩存等,將這些數據拿回來再進行業務邏輯封裝,最後將結果展示給用戶。所以將監控請求作爲一個監控上下文存入線程變量就非常合適。

這裏寫圖片描述
(此圖源自作者的公開分享,原圖來源請點擊此處)

總結
至此,我們可以看到Cat-SDK通過ThreadLocal對消息進行收集,
收集進來按照時間以及類型構造爲Tree結構,在compele()方法中將這個構造的消息放入一個內存隊列中,等待TcpSockekSender這個Daemon線程異步上報給服務端。

接着我們來看看消息上傳服務端的代碼,這裏會有一個線程cat-TcpSocketSender監聽消費隊列,並消費(上傳服務端):

通信上報服務端使用了Netty-Client,並且自定義了消息協議。
TcpSocketSender.java

	@Override
	public void run() {
		m_active = true;

		while (m_active) {
			ChannelFuture channel = m_manager.channel();

			if (channel != null && checkWritable(channel)) {
				try {
					MessageTree tree = m_queue.poll();

					if (tree != null) {
						sendInternal(tree);//netty NIO編碼後TCP發送到服務端。
						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;
					}
				}
				
				try {
					Thread.sleep(5);
				} catch (Exception e) {
					// ignore it
					m_active = false;
				}
			}
		}
	}

    private void sendInternal(MessageTree tree) {
        ChannelFuture future = m_manager.channel();
        ByteBuf buf = PooledByteBufAllocator.DEFAULT.buffer(10 * 1024); // 10K

        System.out.println(tree);

        m_codec.encode(tree, buf);//編碼後發送

        int size = buf.readableBytes();
        Channel channel = future.channel();

        channel.writeAndFlush(buf);
        if (m_statistics != null) {
            m_statistics.onBytes(size);
        }
    }

接下來我們着重看看,隨着cat-client加載啓動的幾個daemon Thread後臺線程:

cat-merge-atomic-task

接上文,符合如下邏輯判斷的atomicMessage會放入m_atomicTrees消息隊列,然後由這個後臺線程監聽並消費。
具體代碼如下:

TcpSocketSender.java

private MessageQueue m_atomicTrees = new DefaultMessageQueue(SIZE);

......

		private boolean isAtomicMessage(MessageTree tree) {
		Message message = tree.getMessage();//從tree上拿去message

		if (message instanceof Transaction) {//如果這個message實現了Transaction接口,也就是Transaction類型的消息
			String type = message.getType();

			if (type.startsWith("Cache.") || "SQL".equals(type)) {//如果以Cache.,SQL開頭的則返回True
				return true;
			} else {
				return false;
			}
		} else {
			return true;
		}
		//看到這裏,也就是說,"Cache","SQL"開頭的Transaction消息,或者非Transaction消息,認爲是atomicMessage.
	}
	
......

public void send(MessageTree tree) {
		if (isAtomicMessage(tree)) {//如果符合atomicMessage
			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);
			}
		}
	}
......

public class DefaultMessageQueue implements MessageQueue {
	private BlockingQueue<MessageTree> m_queue;

	private AtomicInteger m_count = new AtomicInteger();

	public DefaultMessageQueue(int size) {
		m_queue = new LinkedBlockingQueue<MessageTree>(size);
	}

	@Override
	public boolean offer(MessageTree tree) {
		return m_queue.offer(tree);
	}

	@Override
	public boolean offer(MessageTree tree, double sampleRatio) {
		if (tree.isSample() && sampleRatio < 1.0) {//如果這個消息是sample,並且sampleRation大於1
			if (sampleRatio > 0) {//這段邏輯就是按採樣率去剔除一些消息,只選取其中一部分進行後續的消費上傳。
				int count = m_count.incrementAndGet();

				if (count % (1 / sampleRatio) == 0) {
					return offer(tree);
				}
			}
			return false;
		} else {//不做採樣過濾,放入隊列
			return offer(tree);
		}
	}

	@Override
	public MessageTree peek() {
		return m_queue.peek();
	}

	@Override
	public MessageTree poll() {
		try {
			return m_queue.poll(5, TimeUnit.MILLISECONDS);
		} catch (InterruptedException e) {
			return null;
		}
	}

	@Override
	public int size() {
		return m_queue.size();
	}
}

接下來,看看這個後臺進程的消費動作:

......

private boolean shouldMerge(MessageQueue trees) {
		MessageTree tree = trees.peek();//獲取對頭元素,非移除

		if (tree != null) {
			long firstTime = tree.getMessage().getTimestamp();
			int maxDuration = 1000 * 30;
			//消息在30s內生成,或者隊列擠壓消息超過200,則需要merge
			if (System.currentTimeMillis() - firstTime > maxDuration || trees.size() >= MAX_CHILD_NUMBER) {
				return true;
			}
		}
		return false;
	}

......

		@Override
		public void run() {
			while (true) {
				if (shouldMerge(m_atomicTrees)) {
					MessageTree tree = mergeTree(m_atomicTrees);//把m_atomicTrees隊列中的消息merge爲一條消息樹
					boolean result = m_queue.offer(tree);//放入m_queue隊列,等待cat-TcpSocketSender線程正常消費

					if (!result) {
						logQueueFullInfo(tree);
					}
				} else {
					try {
						Thread.sleep(5);
					} catch (InterruptedException e) {
						break;
					}
				}
			}
		}

.....

private MessageTree mergeTree(MessageQueue trees) {
		int max = MAX_CHILD_NUMBER;
		DefaultTransaction tran = new DefaultTransaction("_CatMergeTree", "_CatMergeTree", null);//增加merge處理埋點
		MessageTree first = trees.poll();//從隊列頭部移除

		tran.setStatus(Transaction.SUCCESS);
		tran.setCompleted(true);
		tran.addChild(first.getMessage());
        tran.setTimestamp(first.getMessage().getTimestamp());
        long lastTimestamp = 0;
        long lastDuration = 0;

        //這段邏輯就是不停從這個m_atomicTrees隊列頭部拿去messsage,並使用同一個messageId,把隊列中所有的消息合併爲一條Transaction消息。
		while (max >= 0) {
			MessageTree tree = trees.poll();//接着 從隊列頭部移除

			if (tree == null) {
                tran.setDurationInMillis(lastTimestamp - tran.getTimestamp() + lastDuration);
				break;
			}
            lastTimestamp = tree.getMessage().getTimestamp();
            if(tree.getMessage() instanceof DefaultTransaction){
                lastDuration = ((DefaultTransaction) tree.getMessage()).getDurationInMillis();
            } else {
                lastDuration = 0;
            }
			tran.addChild(tree.getMessage());
			m_factory.reuse(tree.getMessageId());
			max--;
		}
		((DefaultMessageTree) first).setMessage(tran);
		return first;
	} 

爲什麼要使用TCP協議

從上邊的代碼可以看到,CAT使用了TCP協議上報消息(引入了netty框架)。那麼爲什麼不適用http協議上報呢?

選擇TCP的理由:對於客戶端的數據採集儘量降低性能損耗,TCP協議比HTTP協議更加輕量級(比如TCP不需要header等額外的損耗),在高qps的場景下具備明顯的性能優勢。另外,CAT的設計也不需要保留一個
Http鏈接供外部調用,這樣的埋點方式效率低下,並不考慮。

TcpSocketSender-ChannelManager 後臺線程

這個線程是通過服務端配置的路由ip,10s輪詢一次,當滿足自旋n(n=m_count%30)次,去檢查路由服務端ip是否變動,並保證連接正常。

典型的拉取配置信息機制。

	@Override
	public void run() {
        while (m_active) {
            // make save message id index asyc
            m_idfactory.saveMark();
            checkServerChanged();// 每100s檢查連接信息(shouldCheckServerConfig),並進行連接,使用TCP協議建立長連接

            ChannelFuture activeFuture = m_activeChannelHolder.getActiveFuture();//根據服務端配置的路由,獲取其中一個服務端ip並建立連接.
            try {
            } catch (Exception e) {
                e.printStackTrace();
            }
            List<InetSocketAddress> serverAddresses = m_activeChannelHolder.getServerAddresses();

            doubleCheckActiveServer(activeFuture);//檢查當前連接是否正常
            reconnectDefaultServer(activeFuture, serverAddresses);//如果不正常,則繼續嘗試建立其他連接。當所有default-server ip都無法連接時,默認會走backServer的Ip進行連接。

             try {
            Thread.sleep(10 * 1000L); // check every 10 seconds
             } catch (InterruptedException e) {
             }
        }
	}
....

	private void checkServerChanged() {
		if (shouldCheckServerConfig(++m_count)) {//每遍歷監聽n(n=m_count%30)次或者沒有成功的連接,則檢查連接信息
			Pair<Boolean, String> pair = routerConfigChanged();

			if (pair.getKey()) {
				String servers = pair.getValue();
				List<InetSocketAddress> serverAddresses = parseSocketAddress(servers);
				ChannelHolder newHolder = initChannel(serverAddresses, servers);//建立連接

				if (newHolder != null) {
					if (newHolder.isConnectChanged()) {
						ChannelHolder last = m_activeChannelHolder;

						m_activeChannelHolder = newHolder;
						closeChannelHolder(last);
						m_logger.info("switch active channel to " + m_activeChannelHolder);
					} else {
						m_activeChannelHolder = newHolder;
					}
				}
			}
		}
	}

private ChannelHolder initChannel(List<InetSocketAddress> addresses, String serverConfig) {
		try {
			int len = addresses.size();

			for (int i = 0; i < len; i++) {//遍歷,連接成功返回
				InetSocketAddress address = addresses.get(i);
				String hostAddress = address.getAddress().getHostAddress();
				ChannelHolder holder = null;

				if (m_activeChannelHolder != null && hostAddress.equals(m_activeChannelHolder.getIp())) {//當前的鏈接ip和address一致,那麼就複用,否則新建立連接。(稍後關閉之前過期的連接。)
					holder = new ChannelHolder();
					holder.setActiveFuture(m_activeChannelHolder.getActiveFuture()).setConnectChanged(false);
				} else {
					ChannelFuture future = createChannel(address);

					if (future != null) {
						holder = new ChannelHolder();
						holder.setActiveFuture(future).setConnectChanged(true);//true表示需要關閉之前的鏈接
					}
				}
				if (holder != null) {
					holder.setActiveIndex(i).setIp(hostAddress);
					holder.setActiveServerConfig(serverConfig).setServerAddresses(addresses);

					m_logger.info("success when init CAT server, new active holder" + holder.toString());
					return holder;
				}
			}
		} catch (Exception e) {
			m_logger.error(e.getMessage(), e);
		}

		try {
			StringBuilder sb = new StringBuilder();

			for (InetSocketAddress address : addresses) {
				sb.append(address.toString()).append(";");
			}
			m_logger.info("Error when init CAT server " + sb.toString());
		} catch (Exception e) {
			// ignore
		}
		return null;
	}

private boolean shouldCheckServerConfig(int count) {
		int duration = 30;
//m_activeChannelHolder.getActiveIndex() == -1表示關閉了當前連接
		if (count % duration == 0 || m_activeChannelHolder.getActiveIndex() == -1) {
			return true;
		} else {
			return false;
		}
	}

private Pair<Boolean, String> routerConfigChanged() {
		String current = loadServerConfig();//獲取當前路由表中的服務地址信息。示例:ip1:2280;ip2:2280...;

//current不爲空 && 路由表中的配置沒有任何變化
		if (!StringUtils.isEmpty(current) && !current.equals(m_activeChannelHolder.getActiveServerConfig())) {
		
			return new Pair<Boolean, String>(true, current);
		} else {
			return new Pair<Boolean, String>(false, current);
		}
	}
	
private String loadServerConfig() {
		try {
		//使用http請求獲取路由表配置信息
		//示例url:http://ip:port/cat/s/router?domain=someDomain&ip=當前客戶端ip&op=json
		//返回的content :{"kvs":{"routers":"ip1:2280;ip2:2280;..;","sample":"1.0"}}
		
			String url = m_configManager.getServerConfigUrl();
			InputStream inputstream = Urls.forIO().readTimeout(2000).connectTimeout(1000).openStream(url);
			String content = Files.forIO().readFrom(inputstream, "utf-8");

			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;
	}

StatusUpdateTask 後臺線程

這個線程很簡單,類似傳統的agent,每分鐘上報關於應用的各種信息(OS、MXBean信息等等)。而且,在每次線程啓動時上報一個Reboot消息表示重啓動。

MessageId的設計

CAT消息的Message-ID格式applicationName-0a010680-375030-2,CAT消息一共分爲四段:
第一段是應用名applicationName。
第二段是當前這臺機器的IP的16進制格式:

if (m_ipAddress == null) {
			String ip = NetworkInterfaceManager.INSTANCE.getLocalHostAddress();
			List<String> items = Splitters.by(".").noEmptyItem().split(ip);
			byte[] bytes = new byte[4];

			for (int i = 0; i < 4; i++) {
				bytes[i] = (byte) Integer.parseInt(items.get(i));
			}

			StringBuilder sb = new StringBuilder(bytes.length / 2);

			for (byte b : bytes) {
			    //1.一個byte 8位
			    //2.先獲取高4位的16進制字符
			    //3.在獲取低4位的16進制數		    
				sb.append(Integer.toHexString((b >> 4) & 0x0F));//通常使用0x0f來與一個整數進行&運算,來獲取該整數的最低4個bit位
				sb.append(Integer.toHexString(b & 0x0F));
			}

			m_ipAddress = sb.toString();

第三段的375030,是系統當前時間除以小時得到的整點數。
第四段的2,是表示當前這個客戶端在當前小時的順序遞增號(AtomicInteger自增,每小時結束後重置)。

 public String getNextId() {
        String id = m_reusedIds.poll();

        if (id != null) {
            return id;
        } else {
            long timestamp = getTimestamp();

            if (timestamp != m_timestamp) {
                m_index = new AtomicInteger(0);
                m_timestamp = timestamp;
            }

            int index = m_index.getAndIncrement();

            StringBuilder sb = new StringBuilder(m_domain.length() + 32);

            sb.append(m_domain);
            sb.append('-');
            sb.append(m_ipAddress);
            sb.append('-');
            sb.append(timestamp);
            sb.append('-');
            sb.append(index);

            return sb.toString();
        }

總之,同一個小時內、同一個domain、同一個ip , messageId的唯一性需要 AtomicInteger保證。

相關推薦:分佈式監控CAT源碼解析——Server

發佈了126 篇原創文章 · 獲贊 196 · 訪問量 26萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章