SpringBoot 使用 Tomcat APR 模式

SpringBoot 使用 Tomcat APR 模式

SpringBoot 内置的是 Tomcat Embed 容器,就是把 Tomcat 打包成一个 jar包库 来使用,直接运行 Application 就可以启动 Web服务器。本质没有区别,默认独立的 Tomcat 性能高于 SpringBoot 内置的 Tomcat。因为独立的 Tomcat 很多都配置了 APR 模式特性,大多数比较的时候 SpringBoot 内置的 Tomcat 并没有开启这个模式。可以选择自己启动这个特性。

从 GitHub 上的讨论看,如果需要 TLS 支持,使用 APR 是比较好的。否则没必要使用 APR,未来 APR 在 Tomcat 10 中可能会被移除。
https://github.com/spring-projects/spring-boot/pull/10079

在 Tomcat 中提供了三种方式:BIO、NIO、APR。

BIO 模式

采用 Java IO 技术,单线程处理单请求(Tomcat7以下默认)
Tomcat 7 以下的版本都是 BIO,就是一个请求是一个独立的线程。不能适用高并发的场景。阻塞式 I/O,采用传统的 java I/O 进行操作,该模式下每个请求都会创建一个线程,适用于并发量小的场景

NIO 模式

采用 Java NIO 技术,少量线程处理大量请求(Tomcat8以上默认)
Tomcat 8 以上的版本,默认都是 NIO。同步非阻塞,比传统 BIO 能更好的支持大并发,Tomcat 8.0 后默认采用该模式

APR 模式

APR(Apache Portable Runtime)
采用 JNI 技术,从操作层面解决 I/O 阻塞问题,适合高并发场景
APR 的整体模式还是非阻塞 I/O,实现的线程模型也是按照 NIO 的标准模型实现的,
APR 是一种基于 JNI 形式调用http服务器的核心动态链接库来处理的文件和网络读写模式(文件读取和网络传输操作)。需要预先编译安装 APR库,现在很多高版本的 Tomcat 默认都走它了。在 Tomcat 中配置,很好配置,直接修改 protocol 就可以了。但是在 SpringBoot 中,配置是在 Java 代码中写的。
从官方文档 http://apr.apache.org/docs/apr/1.6/modules.html
可以看到 APR 根据不同操作系统,分别用 C 重写了大部分IO和系统线程操作模块,
这就是为什么 APR 在不改动代码的情况下能够提升。

SpringBoot 开启 APR 模式

在 SpringBoot 中内嵌的 Tomcat 默认启动开启的是 NIO 模式,这里如果我们要在 Linux 内核的系统上使用 APR 模式,那么需要安装一些 lib库,可以通过 rpm -q | grep apr 来查看是否安装了 apr,如果安装了则不再需要安装。

安装依赖

1
yum -y install apr-devel gcc gcc-devel openssl openssl-devel expat-devel

OpenSSL 安装

OpenSSL 需要版本大于 1.0.2,如果不使用 https openssl 也可以不安装,就是在启动的时候会报 OpenSSL 的错误,直接忽视就可以了。

1
2
3
4
# OpenSSL 下载地址
https://www.openssl.org/source/

wget -c https://www.openssl.org/source/openssl-1.1.1a.tar.gz

安装 OpenSSL

1
2
3
4
5
6
tar -xvf openssl-1.1.1a.tar.gz
cd openssl-1.1.1a/
./config --prefix=/usr/local/openssl
./config -t
make -j 4
make install

将 openssl 的 lib 加入系统 ldconfig 中

1
2
3
4
5
6
vim /etc/ld.so.conf.d/openssl.conf
/usr/local/openssl/lib

# 加载一下
ldconfig -v
ldconfig -v | grep libssl

查看 OpenSSL 版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
cd /usr/local/openssl/
./bin/openssl version -a
OpenSSL 1.1.1a 20 Nov 2018
built on: Wed Jan 2 02:19:47 2019 UTC
platform: linux-x86_64
options: bn(64,64) rc4(16x,int) des(int) idea(int) blowfish(ptr)
compiler: gcc -fPIC -pthread -m64 -Wa,--noexecstack -Wall -O3 -DOPENSSL_USE_NODELETE -DL_ENDIAN -DOPENSSL_PIC -DOPENSSL_CPUID_OBJ -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DKECCAK1600_ASM -DRC4_ASM -DMD5_ASM -DAES_ASM -DVPAES_ASM -DBSAES_ASM -DGHASH_ASM -DECP_NISTZ256_ASM -DX25519_ASM -DPADLOCK_ASM -DPOLY1305_ASM -DNDEBUG
OPENSSLDIR: "/usr/local/openssl/ssl"
ENGINESDIR: "/usr/local/openssl/lib/engines-1.1"
Seeding source: os-specific

# 如果遇到以下错误
openssl: error while loading shared libraries: libssl.so.1.1: cannot open shared object file: No such file or directory
openssl: error while loading shared libraries: libcrypto.so.1.1: cannot open shared object file: No such file or directory

# 可能是由于 OpenSSL 库的位置不正确造成的
# 做一下软链接,就好了
ln -svnf /usr/local/openssl/lib/libssl.so.1.1 /usr/lib64/libssl.so.1.1
ln -svnf /usr/local/openssl/lib/libcrypto.so.1.1 /usr/lib64/libcrypto.so.1.1

配置环境变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
vim /etc/profile.d/openssl.sh
#!/bin/bash

export OPENSSL_HOME=/usr/local/openssl
export PATH=$PATH:$OPENSSL_HOME/bin
:wq

source profile
openssl version -a

# 或把原来的命令备份一下,做个软链接
ll -h /usr/bin/openssl
mv /usr/bin/openssl{,_bak}
mv /usr/include/openssl{,openssl_bak}
ln -svnf /usr/local/openssl/bin/openssl /usr/bin/openssl
ln -svnf /usr/local/openssl/include/openssl /usr/include/openssl

APR 安装

1
2
# APR 下载地址
http://apr.apache.org/download.cgi

安装 apr

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
cd /data/tools/
tar -zxvf apr-1.6.5.tar.gz
cd apr-1.6.5/

# 检查是否符合安装条件并配置安装参数,检查是否缺失类库,一般来说如果安装的不是精简版系统都是能顺利通过的
./configure \
--prefix=/usr/local/apr
# 报错信息
config.status: executing libtool commands
rm: cannot remove 'libtoolT': No such file or directory
config.status: executing default commands

# 解决方法
# 编辑 configure 文件
cd apr-1.6.5/
vim configure
$RM "$cfgfile" 这行代码注释掉
或 把 $RM "$cfgfile" 这行删除掉
或 写成 $RM -f "$cfgfile"
重新再运行 ./configure 就可以了

make -j 4
make install
# 如果不设置安装路径,系统默认安装路径为 /usr/apr/lib

安装 apr-util

1
2
3
4
5
6
7
8
9
10
cd /data/tools/
tar -zxvf apr-util-1.6.1.tar.gz
cd apr-util-1.6.1/
# 安装 apr-util 需要配置 apr路径 和 jvm路径,否则会报错找不到apr
./configure \
--prefix=/usr/local/apr-utils \
--with-apr=/usr/local/apr
# --with-java-home=/data/jdk1.8.0_192
make -j 4
make install

安装 apr-iconv

1
2
3
4
5
6
7
8
9
10
11
12
13
# 安装 expat 开发库
yum -y install expat-devel

cd /data/tools/
tar -xvf apr-iconv-1.2.2.tar.gz

cd apr-iconv-1.2.2/
./configure \
--prefix=/usr/local/apr-iconv \
--with-apr=/usr/local/apr
# --with-java-home=/data/jdk1.8.0_192
make -j 4
make install

安装 tomcat native

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 安装 libtool 依赖库
yum -y install libtool libtool-devel

cd /data/tools/
tar -zxvf apache-tomcat-8.5.37.tar.gz

cd apache-tomcat-8.5.37/bin/
tar -zxvf tomcat-native.tar.gz

cd tomcat-native-1.2.19-src/native/
./configure \
--with-apr=/usr/local/apr \
--with-java-home=/data/jdk1.8.0_192 \
--with-ssl=/usr/local/openssl \
--with-ssl=yes
make -j 4
make install

配置 Apr

1
2
3
4
5
6
7
8
# vim /etc/profile
vim /etc/profile.d/apr.sh
#!/bin/bash

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/apr/lib
:wq

source /etc/profile

新增 APRConfig 类

网上大部分讲解配置tomcat apr的文章,都只是讲了如何在独立 Tomcat 服务上如何配置 apr,
只需要修改 server.xml 中的 connnector 的 protocol 就可以了,
对于 SpringBoot 会稍微复杂些,需要增加一个 apr 配置类在启动的时候修改 Embed 的 tomcat connector 网络接入协议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
packagecom.ochain.data2chain.gateway.config;

importorg.apache.catalina.LifecycleListener;
importorg.apache.catalina.core.AprLifecycleListener;
importorg.springframework.beans.factory.annotation.Value;
importorg.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
importorg.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
importorg.springframework.context.annotation.Bean;
importorg.springframework.context.annotation.Configuration;

@Configuration
public class APRConfig {
@Value("${tomcat.apr:false}")
privateboolean enabled;

@Bean
public EmbeddedServletContainerFactory servletContainer() {
TomcatEmbeddedServletContainerFactory container = new TomcatEmbeddedServletContainerFactory();
if(enabled) {
LifecycleListener arpLifecycle = new AprLifecycleListener();
container.setProtocol("org.apache.coyote.http11.Http11AprProtocol");
container.addContextLifecycleListeners(arpLifecycle);
}
return container;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import org.apache.catalina.core.AprLifecycleListener;
import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class APRConfig {
// https://docs.spring.io/spring-boot/docs/1.5.6.RELEASE/reference/htmlsingle/#howto-discover-build-in-options-for-external-properties
@Bean
public EmbeddedServletContainerFactory servletContainer() {
TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory();
tomcat.setProtocol("org.apache.coyote.http11.Http11AprProtocol");
tomcat.addContextLifecycleListeners(new AprLifecycleListener());
return tomcat;
}
}

配置完启动,有可能会报错

错误问题

1
2
2019-01-02 00:45:55,891 [main] ERROR org.apache.catalina.core.StandardService:181 - Failed to start connector [Connector[org.apache.coyote.http11.Http11AprProtocol-8080]]
org.apache.catalina.LifecycleException: Failed to initialize component [Connector[org.apache.coyote.http11.Http11AprProtocol-8080]]

需要在启动 SpringBoot 的服务器上安装 tomcat-native 和 apr 的模块

启动 SpringBoot 系统找不到 apr 的 lib 库

1
2
3
4
5
6
7
8
org.springframework.boot.context.embedded.tomcat.ConnectorStartFailedException: Connector configured tolisten onport 8080 failed tostart
...
***************************
APPLICATION FAILED TOSTART
***************************
Description:

The Tomcat connector configured tolisten onport 8080 failed tostart. Theport may already be inuse orthe connector may be misconfigured.

打开 debug 后查看系统日志发现真正的原因是系统找不到 apr 的 lib库

1
Caused by: org.apache.catalina.LifecycleException: The configured protocol [org.apache.coyote.http11.Http11AprProtocol] requires the APR/native library which is not available

解决方法
在启动命令行中添加指定 apr库路径

1
java -Djava.library.path=/usr/local/apr/lib -jar xxxx-0.0.1-SNAPSHOT.jar

启动 SpringBoot

启动成功后看到日志中输出以下内容,则表示 apr 模式启动成功

1
2
3
2019-01-02 15:31:19,032 - Initializing ProtocolHandler ["http-apr-8080"]
2019-01-02 15:31:19,051 - Starting ProtocolHandler ["http-apr-8080"]
2019-01-02 15:31:19,080 - Tomcat started on port(s): 8080(http)

参考文档

https://www.jianshu.com/p/f716726ba340
https://www.cnblogs.com/yueli/p/9668088.html
http://www.cnblogs.com/xing901022/p/9145914.html

---------------- The End ----------------
0%