1、编译安装haproxy

准备环境

由于centos7之前版本自带的lua版本比较低并不符合HAProxy要求的lua最低版本(5.3)的要求,因此需要编译安装较新版本的lua环境,然后才能编译安装HAProxy,过程如下

#当前系统版本
[root@centos7-01 ~]# lua -v
Lua 5.1.4  Copyright (C) 1994-2008 Lua.org, PUC-Rio
#安装基础命令及编译依赖环境
[root@centos7-01 ~]# yum install gcc readline-devel -y
#下载lua
[root@centos7-01 ~]# wget http://www.lua.org/ftp/lua-5.4.4.tar.gz
#解压缩
[root@centos7-01 ~]# tar xvf lua-5.4.4.tar.gz -C /usr/local/src
#编译安装
[root@centos7-01 ~]# cd /usr/local/src/lua-5.4.4/
[root@centos7-01 lua-5.4.4]# make linux test
#查看编译安装的版本
[root@centos7-01 lua-5.4.4]# src/lua -v
Lua 5.4.4  Copyright (C) 1994-2022 Lua.org, PUC-Rio

编译安装HAProxy

#下载HAProxy
[root@centos7-01 ~]# wget https://www.haproxy.org/download/2.4/src/haproxy-2.4.15.tar.gz
#HAProxy 2.0以上版本编译参数:
[root@centos7-01 ~]# yum -y install gcc openssl-devel pcre-devel systemd-devel
[root@centos7-01 ~]# tar xvf haproxy-2.4.15.tar.gz -C /usr/local/src
#编译安装
[root@centos7-01 ~]# cd /usr/local/src/haproxy-2.4.15/
[root@centos7-01 haproxy-2.4.15]# 
#查看安装方法
[root@centos7-01 haproxy-2.4.15]# less INSTALL
[root@centos7-01 haproxy-2.4.15]# less Makefile
[root@centos7-01 haproxy-2.4.15]# make ARCH=x86_64 TARGET=linux-glibc USE_PCRE=1 USE_OPENSSL=1 USE_ZLIB=1 USE_SYSTEMD=1 USE_LUA=1 LUA_INC=/usr/local/src/lua-5.4.4/src/ LUA_LIB=/usr/local/src/lua-5.4.4/src/
[root@centos7-01 haproxy-2.4.15]# make install PREFIX=/apps/haproxy
#软链接
[root@centos7-01 haproxy-2.4.15]# ln -s /apps/haproxy/sbin/haproxy /usr/sbin/
#验证HAProxy版本
[root@centos7-01 haproxy-2.4.15]# which haproxy
/usr/sbin/haproxy
#大写-V选项显示版本和帮助用法
[root@centos7-01 haproxy-2.4.15]# haproxy -v
HAProxy version 2.4.15-7782e23 2022/03/14 - https://haproxy.org/
Status: long-term supported branch - will stop receiving fixes around Q2 2026.
Known bugs: http://www.haproxy.org/bugs/bugs-2.4.15.html
Running on: Linux 3.10.0-1160.el7.x86_64 #1 SMP Mon Oct 19 16:18:59 UTC 2020 x86_64
[root@centos7-01 haproxy-2.4.15]# #haproxy -V
[root@centos7-01 haproxy-2.4.15]# haproxy -V
HAProxy version 2.4.15-7782e23 2022/03/14 - https://haproxy.org/
Status: long-term supported branch - will stop receiving fixes around Q2 2026.
Known bugs: http://www.haproxy.org/bugs/bugs-2.4.15.html
Running on: Linux 3.10.0-1160.el7.x86_64 #1 SMP Mon Oct 19 16:18:59 UTC 2020 x86_64
Usage : haproxy [-f <cfgfile|cfgdir>]* [ -vdVD ] [ -n <maxconn> ] [ -N <maxpconn> ]
        [ -p <pidfile> ] [ -m <max megs> ] [ -C <dir> ] [-- <cfgfile>*]
        -v displays version ; -vv shows known build options.
        -d enters debug mode ; -db only disables background mode.
        -dM[<byte>] poisons memory with <byte> (defaults to 0x50)
        -V enters verbose mode (disables quiet mode)
        -D goes daemon ; -C changes to <dir> before loading files.
        -W master-worker mode.
        -Ws master-worker mode with systemd notify support.
        -q quiet mode : dont display messages
        -c check mode : only check config files and exit
        -n sets the maximum total # of connections (uses ulimit -n)
        -m limits the usable amount of memory (in MB)
        -N sets the default, per-proxy maximum # of connections (0)
        -L set local peer name (default to hostname)
        -p writes pids of all children to this file
        -de disables epoll() usage even when available
        -dp disables poll() usage even when available
        -dS disables splice usage (broken on old kernels)
        -dG disables getaddrinfo() usage
        -dR disables SO_REUSEPORT usage
        -dL dumps loaded object files after config checks
        -dr ignores server address resolution failures
        -dV disables SSL verify on servers side
        -dW fails if any warning is emitted
        -dD diagnostic mode : warn about suspicious configuration statements
        -sf/-st [pid ]* finishes/terminates old pids.
        -x <unix_socket> get listening sockets from a unix socket
        -S <bind>[,<bind options>...] new master CLI
#准备HAProxy启动文件
[root@centos7-01 haproxy-2.4.15]# vim /usr/lib/systemd/system/haproxy.service
[Unit]
Description=HAProxy Load Balancer
After=syslog.target network.target

[Service]
ExecStartPre=/usr/sbin/haproxy -f /etc/haproxy/haproxy.cfg -c -q
ExecStart=/usr/sbin/haproxy -Ws -f /etc/haproxy/haproxy.cfg -p /var/lib/haproxy/haproxy.pid
ExecReload=/bin/kill -USR2 $MAINPID
LimitNOFILE=100000

[Install]
WantedBy=multi-user.target
#配置文件
[root@centos7-01 haproxy-2.4.15]# mkdir /etc/haproxy
[root@centos7-01 haproxy-2.4.15]# vim /etc/haproxy/haproxy.cfg
global
    maxconn 100000
    chroot /apps/haproxy
    stats socket /var/lib/haproxy/haproxy.sock mode 600 level admin
    #uid 99
    #gid 99
    user haproxy
    group haproxy
    daemon
    #nbproc 4
    #cpu-map 1 0
    #cpu-map 2 1
    #cpu-map 3 2
    #cpu-map 4 3
    pidfile /var/lib/haproxy/haproxy.pid
    log 127.0.0.1 local2 info

defaults
    option http-keep-alive
    option forwardfor
    maxconn 100000
    mode http
    timeout connect 300000ms
    timeout client 300000ms
    timeout server 300000ms

listen stats
    mode http
    bind 0.0.0.0:9999
    stats enable
    log global
    stats uri /haproxy-status
    stats auth haadmin:123456

listen web_port
    bind 10.0.0.131:80
    mode http
    log global
    server web1 127.0.0.1:8080 check inter 3000 fall 2 rise 5
#启动 haproxy
#准备socket文件目录
[root@centos7-01 haproxy-2.4.15]# mkdir /var/lib/haproxy
#设置用户和目录权限
[root@centos7-01 haproxy-2.4.15]# useradd -r -s /sbin/nologin -d /var/lib/haproxy haproxy
#启动
[root@centos7-01 haproxy-2.4.15]# systemctl enable --now haproxy
#验证 haproxy 状态
[root@centos7-01 ~]# systemctl status haproxy
● haproxy.service - HAProxy Load Balancer
   Loaded: loaded (/usr/lib/systemd/system/haproxy.service; enabled; vendor preset: disabled)
   Active: active (running) since Thu 2022-06-16 22:56:48 UTC; 5s ago
  Process: 1480 ExecStartPre=/usr/sbin/haproxy -f /etc/haproxy/haproxy.cfg -c -q (code=exited, status=0/SUCCESS)
 Main PID: 1483 (haproxy)
   CGroup: /system.slice/haproxy.service
           ├─1483 /usr/sbin/haproxy -Ws -f /etc/haproxy/haproxy.cfg -p /var/lib/haproxy/haproxy.pid
           └─1486 /usr/sbin/haproxy -Ws -f /etc/haproxy/haproxy.cfg -p /var/lib/haproxy/haproxy.pid

Jun 16 22:56:48 centos7-01 systemd[1]: Starting HAProxy Load Balancer...
Jun 16 22:56:48 centos7-01 systemd[1]: Started HAProxy Load Balancer.
Jun 16 22:56:48 centos7-01 haproxy[1483]: [NOTICE]   (1483) : New worker #1 (1486) forked
Jun 16 22:56:48 centos7-01 haproxy[1483]: [WARNING]  (1486) : Server web_port/web1 is DOWN, reason: Layer4 connection problem, info: "Connection refused", check duration: 2...ng in queue.
Jun 16 22:56:48 centos7-01 haproxy[1483]: [NOTICE]   (1486) : haproxy version is 2.4.15-7782e23
Jun 16 22:56:48 centos7-01 haproxy[1483]: [NOTICE]   (1486) : path to executable is /usr/sbin/haproxy
Jun 16 22:56:48 centos7-01 haproxy[1483]: [ALERT]    (1486) : proxy 'web_port' has no server available!
Hint: Some lines were ellipsized, use -l to show in full.

浏览器登录验证,用户名haadmin 密码123456

 

2、总结haproxy各调度算法的实现方式及其应用场景

一、静态算法

1.1 static-rr

基于权重的轮询调度,不支持运行时利用socat进行权重的动态调整(只支持0和1,不支持其它值)及后端服务器慢启动,其后端主机数量没有限制,相当于LVS中的 wrr。

listen web_host
	bind 10.0.0.7:80
	mode http
	log global
	balance static-rr	
	server web1 10.0.0.17:80 weight 1 check inter 3000 fall 2 rise 5
	server web2 10.0.0.27:80 weight 2 check inter 3000 fall 2 rise 5

1.2 first

根据服务器在列表中的位置,自上而下进行调度,但是其只会当第一台服务器的连接数达到上限,新请求才会分配给下一台服务,因此会忽略服务器的权重设置,此方式使用较少。不支持用socat进行动态修改权重,可以设置0和1,可以设置其它值但无效。

listen web_host
	bind 10.0.0.7:80
	mode http
	log global
	balance first
	server web1 10.0.0.17:80 maxconn 2 weight 1 check inter 3000 fall 2 rise 5
	server web2 10.0.0.27:80 weight 1 check inter 3000 fall 2 rise 5

二、动态算法

2.1 roundrobin

基于权重的轮询动态调度算法,支持权重的运行时调整,不同于lvs中的rr轮训模式,HAProxy中的roundrobin支持慢启动(新加的服务器会逐渐增加转发数),其每个后端backend中最多支持4095个real server,支持对real server权重动态调整,roundrobin为默认调度算法,此算法使用广泛。

listen web_host
	bind 10.0.0.7:80
	mode http
	log global
	balance roundrobin
	server web1 10.0.0.17:80 weight 1 check inter 3000 fall 2 rise 5
	server web2 10.0.0.27:80 weight 2 check inter 3000 fall 2 rise 5

2.2 leastconn

leastconn加权的最少连接的动态,支持权重的运行时调整和慢启动,即:根据当前连接最少的后端服务器而非权重进行优先调度(新客户端连接),比较适合长连接的场景使用,比如:MySQL等场景。

listen web_host
	bind 10.0.0.7:80,:8801-8810,10.0.0.7:9001-9010
	mode http
	log global
	balance leastconn
	server web1 10.0.0.17:80 weight 1 check inter 3000 fall 2 rise 5
	server web2 10.0.0.27:80 weight 1 check inter 3000 fall 2 rise 5

2.3 random

在1.9版本开始增加 random的负载平衡算法,其基于随机数作为一致性hash的key,随机负载平衡对于大型服务器场或经常添加或删除服务器非常有用,支持weight的动态调整,weight较大的主机有更大概率获取新请求。

listen web_host
	bind 10.0.0.7:80
	mode http
	log global
	balance random
	server web1 10.0.0.17:80 weight 1 check inter 3000 fall 2 rise 5
	server web2 10.0.0.27:80 weight 1 check inter 3000 fall 2 rise 5

三、其他算法

其它算法即可作为静态算法,又可以通过选项成为动态算法

3.1 source

  • 源地址hash,基于用户源地址hash并将请求转发到后端服务器,后续同一个源地址请求将被转发至同一个后端web服务器。此方式当后端服务器数据量发生变化时,会导致很多用户的请求转发至新的后端服务器,默认为静态方式,但是可以通过hash-type支持的选项更改

  • 这个算法一般是在不插入Cookie的TCP模式下使用,也可给拒绝会话cookie的客户提供最好的会话粘性,适用于session会话保持但不支持cookie和缓存的场景

  • 源地址有两种转发客户端请求到后端服务器的服务器选取计算方式,分别是取模法和一致性hash

3.1.1 map-base 取模法

map-based取模法,对source地址进行hash计算,再基于服务器总权重的取模,最终结果决定将此请求转发至对应的后端服务器。此方法是静态的,即不支持在线调整权重,不支持慢启动,可实现对后端服务器均衡调度。缺点是当服务器的总权重发生变化时,即有服务器上线或下线,都会因总权重发生变化而导致调度结果整体改变,hash-type 指定的默认值为此算法。

listen web_host
	bind 10.0.0.7:80
	mode tcp
	log global
	balance source
	hash-type map-based	#不写则默认map-based静态算法
	server web1 10.0.0.17:80 weight 1 check inter 3000 fall 2 rise 3
	server web2 10.0.0.27:80 weight 1 check inter 3000 fall 2 rise 3

3.1.2一致性hash

一致性哈希,当服务器的总权重发生变化时,对调度结果影响是局部的,不会引起大的变动,hash(o)mod n ,该hash算法是动态的,支持使用 socat等工具进行在线权重调整,支持慢启动。

listen web_host
	bind 10.0.0.7:80
	mode tcp
	log global
	balance source
	hash-type consistent
	server web1 10.0.0.17:80 weight 1 check inter 3000 fall 2 rise 5
	server web2 10.0.0.27:80 weight 1 check inter 3000 fall 2 rise 5

3.4 uri

基于对用户请求的URI的左半部分或整个uri做hash,再将hash结果对总权重进行取模后,根据最终结果将请求转发到后端指定服务器,适用于后端是缓存服务器场景,默认是静态算法,也可以通过hash-type指定map-based和consistent,来定义使用取模法还是一致性hash。

listen web_host
	bind 10.0.0.7:80,:8801-8810,10.0.0.7:9001-9010
	mode http
	log global
	balance uri
	server web1 10.0.0.17:80 weight 1 check inter 3000 fall 2 rise 5
	server web2 10.0.0.27:80 weight 1 check inter 3000 fall 2 rise 5

3.5 url_param

url_param对用户请求的url中的 params 部分中的一个参数key对应的value值作hash计算,并由服务器总权重相除以后派发至某挑出的服务器;通常用于追踪用户,以确保来自同一个用户的请求始终发往同一个real server,如果无没key,将按roundrobin算法

#url_param取模法配置示例
listen web_host
	bind 10.0.0.7:80,:8801-8810,10.0.0.7:9001-9010
	mode http
	log global
	balance url_param userid 
	server web1 10.0.0.17:80 weight 1 check inter 3000 fall 2 rise 5
	server web2 10.0.0.27:80 weight 1 check inter 3000 fall 2 rise 5

3.6 hdr

针对用户每个http头部(header)请求中的指定信息做hash,此处由 name 指定的http首部将会被取出并做hash计算,然后由服务器总权重取模以后派发至某挑出的服务器,如果无有效值,则会使用默认的轮询调度。

#hdr取模法配置示例
listen web_host
	bind 10.0.0.7:80,:8801-8810,10.0.0.7:9001-9010
	mode http
	log global
	balance hdr(User-Agent)
	#balance hdr(host)
	
	server web1 10.0.0.17:80 weight 1 check inter 3000 fall 2 rise 5
	server web2 10.0.0.27:80 weight 1 check inter 3000 fall 2 rise 5

3.7 rdp-cookie

rdp-cookie对远windows远程桌面的负载,使用cookie保持会话,默认是静态,也可以通过hash-type指定map-based和consistent,来定义使用取模法还是一致性hash。

#rdp-cookie 取模法配置示例
listen RDP
	bind 10.0.0.7:3389
	balance rdp-cookie
	mode tcp
	server rdp0 10.0.0.17:3389 check fall 3 rise 5 inter 2000 weight 1

四、使用场合

 

 

3、使用haproxy的ACL实现基于文件后缀名的动静分离

haproxy服务器           10.0.0.131(主机名:centos7-01)

web服务器1               10.0.0.132(主机名:centos7-02)

web服务器2               10.0.0.133(主机名:centos7-03)

客户端                        10.0.0.134(主机名:centos7-04)

ACL

访问控制列表(ACL,Access Control Lists)是一种基于包过滤的访问控制技术,它可以根据设定的条件对经过服务器传输的数据包进行过滤(条件匹配),即对接收到的报文进行匹配和过滤,基于请求报文头部中的源地址、源端口、目标地址、目标端口、请求方法、URL、文件后缀等信息内容进行匹配并执行进一步操作,比如允许其通过或丢弃。

ACL配置选项

acl 	<aclname>	 <criterion>	 [flags]	 [operator]		 [<value>]
acl 	   名称 		 匹配规范		 匹配模式	   具体操作符	   操作对象类型

ACL-Name
#ACL名称,可以使用大字母A-Z、小写字母a-z、数字0-9、冒号:、点.、中横线和下划线,并且严格区分大
小写,比如:my_acl和My_Acl就是两个完全不同的acl

ACL-criterion
定义ACL匹配规范,即:判断条件
path_end : suffix match #请求的URL中资源的结尾,如 .gif .png .css .js .jpg .jpeg

ACL-flags
-i 不区分大小写

ACL基于文件后缀名实现动静分离

#创建目录
[root@centos7-01 haproxy]# mkdir /etc/haproxy/conf.d
#创建cfg文件
[root@centos7-01 haproxy]# vim /etc/haproxy/conf.d/test.cfg
frontend ha1_web_80
    bind 10.0.0.131:80
    balance roundrobin

###################### acl setting ###############################
    acl acl_static path_end -i  .jpg .jpeg .png .gif .css .js .html
    acl acl_php path_end -i     .php
###################### acl hosts #################################
    use_backend static_hosts          if acl_static
    use_backend php_hosts             if acl_php
###################### backend hosts #############################

backend static_hosts
    server rs2 10.0.0.132:80 check inter 3000 fall 2 rise 5

backend php_hosts
    server rs1 10.0.0.133:80 check inter 3000 fall 2 rise 5
    
#修改service文件
[root@centos7-01 haproxy]# systemctl reload haproxy.service
[Unit]
Description=HAProxy Load Balancer
After=syslog.target network.target

[Service]
ExecStartPre=/usr/sbin/haproxy -f /etc/haproxy/haproxy.cfg -f /etc/haproxy/conf.d/  -c -q
ExecStart=/usr/sbin/haproxy -Ws -f /etc/haproxy/haproxy.cfg -f /etc/haproxy/conf.d/  -p /var/lib/haproxy/haproxy.pid
ExecReload=/bin/kill -USR2 $MAINPID
LimitNOFILE=100000

[Install]
WantedBy=multi-user.target

#分别在web服务器1和web服务器2安装httpd并准备相关文件
[root@centos7-02 ~]# yum install httpd
[root@centos7-02 ~]# systemctl start httpd
#web服务器1作为静态
[root@centos7-02 ~]# echo 10.0.0.132 > /var/www/html/index.html
[root@centos7-03 ~]# yum install httpd
[root@centos7-03 ~]# systemctl start httpd
#web服务器2作为动态
[root@centos7-03 ~]# echo 10.0.0.133 > /var/www/html/test.php

#客户端访问测试
[root@centos7-04 ~]# curl 10.0.0.131/index.html
10.0.0.132
[root@centos7-04 ~]# curl 10.0.0.131/test.php
10.0.0.133

 

 

 

 

 

4、haproxy https实现

haproxy服务器                      10.0.0.131(主机名:centos7-01)

web服务器1                          10.0.0.132(主机名:centos7-02)

web服务器2                          10.0.0.133(主机名:centos7-03)

客户端                                   10.0.0.134(主机名:centos7-04)

haproxy可以实现https的证书安全,从用户到haproxy为https,从haproxy到后端服务器用http通信。但基于性能考虑,生产中证书都是在后端服务器比如nginx上实现。

#配置HAProxy支持https协议,支持ssl会话;
	bind *:443 ssl crt /PATH/TO/SOME_PEM_FILE
	
#指令crt后证书文件为PEM格式,需要同时包含证书和所有私钥
	cat demo.key demo.crt > demo.pem

#把80端口的请求重向定443
	bind *:80
	redirect scheme https if !{ ssl_fc }
	
#向后端传递用户请求的协议和端口(frontend或backend)
	http_request set-header X-Forwarded-Port %[dst_port]
	http_request add-header X-Forwared-Proto https if { ssl_fc }
#创建证书存放目录
[root@centos7-01 ~]# mkdir /etc/haproxy/conf.d/ssl
#制作证书
[root@centos7-01 ~]# cd /etc/pki/tls/certs/
#修改此处 /usr/bin/openssl genrsa -aes128 $(KEYLEN) > $@
[root@centos7-01 certs]# vim Makefile
    /usr/bin/openssl genrsa  $(KEYLEN) > $@
#创建证书
[root@centos7-01 certs]# make /etc/haproxy/conf.d/ssl/www.linux666666.org.crt
[root@centos7-01 certs]# make /etc/haproxy/conf.d/ssl/www.linux666666.org.crt
umask 77 ; \
/usr/bin/openssl req -utf8 -new -key /etc/haproxy/conf.d/ssl/www.linux666666.org.key -x509 -days 365 -out /etc/haproxy/conf.d/ssl/www.linux666666.org.crt 
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:CN
State or Province Name (full name) []:shanghai
Locality Name (eg, city) [Default City]:shanghai
Organization Name (eg, company) [Default Company Ltd]:linux666666
Organizational Unit Name (eg, section) []:it
Common Name (eg, your name or your servers hostname) []:www.linux666666.org
Email Address []:
[root@centos7-01 certs]# cd /etc/haproxy/conf.d/ssl/
[root@centos7-01 ssl]# ls
www.linux666666.org.crt  www.linux666666.org.key
#合并证书文件
[root@centos7-01 ssl]# cat www.linux666666.org.key www.linux666666.org.crt > www.linux666666.org.pem
#https配置
[root@centos7-01 ssl]# cd ..
[root@centos7-01 conf.d]# ls
ssl  test.cfg
[root@centos7-01 conf.d]# vim test.cfg 
listen ha1_https_443
    bind 10.0.0.131:80
    bind 10.0.0.131:443 ssl crt /etc/haproxy/conf.d/ssl/www.linux666666.org.pem
    redirect scheme https if !{ ssl_fc }
    http-request set-header X-forwarded-Port %[dst_port]
    http-request add-header X-forwarded-Proto https if { ssl_fc }
    balance roundrobin
    server rs1 10.0.0.132:80 check inter 3000 fall 2 rise 5
    server rs2 10.0.0.133:80 check inter 3000 fall 2 rise 5

[root@centos7-01 conf.d]# systemctl restart haproxy
[root@centos7-01 conf.d]# ss -ntl
State       Recv-Q Send-Q                                                Local Address:Port                                                               Peer Address:Port              
LISTEN      0      100                                                       127.0.0.1:25                                                                            *:*                  
LISTEN      0      128                                                      10.0.0.131:443                                                                           *:*                  
LISTEN      0      128                                                       127.0.0.1:9000                                                                          *:*                  
LISTEN      0      128                                                               *:9999                                                                          *:*                  
LISTEN      0      128                                                      10.0.0.131:80                                                                            *:*                  
LISTEN      0      128                                                               *:22                                                                            *:*                  
LISTEN      0      100                                                           [::1]:25                                                                         [::]:*                  
LISTEN      0      128                                                            [::]:22                                                                         [::]:*  


#修改web服务器1和web服务器2的日志格式
#分别在web服务器1和web服务器2安装httpd
[root@centos7-02 ~]# yum install httpd
[root@centos7-02 ~]# systemctl start httpd

[root@centos7-03 ~]# yum install httpd
[root@centos7-03 ~]# systemctl start httpd

[root@centos7-02 html]# vim /etc/httpd/conf/httpd.conf
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" \"%{X-forwarded-Port}i\" \"%{X-forwarded-Proto}i\"" combined
[root@centos7-02 html]# httpd -t
AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using fe80::5407:34ff:f129:19fe. Set the 'ServerName' directive globally to suppress this message
Syntax OK
[root@centos7-02 html]# systemctl restart httpd.service

[root@centos7-03 html]# vim /etc/httpd/conf/httpd.conf
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" \"%{X-forwarded-Port}i\" \"%{X-forwarded-Proto}i\"" combined
[root@centos7-03 html]# httpd -t
AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using fe80::5407:34ff:f129:19fe. Set the 'ServerName' directive globally to suppress this message
Syntax OK
[root@centos7-03 html]# systemctl restart httpd.service

#验证https
[root@centos7-02 ~]# echo 10.0.0.132 > /var/www/html/index.html
[root@centos7-03 ~]# echo 10.0.0.133 > /var/www/html/index.html

#客户端进行验证,先修改 cat /etc/hosts
[root@centos7-04 ~]# vim /etc/hosts
10.0.0.131 www.linux666666.org
[root@centos7-04 ~]# curl -k https://www.linux666666.org
10.0.0.133
[root@centos7-04 ~]# curl -k https://www.linux666666.org
10.0.0.132
[root@centos7-04 ~]# curl -Ik https://www.linux666666.org
HTTP/1.1 200 OK
date: Thu, 23 Jun 2022 15:47:12 GMT
server: Apache/2.4.6 (CentOS)
last-modified: Thu, 23 Jun 2022 15:40:31 GMT
etag: "b-5e21f461509b8"
accept-ranges: bytes
content-length: 11
content-type: text/html; charset=UTF-8

[root@centos7-04 ~]# curl -ILk http://www.linux666666.org
HTTP/1.1 302 Found
content-length: 0
location: https://www.linux666666.org/
cache-control: no-cache

HTTP/1.1 200 OK
date: Thu, 23 Jun 2022 15:51:12 GMT
server: Apache/2.4.6 (CentOS)
last-modified: Thu, 23 Jun 2022 15:39:41 GMT
etag: "b-5e21f43202bea"
accept-ranges: bytes
content-length: 11
content-type: text/html; charset=UTF-8

 

 

 

 

 

5、总结tomcat的核心组件以及根目录结构 

1 tomcat的目录结构

[root@centos7 tomcat]#pwd
/usr/local/tomcat
[root@centos7 tomcat]#tree -L 1 -d
.
├── bin
├── conf
├── lib
├── logs
├── temp
├── webapps
└── work

7 directories

1.1 bin目录

用于存放 Tomcat的启动、停止等相关程序文件和Shell脚本

[root@centos7 tomcat]#ll  bin/
total 880
-rw-r----- 1 tomcat tomcat  36192 Apr  1 00:05 bootstrap.jar
-rw-r----- 1 tomcat tomcat  16840 Apr  1 00:05 catalina.bat
-rwxr-x--- 1 tomcat tomcat  25294 Apr  1 00:05 catalina.sh
-rw-r----- 1 tomcat tomcat   1664 Apr  1 00:05 catalina-tasks.xml
-rw-r----- 1 tomcat tomcat   2123 Apr  1 00:05 ciphers.bat
-rwxr-x--- 1 tomcat tomcat   1997 Apr  1 00:05 ciphers.sh
-rw-r----- 1 tomcat tomcat  25308 Apr  1 00:05 commons-daemon.jar
-rw-r----- 1 tomcat tomcat 210038 Apr  1 00:05 commons-daemon-native.tar.gz
-rw-r----- 1 tomcat tomcat   2040 Apr  1 00:05 configtest.bat
-rwxr-x--- 1 tomcat tomcat   1922 Apr  1 00:05 configtest.sh
-rwxr-x--- 1 tomcat tomcat   9100 Apr  1 00:05 daemon.sh
-rw-r----- 1 tomcat tomcat   2091 Apr  1 00:05 digest.bat
-rwxr-x--- 1 tomcat tomcat   1965 Apr  1 00:05 digest.sh
-rw-r----- 1 tomcat tomcat   3460 Apr  1 00:05 setclasspath.bat
-rwxr-x--- 1 tomcat tomcat   3708 Apr  1 00:05 setclasspath.sh
-rw-r----- 1 tomcat tomcat   2020 Apr  1 00:05 shutdown.bat
-rwxr-x--- 1 tomcat tomcat   1902 Apr  1 00:05 shutdown.sh
-rw-r----- 1 tomcat tomcat   2022 Apr  1 00:05 startup.bat
-rwxr-x--- 1 tomcat tomcat   1904 Apr  1 00:05 startup.sh
-rw-r----- 1 tomcat tomcat  51379 Apr  1 00:05 tomcat-juli.jar
-rw-r----- 1 tomcat tomcat 429747 Apr  1 00:05 tomcat-native.tar.gz
-rw-r----- 1 tomcat tomcat   4574 Apr  1 00:05 tool-wrapper.bat
-rwxr-x--- 1 tomcat tomcat   5540 Apr  1 00:05 tool-wrapper.sh
-rw-r----- 1 tomcat tomcat   2026 Apr  1 00:05 version.bat
-rwxr-x--- 1 tomcat tomcat   1908 Apr  1 00:05 version.sh

1.2 conf目录

用于存放 Tomcat的相关配置文件

注意:配置文件大小写敏感

[root@centos7 tomcat]#ll  conf/
total 232
drwxr-x--- 3 tomcat tomcat   4096 Apr 22 20:16 Catalina
-rw------- 1 tomcat tomcat  12954 Apr  1 00:05 catalina.policy
-rw------- 1 tomcat tomcat   7707 Apr  1 00:05 catalina.properties
-rw------- 1 tomcat tomcat   1338 Apr  1 00:05 context.xml
-rw------- 1 tomcat tomcat   1149 Apr  1 00:05 jaspic-providers.xml
-rw------- 1 tomcat tomcat   2313 Apr  1 00:05 jaspic-providers.xsd
-rw------- 1 tomcat tomcat   3916 Apr  1 00:05 logging.properties
-rw------- 1 tomcat tomcat   7580 Apr  1 00:05 server.xml
-rw-r--r-- 1 tomcat tomcat     25 Apr 22 20:16 tomcat.conf
-rw------- 1 tomcat tomcat   2756 Apr  1 00:05 tomcat-users.xml
-rw------- 1 tomcat tomcat   2558 Apr  1 00:05 tomcat-users.xsd
-rw------- 1 tomcat tomcat 171879 Apr  1 00:05 web.xml

1.3 lib目录

Tomcat服务器依赖库目录,包含 Tomcat服务器运行环境依赖 jar包

[root@centos7 tomcat]#ll  lib/
total 8740
-rw-r----- 1 tomcat tomcat   12356 Apr  1 00:05 annotations-api.jar
-rw-r----- 1 tomcat tomcat   54059 Apr  1 00:05 catalina-ant.jar
-rw-r----- 1 tomcat tomcat  120997 Apr  1 00:05 catalina-ha.jar
-rw-r----- 1 tomcat tomcat 1721764 Apr  1 00:05 catalina.jar
-rw-r----- 1 tomcat tomcat   77625 Apr  1 00:05 catalina-storeconfig.jar
-rw-r----- 1 tomcat tomcat  294101 Apr  1 00:05 catalina-tribes.jar
-rw-r----- 1 tomcat tomcat 2450404 Apr  1 00:05 ecj-4.6.3.jar
-rw-r----- 1 tomcat tomcat   89441 Apr  1 00:05 el-api.jar
-rw-r----- 1 tomcat tomcat  170106 Apr  1 00:05 jasper-el.jar
-rw-r----- 1 tomcat tomcat  602651 Apr  1 00:05 jasper.jar
-rw-r----- 1 tomcat tomcat   26799 Apr  1 00:05 jaspic-api.jar
-rw-r----- 1 tomcat tomcat   61742 Apr  1 00:05 jsp-api.jar
-rw-r----- 1 tomcat tomcat  249335 Apr  1 00:05 servlet-api.jar
-rw-r----- 1 tomcat tomcat   10648 Apr  1 00:05 tomcat-api.jar
-rw-r----- 1 tomcat tomcat  883924 Apr  1 00:05 tomcat-coyote.jar
-rw-r----- 1 tomcat tomcat  285968 Apr  1 00:05 tomcat-dbcp.jar
-rw-r----- 1 tomcat tomcat   75372 Apr  1 00:05 tomcat-i18n-de.jar
-rw-r----- 1 tomcat tomcat  106263 Apr  1 00:05 tomcat-i18n-es.jar
-rw-r----- 1 tomcat tomcat  159755 Apr  1 00:05 tomcat-i18n-fr.jar
-rw-r----- 1 tomcat tomcat  180465 Apr  1 00:05 tomcat-i18n-ja.jar
-rw-r----- 1 tomcat tomcat  179224 Apr  1 00:05 tomcat-i18n-ko.jar
-rw-r----- 1 tomcat tomcat   48430 Apr  1 00:05 tomcat-i18n-ru.jar
-rw-r----- 1 tomcat tomcat  164122 Apr  1 00:05 tomcat-i18n-zh-CN.jar
-rw-r----- 1 tomcat tomcat  149103 Apr  1 00:05 tomcat-jdbc.jar
-rw-r----- 1 tomcat tomcat   34823 Apr  1 00:05 tomcat-jni.jar
-rw-r----- 1 tomcat tomcat  185499 Apr  1 00:05 tomcat-util.jar
-rw-r----- 1 tomcat tomcat  214053 Apr  1 00:05 tomcat-util-scan.jar
-rw-r----- 1 tomcat tomcat  238006 Apr  1 00:05 tomcat-websocket.jar
-rw-r----- 1 tomcat tomcat   38449 Apr  1 00:05 websocket-api.jar

1.4 logs目录

Tomcat默认的日志存放路径

[root@centos7 tomcat]#ll  logs/
total 52
-rw-r----- 1 tomcat tomcat 12238 Apr 22 22:17 catalina.2022-04-22.log
-rw-r----- 1 tomcat tomcat  6008 Apr 23 09:07 catalina.2022-04-23.log
-rw-r----- 1 tomcat tomcat 18246 Apr 23 09:07 catalina.out
-rw-r----- 1 tomcat tomcat     0 Apr 22 20:16 host-manager.2022-04-22.log
-rw-r----- 1 tomcat tomcat     0 Apr 23 09:07 host-manager.2022-04-23.log
-rw-r----- 1 tomcat tomcat   917 Apr 22 22:17 localhost.2022-04-22.log
-rw-r----- 1 tomcat tomcat   459 Apr 23 09:07 localhost.2022-04-23.log
-rw-r----- 1 tomcat tomcat  1422 Apr 22 20:22 localhost_access_log.2022-04-22.txt
-rw-r----- 1 tomcat tomcat     0 Apr 23 09:07 localhost_access_log.2022-04-23.txt
-rw-r----- 1 tomcat tomcat     0 Apr 22 20:16 manager.2022-04-22.log
-rw-r----- 1 tomcat tomcat     0 Apr 23 09:07 manager.2022-04-23.log

1.5 temp目录

存放tomcat在运行过程中产生的临时文件

[root@centos7 tomcat]#ll  temp/
total 0
-rw-r----- 1 tomcat tomcat 0 Apr  1 00:05 safeToDelete.tmp

1.6 webapps目录

Tomcat默认的Web应用部署目录

目录及文件 说明
webapps/ROOT/ 网站默认根目录,类似于httpd的/var/www/html/

假设有一个testapp,/usr/local/tomcat/webapps/testapp就相当于/var/www/html/testapp

[root@centos7 tomcat]#ll  webapps/
total 20
drwxr-x--- 15 tomcat tomcat 4096 Apr 22 20:16 docs
drwxr-x---  7 tomcat tomcat 4096 Apr 22 20:16 examples
drwxr-x---  6 tomcat tomcat 4096 Apr 22 20:16 host-manager
drwxr-x---  6 tomcat tomcat 4096 Apr 22 20:16 manager
drwxr-x---  3 tomcat tomcat 4096 Apr 22 20:16 ROOT

1.7 work目录

存放Web应用 JSP 代码生成和编译后产生的 class 字节码文件。

JSP中的Java代码执行过程:

(1)先找到 jsp页面,然后Tomcat会生成一个jsp对应的java文件和一个编译生成的class字节码文件。

(2)然后加载class字节码文件

(3)调用jsp的 service方法

(4)然后产生结果,并把结果返回给client

[root@centos7 tomcat]#tree work/
work/
└── Catalina
    └── localhost
        ├── docs
        ├── examples
        ├── host-manager
        ├── manager
        └── ROOT
            └── org
                └── apache
                    └── jsp
                        ├── index_jsp.class  #字节码文件
                        └── index_jsp.java   #servlet文件

10 directories, 2 files


[root@centos7 tomcat]#pwd
/usr/local/tomcat
[root@centos7 tomcat]#mv test.jsp /usr/local/tomcat/webapps/ROOT/

#浏览器访问jsp文件:http://10.0.0.7:8080/test.jsp

#当访问test.jsp文件后,jsp目录中会生成对应的新的test_jsp.java文件和test_jsp.class字节码文件

#Java程序上线前建议提前预热访问一下,所谓预热访问,就是生成应用程序对应的.java文件和.class文件,
等用户访问应用程序时,访问速度就会加快,这样用户访问就不慢了。

#注意:jsp目录类似于缓存,在上线Java程序时,经常会有一些版本升级,升级新版本前,要将旧版本的jsp目录下的
内容删除,下次访问时,就会生成新版本的jsp文件。

[root@centos7 tomcat]#tree work/
work/
└── Catalina
    └── localhost
        ├── docs
        ├── examples
        ├── host-manager
        ├── manager
        └── ROOT
            └── org
                └── apache
                    └── jsp
                        ├── index_jsp.class
                        ├── index_jsp.java
                        ├── test_jsp.class
                        └── test_jsp.java

10 directories, 4 files

2 tomcat的组件分类

顶级组件Server,代表整个Tomcat容器,一台主机可以启动多tomcat实例,需要确保端口不要产生冲突

服务类组件Service,实现组织Engine和Connector,建立两者之间关联关系,service 里面只能包含一个Engine

连接器组件Connector,有HTTP(默认端口8080/tcp)、HTTPS(默认端口8443/tcp)、AJP(默认端口8009/tcp)协议的连接器,AJP(Apache Jserv protocol)是一种基于TCP的二进制通讯协议。

容器类Engine、Host(虚拟主机)、Context(上下文件,解决路径映射)都是容器类组件,可以嵌入其它组件,内部配置如何运行应用程序。

内嵌类可以内嵌到其他组件内,valve、logger、realm、loader、manager等。以logger举例,在不同容器组件内分别定义。

集群类组件listener、cluster

2.1 Tomcat 内部组成

 

每一个组件都由一个Java“类”实现,这些组件大体可分为以下几个类型:

顶级组件:Server
服务类组件:Service
连接器组件:http, https, ajp(apache jserv protocol)
容器类:Engine, Host, Context
被嵌套类:valve, logger, realm, loader, manager, ...
集群类组件:listener, cluster, ...

2.2 tomcat的核心组件

Tomcat启动一个Server进程。可以启动多个Server,即tomcat的多实例, 但一般只启动一个

创建一个Service提供服务。可以创建多个Service,但一般也只创建一个

每个Service中,是Engine和其连接器Connector的关联配置可以为这个Service提供多个连接器Connector,这些Connector使用了不同的协议,绑定了不同的端口。其作用就是处理来自客户端的不同的连接请求或响应

Service 内部还定义了Engine,引擎才是真正的处理请求的入口,其内部定义多个虚拟主机Host

Engine对请求头做了分析,将请求发送给相应的虚拟主机如果没有匹配,数据就发往Engine上的defaultHost缺省虚拟主机Engine上的缺省虚拟主机可以修改Host 定义虚拟主机,虚拟主机有name名称,通过名称匹配

Context 定义应用程序单独的路径映射和配置

#多个组件关系 conf/server.xml
<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
  <Service name="Catalina">
    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
    <Connector protocol="AJP/1.3"
               address="::1"
               port="8009"
               redirectPort="8443" />
    <Engine name="Catalina" defaultHost="localhost">
      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">
          <Context >
		  <Context />          
      </Host>
    </Engine>
  </Service>
</Server>

3 tomcat的根目录结构

Tomcat中默认网站根目录是$CATALINA_BASE/webapps/在Tomcat中部署主站应用程序和其他应用程序,和之前WEB服务程序不同。

#假设有个网站:www.linux666666.com
#网页对应关系
/var/www/html/index.html  =>  http://www.linux666666.com/index.html
/var/www/html/test/index.html  =>  http://www.linux666666.com/test/index.html

/usr/local/tomcat/webapps/ROOT/index.jsp  =>  http://www.linux666666.com/index.jsp
/usr/local/tomcat/webapps/test/index.jsp  =>  http://www.linux666666.com/test/index.jsp

www.linux666666.com/test.html  =>  /usr/local/tomcat/webapps/ROOT/test.html
www.linux666666.com/blog/  =>  /usr/local/tomcat/webapps/blog/index.html

#目录对应关系:
www.linux666666.com/blog/   =>   /usr/local/tomcat/webapps/blog/  
www.linux666666.com/forum/   =>   /usr/local/tomcat/webapps/forum/ 

nginx

假设在nginx中部署2个网站应用eshop、forum,假设网站根目录是/data/nginx/html,那么部署可以是这样的。eshop解压缩所有文件放到 /data/nginx/html/ 目录下,forum 的文件放在 /data/nginx/html/forum/ 下。最终网站链接有以下对应关系

http://localhost/ 对应于eshop的应用,即 /data/nginx/html/
http://localhost/forum/ 对应于forum的应用,即/data/nginx/html/forum/

Tomcat

Tomcat中默认网站根目录是$CATALINA_BASE/webapps/

在Tomcat的webapps目录中,有个非常特殊的目录ROOT,它就是网站默认根目录。

将eshop解压后的文件放到这个$CATALINA_BASE/webapps/ROOT中。

bbs解压后文件都放在$CATALINA_BASE/webapps/forum目录下。

$CATALINA_BASE/webapps下面的每个目录都对应一个Web应用,即WebApp

最终网站链接有以下对应关系

http://localhost/ 对应于eshop的应用WebApp,即$CATALINA_BASE/webapps/ROOT/目录,
http://localhost/forum/ 对应于forum的应用WebApp,即$CATALINA_BASE/webapps/forum/

如果同时存在$CATALINA_BASE /webapps/ROOT/forum ,仍以 $CATALINA_BASE/webapps/forum/ 优先生效
每一个虚拟主机都可以使用appBase指令配置自己的站点目录,使用appBase目录下的ROOT目录作为主站目录。

[root@centos7 webapps]#pwd
/usr/local/tomcat/webapps
[root@centos7 webapps]#mkdir blog
[root@centos7 webapps]#vim blog/index.html
/usr/local/tomcat/webapps/blog/index.html
#浏览器访问:http://10.0.0.7:8080/blog/

[root@centos7 webapps]#pwd
/usr/local/tomcat/webapps
[root@centos7 webapps]#ls
blog  docs  examples  host-manager  manager  ROOT
[root@centos7 webapps]#vim ROOT/test.html
/usr/local/tomcat/webapps/ROOT/test.html

#浏览器访问:http://10.0.0.7:8080/test.html

4 JSP WebApp目录结构

$CATALINA_BASE/webapps下面的每个目录对应的WebApp,可能有以下子目录,但下面子目录是非必须的

主页配置:默认按以下顺序查找主页文件 index.html,index.htm、index.jsp

WEB-INF/:当前目录WebApp的私有资源路径,通常存储当前应用使用的web.xml和context.xml配置文件

META-INF/:类似于WEB-INF,也是私有资源的配置信息,和WEB-INF/目录一样浏览器无法访问

classes/:类文件,当前webapp需要的类

lib/:当前应用依赖的jar包

 

 

 

 

 

6、tomcat实现多虚拟主机

tomcat服务器                     10.0.0.131(主机名:centos7-01)

客户端                                 10.0.0.132(主机名:centos7-02)

tomcat下载地址:              https://archive.apache.org/dist/tomcat/

java下载地址:                   https://www.oracle.com/java/technologies/downloads/

#下载好tomcat和java包
[root@centos7-01 ~]# ls
apache-tomcat-9.0.48.tar.gz  jdk-8u291-linux-x64.tar.gz  
#采用脚本安装tomcat
[root@centos7-01 ~]# cat install_tomcat.sh 
#!/bin/bash

DIR=`pwd`
JDK_FILE="jdk-8u291-linux-x64.tar.gz"
TOMCAT_FILE="apache-tomcat-9.0.48.tar.gz"
JDK_DIR="/usr/local"
TOMCAT_DIR="/usr/local"

color () {
    RES_COL=60
    MOVE_TO_COL="echo -en \\033[${RES_COL}G"
    SETCOLOR_SUCCESS="echo -en \\033[1;32m"
    SETCOLOR_FAILURE="echo -en \\033[1;31m"
    SETCOLOR_WARNING="echo -en \\033[1;33m"
    SETCOLOR_NORMAL="echo -en \E[0m"
    echo -n "$2" && $MOVE_TO_COL
    echo -n "["
    if [ $1 = "success" -o $1 = "0" ] ;then
        ${SETCOLOR_SUCCESS}
        echo -n $"  OK  "    
    elif [ $1 = "failure" -o $1 = "1"  ] ;then
        ${SETCOLOR_FAILURE}
        echo -n $"FAILED"
    else
        ${SETCOLOR_WARNING}
        echo -n $"WARNING"
    fi
    ${SETCOLOR_NORMAL}
    echo -n "]"
    echo                                                                                                                              
}



install_jdk(){
if !  [  -f "$DIR/$JDK_FILE" ];then
    color 1 "$JDK_FILE 文件不存在" 
    exit; 
elif [ -d $JDK_DIR/jdk ];then
    color 1  "JDK 已经安装" 
    exit
else 
    [ -d "$JDK_DIR" ] || mkdir -pv $JDK_DIR
fi
tar xvf $DIR/$JDK_FILE  -C $JDK_DIR
cd  $JDK_DIR && ln -s jdk1.8.* jdk 

cat >  /etc/profile.d/jdk.sh <<EOF
export JAVA_HOME=$JDK_DIR/jdk
export JRE_HOME=\$JAVA_HOME/jre
export CLASSPATH=\$JAVA_HOME/lib/:\$JRE_HOME/lib/
export PATH=\$PATH:\$JAVA_HOME/bin
EOF
.  /etc/profile.d/jdk.sh
java -version && color 0 "JDK 安装完成" || { color 1  "JDK 安装失败" ; exit; }

}

install_tomcat(){
if ! [ -f "$DIR/$TOMCAT_FILE" ];then
    color 1 "$TOMCAT_FILE 文件不存在" 
    exit; 
elif [ -d $TOMCAT_DIR/tomcat ];then
    color 1 "TOMCAT 已经安装" 
    exit
else 
    [ -d "$TOMCAT_DIR" ] || mkdir -pv $TOMCAT_DIR
fi
tar xf $DIR/$TOMCAT_FILE -C $TOMCAT_DIR
cd  $TOMCAT_DIR && ln -s apache-tomcat-*/  tomcat
echo "PATH=$TOMCAT_DIR/tomcat/bin:"'$PATH' > /etc/profile.d/tomcat.sh
id tomcat &> /dev/null || useradd -r -s /sbin/nologin tomcat

cat > $TOMCAT_DIR/tomcat/conf/tomcat.conf <<EOF
JAVA_HOME=$JDK_DIR/jdk
EOF

chown -R tomcat.tomcat $TOMCAT_DIR/tomcat/

cat > /lib/systemd/system/tomcat.service  <<EOF
[Unit]
Description=Tomcat
#After=syslog.target network.target remote-fs.target nss-lookup.target
After=syslog.target network.target 

[Service]
Type=forking
EnvironmentFile=$TOMCAT_DIR/tomcat/conf/tomcat.conf
ExecStart=$TOMCAT_DIR/tomcat/bin/startup.sh
ExecStop=$TOMCAT_DIR/tomcat/bin/shutdown.sh
RestartSec=3
PrivateTmp=true
User=tomcat
Group=tomcat

[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now tomcat.service &> /dev/null
systemctl is-active tomcat.service &> /dev/null &&  color 0 "TOMCAT 安装完成" || { color 1 "TOMCAT 安装失败" ; exit; }

}

install_jdk 

install_tomcat

#开始安装
[root@centos7-01 ~]# bash install_tomcat.sh 
#提示安装完成
java version "1.8.0_291"
Java(TM) SE Runtime Environment (build 1.8.0_291-b10)
Java HotSpot(TM) 64-Bit Server VM (build 25.291-b10, mixed mode)
JDK 安装完成                                               [  OK  ]
TOMCAT 安装完成                                            [  OK  ]

#准备数据目录
[root@centos7-01 ~]# mkdir /data/website{1,2,3}/ROOT -pv
[root@centos7-01 ~]# cat /data/website1/ROOT/index.html
www.a.com   /data/website1/ROOT/index.html
[root@centos7-01 ~]# cat /data/website2/ROOT/index.html
www.b.com   /data/website2/ROOT/index.html
[root@centos7-01 ~]# cat /data/website3/ROOT/index.html
www.c.com   /data/website3/ROOT/index.html
#设置权限
[root@centos7-01 ~]# chown -R tomcat.tomcat /data/website{1,2,3}/
#修改配置
[root@centos7-01 ~]# cd /usr/local/tomcat/
[root@centos7-01 tomcat]# vim conf/server.xml
#server.xml的</Host>下方加入以下内容
      <Host name="www.a.com"  appBase="/data/website1/"
            unpackWARs="true" autoDeploy="true">
      </Host>
      <Host name="www.b.com"  appBase="/data/website2/"
            unpackWARs="true" autoDeploy="true">
      </Host>
      <Host name="www.c.com"  appBase="/data/website3/"
            unpackWARs="true" autoDeploy="true">
      </Host>
#重启tomcat
[root@centos7-01 tomcat]# systemctl restart tomcat.service
#客户端测试访问
[root@centos7-02 ~]# vim /etc/hosts
10.0.0.131 www.a.com www.b.com www.c.com
[root@centos7-02 ~]# curl www.a.com:8080
www.a.com   /data/website1/ROOT/index.html
[root@centos7-02 ~]# curl www.b.com:8080
www.b.com   /data/website2/ROOT/index.html
[root@centos7-02 ~]# curl www.c.com:8080
www.c.com   /data/website3/ROOT/index.html

 

 

 

 

 

 

7、nginx实现后端tomcat的负载均衡调度

nginx服务器                         10.0.0.131(主机名:centos7-01)

tomcat服务器1                     10.0.0.132(主机名:centos7-02)

tomcat服务器2                     10.0.0.133(主机名:centos7-03)

客户端                                  10.0.0.134(主机名:centos7-04)

tomcat下载地址:                 https://archive.apache.org/dist/tomcat/

java下载地址:                     https://www.oracle.com/java/technologies/downloads/

一、编译安装nginx

#安装依赖包
[root@centos7-01 ~]# yum -y install gcc pcre-devel openssl-devel zlib-devel
#创建账号
[root@centos7-01 ~]# useradd -s /sbin/nologin nginx
#下载nginx-1.18,注意需要提前安装wget程序
[root@centos7-01 ~]# wget http://nginx.org/download/nginx-1.18.0.tar.gz
#解压缩
[root@centos7-01 ~]# ls
anaconda-ks.cfg  nginx-1.18.0.tar.gz
[root@centos7-01 ~]# tar xf nginx-1.18.0.tar.gz
[root@centos7-01 ~]# cd nginx-1.18.0/
[root@centos7-01 nginx-1.18.0]# ls
auto  CHANGES  CHANGES.ru  conf  configure  contrib  html  LICENSE  man  README  src
#创建文件夹
[root@centos7-01 nginx-1.18.0]# mkdir -p  /apps/nginx
#编译
[root@centos7-01 nginx-1.18.0]# ./configure --prefix=/apps/nginx \
--user=nginx \
--group=nginx \
--with-http_ssl_module \
--with-http_v2_module \
--with-http_realip_module \
--with-http_stub_status_module \
--with-http_gzip_static_module \
--with-pcre \
--with-stream \
--with-stream_ssl_module \
--with-stream_realip_module
[root@centos7-01 nginx-1.18.0]# make -j 2
#安装
[root@centos7-01 nginx-1.18.0]# make install
#修改文件夹权限
[root@centos7-01 nginx-1.18.0]# chown -R nginx.nginx /apps/nginx/
#生成的目录
[root@centos7-01 nginx-1.18.0]# ll /apps/nginx/
total 0
drwxr-xr-x 2 nginx nginx 333 Jun 15 06:24 conf
drwxr-xr-x 2 nginx nginx  40 Jun 15 06:24 html
drwxr-xr-x 2 nginx nginx   6 Jun 15 06:24 logs
drwxr-xr-x 2 nginx nginx  19 Jun 15 06:24 sbin
#conf:保存nginx所有的配置文件,其中nginx.conf是nginx服务器的最核心最主要的配置文件,其他的.conf则是用来配置nginx相关的功能的,例如fastcgi功能使用的是fastcgi.conf和fastcgi_params两个文件,配置文件一般都有个样板配置文件,是文件名.default结尾,使用的使用将其复制为并将default去掉即可。

#html:保存了nginx服务器的web文件,但是可以更改为其他目录保存web文件,另外还有一个50x的web文件是默认的错误页面提示页面。

#logs:用来保存nginx服务器的访问日志错误日志等日志,logs目录可以放在其他路径,比如/var/logs/nginx里面。

#sbin:保存nginx二进制启动脚本,可以接受不同的参数以实现不同的功能。

#验证版本及编译参数
[root@centos7-01 nginx-1.18.0]# ls /apps/nginx/sbin/
nginx
#创建软连接
[root@centos7-01 nginx-1.18.0]# ln -s /apps/nginx/sbin/nginx /usr/sbin/
#查看版本
[root@centos7-01 nginx-1.18.0]# nginx -v
nginx version: nginx/1.18.0
#查看编译参数
[root@centos7-01 nginx-1.18.0]# nginx -V
nginx version: nginx/1.18.0
built by gcc 4.8.5 20150623 (Red Hat 4.8.5-44) (GCC) 
built with OpenSSL 1.0.2k-fips  26 Jan 2017
TLS SNI support enabled
configure arguments: --prefix=/apps/nginx --user=nginx --group=nginx --with-http_ssl_module --with-http_v2_module --with-http_realip_module --with-http_stub_status_module --with-http_gzip_static_module --with-pcre --with-stream --with-stream_ssl_module --with-stream_realip_module

#创建service文件
[root@centos7-01 nginx-1.18.0]# vim /usr/lib/systemd/system/nginx.service
[Unit]
Description=nginx - high performance web server
Documentation=http://nginx.org/en/docs/
After=network-online.target remote-fs.target nss-lookup.target
Wants=network-online.target

[Service]
Type=forking
PIDFile=/apps/nginx/run/nginx.pid
ExecStart=/apps/nginx/sbin/nginx -c /apps/nginx/conf/nginx.conf
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s TERM $MAINPID
LimitNOFILE=100000

[Install]
WantedBy=multi-user.target

##创建目录存放pid
[root@centos7-01 nginx-1.18.0]# mkdir /apps/nginx/run/
[root@centos7-01 nginx-1.18.0]# chown -R nginx.nginx /apps/nginx/run/
#修改配置文件
[root@centos7-01 nginx-1.18.0]# vim /apps/nginx/conf/nginx.conf
#配置文件加入pid路径
pid        /apps/nginx/run/nginx.pid;
#验证 Nginx 自启动文件
[root@centos7-01 nginx-1.18.0]# systemctl daemon-reload
[root@centos7-01 nginx-1.18.0]# systemctl enable --now nginx
#80端口已启动
[root@centos7-01 nginx-1.18.0]# ss -ntl
State       Recv-Q Send-Q                                                Local Address:Port                                                               Peer Address:Port              
LISTEN      0      128                                                               *:22                                                                            *:*                  
LISTEN      0      100                                                       127.0.0.1:25                                                                            *:*                  
LISTEN      0      128                                                            [::]:80                                                                         [::]:*                  
LISTEN      0      128                                                            [::]:22                                                                         [::]:*                  
LISTEN      0      100                                                           [::1]:25                                                                         [::]:*  

#pid已自动生成
[root@centos7-01 nginx-1.18.0]# ll /apps/nginx/run/
total 4
-rw-r--r-- 1 root root 5 Jun 15 06:38 nginx.pid
#客户端测试验证
[root@centos7-02 ]# curl -I 10.0.0.131
HTTP/1.1 200 OK
Server: nginx/1.18.0
Date: Wed, 15 Jun 2022 06:40:15 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Wed, 15 Jun 2022 06:24:53 GMT
Connection: keep-alive
ETag: "62a97b35-264"
Accept-Ranges: bytes

二、配置nginx代理

[root@centos7-01 apps]# vim /apps/nginx/conf/nginx.conf

   sendfile       on;
   keepalive_timeout  65;
   upstream tomcat-server {
        server  10.0.0.132:8080;
        server  10.0.0.133:8080;
   }
   server {
        listen       80;
        server_name  localhost;

        location / {
            root   html;
            index  index.html index.htm;
        }
        location ~* \.(jsp|do)$ {
            proxy_pass http://tomcat-server;
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
   }
#重启nginx服务
[root@centos7-01 apps]# systemctl restart nginx

三、tomcat服务器1和tomcat服务器2安装tomcat

#下载好tomcat和java包
[root@centos7-02 ~]# ls
apache-tomcat-9.0.48.tar.gz  jdk-8u291-linux-x64.tar.gz  
#采用脚本安装tomcat
[root@centos7-02 ~]# cat install_tomcat.sh 
#!/bin/bash

DIR=`pwd`
JDK_FILE="jdk-8u291-linux-x64.tar.gz"
TOMCAT_FILE="apache-tomcat-9.0.48.tar.gz"
JDK_DIR="/usr/local"
TOMCAT_DIR="/usr/local"

color () {
    RES_COL=60
    MOVE_TO_COL="echo -en \\033[${RES_COL}G"
    SETCOLOR_SUCCESS="echo -en \\033[1;32m"
    SETCOLOR_FAILURE="echo -en \\033[1;31m"
    SETCOLOR_WARNING="echo -en \\033[1;33m"
    SETCOLOR_NORMAL="echo -en \E[0m"
    echo -n "$2" && $MOVE_TO_COL
    echo -n "["
    if [ $1 = "success" -o $1 = "0" ] ;then
        ${SETCOLOR_SUCCESS}
        echo -n $"  OK  "    
    elif [ $1 = "failure" -o $1 = "1"  ] ;then
        ${SETCOLOR_FAILURE}
        echo -n $"FAILED"
    else
        ${SETCOLOR_WARNING}
        echo -n $"WARNING"
    fi
    ${SETCOLOR_NORMAL}
    echo -n "]"
    echo                                                                                                                              
}



install_jdk(){
if !  [  -f "$DIR/$JDK_FILE" ];then
    color 1 "$JDK_FILE 文件不存在" 
    exit; 
elif [ -d $JDK_DIR/jdk ];then
    color 1  "JDK 已经安装" 
    exit
else 
    [ -d "$JDK_DIR" ] || mkdir -pv $JDK_DIR
fi
tar xvf $DIR/$JDK_FILE  -C $JDK_DIR
cd  $JDK_DIR && ln -s jdk1.8.* jdk 

cat >  /etc/profile.d/jdk.sh <<EOF
export JAVA_HOME=$JDK_DIR/jdk
export JRE_HOME=\$JAVA_HOME/jre
export CLASSPATH=\$JAVA_HOME/lib/:\$JRE_HOME/lib/
export PATH=\$PATH:\$JAVA_HOME/bin
EOF
.  /etc/profile.d/jdk.sh
java -version && color 0 "JDK 安装完成" || { color 1  "JDK 安装失败" ; exit; }

}

install_tomcat(){
if ! [ -f "$DIR/$TOMCAT_FILE" ];then
    color 1 "$TOMCAT_FILE 文件不存在" 
    exit; 
elif [ -d $TOMCAT_DIR/tomcat ];then
    color 1 "TOMCAT 已经安装" 
    exit
else 
    [ -d "$TOMCAT_DIR" ] || mkdir -pv $TOMCAT_DIR
fi
tar xf $DIR/$TOMCAT_FILE -C $TOMCAT_DIR
cd  $TOMCAT_DIR && ln -s apache-tomcat-*/  tomcat
echo "PATH=$TOMCAT_DIR/tomcat/bin:"'$PATH' > /etc/profile.d/tomcat.sh
id tomcat &> /dev/null || useradd -r -s /sbin/nologin tomcat

cat > $TOMCAT_DIR/tomcat/conf/tomcat.conf <<EOF
JAVA_HOME=$JDK_DIR/jdk
EOF

chown -R tomcat.tomcat $TOMCAT_DIR/tomcat/

cat > /lib/systemd/system/tomcat.service  <<EOF
[Unit]
Description=Tomcat
#After=syslog.target network.target remote-fs.target nss-lookup.target
After=syslog.target network.target 

[Service]
Type=forking
EnvironmentFile=$TOMCAT_DIR/tomcat/conf/tomcat.conf
ExecStart=$TOMCAT_DIR/tomcat/bin/startup.sh
ExecStop=$TOMCAT_DIR/tomcat/bin/shutdown.sh
RestartSec=3
PrivateTmp=true
User=tomcat
Group=tomcat

[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now tomcat.service &> /dev/null
systemctl is-active tomcat.service &> /dev/null &&  color 0 "TOMCAT 安装完成" || { color 1 "TOMCAT 安装失败" ; exit; }

}

install_jdk 

install_tomcat

#开始安装
[root@centos7-02 ~]# bash install_tomcat.sh 
#提示安装完成
java version "1.8.0_291"
Java(TM) SE Runtime Environment (build 1.8.0_291-b10)
Java HotSpot(TM) 64-Bit Server VM (build 25.291-b10, mixed mode)
JDK 安装完成                                               [  OK  ]
TOMCAT 安装完成                                            [  OK  ]

四、tomcat服务器1的配置

# vim /usr/local/tomcat/conf/server.xml
#添加虚拟主机,并且将Host name test.node1.org在defaultHost位置设置为默认主机
[root@centos7-02 ~]# vim /usr/local/tomcat/conf/server.xml
<Engine name="Catalina" defaultHost="test.node1.org">
#在当前的server.xml的</Host>后面加上name="test.node1.org"
	<Host name="test.node1.org" appBase="/data/webapps" autoDeploy="true" >
	</Host>
</Engine>
#准备文件目录
[root@centos7-02 ~]# mkdir /data/webapps/ROOT -pv
mkdir: created directory ‘/data/webapps’
mkdir: created directory ‘/data/webapps/ROOT’
#创建测试页面
[root@centos7-02 ~]# vim /data/webapps/ROOT/index.jsp
<%@ page import="java.util.*" %>
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>tomcat test node1</title>
</head>
<body>
<div>On <%=request.getServerName() %></div>
<div><%=request.getLocalAddr() + ":" + request.getLocalPort() %></div>
<div>SessionID = <span style="color:blue"><%=session.getId() %></span></div>
<%=new Date()%>
</body>
</html>

#设置权限
[root@centos7-02 ~]# chown -R tomcat.tomcat /data/webapps/
#重启tomcat
[root@centos7-02 ~]# systemctl restart tomcat

五、tomcat服务器2的配置

# vim /usr/local/tomcat/conf/server.xml
#添加虚拟主机,并且将Host name test.node1.org在defaultHost位置设置为默认主机
[root@centos7-03 ~]# vim /usr/local/tomcat/conf/server.xml
<Engine name="Catalina" defaultHost="test.node1.org">
#在当前的server.xml的</Host>后面加上name="test.node1.org"
	<Host name="test.node1.org" appBase="/data/webapps" autoDeploy="true" >
	</Host>
</Engine>
#准备文件目录
[root@centos7-03 ~]# mkdir /data/webapps/ROOT -pv
mkdir: created directory ‘/data/webapps’
mkdir: created directory ‘/data/webapps/ROOT’
#创建测试页面
[root@centos7-03 ~]# vim /data/webapps/ROOT/index.jsp
<%@ page import="java.util.*" %>
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>tomcat test node1</title>
</head>
<body>
<div>On <%=request.getServerName() %></div>
<div><%=request.getLocalAddr() + ":" + request.getLocalPort() %></div>
<div>SessionID = <span style="color:blue"><%=session.getId() %></span></div>
<%=new Date()%>
</body>
</html>

#设置权限
[root@centos7-03 ~]# chown -R tomcat.tomcat /data/webapps/
#重启tomcat
[root@centos7-03 ~]# systemctl restart tomcat

六、客户端访问测试

[root@centos7-04 ~]# vim /etc/hosts
10.0.0.131 test.node1.org
[root@centos7-04 ~]# curl 10.0.0.131/index.jsp

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>tomcat test node1</title>
</head>
<body>
<div>On tomcat-server</div>
<div>10.0.0.133:8080</div>
<div>SessionID = <span style="color:blue">D35AD7A456CDBF8BA56A5597ED320726</span></div>
Thu Jun 23 22:07:36 UTC 2022
</body>
</html>
[root@centos7-04 ~]# curl 10.0.0.131/index.jsp

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>tomcat test node1</title>
</head>
<body>
<div>On tomcat-server</div>
<div>10.0.0.132:8080</div>
<div>SessionID = <span style="color:blue">F9BDAD6A6E094089593262EB814D1776</span></div>
Thu Jun 23 22:07:37 UTC 2022
</body>
</html>

 

 

 

 

 

8、简述memcached的工作原理

数据库可以分为两大类,一类为关系型数据库,例如我们之前使用过的MySQL,另一类是非传统关系型数据库,我们称之为NoSQL(Not Only SQL)。NoSQL包括k/v数据库(Key-value Store)、文档数据库(Document Store)、列存数据库(Column Store)、图数据库(Graph DB)和时序数据库(Time Series)等分类,memcached属于k/v数据库的一种,是一款高速运行的分布式缓存服务器。

memcached特点    memcached具有如下特点:

    ①基于Key-Value做内存缓存

    memcached只支持能序列化的数据类型,不支持持久化,它是基于Key-Value的内存缓存系统。

    ②集群同步功能

    memcached虽然没有像redis所具备的数据持久化功能,比如RDB和AOF都没有,但是可以通过做集群同步的方式,让各memcached服务器的数据进行同步,从而实现数据的一致性,即保证各memcached的数据是一样的,即使有任何一台memcached发生故障,只要集群中有一台memcached可用就不会出现数据丢失,当其他memcached重新加入到集群的时候,可以自动从有数据的memcached当中自动获取数据并提供服务。

    ③高效读写

    memcached借助了操作系统的libevent工具做高效的读写。libevent是个程序库,它将Linux的epoll、BSD类操作系统的kqueue等事件处理功能封装成统一的接口,即使对服务器的连接数增加,也能发挥高性能。memcached使用这个libevent库,因此能在Linux、BSD、Solaris等操作系统上发挥其高性能。

    ④实现session共享

    memcached支持最大的内存存储对象为1M,超过1M的数据可以使用客户端压缩或拆分报包放到多个key中,比较大的数据在进行读取的时候需要消耗的时间比较长,memcached最适合保存用户的session实现session共享。

    ⑤采用Slab Allocator机制

    memcached存储数据时,memcached会去申请1MB的内存,把该块内存称为一个slab,也称为一个page。

    ⑥支持多语言

    memcached支持多种开发语言,包括JAVA、C、Python、PHP、C#、Ruby和Perl等。

 

memcached工作机制2.1 内存分配机制

    应用程序运行需要使用内存存储数据,但对于一个缓存系统来说,申请内存、释放内存将十分频繁,非常容易导致大量内存碎片,最后导致无连续可用内存可用,memcached采用了Slab Allocator机制来分配、管理内存。在memcached中,我们需要注意Page、Chunk和Slab Class这三个关键术语:

    ①Page:分配给Slab的内存空间,默认为1MB,分配后就得到一个Slab,Slab分配之后内存按照固定字节大小等分成chunk。

    ②Chunk:用于缓存记录k/v值的内存空间,memcached会根据数据大小选择存到哪一个chunk中,假设chunk有128bytes、64bytes等多种,数据只有100bytes存储在128bytes中,存在少许浪费。Chunk最大就是Page的大小,即一个Page中就一个Chunk。

    ③Slab Class:Slab按照Chunk的大小分组,就组成不同的Slab Class,第一个Chunk大小为96B的Slab为Class 1,Chunk大小为120B的Slab为Class 2,如果有100bytes要存,那么Memcached会选择Slab Class 2 存储,因为它是120bytes的Chunk。Slab之间的差异可以使用Growth Factor 控制,默认1.25。

2.2 懒过期机制

    memcached不会监视数据是否过期,而是在取数据时才看是否过期,如果过期,就把数据有效期限标识为0,并不清除该数据,以后可以覆盖该位置存储其它数据。

2.3 LRU机制

    当内存不足时,memcached会使用LRU(Least Recently Used,最近最少使用)机制来查找可用空间,分配给新记录使用。

2.4 集群机制

    memcached集群,称为基于客户端的分布式集群,是由客户端实现集群功能,即memcached本身不支持集群。另外memcached集群内部并不互相通信,一切都需要客户端连接到memcached服务器后自行组织这些节点,并决定数据存储的节点。

 

memcached工作原理    ①memcached是基于key-value的方式来做内存缓存的,所以它的处理对象是一个个的kv对,key在经过哈希算法转化成hash-key,便于查找、对比以及做到尽可能的散列。同时,memcached用的是一个二级散列,通过一张大hash表来维护。

    ②memcached是C/S架构的,由服务器端(server)和客户端(client)两个核心组件组成,在一个memcached的查询中,client先通过计算key的哈希值来确定kv对所处在的server位置。当server确定后,客户端就会发送一个查询请求给对应的server,让它来查找确切的数据。因为这之间没有交互以及多播协议,所以memcached交互带给网络的影响是最小化的。

 

 

 

9、总结tomcat优化方法

1 Tomcat 性能优化在目前流行的互联网架构中,Tomcat在目前的网络编程中是举足轻重的,由于Tomcat的运行依赖于JVM,从虚拟机的角度把Tomcat的优化分为外部环境调优 JVM 和 Tomcat 自身调优两部分

1.1 jVM组成部分

  • 类加载子系统:使用Java语言编写。java Source Code文件,通过javac编译成。class Byte Code文件。class loader类加载器将所需所有类加载到内存,必要时将类实例化成实例

  • 运行时数据区:最消耗内存的空间,需要优化

  • 执行引擎:包括JIT (JustInTimeCompiler)即时编译器,GC垃圾回收器

  • 本地方法接口:将本地方法栈通过JNI(Java Native Interface)调用Native Method Libraries,比如:C,C++库等,扩展Java功能,融合不同的编程语言为Java所用

 

JVM运行时数据区域由下面部分构成:

  • Method Area (线程共享):方法区是所有线程共享的内存空间,存放已加载的类信息(构造方法,接口定义),常量(final),静态变量(static),运行时常量池等。但实例变量存放在堆内存中。从JDK8开始此空间由永久代改名为元空间

  • heap (线程共享):堆在虚拟机启动时创建,存放创建的所有对象信息。如果对象无法申请到可用内存将抛出OOM异常。堆是靠GC垃圾回收器管理的,通过-Xmx -Xms 指定最大堆和最小堆空间大小

  • Java stack (线程私有):Java栈是每个线程会分配一个栈,存放java中8大基本数据类型,对象引用,实例的本地变量,方法参数和返回值等,基于FILO()(First In Last Out),每个方法为一个栈帧

  • Program Counter Register (线程私有):PC寄存器就是一个指针,指向方法区中的方法字节码,每一个线程用于记录当前线程正在执行的字节码指令地址。由执行引擎读取下一条指令。因为线程需要切换,当一个线程被切换回来需要执行的时候,知道执行到哪里了

  • Native Method stack (线程私有):本地方法栈为本地方法执行构建的内存空间,存放本地方法执行时的局部变量、操作数等。

所谓本地方法,使用native 关健字修饰的方法,比如:Thread.sleep方法。简单的说是非Java实现的方法,例如操作系统的C编写的库提供的本地方法,Java调用这些本地方法接口执行。但是要注意,本地方法应该避免直接编程使用,因为Java可能跨平台使用,如果用了Windows API,换到了Linux平台部署就有了问题

1.2 GC (Garbage Collection) 垃圾收集器在堆内存中如果创建的对象不再使用,仍占用着内存,此时即为垃圾。需要及时进行垃圾回收,从而释放内存空间给其它对象使用

其实不同的开发语言都有垃圾回收问题,C,C++需要程序员人为回收,造成开发难度大,容易出错等问题,但执行效率高,而JAVA和Python中不需要程序员进行人为的回收垃圾,而由JVM或相关程序自动回收垃圾,减轻程序员的开发难度,但可能会造成执行效率低下

堆内存里面经常创建、销毁对象,内存也是被使用、被释放。如果不妥善处理,一个使用频繁的进程,可能会出现虽然有足够的内存容量,但是无法分配出可用内存空间,因为没有连续成片的内存了,内存全是碎片化的空间。

所以需要有合适的垃圾回收机制,确保正常释放不再使用的内存空间,还需要保证内存空间尽可能的保持一定的连续

对于垃圾回收,需要解决三个问题:

  • 哪些是垃圾要回收

  • 怎么回收垃圾

  • 什么时候回收垃圾

 

1.2.1 Garbage 垃圾确定方法

  • 引用计数:每一个堆内对象上都与一个私有引用计数器,记录着被引用的次数,引用计数清零,该对象所占用堆内存就可以被回收。循环引用的对象都无法将引用计数归零,就无法清除。Python中即使用此种方式

  • 根搜索(可达)算法 Root Searching

 

1.2.2垃圾回收基本算法

1.2.2.1 标记-清除 Mark-Sweep

分垃圾标记阶段和内存释放阶段。标记阶段,找到所有可访问对象打个标记。清理阶段,遍历整个堆,对未标记对象(即不再使用的对象)逐一进行清理。

标记-清除最大的问题是会造成内存碎片,但是不浪费空间,效率较高(如果对象较多,逐一删除效率也会影响)

1.2.2.2 标记-压缩 (压实)Mark-Compact分垃圾标记阶段和内存整理阶段。标记阶段,找到所有可访问对象打个标记。内存清理阶段时,整理时将对象向内存一端移动,整理后存活对象连续的集中在内存一端。

标记-压缩算法的好处是整理后内存空间连续分配,有大段的连续内存可分配,没有内存碎片。缺点是内存整理过程有消耗,效率相对低下

1.2.2.3 复制 Copying先将可用内存分为大小相同两块区域A和B,每次只用其中一块,比如A。当A用完后,则将A中存活的对象复制到B。复制到B的时候连续的使用内存,最后将A一次性清除干净。

缺点是比较浪费内存,只能使用原来一半内存,因为内存对半划分了,复制过程毕竟也是有代价。好处是没有碎片,复制过程中保证对象使用连续空间,且一次性清除所有垃圾,所以效率很高

1.2.2.4 多种算法总结没有最好的算法,在不同场景选择最合适的算法

  • 效率:标记清除算法>复制算法> 标记压缩算法

  • 内存整齐度:复制算法=标记压缩算法> 标记清除算法

  • 内存利用率:标记压缩算法=标记清除算法>复制算法

 

1.2.2.5 STW

对于大多数垃圾回收算法而言,GC线程工作时,停止所有工作的线程,称为Stop The World。GC 完成时,恢复其他工作线程运行。这也是JVM运行中最头疼的问题。

1.2.3 分代堆内存GC策略对不同数据进行区分管理,不同分区对数据实施不同回收策略,分而治之

1.2.3.1 堆内存分代将heap内存空间分为三个不同类别:年轻代、老年代、持久代

Heap堆内存分为

  • 年轻代Young:Young Generation

  • 伊甸园区eden:只有一个,刚刚创建的对象

  • 幸存(存活)区Servivor Space:有2个幸存区,一个是from区,一个是to区。大小相等、地位相同、可互换。

    • from 指的是本次复制数据的源区

    • to 指的是本次复制数据的目标区

  • 老年代Tenured:Old Generation,长时间存活的对象

永久代:JDK1.7之前使用,即Method Area方法区,保存JVM自身的类和方法,存储JAVA运行时的环境信息,JDK1.8后改名为 MetaSpace,此空间不存在垃圾回收,关闭JVM会释放此区域内存,此空间物理上不属于heap内存,但逻辑上存在于heap内存

  • 永久代必须指定大小限制,字符串常量JDK1.7存放在永久代,1.8后存放在heap中

  • MetaSpace 可以设置,也可不设置,无上限

规律:一般情况99%的对象都是临时对象

查看JVM内存分配情况

[root@centos7 ~]#cat Heap.java
public class Heap {
    public static void main(String[] args){
        //返回虚拟机试图使用的最大内存,字节单位
        long max = Runtime.getRuntime().maxMemory();
        //返回JVM初始化总内存
        long total = Runtime.getRuntime().totalMemory();

        System.out.println("max="+max+"字节\t"+(max/(double)1024/1024)+"MB");
        System.out.println("total="+total+"字节\t"+(total/(double)1024/1024)+"MB");
    }
}

[root@centos7 ~]#javac Heap.java
[root@centos7 ~]#java -classpath . Heap
max=462422016字节	441.0MB
total=32505856字节	31.0MB
[root@centos7 ~]#free -m
              total        used        free      shared  buff/cache   available
Mem:           1980         275        1495           9         209        1552
Swap:          2047           0        2047
[root@centos7 ~]#echo 441*4|bc
1764
[root@centos7 ~]#echo 1980/31|bc
63
#默认JVM试图分配的最大内存为总内存的1/4,初始化默认总内存为总内存的1/64

[root@centos7 ~]#java -XX:+PrintGCDetails -cp . Heap
max=462422016字节	441.0MB
total=32505856字节	31.0MB
Heap
 PSYoungGen      total 9728K, used 870K [0x00000000f5b00000, 0x00000000f6580000, 0x0000000100000000)
  eden space 8704K, 10% used [0x00000000f5b00000,0x00000000f5bd9b88,0x00000000f6380000)
  from space 1024K, 0% used [0x00000000f6480000,0x00000000f6480000,0x00000000f6580000)
  to   space 1024K, 0% used [0x00000000f6380000,0x00000000f6380000,0x00000000f6480000)
 ParOldGen       total 22016K, used 0K [0x00000000e1000000, 0x00000000e2580000, 0x00000000f5b00000)
  object space 22016K, 0% used [0x00000000e1000000,0x00000000e1000000,0x00000000e2580000)
 Metaspace       used 2534K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 269K, capacity 386K, committed 512K, reserved 1048576K
[root@centos7 ~]#echo 9728+22016|bc
31744
[root@centos7 ~]#echo 31*1024|bc
31744
#说明年轻代+老年代占用了所有heap空间,Metaspace实际不占heap空间,逻辑上存在于Heap

1.2.3.2 年轻代回收 Minor GC

  1. 起始时,所有新建对象(特大对象直接进入老年代)都出生在eden,当eden满了,启动GC。这个称为Young GC 或者 Minor GC。

  2. 先标记eden存活对象,然后将存活对象复制到s0(假设本次是s0,也可以是s1,它们可以调换),eden剩余所有空间都清空。GC完成。

  3. 继续新建对象,当eden再次满了,启动GC。

  4. 先同时标记eden和s0中存活对象,然后将存活对象复制到s1。将eden和s0清空,此次GC完成。

  5. 继续新建对象,当eden满了,启动GC。

  6. 先标记eden和s1中存活对象,然后将存活对象复制到s0。将eden和s1清空,此次GC完成。

以后就重复上面的步骤。

通常场景下,大多数对象都不会存活很久,而且创建活动非常多,新生代就需要频繁垃圾回收。

但是,如果一个对象一直存活,它最后就在from、to来回复制,如果from区中对象复制次数达到阈值 (默认15次,CMS为6次,可通过java的选项 -XX:MaxTenuringThreshold=N 指定),就直接复制到老年代。

1.2.3.3 老年代回收 Major GC进入老年代的数据较少,所以老年代区被占满的速度较慢,所以垃圾回收也不频繁。

如果老年代也满了,会触发老年代GC,称为Old GC或者 Major GC。

由于老年代对象一般来说存活次数较长,所以较常采用标记-压缩算法。

当老年代满时,会触发 Full GC,即对所有”代”的内存进行垃圾回收

Minor GC比较频繁,Major GC较少。但一般Major GC时,由于老年代对象也可以引用新生代对象,所以先进行一次Minor GC,然后在Major GC会提高效率。可以认为回收老年代的时候完成了一次Full GC。

所以可以认为 MajorGC = FullGC

1.2.3.4 GC 触发条件Minor GC 触发条件:当eden区满了触发Full GC 触发条件:

  • 老年代满了

  • System.gc()手动调用。不推荐

年轻代:

  • 存活时长低

  • 适合复制算法

老年代:

  • 区域大,存活时长高

  • 适合标记压缩算法

1.2.4 java 内存调整相关参数1.2.4.1 JVM 内存常用相关参数

选项分类

  • -选项名称 此为标准选项,所有HotSpot都支持

  • -X选项名称 此为稳定的非标准选项

  • -XX:选项名称 非标准的不稳定选项,下一个版本可能会取消

参数 说明 举例
-Xms 设置应用程序初始使用的堆内存大小(年轻代+老年代) -Xms2g
-Xmx 设置应用程序能获得的最大堆内存,早期JVM不建议超过32G,内存管理效率下降 -Xms4g
-XX:NewSize 设置初始新生代大小 -XX:NewSize=128m
-XX:MaxNewSize 设置最大新生代内存空间 -XX:MaxNewSize=256m
-Xmnsize 同时设置-XX:NewSize 和 -XX:MaxNewSize,代替两者 -Xmn1g
-XX:NewRatio 以比例方式设置新生代和老年代 -XX:NewRatio=2 new/old=1/2
-XX:SurvivorRatio 以比例方式设置eden和survivor(S0或S1) -XX:SurvivorRatio=6 eden/survivor=6/1 new/survivor=8/1
-Xss 设置每个线程私有的栈空间大小,依据具体线程大小和数量 -Xss256k

 

查看java的选项帮助

#查看java命令标准选项
[root@centos7 ~]#java

#查看java的非标准选项
[root@centos7 ~]#java -X

#查看所有不稳定选项的当前生效值
[root@centos7 ~]#java -XX:+PrintFlagsFinal

#查看所有不稳定选项的默认值
[root@centos7 ~]#java -XX:+PrintFlagsInitial

指定内存空间

[root@centos7 ~]#java -Xms1024m -Xmx1024m -XX:+PrintGCDetails -cp . Heap
max=1029177344字节	981.5MB
total=1029177344字节	981.5MB
Heap
 PSYoungGen      total 305664K, used 15729K [0x00000000eab00000, 0x0000000100000000, 0x0000000100000000)
  eden space 262144K, 6% used [0x00000000eab00000,0x00000000eba5c420,0x00000000fab00000)
  from space 43520K, 0% used [0x00000000fd580000,0x00000000fd580000,0x0000000100000000)
  to   space 43520K, 0% used [0x00000000fab00000,0x00000000fab00000,0x00000000fd580000)
 ParOldGen       total 699392K, used 0K [0x00000000c0000000, 0x00000000eab00000, 0x00000000eab00000)
  object space 699392K, 0% used [0x00000000c0000000,0x00000000c0000000,0x00000000eab00000)
 Metaspace       used 2535K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 269K, capacity 386K, committed 512K, reserved 1048576K

1.2.4.2 Tomcat的JVM参数设置

在bin/catalina.sh中增加一行

[root@centos7 ~]#vim /usr/local/tomcat/bin/catalina.sh
# OS specific support.  $var _must_ be set to either true or false.
#添加下面一行
JAVA_OPTS="-server -Xms512m -Xmx512m -XX:NewSize=100m -XX:MaxNewSize=200m"

cygwin=false
darwin=false

#-server:VM运行在server模式,为在服务器端最大化程序运行速度而优化

#重启Tomcat服务
[root@centos7 ~]#systemctl restart tomcat.service

#确认参数设置
[root@centos7 ~]#ps aux|grep tomcat
tomcat     1781 30.7  7.7 3040164 156628 ?      Sl   08:18   0:33 /usr/local/jdk/bin/java -Djava.util.logging.config.file=/usr/local/tomcat/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -server -Xms512m -Xmx512m -XX:NewSize=100m -XX:MaxNewSize=200m -Djdk.tls.ephemeralDHKeySize=2048 -Djava.protocol.handler.pkgs=org.apache.catalina.webresources -Dorg.apache.catalina.security.SecurityListener.UMASK=0027 -Dignore.endorsed.dirs=-classpath /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar -Dcatalina.base=/usr/local/tomca -Dcatalina.home=/usr/local/tomcat -Djava.io.tmpdir=/usr/local/tomcat/temp org.apache.catalina.startup.Bootstrap start
root       1816  0.0  0.0 112808   968 pts/0    S+   08:20   0:00 grep --color=auto tomcat

1.2.5 垃圾收集方式

按工作模式不同:指的是GC线程和工作线程是否一起运行

  • 独占垃圾回收器:只有GC在工作,STW 一直进行到回收完毕,工作线程才能继续执行

  • 并发垃圾回收器:让GC线程垃圾回收某些阶段可以和工作线程一起进行,如:标记阶段并行,回收阶段仍然串行

按回收线程数:指的是GC线程是否串行或并行执行

  • 串行垃圾回收器:一个GC线程完成回收工作

  • 并行垃圾回收器:多个GC线程同时一起完成回收工作,充分利用CPU资源

1.2.6 调整策略

对JVM调整策略应用极广

  • 在WEB领域中Tomcat等

  • 在大数据领域Hadoop生态各组件

  • 在消息中间件领域的Kafka等

  • 在搜索引擎领域的ElasticSearch、Solr等

注意:在不同领域和场景对JVM需要不同的调整策略

  • 减少 STW 时长,串行变并行

  • 减少 GC 次数,要分配合适的内存大小

一般情况下,大概可以使用以下原则:

  • 客户端或较小程序,内存使用量不大,可以使用串行回收

  • 对于服务端大型计算,可以使用并行回收

  • 大型WEB应用,用户端不愿意等待,尽量少的STW,可以使用并发回收

1.2.7 垃圾回收器1.2.7.1 按分代设置不同垃圾回收器新生代

  • 新生代串行收集器Serial:单线程、独占式串行,采用复制算法,简单高效但会造成STW

  • 新生代并行回收收集器PS(Parallel Scavenge):多线程并行、独占式,会产生STW,使用复制算法,关注调整吞吐量,此收集器关注点是达到一个可控制的吞吐量

吞吐量 = 运行用户代码时间/(运行用户代码时间+垃圾收集时间),比如虚拟机总共运行100分钟,其中垃圾回收花掉1分钟,那吞吐量就是99%。

高吞吐量可以高效率利用CPU时间,尽快完成运算任务,主要适合在后台运算而不需要太多交互的任务。

除此之外,Parallel Scavenge 收集器具有自适应调节策略,它可以将内存管理的调优任务交给虚拟机去完成。自适应调节策略也是Parallel Scavenge与 ParNew 收集器的一个重要区别。

和ParNew不同,PS不可以和老年代的CMS组合

  • 新生代并行收集器ParNew:就是Serial 收集器的多线程版,将单线程的串行收集器变成了多线程并行、独占式,使用复制算法,相当于PS的改进版

经常和CMS配合使用,关注点是尽可能地缩短垃圾收集时用户线程的停顿时间,适合需要与用户交互的程序,良好的响应速度能提升用户体验

老年代

  • 老年代串行收集器Serial Old:Serial Old是Serial收集器的老年代版本,单线程、独占式串行,回收算法使用标记压缩

  • 老年代并行回收收集器Parallel Old:多线程、独占式并行,回收算法使用标记压缩,关注调整吞吐量

Parallel Old收集器是Parallel Scavenge 收集器的老年代版本,这个收集器是JDK1.6之后才开始提供,Parallel Scavenge 收集器无法与CMS收集器配合工作,因为一个是为了吞吐量,一个是为了客户体验(也就是暂停时间的缩短)

  • CMS (Concurrent Mark Sweep并发标记清除算法) 收集器

    在某些阶段尽量使用和工作线程一起运行,减少STW时长(200ms以内),提升响应速度,是互联网服务端BS系统上较佳的回收算法

分为4个阶段:初始标记、并发标记、重新标记、并发清除,在初始标记、重新标记时需要STW。

初始标记:此过程需要STW(Stop The Word),只标记一下GC Roots能直接关联到的对象,速度很快。

并发标记:就是GC Roots进行扫描可达链的过程,为了找出哪些对象需要收集。这个过程远远慢于初始标记,但它是和用户线程一起运行的,不会出现STW,所有用户并不会感受到。

重新标记:为了修正在并发标记期间,用户线程产生的垃圾,这个过程会比初始标记时间稍微长一点,但是也很快,和初始标记一样会产生STW。

并发清理:在重新标记之后,对现有的垃圾进行清理,和并发标记一样也是和用户线程一起运行的,耗时较长(和初始标记比的话),不会出现STW。

由于整个过程中,耗时最长的并发标记和并发清理都是与用户线程一起执行的,所以总体上来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。

JVM 1.8 默认的垃圾回收器:PS + Parallel Old,所以大多数都是针对此进行调优

1.2.7.2 垃圾收集器设置

优化调整Java 相关参数的目标:尽量减少FullGC和STW

通过以下选项可以单独指定新生代、老年代的垃圾收集器

  • -server 指定为Server模式,也是默认值,一般使用此工作模式

  • -XX:+UseSerialGC

    • 运行在Client模式下,新生代是Serial,老年代使用Serial Old

  • -XX:+UseParNewGC

    • 新生代使用ParNew,老年代使用Serial Old

  • -XX:+UseParallelGC

    • 运行于server模式下,新生代使用Parallel Scavenge , 老年代使用Serial Old

  • -XX:+UseParallelOldGC

    • 新生代使用Paralell Scavenge, 老年代使用Paralell Old

  • -XX:ParallelGCThreads=N,在关注吞吐量的场景使用此选项增加并行线程数

  • -XX:+UseConcMarkSweepGC

    • 新生代使用ParNew,老年代优先使用CMS,备选方式为Serial Old

    • 响应时间要短,停顿短使用这个垃圾收集器

  • -XX:CMSInitiatingOccupancyFraction=N,N为0-100整数表示达到老年代的大小的百分比多少触发回收,默认68

    • -XX:+UseCMSCompactAtFullCollection 开启此值,在CMS收集后,进行内存碎片整理

  • -XX:CMSFullGCsBeforeCompaction=N 设定多少次CMS后,进行一次内存碎片整理

  • -XX:+CMSParallelRemarkEnabled 降低标记停顿

指定垃圾回收设置

#将参数加入到bin/catalina.sh中,重启观察Tomcat服务。老年代已经使用CMS
[root@centos7 ~]#vim /usr/local/tomcat/bin/catalina.sh
# OS specific support.  $var _must_ be set to either true or false.
#添加下面一行
JAVA_OPTS="-server -Xmx512m -Xms128m -XX:NewSize=48m -XX:MaxNewSize=200m -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=5"

cygwin=false
darwin=false

#重启Tomcat服务
[root@centos7 ~]#systemctl restart tomcat.service

#确认参数设置
[root@centos7 ~]#ps aux|grep tomcat
tomcat     2214 40.0  5.8 3057244 118992 ?      Sl   11:08   0:16 /usr/local/jdk/bin/java -Djava.util.logging.config.file=/usr/local/tomcat/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -server -Xmx512m -Xms128m -XX:NewSize=48m -XX:MaxNewSize=200m -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=5 -Djdk.tls.ephemeralDHKeySize=2048 -Djava.protocol.handler.pkgs=org.apache.catalina.webresources -Dorg.apache.catalina.security.SecurityListener.UMASK=0027 -Dignore.endorsed.dirs= -classpath /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar -Dcatalina.base=/usr/local/tomcat -Dcatalina.home=/usr/local/tomcat -Djava.io.tmpdir=/usr/local/tomcat/temp org.apache.catalina.startup.Bootstrap start
root       2248  0.0  0.0 112808   968 pts/0    S+   11:09   0:00 grep --color=auto tomcat

1.2.8 JAVA参数总结

参数名称 含义 默认值 描述
-Xms 初始堆大小 物理内存的1/64(<1GB) 默认(MinHeapFreeRatio参数可以调整)空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制
-Xmx 最大堆大小 物理内存的1/4(<1GB) 默认(MaxHeapFreeRatio参数可以调整)空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制
-Xmn 年轻代大小(1.4or lator)   注意:此处的大小是(eden+ 2 survivor space)。与jmap -heap中显示的Newgen是不同的。 整个堆大小=年轻代大小 + 年老代大小 +持久代大小. 增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8
-XX:NewSize 设置年轻代大小(for 1.3/1.4)    
-XX:MaxNewSize 年轻代最大值(for 1.3/1.4)    
-XX:PermSize 设置持久代(perm gen)初始值 物理内存的1/64  
XX:MaxPermSize 设置持久代最大值 物理内存的1/4  
-Xss 每个线程的堆栈大小   JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K,更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。一般小的应用,如果栈不是很深,应该是128k够用的。大的应用建议使用256k。这个选项对性能影响比较大,需要严格的测试。

 

参数名称 含义 默认值 描述
-XX:ThreadStackSize 线程堆栈大小   0表示使用默认堆栈大小[Sparc:512;Solaris] x86:320;Sparc 64位:1024;Linux amd64:1024
-XX:NewRatio 年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)   -XX:NewRatio=4表示年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5Xms=Xmx并且设置了Xmn的情况下,该参数不需要进行设置。
-XX:SurvivorRatio Eden区与Survivor区的大小比值   设置为8,则两个Survivor区,与一个Eden区的比值为2:8,一个Survivor区占整个年轻代的1/10
-XX:LargePageSizeInBytes 内存页的大小不可设置过大, 会影响Perm的大小   =128m
-XX:+UseFastAccessorMethods 原始类型的快速优化    
-XX:+DisableExplicitGC 关闭System.gc()   这个参数需要严格的测试
-XX:MaxTenuringThreshold 垃圾最大年龄   如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年轻代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加再年轻代即被回收的概率。该参数只有在串行GC时才有效
-XX:+AggressiveOpts 加快编译    
-XX:+UseBiasedLocking 锁机制的性能改善    
-Xnoclassgc 禁用垃圾回收    
XX:SoftRefLRUPolicyMSPerMB 每兆堆空闲空间中SoftReference的存活时间   可达的对象在上次被引用后将保留一段时间。 缺省值是堆中每个空闲兆字节的生命周期的一秒钟
-XX:PretenureSizeThreshold 对象超过多大是直接在旧生代分配 0 单位字节。新生代采用Parallel Scavenge GC时无效。另一种直接在旧生代分配的情况是大的数组对象,且数组中无外部引用对象。
-XX:TLABWasteTargetPercent TLAB占eden区的百分比 1%  
-XX:+CollectGen0First FullGC时是否先YGC false  

 

并行收集器相关参数

参数名称 含义 默认值 描述
-XX:+UseParallelGC Full GC采用parallel MSC   选择垃圾收集器为并行收集器,此配置仅对年轻代有效。即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集
-XX:+UseParNewGC 设置年轻代为并行收集   可与CMS收集同时使用。 JDK5.0以上,JVM会根据系统配置自行设置,所以无需再设置此值
-XX:ParallelGCThreads 并行收集器的线程数   此值最好配置与处理器数目相等。 同样适用于CMS
-XX:+UseParallelOldGC 年老代垃圾收集方式为并行收集(Parallel Compacting)   这个是JAVA 6出现的参数选项
-XX:MaxGCPauseMillis 每次年轻代垃圾回收的最长时间(最大暂停时间)   如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值
-XX:+UseAdaptiveSizePolicy 自动选择年轻代区大小和相应的Survivor区比例   设置此选项后,并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低相应时间或者收集频率等。此值建议使用并行收集器时,一直打开。
-XX:GCTimeRatio 设置垃圾回收时间占程序运行时间的百分比   公式为1/(1+n)
XX:+ScavengeBeforeFullGC Full GC前调用YGC true  

 

CMS相关参数

参数名称 含义 默认值 描述
-XX:+UseConcMarkSweepGC 使用CMS内存收集   测试中配置这个以后,-XX:NewRatio=4的配置可能失效,所以,此时年轻代大小最好用-Xmn设置
-XX:+AggressiveHeap     试图使用大量的物理内存。 长时间大内存使用的优化,能检查计算资源(内存, 处理器数量) 至少需要256MB内存。
-XX:CMSFullGCsBeforeCompaction 多少次后进行内存压缩   由于并发收集器不对内存空间进行压缩,整理,所以运行一段时间以后会产生”碎片”,使得运行效率降低。此值设置运行多少次GC以后对内存空间进行压缩,整理。
-XX:+CMSParallelRemarkEnabled 降低标记停顿    
-XX+UseCMSCompactAtFullCollection 在FULLGC的时候,对老年代的压缩   CMS是不会移动内存的,因此,这个非常容易产生碎片,导致内存不够用。 因此,内存的压缩这个时候就会被启用。增加这个参数是个好习惯。 可能会影响性能,但是可以消除碎片
-XX:+UseCMSInitiatingOccupancyOnly 使用手动定义初始化定义开始CMS收集   禁止hostspot自行触发CMS GC
-XX:CMSInitiatingOccupancyFraction=70 使用cms作为垃圾回收,使用70%后开始CMS收集 92  
XX:CMSInitiatingPermOccupancyFraction 设置PermGen使用到达多少比率时触发 92  
-XX:+CMSIncrementalMode 设置为增量模式   用于单CPU情况

 

1.3 Tomcat 性能优化常用配置

1.3.1 内存空间优化

JAVA_OPTS="-server -Xms4g -Xmx4g -XX:NewSize= -XX:MaxNewSize= "

-server:服务器模式
-Xms:堆内存初始化大小
-Xmx:堆内存空间上限
-XX:NewSize=:新生代空间初始化大小
-XX:MaxNewSize=:新生代空间最大值

生产案例:

[root@centos7 ~]#vim /usr/local/tomcat/bin/catalina.sh
# OS specific support.  $var _must_ be set to either true or false.
JAVA_OPTS="-server -Xms4g -Xmx4g -Xss512k -Xmn1g -XX:CMSInitiatingOccupancyFraction=65 -XX:+AggressiveOpts -XX:+UseBiasedLocking -XX:+DisableExplicitGC -XX:MaxTenuringThreshold=10 -XX:NewRatio=2 -XX:PermSize=128m -XX:MaxPermSize=512m -XX:CMSFullGCsBeforeCompaction=5 -XX:+ExplicitGCInvokesConcurrent -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:LargePageSizeInBytes=128m -XX:+UseFastAccessorMethods"

cygwin=false
darwin=false
#一台tomcat服务器并发连接数不高,生产环境建议分配物理内存通常4G到8G较多,如果需要更多连接,一般会利用虚拟化技术实现多台tomcat

1.3.2 线程池调整

[root@centos7 ~]#vim /usr/local/tomcat/conf/server.xml
    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />

常用属性:

  • connectionTimeout :连接超时时长,单位ms

  • maxThreads:最大线程数,默认200

  • minSpareThreads:最小空闲线程数

  • maxSpareThreads:最大空闲线程数

  • acceptCount:当启动线程满了之后,等待队列的最大长度,默认100

  • URIEncoding:URI 地址编码格式,建议使用 UTF-8

  • enableLookups:是否启用客户端主机名的DNS反向解析,缺省禁用,建议禁用,就使用客户端IP就行

  • compression:是否启用传输压缩机制,建议 “on”,CPU和流量的平衡

    • compressionMinSize:启用压缩传输的数据流最小值,单位是字节

    • compressableMimeType:定义启用压缩功能的MIME类型text/html,text/xml,text/css,text/javascript

 

 

 

10、java程序出现oom如何解决?什么场景下会出现oom?

oomOut of memory(OOM)是一种操作系统或者程序已经无法再申请到内存的状态。经常是因为所有可用的内存,包括磁盘交换空间都已经被分配了。当JVM因为没有足够的内存来为对象分配空间、并且垃圾回收器也已经没有空间可回收时,就会抛出 java.lang.Out Of Memory Error 错误

1 Java heap space当堆内存(Heap Space)没有足够空间存放新创建的对象时,就会抛出 java.lang.Out Of Memory Error:Java heap space 错误(根据实际生产经验,可以对程序日志中的 Out Of Memory Error 配置关键字告警,一经发现,立即处理)。

原因分析

Javaheap space 错误产生的常见原因可以分为以下几类:(1)请求创建一个超大对象,通常是一个大数组。(2)超出预期的访问量/数据量,通常是上游系统请求流量飙升,常见于各类促销/秒杀活动,可以结合业务流量指标排查是否有尖状峰值。(3)过度使用终结器(Finalizer),该对象没有立即被 GC。(4)内存泄漏(Memory Leak),大量对象引用没有释放,JVM 无法对其自动回收,常见于使用了 File 等资源没有回收。

解决方案

针对大部分情况,通常只需要通过 -Xmx 参数调高 JVM 堆内存空间即可。如果仍然没有解决,可以参考以下情况做进一步处理:(1)如果是超大对象,可以检查其合理性,比如是否一次性查询了数据库全部结果,而没有做结果数限制。(2)如果是业务峰值压力,可以考虑添加机器资源,或者做限流降级。(3)如果是内存泄漏,需要找到持有的对象,修改代码设计,比如关闭没有释放的连接。

2 GC overhead limit exceeded当 Java 进程花费 98% 以上的时间执行 GC,但只恢复了不到 2% 的内存,且该动作连续重复了 5 次,就会抛出 java.lang.Out Of Memory Error:GC overhead limit exceeded 错误。简单地说,就是应用程序已经基本耗尽了所有可用内存, GC 也无法回收。

此类问题的原因与解决方案跟 Java heap space 非常类似,可以参考上文。

3 Permgen space该错误表示永久代(Permanent Generation)已用满,通常是因为加载的 class 数目太多或体积太大。

原因分析

永久代存储对象主要包括以下几类:(1)加载/缓存到内存中的 class 定义,包括类的名称,字段,方法和字节码;(2)常量池;(3)对象数组/类型数组所关联的 class;(4)JIT 编译器优化后的 class 信息。

PermGen 的使用量与加载到内存的 class 的数量/大小正相关。

解决方案

根据 Permgen space 报错的时机,可以采用不同的解决方案,如下所示:(1)程序启动报错,修改 -XX:MaxPermSize 启动参数,调大永久代空间。(2)应用重新部署时报错,很可能是没有应用没有重启,导致加载了多份 class 信息,只需重启 JVM 即可解决。(3)运行时报错,应用程序可能会动态创建大量 class,而这些 class 的生命周期很短暂,但是 JVM 默认不会卸载 class,可以设置 -XX:+CMSClassUnloadingEnabled 和 -XX:+UseConcMarkSweepGC这两个参数允许 JVM 卸载 class。

如果上述方法无法解决,可以通过 jmap 命令 dump 内存对象 jmap-dump:format=b,file=dump.hprof ,然后利用 Eclipse MAT (https://www.eclipse.org/mat) 功能逐一分析开销最大的 classloader 和重复 class。

4 MetaspaceJDK 1.8 使用 Metaspace 替换了永久代(Permanent Generation),该错误表示 Metaspace 已被用满,通常是因为加载的 class 数目太多或体积太大。

此类问题的原因与解决方法跟 Permgen space 非常类似,可以参考上文。需要特别注意的是调整 Metaspace 空间大小的启动参数为 -XX:MaxMetaspaceSize。

5 Unable to create new native thread每个 Java 线程都需要占用一定的内存空间,当 JVM 向底层操作系统请求创建一个新的 native 线程时,如果没有足够的资源分配就会报此类错误。

原因分析

JVM 向 OS 请求创建 native 线程失败,就会抛出 Unable to create new native thread,常见的原因包括以下几类:(1)线程数超过操作系统最大线程数 ulimit 限制;(2)线程数超过 kernel.pid_max(只能重启);(3)native 内存不足;

该问题发生的常见过程主要包括以下几步:1、JVM 内部的应用程序请求创建一个新的 Java 线程;2、JVM native 方法代理了该次请求,并向操作系统请求创建一个 native 线程;3、操作系统尝试创建一个新的 native 线程,并为其分配内存;4、如果操作系统的虚拟内存已耗尽,或是受到 32 位进程的地址空间限制,操作系统就会拒绝本次 native 内存分配;5、JVM 将抛出 java.lang.Out Of Memory Error:Unable to create new native thread 错误。

解决方案

(1)升级配置,为机器提供更多的内存;(2)降低 Java Heap Space 大小;(3)修复应用程序的线程泄漏问题;(4)限制线程池大小;(5)使用 -Xss 参数减少线程栈的大小;(6)调高 OS 层面的线程最大数:执行 ulimia-a 查看最大线程数限制,使用 ulimit-u xxx 调整最大线程数限制。ulimit -a … 省略部分内容 … max user processes (-u) 16384

6 Out of swap space该错误表示所有可用的虚拟内存已被耗尽。虚拟内存(Virtual Memory)由物理内存(Physical Memory)和交换空间(Swap Space)两部分组成。当运行时程序请求的虚拟内存溢出时就会报 Outof swap space? 错误。

原因分析

该错误出现的常见原因包括以下几类:(1)地址空间不足;(2)物理内存已耗光;(3)应用程序的本地内存泄漏(native leak),例如不断申请本地内存,却不释放。(4)执行 jmap-histo:live 命令,强制执行 Full GC;如果几次执行后内存明显下降,则基本确认为 Direct Byte Buffer 问题。

解决方案

根据错误原因可以采取如下解决方案:(1)升级地址空间为 64 bit;(2)使用 Arthas 检查是否为 Inflater/Deflater 解压缩问题,如果是,则显式调用 end 方法。(3)Direct Byte Buffer 问题可以通过启动参数 -XX:MaxDirectMemorySize 调低阈值。(4)升级服务器配置/隔离部署,避免争用。

7 Kill process or sacrifice child有一种内核作业(Kernel Job)名为 Out of Memory Killer,它会在可用内存极低的情况下“杀死”(kill)某些进程。OOM Killer 会对所有进程进行打分,然后将评分较低的进程“杀死”,具体的评分规则可以参考 Surviving the Linux OOM Killer。

不同于其他的 OOM 错误, Kill process or sacrifice child 错误不是由 JVM 层面触发的,而是由操作系统层面触发的。

原因分析

默认情况下,Linux 内核允许进程申请的内存总量大于系统可用内存,通过这种“错峰复用”的方式可以更有效的利用系统资源。

然而,这种方式也会无可避免地带来一定的“超卖”风险。例如某些进程持续占用系统内存,然后导致其他进程没有可用内存。此时,系统将自动激活 OOM Killer,寻找评分低的进程,并将其“杀死”,释放内存资源。

解决方案

(1)升级服务器配置/隔离部署,避免争用。(2)OOM Killer 调优。

8 Requested array size exceeds VM limitJVM 限制了数组的最大长度,该错误表示程序请求创建的数组超过最大长度限制。

JVM 在为数组分配内存前,会检查要分配的数据结构在系统中是否可寻址,通常为 Integer.MAX_VALUE-2。

此类问题比较罕见,通常需要检查代码,确认业务是否需要创建如此大的数组,是否可以拆分为多个块,分批执行。

9 Direct buffer memoryJava 允许应用程序通过 Direct Byte Buffer 直接访问堆外内存,许多高性能程序通过 Direct Byte Buffer 结合内存映射文件(Memory Mapped File)实现高速 IO。

原因分析

Direct Byte Buffer 的默认大小为 64 MB,一旦使用超出限制,就会抛出 Direct buffer memory 错误。

解决方案

(1)Java 只能通过 Byte Buffer.allocateDirect 方法使用 Direct Byte Buffer,因此,可以通过 Arthas 等在线诊断工具拦截该方法进行排查。(2)检查是否直接或间接使用了 NIO,如 netty,jetty 等。(3)通过启动参数 -XX:MaxDirectMemorySize 调整 Direct Byte Buffer 的上限值。(4)检查 JVM 参数是否有 -XX:+DisableExplicitGC 选项,如果有就去掉,因为该参数会使 System.gc() 失效。(5)检查堆外内存使用代码,确认是否存在内存泄漏;或者通过反射调用 sun.misc.Cleaner 的 clean() 方法来主动释放被 Direct ByteBuffer 持有的内存空间。(6)内存容量确实不足,升级配置。

版权声明:本文为go2022原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/go2022/p/16408096.html