离职

还有2个星期就要走了,在这个公司收获了很多。以后不知道自己会怎么样,但是在公司写代码还是挺开心的,不需要产品经理总是改需求。

一,工作

1,代码

有时候发现,写代码也不是那么枯燥无味。总比什么都不干坐在那等死的好。
13年4月22日入职到公司以来,已经过了2年半了。代码大概也写了这么长时间,很感激老大给了我这么一段安静的时光写代码,不需要为需求改来改去。

由于svn的地址更换,很早以前的代码记录找不到了,仅仅找到了14年3月到现在的提交记录。如下是代码统计:




2,项目

主要做了2个项目,综合数据平台和BI分析引擎。

二,生活

1,女友

没有女朋友,没有女朋友,没有女朋友,重要的事情说三遍。
找个女朋友太难了,本来在公司遇到一个自己喜欢的。无奈怎么说也不喜欢我,最后跟别人结婚了。有时候感觉好伤心好无助,为什么无论怎样都不对我看一眼。算了,不提了。

2,运动

计划的爬山也都没去,好想跟几个搭档一起去爬山,可是一直苦于没有机会。
现在基本上每个星期都会跑一下步,以前是在清华操场跑,8圈左右,大概2二点多公里。后面去了一次奥森,彻底被奥森折服了。于是经常去那跑,基本上都是一圈5公里的路程。

3,周末

周末没事做,有时候看看书,打打酱油,写写文章,就过去了。

三,nothing

列式压缩技术汇总

一,前言

孔子说,温故而知新,可以为师矣。好久没探讨列式数据库了,今天在此再次总结一遍,供以后查阅。本文介绍的内容如下:

二,存储算法

对于一列值,你认为会有哪些存储方式呢?下面我们来看看常用的存储算法:

2.1 dictionary(字典项编码)

这个最常用的,不说了,so easy。

2.2 bit Vector(比特向量)

适用于重复度比较高的。
比如一列 ABAABCCCAABB
对于字段值A,B,C分别用一个bit Vector来存储他们的位置。
A 101100001100
B 010010000011
C 000001110000

2.3 RLE(Run-length encoding,行程编码)

RLE适合用于对已排序好的数据进行编码。
RLE由一下3部分组成:

控制符+重复次数+被重复字符

例如,字符串 RTAAAASDEEEEE
经RLE压缩后为: RT4ASD5E

在这里,”4A” 代替了流”AAAA”,”5E” 代替”EEEEE”。其中,控制符采用特殊字符’*’ 指出一个RLE编码的开始,后面的数字表示重复的次数,数字后的单个字符是被重复的字符。显然,重复字符数为4或大于4,RLE编码效率才高,因为一个重复至少需要3个符号来表示。

2.4 LZ算法簇

LZ算法簇包括LZ77,LZW,LZ78,LZSS,他们都是以LZ77算法为基础的,我们重点介绍LZ77算法。

我们先看个例子: the brown fox jumped over the brown foxy jumping frog
这个短语的长度总共是53个八位组 = 424 bit。算法从左向右处理这个文本。初始时,每个字符被映射成9 bit的编码,二进制的1跟着该字符的8 bit ASCII码。在处理进行时,算法查找重复的序列。当碰到一个重复时,算法继续扫描直到该重复序列终止。换句话说,每次出现一个重复时,算法包括尽可能多的字符。碰到的第一个这样的序列是the brown fox。这个序列被替换成指向前一个序列的指针和序列的长度。在这种情况下,前一个序列的the brown fox出现在26个字符之前,序列的长度是13个字符。对于这个例子,假定存在两种编码选项:8 bit的指针和4 bit的长度,或者12 bit的指针和6 bit的长度。使用2 bit的首部来指示选择了哪种选项,00表示第一种选项,01表示第二种选项。因此,the brown fox的第二次出现被编码为 <00b><26d><13 d="">,或者00 00011010 1101。
压缩报文的剩余部分是字母y;序列<00b><27d><5 d="">替换了由一个空格跟着jump组成的序列,以及字符序列ing frog。
下图演示了压缩映射的过程。压缩过的报文由35个9 bit字符和两个编码组成,总长度为35 x 9 + 2 x 14 = 343比特。和原来未压缩的长度为424比特的报文相比,压缩比为1.24。

LZ77(及其变体)的压缩算法使用了两个缓存。滑动历史缓存包含了前面处理过的N个源字符,前向缓存包含了将要处理的下面L个字符。算法尝试将前向缓存开始的两个或多个字符与滑动历史缓存中的字符串相匹配。如果没有发现匹配,前向缓存的第一个字符作为9 bit的字符输出并且移入滑动窗口,滑动窗口中最久的字符被移出。如果找到匹配,算法继续扫描以找出最长的匹配。然后匹配字符串作为三元组输出(指示标记、指针和长度)。对于K个字符的字符串,滑动窗口中最久的K个字符被移出,并且被编码的K个字符被移入窗口。

2.5 哈弗曼编码

哈弗曼编码为前缀编码,即一个编码不是另一个编码的前缀。

2.6 trie

Trie数据结构一般也叫prefix trees, 一般用在数据类型为string并且排序之后有明显倾斜的数据分布的列,比如URL , 家庭住址, 这些字段的前缀经过排序之后在局部区域往往都有很高的压缩比,在最近的Hbase 里面也使用了这种方式压缩rowKey 的部分,Google PowerDrill也同时使用Trie Encoding压缩由”字典表”和”字典表所在位置”所组成的文件格式及其对应的内存数据结构.

三,数据库

对于列式数据库都有哪些,他们都有哪些优缺点,下面我们将介绍一下。

3.1 impala

impala是基于hadoop大数据的列式数据库。impala主要使用的列式存储是parquet。

impala的性能快的原因:

  • Impala不需要把中间结果写入磁盘,省掉了大量的I/O开销。
  • 省掉了MapReduce作业启动的开销。
  • Impala完全抛弃了MapReduce这个不太适合做SQL查询的范式,而是像Dremel一样借鉴了MPP并行数据库的思想另起炉灶,因此可做更多的查询优化,从而省掉不必要的shuffle、sort等开销。
  • 用C++实现,做了很多有针对性的硬件优化,例如使用SSE指令。
  • 使用了支持Data locality的I/O调度机制,尽可能地将数据和计算分配在同一台机器上进行,减少了网络开销。

3.2 hbase

hbase建立的hdfs之上,提供高可靠性、高性能、列存储、可伸缩、实时读写的数据库系统。

3.3 infobright

infobright是基于mysql的,它也是利用了MPP分布式架构。
infobright相比其他的数据库最大的特点就是知识网格的特性。
Knowledge Grid(知识网格)包含了Data Pack Node(知识节点),每个知识节点又对应于一个Data Pack(数据块)。
存储层最底层就是一个个的Data Pack(数据块)。每一个Pack装着某一列的64K个元素,所有数据按照这样的形式打包存储,每一个数据块进行类型相关的压缩(即根据不同数据类型采 用不同的压缩算法),压缩比很高。

3.4 greenplum

Greenplum是一种基于postgresql的分布式数据库,它也利用了MPP分布式架构。

3.5 dremel

Dremel 是Google 的“交互式”数据分析系统。可以组建成规模上千的集群,处理PB级别的数据。
Dremel的特点:

  • 按列存储的嵌套数据格式
  • 多级执行查询树

3.6 monetdb

monetdb暂时介绍的太少,没有商用。

四,其他

说了这么多,那么列式数据库的高效到底在哪里?为什么列存储会比行存储更高效呢?我们看看其原理:

4.1 原理

列式查询的原理就是延迟物化,直到最后才把值查出来。
假设我们用的数据字典压缩存储列。如果要查询符合条件的记录,我们可以按照以下方式:

  1. 去字典表里找到字符串对应数字。
  2. 用数字去列表里匹配,匹配上的位置设为1。
  3. 把不同列的匹配结果进行位运算得到符合所有条件的记录下标。
  4. 使用这个下标组装出最终的结果集。

4.2 特点

列式数据库优点:

  • 极高的装载速度(最高可以等于所有硬盘IO的总和,基本是极限了)
  • 适合大量的数据而不是小数据
  • 实时加载数据仅限于增加(删除和更新需要解压缩Block 然后计算然后重新压缩储存)
  • 高效的压缩率,不仅节省储存空间也节省计算内存和CPU。
  • 非常适合做聚合操作。

列式数据库缺点:

  • 不适合扫描小量数据
  • 不适合随机的更新
  • 批量更新情况各异,有的优化的比较好的列式数据库(比如Vertica)表现比较好,有些没有针对更新的数据库表现比较差。
  • 不适合做含有删除和更新的实时操作。

tomcat8源码阅读(六)--接收请求

一,回顾

上篇文章讲了tomcat的Connector启动过程,启动之后,就可以接收请求了。这篇文章主要讲解tomcat如何接收请求。

二,NioEndpoint

2.1 NioEndpoint#Acceptor创建链接

在NioEndpoint#Acceptor的run方法中,接受socket链接,代码如下:

NioEndpoint#Acceptor    
public void run() {

        int errorDelay = 0;

        // Loop until we receive a shutdown command
        while (running) {

           ...//略 是否要暂停 Loop if endpoint is paused

            state = AcceptorState.RUNNING;

            try {
                //if we have reached max connections, wait
                countUpOrAwaitConnection();

                SocketChannel socket = null;
                try {
                    // Accept the next incoming connection from the server
                    // socket
                    socket = serverSock.accept();
                } catch (IOException ioe) {
                    //we didn't get a socket
                    countDownConnection();
                    // Introduce delay if necessary
                    errorDelay = handleExceptionWithDelay(errorDelay);
                    // re-throw
                    throw ioe;
                }
                // Successful accept, reset the error delay
                errorDelay = 0;

                // setSocketOptions() will add channel to the poller
                // if successful
                ...//略,状态处理
            } catch (SocketTimeoutException sx) {
                // Ignore: Normal condition
            } catch (IOException x) {
                if (running) {
                    log.error(sm.getString("endpoint.accept.fail"), x);
                }
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
                log.error(sm.getString("endpoint.accept.fail"), t);
            }
        }
        state = AcceptorState.ENDED;
    }

核心代码在socket = serverSock.accept()中,当创建链路后。Selector会对此链路进行监控,然后进行数据的读写。

2.2 NioEndpoint#Poller接受数据

在poller的run方法中,对SelectionKey进行遍历。

public void run() {
    // Loop until destroy() is called
    while (true) {

        boolean hasEvents = false;

        ...略// Time to terminate?

        try {
            if ( !close ) {
                if (wakeupCounter.getAndSet(-1) > 0) {
                    //if we are here, means we have other stuff to do
                    //do a non blocking select
                    keyCount = selector.selectNow();
                } else {
                    keyCount = selector.select(selectorTimeout);
                }
                wakeupCounter.set(0);
            }
            if (close) {
                events();
                timeout(0, false);
                try {
                    selector.close();
                } catch (IOException ioe) {
                    log.error(sm.getString(
                            "endpoint.nio.selectorCloseFail"), ioe);
                }
                break;
            }
        } catch (Throwable x) {
            ExceptionUtils.handleThrowable(x);
            log.error("",x);
            continue;
        }
        //either we timed out or we woke up, process events first
        if ( keyCount == 0 ) hasEvents = (hasEvents | events());

        //遍历SelectionKey    
        Iterator<SelectionKey> iterator =
            keyCount > 0 ? selector.selectedKeys().iterator() : null;
        // Walk through the collection of ready keys and dispatch
        // any active event.
        while (iterator != null && iterator.hasNext()) {
            SelectionKey sk = iterator.next();
            NioSocketWrapper attachment = (NioSocketWrapper)sk.attachment();
            // Attachment may be null if another thread has called
            // cancelledKey()
            if (attachment == null) {
                iterator.remove();
            } else {
                iterator.remove();
                //处理SelectionKey
                processKey(sk, attachment);
            }
        }//while

        //process timeouts
        timeout(keyCount,hasEvents);
    }//while

    stopLatch.countDown();
}

在run方法中,有一句processKey(sk, attachment),对SelectionKey进行处理,我们看其具体实现逻辑:

 protected void processKey(SelectionKey sk, NioSocketWrapper attachment) {
    try {
        if ( close ) {
            cancelledKey(sk);
        } else if ( sk.isValid() && attachment != null ) {
            if (sk.isReadable() || sk.isWritable() ) {
                if ( attachment.getSendfileData() != null ) {
                    //处理文件发送
                    processSendfile(sk,attachment, false);
                } else {
                    if ( isWorkerAvailable() ) {
                        unreg(sk, attachment, sk.readyOps());
                        boolean closeSocket = false;
                        // Read goes before write
                        if (sk.isReadable()) {
                            //读操作
                            if (!processSocket(attachment, SocketStatus.OPEN_READ, true)) {
                                closeSocket = true;
                            }
                        }
                        if (!closeSocket && sk.isWritable()) {
                            //写操作                    
                            if (!processSocket(attachment, SocketStatus.OPEN_WRITE, true)) {
                                closeSocket = true;
                            }
                        }
                        if (closeSocket) {
                            cancelledKey(sk);
                        }
                    }
                }
            }
        } else {
            //invalid key
            cancelledKey(sk);
        }
    } catch ( CancelledKeyException ckx ) {
        cancelledKey(sk);
    } catch (Throwable t) {
        ExceptionUtils.handleThrowable(t);
        log.error("",t);
    }
}

processKey对文件进行了隔离操作,然后使用processSocket方法进行读写操作。

protected boolean processSocket(NioSocketWrapper attachment, SocketStatus status, boolean dispatch) {
    try {
        if (attachment == null) {
            return false;
        }
        SocketProcessor sc = processorCache.pop();
        if ( sc == null ) sc = new SocketProcessor(attachment, status);
        else sc.reset(attachment, status);
        Executor executor = getExecutor();
        if (dispatch && executor != null) {
            executor.execute(sc);
        } else {
            sc.run();
        }
    } catch (RejectedExecutionException ree) {
        return false;
    } catch (Throwable t) {
        return false;
    }
    return true;
}

processSocket创建了一个SocketProcessor,然后提交到线程池中进行处理。我们看SocketProcessor的run方法。

public void run() {
    NioChannel socket = ka.getSocket();
    if (socket == null) {
        return;
    }
    SelectionKey key = socket.getIOChannel().keyFor(
            socket.getPoller().getSelector());

    synchronized (socket) {
        try {
            int handshake = -1;

            try {
                ...//略,握手处理
            if (handshake == 0) {
                SocketState state = SocketState.OPEN;
                // Process the request from this socket
                if (status == null) {
                    state = handler.process(ka, SocketStatus.OPEN_READ);
                } else {
                    state = handler.process(ka, status);
                }
                if (state == SocketState.CLOSED) {
                    close(socket, key);
                }
            } else if (handshake == -1 ) {
                close(socket, key);
            } else {
                ka.getPoller().add(socket,handshake);
            }
        } catch (CancelledKeyException cx) {
            socket.getPoller().cancelledKey(key);
        } catch (VirtualMachineError vme) {
            ExceptionUtils.handleThrowable(vme);
        } catch (Throwable t) {
            log.error("", t);
            socket.getPoller().cancelledKey(key);
        } finally {
            ka = null;
            status = null;
            //return to cache
            if (running && !paused) {
                processorCache.push(this);
            }
        }
    }
}

run方法对握手进行了处理,然后开始处理数据。在handler的procee实现了具体的处理方式:

public SocketState process(SocketWrapperBase<S> wrapper,
        SocketStatus status) {
    ...//略,对状态的一些判断

    Processor processor = connections.get(socket);
    if (status == SocketStatus.DISCONNECT && processor == null) {
        // Nothing to do. Endpoint requested a close and there is no
        // longer a processor associated with this socket.
        return SocketState.CLOSED;
    }

    wrapper.setAsync(false);
    ContainerThreadMarker.set();

    try {
        ...//对process的判断
        processor.setSslSupport(
                wrapper.getSslSupport(getProtocol().getClientCertProvider()));

        SocketState state = SocketState.CLOSED;
        Iterator<DispatchType> dispatches = null;
        do {
            if (status == SocketStatus.CLOSE_NOW) {
                ...//略,很多状态
            } else {
                state = processor.process(wrapper);
            }

            if (state != SocketState.CLOSED && processor.isAsync()) {
                state = processor.asyncPostProcess();
            }

            ...//略,协议更新处理
            if (dispatches == null || !dispatches.hasNext()) {
                // Only returns non-null iterator if there are
                // dispatches to process.
                dispatches = wrapper.getIteratorAndClearDispatches();
            }
        } while (state == SocketState.ASYNC_END ||
                state == SocketState.UPGRADING ||
                dispatches != null && state != SocketState.CLOSED);

        ...//略,状态判断
        return state;
    } catch(java.net.SocketException e) {
       ...//略,异常
    }
    finally {
        ContainerThreadMarker.clear();
    }

    // Make sure socket/processor is removed from the list of current
    // connections
    connections.remove(socket);
    // Don't try to add upgrade processors back into the pool
    if (processor !=null && !processor.isUpgrade()) {
        release(wrapper, processor, false);
    }
    return SocketState.CLOSED;
}

在这个方法中,我们只看最重要的 processor.process(wrapper);对于http协议,processor是Http11Processor,下面将详细讲解。

三,Http11Processor

下面我们看看Process是如何对NioSocketWrapper进行处理的。我们看process方法,方法太长,省略了许多:

public SocketState process(SocketWrapperBase<?> socketWrapper)
    throws IOException {
    RequestInfo rp = request.getRequestProcessor();
    rp.setStage(org.apache.coyote.Constants.STAGE_PARSE);

    // Setting up the I/O
     ...//略
    // Flags
    keepAlive = true;
    openSocket = false;
    sendfileInProgress = false;
    readComplete = true;
    keptAlive = false;

    while (!getErrorState().isError() && keepAlive && !isAsync() &&
            httpUpgradeHandler == null && !endpoint.isPaused()) {

        // Parsing the request header
        try {
            if (!inputBuffer.parseRequestLine(keptAlive)) {
                if (handleIncompleteRequestLineRead()) {
                    break;
                }
            }

            if (endpoint.isPaused()) {
                // 503 - Service unavailable
                response.setStatus(503);
                setErrorState(ErrorState.CLOSE_CLEAN, null);
            } else {
                keptAlive = true;
                // Set this every time in case limit has been changed via JMX
                request.getMimeHeaders().setLimit(endpoint.getMaxHeaderCount());
                if (!inputBuffer.parseHeaders()) {
                    // We've read part of the request, don't recycle it
                    // instead associate it with the socket
                    openSocket = true;
                    readComplete = false;
                    break;
                }
                if (!disableUploadTimeout) {
                    socketWrapper.setReadTimeout(connectionUploadTimeout);
                }
            }
        } catch (Throwable t) {
            ...//略,其他异常
            // 400 - Bad Request ,返回400错误
            response.setStatus(400);
            setErrorState(ErrorState.CLOSE_CLEAN, t);
            getAdapter().log(request, response, 0);
        }

        // Has an upgrade been requested?
        ...//略,更新请求

        if (!getErrorState().isError()) {
            // Setting up filters, and parse some request headers
            rp.setStage(org.apache.coyote.Constants.STAGE_PREPARE);
            try {
                prepareRequest();
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
                if (log.isDebugEnabled()) {
                    log.debug(sm.getString("http11processor.request.prepare"), t);
                }
                // 500 - Internal Server Error
                response.setStatus(500);
                setErrorState(ErrorState.CLOSE_CLEAN, t);
                getAdapter().log(request, response, 0);
            }
        }

        if (maxKeepAliveRequests == 1) {
            keepAlive = false;
        } else if (maxKeepAliveRequests > 0 &&
                socketWrapper.decrementKeepAlive() <= 0) {
            keepAlive = false;
        }

        // Process the request in the adapter
        if (!getErrorState().isError()) {
            try {
                rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
                getAdapter().service(request, response);
                // Handle when the response was committed before a serious
                // error occurred.  Throwing a ServletException should both
                // set the status to 500 and set the errorException.
                // If we fail here, then the response is likely already
                // committed, so we can't try and set headers.
                if(keepAlive && !getErrorState().isError() && (
                        response.getErrorException() != null ||
                                (!isAsync() &&
                                statusDropsConnection(response.getStatus())))) {
                    setErrorState(ErrorState.CLOSE_CLEAN, null);
                }
            } catch (InterruptedIOException e) {
                setErrorState(ErrorState.CLOSE_NOW, e);
            } catch (HeadersTooLargeException e) {
                // The response should not have been committed but check it
                // anyway to be safe
                if (response.isCommitted()) {
                    setErrorState(ErrorState.CLOSE_NOW, e);
                } else {
                    response.reset();
                    response.setStatus(500);
                    setErrorState(ErrorState.CLOSE_CLEAN, e);
                    response.setHeader("Connection", "close"); // TODO: Remove
                }
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
                log.error(sm.getString("http11processor.request.process"), t);
                // 500 - Internal Server Error
                response.setStatus(500);
                setErrorState(ErrorState.CLOSE_CLEAN, t);
                getAdapter().log(request, response, 0);
            }
        }

        // Finish the handling of the request
        rp.setStage(org.apache.coyote.Constants.STAGE_ENDINPUT);
        if (!isAsync()) {
            // If this is an async request then the request ends when it has
            // been completed. The AsyncContext is responsible for calling
            // endRequest() in that case.
            endRequest();
        }
        rp.setStage(org.apache.coyote.Constants.STAGE_ENDOUTPUT);

        // If there was an error, make sure the request is counted as
        // and error, and update the statistics counter
        if (getErrorState().isError()) {
            response.setStatus(500);
        }
        request.updateCounters();

        ...//略

        rp.setStage(org.apache.coyote.Constants.STAGE_KEEPALIVE);

        if (breakKeepAliveLoop(socketWrapper)) {
            break;
        }
    }

    rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);

    if (getErrorState().isError() || endpoint.isPaused()) {
        return SocketState.CLOSED;
    } else if (isAsync()) {
        return SocketState.LONG;
    } else if (isUpgrade()) {
        return SocketState.UPGRADING;
    } else {
        if (sendfileInProgress) {
            return SocketState.SENDFILE;
        } else {
            if (openSocket) {
                if (readComplete) {
                    return SocketState.OPEN;
                } else {
                    return SocketState.LONG;
                }
            } else {
                return SocketState.CLOSED;
            }
        }
    }
}

方法太长,略去了一部分。代码主要是作用是生成了org.apache.coyote.Requset对象和org.apache.coyote.Response对象,对升级和错误进行了额外的处理等。我们重点看getAdapter().service(request, response);这段代码会转发到Servlet请求上。getAdapter()返回一个CoyoteAdapter对象,我们接着看。

四,CoyoteAdapter

service方法对req和res进行转型,转成ServletRequest和ServletResponse
public void service(org.apache.coyote.Request req,
org.apache.coyote.Response res)
throws Exception {

    Request request = (Request) req.getNote(ADAPTER_NOTES);
    Response response = (Response) res.getNote(ADAPTER_NOTES);

    if (request == null) {

        // Create objects
        request = connector.createRequest();
        request.setCoyoteRequest(req);
        response = connector.createResponse();
        response.setCoyoteResponse(res);

        // Link objects
        request.setResponse(response);
        response.setRequest(request);

        // Set as notes
        req.setNote(ADAPTER_NOTES, request);
        res.setNote(ADAPTER_NOTES, response);

        // Set query string encoding
        req.getParameters().setQueryStringEncoding
            (connector.getURIEncoding());

    }

    if (connector.getXpoweredBy()) {
        response.addHeader("X-Powered-By", POWERED_BY);
    }

    boolean async = false;

    try {

        // Parse and set Catalina and configuration specific
        // request parameters
        req.getRequestProcessor().setWorkerThreadName(THREAD_NAME.get());
        boolean postParseSuccess = postParseRequest(req, request, res, response);
        if (postParseSuccess) {
            //check valves if we support async
            request.setAsyncSupported(connector.getService().getContainer().getPipeline().isAsyncSupported());
            // Calling the container
            connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
        }
        AsyncContextImpl asyncConImpl = (AsyncContextImpl)request.getAsyncContext();
        if (asyncConImpl != null) {
            async = true;
            ReadListener readListener = req.getReadListener();
            if (readListener != null && request.isFinished()) {
                // Possible the all data may have been read during service()
                // method so this needs to be checked here
                ClassLoader oldCL = null;
                try {
                    oldCL = request.getContext().bind(false, null);
                    if (req.sendAllDataReadEvent()) {
                        req.getReadListener().onAllDataRead();
                    }
                } finally {
                    request.getContext().unbind(false, oldCL);
                }
            }
        } else {
            request.finishRequest();
            response.finishResponse();
            if (postParseSuccess) {
                // Log only if processing was invoked.
                // If postParseRequest() failed, it has already logged it.
                request.getMappingData().context.logAccess(
                        request, response,
                        System.currentTimeMillis() - req.getStartTime(),
                        false);
            }
        }

    } catch (IOException e) {
        // Ignore
    } finally {
        req.getRequestProcessor().setWorkerThreadName(null);
        AtomicBoolean error = new AtomicBoolean(false);
        res.action(ActionCode.IS_ERROR, error);
        // Recycle the wrapper request and response
        if (!async || error.get()) {
            request.recycle();
            response.recycle();
        } else {
            // Clear converters so that the minimum amount of memory
            // is used by this processor
            request.clearEncoders();
            response.clearEncoders();
        }
    }

}

太长了,我们只关注最重要的:connector.getService().getMapper().map(serverName, decodedURI, version, request.getMappingData());

connector.getService()返回了StandardService,然后映射到了具体的Servlet上,这里就是详细解释了,要去跑步了,88.

五,结束

请求处理完后就会发送数据,就不一一详解了。

tomcat8源码阅读(五)--启动四

一,回顾

还记得我们在启动二讲过的嘛?container的启动和初始化讲完了,connector还没开始。下面,我们开始看connector是如何初始化和启动的。

二,再读Lifecycle


我们看Lifecycle的类层次图,发现很多类都实现了Lifecycle,包括connector和container,这里我只关心connector。由于Connector继承了LifecycleBase,跟container一样,当调用其init和start方法时,会调用其内部的initInternal和startInternal方法,下面我们就关注其方法。

三,Connector的initInternal

看方法:

protected void initInternal() throws LifecycleException {

    super.initInternal();

    // Initialize adapter
    adapter = new CoyoteAdapter(this);
    protocolHandler.setAdapter(adapter);    

    try {
        protocolHandler.init();
    } catch (Exception e) {
        throw new LifecycleException
            (sm.getString
             ("coyoteConnector.protocolHandlerInitializationFailed"), e);
    }
}

这里我们要重点关注protocolHandler,它是怎么创建的呢?我们看Connector的创建。

3.1 创建Connector

在Catalian的createStartDigester()方法中,有这样一段代码:

Catalian
digester.addRule("Server/Service/Connector",
                     new ConnectorCreateRule());

我们看代码,这里创建了一个ConnectorCreateRule对象,到这个里面看看。
ConnectorCreateRule
public void begin(String namespace, String name, Attributes attributes)
throws Exception {
Service svc = (Service)digester.peek();
Executor ex = null;
if ( attributes.getValue(“executor”)!=null ) {
ex = svc.getExecutor(attributes.getValue(“executor”));
}
Connector con = new Connector(attributes.getValue(“protocol”));
if (ex != null) {
setExecutor(con, ex);
}
String sslImplementationName = attributes.getValue(“sslImplementationName”);
if (sslImplementationName != null) {
setSSLImplementationName(con, sslImplementationName);
}
digester.push(con);
}

中间有一句new Connector(attributes.getValue("protocol"));,好家伙,我们看看Connector的构造方法。

public Connector(String protocol) {
    setProtocol(protocol);
    // Instantiate protocol handler
    ProtocolHandler p = null;
    try {
        Class<?> clazz = Class.forName(protocolHandlerClassName);
        p = (ProtocolHandler) clazz.newInstance();
    } catch (Exception e) {
        log.error(sm.getString(
                "coyoteConnector.protocolHandlerInstantiationFailed"), e);
    } finally {
        this.protocolHandler = p;
    }

    ...//other
}
//默认类为Http11NioProtocol
protected String protocolHandlerClassName =
    "org.apache.coyote.http11.Http11NioProtocol";

我们看到,这里用反射创建了一个ProtocolHandler,默认为Http11NioProtocol,并且根据server.xml属性配置设置了协议。

3.2 Http11NioProtocol的init

我们看代码:

AbstractHttp11Protocol
public void init() throws Exception {
    for (UpgradeProtocol upgradeProtocol : upgradeProtocols) {
        configureUpgradeProtocol(upgradeProtocol);
    }

    super.init();
}

调用了父类的init
AbstractProtocol
public void init() throws Exception {

    if (oname == null) {
        // Component not pre-registered so register it
        oname = createObjectName();
        if (oname != null) {
            Registry.getRegistry(null, null).registerComponent(this, oname,
                null);
        }
    }

    if (this.domain != null) {
        try {
            tpOname = new ObjectName(domain + ":" +
                    "type=ThreadPool,name=" + getName());
            Registry.getRegistry(null, null).registerComponent(endpoint,
                    tpOname, null);
        } catch (Exception e) {
            getLog().error(sm.getString(
                    "abstractProtocolHandler.mbeanRegistrationFailed",
                    tpOname, getName()), e);
        }
        rgOname=new ObjectName(domain +
                ":type=GlobalRequestProcessor,name=" + getName());
        Registry.getRegistry(null, null).registerComponent(
                getHandler().getGlobal(), rgOname, null );
    }

    String endpointName = getName();
    endpoint.setName(endpointName.substring(1, endpointName.length()-1));

    try {
        endpoint.init();
    } catch (Exception ex) {
        getLog().error(sm.getString("abstractProtocolHandler.initError",
                getName()), ex);
        throw ex;
    }
}
AbstractEndpoint
public final void init() throws Exception {
    if (bindOnInit) {
        bind();
        bindState = BindState.BOUND_ON_INIT;
    }
}

先调用bind方法,绑定端口:

public void bind() throws Exception {

    serverSock = ServerSocketChannel.open();
    socketProperties.setProperties(serverSock.socket());
    InetSocketAddress addr = (getAddress()!=null?new InetSocketAddress(getAddress(),getPort()):new InetSocketAddress(getPort()));
    serverSock.socket().bind(addr,getBacklog());
    serverSock.configureBlocking(true); //mimic APR behavior
    serverSock.socket().setSoTimeout(getSocketProperties().getSoTimeout());

    // Initialize thread count defaults for acceptor, poller
    if (acceptorThreadCount == 0) {
        // FIXME: Doesn't seem to work that well with multiple accept threads
        acceptorThreadCount = 1;
    }
    if (pollerThreadCount <= 0) {
        //minimum one poller thread
        pollerThreadCount = 1;
    }
    stopLatch = new CountDownLatch(pollerThreadCount);

    // Initialize SSL if needed
    initialiseSsl();

    selectorPool.open();
}

四,Connector的startInternal

直接上方法:

protected void startInternal() throws LifecycleException {
    ...//检验
    setState(LifecycleState.STARTING);
    try {
        protocolHandler.start();
    } catch (Exception e) {
        throw new LifecycleException
            (errPrefix + " " + sm.getString
             ("coyoteConnector.protocolHandlerStartFailed"), e);
    }
}

方法很简单,调用了protocolHandler的start方法。

4.1 Http11NioProtocol的start

public void start() throws Exception {
    try {
        endpoint.start();
    } catch (Exception ex) {
        getLog().error(sm.getString("abstractProtocolHandler.startError",
                getName()), ex);
        throw ex;
    }
}

代码直接启动了endpoint,在Http11NioProtocol为NioEndpoint对象,这里就不解释了。

我们看其内部方法:

AbstractEndpoint
public final void start() throws Exception {
    if (bindState == BindState.UNBOUND) {
        bind();
        bindState = BindState.BOUND_ON_START;
    }
    startInternal();
}

代码采用Blocking模式接收连接请求。接着调用了startInternal方法:

public void startInternal() throws Exception {

    if (!running) {
        running = true;
        paused = false;

        processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
                socketProperties.getProcessorCache());
        eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
                        socketProperties.getEventCache());
        nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
                socketProperties.getBufferPool());

        // Create worker collection
        if ( getExecutor() == null ) {
            createExecutor();
        }

        initializeConnectionLatch();

        // Start poller threads
        pollers = new Poller[getPollerThreadCount()];
        for (int i=0; i<pollers.length; i++) {
            pollers[i] = new Poller();
            Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-"+i);
            pollerThread.setPriority(threadPriority);
            pollerThread.setDaemon(true);
            pollerThread.start();
        }

        startAcceptorThreads();
    }
}

代码首先创建了线程池,然后初始化了最大连接限制,启动了poller线程,和acceptor线程。这样,整个流程就结束了。Poller主要作用于socket读写事件,Acceptor主要作用于接收请求。

redis学习

一,redis数据结构

redis总共包含5种类型的数据结构,分别是String,Hash,List,Set,Sorted Set。下面我们来逐一介绍.

1.1 String

String就是基本的key-value数据结构。

常用命令:

get,set,mset,mget,decr,incr,getset,del,exists等。

1.2 Hash

Hash提供对象关系存储,相比String而言多了一层查找字段的功能。
举个例子,我们看hash的set命令:

HSET key field value

相比String而言,多了field名称,这样我们就可以以对象来存储对象的值。例如有一个对象Person,它有2个字段 name和sex。我们就可以使用

HSET person:1 name '张三';
HSET person:1 sex '男';
或者使用 HMSET person:1 name  '张三' sex '男' ;

如果我们需要修改”张三”的姓名时,只需要

HSET person:1 name '李四'

即可。

常用命令:

hset,hget,hlen,hmset,hmget,hkeys,hdel,hincrby

1.3 List

List使用双向链表构成,它维护了一个给定key的一组数组值。你可以对这个list进行增删改查。
我们来看一个它的简单命令:

LPUSH key value [value …]

这里value可以是一个或者多个,这些value组成了一个list。

常用命令:

lpush,lpop,rpush,rpop,llen,lrange,lrem

1.4 Set

Set相比List就是存储的值不重复,即唯一的。Set操作还支持集合操作,交集,差集和并集。
命令格式如下:

SADD key member [member …]

常用命令:

sadd,srem,smembers,sismember,sdiff,sinter,sunion,scard等

1.4 Sorted Set

Sorted Set是有序的集合,使用跳表(skip list)实现的。
添加命令:

ZADD key [NX|XX] [CH] [INCR] score member [score member …]

zadd命令用于向有序集合中加入一个元素和改元素对应的分数,如果该元素存在则会用新的分数替换原来的分数。

常用命令:

zadd,zscore,zrange,zrangebyscore,zrem,zcard,zcount

二,存储结构

Redis内部使用一个redisObject对象来表示所有的key和value,我们先看redisObject:

type代表一个value对象具体是何种数据类型,encoding是不同数据类型在redis内部的存储方式,比如:type=string代表value存储的是一个普通字符串,那么对应的encoding可以是raw或者是int,如果是int则代表实际redis内部是按数值型类存储和表示这个字符串的,当然前提是这个字符串本身可以用数值表示,比如:”123” “456”这样的字符串。

接下来,我们看redis的内存结构:

我们看到,dictEntry存储了key和val,它主要由链表实现。key是一个字符串,val可以是上面5种不同的数据类型。

更详尽的介绍请移步Redis内存存储结构分析

三,其他特性

这一小节主要介绍了redis的缓存失效,事务,发布订阅,持久化等方面。

3.1 缓存失效

在 Redis 提供的诸多命令中,EXPIRE、EXPIREAT、PEXPIRE、PEXPIREAT 以及 SETEX 和 PSETEX 均可以用来设置一条 Key-Value 对的失效时间,而一条 Key-Value 对一旦被关联了失效时间就会在到期后自动删除(或者说变得无法访问更为准确)。

Redis 删除失效主键的方法主要有两种:

  • 消极方法(passive way),在主键被访问时如果发现它已经失效,那么就删除它
  • 积极方法(active way),周期性地从设置了失效时间的主键中选择一部分失效的主键删除—使用抽样方法,如果失效的主键数占抽样数的百分比大于25%,则继续抽样删除过程

3.2 事务

使用互斥

可以使用 multi(开始事务) ,discard(取消事务),exec(提交事务)。
注意:Redis 不支持回滚。

使用cas

WATCH 命令可以为 Redis 事务提供 check-and-set (CAS)行为。
被 WATCH 的键会被监视,并会发觉这些键是否被改动过了。 如果有至少一个被监视的键在 EXEC 执行之前被修改了, 那么整个事务都会被取消, EXEC 返回空多条批量回复(null multi-bulk reply)来表示事务已经失败。

WATCH mykey

val = GET mykey
val = val + 1

MULTI
SET mykey $val
EXEC

3.3 发布订阅

redis使用SUBSCRIBE 、 UNSUBSCRIBE 和 PUBLISH 来做发布订阅操作。你可以设定对某一个 key 值进行消息发布及消息订阅,当一个 key 值上进行了消息发布后,所有订阅它的客户端都会收到相应的消息。

3.4 持久化

redis的持久化有rdb和aof两种。

  • rdb 记录一段时间内的操作,一般的配置是一段时间内操作超过多少次就持久化
  • aof可以实现每次操作都持久化

参考资料

tomcat8源码阅读(四)--启动三

一,回顾

上文介绍了Server的启动过程,但是没有详细介绍Container和Connector的详细启动流程,下面我将介绍Container的启动流程。

二,Container的init

下面我看看Container的继承结构:

Engine,Host,Context,Wrapper都属于Container。下面,我们根据server.xml的默认配置结构来看看。

<Engine name="Catalina" defaultHost="localhost">
  <!--
  <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>
  --> 
   //LockOutRealm防止暴力破解
  <Realm className="org.apache.catalina.realm.LockOutRealm">
    <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
           resourceName="UserDatabase"/>
  </Realm>

  <Host name="localhost"  appBase="webapps"
        unpackWARs="true" autoDeploy="true">

    <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
           prefix="localhost_access_log" suffix=".txt"
           pattern="%h %l %u %t &quot;%r&quot; %s %b" />

  </Host>
</Engine>

其中有Realm,这个是TOMCAT的安全域,我们暂时不关心,看其他的几个。

2.1 Engine的initInternal

 protected void initInternal() throws LifecycleException {
    //获取一个默认的安全域
    getRealm();
    super.initInternal();
}

方法比较简单,我们关注super.initInternal()方法:

2.2 ContainerBase的initInternal

ContainerBase主要是创建一个startStopExecutor线程池用来启动停止子节点。

ContainerBase
protected void initInternal() throws LifecycleException {
    BlockingQueue<Runnable> startStopQueue = new LinkedBlockingQueue<>();
    //创建线程池 
    startStopExecutor = new ThreadPoolExecutor(
            getStartStopThreadsInternal(),
            getStartStopThreadsInternal(), 10, TimeUnit.SECONDS,
            startStopQueue,
            new StartStopThreadFactory(getName() + "-startStop-"));
    startStopExecutor.allowCoreThreadTimeOut(true);
    super.initInternal();
}        

2.3 StandardHost的initInternal

protected synchronized void startInternal() throws LifecycleException {
    //设置异常处理valve
    String errorValve = getErrorReportValveClass();
    super.startInternal();
}

2.4 StandardContext的initInternal

protected void initInternal() throws LifecycleException {
    super.initInternal();

    //注册命名服务
    if (namingResources != null) {
        namingResources.init();
    }
    //启动
    if (resources != null) {
        resources.start();
    }

    // Send j2ee.object.created notification
    if (this.getObjectName() != null) {
        Notification notification = new Notification("j2ee.object.created",
                this.getObjectName(), sequenceNumber.getAndIncrement());
        broadcaster.sendNotification(notification);
    }
}

2.5 StandardWrapper的initInternal

StandardWrapper的initInternal继承的父类ContainerBase的initInternal。

这样,初始化工作就告一段落了,下面,我们看start方法。

三,Container的start

上文中我们知道,Container的start最终会调用自己的startInternal方法,下面我们看具体实现。

3.1 StandardEngine的startInternal

StandardEngine
protected synchronized void startInternal() throws LifecycleException {
    super.startInternal();
}

StandardEngine调用了父类ContainerBase的startInternal方法。

3.2 ContainerBase的startInternal

ContainerBase
protected synchronized void startInternal() throws LifecycleException {
    logger = null;
    getLogger();
    //获取并启动集群
    Cluster cluster = getClusterInternal();
    if (cluster instanceof Lifecycle) {
        ((Lifecycle) cluster).start();
    }
    //获取并启动安全域
    Realm realm = getRealmInternal();
    if (realm instanceof Lifecycle) {
        ((Lifecycle) realm).start();
    }

    //使用线程池启动子容器
    Container children[] = findChildren();
    List<Future<Void>> results = new ArrayList<>();
    for (int i = 0; i < children.length; i++) {
        results.add(startStopExecutor.submit(new StartChild(children[i])));
    }
    //异步获取启动结果
    boolean fail = false;
    for (Future<Void> result : results) {
        try {
            result.get();
        } catch (Exception e) {
            log.error(sm.getString("containerBase.threadedStartFailed"), e);
            fail = true;
        }

    }
    if (fail) {
        throw new LifecycleException(
                sm.getString("containerBase.threadedStartFailed"));
    }

    // 启动pipeline中的Valves
    if (pipeline instanceof Lifecycle)
        ((Lifecycle) pipeline).start();

    setState(LifecycleState.STARTING);

    //启动后台线程,定期检查session超时
    threadStart();
}

就这样,ContainerBase会将它的子容器用线程池启动起来。

3.3 StandardHost的startInternal

StandardHost
protected synchronized void startInternal() throws LifecycleException {
    String errorValve = getErrorReportValveClass();
    super.startInternal();
}

啥也没做,继续调用父类的startInternal方法。

3.4 StandardContext的startInternal

StandardContext
protected synchronized void startInternal() throws LifecycleException {
    // Send j2ee.state.starting notification
    if (this.getObjectName() != null) {
        Notification notification = new Notification("j2ee.state.starting",
                this.getObjectName(), sequenceNumber.getAndIncrement());
        broadcaster.sendNotification(notification);
    }

    setConfigured(false);
    boolean ok = true;

    // Currently this is effectively a NO-OP but needs to be called to
    // ensure the NamingResources follows the correct lifecycle
    if (namingResources != null) {
        namingResources.start();
    }

    // Add missing components as necessary
    if (getResources() == null) {   // (1) Required by Loader
        if (log.isDebugEnabled())
            log.debug("Configuring default Resources");

        try {
            setResources(new StandardRoot(this));
        } catch (IllegalArgumentException e) {
            log.error(sm.getString("standardContext.resourcesInit"), e);
            ok = false;
        }
    }
    if (ok) {
        resourcesStart();
    }

    if (getLoader() == null) {
        WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
        webappLoader.setDelegate(getDelegate());
        setLoader(webappLoader);
    }

    // Initialize character set mapper
    getCharsetMapper();

    // Post work directory
    postWorkDirectory();

    // Validate required extensions
    boolean dependencyCheck = true;
    try {
        dependencyCheck = ExtensionValidator.validateApplication
            (getResources(), this);
    } catch (IOException ioe) {
        log.error(sm.getString("standardContext.extensionValidationError"), ioe);
        dependencyCheck = false;
    }

    if (!dependencyCheck) {
        // do not make application available if depency check fails
        ok = false;
    }

    // Reading the "catalina.useNaming" environment variable
    String useNamingProperty = System.getProperty("catalina.useNaming");
    if ((useNamingProperty != null)
        && (useNamingProperty.equals("false"))) {
        useNaming = false;
    }

    if (ok && isUseNaming()) {
        if (getNamingContextListener() == null) {
            NamingContextListener ncl = new NamingContextListener();
            ncl.setName(getNamingContextName());
            ncl.setExceptionOnFailedWrite(getJndiExceptionOnFailedWrite());
            addLifecycleListener(ncl);
            setNamingContextListener(ncl);
        }
    }

    // Standard container startup
    if (log.isDebugEnabled())
        log.debug("Processing standard container startup");


    // Binding thread
    ClassLoader oldCCL = bindThread();

    try {
        if (ok) {
            // Start our subordinate components, if any
            Loader loader = getLoader();
            if (loader instanceof Lifecycle) {
                ((Lifecycle) loader).start();
            }

            // since the loader just started, the webapp classloader is now
            // created.
            setClassLoaderProperty("clearReferencesStatic",
                    getClearReferencesStatic());
            setClassLoaderProperty("clearReferencesStopThreads",
                    getClearReferencesStopThreads());
            setClassLoaderProperty("clearReferencesStopTimerThreads",
                    getClearReferencesStopTimerThreads());
            setClassLoaderProperty("clearReferencesHttpClientKeepAliveThread",
                    getClearReferencesHttpClientKeepAliveThread());

            // By calling unbindThread and bindThread in a row, we setup the
            // current Thread CCL to be the webapp classloader
            unbindThread(oldCCL);
            oldCCL = bindThread();

            // Initialize logger again. Other components might have used it
            // too early, so it should be reset.
            logger = null;
            getLogger();

            Cluster cluster = getClusterInternal();
            if (cluster instanceof Lifecycle) {
                ((Lifecycle) cluster).start();
            }
            Realm realm = getRealmInternal();
            if (realm instanceof Lifecycle) {
                ((Lifecycle) realm).start();
            }

            // Notify our interested LifecycleListeners
            fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);

            // Start our child containers, if not already started
            for (Container child : findChildren()) {
                if (!child.getState().isAvailable()) {
                    child.start();
                }
            }

            // Start the Valves in our pipeline (including the basic),
            // if any
            if (pipeline instanceof Lifecycle) {
                ((Lifecycle) pipeline).start();
            }

            // Acquire clustered manager
            Manager contextManager = null;
            Manager manager = getManager();
            if (manager == null) {
                if (log.isDebugEnabled()) {
                    log.debug(sm.getString("standardContext.cluster.noManager",
                            Boolean.valueOf((getCluster() != null)),
                            Boolean.valueOf(distributable)));
                }
                if ( (getCluster() != null) && distributable) {
                    try {
                        contextManager = getCluster().createManager(getName());
                    } catch (Exception ex) {
                        log.error("standardContext.clusterFail", ex);
                        ok = false;
                    }
                } else {
                    contextManager = new StandardManager();
                }
            }

            // Configure default manager if none was specified
            if (contextManager != null) {
                if (log.isDebugEnabled()) {
                    log.debug(sm.getString("standardContext.manager",
                            contextManager.getClass().getName()));
                }
                setManager(contextManager);
            }

            if (manager!=null && (getCluster() != null) && distributable) {
                //let the cluster know that there is a context that is distributable
                //and that it has its own manager
                getCluster().registerManager(manager);
            }
        }

        if (!getConfigured()) {
            log.error(sm.getString("standardContext.configurationFail"));
            ok = false;
        }

        // We put the resources into the servlet context
        if (ok)
            getServletContext().setAttribute
                (Globals.RESOURCES_ATTR, getResources());

        if (ok ) {
            if (getInstanceManager() == null) {
                javax.naming.Context context = null;
                if (isUseNaming() && getNamingContextListener() != null) {
                    context = getNamingContextListener().getEnvContext();
                }
                Map<String, Map<String, String>> injectionMap = buildInjectionMap(
                        getIgnoreAnnotations() ? new NamingResourcesImpl(): getNamingResources());
                setInstanceManager(new DefaultInstanceManager(context,
                        injectionMap, this, this.getClass().getClassLoader()));
                getServletContext().setAttribute(
                        InstanceManager.class.getName(), getInstanceManager());
            }
        }

        // Create context attributes that will be required
        if (ok) {
            getServletContext().setAttribute(
                    JarScanner.class.getName(), getJarScanner());
        }

        // Set up the context init params
        mergeParameters();

        // Call ServletContainerInitializers
        for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
            initializers.entrySet()) {
            try {
                entry.getKey().onStartup(entry.getValue(),
                        getServletContext());
            } catch (ServletException e) {
                log.error(sm.getString("standardContext.sciFail"), e);
                ok = false;
                break;
            }
        }

        // Configure and call application event listeners
        if (ok) {
            if (!listenerStart()) {
                log.error(sm.getString("standardContext.listenerFail"));
                ok = false;
            }
        }

        // Check constraints for uncovered HTTP methods
        // Needs to be after SCIs and listeners as they may programatically
        // change constraints
        if (ok) {
            checkConstraintsForUncoveredMethods(findConstraints());
        }

        try {
            // Start manager
            Manager manager = getManager();
            if (manager instanceof Lifecycle) {
                ((Lifecycle) manager).start();
            }
        } catch(Exception e) {
            log.error(sm.getString("standardContext.managerFail"), e);
            ok = false;
        }

        // Configure and call application filters
        if (ok) {
            if (!filterStart()) {
                log.error(sm.getString("standardContext.filterFail"));
                ok = false;
            }
        }

        // Load and initialize all "load on startup" servlets
        if (ok) {
            if (!loadOnStartup(findChildren())){
                log.error(sm.getString("standardContext.servletFail"));
                ok = false;
            }
        }

        // Start ContainerBackgroundProcessor thread
        super.threadStart();
    } finally {
        // Unbinding thread
        unbindThread(oldCCL);
    }

    // Set available status depending upon startup success
    if (ok) {
        if (log.isDebugEnabled())
            log.debug("Starting completed");
    } else {
        log.error(sm.getString("standardContext.startFailed", getName()));
    }

    startTime=System.currentTimeMillis();

    // Send j2ee.state.running notification
    if (ok && (this.getObjectName() != null)) {
        Notification notification =
            new Notification("j2ee.state.running", this.getObjectName(),
                             sequenceNumber.getAndIncrement());
        broadcaster.sendNotification(notification);
    }

    // The WebResources implementation caches references to JAR files. On
    // some platforms these references may lock the JAR files. Since web
    // application start is likely to have read from lots of JARs, trigger
    // a clean-up now.
    getResources().gc();

    // Reinitializing if something went wrong
    if (!ok) {
        setState(LifecycleState.FAILED);
    } else {
        setState(LifecycleState.STARTING);
    }
}

上面代码有点长,主要是做了资源的设置,类加载器的设置,session管理的设置等。当然也启动了pipeline。但是有一点我们需要注意的是,子容器并不是在pipeline的StandardContextValve中传递执行的。那么是在哪里呢?其实就在StandardContext中的startInternal执行的。为什么要这样设计呢?我们发现,这是由于servlet在xml中的顺序决定了启动顺序。
接下来有人会不会有另一个疑惑,要是我在server.xml没有配置context,只是放在了webapps目录下面,那么这个context是如何加载的呢?接下来,我就讲讲这个。

四,Context调用时机

context可以有2种表现形式,一种是直接写在server.xml文件中,另一种是放在tomcat的webapps目录下。
第一种形式已经在前面介绍过了,下面,我们介绍第二中形式。

4.1 Host启动触发监听事件

大家还记得否,当我们每次调用LifecycleBase的start方法时,都会调用下面这句:

setStateInternal(LifecycleState.STARTED, null, false);

那这句话做了什么工作呢?我们看其源码实现:

LifecycleBase
private synchronized void setStateInternal(LifecycleState state,
        Object data, boolean check) throws LifecycleException {

    ...//检查状态
    this.state = state;
    String lifecycleEvent = state.getLifecycleEvent();
    if (lifecycleEvent != null) {
        fireLifecycleEvent(lifecycleEvent, data);
    }
}

protected void fireLifecycleEvent(String type, Object data) {
    LifecycleEvent event = new LifecycleEvent(this, type, data);
    LifecycleListener interested[] = listeners;
    for (int i = 0; i < interested.length; i++) {
        interested[i].lifecycleEvent(event);
    }
}

我们看到,这里会触犯监听器执行感兴趣的事件。那在StandardHost中有哪些监听器呢?

4.2 创建Host监听

我们要跳到Digester那一块了,大家还记得Digester那块嘛?在Catalina的createStartDigester() 方法中,我们看到下面一句话:

digester.addRuleSet(new HostRuleSet(“Server/Service/Engine/“));

那这句话内部干了什么呢?我们看其实现:

Digester
public void addRuleSet(RuleSet ruleSet) {

    String oldNamespaceURI = getRuleNamespaceURI();
    String newNamespaceURI = ruleSet.getNamespaceURI();

    setRuleNamespaceURI(newNamespaceURI);
    ruleSet.addRuleInstances(this); //我们重点看这句
    setRuleNamespaceURI(oldNamespaceURI);
}

HostRuleSet
public void addRuleInstances(Digester digester) {

    digester.addObjectCreate(prefix + "Host",
                             "org.apache.catalina.core.StandardHost",
                             "className");
    digester.addSetProperties(prefix + "Host");
    digester.addRule(prefix + "Host",
                     new CopyParentClassLoaderRule());

    //这里创建了HostConfig监听器
    digester.addRule(prefix + "Host",
                     new LifecycleListenerRule
                     ("org.apache.catalina.startup.HostConfig",
                      "hostConfigClass"));
    digester.addSetNext(prefix + "Host",
                        "addChild",
                        "org.apache.catalina.Container");

    digester.addCallMethod(prefix + "Host/Alias",
                           "addAlias", 0);

    //Cluster configuration start
    digester.addObjectCreate(prefix + "Host/Cluster",
                             null, // MUST be specified in the element
                             "className");
    digester.addSetProperties(prefix + "Host/Cluster");
    digester.addSetNext(prefix + "Host/Cluster",
                        "setCluster",
                        "org.apache.catalina.Cluster");
    //Cluster configuration end

    digester.addObjectCreate(prefix + "Host/Listener",
                             null, // MUST be specified in the element
                             "className");
    digester.addSetProperties(prefix + "Host/Listener");
    digester.addSetNext(prefix + "Host/Listener",
                        "addLifecycleListener",
                        "org.apache.catalina.LifecycleListener");

    digester.addRuleSet(new RealmRuleSet(prefix + "Host/"));

    digester.addObjectCreate(prefix + "Host/Valve",
                             null, // MUST be specified in the element
                             "className");
    digester.addSetProperties(prefix + "Host/Valve");
    digester.addSetNext(prefix + "Host/Valve",
                        "addValve",
                        "org.apache.catalina.Valve");

}

好,在这里我们看到了HostConfig被添加到监听器里面了。那这个HostConfig干了什么呢?我们接下来继续看。

4.3 触发HostConfig调用start方法

我们看他的lifecycleEvent方法实现:

HostConfig
public void lifecycleEvent(LifecycleEvent event) {

    // Identify the host we are associated with
    try {
        host = (Host) event.getLifecycle();
        if (host instanceof StandardHost) {
            setCopyXML(((StandardHost) host).isCopyXML());
            setDeployXML(((StandardHost) host).isDeployXML());
            setUnpackWARs(((StandardHost) host).isUnpackWARs());
            setContextClass(((StandardHost) host).getContextClass());
        }
    } catch (ClassCastException e) {
        log.error(sm.getString("hostConfig.cce", event.getLifecycle()), e);
        return;
    }

    // Process the event that has occurred
    if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {
        check();
    } else if (event.getType().equals(Lifecycle.START_EVENT)) {
        start();
    } else if (event.getType().equals(Lifecycle.STOP_EVENT)) {
        stop();
    }
}

我们看到,当传递了一个Lifecycle.START_EVENT事件时,调用了HostConfig的start方法,接下来,我们继续看start方法:

HostConfig
public void start() {

    if (log.isDebugEnabled())
        log.debug(sm.getString("hostConfig.start"));

    try {
        ObjectName hostON = host.getObjectName();
        oname = new ObjectName
            (hostON.getDomain() + ":type=Deployer,host=" + host.getName());
        Registry.getRegistry(null, null).registerComponent
            (this, oname, this.getClass().getName());
    } catch (Exception e) {
        log.error(sm.getString("hostConfig.jmx.register", oname), e);
    }

    if (host.getCreateDirs()) {
        File[] dirs = new File[] {host.getAppBaseFile(),host.getConfigBaseFile()};
        for (int i=0; i<dirs.length; i++) {
            if (!dirs[i].mkdirs() && !dirs[i].isDirectory()) {
                log.error(sm.getString("hostConfig.createDirs",dirs[i]));
            }
        }
    }

    if (!host.getAppBaseFile().isDirectory()) {
        log.error(sm.getString("hostConfig.appBase", host.getName(),
                host.getAppBaseFile().getPath()));
        host.setDeployOnStartup(false);
        host.setAutoDeploy(false);
    }

    if (host.getDeployOnStartup())
        deployApps();

}

start方法首先注册自己的mbean中,然后创建了一个目录,最后部署应用。

4.3 部署应用程序

HostConfig的start方法做了很多事,前面的我们都不关心,我们只看deployApps方法:

HostConfig
protected void deployApps() {
    //获取应用路径,即webapps目录
    File appBase = host.getAppBaseFile();
    File configBase = host.getConfigBaseFile();
    String[] filteredAppPaths = filterAppPaths(appBase.list());
    // Deploy XML descriptors from configBase
    deployDescriptors(configBase, configBase.list());
    // Deploy WARs
    deployWARs(appBase, filteredAppPaths);
    // Deploy expanded folders
    deployDirectories(appBase, filteredAppPaths);
}

前面的跟具体功能相关,就不解析了,我们只看最后一段deployDirectories:

    protected void deployDirectories(File appBase, String[] files) {

    if (files == null)
        return;

    ExecutorService es = host.getStartStopExecutor();
    List<Future<?>> results = new ArrayList<>();

    for (int i = 0; i < files.length; i++) {

        if (files[i].equalsIgnoreCase("META-INF"))
            continue;
        if (files[i].equalsIgnoreCase("WEB-INF"))
            continue;
        File dir = new File(appBase, files[i]);
        if (dir.isDirectory()) {
            ContextName cn = new ContextName(files[i], false);

            if (isServiced(cn.getName()) || deploymentExists(cn.getName()))
                continue;

            results.add(es.submit(new DeployDirectory(this, cn, dir)));
        }
    }

    for (Future<?> result : results) {
        try {
            result.get();
        } catch (Exception e) {
            log.error(sm.getString(
                    "hostConfig.deployDir.threaded.error"), e);
        }
    }
}

我们看到,这里获取到了webapps目录下的所有目录,然后使用线程池进行加载每个子目录。

4.4 DeployDirectory内部实现

由于使用的是线程池,我们看它的run方法:

DeployDirectory
public void run() {
    config.deployDirectory(cn, dir);
}

返回来调用了HostConfig的deployDirectory方法,我们继续跟踪:

HostConfig
protected void deployDirectory(ContextName cn, File dir) {

    Context context = null;
    File xml = new File(dir, Constants.ApplicationContextXml);
    File xmlCopy =
            new File(host.getConfigBaseFile(), cn.getBaseName() + ".xml");

    DeployedApplication deployedApp;
    boolean copyThisXml = copyXML;
    //获取web.xml文件,并创建一个Context
    try {
        if (deployXML && xml.exists()) {
            synchronized (digesterLock) {
                try {
                    context = (Context) digester.parse(xml);
                } catch (Exception e) {
                    context = new FailedContext();
                } finally {
                    if (context == null) {
                        context = new FailedContext();
                    }
                    digester.reset();
                }
            }

            ...//略
        } 
         ....//略
        //加载ContextConfig并实例化    
        Class<?> clazz = Class.forName(host.getConfigClass());
        LifecycleListener listener =
            (LifecycleListener) clazz.newInstance();
        context.addLifecycleListener(listener);

        context.setName(cn.getName());
        context.setPath(cn.getPath());
        context.setWebappVersion(cn.getVersion());
        context.setDocBase(cn.getBaseName());
        //将Context添加到host中。
        host.addChild(context);
    } catch (Throwable t) {
        ExceptionUtils.handleThrowable(t);
        log.error(sm.getString("hostConfig.deployDir.error",
                dir.getAbsolutePath()), t);
    } finally {
        ...//略
}

在这里,我们发现,创建了一个Context,并将其添加到Host中,就完成了未在server.xml中配置Context也可以启动webapps目录下的工程了。好的,container启动部分就到此结束了,下文将介绍connector启动部分。

五,流程图



tomcat8源码阅读(三)--启动二

一,Catalina启动

接上文,BootStart启动后会启动Catalina的start方法:

 public void start() {
    //加载服务    
    if (getServer() == null) {
        load();
    }
    //启动服务
    try {
        getServer().start();
    } catch (LifecycleException e) {
        return;
    }
    // 注册关闭钩子,做一些资源清理处理等。
    if (useShutdownHook) {
        if (shutdownHook == null) {
            shutdownHook = new CatalinaShutdownHook();
        }
        Runtime.getRuntime().addShutdownHook(shutdownHook);
    }
    //监听tomcat关闭事件
    if (await) {
        await();
        stop();
    }
}

在代码中我们看到Catalina做了4件事,分别是加载服务配置,启动服务,注册关闭钩子,监听关闭事件。下面我们重点介绍前面2个步骤。

二,Catalina加载配置服务

我们查看load方法:

Catalina
public void load() {
    // 初始化命名
    initNaming();

    // 创建和执行Digester
    Digester digester = createStartDigester();

    InputSource inputSource = null;
    InputStream inputStream = null;
    File file = null;
    try {
        //读取conf/server.xml文件
        file = configFile();
        inputStream = new FileInputStream(file);
        inputSource = new InputSource(file.toURI().toURL().toString());
    } catch (Exception e) {
    }
    ...//略,读取配置文件
    try {
        inputSource.setByteStream(inputStream);
        digester.push(this);
        digester.parse(inputSource);
    } catch (SAXParseException spe) {
    } finally {
        try {
            inputStream.close();
        } catch (IOException e) {
            // Ignore
        }
    }

    getServer().setCatalina(this);
    getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
    getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());

    // 初始化server
    try {
        getServer().init();
    } catch (LifecycleException e) {

    }
}

这里我们重点关注createStartDigester()方法,它加载了Server的配置文件。

Catalina
protected Digester createStartDigester() {
    long t1=System.currentTimeMillis();
    // Initialize the digester
    Digester digester = new Digester();
    digester.setValidating(false);
    digester.setRulesValidation(true);
    HashMap<Class<?>, List<String>> fakeAttributes = new HashMap<>();
    ArrayList<String> attrs = new ArrayList<>();
    attrs.add("className");
    fakeAttributes.put(Object.class, attrs);
    digester.setFakeAttributes(fakeAttributes);
    digester.setUseContextClassLoader(true);

    // Configure the actions we will be using
    digester.addObjectCreate("Server",
                             "org.apache.catalina.core.StandardServer",
                             "className");
    digester.addSetProperties("Server");
    digester.addSetNext("Server",
                        "setServer",
                        "org.apache.catalina.Server");

    digester.addObjectCreate("Server/GlobalNamingResources",
                             "org.apache.catalina.deploy.NamingResourcesImpl");
    digester.addSetProperties("Server/GlobalNamingResources");
    digester.addSetNext("Server/GlobalNamingResources",
                        "setGlobalNamingResources",
                        "org.apache.catalina.deploy.NamingResourcesImpl");

    digester.addObjectCreate("Server/Listener",
                             null, // MUST be specified in the element
                             "className");
    digester.addSetProperties("Server/Listener");
    digester.addSetNext("Server/Listener",
                        "addLifecycleListener",
                        "org.apache.catalina.LifecycleListener");

    digester.addObjectCreate("Server/Service",
                             "org.apache.catalina.core.StandardService",
                             "className");
    digester.addSetProperties("Server/Service");
    digester.addSetNext("Server/Service",
                        "addService",
                        "org.apache.catalina.Service");

    digester.addObjectCreate("Server/Service/Listener",
                             null, // MUST be specified in the element
                             "className");
    digester.addSetProperties("Server/Service/Listener");
    digester.addSetNext("Server/Service/Listener",
                        "addLifecycleListener",
                        "org.apache.catalina.LifecycleListener");

    //Executor
    digester.addObjectCreate("Server/Service/Executor",
                     "org.apache.catalina.core.StandardThreadExecutor",
                     "className");
    digester.addSetProperties("Server/Service/Executor");

    digester.addSetNext("Server/Service/Executor",
                        "addExecutor",
                        "org.apache.catalina.Executor");


    digester.addRule("Server/Service/Connector",
                     new ConnectorCreateRule());
    digester.addRule("Server/Service/Connector",
                     new SetAllPropertiesRule(new String[]{"executor", "sslImplementationName"}));
    digester.addSetNext("Server/Service/Connector",
                        "addConnector",
                        "org.apache.catalina.connector.Connector");

    digester.addObjectCreate("Server/Service/Connector/SSLHostConfig",
                             "org.apache.tomcat.util.net.SSLHostConfig");
    digester.addSetProperties("Server/Service/Connector/SSLHostConfig");
    digester.addSetNext("Server/Service/Connector/SSLHostConfig",
            "addSslHostConfig",
            "org.apache.tomcat.util.net.SSLHostConfig");

    digester.addRule("Server/Service/Connector/SSLHostConfig/Certificate",
                     new CertificateCreateRule());
    digester.addRule("Server/Service/Connector/SSLHostConfig/Certificate",
                     new SetAllPropertiesRule(new String[]{"type"}));
    digester.addSetNext("Server/Service/Connector/SSLHostConfig/Certificate",
                        "addCertificate",
                        "org.apache.tomcat.util.net.SSLHostConfigCertificate");

    digester.addObjectCreate("Server/Service/Connector/Listener",
                             null, // MUST be specified in the element
                             "className");
    digester.addSetProperties("Server/Service/Connector/Listener");
    digester.addSetNext("Server/Service/Connector/Listener",
                        "addLifecycleListener",
                        "org.apache.catalina.LifecycleListener");

    digester.addObjectCreate("Server/Service/Connector/UpgradeProtocol",
                              null, // MUST be specified in the element
                              "className");
    digester.addSetProperties("Server/Service/Connector/UpgradeProtocol");
    digester.addSetNext("Server/Service/Connector/UpgradeProtocol",
                        "addUpgradeProtocol",
                        "org.apache.coyote.UpgradeProtocol");

// Add RuleSets for nested elements
    digester.addRuleSet(new NamingRuleSet("Server/GlobalNamingResources/"));
    digester.addRuleSet(new EngineRuleSet("Server/Service/"));
    digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));
    digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/"));
    addClusterRuleSet(digester, "Server/Service/Engine/Host/Cluster/");
    digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/Host/Context/"));

    // When the 'engine' is found, set the parentClassLoader.
    digester.addRule("Server/Service/Engine",
                     new SetParentClassLoaderRule(parentClassLoader));
    addClusterRuleSet(digester, "Server/Service/Engine/Cluster/");

    long t2=System.currentTimeMillis();
    if (log.isDebugEnabled()) {
        log.debug("Digester for server.xml created " + ( t2-t1 ));
    }
    return (digester);
}

这个有点长,我们可以看到它其实就是针对Server.xml进行了实例化。它的原理是利用sax的DefaultHandler2来解析各个不同的对象。

三,Lifecycle初始化init方法

当Server实例化之后,接下来就需要调用它的init方法了。我们看到init实现了Lifecycle的init接口。那么Lifecycle是什么呢?从字面意思来看就是生命周期。它有初始化,启动,停止,销毁等状态。容器里的Server,Service,Engine,Host,Context等都有自己的生命周期。
下面我们看看它的init方法。

3.1 LifecycleBase的init

LifecycleBase
public final synchronized void init() throws LifecycleException {
    //检查当前状态
    if (!state.equals(LifecycleState.NEW)) {
        invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
    }
    //生命周期的状态,设置标志位表示当前正在初始化中,状态改变的时候可能会触发监听器做一些事情
    setStateInternal(LifecycleState.INITIALIZING, null, false);

    try {
        initInternal();
    } catch (Throwable t) {

    }
    //生命周期的状态,设置标志位表示当前正在初始化完成
    setStateInternal(LifecycleState.INITIALIZED, null, false);
}

3.2 StandardServer的initInternal

我们看initInternal方法:

StandardServer
protected void initInternal() throws LifecycleException {
    //初始化Mbean服务并注册mbean 
    super.initInternal();
    //注册命名服务
    globalNamingResources.init();
    //初始化Service
    for (int i = 0; i < services.length; i++) {
        services[i].init();
    }
}

我们可以看到Server接着又初始化了Service的init方法。

3.3 StandardService的initInternal

StandardService的initInternal主要做的就是初始化Connector和Container。

protected void initInternal() throws LifecycleException {

    super.initInternal();

    //container初始化
    if (container != null) {
        container.init();
    }

    // 初始化Executors
    for (Executor executor : findExecutors()) {
        if (executor instanceof JmxEnabled) {
            ((JmxEnabled) executor).setDomain(getDomain());
        }
        executor.init();
    }

    // Initialize mapper listener
    mapperListener.init();

    // Initialize our defined Connectors
    synchronized (connectorsLock) {
        for (Connector connector : connectors) {
            try {
                //connector初始化
                connector.init();
            } catch (Exception e) {
            }
        }
    }
}

我们可以看到,一个Service对于一个Container和多个Connector。
在server.xml文件中。有2个Connector:

 server.xml
<Connector port="8080" protocol="HTTP/1.1"
           connectionTimeout="20000"
           redirectPort="8443" />

 <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />

他们分别接受Http和Ajp协议。由于Container和Connector的初始化流程太长,放到后面讲。

六,启动服务Server

初始化所有的节点后,接下来就是启动Server了。我们先看Server的start方法。同样的,Server继承了LifecycleBase的start方法。

6.1 Lifecycle的start

LifecycleBase
public final synchronized void start() throws LifecycleException {

    if (LifecycleState.STARTING_PREP.equals(state) ||
            LifecycleState.STARTING.equals(state) ||
            LifecycleState.STARTED.equals(state)) {

        return;
    }

    if (state.equals(LifecycleState.NEW)) {
        init();
    } else if (state.equals(LifecycleState.FAILED)){
        stop();
    } else if (!state.equals(LifecycleState.INITIALIZED) &&
            !state.equals(LifecycleState.STOPPED)) {
        invalidTransition(Lifecycle.BEFORE_START_EVENT);
    }

    setStateInternal(LifecycleState.STARTING_PREP, null, false);

    try {
        startInternal();
    } catch (Throwable t) {

    }

    if (state.equals(LifecycleState.FAILED) ||
            state.equals(LifecycleState.MUST_STOP)) {
        stop();
    } else {
        // Shouldn't be necessary but acts as a check that sub-classes are
        // doing what they are supposed to.
        if (!state.equals(LifecycleState.STARTING)) {
            invalidTransition(Lifecycle.AFTER_START_EVENT);
        }
        setStateInternal(LifecycleState.STARTED, null, false);
    }
}

我们看到start会根据当前的状态做判断,我们只看startInternal方法,由于这个方法是个抽象方法,由之类实现,所以接下来我们看Server的startInternal。

6.2 Server的startInternal

StandardServer
protected void startInternal() throws LifecycleException {

    fireLifecycleEvent(CONFIGURE_START_EVENT, null);
    setState(LifecycleState.STARTING);

    globalNamingResources.start();

    // Start our defined Services
    synchronized (servicesLock) {
        for (int i = 0; i < services.length; i++) {
            services[i].start();
        }
    }
}

Server的startInternal方法会启动Service的start方法。同样,会触发Service的startInternal方法。

6.3 Service的startInternal

protected void startInternal() throws LifecycleException {
    setState(LifecycleState.STARTING);
    // Start our defined Container first
    if (container != null) {
        synchronized (container) {
            container.start();
        }
    }

    synchronized (executors) {
        for (Executor executor: executors) {
            executor.start();
        }
    }

    mapperListener.start();

    // Start our defined Connectors second
    synchronized (connectorsLock) {
        for (Connector connector: connectors) {
            try {
                // If it has already failed, don't try and start it
                if (connector.getState() != LifecycleState.FAILED) {
                    connector.start();
                }
            } catch (Exception e) {
                log.error(sm.getString(
                        "standardService.connector.startFailed",
                        connector), e);
            }
        }
    }
}

同样,在这段代码中,也调用了container和connector的start方法。在接下来的文章中,我将详细介绍container和connector的启动方式。

五,流程图

说了这么多,没图怎么能行呢?下面就是Boostart到Catalina加载的流程图。

tomcat8源码阅读(二)--启动一

一,前言

在本文中,我将主要介绍tomcat的启动阶段,包括main方法的运行,配置文件的加载,对象的实例化关联等。

二,Bootstrap的mian

当我们调试tomcat的时候,是从Bootstrap的main方法开始启动的。那这个main方法主要做了哪些工作,我们看看内部实现。

Bootstrap    
public static void main(String args[]) {
     Bootstrap bootstrap = new Bootstrap();
     //初始化
     bootstrap.init();
     daemon = bootstrap;

    try {
        String command = "start";
        if (args.length > 0) {
            command = args[args.length - 1];
        }
        if (command.equals("startd")) {
            args[args.length - 1] = "start";
            daemon.load(args);
            daemon.start();
        } else if (command.equals("stopd")) {
            args[args.length - 1] = "stop";
            daemon.stop();
        } else if (command.equals("start")) {
            daemon.setAwait(true);
            daemon.load(args);
            daemon.start();
        } else if (command.equals("stop")) {
            daemon.stopServer(args);
        }else other ...                 
    } catch (Throwable t) {
        ...//异常
        System.exit(1);
    }

}

代码很简单,创建一个Bootstrap,然后初始化init,最后调用start方法启动。

三,init方法

那么初始化方法做了什么呢?我们接下来看看:

Bootstrap
public void init() throws Exception {
    //初始化类加载器
    initClassLoaders();
    //设置当前上下文类加载器为catalinaLoader
    Thread.currentThread().setContextClassLoader(catalinaLoader);

    ...//略
    Class<?> startupClass =
        catalinaLoader.loadClass
        ("org.apache.catalina.startup.Catalina");
    //加载类Catalina,并实例化
    Object startupInstance = startupClass.newInstance();
    String methodName = "setParentClassLoader";
    Class<?> paramTypes[] = new Class[1];
    paramTypes[0] = Class.forName("java.lang.ClassLoader");
    Object paramValues[] = new Object[1];
    paramValues[0] = sharedLoader;
    Method method =
        startupInstance.getClass().getMethod(methodName, paramTypes);
    //调用Catalina的setParentClassLoader方法设置父类加载器为catalinaLoader
    method.invoke(startupInstance, paramValues);

    catalinaDaemon = startupInstance;
}

初始化做了2件事,第一件事是初始化类加载器,并设置了线程上下文类加载器。第二件事是初始化Catalina,并设置它的父类加载器为当前线程上下文类加载器。

3.1,初始化类加载器

接下来我们就看看类加载器是如何初始化的。

Bootstrap
private void initClassLoaders() {
    try {
        commonLoader = createClassLoader("common", null);
        if( commonLoader == null ) {
            commonLoader=this.getClass().getClassLoader();
        }
        catalinaLoader = createClassLoader("server", commonLoader);
        sharedLoader = createClassLoader("shared", commonLoader);
    } catch (Throwable t) {
        //....异常
        System.exit(1);
    }
}

上面代码创建了3个类加载器,至于这3个的用处,以后再说,我们看是如何实现的。

private ClassLoader createClassLoader(String name, ClassLoader parent)
    throws Exception {

    String value = CatalinaProperties.getProperty(name + ".loader");//第一处
    if ((value == null) || (value.equals("")))
        return parent;

    value = replace(value);

    List<Repository> repositories = new ArrayList<>();

    String[] repositoryPaths = getPaths(value);

    for (String repository : repositoryPaths) {
        // Check for a JAR URL repository
        try {
            @SuppressWarnings("unused")
            URL url = new URL(repository);
            repositories.add(
                    new Repository(repository, RepositoryType.URL));
            continue;
        } catch (MalformedURLException e) {
            // Ignore
        }

        // Local repository
        if (repository.endsWith("*.jar")) {
            repository = repository.substring
                (0, repository.length() - "*.jar".length());
            repositories.add(
                    new Repository(repository, RepositoryType.GLOB));
        } else if (repository.endsWith(".jar")) {
            repositories.add(
                    new Repository(repository, RepositoryType.JAR));
        } else {
            repositories.add(
                    new Repository(repository, RepositoryType.DIR));
        }
    }

    return ClassLoaderFactory.createClassLoader(repositories, parent);
}

我们看上面代码第一处,从属性文件中获取值。
我们打开conf/catalina.properties文件,发现

catalina.properties
common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
server.loader=
shared.loader=

只有common.loader才有值,表示加载的是tomcatlib目录下的jar包。由于server.loader和shared.loader为空,catalinaLoader和sharedLoader都会引用的commonLoader。
最后我们看具体的创建过程,即ClassLoaderFactory.createClassLoader(repositories, parent)方法。

    public static ClassLoader createClassLoader(List<Repository> repositories,
                                            final ClassLoader parent)
    throws Exception {
    Set<URL> set = new LinkedHashSet<>();
    if (repositories != null) {
        for (Repository repository : repositories)  {
            ...//略去其他类型
            File directory = new File(repository.getLocation());
            directory = directory.getCanonicalFile();
            if (!validateFile(directory, RepositoryType.DIR)) {
                continue;
            }
            URL url = directory.toURI().toURL();
            if (log.isDebugEnabled())
                log.debug("  Including directory " + url);
            set.add(url);
        }
    }

    // Construct the class loader itself
    final URL[] array = set.toArray(new URL[set.size()]);

    return AccessController.doPrivileged(
            new PrivilegedAction<URLClassLoader>() {
                @Override
                public URLClassLoader run() {
                    if (parent == null)
                        return new URLClassLoader(array);
                    else
                        return new URLClassLoader(array, parent);
                }
            });
}

最后我们从代码中可以看到,生成了一个URLClassLoader加载器。记得tomcat6的时候这里生成的StandardClassLoader,看来StandardClassLoader没啥作用,在后面的tomcat中移除了。

3.2,初始化Catalina

初始化Catalina用反射即可,不多说了。

四,start启动

初始化完成之后,就可以启动了。我们看看启动start方法。

Bootstrap
//设置等待标志,为关闭做准备,暂时不关注
daemon.setAwait(true);
//加载参数,无参数,暂时不关注
daemon.load(args);
//启动
daemon.start();

public void start()
    throws Exception {
    if( catalinaDaemon==null ) init();
    Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
    method.invoke(catalinaDaemon, (Object [])null);
}

start方法很简单,利用反射调用了catalina的start方法。
Catalina的start方法做了哪些事情呢?我们下文继续。

tomcat8源码阅读(一)--准备

一,前言

以前看过《how tomcat works》,那时候是以tomcat5.0为基础进行讲解的。现在有必要进行巩固下,更深入的了解tomcat的运行机制,也了解下tomcat6的nio方式,及最新的tomcat变化。本来最开始想研究的是tomcat7,看到市面上有很多人已经研究了,那索性来看看8。

二,搭建源码包

其实tomcat7和tomcat8搭建方式都差不多,2者本质上并无太大差异。
我们可以参考Tomcat7调试运行环境搭建与源代码分析入门的方法进行tomcat8的搭建。
搭建比较简单,我也备份了一份到github上https://github.com/sqtds/tomcat8。 如果觉得搭建麻烦,可以直接下载使用我的即可。

三,tomcat8整体架构

参考我的wiki页面https://github.com/sqtds/tomcat8/wiki,这里不不多说了。
本文主要研究的是tomcat的catalina架构,其他有时间再进行研究。

四,参考指南

hessian协议解析--hessian反序列化

一,前言

上篇文章我们介绍了hessian的序列化机制,那么,它是怎么反序列化的呢,我们接下来看看。

二,常规反序列化

反序列化比较简单,在上一文的最后,hessian已经定义了第一个字节不同的数字代表的不同的含义。那么,我们根据这些数字进行反解即可。例如,我们遇到第一个字节为’N’的,即表示空。在x00 - x1f之间,表示长度在32之内的字符串。在x80 - xbf表示-x10到x3f之间的整数等等。好了,不多说了,直接上代码。