CAT服务端初始化

Posted yx88

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CAT服务端初始化相关的知识,希望对你有一定的参考价值。

Cat模块

技术图片


 

Cat-client : cat客户端,编译后生成 cat-client-2.0.0.jar ,用户可以通过它来向cat-home上报统一格式的日志信息,可以集成到 mybatis、spring、微服务 dubbo 的监控等等流行框架。 

Cat-consumer: 用于实时分析从客户端提供的数据。在实际开发和部署中,Cat-consumer和Cat-home是部署在一个JVM内部,每个CAT服务端都可以作为consumer也可以作为home,这样既能减少整个层级结构,也可以增加系统稳定性。

Cat-core:Cat核心模块

Cat-hadoop : 大数据统计依赖模块。

cat-home:大众点评CAT服务器端主程序,编译安装之后生成 cat-alpha-3.0.0.war 包部署于servlet容器中,我们用的是Tomcat,war包依赖cat-client.jar、cat-consumer.jar, cat-core.jar, cat-hadoop.jar 包,通过web.xml 配置,看到Cat会启动 cat-servlet 和 mvc-servlet , mvc-servlet 是一个类似 spring MVC 的框架,用于处理用户WEB管理平台请求。cat-servlet是CAT服务端监听入口,CAT会在这里开启监听端口,接收处理客户端的日志记录请求,本章主要介绍cat-servlet。

技术图片
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    version="2.5">
...
    <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>
....
 
View Code

 

Cat-servlet初始化

技术图片

                                                                        图1 - 容器初始化类图

    CatServlet 首先会调用父类 AbstractContainerServlet 的init方法做初始化工作, 可以认为这是CatServlet的入口,他主要做了3件事情,首先调用基类HttpServlet的init方法对Servlet进行初始化,然后初始化Plexus容器,最后调用子类initComponents初始化Module模块。

技术图片
public abstract class AbstractContainerServlet extends HttpServlet 
    public void init(ServletConfig config) throws ServletException 
        super.init(config);
 
        try 
            if(this.m_container == null) 
                this.m_container = ContainerLoader.getDefaultContainer();
            
 
            this.m_logger = this.m_container.getLogger();
            this.initComponents(config);
         catch (Exception var3) 
            ...
        
    
View Code

 


 plexus - IOC容器
 上面讲到init(...)方法在初始化完Servlet之后调用 ContainerLoader.getDefaultContainer() 初始化plexus容器。

    注:这里可能大家不太了解plexus,它相当于Spring的IoC容器,但是它和Spring框架不同,它并不是一个完整的,拥有各种组件的大型框架,仅仅是一个纯粹的IoC容器,它的开发者与Maven的开发者是同一拨人,最初开发Maven的时候,Spring并不成熟,所以Maven的开发者决定使用自己维护的IoC容器Plexus,它与Spring在语法和描述方式稍有不同。在Plexus中,有ROLE的概念,相当于Spring中的一个Bean。支持组件生命周期管理。

   非JAVA开发者不懂IOC容器?简单来说,IOC容器就相当于一个对象装载器,对象不是由程序员new创建,而是框架在初始化的时候从配置文件中读取需要实例化的类信息,将信息装入一个对象装载器,然后在需要的时候,从对象装载器中找是否存在该类的信息,存在则返回类的对象。

        plexus容器是如何工作的呢?就上面的类图来说,

    a. AbstractContainerServlet 通过容器工厂ContainerLoader 的 getDefaultContainer方法,该方法会创建 MyPlexusContainer 容器,MyPlexusContainer是接口 PlexusContainer 的实现,MyPlexusContainer在构造函数中会创建组件管理器(ComponentManager),可以认为每个类都是容器中的一个组件,ComponentManager就是用来管理这些组件的,包括他的生命周期,组件在Plexus容器配置文件中配置。

     b.组件管理器(ComponentManager)会创建组件模型管理器(ComponentModelManager)以及组件生命周期管理器(ComponentLifecycle),ComponentModelManager用于存储Plexus容器配置文件中的所有component组件信息,它的loadComponentsFromClasspath()方法会扫描各个jar包中存在的plexus容器配置文件,如图2,将xml内容解析之后放入PlexusModel 列表中。

技术图片
public class ComponentManager 
    private Map<String, ComponentBox<?>> m_components = new HashMap();
    private PlexusContainer m_container;
    private ComponentLifecycle m_lifecycle;
    private ComponentModelManager m_modelManager;
    private LoggerManager m_loggerManager;
    
    public ComponentManager(PlexusContainer container, InputStream in) throws Exception 
        this.m_container = container;
        this.m_modelManager = new ComponentModelManager();
        this.m_lifecycle = new ComponentLifecycle(this);
        if(in != null) 
            this.m_modelManager.loadComponents(in);
        
 
        this.m_modelManager.loadComponentsFromClasspath();
        this.m_loggerManager = (LoggerManager)this.lookup(new ComponentKey(LoggerManager.class, (String)null));
        this.register(new ComponentKey(PlexusContainer.class, (String)null), container);
        this.register(new ComponentKey(Logger.class, (String)null), this.m_loggerManager.getLoggerForComponent(""));
    
View Code

 


    我们也可以将我们自己写的类交给容器管理,只需要将类配置到容器配置文件中,例如:cat-consumer/src/main/resources/META-INF/plexus/components-cat-consumer.xml, 只要是存在于 META-INF/plexus/ 目录下,并且文件名以"components-" 开头的 ".xml" 文件,都会被 ComponentModelManager 认为是容器配置文件。

 技术图片

                          图2 - plexus IOC容器类配置文件

 

       c.然后就可以通过lookup方法找到类,并在首次使用的时候实例化,并且xml配置中的该类依赖的其它类也会被一并实例化,另外如果类方法实现了 Initializable 接口,创建对象后会执行类的 initialize() 方法做一些初始化的工作。

技术图片
if(component instanceof Initializable) 
    try 
        ((Initializable)component).initialize();
     catch (Throwable var5) 
        ComponentModel model = ctx.getComponentModel();
        throw new ComponentLookupException("Error when initializing component!", model.getRole(), model.getHint(), var5);
    
View Code


 

模块的加载 - 模型模式
init(...)函数最后会调用CatServlet的initComponents()方法初始化Module模块。

 技术图片

                图3 - 模块初始化类图   

initComponents()方法首先创建一个模块上下文 DefaultModuleContext对象,该对象拥有plexus容器的指针,以及server.xml、client.xml配置文件信息 ,服务端配置server.xml中有消息存储路径、HDFS上传等一系列配置,由于cat-home默认是服务端也是客户端,也就是说cat-home自身也会被监控,所以我们在这里看到有client.xml配置,配置文件所在目录由环境变量CAT_HOME指定,如果未指定,默认是/data/appdatas/cat。

  随后CatServlet创建一个模块初始器 DefaultModuleInitializer,并调用他的execute(ctx)方法创建并初始化模块。

  注:DefaultModuleInitializer有一个模块管理器DefaultModelManager m_manager, 读者可能没有看见m_manager的创建过程,实际上,对象在components-foundation-service.xml配置文件中配置的,然后在plexus容器实例化类对象的过程中创建的,后面还有很多对象的属性也是通过plexus容器注入的。比如DefaultModuleManager的m_topLevelModules属性通过以下配置注入。

技术图片
<component>
    <role>org.unidal.initialization.ModuleManager</role>
    <implementation>org.unidal.initialization.DefaultModuleManager</implementation>
    <configuration>
        <topLevelModules>cat-home</topLevelModules>
    </configuration>
</component>
View Code

 


    上面XML配置显示m_topLevelModules 指定为cat-home,这样DefaultModuleInitializer通过DefaultModelManager的getTopLevelModules()方法获取的就是CatHomeModule模块对象,可以认为cat-home是一个顶层模块,所有Module都包含getDependencies方法,该方法会找到当前模块所依赖的其他模块,并实例化模块,比如下面cat-home就依赖cat-consumer模块,

技术图片
public class CatHomeModule extends AbstractModule 
    @Override
    public Module[] getDependencies(ModuleContext ctx) 
        return ctx.getModules(CatConsumerModule.ID);
    
View Code

 


从cat-consumer的getDependencies看出他依赖cat-core模块,cat-core模块又依赖cat-client模块,这样子我们就从顶层模块引出了所有依赖的其它模块,在实例化模块的同时调用模块的setup方法安装模块。在所有模块安装完成之后,依次调用模块的execute方法完成初始化,但是初始化顺序则是按照安装顺序反着来的,cat-client -> cat-core -> cat-consumer -> cat-home ,Modules之间的设计使用了典型的模板模式。

 

cat-home的setup
在上一章讲到模块初始化的时候, 讲到setup安装cat-home模块,对于客户端的请求的监听处理,就是在这里完成的。

技术图片
@Named(type = Module.class, value = CatHomeModule.ID)
public class CatHomeModule extends AbstractModule 
    @Override
    protected void setup(ModuleContext ctx) throws Exception 
        if (!isInitialized()) 
            File serverConfigFile = ctx.getAttribute("cat-server-config-file");
            ServerConfigManager serverConfigManager = ctx.lookup(ServerConfigManager.class);
            final TcpSocketReceiver messageReceiver = ctx.lookup(TcpSocketReceiver.class);
 
            serverConfigManager.initialize(serverConfigFile);
            messageReceiver.init();
 
            Runtime.getRuntime().addShutdownHook(new Thread() 
                @Override
                public void run() 
                    messageReceiver.destory();
                
            );
        
    

 
View Code

 


 

1、读取 server.xml 配置,装进配置管理器(ServerConfigManager)。

 

2、创建消息接收器 final TcpSocketReceiver messageReceiver;

3、messageReceiver.init() 初始化服务,采用的经典的 netty reactor 模型。

4、注册一个JVM关闭的钩子,在进程挂掉的时候,执行一些清理现场的代码。

 

TcpSocketReceiver--- netty reactor 模式的应用
我们来看看CatHomeModule对TcpSocketReceiver的初始化做了什么,如下源码:

技术图片
public final class TcpSocketReceiver implements LogEnabled 
    public void init() 
        try 
            startServer(m_port);
         catch (Throwable e) 
            m_logger.error(e.getMessage(), e);
        
    
    
    public synchronized void startServer(int port) throws InterruptedException 
        boolean linux = getOSMatches("Linux") || getOSMatches("LINUX");
        int threads = 24;
        ServerBootstrap bootstrap = new ServerBootstrap();
 
        m_bossGroup = linux ? new EpollEventLoopGroup(threads) : new NioEventLoopGroup(threads);
        m_workerGroup = linux ? new EpollEventLoopGroup(threads) : new NioEventLoopGroup(threads);
        bootstrap.group(m_bossGroup, m_workerGroup);
        bootstrap.channel(linux ? EpollServerSocketChannel.class : NioserverSocketChannel.class);
 
        bootstrap.childHandler(new ChannelInitializer<SocketChannel>() 
            @Override
            protected void initChannel(SocketChannel ch) throws Exception 
                ChannelPipeline pipeline = ch.pipeline();
 
                pipeline.addLast("decode", new MessageDecoder());
            
        );
 
        bootstrap.childOption(ChannelOption.SO_REUSEADDR, true);
        bootstrap.childOption(ChannelOption.TCP_NODELAY, true);
        bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
        bootstrap.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
 
        try 
            m_future = bootstrap.bind(port).sync();
            m_logger.info("start netty server!");
         catch (Exception e) 
            m_logger.error("Started Netty Server Failed:" + port, e);
        
    
View Code

 


1、创建EventLoopGroup对象, EventLoopGroup是用来处理IO操作的多线程事件循环器,m_bossGroup作为一个acceptor负责接收来自客户端的请求,然后分发给m_workerGroup用来所有的事件event和channel的IO。

2、创建ServerBootstrap对象,ServerBootstrap 是一个启动Epoll(非Linux为NIO)服务的辅助启动类,他将设置bossGroup和workerGroup两个多线程时间循环器。

3、接下来的channel()方法设置了ServerBootstrap 的 ChannelFactory,这里传入的参数是EpollServerSocketChannel.class (非Linux为NioServerSocketChannel.class),也就是说这个ChannelFactory创建的就是EpollServerSocketChannel/NioServerSocketChannel的实例。

    Channel是Netty的核心概念之一,它是Netty网络通信的主体,他从EventLoopGroup获得一个EventLoop,并注册到该EventLoop,channel生命周期内都和该EventLoop在一起,由它负责对网络通信连接的打开、关闭、连接和读写操作。如果是对于读写事件,执行线程调度pipeline来处理用户业务逻辑。

4、接下来bootstrap.childHandler的目的是添加一个handler,用来监听已经连接的客户端的Channel的动作和状态,传入的 ChannelInitializer重写了initChannel方法,这个方法在Channel被注册到EventLoop的时候会被调用。

5、initChannel会创建ChannelPipeline对象,并调用addLast添加ChannelHandler。有网络请求时,ChannelPipeline会调用ChannelHandler来处理,有ChannelInboundHandler和ChannelOutboundHandler两种,ChannelPipeline会从头到尾顺序调用ChannelInboundHandler处理网络请求内容,从尾到头调用ChannelOutboundHandler处理网络请求内容。这也是Netty用来灵活处理网络请求的机制之一,因为使用的时候可以用多个decoder和encoder进行组合,从而适应不同的网络协议。而且这种类似分层的方式可以让每一个Handler专注于处理自己的任务而不用管上下游,这也是pipeline机制的特点。这跟TCP/IP协议中的五层和七层的分层机制有异曲同工之妙。

      在这里,ChannelPipeline添加的 ChannelHandler 是MessageDecoder ,MessageDecoder的祖先类实现了ChannelHandler接口,他本质上还是一个Handler,是网络IO事件具体处理类,当客户端将日志数据上传到服务器之后,会交给MessageDecoder 解码数据,然后进行后续处理。

6、调用 childOption 设置 channel 的参数。

7、最后调用bind()方法启动服务。 

关于netty ,我就讲到这里,网上关于netty框架的文章非常多,大家可以自行去查。

 技术图片

 

 

 

消息的解码

技术图片

 


    上一章我们讲到Netty将接收到的消息交给 MessageDecoder 去做解码,解码是交由PlainTextMessageCodec对象将接收到的字节码反序列化为MessageTree对象(所有的消息都是由消息树来组织),具体的解码逻辑在这里暂不做详细阐述,在第三章我们会阐述编码过程,解码只是编码的一个逆过程。

解码之后调用 DefaultMessageHandler 的 handle方法对消息进行处理,handle方法就干了一件事情,就是调用 m_consumer.consume(tree) 方法去消费消息树,在消费模块,CAT实现了队列化,异步化,在消息消费章节会详细阐述。

    当然netty handler也是支持异步处理的,我们也可以将 DefaultMessageHandler 像 MessageDecoder那样向netty注册handler, 再由netty来做线程池分发。

技术图片
public class MessageDecoder extends ByteToMessageDecoder 
    
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) throws Exception 
        if (buffer.readableBytes() < 4) 
            return;
        
        buffer.markReaderIndex();
        int length = buffer.readInt();
        
        ...
        
        ByteBuf readBytes = buffer.readBytes(length + 4);
        
        ...
        
        DefaultMessageTree tree = (DefaultMessageTree) m_codec.decode(readBytes);
        
        readBytes.resetReaderIndex();
        tree.setBuffer(readBytes);
        m_handler.handle(tree);
        m_processCount++;
        
        ...
    
View Code

 


---------------------
作者:曹号
来源:CSDN
原文:https://blog.csdn.net/caohao0591/article/details/80207771
版权声明:本文为博主原创文章,转载请附上博文链接!

以上是关于CAT服务端初始化的主要内容,如果未能解决你的问题,请参考以下文章

服务框架 Pigeon 的设计与实现

初始化服务器端处理数据表后如何调整列?

在 Laravel Inertia Vue 中初始化服务器端多列排序

服务端socket重用属性设置

禁用初始自动 ajax 调用 - DataTable 服务器端分页

Legend of Mir(传奇)官方源码学习3服务端程序初始化过程