深入理解監控系統——CAT Server端源碼解析(初始化啓動)

相關文章:

[分佈式監控CAT] Client端源碼解析
[分佈式監控CAT] Server端源碼解析——消息消費\報表處理

前言

本文主要講解CAT-Server(包括Cat-home\Cat-consumer等模塊),從Cat-Server端的概述到具體初始化源碼分析,後續文章會繼續從源碼入手進行分析。

Server端

(Cat-consumer 用於實時分析從客戶端提供的數據\Cat-home 作爲用戶給用戶提供展示的控制端
,並且Cat-home做展示時,通過對Cat-Consumer的調用獲取其他節點的數據,將所有數據彙總展示)

consumer、home以及路由中心都是部署在一起的,每個服務端節點都可以充當任何一個角色

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

相關文章:
[分佈式監控CAT] Server端源碼解析——初始化
[分佈式監控CAT] Client端源碼解析
[分佈式監控CAT] Server端源碼解析——消息消費\報表處理

Server端概述

(Cat-consumer 用於實時分析從客戶端提供的數據\Cat-home 作爲用戶給用戶提供展示的控制端
,並且Cat-home做展示時,通過對Cat-Consumer的調用獲取其他節點的數據,將所有數據彙總展示)

consumer、home以及路由中心都是部署在一起的,每個服務端節點都可以充當任何一個角色

這裏寫圖片描述

CAT服務端在整個實時處理中,基本上實現了全異步化處理:

  • 消息消費基於Netty的NIO實現(Netty-Server)。
  • 消息消費到服務端就存放內存隊列,然後程序開啓一個線程會消費這個消息做消息分發(異步消費處理)。
  • 每個消息都會有一批線程併發消費各自隊列的數據,以做到消息處理的隔離。(每報表每線程,分別按照自己的規則解析消費這個消息,並且可以動態控制對某種報表類型的處理線程個數)
  • 消息(原始的消息logView)存儲是先存入本地磁盤,然後異步上傳到HDFS文件,這也避免了強依賴HDFS。

服務端初始化

Servlet容器加載、啓動

CAT目前是使用war包放入Servlet容器(如:tomcat或者jetty,以下假設使用tomcat容器)中的方式部署啓動。
熟悉servlet容器的同學應該知道,容器啓動時會讀取每個Context(可理解爲web工程)中的web.xml然後啓動Servlet等其他組件。

在cat-home模塊中的web.xml中可以看到,除了容器默認的Servlet之外,tomcat啓動時會啓動CatServlet、MVC這兩個Servlet(因爲load-on-startup>0,也就是會調用init方法初始化):

<web-app>

<filter>...</filter>

<servlet>
		<servlet-name>cat-servlet</servlet-name>
		<servlet-class>com.dianping.cat.servlet.CatServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet>
		<servlet-name>mvc-servlet</servlet-name>
		<servlet-class>org.unidal.web.MVC</servlet-class>
		<init-param>
			<param-name>cat-client-xml</param-name>
			<param-value>client.xml</param-value>
		</init-param>
		<init-param>
			<param-name>init-modules</param-name>
			<param-value>false</param-value>
		</init-param>
		<load-on-startup>2</load-on-startup>
	</servlet>

<filter-mapping>...</filter-mapping>
<servlet-mapping>...</servlet-mapping>
<jsp-config>...</jsp-config>

</web-app>

com.dianping.cat.servlet.CatServlet

按照web.xml中Servlet的加載順序CatServlet會優先於MVC完成初始化。
CatServlet的邏輯基本可以概括爲如下兩條線:

CatServlet.init——>CatServlet.initComponents——>DefaultModuleInitializer.execute(...) 
			——>com.dianping.cat.CatHomeModule.setup(ModuleContext ctx)
				——>TCPSocketReceiver(netty服務器)

CatServlet.init——>CatServlet.initComponents——>DefaultModuleInitializer.execute(...) 
		——>com.dianping.cat.***Module.execute(ModuleContext ctx)(完成各個模塊的初始化)

com.dianping.cat.servlet.CatServlet.init(ServletConfig servletConfig)

public void init(ServletConfig config) throws ServletException {
		super.init(config);

		try {//1.plexus IOC容器初始化(根據components.xml的設定完成IOC初始化)
			if (m_container == null) {
				m_container = ContainerLoader.getDefaultContainer();
			}
			//2.用來打印日誌的m_logger對象實例化(根據plexus.xml設定完成實例化)
			m_logger = ((DefaultPlexusContainer) m_container).getLoggerManager().getLoggerForComponent(
			      getClass().getName());
			//3.初始化CAT-Server必備的組件模塊:cat-home\cat-consumer\cat-core
			initComponents(config);
		} catch (Exception e) {
			if (m_logger != null) {
				m_logger.error("Servlet initializing failed. " + e, e);
			} else {
				System.out.println("Servlet initializing failed. " + e);
				e.printStackTrace(System.out);
			}

			throw new ServletException("Servlet initializing failed. " + e, e);
		}
	}

進入initComponents(config); 我們繼續看下爲了啓動server服務,各個cat-*模塊如何初始化:

com.dianping.cat.servlet.CatServlet.initComponents(ServletConfig servletConfig)

	@Override
	protected void initComponents(ServletConfig servletConfig) throws ServletException {
		try {
		//ModuleContext ctx這個對象裏主要作用:
		//1.持有 plexus IOC 容器的引用;
		//2.持有 logger對象引用,用來打日誌。
		//3.持有 需要使用到的配置文件路徑。
		//比如:cat-server-config-file=\data\appdatas\cat\server.xml 
		//cat-client-config-file=\data\appdatas\cat\client.xml

			ModuleContext ctx = new DefaultModuleContext(getContainer());
			ModuleInitializer initializer = ctx.lookup(ModuleInitializer.class);
			File clientXmlFile = getConfigFile(servletConfig, "cat-client-xml", "client.xml");
			File serverXmlFile = getConfigFile(servletConfig, "cat-server-xml", "server.xml");

			ctx.setAttribute("cat-client-config-file", clientXmlFile);
			ctx.setAttribute("cat-server-config-file", serverXmlFile);
			//通過查找啓動cat-home必要的模塊,然後依次初始化各個模塊。
			initializer.execute(ctx);
		} catch (Exception e) {
			m_exception = e;
			System.err.println(e);
			throw new ServletException(e);
		}
	}

org.unidal.initialization.DefaultModuleInitializer.execute(…). 執行各個模塊的初始化

   @Override
   public void execute(ModuleContext ctx) {
   
   //我們的topLevelModule是cat-home模塊,通過這個模塊去查找需要依賴的其他模塊並初始化他們。
      Module[] modules = m_manager.getTopLevelModules();
      execute(ctx, modules);
      }


   @Override
   public void execute(ModuleContext ctx, Module... modules) {
      Set<Module> all = new LinkedHashSet<Module>();

      info(ctx, "Initializing top level modules:");

      for (Module module : modules) {
         info(ctx, "   " + module.getClass().getName());
      }

      try {
      //1.根據頂層Module獲取到下層所有依賴到的modules,並分別調用他們的setup方法
         expandAll(ctx, modules, all);
      //2.依次調用module實現類的execute方法
         for (Module module : all) {
            if (!module.isInitialized()) {
               executeModule(ctx, module, m_index++);
            }
         }
      } catch (Exception e) {
         throw new RuntimeException("Error when initializing modules! Exception: " + e, e);
      }
   }

   private void expandAll(ModuleContext ctx, Module[] modules, Set<Module> all) throws Exception {
      if (modules != null) {
         for (Module module : modules) {
            expandAll(ctx, module.getDependencies(ctx), all);

            if (!all.contains(module)) {
               if (module instanceof AbstractModule) {
                  ((AbstractModule) module).setup(ctx);//調用各個module實現類的setup
               }
	//all 最終元素以及順序:
	//CatClientModule\CatCoreModule\CatConsumerModule\CatHomeModule

               all.add(module);
            }
         }
      }
   }

我們看到cat-home模塊是一個頂層模塊,接着根據這個模塊找到其他依賴模塊
(CatClientModule\CatConsumerModule\CatCoreModule),並且依次調用setup方法,解析依次調用模塊的execute方法完成初始化。

Modules之間的設計使用了典型的模板模式。
這裏寫圖片描述.

模塊依賴關係:
null<——CatClientModule<——CatClientModule<——CatCoreModule<——CatConsumerModule<——CatHomeModule

接着着重看一下子類 CatHomeModule的setup的實現。注意除了這個子類,Module的子類steup()方法爲空
com.dianping.cat.CatHomeModule.setup(ModuleContext ctx)

@Override
	protected void setup(ModuleContext ctx) throws Exception {
		File serverConfigFile = ctx.getAttribute("cat-server-config-file");//獲取server.xml文件的路徑
		//通過 plexus IOC 初始化一個 ServerConfigManager bean
		ServerConfigManager serverConfigManager = ctx.lookup(ServerConfigManager.class);
		//通過 plexus IOC 初始化一個 TcpSocketReceiver bean
		final TcpSocketReceiver messageReceiver = ctx.lookup(TcpSocketReceiver.class);
		//加載\...\server.xml中的配置
		serverConfigManager.initialize(serverConfigFile);
		//啓動TCPSocketReceiver,就是一個典型的 netty 事件驅動服務器,用來接收客戶端的TCP長連接請求
		messageReceiver.init();
        //增加一個進程觀察者,在這個JVM關閉時回調
		Runtime.getRuntime().addShutdownHook(new Thread() {

			@Override
			public void run() {
				messageReceiver.destory();
			}
		});
	}

各個模塊的啓動,executeModule
各個模塊setup就說到這裏,setup完成後,會依次調用module.execute(…)用來完成各個模塊的啓動。

依次調用:
CatClientModule\CatCoreModule\CatConsumerModule\CatHomeModule.其中只有CatClientModule、CatHomeModule實現了有效的execute方法。

com.dianping.cat.CatClientModule.execute(ModuleContext ctx)
注意:這裏的客戶端是用來監控服務端的,具體client的解析可以參考:CAT客戶端解析

@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
        // Threads用來對線程做管理的類。這裏默認給每個新建的線程加上監聽器或者說是觀察者
		Threads.addListener(new CatThreadListener(ctx));

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

		// bring up TransportManager:實例化這個類
		ctx.lookup(TransportManager.class);
		
        //ClientConfigManager對象是加載了client.xml的客戶端配置管理對象。
        //客戶端的解析不進行展開,請看之前寫的《分佈式監控CAT源碼解析——cat-client》
		ClientConfigManager clientConfigManager = ctx.lookup(ClientConfigManager.class);
		if (clientConfigManager.isCatEnabled()) {
			StatusUpdateTask statusUpdateTask = ctx.lookup(StatusUpdateTask.class);
			Threads.forGroup("cat").start(statusUpdateTask);
			LockSupport.parkNanos(10 * 1000 * 1000L); // wait 10 ms

		}
	}

com.dianping.cat.CatHomeModule.execute(ModuleContext ctx)
CatHomeModule涉及很多可說的,此處暫時不做展開,繼續按照Servlet啓動的流程講解。

	@Override
	protected void execute(ModuleContext ctx) throws Exception {
		ServerConfigManager serverConfigManager = ctx.lookup(ServerConfigManager.class);
		//初始化MessageConsumer子類RealtimeConsumer,不僅實例化這個類MessageConsumer對象,還會把這個類中的成員全部實例化
		//		<plexus>
		//	    <components>
		//	        <component>
		//	            <role>com.dianping.cat.analysis.MessageConsumer</role>
		//	            <implementation>com.dianping.cat.analysis.RealtimeConsumer</implementation>
		//	            <requirements>
		//	                <requirement>
		//	                    <role>com.dianping.cat.analysis.MessageAnalyzerManager</role>
		//	                </requirement>
		//	                <requirement>
		//	                    <role>com.dianping.cat.statistic.ServerStatisticManager</role>
		//	                </requirement>
		//	                <requirement>
		//	                    <role>com.dianping.cat.config.server.BlackListManager</role>
		//	                </requirement>
		//	            </requirements>
		//	        </component>

		ctx.lookup(MessageConsumer.class);

		ConfigReloadTask configReloadTask = ctx.lookup(ConfigReloadTask.class);
		Threads.forGroup("cat").start(configReloadTask);

		if (serverConfigManager.isJobMachine()) {
			DefaultTaskConsumer taskConsumer = ctx.lookup(DefaultTaskConsumer.class);

			Threads.forGroup("cat").start(taskConsumer);
		}

		if (serverConfigManager.isAlertMachine()) {//如果當前結點開啓了告警功能,則對每種報表啓動一個daemon線程。1分鐘檢查一次
			BusinessAlert metricAlert = ctx.lookup(BusinessAlert.class);
			NetworkAlert networkAlert = ctx.lookup(NetworkAlert.class);
			DatabaseAlert databaseAlert = ctx.lookup(DatabaseAlert.class);
			SystemAlert systemAlert = ctx.lookup(SystemAlert.class);
			ExceptionAlert exceptionAlert = ctx.lookup(ExceptionAlert.class);
			FrontEndExceptionAlert frontEndExceptionAlert = ctx.lookup(FrontEndExceptionAlert.class);
			HeartbeatAlert heartbeatAlert = ctx.lookup(HeartbeatAlert.class);
			ThirdPartyAlert thirdPartyAlert = ctx.lookup(ThirdPartyAlert.class);
			ThirdPartyAlertBuilder alertBuildingTask = ctx.lookup(ThirdPartyAlertBuilder.class);
			AppAlert appAlert = ctx.lookup(AppAlert.class);
			WebAlert webAlert = ctx.lookup(WebAlert.class);
			TransactionAlert transactionAlert = ctx.lookup(TransactionAlert.class);
			EventAlert eventAlert = ctx.lookup(EventAlert.class);
			StorageSQLAlert storageDatabaseAlert = ctx.lookup(StorageSQLAlert.class);
			StorageCacheAlert storageCacheAlert = ctx.lookup(StorageCacheAlert.class);

			Threads.forGroup("cat").start(networkAlert);
			Threads.forGroup("cat").start(databaseAlert);
			Threads.forGroup("cat").start(systemAlert);
			Threads.forGroup("cat").start(metricAlert);
			Threads.forGroup("cat").start(exceptionAlert);
			Threads.forGroup("cat").start(frontEndExceptionAlert);
			Threads.forGroup("cat").start(heartbeatAlert);
			Threads.forGroup("cat").start(thirdPartyAlert);
			Threads.forGroup("cat").start(alertBuildingTask);
			Threads.forGroup("cat").start(appAlert);
			Threads.forGroup("cat").start(webAlert);
			Threads.forGroup("cat").start(transactionAlert);
			Threads.forGroup("cat").start(eventAlert);
			Threads.forGroup("cat").start(storageDatabaseAlert);
			Threads.forGroup("cat").start(storageCacheAlert);
		}

		final MessageConsumer consumer = ctx.lookup(MessageConsumer.class);
		Runtime.getRuntime().addShutdownHook(new Thread() {

			@Override
			public void run() {
				consumer.doCheckpoint();
			}
		});
	}

至此,CatServlet初始化完成了,接下來會初始化org.unidal.web.MVC這個Servlet。
我們接着看一下另外一個Servlet:mvc-servlet

org.unidal.web.MVC

MVC這個Servlet繼承了AbstractContainerServlet,與CatServlet非常類似,均是AbstractContainerServlet 的實現類。這個Servlet顧名思義就是用來處理請求的,類似Spring中的DispatcherServlet,集中分配進入的請求到對應的Controller。

public void init(ServletConfig config) throws ServletException {…}
與CatServelet一樣,均繼承自父類:

public void init(ServletConfig config) throws ServletException {
		super.init(config);

		try {
			if (m_container == null) {
			//DefaultPlexusContainer m_container 是單例對象,在CATServlet中已經完成初始化了
				m_container = ContainerLoader.getDefaultContainer();
			}

			m_logger = ((DefaultPlexusContainer) m_container).getLoggerManager().getLoggerForComponent(
			      getClass().getName());

			initComponents(config);
		} ......

org.unidal.web.MVC.initComponents(ServletConfig config) throws Exception

  @Override
   protected void initComponents(ServletConfig config) throws Exception {
	  // /cat
      String contextPath = config.getServletContext().getContextPath();
      // /cat
      String path = contextPath == null || contextPath.length() == 0 ? "/" : contextPath;

      getLogger().info("MVC is starting at " + path);
//使用client.xml初始化代表CATClient的com.dianping.cat.Cat對象(如果CAT未被初始化)。
      initializeCat(config);
      initializeModules(config);

      m_handler = lookup(RequestLifecycle.class, "mvc");
      m_handler.setServletContext(config.getServletContext());

      config.getServletContext().setAttribute(ID, this);
      getLogger().info("MVC started at " + path);
   }

至此,容器啓動成功,http://localhost:2281/cat/r 進入頁面。

接下來,我們詳細分解CAT-Server的核心功能實現,請看下一篇。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章