Tomcat-connector的微调

acceptCount参数

对于acceptCount这个参数,含义跟字面意思并不是特别一致(个人感觉),容易跟maxConnections,maxThreads等参数混淆;实际上这个参数在tomcat里会被映射成backlog:

static {
    replacements.put("acceptCount", "backlog");
    replacements.put("connectionLinger", "soLinger");
    replacements.put("connectionTimeout", "soTimeout");
    replacements.put("rootFile", "rootfile");
}

backlog表示积压待处理的事物,是socket的参数,在bind的时候传入的,比如在Endpoint里的bind方法里:

public void bind() throws Exception {

    serverSock = ServerSocketChannel.open();
    ...
    serverSock.socket().bind(addr,getBacklog());
    ...
}

这个参数跟tcp底层实现的半连接队列和完全连接队列有什么关系呢?我们在tomcat默认BIO模式下模拟一下它的效果。

模拟的思路还是简单的通过shell脚本,建立一个长连接发送请求,持有20秒再断开,好有时间观察网络状态。注意BIO模式下默认超过75%的线程时会关闭keep-alive,需要把这个百分比调成100,这样就不会关闭keep-alive了。修改后的connector如下,最后边的三行参数是新增的:

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

    maxThreads="1"
    disableKeepAlivePercentage="100"
    acceptCount="2"
/>

上面的配置里我们把tomcat的最大线程数设置为1个,一直开启keep-alive,acceptCount设置为2。在linux上可以通过ss命令检测参数是否生效:

$ ss -ant  
State       Recv-Q Send-Q     Local Address:Port     Peer Address:Port
LISTEN      0      2          :::7001                :::*

可以看到7001端口是LISTEN状态,send-q的值是2,也就是我们设置的backlog的值。如果我们不设置,tomcat默认会设置为100,java则默认是50。

然后用下面的脚本模拟一次长连接:

$ { 
    echo -ne "POST /main HTTP/1.1\nhost: localhost:7001\n\n";
    sleep 20
  } | telnet localhost 7001

这个时候看服务器端socket的状况,是ESTABLISHED,并且Recv-QSend-Q都是没有堆积的,说明请求已经处理完

$ netstat -an | awk 'NR==2 || $4~/7001/'
Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp4       0      0  127.0.0.1.7001         127.0.0.1.54453        ESTABLISHED

现在我们模拟多个连接:

$ for i in {1..5}; do 
    ( 
        {
          echo -ne "POST /main HTTP/1.1\nhost: localhost:7001\n\n"; 
          sleep 20
        } | telnet localhost 7001
    )&;  
  done 

上面发起了5个链接,服务器端只有1个线程,只有第一个连接上的请求会被处理,另外4次连接,有2个连接还是完成了建立(ESTABLISHED状态),还有2个连接则因为服务器端的连接队列已满,没有响应,发送端处于SYN_SENT状态。下面列出发送端的tcp状态:

$ netstat -an | awk 'NR==2 || $5~/7001/'
Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp4       0      0  127.0.0.1.51389        127.0.0.1.7001         SYN_SENT
tcp4       0      0  127.0.0.1.51388        127.0.0.1.7001         SYN_SENT
tcp4       0      0  127.0.0.1.51387        127.0.0.1.7001         ESTABLISHED
tcp4       0      0  127.0.0.1.51386        127.0.0.1.7001         ESTABLISHED
tcp4       0      0  127.0.0.1.51385        127.0.0.1.7001         ESTABLISHED

再看tomcat端的状态:

$ netstat -an | awk 'NR==2 || $4~/7001/'
Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)
tcp4      45      0  127.0.0.1.7001         127.0.0.1.51387        ESTABLISHED
tcp4      45      0  127.0.0.1.7001         127.0.0.1.51386        ESTABLISHED
tcp4       0      0  127.0.0.1.7001         127.0.0.1.51385        ESTABLISHED

有3个链接,除了第一条连接请求的Recv-Q是0,另外两个连接的Recv-Q则有数据堆积(大小表示发送过来的字节长度)。注意,在ESTABLISHED状态下看到的Recv-QSend-Q的大小与在LISTEN状态下的含义不同,在LISTEN状态下的大小表示队列的长度,而非数据的大小。

从上面的模拟可以看出acceptCount参数是指服务器端线程都处于busy状态时(线程池已满),还可接受的连接数,即tcp的完全连接队列的大小。对于完全队列的计算,在linux上是:

min(backlog,somaxconn) 

backlog参数和proc/sys/net/core/somaxconn这两个值哪个小选哪个。

不过acceptCount/backlog参数还不仅仅决定完全连接队列的大小,对于半连接队列也有影响。参考同事飘零的blog,在linux 2.6.20内核之后,它的计算方式大致是:

table_entries = min(min(somaxconn,backlog),tcp_max_syn_backlog)
roundup_pow_of_two(table_entries + 1)

第二行的函数roundup_pow_of_two表示取最近的2的n次方的值,举例来说:假设somaxconn为128,backlog值为50,tcp_max_syn_backlog值为4096,则第一步计算出来的为50,然后roundup_pow_of_two(50 + 1),找到比51大的2的n次方的数为64,所以最终半连接队列的长度是64。

所以对于acceptCount这个值,需要慎重对待,如果请求量不是很大,通常tomcat默认的100也ok,但若访问量较大的情况,建议这个值设置的大一些,比如1024或更大。如果在tomcat前边一层对synflood攻击的防御没有把握的话,最好也开启syn cookie来防御。

maxConnections, maxThreads

1) 最大连接数

tomcat的最大连接数参数是maxConnections,这个值表示最多可以有多少个socket连接到tomcat上。BIO模式下默认最大连接数是它的最大线程数(缺省是200),NIO模式下默认是10000,APR模式则是8192(windows上则是低于或等于maxConnections的1024的倍数)。如果设置为-1则表示不限制。

在tomcat里通过一个计数器来控制最大连接,比如在Endpoint的Acceptor里大致逻辑如下:

while (running) {
    ...    
    //if we have reached max connections, wait
    countUpOrAwaitConnection(); //计数+1,达到最大值则等待

    ...
    // Accept the next incoming connection from the server socket
    socket = serverSock.accept();

    ...
    processSocket(socket);

    ...
    countDownConnection(); //计数-1
    closeSocket(socket);
}

计数器是通过LimitLatch锁来实现的,它内部主要通过一个java.util.concurrent.locks.AbstractQueuedSynchronizer的实现来控制。

我们在server.xml里对Connector增加maxConnections="1"这个参数,然后模拟2个连接:

for i in {1..2}; do ( 
    {
        echo -ne "POST /main HTTP/1.1\nhost: localhost:7001\n\n"; 
        sleep 20
    } | telnet localhost 7001
)&;  done

然后通过jstack可以看到acceptor线程阻塞在countUpOrAwaitConnection方法上:

"http-bio-7001-Acceptor-0" #19 daemon prio=5 os_prio=31 tid=0x00007f8acbcf1000 nid=0x6903 waiting on condition [0x0000000129c58000]
 java.lang.Thread.State: WAITING (parking)
 at sun.misc.Unsafe.park(Native Method)
 - parking to wait for  <0x0000000740353f40> (a org.apache.tomcat.util.threads.LimitLatch$Sync)    at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
 at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
 at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:997)
 at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1304)
 at org.apache.tomcat.util.threads.LimitLatch.countUpOrAwait(LimitLatch.java:115)
 at org.apache.tomcat.util.net.AbstractEndpoint.countUpOrAwaitConnection(AbstractEndpoint.java:755)
 at org.apache.tomcat.util.net.JIoEndpoint$Acceptor.run(JIoEndpoint.java:214)
 at java.lang.Thread.run(Thread.java:745)

对于NIO和APR的最大连接数默认值比较大,适合大量连接的场景;如果是BIO模式线程池又设置的比较小的话,就需要注意一下连接的处理是否够快,如果连接处理的时间较长,或新涌入的连接量比较大是不太适合用BIO的,调大BIO的线程数也可能存在利用率不高的情况

2) 最大线程数

如果没有对connector配置额外的线程池的话,maxThreads参数用来设置默认线程池的最大线程数。tomcat默认是200,对一般访问量的应用来说足够了。

processorCache与socket.processorCache

tomcat在处理每个连接时,Acceptor角色负责将socket上下文封装为一个任务SocketProcessor然后提交给线程池处理。在BIO和APR模式下,每次有新请求时,会创建一个新的SocketProcessor实例(在之前的tomcat对keep-alive的实现逻辑里也介绍过可以简单的通过SocketProcessorSocketWrapper实例数对比socket的复用情况);而在NIO里,为了追求性能,对SocketProcessor也做了cache,用完后将对象状态清空然后放入cache,下次有新的请求过来先从cache里获取对象,获取不到再创建一个新的。

这个cache是一个ConcurrentLinkedQueue,默认最多可缓存500个对象(见SocketProperties)。可以通过socket.processorCache来设置这个缓存的大小,注意这个参数是NIO特有的。

接下来在SocketProcessor执行过程中,真正的业务逻辑是通过一个org.apache.coyote.Processor的接口来封装的,默认这个Processor的实现是org.apache.coyote.http11.Http11Processor。我们看一下SocketProcessor.process(...)方法的大致逻辑:

public SocketState process(SocketWrapper<S> wrapper, SocketStatus status) {
    ...
    // 针对长轮询或upgrade情况
    Processor<S> processor = connections.get(socket);
    ...

    if (processor == null) {
        // 1) 尝试从回收队列里获取对象
        processor = recycledProcessors.poll();
    }
    if (processor == null) {
        // 2) 没有再创建新的
        processor = createProcessor();
    }

    ...
    state = processor.process(wrapper);

    ...
    release(wrapper, processor, ...);

    ...
    return SocketState.CLOSED;
}

上面的方法是在AbstractProtocol模板类里,所以BIO/APR/NIO都走这段逻辑,这里使用了一个回收队列来缓存Processor,这个回收队列是ConcurrentLinkedQueue的一个子类,队列的长度可通过server.xml里connector节点的processorCache属性来设置,默认值是200,如果不做限制的话可以设置为-1,这样cache的上限将是最大连接数maxConnections的大小。

在原有的一张ppt上加工了一下把这两个缓存队列所在位置标示了一下,图有点乱,重点是两个绿颜色的cache队列:

图中位于上面的socket.processorCache队列是NIO独有的,下面的processorCache是三种连接器都可以设置的。processorCache这个参数在并发量比较大的情况下也蛮重要的,如果设置的太小,可能引起瓶颈。我们模拟一下,看看这个瓶颈是怎么回事。先修改server.xml里的connector节点,把processorCache设置为0:

    <Connector port="7001"
           protocol="org.apache.coyote.http11.Http11NioProtocol"
           connectionTimeout="20000"
           redirectPort="8443" 
           processorCache="0"/>

启动tomcat后,使用ab模拟并发请求:

$ ab -n100000 -c10 http://localhost:7001/main

然后在ab的执行过程中立刻执行jstack观察堆栈信息,会发现一大半线程阻塞在AbstractConnectionHandler.registerAbstractConnectionHandler.unregister方法上:

"http-nio-7001-exec-11" #34 daemon prio=5 os_prio=31 tid=0x00007fd05ab05000 nid=0x8903 waiting for monitor entry [0x000000012b3b7000]
 java.lang.Thread.State: BLOCKED (on object monitor)
 at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.register(AbstractProtocol.java:746)
 - waiting to lock <0x00000007403b8950> (a org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler)
 at org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.createProcessor(Http11NioProtocol.java:277)
 at org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.createProcessor(Http11NioProtocol.java:139)
 at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:585)
 at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1720)
 at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1679)

 ...
"http-nio-7001-exec-4" #27 daemon prio=5 os_prio=31 tid=0x00007fd0593e3000 nid=0x7b03 waiting for monitor entry [0x000000012aca2000]
 java.lang.Thread.State: BLOCKED (on object monitor)
 at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.unregister(AbstractProtocol.java:773)
 - locked <0x00000007403b8950> (a org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler)
 at org.apache.coyote.AbstractProtocol$RecycledProcessors.offer(AbstractProtocol.java:820)
 at org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.release(Http11NioProtocol.java:219)
 at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:690)
 at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1720)

registerunregister分别是在创建和回收processor的时候调用的;看一下createProcessor方法里的大致逻辑:

public Http11NioProcessor createProcessor() {
    Http11NioProcessor processor = new Http11NioProcessor(...);
    processor.setXXX(...);
    ...

    // 这里,注册到jmx
    register(processor);
    return processor;
}

tomcat对jmx支持的非常好,运行时信息也有很多可以通过jmx获取,所以在每个新连接处理的时候,会在创建processor对象的时候注册一把,然后在processor处理完回收的时候再反注册一把;但这两个方法的实现都是同步的,同步的锁是一个全局的ConnectionHandler对象,造成了多个线程会在这里串行。

绝大部分应用没有特别高的访问量,通常并不需要调整processorCache参数,但对于网关或代理一类的应用(尤其是使用servlet3的情况)这个地方可以设置的大一些,比如调到1000或者-1。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 要下载tomcat-redis-session-manager-1.2-tomcat-7.jar,您可以按照以下步骤进行操作: 1. 打开您的网络浏览器,进入您通常使用的搜索引擎网站,如Google或百度。 2. 在搜索框中输入“tomcat-redis-session-manager-1.2-tomcat-7.jar下载”并按下回车键。 3. 检查搜索结果,找到可靠且权威的网站,比如Apache官方网站或者Maven中央库。 4. 点击打开所选网站,并在搜索栏中输入“tomcat-redis-session-manager-1.2-tomcat-7.jar”。 5. 您会看到相关的下载链接或页面,点击下载按钮或链接。 6. 选择一个合适的下载位置或文件夹,单击“保存”或“确认”按钮开始下载。 7. 等待下载完成,这可能需要一些时间,具体取决于您的网络连接速度和该文件的大小。 8. 下载完成后,在指定的下载位置或文件夹中找到下载的jar文件。 现在,您已经成功下载了tomcat-redis-session-manager-1.2-tomcat-7.jar文件,并可以将其用于相应的项目或应用程序中。请确保在使用该jar文件之前,仔细阅读相关的文档和说明,以确保正确配置和使用。 ### 回答2: 要下载tomcat-redis-session-manager-1.2-tomcat-7.jar,您可以按照以下步骤进行操作: 1. 打开您所用的网络浏览器(如Chrome、Firefox等)。 2. 在浏览器的地址栏中输入“tomcat-redis-session-manager-1.2-tomcat-7.jar”进行搜索。 3. 找到一个可信赖的下载来源,例如官方网站、GitHub等,确保文件来源可靠。 4. 点击下载链接或按钮,开始下载jar文件。 5. 下载完成后,您可以选择保存文件到您的电脑的特定目录,以便日后使用。 6. 如果您使用的是Tomcat服务器(版本7),则可以将该jar文件放置在您的Tomcat安装目录(通常是"tomcat7/lib"文件夹)下的“lib”文件夹中。 7. 确保您已经正确配置了Tomcat服务器,以便正确使用tomcat-redis-session-manager-1.2-tomcat-7.jar文件。 8. 现在,您已经成功下载并准备好使用tomcat-redis-session-manager-1.2-tomcat-7.jar文件了。 请注意,下载jar文件是一种常见的操作,确保您从可信赖的来源下载文件,以避免潜在的安全问题。另外,请根据您所使用的Tomcat服务器的版本选择正确的jar文件。 ### 回答3: 要下载 tomcat-redis-session-manager-1.2-tomcat-7.jar,您可以按照以下步骤进行操作: 1. 确定您的计算机上已安装 Java 运行时环境(JRE)和 Apache Tomcat 7。如果没有安装,您需要先下载和安装这些软件。 2. 打开您的网络浏览器,转到可信赖的软件下载网站,例如 Apache Tomcat 的官方网站或 Maven 仓库。 3. 在搜索框中输入 "tomcat-redis-session-manager-1.2-tomcat-7.jar",然后点击搜索按钮。 4. 在搜索结果中找到正确的版本,通常它会有与您正在使用的 Tomcat 版本相匹配的名称。 5. 单击下载按钮或链接,以开始下载该文件。文件大小可能会有所不同,所以可能需要一些时间来完成下载。 6. 一旦下载完成,将 jar 文件保存到您计算机上的合适位置,例如 Tomcat 的 lib 目录。 7. 确保您的 Tomcat 服务器已关闭。如果正在运行,请停止它。 8. 打开 Tomcat 的安装目录,找到和打开 conf 目录。 9. 在 conf 目录中找到 context.xml 文件,然后用文本编辑器打开。 10. 在 context.xml 文件中找到 `<Context>` 标签,并在其内部添加以下内容: ``` <Manager className="de.javakaffee.web.session.RedisSessionManager" host="localhost" port="6379" database="0" maxInactiveInterval="60" /> ``` 根据您的 Redis 服务器设置,可能需要进行其他自定义配置,例如主机名、端口号和数据库。 11. 保存并关闭 context.xml 文件。 12. 重新启动 Tomcat 服务器,以使更改生效。 现在,您已经成功下载了 tomcat-redis-session-manager-1.2-tomcat-7.jar,并将其集成到您的 Tomcat 服务器中。您可以使用 Redis 来存储和管理在 Tomcat 上运行的应用程序的会话。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值