相關文章:
[分佈式監控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的核心功能實現,請看下一篇。