Tomcat调优指南

摘要: Tomcat安装:wget http://mirrors.tuna.tsinghua.edu.cn/apache/tomcat/tomcat-8/v8.5.31/bin/apache-tomcat-8.5.31.tar.gz 解压缩后直接在apache/bin目录下./startup.sh 启动小技巧:当出现Tomcat一直卡在启动页面时,可以是因为Java.security配置文件里写的是/dev/random,/dev/random和/dev/urandom是Linux系统中提供的随机伪设备,这两个设备的任务,是提供永不为空的随机字节数据流。

Tomcat安装:

wget http://mirrors.tuna.tsinghua.edu.cn/apache/tomcat/tomcat-8/v8.5.31/bin/apache-tomcat-8.5.31.tar.gz

未分类

解压缩后直接在apache/bin目录下./startup.sh

启动小技巧:
当出现Tomcat一直卡在启动页面时,可以是因为Java.security配置文件里写的是/dev/random,/dev/random和/dev/urandom是Linux系统中提供的随机伪设备,这两个设备的任务,是提供永不为空的随机字节数据流。很多解密程序与安全应用程序(如SSH Keys,SSL Keys等)需要它们提供的随机数据流。当使用/dev/random因为需要生成随机数如果没有完成这个随机数的创建就会一直卡在启动页面。建议找到jdk1.x.x_xx/jre/lib/security/Java.security文件,在文件中找到securerandom.source这个设置项,将其改为:
securerandom.source=file:/dev/unrandom

在 apache/conf/server.xml目录下 tomcat默认参数设置:

  • maxThreads:tomcat可用于请求处理的最大线程数,默认是200 —-连接数
  • minSpareThreads:tomcat初始线程数,即最小空闲线程数 == minProcessors相同
  • maxSpareThreads:tomcat最大空闲线程数,超过的会被关闭==maxThreads==maxProcessors
  • acceptCount:当所有可以使用的处理请求的线程数都被使用时,可以放到处理队列中的请求数,超过这个数的请求将不予处理.默认100— 最大排队数

  • minProcessors:最小连接线程数,用于提高系统处理性能,默认值为10

  • maxProcessors:最大连接线程数,即:并发处理的最大请求数,默认值为75
  • enableLookups:是否反查域名,取值为:true或false。为了提高处理能力,应设置为false
  • connectionTimeout:网络连接超时,单位:毫秒。设置为0表示永不超时,这样设置有隐患的。通常可设置为30000毫秒。

其中和最大连接数相关的参数为maxProcessors和acceptCount。如果要加大并发连接数,应同时加大这两个参数。

最大连接数 maxThreads
tomcat同时处理的线程数。

配置依据:

(1)、部署的程序偏计算型,主要利用cpu资源,应该将该参数设置小一点,减小同一时间抢占cpu资源的线程个数。
(2)、部署的程序对io、数据库占用时间较长,线程处于等待的时间较长,应该将该参数调大一点,增加处理个数。
最大排队数 acceptCount
当tomcat的线程数达到maxThreads后,新的请求就会排队等待,超过排队数的请求会被拒绝。
我一般设置和maxThreads相同。

“maxPostSize”该参数限制了post方式上传文件的大小,当maxPostSize<=0时,POST方式上传的文件大小不会被限制,maxPostSize参数只有当request的Content-Type为“application/x-www-form-urlencoded”时起作用。
“maxHttpHeaderSize”来自于客户端请求的Request和Response的HTTP,http请求头信息的最大程度,超过此长度的部分不予处理,一般8K。
“maxThreads”客户请求最大线程数,Tomcat使用线程来处理接收的每个请求。这个值表示Tomcat可创建的最大的线程数。
“minSpareThreads”最小空闲线程数,Tomcat初始化时创建的 socket 线程数.
“maxSpareThreads”最大连接线程数,即:并发处理的最大请求数,默认值为75,一旦创建的线程超过这个值,Tomcat就会关闭不再需要的socket线程。
“minProcessors”最小空闲连接线程数,用于提高系统处理性能,默认值为 10。
“acceptCount”允许的最大连接数,应大于等于 maxProcessors ,默认值为 100。
“enableLookups”若设为true, 则支持域名解析,可把 ip 地址解析为主机名,为了提高处理能力,应设置为false。
“compression”打开压缩功能。
“compressionMinSize “启用压缩的输出内容大小,这里面默认为2KB
“compressableMimeType”压缩类型。
“connectionTimeout”网络连接超时,单位:毫秒。设置为 0 表示永不超时,这样设置有隐患的。通常可设置为 30000 毫秒。
“URIEncoding”URL统一编码 。
“redirectPort”这里系统默认的,它指定转发端口,如果当前只支持non-SSL请求,在需要安全通信的场所,将把客户请求转发至SSL的redirectPort端口。
“disableUploadTimeout”上传时是否使用超时机制,如果不指定,该属性为“false”。

上述配置读者可通过实际业务需求进行调整,达到tomcat性能最优,关于更多tomcat详细部署读者可参考笔者的该篇文章,希望能有所帮助。

一个有意思的Tomcat 异常

之前有位读者留言,说了一个 Tomcat 异常的问题。
即 Tomcat 各功能正常,不影响使用,但是偶尔的在日志中会看到类似于这样的异常信息:

INFO [https-apr-8443-exec-5] org.apache.coyote.http11.Http11Processor.service Error parsing HTTP request header 
 Note: further occurrences of HTTP header parsing errors will be logged at DEBUG level. 
 java.lang.IllegalArgumentException: Invalid character (CR or LF) found in method name 
    at org.apache.coyote.http11.Http11InputBuffer.parseRequestLine(Http11InputBuffer.java:443) 
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:982) 

为啥报这个呢?明明自己没做什么操作。

顺着异常信息我们往上看,首先这个提示是解析请求头出现的错误。更细节一些是解析请求头中第一行,所谓的「Request Line」的时候出了问题。

什么是「Request Line」呢? 就是HTTP 规范中指定的,以请求方法开头 再加上请求URI 等。具体看这个规范说明

未分类

这里我们的异常信息提示我们是在解析 Method name的时候出了问题。看规范里说了「The Request-Line begins with a method token」也就是有固定的东西的,不是啥都能叫一个method name。我们熟悉的GET/POST/PUT/DELETE都是这里允许的。

我们再来看 Tomcat 的源码,是如何判断这里的 Requet Line 是不是一个包含一个合法的 method name。
顺着异常的类和方法,轻车熟路,直接就能看到了。

if (parsingRequestLinePhase == 2) { 
    // 
    // Reading the method name 
    // Method name is a token 
    // 
    boolean space = false; 
    while (!space) { 
        // Read new bytes if needed 
        if (byteBuffer.position() >= byteBuffer.limit()) { 
            if (!fill(false)) // request line parsing 
                return false; 
        } 
        // Spec says method name is a token followed by a single SP but 
        // also be tolerant of multiple SP and/or HT. 
        int pos = byteBuffer.position(); 
        byte chr = byteBuffer.get(); 
        if (chr == Constants.SP || chr == Constants.HT) { 
            space = true; 
            request.method().setBytes(byteBuffer.array(), parsingRequestLineStart, 
                    pos - parsingRequestLineStart); 
        } else if (!HttpParser.isToken(chr)) { 
            byteBuffer.position(byteBuffer.position() - 1); 
            throw new IllegalArgumentException(sm.getString("iib.invalidmethod")); 
        } 
    } 
    parsingRequestLinePhase = 3; 
} 

我们注意红色的异常就是上面产生的内容。产生这个是由于读取的byte 不是个 SP 同时下面的 isToken 也不是true导致。
那Token都有谁是怎么定义的?
这里挺有意思的,直接用一个boolean数组来存,前面我们传进来的byte,对应的是这个数组的下标。

public static boolean isToken(int c) { 
    // Fast for correct values, slower for incorrect ones 
    try { 
        return IS_TOKEN[c]; 
    } catch (ArrayIndexOutOfBoundsException ex) { 
        return false; 
    } 
} 

这里的boolean数组,初始化时有几个关联的数组一起,长度为128。

private static final boolean[] IS_CONTROL = new boolean[ARRAY_SIZE]; 
private static final boolean[] IS_SEPARATOR = new boolean[ARRAY_SIZE]; 
private static final boolean[] IS_TOKEN = new boolean[ARRAY_SIZE]; 
// Control> 0-31, 127 
if (i < 32 || i == 127) { 
    IS_CONTROL[i] = true; 
} 
// Separator 
if (    i == '(' || i == ')' || i == '<' || i == '>'  || i == '@'  || 
        i == ',' || i == ';' || i == ':' || i == '\' || i == '"' || 
        i == '/' || i == '[' || i == ']' || i == '?'  || i == '='  || 
        i == '{' || i == '}' || i == ' ' || i == 't') { 
    IS_SEPARATOR[i] = true; 
} 

// Token: Anything 0-127 that is not a control and not a separator 
if (!IS_CONTROL[i] && !IS_SEPARATOR[i] && i < 128) { 
    IS_TOKEN[i] = true; 
} 

所以这里token的定义明确了,非控制字符,非分隔符,ascii 码小于128 的都是 token。

所以问题产生原因定位了,是由于我们的请求头中传递了「非法」方法名称,导致请求不能正确处理。

我们来看一个正常的请求信息

未分类

Request Line 就是上面看到的第一行内容。 GET /a/ HTTP/1.1

那有问题的内容大概是这个样子

未分类

谁能从上面解析出来请求方法?

这时你可能会问,正常请求都好好的,你这个怎么搞的?

对。正常没问题,如果我们的Connector 是普通的此时可以响应请求,如果你一直http://localhost:port/a ,可以正常响应,此时后台收到一个https://localhost:port1/a,你要怎么响应?

要知道这两个编码大不一样。所以就出现了本文开头的问题。
如果不想走寻常路,可以自己写个Socket ,连到 Tomcat Server上,发个不合法的请求,大概也是一个样子。

那出现了这类问题怎么排查呢? 别忘了 Tomcat 提供了一系列有用的 Valve ,其中一个查看请求的叫AccessLogValve(阀门(Valve)常打开,快发请求过来 | Tomcat的AccessLogValve介绍)

在 Log里可以查看每个到达的请求来源IP,请求协议,响应状态,请求方法等。但是如果上面的异常产生时,请求方法这类有问题的内容也是拿不到的,此时的response status 是400 。但通过IP我们能看到是谁在一直请求。如果判断是非法请求后,可以再增加我们的过滤Valve,直接将其设置为Deny就OK了。

Tomcat是怎样处理搜索引擎爬虫请求的?

每个置身于互联网中的站点,都需要搜索引擎的收录,以及在适时在结果中的展现,从而将信息提供给用户、读者。而搜索引擎如何才能收录我们的站点呢?

这就涉及到一个「搜索引擎的爬虫」爬取站点内容的过程。只有被搜索引擎爬过并收录的内容才有机会在特定query命中之后在结果中展现。

这些搜索引擎内容的工具,又被称为爬虫、Sprider,Web crawler 等等。我们一方面欢迎其访问站点以便收录内容,一方面又因其对于正常服务的影响头疼。毕竟 Spider 也是要占用服务器资源的, Spider 太多太频繁的资源占用,正常用户请求处理就会受到影响。所以一些站点干脆直接为搜索引擎提供了单独的服务供其访问,其他正常的用户请求走另外的服务器。

说到这里需要提一下,对于是否是 Spider 的请求识别,是通过HTTP 请求头中的User-Agent 字段来判断的,每个搜索引擎有自己的独立标识。而且通过这些内容,管理员也可以在访问日志中了解搜索引擎爬过哪些内容。

此外,在对搜索引擎的「爬取声明文件」robots.txt中,也会有类似的User-agent 描述。比如下面是taobao 的robots.txt描述

User-agent:  Baiduspider 
Allow:  /article 
Allow:  /oshtml 
Disallow:  /product/ 
Disallow:  / 

User-Agent:  Googlebot 
Allow:  /article 
Allow:  /oshtml 
Allow:  /product 
Allow:  /spu 
Allow:  /dianpu 
Allow:  /oversea 
Allow:  /list 
Disallow:  / 

User-agent:  Bingbot 
Allow:  /article 
Allow:  /oshtml 
Allow:  /product 
Allow:  /spu 
Allow:  /dianpu 
Allow:  /oversea 
Allow:  /list 
Disallow:  / 

User-Agent:  360Spider 
Allow:  /article 
Allow:  /oshtml 
Disallow:  / 

User-Agent:  Yisouspider 
Allow:  /article 
Allow:  /oshtml 
Disallow:  / 

User-Agent:  Sogouspider 
Allow:  /article 
Allow:  /oshtml 
Allow:  /product 
Disallow:  / 

User-Agent:  Yahoo!  Slurp 
Allow:  /product 
Allow:  /spu 
Allow:  /dianpu 
Allow:  /oversea 
Allow:  /list 
Disallow:  / 

我们再来看 Tomcat对于搜索引擎的请求做了什么特殊处理呢?
对于请求涉及到 Session,我们知道通过 Session,我们在服务端得以识别一个具体的用户。那 Spider 的大量请求到达后,如果访问频繁同时请求量大时,就需要创建巨大量的 Session,需要占用和消耗很多内存,这无形中占用了正常用户处理的资源。

为此, Tomcat 提供了一个 「Valve」,用于对 Spider 的请求做一些处理。

首先识别 Spider 请求,对于 Spider 请求,使其使用相同的 SessionId继续后面的请求流程,从而避免创建大量的 Session 数据。
这里需要注意,即使Spider显式的传了一个 sessionId过来,也会弃用,而是根据client Ip 来进行判断,即对于 相同的 Spider 只提供一个Session。

我们来看代码:

// If the incoming request has a valid session ID, no action is required 
if (request.getSession(false) == null) { 

    // Is this a crawler - check the UA headers 
    Enumeration<String> uaHeaders = request.getHeaders("user-agent"); 
    String uaHeader = null; 
    if (uaHeaders.hasMoreElements()) { 
        uaHeader = uaHeaders.nextElement(); 
    } 

    // If more than one UA header - assume not a bot 
    if (uaHeader != null && !uaHeaders.hasMoreElements()) { 
        if (uaPattern.matcher(uaHeader).matches()) { 
            isBot = true; 
            if (log.isDebugEnabled()) { 
                log.debug(request.hashCode() + 
                        ": Bot found. UserAgent=" + uaHeader); 
            } 
        } 
    } 

    // If this is a bot, is the session ID known? 
    if (isBot) { 
        clientIp = request.getRemoteAddr(); 
        sessionId = clientIpSessionId.get(clientIp); 
        if (sessionId != null) { 
            request.setRequestedSessionId(sessionId); // 重用session 
        } 
    } 
} 

getNext().invoke(request, response); 

if (isBot) { 
    if (sessionId == null) { 
        // Has bot just created a session, if so make a note of it 
        HttpSession s = request.getSession(false); 
        if (s != null) { 
            clientIpSessionId.put(clientIp, s.getId()); //针对Spider生成session 
            sessionIdClientIp.put(s.getId(), clientIp); 
            // #valueUnbound() will be called on session expiration 
            s.setAttribute(this.getClass().getName(), this); 
            s.setMaxInactiveInterval(sessionInactiveInterval); 

            if (log.isDebugEnabled()) { 
                log.debug(request.hashCode() + 
                        ": New bot session. SessionID=" + s.getId()); 
            } 
        } 
    } else { 
        if (log.isDebugEnabled()) { 
            log.debug(request.hashCode() + 
                    ": Bot session accessed. SessionID=" + sessionId); 
        } 
    } 
} 

判断Spider 是通过正则

private String crawlerUserAgents = 
    ".*[bB]ot.*|.*Yahoo! Slurp.*|.*Feedfetcher-Google.*"; 
// 初始化Valve的时候进行compile 
uaPattern = Pattern.compile(crawlerUserAgents); 

这样当 Spider 到达的时候就能通过 User-agent识别出来并进行特别处理从而减小受其影响。

这个 Valve的名字是:「CrawlerSessionManagerValve」,好名字一眼就能看出来作用。

其他还有问题么?我们看看,通过ClientIp来判断进行Session共用。
最近 Tomcat 做了个bug fix,原因是这种通过ClientIp的判断方式,当 Valve 配置在Engine下层,给多个Host 共用时,只能有一个Host生效。 fix之后,对于请求除ClientIp外,还有Host和 Context的限制,这些元素共同组成了 client标识,就能更大程度上共用Session。
修改内容如下:

未分类

总结下, 该Valve 通过标识识别出 Spider 请求后,给其分配一个固定的Session,从而避免大量的Session创建导致我资源占用。
默认该Valve未开启,需要在 server.xml中 增加配置开启。另外我们看上面提供的 正则 pattern,和taobao 的robots.txt对比下,你会出现并没有包含国内的这些搜索引擎的处理,这个时候怎么办呢?
在配置的时候传一下进来就OK啦,这是个public 的属性

public void setCrawlerUserAgents(String crawlerUserAgents) { 
    this.crawlerUserAgents = crawlerUserAgents; 
    if (crawlerUserAgents == null || crawlerUserAgents.length() == 0) { 
        uaPattern = null; 
    } else { 
        uaPattern = Pattern.compile(crawlerUserAgents); 
    } 
}

Tomcat禁用端口方法

tomcat默认启用端口解释:

  • 8005,tomcat关闭端口。
  • 8080,tomcat页面http端口。通常我们会修改为80
  • 8009,tomcat-AJP代理端口,通常做负载均衡用

多套tomcat防端口冲突方法:

1、修改conf/server.xml中的三个端口:8005,8080,8009,分别重置为新端口。这个感觉操作起来有些繁琐,

2、建议tomcat仅使用单个 http 端口。

2-1、禁用关闭端口

将 Server 元素的 port 属性设置为 -1,这将禁用 Tomcat 关闭端口。
我们强制tomcat不使用关闭端口,通过使用“kill -9 PID”命令来停止任何独立应用程序实例。
只需要将tomcat/conf/server.xml中的:

<Server port="8005" shutdown="SHUTDOWN"> 

修改为

<Server port="-1" command="SHUTDOWN"> 

2-2、禁用AJP
应通过注释掉tomcat/conf/server.xml中显示的节来禁用 AJP 连接器,以确保我们不会遇到端口冲突。

    <!-- Define an AJP 1.3 Connector on port 8009 --> 
    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" /> 

修改为:

    <!-- Define an AJP 1.3 Connector on port 8009 
    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" /> 
    --> 

2-3、修改http端口为80

    <Connector port="80" protocol="org.apache.coyote.http11.Http11NioProtocol"  
               connectionTimeout="20000"  
               redirectPort="8443" maxThreads="150"/> 

Tomcat 安装配置与优化

什么是 Tomcat

Tomcat 是 Java web 服务器中使用最广的中间件,jsp 必须要使用类似 Tomcat 这样的程序进行解析。

Tomcat是Apache 软件基金会(Apache Software Foundation)的Jakarta 项目中的一个核心项目,由Apache、Sun 和其他一些公司及个人共同开发而成。因为Tomcat 技术先进、性能稳定,而且免费,因而深受Java 爱好者的喜爱并得到了部分软件开发商的认可,成为目前比较流行的Web 应用服务器。 Tomcat 是一个轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试JSP 程序的首选。对于一个初学者来说,可以这样认为,当在一台机器上配置好Apache 服务器,可利用它响应对HTML 页面的访问请求。实际上Tomcat 部分是Apache 服务器的扩展,但它是独立运行的,所以当你运行tomcat 时,它实际上作为一个与Apache 独立的进程单独运行的。

安装 Java

1、下载

wget -c http://file201503.oss-cn-shanghai.aliyuncs.com/ftp/jdk-8u151-linux-x64.tar.gz

2、解压后放到/usr/local/下

mv jdk1.8.0_151/ /usr/local/jdk18  

3、配置环境变量

vim /etc/profile  

输入

export JAVA_HOME=/usr/local/jdk18
export PATH=$JAVA_HOME/bin:$PATH
export CLASSPATH=.$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar 

保存后执行

source /etc/profile    

安装 Tomcat

1、打开官网下载

未分类

wget -c http://supergsego.com/apache/tomcat/tomcat-8/v8.5.29/bin/apache-tomcat-8.5.29.tar.gz

解压后复制3份分别为 Tomcat1 Tomcat2 Tomcat3

[root@centos ~]# cd /usr/local/tomcat
tomcat1/ tomcat2/ tomcat3/

分别修改3个目录下 conf 下的 server.xml 端口为 8080、8081、8082

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

启动 3 个 Tomcat

[root@centos bin]# ./catalina.sh start
Using CATALINA_BASE:   /usr/local/tomcat1
Using CATALINA_HOME:   /usr/local/tomcat1
Using CATALINA_TMPDIR: /usr/local/tomcat1/temp
Using JRE_HOME:        /usr/local/jdk18
Using CLASSPATH:       /usr/local/tomcat1/bin/bootstrap.jar:/usr/local/tomcat1/bin/tomcat-juli.jar
Tomcat started.

Tomcat 目录说明:

bin:二进制执行文件。里面最常用的文件是startup.bat,如果是 Linux 或 Mac 系统启动文件为 startup.sh。
conf:配置目录。里面最核心的文件是server.xml。可以在里面改端口号等。默认端口号是8080,也就是说,此端口号不能被其他应用程序占用。
lib:库文件。tomcat运行时需要的jar包所在的目录
logs:日志
temp:临时产生的文件,即缓存
webapps:web的应用程序。web应用放置到此目录下浏览器可以直接访问
work:编译以后的class文件。

Tomcat 自身配置域名和80端口访问

如果要使用80端口访问域名则配置

<Connector port="80" protocol="HTTP/1.1"
             connectionTimeout="20000"
             redirectPort="8443" />

         ……

<Host name="www.v5linux.com"  appBase="webapps"
          unpackWARs="true" autoDeploy="true">

      <!-- SingleSignOn valve, share authentication between web applications
           Documentation at: /docs/config/valve.html -->
      <!--
      <Valve className="org.apache.catalina.authenticator.SingleSignOn" />
      -->

      <!-- Access log processes all example.
           Documentation at: /docs/config/valve.html
           Note: The pattern used is equivalent to using pattern="common" -->
      <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" />
       <Context docBase="/data/" path="" debug="0"  reloadable="true"/>
    </Host>

配置 nginx 代理多Tomcat 访问

在 nginx http 段加入

http {
    upstream tomcatcluster {
    ip_hash;
        server 127.0.0.1:8080;
        server 127.0.0.1:8081;
        server 127.0.0.1:8082;
   }
   ……

在 server 段配置,这里我配置了80端口跳443,并且加了 ssl 证书

    server {
    listen  80;
    server_name _;
    server_name www.v5linux.com v5linux.com;
    if ($request_method !~ ^(GET|HEAD|POST)$ ) {
            return        444;
        }
    rewrite ^(.*)$  https://www.v5linux.com$1 permanent;
}
    server {
        listen       443;
        server_name  www.v5linux.com;
        ssl on;
        ssl_certificate //usr/local/nginx/ssl/v5linux/fullchain.cer;
        ssl_certificate_key /usr/local/nginx/ssl/v5linux/v5linux.key;
        ssl_ciphers                EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
        ssl_prefer_server_ciphers  on;
        ssl_protocols              TLSv1.2;
        ssl_session_cache          shared:SSL:50m;
        ssl_session_timeout        1d;
        ssl_session_tickets        on;
        resolver                   114.114.114.114 valid=300s;
        resolver_timeout           10s;

        access_log  logs/host.access.log  main;

        location / {
            root   html;
            index  index.html index.htm;
            proxy_pass http://tomcatcluster;
        }

    }

开放防火墙

iptables -A INPUT -p tcp --dport 443 -j ACCEPT

做好解析,并确认

# dig www.v5linux.com

; <<>> DiG 9.10.6 <<>> www.v5linux.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 62933
;; flags: qr aa rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;www.v5linux.com.        IN    A

;; ANSWER SECTION:
www.v5linux.com.    0    IN    A    59.111.92.121

;; Query time: 12 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Wed Apr 04 14:00:14 CST 2018
;; MSG SIZE  rcvd: 49

浏览器访问

未分类

发现没有问题

Tomcat 自身配 ssl 证书,可以在 server.xml 中这段进行配置

<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
            maxThreads="150" SSLEnabled="true">
     <SSLHostConfig>
         <Certificate certificateKeystoreFile="conf/localhost-rsa.jks"
                      type="RSA" />
     </SSLHostConfig>
 </Connector>

Tomcat 默认的路径是在 Tomcat 的目录下的 webapps ,可以在配置文件中修改

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

我们将其修改为

未分类

<Context docBase="/data/" path="" debug="0"  reloadable="true"/>    

创建 data 目录

mkdir /data/

然后新建一个 jsp 文件

 <%@ page contentType="text/html;charset=UTF-8" %>
<%
out.print("test");
%> 

访问

未分类

优化

  • java.lang.OutOfMemoryError: Java heap space异常
    表示堆内存空间满了,如果不是程序逻辑的bug,可能是因为项目中引用的jar比较多,导到内存溢出。JVM默认堆的最小使用内存为物理内存的1/64,最大使用内存为物理内存的1/4,如8G的物理内存,JVM默认堆的最小和最大内存分别为128m和2048m。通过调整JVM的-Xms(初始内存)和-Xmx(最大内存)两个参数加大内存使用限制。
  • java.lang.OutOfMemoryError: PermGen space异常
    表示静态内存区满了,通常是由于加载的类过多导致。jdk8以下版本通过修改JVM的-XX:PermSize和-XX:MaxPermSize两个参数,限制静态区最小和最大内存范围。jdk8改变了内存模型,将类定义存放到了元数据(MetaspaceSize)空间,而元数据空间是与堆空间共享同一块内存区域的,所以在JDK8以后版本不会存在PermGen space异常了,故不用设置此参数。
  • java.lang.StackOverflowError异常
    表示栈内存溢出。通常是由于死循环、无限递归导致。

修改Tomcat的内存配置,打开$TOMCAT_HOME/bin/catalina.sh文件(Windows系统是catalina.bat文件),大楖在250行左右,在JAVA_OPTS参数上添加内存参数设置即可。完整的JVM参数设置如下所示:

JAVA_OPTS="$JAVA_OPTS -server -Xms2048m -Xmx2048m -XX:PermSize=128m -XX:MaxPermSize=256 -Djava.awt.headless=true"

-server参数:表示以服务模式启动,启动速度会稍微慢一点,但性能会高很多。不加这个参数,默认是以客户端模式启动。
java.awt.headless=true参数:与图形操作有关,适用于linux系统。如生成验证码,含义是当前使用的是没有安装图安装图形界面的服务器,应用中如果获取系统显示有关参数会抛异常,可通过jmap -heap proccess_id查看设置是否成功。

未分类

修改后

未分类

并发配置优化

主要配置Tomcat能处理的请求数,当一个进程的线程数超过500个的话,那么这个进程的运行效率就很低了。表面上看线程越多处理的请求越多,其实过多的线程会占用CPU在不同线程之间切换的资源,导致CPU在每个线程上处理的时间片极期有限,反而会降低服务器的响应性能。

<Connector port="8080" protocol="org.apache.coyote.http11.Http11AprProtocol"
               connectionTimeout="20000"
               redirectPort="8443" 

                maxThreads="500"
                minSpareThreads="100"
                maxSpareThreads="200"
                acceptCount="200"
                maxIdleTime="30000"
                enableLookups="false"
               />

Tomcat的并发请求处理数量=maxThreads + acceptCount

  • maxThreads:最大能接受的请求数,默认为200
  • minSpareThreads:最少备用线程数,默认初始化,默认为25
  • maxSpareThreads:最多备用线程数,一旦创建的线程超过这个值,Tomcat就会关闭不再需要的socket线程
  • acceptCount:等待处理的请求队列,默认为100,超过队列长度,服务器则拒绝客户端请求,直接返回403
  • maxIdleTime:如果一个线程在30秒以内没有活跃,则终止运行并从线程池中移除。除非线程池数量小于或等于minSpareThreads数量。默认值是1分钟
  • enableLookups:如果为true,调用request.getRemoteHost会执行DNS反查,反向解析IP对应的域名或主机,效率较低,建议设为false。

更多参数设置,请参考Tomcat官方文档:http://tomcat.apache.org/tomcat-8.0-doc/config/http.html

Tomcat控制台总是打印日志问题的解决办法

问题

使用gradle启动项目,在tomcat控制台中不停地打印perf4j性能日志,导致开发过程很卡很慢。明明修改了logback.xml配置文件,让它输出到log文件中,而不是控制台,但是不起作用。

未分类

在Windows系统中,在启动tomcat后会出现上图这样的情况,

在ubuntu系统中,没有这种情况,应该是在ubuntu系统中,没有tomcat的控制台吧。
同样,部署在线上的时候也没有问题。

唯独在windows中进行开发的时候有问题。

解决过程

真的是尝试很久很久。

  • 刚开始调整日志打印路径,无果;
  • 然后就是调整日志打印级别,只打印Error的,这个实现了,却仍然打印;
  • 最后决定替换tomcat的Jar包里的代码,不让它打印日志了,解决问题。

Jar包中性能打印的语句为:

StopWatch stopWatch = new Log4JStopWatch("Messages.searchFiles");
try {
  ......
} finally {
  stopWatch.stop();
}

调用的perf4j-*.jar包中的方法

public Log4JStopWatch(String tag) {
  this(tag, null, Logger.getLogger(DEFAULT_LOGGER_NAME), Level.INFO, Level.WARN);
}

而这个方法最终打印在控制台上的语句为:

protected void log(String stopWatchAsString, Throwable exception) {
  logger.log((exception == null) ? normalPriority : exceptionPriority, stopWatchAsString, exception);
}

再跟踪这个方法,跳转到了log4j-*.jar包中的 Category.java类中的log 方法

public void log(Priority priority, Object message, Throwable t) {
  if(repository.isDisabled(priority.level)) {
    return;
  }
  if(priority.isGreaterOrEqual(this.getEffectiveLevel()))
  forcedLog(FQCN, priority, message, t);
}

再跳转时:

protected void forcedLog(String fqcn, Priority level, Object message, Throwable t) {
  callAppenders(new LoggingEvent(fqcn, this, level, message, t));
}

再往后就是具体打印的方法了。

之前问题的所有打印日志的方法都会走到这里,于是我把这个jar包中的log方法和forcedLog方法的内容都删除了,然后替换原来的log4j-*.jar包,实现效果。

这里提供一个修改后的jar包:

链接:https://pan.baidu.com/s/1078F50P8UuWW-Hgxt36QbQ 密码:j4w9

但如果是maven项目,不能直接替换jar包,可以根据tomcat的加载优先级,将修改后的jar包放到tomcat 中的lib 目录下进行覆盖。

Ubuntu Docker 配置 Tomcat 和 Nginx 使用 HTTPS 访问

安装 Docker

使用脚本自动安装

curl -fsSL get.docker.com -o get-docker.sh
sudo sh get-docker.sh --mirror Aliyun

更改镜像地址

  • 修改或新建 /etc/docker/daemon.json
{
  "registry-mirrors": [
    "https://registry.docker-cn.com"
  ]
}

启动 Docker

sudo systemctl daemon-reload
sudo systemctl enable docker
sudo systemctl start docker

配置 Tomcat

启动 Tomcat 容器

docker pull tomcat
docker run --name tomcat -d -p 8080:8080 tomcat

修改 Tomcat Manager 应用

docker exec -it tomcat /bin/bash
  • 修改 webapps/manager/META-INF/content.xml,允许需要的IP访问,这里运行所有的IP访问
<Context antiResourceLocking="false" privileged="true" >
  <Valve className="org.apache.catalina.valves.RemoteAddrValve"
         allow="^.*$" />
  <Manager sessionAttributeValueClassNameFilter="java.lang.(?:Boolean|Integer|Long|Number|String)|org.apache.catalina.filters.CsrfPreventionFilter$LruCache(?:$1)?|java.util.(?:Linked)?HashMap"/>
</Context>

配置 Tomcat 用户

  • 修改 conf/tomcat-user.xml,添加用户
<role rolename="admin-gui"/>
<role rolename="manager-gui"/>
<user username="tomcat" password="tomcat" roles="manager-gui,admin-gui"/>

配置 Nginx

配置目录

  • 新建目录 /home/ubuntu/hellowood/dev/nginx/conf, /home/ubuntu/hellowood/dev/nginx/log, /home/ubuntu/hellowood/dev/nginx/certs
  • 下载并解压相应的Nginx证书文件到 /home/ubuntu/hellowood/dev/nginx/conf

添加 Nginx 配置

  • nginx.conf
server {
      listen 80;
      listen 443 ssl;
      server_name hellowood.com.cn;
      ssl_certificate /etc/nginx/certs/hellowood.com.cn_bundle.crt;
      ssl_certificate_key /etc/nginx/certs/hellowood.com.cn.key;
      location / {
        proxy_pass http://tomcat:8080;
      }
}

http://tomcat:8080: 将所有请求都转发到 tomcat 容器的 8080端口

启动 Nginx 容器

docker pull nginx 
docker run --name nginx -d -p 80:80 -p 443:443 
  --link tomcat:tomcat 
  -v /home/ubuntu/hellowood/dev/nginx/conf:/etc/nginx/conf.d  
  -v /home/ubuntu/hellowood/dev/nginx/log:/var/log/nginx  
  -v /home/ubuntu/hellowood/dev/nginx/certs:/etc/nginx/certs nginx

此时,访问相应的域名:http://hellowood.com.cn和https://hellowood.com.cn会显示Tomcat 的首页,配置完成

tomcat简单优化

一、内存优化

1、修改tomcat路径bin目录下的catalina.sh

cat >> catalina.sh << END
JAVA_OPTS='-Xms20480m -Xmx20480m -Xss256k -XX:PermSize=256M -XX:MaxNewSize=256m -XX:MaxPermSize=256m'
END

参数:

-Xms  jvm初始化堆内存大小
-Xmx  jvm堆的最大内存   #堆内存建议设为服务器内存的60%~80%
-Xss  线程栈大小       #一般设置为256k

-XX:PermSize     jvm非堆区初始内存分配大小
-XX:MaxPermSize  jvm非堆区最大内存 #非堆内存不可回收,看项目大小而定

2、重启tomcat验证:jmap -heap 12345(tomcat PID)

二、配置优化

修改server.xml添加参数

maxThreads        客户请求最大线程数 
minSpareThreads   初始化时创建的 socket 线程数 
maxSpareThreads   连接器的最大空闲 socket 线程数 
enableLookups     若设为true, 则支持域名解析,可把 ip 地址解析为主机名 
redirectPort      在需要基于安全通道的场合,把客户请求转发到基于SSL 的 redirectPort 端口 
acceptAccount     监听端口队列最大数,满了之后客户请求会被拒绝(不能小于maxSpareThreads) 
connectionTimeout 连接超时 
minProcessors     服务器创建时的最小处理线程数 
maxProcessors     服务器同时最大处理线程数 
URIEncoding URL   统一编码

生产中配置:

<Connector executor="tomcatThreadPool"
           port="8080"
           protocol="HTTP/1.1"
           connectionTimeout="20000"
           minSpareThreads="150" 
           maxSpareThreads="500"
           enableLookups="false" 
           disableUploadTimeout="true" 
           acceptCount="1000"   
           maxThreads="1000" 
           maxProcessors="1000" 
           minProcessors="5"
           useURIValidationHack="false"
           compression="on" 
           compressionMinSize="2048"
           compressableMimeType="text/html,text/xml,text/javascript,text/css,text/plain"
           uriencoding="utf-8"
           redirectPort="8443" />

三、优化网卡驱动

$ cat >> /etc/sysctl.cnf << END
net.core.netdev_max_backlog = 32768
net.core.somaxconn = 32768 
net.core.wmem_default = 8388608
net.core.rmem_default = 8388608
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.ip_local_port_range = 1024 65000
net.ipv4.route.gc_timeout = 100
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_keepalive_time = 1200
net.ipv4.tcp_timestamps = 0
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_syn_retries = 2
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_mem = 94500000 915000000 927000000
net.ipv4.tcp_max_orphans = 3276800
net.ipv4.tcp_max_syn_backlog = 65536

END

$ sysctl -p

Nginx+Tomcat+memcached高可用会话保持

一、概述

之前文章已经描述了企业高可用负载相关的架构及实现,其中常用的nginx或haproxy,LVS结合keepalived做前端高可用调度器;但之前没有提到会话高可用保持;
本文通过 Tomcat Session Replication Cluster(tomcat自带)和tomcat结合memcat及第三方组件实现Tomcat Memcache Session Server高可用会话缓存服务;
实现的效果:
同一客户端访问业务网站,经过调度器负载调度到达后端,不管选择的是那个后端,session ID都不变,都保存在两台或多台的memcached缓存中(负载冗余);以保持持会话;

架构图:

未分类

说明:客户端请求时nginx通过负载调度算法将请求调度至某一后端服务器;tomcat 把会话通过组播的方式复制到集群各节点;所有节点共享会话;

未分类

说明:客户端请求时nginx通过负载调度算法将请求调度至某一后端服务器;并把session存储到两台memcached中;客户端刷新(不换浏览器)时,请求换成另一个后端服务器响应时session ID保持不变;

测试环境:

nginx: CentOS7 epel 安装nginx WAN:172.16.3.152 LAN:192.168.10.254
tomcat A: CentOS7 node1.san.com epel 安装 tomcat 7 openjdk-1.8.0 memcached(现实环境中单独服务器)
tomcat B: CentOS7 nodde2.san.com epel 安装 tomcat 7 openjdk-1.8.0 memcached 现实环境中单独服务器)
测试客户端ubuntu 16.04

cat /etc/hosts
172.16.3.152       www.san.com

二、安装配置集群

nginx安装

[root@nginx ~]# yum install epel-release -y
[root@nginx ~]# yum install nginx -y

nginx配置

在/etc/nginx/nginx.conf http段添加如下行

    upstream tcsrvs {
        server 192.168.10.11:8080;
        server 192.168.10.12:8080;
        }

cat /etc/nginx/conf.d/san.com.conf

[root@nginx ~]# cat /etc/nginx/conf.d/san.com.conf 
server {
            listen 80;
            server_name www.san.com;
            location / {
                proxy_pass http://tcsrvs;
                }
        }

Tomcat配置:
两台均需要安装

#yum install epel-release -y
#yum install java-1.8.0 java-1.8.0-openjdk-devel tomcat tomcat-webapps tomcat-admin-webapps tomcat-docs-webapp  -y

说明:也可以通过oracle官方下载jdk 下载tomcat 解压到指定目录并添加环境变量;一般企业推荐此种方式;为了快捷,我们用epel仓库中的稳定版本;

添加测试页

yum 安装的tomcat工作目录在/var/lib/tomcat/webapps 分别在node1与node2上,此目录下创建测试项目,title 分别叫Tomcat A与Tomcat B 颜色分别为green与red;以示区别;生产环境node1 与node2内容一致;这里为了测试区别node1与node2内容;

#mkdir -pv /var/lib/tomcat/webapps/test/{WEB-INF,META-INF,classes,lib}
#cat /var/lib/tomcat/webapps/test/index.jsp
<%@ page language="java" %>
 <html>
      <head><title>Tomcat A</title></head>
          <body>
             <h1><font color="red">TomcatA.san.com</font></h1>
           <table align="centre" border="1">
            <tr>
                <td>Session ID</td>
            <% session.setAttribute("san.com","san.com"); %>
            <td><%= session.getId() %></td>
            </tr>
            <tr>
            <td>Created on</td>
           <td><%= session.getCreationTime() %></td>
           </tr>
      </table>
    </body>
</html>

配置管理页密码

tomcat与管理程序安装好后配置访问密码
修改注释/etc/tomcat/tomcat-users.xml文件

<role rolename="admin-gui"/>
<role rolename="manager-gui"/>
<user username="tomcat" password="tomcat" roles="manager-gui,admin-gui"/>

备份默认/etc/tomcat/server.xml文件

cd /etc/tomcat
cp server.xml server.xml_def

测试页访问

http://www.san.com/test 如图:出现Tomcat A

未分类

Ctrl+F5强制刷新 又出现Tomcat B

未分类

引发问题:如果是两台内容一样的配置,客户端访问刷新一下就换到另一个后端处理;类似通过session保留信息的服务(购买物车)如何保留?换句话说,如何保持会话不中断,无论请求被分配到那一个后端?

解决方案

1)会话sticky(粘性):分为source_ip 基于源ip和cookie

source_ip在不同的调度器上有不同的实现方式:
lvs:sh算法;
nginx:ip_hash或hash $request_uri consistent(一致性哈希算法)
haproxy: source

cookie:
nginx:hash 或 hash $cookie_name consistent;
haproxy:cookie

2)会话集群(session cluster):delta session manager

3)session server: redis(store),memcached(cache)

以下基于tomcat自带会话集群与memcached实现会话保持 功能;

三、Tomcat Session Replication Cluster配置

Tomcat Session Replication Cluster中文又叫 tomcat 会话复制集群,即会话通过组播方式复制到每一个后端tomcat节点;
可参考自带帮助文档:http://www.san.com/docs/cluster-howto.html
两台node1 node2节点/etc/hosts中添加如下:

#cat /etc/hosts
192.168.10.11 node1.san.com node1
192.168.10.12 node2.san.com node2

两台tomcat 节点sever.xml的Host字段中添加如下内容:

<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
                 channelSendOptions="8">

          <Manager className="org.apache.catalina.ha.session.DeltaManager"
                   expireSessionsOnShutdown="false"
                   notifyListenersOnReplication="true"/>

          <Channel className="org.apache.catalina.tribes.group.GroupChannel">
            <Membership className="org.apache.catalina.tribes.membership.McastService"
                        address="228.10.0.4"
                        port="45564"
                        frequency="500"
                        dropTime="3000"/>
            <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
                      address="auto"           <!--   如果没有/etc/hosts解析则需要本机ip   -->
                      port="4000"
                      autoBind="100"
                      selectorTimeout="5000"
                      maxThreads="6"/>

            <Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
              <Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
            </Sender>
            <Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>
            <Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor"/>
          </Channel>

          <Valve className="org.apache.catalina.ha.tcp.ReplicationValve"
                 filter=""/>
          <Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"/>

          <Deployer className="org.apache.catalina.ha.deploy.FarmWarDeployer"
                    tempDir="/tmp/war-temp/"
                    deployDir="/tmp/war-deploy/"
                    watchDir="/tmp/war-listen/"
                    watchEnabled="false"/>

          <ClusterListener className="org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener"/>
          <ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
        </Cluster>

复制 /etc/tomcat/web.xml /var/lib/tomcat/webapps/test/WEB-INF/ 下并在web.xml的”“字段下添加 ““;
重启tomcat 并再次访问http://www.san.com/test 如图:

未分类

Ctrl + F5强制刷新如图:

未分类

可以可出会话得到保持,只要是从同一个客户端中请求,刷新或关闭重新打开(基于同一个浏览器) 只要会话没有过期,会话(session id) 无论来自那个后端,均是一样;

缺点:

tomcat自带支持会话集群(能过多播方式发送各节点);但有一个缺点;后端tomcat节点过多时效率低下,不适用大规模;

四、Tomcat Memcache Session Server高可用配置

原理说明:
客户端请求到达前端nginx调度器并分配到后端某tomcat节点时,tomcat会优先使用本机内存保存session,当一个请求结束后,tomcat会通过第三方组件(kryo,javolution,xstream,flexjson)把session序列化并发送到memcached节点上存放作备份,第二次请求时,如果本地有session就直接返回,第二次请求结束,把session修改后的信息更新到后端的memcached服务器,以这样的方式来保持本地的session与memcached上的session同步。当这个tomcat节点宕机时,那么用户的下一次请求就会被前端的负载均衡器路由到另一个tomcat节点上,而这个节点上并没有这个用户的session信息,这个节点就从memcached服务器上去读取session,并把session保存到本地的内存,当请求结束,session又被修改,再送回到memcached进行存放备份
当后端配置了多台memcached时,tomcat在更新session信息时会同时向多个memcached节点更新session,当一个memcached节点故障时,tomcat可以从选择一个正常工作的memcached节点读取session信息来发送给用户的浏览器,让其重置session信息,这样,memcached也达到了高可用的目的;
以下操作均在两台node上操作

还原默认配置文件

#cd /etc/tomcat/
#cp server.xml server.xml_cluster
#cp server.xml_def server.xml
#systemctl stop tomcat

安装memcached服务

#yum install memcached -y
#systemctl start memcached

memcache配置(默认即可,生产环境时需要加大内存与并发连接数)

# cat /etc/sysconfig/memcached 
PORT="11211"
USER="memcached"
MAXCONN="1024"
CACHESIZE="64"
OPTIONS=""

两台/etc/tomcat/server.xml Host段中添加如下内容:

<Context path="/test" docBase="test" reloadable="true">
        <Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
                memcachedNodes="m1:192.168.10.11:11211,m2:192.168.10.12:11211"
                failoverNodes="m1"
                requestUriIgnorePattern=".*.(ico|png|gif|jpg|css|js)$"
                transcoderFactoryClass="de.javakaffee.web.msm.serializer.javolution.JavolutionTranscoderFactory"/>
        </Context>

说明:

添加两个冗余备份memcached节点分别叫m1,m2 failoverNodes=”m1″ 表示m1作为备份;当m2失败时连接;即使用m2;

安装对应版本组件

下载以下JAR包到tomcat库目录;
cd /usr/share/tomcat/lib 
wget http://www.java2s.com/Code/JarDownload/javolution/javolution-5.5.1.jar.zip  
#需要解压 unzip javolution-5.5.1.jar.zip
wget http://repo1.maven.org/maven2/net/spy/spymemcached/2.12.1/spymemcached-2.12.1.jar
wget http://repo1.maven.org/maven2/de/javakaffee/msm/msm-javolution-serializer/2.1.1/msm-javolution-serializer-2.1.1.jar
wget http://repo1.maven.org/maven2/de/javakaffee/msm/memcached-session-manager-tc7/2.1.1/memcached-session-manager-tc7-2.1.1.jar
wget http://repo1.maven.org/maven2/de/javakaffee/msm/memcached-session-manager/2.1.1/memcached-session-manager-2.1.1.jar

注意:epel安装的tomcat 和openjdk版本如下:
openjdk: “1.8.0_161”
tomcat : “7.0.76”
以上第三方插件须和对应的版本是兼容的;如发现tomcat启动有问题;无法访问或如下类似错误

#tail -fn 100 /var/log/tomcat/catalina.xxxx.log
三月 23, 2018 4:12:52 下午 org.apache.catalina.core.StandardContext startInternal
严重: The session manager failed to start
org.apache.catalina.LifecycleException: Failed to start component [de.javakaffee.web.msm.MemcachedBackupSessionManager[/test]]
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:162)
    at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5643)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:145)

则表示第三方组件与tomcat不兼容!请重新下载版本;

测试:
浏览器访问http://www.san.com/test 如图:

未分类

Ctrl+F5强刷新 如图:

未分类

从测试上可以看出目前已经通过memcache存储session等缓存信息;并同步到两台memcache上;当前只使用m2节点;

总结

通过nginx快速实现负载tomcat应用;引用session不可保持问题;通过自带的Tomcat Session Replication Cluster和结合memcached及第三方组件实现高可用会话缓存服务来保持会话;前者不适合大规模应用;

Docker下的web开发和Tomcat部署

本期实践的主要目标是开发一个简单的web应用,打包部署到Docker的tomcat容器中去; 第一期为了快速上手,获取docker是从国内的daocloud获取的,本期开始,为了更好的熟悉和了解Docker技术,我们的查找,pull和push都改为在Docker Hub上进行,即网站:hub.docker.com,建议各位去上面注册一个账号,这样就有自己的仓库可以保存镜像了。

在hub.docker.com上搜索tomcat,搜索结果的第一个就是官方镜像,如下图:

未分类

点击Detail按钮,进入详情页,可以发现有好多个tag,例如7.0.75这个,就是tomcat7.0.75版本:

未分类

这么多版本,究竟选哪个呢?我们还是先看看几个具体版本的差异吧,打开tomcat官网下的这个链接:tomcat.apache.org/whichversio…

可以看到具体的差异:

未分类

可以看到,tomcat7 支持servlet3.0,可以满足我们的要求了,所以就用它吧,执行如下命令行即可下载镜像:

docker pull tomcat:7.0.75

命令执行有可能执行失败,多重试几次才行,pull成功后用docker images命令可以看到镜像:

未分类

来快速体验一下镜像的效果,执行命令:

docker run -it --rm -p 8888:8080 tomcat:7.0.75

–rm参数表示container结束时,Docker会自动清理其所产生的数据。

可以看到tomcat启动的日志全部打印在终端了,

未分类

因为我们用-p 8888:8080将容器的8080端口映射到当前电脑的8888端口,所以打开当前电脑的浏览器,输入:localhost:8888,可以看到熟悉的大猫:

未分类

接下来我们开发一个最简单的spring mvc应用,然后部署到docker的tomcat容器中试试,这我用的是IntelliJ IDEA CE,创建maven工程:

未分类

GAV信息如下:

未分类

如下图所示,通过这里增加一个mvn命令:

未分类

增加mvn命令:

未分类

配置命令如下:

未分类

接下来我们给web工程添加spring mvc支持,首先是web.xml文件,替换成下面这样:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                      http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1" metadata-complete="true">

  <servlet>
    <servlet-name>spring</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>spring</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

</web-app>

完整的pom文件内容如下:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.bolingcavalry</groupId>
  <artifactId>helloworldwebapp</artifactId>
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>helloworldwebapp Maven Webapp</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>servlet-api</artifactId>
      <version>2.5</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>4.3.4.RELEASE</version>
    </dependency>
  </dependencies>
  <build>
    <finalName>helloworldwebapp</finalName>
  </build>
</project>

在编译的时候遇到了一点小问题需要在此提一下,在工程上点击右键查看module属性,如下图:

未分类

此时看到的信息如下图所示:

未分类

注意在上图的绿色框框位置,如果你的工程中没有看到绿色框框中的内容,就用鼠标右键点击红色框框位置,在弹出的菜单中点击”Sources”,这样就把java目录加入到工程的编译目录中去了。

这时候去执行mvn命令依然无法编译java文件,在工程上点击右键,执行mvn的reimport命令,如下图,执行完毕后就可以用mvn命令编译java文件了:

未分类

现在开始添加测试代码,先增加一个view目录,里面放个jsp文件,文件结构和jsp文件的内容如下:

未分类

再增加一个java文件,文件路径如下:

未分类

该文件的源码:

package com.bolingcavalry.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class FirstController {

    @RequestMapping(value = "firstview", method = RequestMethod.GET)
    public String index() {
        return "firstview";
    }
}

最后在webapp/WEB-INF目录下增加spring-servlet.xml文件,内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:tx="http://www.springframework.org/schema/tx" xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans.xsd 
       http://www.springframework.org/schema/context 
       http://www.springframework.org/schema/context/spring-context.xsd 
       http://www.springframework.org/schema/tx 
       http://www.springframework.org/schema/tx/spring-tx.xsd
       http://www.springframework.org/schema/mvc
       http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!-- 配置扫描的包 -->
    <context:component-scan base-package="com.bolingcavalry.*" />

    <!-- 注册HandlerMapper、HandlerAdapter两个映射类 -->
    <mvc:annotation-driven />

    <!-- 访问静态资源 -->
    <mvc:default-servlet-handler />

    <!-- 视图解析器 -->
    <bean
        class-og="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/view/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>

</beans>

好了,代码已经写完,可以打包了,执行我们刚才配置好的命令,如下图:

未分类

命令执行后,可以在target目录下看到war包:

未分类

现在我们把文件部署到tomcat上去,先建一个目录,例如我建了这个目录:/Users/bolingcavalry/temp/201703/10/share,然后把helloworldwebapp.war文件复制到这个目录下,再在控制台执行以下命令:

docker run --name helloworldwebapp -p 8888:8080 -d -v /Users/bolingcavalry/temp/201703/10/share:/usr/Downloads tomcat:7.0.75

这样就启动了一个容器,执行以下命令进入容器

docker exec -it helloworldwebapp /bin/bash

进入容器后再执行以下命令将war包复制到tomcat容器目录下:

cp /usr/Downloads/helloworldwebapp.war /usr/local/tomcat/webapps/

这时候再打开浏览器,输入http://localhost:8888/helloworldwebapp/firstview试试吧,如下图,符合预期:

未分类

此时,我们今天测试tomcat部署的目的已经达到了,接下来再试试提交镜像,在容器中输入exit 退出容器,再执行”docker stop helloworldwebapp”停止容器,然后执行以下命令把容器作为镜像保存在本地:

docker commit -a "bolingcavalry" -m "from tomcat 7.0.75,with a demo webapp"  helloworldwebapp bolingcavalry/helloworldwebapp:0.0.1

-a : 作者
-m :提交时的说明文字
0.0.1:tag

执行完毕后,输入docker images,可以看到新增的镜像:

未分类

接下来我们试着把本地镜像提交到hub.docker.com去(前提是已经在这个网站上注册过),输入命令docker login,接下来按照提示输入用户名和密码,执行一下命令提交镜像:

docker push bolingcavalry/helloworldwebapp:0.0.1

有点费时,需要等待:

未分类

等上传成功后,再去hub.docker.com上看看吧,自己的仓库下面已经可以看到刚刚提交的镜像了:

未分类