第十九周

1.haproxy https 实现

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

环境需求

需要三台服务器:
haproxy 负载均衡服务器一台:centos7-templates 192.168.80.100 配置https

后端nginx做web服务器2台:
web01 192.168.80.101
web02 192.168.80.102

后端nginx web服务器

后端两台nginx web服务器执行nginx 安装配置脚本,主配置文件默认即可
文件:
install_nginx_v02.sh
nginx-1.18.0.tar.gz
[root@web02 ~]# cat  install_nginx_v02.sh 

 #!/bin/bash

#!/bin/bash
#=====================================================================================================
#File Name:           /tmp/install_nginx_v00.sh
#Date:                2021-12-21 23-47-26
#Author:              Create by gonghairong
#Description:         This script function is
#Shell Version:       GNU bash version 4.1.2(2)-release x86_64-redhat-linux-gnu
#Copyright (C):       2021 All rights reserved
#=====================================================================================================



#http://nginx.org/download/nginx-1.20.1.tar.gz

src_dir=`pwd`
nginx_file=nginx-1.18.0.tar.gz
nginx_name=$(echo ${nginx_file%*.tar.gz})
#url_nginx=http://nginx.org/download/nginx-1.18.0.tar.gz


install_base=/app
install_path=${install_base}/${nginx_name}



cpus=`lscpu|awk -F: '/^CPU\(s\):/ {print $2}'`

#创建用户
groupadd nginx -g 666 
id nginx &>/dev/null || useradd -r -s /sbin/nologin -g 666 -u 666 nginx


#依赖包安装
install_package() {

osv=`uname -r |grep -o "el[78]"`

case  $osv in

el7)

for i in wget curl  make gcc-c++ libtool pcre pcre-devel zlib zlib-devel openssl openssl-devel perl-ExtUtils-Embed
do
        rpm -q $i &>/dev/null || yum install -y $i
done

cat>>/etc/sysctl.conf<<eof
fs.file-max=65535
net.ipv4.ip_forward = 1
eof
sysctl -p
;;

el8)
for i in wget curl  make gcc pcre-devel openssl-devel zlib-devel perl-ExtUtils-Embed
do
        rpm -q $i &>/dev/null || yum install -y $i
done
cat>>/etc/sysctl.conf<<eof
fs.file-max=65535
net.ipv4.ip_forward = 1
eof
sysctl -p

;;

*)

echo "is not Linux release  el8 or el7  is  ubuntu "

apt update && apt install -y wget curl  make gcc libpcre3 libpcre3-dev openssl libssl-dev zlib1g-dev

esac

}

install_nginx() {

#wget $url_nginx
#源码编译
tar xvf $nginx_file
cd $nginx_name

./configure --prefix=$install_path --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

if [ $? -eq 0 ];then

make -j ${cpus} && make install
echo "PATH=$install_base/nginx/sbin:${PATH}">/etc/profile.d/nginx.sh
chmod u+x  /etc/profile.d/nginx.sh
export PATH=$install_base/nginx/sbin:${PATH}
source   /etc/profile.d/nginx.sh

cd $install_base&&pwd
ln -s  $nginx_name/ nginx

#cp  $install_base/nginx/conf/nginx.conf{,.bak} 
#cp  $src_dir/nginx.conf $install_base/nginx/conf/
cp  $install_base/nginx/html/index.html{,.bak}
echo  "$(hostname)" `hostname -I` >$install_base/nginx/html/index.html


chown -R nginx:nginx  $install_path
chown -R nginx:nginx  ${install_base}/nginx

else

 echo  "configure is error "
 exit 1

fi

cat >/lib/systemd/system/nginx.service<<EOF

[Unit]
Description=The nginx HTTP and reverse proxy server
After=network.target remote-fs.target nss-lookup.target

[Service]
Type=forking
PIDFile=$install_base/nginx/logs/nginx.pid
ExecStartPre=/bin/rm -f $install_base/nginx/logs/nginx.pid
ExecStartPre=$install_base/nginx/sbin/nginx -t
ExecStart=$install_base/nginx/sbin/nginx
ExecReload=/bin/kill -s HUP \$MAINPID
KillSignal=SIGQUIT
TimeoutStopSec=5
KillMode=process
PrivateTmp=true
LimitNOFILE=1000000
[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl restart  nginx&& systemctl enable  nginx


}

main() {

install_package
install_nginx

}

main



#两台后端服务器都执行 install_nginx_v02.sh
# sh install_nginx_v02.sh

#测试后端服务器
[root@centos7-templates ~]# curl http://192.168.80.101
web01 192.168.80.101
[root@centos7-templates ~]# curl http://192.168.80.102
web02 192.168.80.102


haproxy 负载均衡服务器

#haproxy 负载均衡配置https
文件:
[root@centos7-templates ~]# ls -1
haproxy-2.4.8.tar.gz
install_haproxy-2.4.8_v03.sh
lua-5.4.3.tar.gz

#install_haproxy-2.4.8_v03.sh
[root@centos7-templates ~]# cat install_haproxy-2.4.8_v03.sh 
#!/bin/bash
#=====================================================================================================
#File Name:           install_haproxy-2.4.8_v00.sh
#Date:                2021-12-22 18-52-46
#Author:              Create by gong hai rong
#Description:         This script function is
#Shell Version:       GNU bash version 4.1.2(2)-release x86_64-redhat-linux-gnu
#Copyright (C):       2021 All rights reserved
#=====================================================================================================

#lua haproxy
# wget https://www.haproxy.org/download/2.4/src/haproxy-2.4.8.tar.gz
# wget http://www.lua.org/ftp/lua-5.4.3.tar.gz



src_dir=`pwd`

lua_file=lua-5.4.3.tar.gz
lua_name=$(echo ${lua_file%*.tar.gz})

haproxy_file=haproxy-2.4.8.tar.gz
haproxy_name=$(echo ${haproxy_file%*.tar.gz})



install_base=/app
install_home=$install_base/haproxy


install_lua() {

tar xvf $lua_file
cd $lua_name
make all test

[ -d /usr/local/lua ] &&>/dev/null || mkdir -p /usr/local/lua
mv src/ /usr/local/lua/
ls /usr/local/lua/
/usr/local/lua/src/lua -v

}


install_package()  {

p="
wget
make
autoconf
automake
libtool
cmake
gcc
gcc-c++
openssl
openssl-devel
pcre
pcre-devel
zlib
zlib-devel
cmake
gcc
openssl-devel
pcre-devel
systemd-devel
"

for i in $p;
do
    rpm -q $p  &> /dev/null || yum -q -y install $i
done


}

install_haproxy() {

cd && pwd 

#配置文件路径 子配置文件目录
[ -d /etc/haproxy/conf.d/ssl/ ] &>/dev/null  || mkdir -p /etc/haproxy/conf.d/ssl/ &>/dev/null

[ -d /var/lib/haproxy ] &>/dev/null  || mkdir -p /var/lib/haproxy &>/dev/null

#设置用户和目录权限
id  haproxy &>/dev/null ||  useradd -r -s /sbin/nologin  -d /var/lib/haproxy/ haproxy &>/dev/null


echo  'OPTIONS=""' >/etc/sysconfig/haproxy


#ca证书 

cd /etc/haproxy/conf.d/ssl/

openssl req -x509 -newkey rsa:2048 -subj "/CN=www.magedu.org" -keyout haproxy.key -nodes -days 365000 -out haproxy.crt


cat haproxy.key haproxy.crt > haproxy.pem


openssl x509 -in haproxy.pem -noout -text 


#/etc/haproxy/haproxy.cfg

cat>/etc/haproxy/haproxy.cfg<<eof

global
chroot /app/haproxy
stats socket /var/lib/haproxy/haproxy.sock mode 600 level admin process 1
user haproxy
group haproxy
daemon
nbthread 4
cpu-map 1 0
maxconn 100000
maxconnrate 10000
spread-checks 2
pidfile /var/lib/haproxy/haproxy.pid
log 127.0.0.1 local2 info
#----------------------------------------------------------------------------------------------

defaults
#option httpchk HEAD /monitor/monitor.html  HTTP/1.1\r\nHost:\ www.magedu.com
#http-check expect status 200
option http-keep-alive
option forwardfor
maxconn 100000
mode http
timeout connect 300000ms
timeout client 300000ms
timeout server 300000ms
#errorfile 503 /etc/haproxy/errorfiles/503sorry.http

#状态页面
listen stats
#bind 0.0.0.0:9999
bind 192.168.80.100:9999
stats enable
mode http
stats hide-version
stats uri /haproxy-status
stats realm HAProxy\ stats \page
stats auth haadmin:123456
stats auth haadmin2:123456
stats refresh 10s
stats admin if TRUE
#----------------------------------------------------------------------------------------------

eof


#https 
cat>/etc/haproxy/conf.d/v1.cfg<<eof

listen web_http_https_nodes
 bind 192.168.80.100:80
 bind 192.168.80.100:443 ssl crt /etc/haproxy/conf.d/ssl/haproxy.pem
 
 #http 重定向到 https
 redirect scheme https if !{ ssl_fc }
 #mode tcp
 mode http
 #balance roundrobin
 balance static-rr
 server 192.168.80.101 192.168.80.101:80 weight 1 check inter 2000 fall 2 rise 3
 server 192.168.80.102 192.168.80.102:80 weight 1 check inter 2000 fall 2 rise 3


eof




#unit file 

cat>/usr/lib/systemd/system/haproxy.service<<EOF

[Unit]
Description=HAProxy Load Balancer
After=syslog.target network.target

[Service]
ExecStartPre=$install_home/sbin/haproxy -f /etc/haproxy/haproxy.cfg -f /etc/haproxy/conf.d/ -c -q
ExecStart=$install_home/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=10000

[Install]
WantedBy=multi-user.target

EOF


cd $src_dir
tar xvf $haproxy_file
cd $haproxy_name&&pwd&&ls 

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/lua/src/ LUA_LIB=/usr/local/lua/src/ && make install PREFIX=${install_base}/${haproxy_name}

cd $install_base

ln -s ${haproxy_name}   haproxy

#ln  $install_base/haproxy/sbin/haproxy  /usr/sbin/

echo "PATH=$install_base/haproxy/sbin:"'$PATH'>/etc/profile.d/haproxy.sh
source /etc/profile.d/haproxy.sh
which haproxy 
haproxy -v 


systemctl daemon-reload

systemctl enable --now haproxy

systemctl status haproxy

ss -lnt

}


main() {

install_lua
install_package
install_haproxy


}

main
#install_haproxy-2.4.8_v03.sh
[root@centos7-templates ~]# sh install_haproxy-2.4.8_v03.sh 
....

#监听 80  443 
[root@centos7-templates ~]# ss -lnt 
State       Recv-Q Send-Q                               Local Address:Port                                              Peer Address:Port              
LISTEN      0      1024                                192.168.80.100:443                                                          *:*                  
LISTEN      0      1024                                192.168.80.100:9999                                                         *:*                  
LISTEN      0      128                                              *:111                                                          *:*                  
LISTEN      0      1024                                192.168.80.100:80                                                           *:*                  
LISTEN      0      128                                              *:22                                                           *:*                  
LISTEN      0      128                                           [::]:111                                                       [::]:*                  
LISTEN      0      128                                           [::]:22                                                        [::]:*                  
[root@centos7-templates ~]# 

测试https

#http重定向至https
[root@client ~]# curl -IkL http://192.168.80.100
HTTP/1.1 302 Found
content-length: 0
location: https://192.168.80.100/
cache-control: no-cache

HTTP/1.1 200 OK
server: nginx/1.18.0
date: Thu, 23 Dec 2021 10:47:21 GMT
content-type: text/html
content-length: 21
last-modified: Thu, 23 Dec 2021 09:36:26 GMT
etag: "61c4431a-15"
accept-ranges: bytes

#https
[root@client ~]# curl -IkL https://192.168.80.100
HTTP/1.1 200 OK
server: nginx/1.18.0
date: Thu, 23 Dec 2021 10:47:52 GMT
content-type: text/html
content-length: 21
last-modified: Thu, 23 Dec 2021 09:36:30 GMT
etag: "61c4431e-15"
accept-ranges: bytes

2.总结tomcat的核心组件以及根目录结构

目录结构
官方文档:https://tomcat.apache.org/tomcat-9.0-doc/index.html
[root@web01 tomcat]# cd /app/tomcat 
[root@web01 tomcat]# ls -d */|xargs -0|tr  -d  '\/'
bin
conf
lib
logs
temp
webapps
work 
bin 存放Tomcat的一些脚本文件,包含启动和关闭tomcat服务脚本
conf 配置文件,Tomcat服务器的各种全局配置文件,其中最重要的是server.xml和web.xml
lib 库目录,存放web应用能访问的JAR包 tomcat启动所依赖的jar包,包括 bootstrap.jar、tomcat-juli.jar
logs 日志目录
temp 临时目录
webapps 默认网站发布目录,默认情况下把Web应用文件放于此目录,当服务器启动时,会加载所有这个目录下的应用。
work jsp编译后的结果文件、建议提前预热访问
目录详解:
bin:该目录下存放的是二进制可执行文件,如果是解压版,那么会有startup.bat和shutdown.bat startup.sh shutdown.sh文件startup.bat用来启动Tomcat,但需要先配置JAVA_HOME环境变量才能启动,shutdawn.bat  shutdown.sh用来停止Tomcat , version.sh version.bat 文件查询tomcat 软件版本 catalina.sh 这个文件也是tomcat 的启动脚本。  检测配置文件是否正常 ./catalina.sh configtest
conf:这是一个非常非常重要的目录,这个目录下有四个最为重要的文件:
server.xml:配置整个服务器信息。例如修改端口号,添加虚拟主机等
tomcat-users.xml:存储tomcat用户的文件,这里保存的是tomcat的用户名及密码,以及用户的角色信息。可以按着该文件中的注释信息添加tomcat用户,然后就可以在Tomcat主页中进入Tomcat Manager页面了(生产环境一般不使用管理页面)
web.xml:部署描述符文件,这个文件中注册了很多MIME类型,即文档类型
context.xml:对所有应用的统一配置,通常我们不会去配置它
lib:Tomcat的类库,里面是一大堆jar文件。如果需要添加Tomcat依赖的jar文件,可以把它放到这个目录中,当然也可以把应用依赖的jar文件放到这个目录中,这个目录中的jar所有项目都可以共享之,但这样你的应用放到其他Tomcat下时就不能再共享这个目录下的Jar包了,所以建议只把Tomcat需要的Jar包放到这个目录下 tomcat启动所依赖的jar包,包括 bootstrap.jar、tomcat-juli.jar
logs:这个目录中都是日志文件,记录了Tomcat启动和关闭的信息,如果启动Tomcat时有错误,那么异常也会记录在日志文件中
temp:存放Tomcat的临时文件,这个目录下的东西可以在停止Tomcat后删除
webapps:存放web项目的目录,其中每个文件夹都是一个项目;如果这个目录下已经存在了目录,那么都是tomcat自带的项目,其中ROOT是一个特殊的项目,在地址栏中没有给出项目目录时,对应的就是ROOT项目http://localhost:8080/examples,进入示例项目。其中examples就是项目名,即文件夹的名字
work:运行时生成的文件,最终运行的文件都在这里。通过webapps中的项目生成的!可以把这个目录下的内容删除,再次运行时会生再次生成work目录。当客户端用户访问一个JSP文件时,Tomcat会通过JSP生成Java文件,然后再编译Java文件生成class文件,生成的java和class文件都会存放到这个目录下
LICENSE:许可证
NOTICE:说明文件
组件分层和分类
顶级组件
server 代表整个tomcat容器、一台主机可用启动多个tomcat实例,需要确保端口不要产生冲突

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

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

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

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

集群类组件
listener、cluster 
名称 说明
server 服务器、tomcat运行的进程实例,一个 server 中可以有多个 service ,但通常一个
service 服务器、用来组织Engine和Connector的对应关系、一个service中只有一个Engine
connector 连接器,负责客户端的HTTP、HTTPS、AJP等协议连接。一个connector只属于某一个Engine
engine 即引擎,用来响应并处理用户请求,一个Engine上可以绑定多个connector
host 即虚拟主机,可以实现多虚拟主机,例如使用不同的主机头区分
context 应用的上下文、配置特定url路径映射和目录的映射关系:url --> directory
每一个组件都由一个java类实现,这些组件大体可以分以下几个类型
顶级组件: server 
服务类组件: service 
连接器组件: http  https  ajp (apache jserv protocol)
容器类: engine  host  context
备嵌套类:value , logger, ream,  loader , manager 
集群类组件: listenner , cluster 

核心组件

tomcat 启动一个 server 进程,可以启动多个 server 即tomcat 的多实例,但是一般只启动一个
创建一个service提供服务,可以创建多个service,但一般也只创建一个
 每个service中,是engine和其他连接器connector的关联配置
可以为这个service 提供多个连接器connector,这些connector使用了不同的协议,绑定了不同的端口。其作用就是处理来自客户端的不同的连接请求或响应
service 内部还定义了engine,引擎才是真正的处理请求的入口,其内部定义多个虚拟主机 host 
 engine对请求头做分析,将请求发送给相应的虚拟主机
 如果没有匹配,数据就发往engine上的defaultHost缺省的虚拟主机
 engine上的缺省虚拟主机可以修改

Host 定义虚拟主机、虚拟主机有name名称、通过名称匹配
context 定义应用程序单独的路径映射和配置
#server.xml配置文件整体结构
#server.xml 文件过滤注释 <!--    -->

[root@web01 conf]# cat -s server.xml|sed '/<!--.*-->/d'|sed '/^\$/d'|sed '/<!--/,/-->/d'|sed '/^\\s*\$/d' 
<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
  <Listener className="org.apache.catalina.startup.VersionLoggerListener" />
  <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
  <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />

  <GlobalNamingResources>
    <Resource name="UserDatabase" auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml" />
  </GlobalNamingResources>

   <!-- 一个tomcat 进程 一个实例即一个 service  -->
  <Service name="Catalina">

    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
      
    <!-- service内部的引擎 内部默认虚拟主机localhost 可以有多个虚拟主机  -->
    <Engine name="Catalina" defaultHost="localhost">

      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
               resourceName="UserDatabase"/>
      </Realm>
       <!-- 默认的虚拟主机 localhost  可定义多个虚拟主机  -->
      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">

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

      </Host>
    </Engine>
  </Service>
</Server>

3.tomcat 实现多虚拟主机

默认虚拟主机
#默认tomcat就有一个虚拟主机,defaultHost配置,有了虚拟主机干嘛还要自己再增加几个虚拟主机呢?尤其涉及nginx 与后端web服务器上的多虚拟主机做反向代理,负载均衡的时候比较麻烦。

<Host name="localhost"  appBase="webapps" unpackWARs="true" autoDeploy="true">
    <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log" suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />

</Host>
多虚拟主机配置说明
name 必须是主机名,用主机名匹配
appBase当前主机的网页根目录,是相对于$CATALINA_HOME,也可以使用绝对路径
unpackWARs="true"  是否自动解压war格式文件
autoDeploy="true" 热部署,自动加载并运行应用
新增加虚拟主机配置
#参考默认虚拟主机配置,新增虚拟主机配置

[root@web01 ~]# vim /app/tomcat/conf/server.xml

<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
  <Listener className="org.apache.catalina.startup.VersionLoggerListener" />
  <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
  <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />

  <GlobalNamingResources>
    <Resource name="UserDatabase" auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml" />
  </GlobalNamingResources>

  <Service name="Catalina">

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

    <Engine name="Catalina" defaultHost="localhost">

      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
               resourceName="UserDatabase"/>
      </Realm>

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


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

      </Host>

      <!--  新增加的第一个虚拟主机   -->
      <Host name="www.tomcat01.org"  appBase="/data/webapps01"
            unpackWARs="true" autoDeploy="true">

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

      </Host>


      <!--  新增加的第二个虚拟主机   -->
      <Host name="www.tomcat02.org"  appBase="/data/webapps02"
            unpackWARs="true" autoDeploy="true">

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

      </Host>

       <!--  新增加的第三个虚拟主机   -->
      <Host name="www.tomcat03.org"  appBase="/data/webapps03"
            unpackWARs="true" autoDeploy="true">

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

      </Host>
    </Engine>
  </Service>
</Server>
每个虚拟主机数据目录

[root@web01 ~]#mkdir  -p  /data/webapps0{1..3}/ROOT/

#www.tomcat01.org
[root@web01 ~]# cat /data/webapps01/ROOT/index.jsp

 <%@ page language="java" %>
<html>
        <head><title>TomcatAwebapps01</title></head>
        <body>
                <font color="red">TomcatAwebapps01.magedu.com</font>
                <table align="centre" border="1">
                        <tr>
                                <td>Session ID</td>
                        <% session.setAttribute("magedu.com","magedu.com"); %>
                                <td><%= session.getId() %></td>
                        </tr>
                        <tr>
                                <td>Created on</td>
                                <td><%= session.getCreationTime() %></td>
                        </tr>
                </table>
        </body>
</html>


sessionId: <%=request.getSession().getId()%>


#www.tomcat02.org


[root@web01 ~]# cat /data/webapps02/ROOT/index.jsp

 <%@ page language="java" %>
<html>
        <head><title>TomcatAwebapps02</title></head>
        <body>
                <font color="red">TomcatAwebapps02.magedu.com</font>
                <table align="centre" border="1">
                        <tr>
                                <td>Session ID</td>
                        <% session.setAttribute("magedu.com","magedu.com"); %>
                                <td><%= session.getId() %></td>
                        </tr>
                        <tr>
                                <td>Created on</td>
                                <td><%= session.getCreationTime() %></td>
                        </tr>
                </table>
        </body>
</html>


sessionId: <%=request.getSession().getId()%>


#www.tomcat03.org
#第三个虚拟主机部署一个打包好的java 应用程序(tomcat 官方示例程序 不不会java 开发)
[root@web01 webapps03]# ls -1 /data/webapps03/
sample00
sample00.war

#数据目录权限tomcat
[root@web01 ~]# chown -R tomcat:tomcat  /data/

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

#监听
[root@web01 ~]# ss -lnt | grep 80
LISTEN     0      1       [::ffff:127.0.0.1]:8005                  [::]:*
LISTEN     0      100       [::]:8080                  [::]:*
[root@web01 ~]#

测试虚拟主机
#windows 主机 hosts 文件配置域名解析
192.168.80.101   www.tomcat03.org
192.168.80.101   www.tomcat02.org
192.168.80.101  www.tomcat01.org

Snap1.pngSnap2.pngSnap3.png

4.nginx 实现后端tomcat的负载均衡调度

http相关问题

tomcat动态服务器的问题,往往就是并发能力太弱,往往需要多台动态服务器一起提供服务,这就需调度负载均衡,采用一定的调度策略,将请求分发给不同的服务器,这就是load balance 负载均衡
当单机tomcat 演化多机部署的时候,一个问题凸显处出来,这就是 session。而这个问题的由来,都是 http协议再设计之初没有想到未来的发展。
http的无状态,有连接和短连接

无状态:指的是服务器端无法直到2次请求之间的联系,即使是前后2次请求来自同一个浏览器,也没有任何数据能判断出是同一个浏览器的请求,后来可以通过 cookie 、 session 机制来判断。 

浏览器端第一次HTTP请求服务器端时,在服务器端使用 session 这样种技术,就可以在服务器端产生一个随机值即 session 发给浏览器,浏览器端收到后会保持这个 sessionID 在cookie 当中,这个 cookie 的值一般不能持久存储,浏览器关闭就没有了。浏览器在每一次提交HTTP请求的时候会把这个sessionID 传给服务器端,服务器端就可以通过比对知道来的是谁。

session 通常会保存在服务器端内存中,如果没有持久化,则易丢失
session 会定期过期,过期后浏览器如果再访问,服务器端发现没有此ID,将给浏览器重新发送新的sessionID 
更换浏览器也将重新获得新的sessionID 

有连接:是因为它基于TCP协议,是面向连接的,需要3次握手,4词断开

短连接:http1.1之前,都是一个请求一个连接,而tcp的连接创建销毁成本高,对服务器有很大的影响。所以,自http1.1开始,支持keep-alive,默认也开启,一个连接打开后,会保持一段时间(可以设置),浏览器再访问该服务器就使用这个TCP连接,减轻了服务器压力,提高效率。
服务器端如果故障,即使session 被持久化,但是服务器没有恢复前都不能使用这些 sessionID 
如果使用haproxy 或者nginx 等多负载均衡,调度到了不同的tomcat 上,那么也会出现找不到sessionID 的情况

cookie与session是什么?

cookie 是访问某网站以后再本地存储的一些网站相关的信息,下次再访问的时候减少一些步骤,比如加密后的用户名密码信息
cookie 是服务器再客户端浏览器上存储的小段文本并随每一个请求发送至同一个服务器,是一种实现客户端保持状态的方案

session 称为会话信息,位于web服务器上,主要负责访问者与网站之间的交互,当浏览器请求 http 地址时,可以基于之前的 session  实现会话保持,session 共享

session 与cookie 的区别?
1.cookie 以文本文件格式存储再浏览器中,而 session  存储再服务器
2.cookie 的存储限制了数量 只允许4kB  而session是无限制的
3.cookie 包含再每一个客户端请求报文中,一次容易被人捕获
4.cookie  和 session  都可以设置过期时间


会话保持的方式:
1.session sticky 会话粘性
session 绑定
nginx:  source ip  , cookie
haproxy: source ip , cookie 
优点:简单容易配置
缺点:如果目标服务器故障之后,如果没有做session 持久化,就会丢失session  此方式生产很少使用

2.session复制集群
tomcat 自己提供的多播集群,通过多播将任何一台的session  同步到其他节点
缺点:
tomcat实现会话绑定和session复制集群 的同步节点不易过多,互相即使通信同步session  需要太多带宽
每一台都拥有全部session 内存损耗太大

3.session  server 
session 共享服务器,使用memcached  、 redis 做共享的 session 服务器,此为推荐的方式


session  问题方案总结:

通过多组实验,使用不同技术实现 session  持久机制

1.session 绑定,基于IP或session cookie的,其部署简单,尤其基于 session 粘性的方式,粒度小,对负载均衡影响小,但一旦后端服务器有故障,其上的 session  会丢失

2.session 复制集群 ,基于tomcat 实现多个服务器内共享同步所有session ,此方法可以保证任意一台后端服务器故障,其余各服务器上还都存有全部 session ,对业务无影响,
但是它基于多播实现心跳,tcp单播实现复制,但设备节点过多,这种复制机制不是很好的解决方案,且并发连接多的时候,单机上的所有session  占据的内存空间非常大,甚至耗尽内存


3.session  服务器,将所有的session 存储到一个共享的内存空间,使用多个冗余节点保存 session  这样做到 session存储服务器的高可用,且占据业务服务器内存较小,是一种比较好的
解决 session 持久的解决方案

不过以上这些方法都是在内存中实现了 session 的保持,可以使用数据库或者文件系统,把session数据存储起来,持久化,这样服务器重启后,也可以重新恢复session 数据,不过session 数据是有
时效性的,是否需要这样作,视情况而定


环境需求

需要三台服务器:

nginx负载均衡服务器一台:centos7-templates 192.168.80.100 

后端tomcat服务器2台:
java 应用程序:sample.war 官方的示例应用程序
apache-tomcat-9.0.56.tar.gz
jdk-8u291-linux-x64.tar.gz
web01 192.168.80.101 
web02 192.168.80.102 

后端tomcat 服务器

后端两台tomcat服务器
文件:
apache-tomcat-9.0.56.tar.gz
install_jdk_tomcat.sh
jdk-8u291-linux-x64.tar.gz

tomcat基础安装

#install_jdk_tomcat.sh 基础的安装
#apache-tomcat-9.0.56.tar.gz
#jdk-8u291-linux-x64.tar.gz

# cat install_jdk_tomcat.sh

[ -d /app ] &>/dev/null || mkdir -p /app/

jdk_file="jdk-8u291-linux-x64.tar.gz"
java_home=/app/jdk
tar xf $jdk_file -C /app

cd /app
jdk_name=$(ls -d jdk1.8*)
chown -R root:root $jdk_name
ln -sf $jdk_name  jdk

cat>/etc/profile.d/java.sh<<EOF
JAVA_HOME=$java_home
JRE_HOME=\$JAVA_HOME/jre
CLASSPATH=.:\$JAVA_HOME/lib/:\$JRE_HOME/lib/
PATH=\$PATH:\$JAVA_HOME/bin
export JAVA_HOME JRE_HOME  CLASSPATH  PATH
EOF


export JAVA_HOME=$java_home
export JRE_HOME=$JAVA_HOME/jre
export CLASSPATH=.:$JAVA_HOME/lib/:$JRE_HOME/lib/
export PATH=$PATH:$JAVA_HOME/bin
export export JAVA_HOME JRE_HOME  CLASSPATH  PATH
echo 
echo "-----------------------------------------------------------------------"
java -version 
echo 
echo "-----------------------------------------------------------------------"
echo 

echo "=============================================================================================================================================================="


echo 



cd 

tomcat_file="apache-tomcat-9.0.56.tar.gz"
java_home=/app/jdk
tar xf $tomcat_file -C /app

cd /app

ln -s /app/apache-tomcat-9.0.56  tomcat


echo 'PATH=/app/tomcat/bin:$PATH'>/etc/profile.d/tomcat.sh

#export PATH=/app/tomcat/bin:$PATH

id tomcat &>/dev/null || useradd -r -s /sbin/nologin tomcat 

cat >/app/tomcat/conf/tomcat.conf<<EOF
JAVA_HOME=$java_home
EOF

chown -R tomcat:tomcat /app/tomcat/


cat>/usr/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=/app/tomcat/conf/tomcat.conf
ExecStart=/app/tomcat/bin/startup.sh
ExecStop=/app/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 &&  echo  "TOMCAT 安装完成" || { echo "TOMCAT 安装失败" ; exit; }


#两台后端服务器都执行 tomcat 基本安装
[root@web01 ~]# sh install_jdk_tomcat.sh 

#验证服务 两台都验证下最好,这里就验证第一台了
[root@web01 ~]# curl -I  127.0.0.1:8080 
HTTP/1.1 200 
Content-Type: text/html;charset=UTF-8
Transfer-Encoding: chunked
Date: Thu, 23 Dec 2021 16:51:36 GMT

#配置文件默认即可,直接使用默认的虚拟主机,不要自己增加虚拟主机给自己找不必要的麻烦,尤其是nginx 反向代理环境下。
[root@web01 ~]# cp /app/tomcat/conf/server.xml{,.bbk01}
[root@web01 ~]# vim /app/tomcat/conf/server.xml

[root@web01 conf]# cat server.xml
<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
  <Listener className="org.apache.catalina.startup.VersionLoggerListener" />
  <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
  <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />

  <GlobalNamingResources>
    <Resource name="UserDatabase" auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml" />
  </GlobalNamingResources>

  <Service name="Catalina">


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


    <Engine name="Catalina" defaultHost="localhost">


      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
               resourceName="UserDatabase"/>
      </Realm>

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

         <!--  修改日志记录格式 记录客户端真实IP地址   -->

        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log" suffix=".txt"
pattern=" %{X-FORWARDED-FOR}i %h %l %u %t &quot;%r&quot; %s %b  %{User-Agent}i %T" />



      </Host>
    </Engine>
  </Service>
</Server>


两台后端tomcat都需要部署应用,直接部署到tomcat默认部署路径下$CATALINA_HOME/webapps/
sample.war 官方的示例war文件,下载地址一时间想不起来了好像在tomcat的默认管理页面里面找到的。

[root@web01 webapps]# tree  sample/
sample/
├── hello.jsp
├── images
│   └── tomcat.gif
├── index.html
├── META-INF
│   └── MANIFEST.MF
└── WEB-INF
    ├── classes
    │   └── mypackage
    │       └── Hello.class
    ├── lib
    └── web.xml

6 directories, 6 files

#index.html
[root@web01 sample]# cat index.html
<html>
<head>
<title>Sample "Hello, World" Application</title>
</head>
<body bgcolor=white>

<table border="0">
<tr>
<td>
<img src="images/tomcat.gif">
</td>
<td>
Sample "Hello, World" Application
<p> 192.168.80.101 web01
</td>
</tr>
</table>

<p>To prove that they work, you can execute either of the following links:
<ul>
<li>To a <a rel="nofollow" href="hello.jsp">JSP page</a>.
<li>To a <a rel="nofollow" href="hello">servlet</a>.
</ul>

</body>
</html>



# hello.jsp 稍微做了修改 增加了 sessionID 信息
[root@web01 sample]# cat hello.jsp
<%@ page import="java.util.*" %>
<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>tomcat test</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>
<%@ page language="java" %>
<html>
    <head><title>TomcatB</title></head>
    <body>
        <font color="red">www.tomcat.org</font>
        <table align="centre" border="1">
        <tr>
            <td>Session ID</td>
        <% session.setAttribute("ready.com","ready.com"); %>
            <td><%= session.getId() %></td>
        </tr>
            <tr>
                <td>Created on</td>
                <td><%= session.getCreationTime() %></td>
            </tr>
        </table>
    </body>
#web.xml

[root@web01 WEB-INF]# cat web.xml
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
    version="2.4">

    <display-name>Hello, World Application</display-name>
    <description>
        This is a simple web application with a source code organization
        based on the recommendations of the Application Developer's Guide.
    </description>

    <servlet>
        <servlet-name>HelloServlet</servlet-name>
        <servlet-class>mypackage.Hello</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>HelloServlet</servlet-name>
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>



</web-app>

Tomcat 性能优化常用配置

关闭自动部署

默认 Tomcat 是开启了对war包的热部署,为了防止被植入木马等恶意程序,因此我们要关闭自动部署

编辑$CATALINA_HOME/conf/server.xml文件

<!-- 默认配置为  -->
      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log" suffix=".txt"
pattern=" %{X-FORWARDED-FOR}i %h %l %u %t &quot;%r&quot; %s %b  %{User-Agent}i %T" />

      </Host>

<!--修改为: 将Host元素上的autoDeploy设置为“false”非常重要,从而可以避免两次部署Web应用程序 -->
      <Host name="localhost"  appBase="webapps"
            unpackWARs="false" autoDeploy="false">
            
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log" suffix=".txt"
pattern=" %{X-FORWARDED-FOR}i %h %l %u %t &quot;%r&quot; %s %b  %{User-Agent}i %T" />

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

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

方法1:
$CATALINA_HOME/bin 目录下创建文件 setenv.sh 添加 jvm 参数
[root@web01 bin]# vim /app/tomcat/bin/setenv.sh
JAVA_OPTS="$JAVA_OPTS -server -Xms1024m -Xmx1024m -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=512m"


方法2:
在$CATALINA_HOME/bin/catalina.sh 文件中增加一行配置
[root@web01 bin]# vim /app/tomcat/bin/catalina.sh

#在此行的上面或者下面增加一行配置
#OS specific support.  $var _must_ be set to either true or false.
JAVA_OPTS="-server -Xms4g -Xmx4g -XX:NewSize=256M -XX:MaxNewSize=512M

Executor(线程池)优化

Executor表示组件之间Tomcat中共享的线程池,每个连接器都会创建一个线程池,但是当配置为支持Executor,您可以在(主要)连接器之间以及其他组件之间共享线程池
Executor 连接器可以使用共享的Executor(线程池),可以定义一个或多个命名线程池
Executor是Service元素的嵌套元素。并且为了使其能够被连接器拾取,Executor元素必须出现在server.xml中的Connector元素之前
server.xml 配置文件默认配置,Executor 是被注释的,我们打开注释,添加线程池参数

官方文档:
https://tomcat.apache.org/tomcat-9.0-doc/config/executor.html

tomcat线程池调整
常用属性:
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
<!-- 默认配置: -->
<!--The connectors can use a shared executor, you can define one or more named thread pools-->
    <!--
    <Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
        maxThreads="150" minSpareThreads="4"/>
    -->

<!-- 去掉注释 修改配置: -->
<Executor name="tomcatThreadPool" 
namePrefix="catalina-exec-"
maxThreads="1500" 
minSpareThreads="50"
prestartminSpareThreads="true"
maxIdleTime="60000"
maxQueueSize = "100"  />


Connector(连接器)配置优化

官方文档:
https://tomcat.apache.org/tomcat-9.0-doc/config/http.html
在Connector中设置executor属性引用Executor(线程池)
 <!-- 默认配置

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

-->

 <!--  修改为下面  -->
<Connector executor="tomcatThreadPool"
port="8080" 
protocol="protocol="HTTP/1.1"
connectionTimeout="60000"
maxConnections="10000"
enableLookups="false"
acceptCount="100"
maxPostSize="10485760"
maxHttpHeaderSize="8192"
compression="on"
disableUploadTimeout="true"
compressionMinSize="2048"
acceptorThreadCount="2"
URIEncoding="utf-8"
processorCache="20000"
tcpNoDelay="true"
connectionLinger="-1"
server="Server Version 11.0" 
redirectPort="8443"/>


tomcat 配置集群session 复制

session 复制集群 ,基于tomcat 实现多个服务器内共享同步所有session ,此方法可以保证任意一台后端服务器故障,其余各服务器上还都存有全部 session ,对业务无影响,
但是它基于多播实现心跳,tcp单播实现复制,但设备节点过多,这种复制机制不是很好的解决方案,且并发连接多的时候,单机上的所有session  占据的内存空间非常大,甚至耗尽内存。
Tomcat 官方实现了 Session 的复制集群,将每个Tomcat的Session进行相互的复制同步,从而保证所有
Tomcat都有相同的Session信息
官方文档:https://tomcat.apache.org/tomcat-9.0-doc/cluster-howto.html
以下是默认集群配置:

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

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

          <Channel className="org.apache.catalina.tribes.group.GroupChannel">
            <Membership className="org.apache.catalina.tribes.membership.McastService"
                        address="228.0.0.4"
                        port="45564"
                        frequency="500"
                        dropTime="3000"/>
            <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
                      address="auto"
                      port="4000"
                      autoBind="100"
                      selectorTimeout="5000"
                      maxThreads="6"/>

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

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

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

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

修改应用的web.xml文件开启该应用程序的分布式

参考官方说明: https://tomcat.apache.org/tomcat-9.0-doc/cluster-howto.html#Cluster_Basics

Make sure your web.xml has the <distributable/> element
确保您的 web.xml 具有 <distributable/> 元素

为所有tomcat主机应用web.xml的 <web-app> 标签增加子标签 <distributable/> 来开启该应用程序的
分布式。
在所有后端tomcat主机上修改conf/server.xml,增加集群配置
注意:address="192.168.80.101"为当前tomcat主机的IP地址,两台要配置为各自的IP地址。
集群配置语句可以放在 <Host    </Host> 标签内针对某个虚拟主机,也可以放在 <Engine   </Engine> 标签内部,针对所有虚拟主机。
                                                   
<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
  <Listener className="org.apache.catalina.startup.VersionLoggerListener" />
  <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
  <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />

  <GlobalNamingResources>
    <Resource name="UserDatabase" auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml" />
  </GlobalNamingResources>

  <Service name="Catalina">



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



    <Engine name="Catalina" defaultHost="localhost">

     <!--  cluster session replication  add   -->

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

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

          <Channel className="org.apache.catalina.tribes.group.GroupChannel">
            <Membership className="org.apache.catalina.tribes.membership.McastService"
                        address="228.0.0.4"
                        port="45564"
                        frequency="500"
                        dropTime="3000"/>
            <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
                      address="192.168.80.102"
                      port="4000"
                      autoBind="100"
                      selectorTimeout="5000"
                      maxThreads="6"/>

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

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

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

          <ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
        </Cluster>
        
        <!--  cluster session replication  end   -->

      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
               resourceName="UserDatabase"/>
      </Realm>

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


        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log" suffix=".txt"
pattern=" %{X-FORWARDED-FOR}i %h %l %u %t &quot;%r&quot; %s %b  %{User-Agent}i %T" />

 
      </Host>

    </Engine>
  </Service>
</Server>
                                                   
修改应用的web.xml文件开启该应用程序的分布式,两台tomcat服务器都需要修改

[root@web01 ~]# vim /app/tomcat/webapps/sample/WEB-INF/web.xml

<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
    version="2.4">

    <display-name>Hello, World Application</display-name>
    <description>
        This is a simple web application with a source code organization
        based on the recommendations of the Application Developer's Guide.
    </description>

    <servlet>
        <servlet-name>HelloServlet</servlet-name>
        <servlet-class>mypackage.Hello</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>HelloServlet</servlet-name>
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>

<!--    在此增加一行配置  --> 

<distributable/>


</web-app>


给出一个完整的server.xml配置文件
[root@web01 conf]# cat server.xml


<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
  <Listener className="org.apache.catalina.startup.VersionLoggerListener" />
  <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
  <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />

  <GlobalNamingResources>
    <Resource name="UserDatabase" auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml" />
  </GlobalNamingResources>

  <Service name="Catalina">

<!--  Executor    -->     
<Executor name="tomcatThreadPool" 
namePrefix="catalina-exec-"
maxThreads="1500" 
minSpareThreads="50"
prestartminSpareThreads="true"
maxIdleTime="60000"
maxQueueSize = "100"  />

<!--  Connector use  Executor    -->       
      
<Connector executor="tomcatThreadPool"
port="8080" 
protocol="protocol="HTTP/1.1"
connectionTimeout="60000"
maxConnections="10000"
enableLookups="false"
acceptCount="100"
maxPostSize="10485760"
maxHttpHeaderSize="8192"
compression="on"
disableUploadTimeout="true"
compressionMinSize="2048"
acceptorThreadCount="2"
URIEncoding="utf-8"
processorCache="20000"
tcpNoDelay="true"
connectionLinger="-1"
server="Server Version 11.0" 
redirectPort="8443"/>

 <!-- 

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

-->



    <Engine name="Catalina" defaultHost="localhost">


      <!--  cluster session replication  start   -->

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

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

          <Channel className="org.apache.catalina.tribes.group.GroupChannel">
            <Membership className="org.apache.catalina.tribes.membership.McastService"
                        address="228.0.0.4"
                        port="45564"
                        frequency="500"
                        dropTime="3000"/>
            <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
                      address="192.168.80.101"
                      port="4000"
                      autoBind="100"
                      selectorTimeout="5000"
                      maxThreads="6"/>

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

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

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

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

      <!--  cluster session replication  end   -->
                                                                                            
                                                                                            
      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
               resourceName="UserDatabase"/>
      </Realm>


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

        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log" suffix=".txt"
               pattern=" %{X-FORWARDED-FOR}i %h %l %u %t &quot;%r&quot; %s %b  %{User-Agent}i %T" />
       
      </Host>
       <!--  add new  host1 ..n    -->  
                                                                                                 
    </Engine>
  </Service>
</Server>




Nginx负载均衡调度

准备好nginx主配置文件及子配置文件,在脚本中直接一步到位配置好负载均衡
文件:
[root@centos7-templates ~]# ls -1
install_nginx_v00.sh
nginx-1.18.0.tar.gz
nginx.conf
proxy.conf
tomcat.conf
#install_nginx_v00.sh
[root@centos7-templates ~]# cat install_nginx_v00.sh 

 
#!/bin/bash


#http://nginx.org/download/nginx-1.20.1.tar.gz

src_dir=`pwd`
nginx_file=nginx-1.18.0.tar.gz
nginx_name=$(echo ${nginx_file%*.tar.gz})
url_nginx=http://nginx.org/download/nginx-1.18.0.tar.gz


install_base=/app
install_path=${install_base}/${nginx_name}

cpus=`lscpu|awk -F: '/^CPU\(s\):/ {print $2}'`

#创建用户
#id nginx &>/dev/null || useradd -r -s /sbin/nologin nginx
#创建用户
groupadd nginx -g 666 &>/dev/null
id nginx &>/dev/null || useradd -r -s /sbin/nologin -g 666 -u 666 nginx &>/dev/null



#依赖包安装
install_package() {

osv=`uname -r |grep -o "el[78]"`

case  $osv in

el7)

for i in wget curl  make gcc-c++ libtool pcre pcre-devel zlib zlib-devel openssl openssl-devel perl-ExtUtils-Embed
do
        rpm -q $i &>/dev/null || yum install -y $i
done

cat>>/etc/sysctl.conf<<eof
fs.file-max=65535
net.ipv4.ip_forward = 1
eof
sysctl -p
;;

el8)
for i in wget curl  make gcc pcre-devel openssl-devel zlib-devel perl-ExtUtils-Embed
do
        rpm -q $i &>/dev/null || yum install -y $i
done
cat>>/etc/sysctl.conf<<eof
fs.file-max=65535
net.ipv4.ip_forward = 1
eof
sysctl -p

;;

*)

echo "is not Linux release  el8 or el7  is  ubuntu "

apt update && apt install -y wget curl  make gcc libpcre3 libpcre3-dev openssl libssl-dev zlib1g-dev

esac

}

install_nginx() {

#wget $url_nginx
#源码编译
tar xvf $nginx_file
cd $nginx_name

./configure --prefix=$install_path --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

if [ $? -eq 0 ];then

make -j ${cpus} && make install
echo "PATH=$install_base/nginx/sbin:${PATH}">/etc/profile.d/nginx.sh
chmod u+x  /etc/profile.d/nginx.sh
export PATH=$install_base/nginx/sbin:${PATH}
source   /etc/profile.d/nginx.sh

cd $install_base&&pwd
ln -s  $nginx_name/ nginx

#nginx.conf 主配置文件

cp  $install_base/nginx/conf/nginx.conf{,.bak} 
cp  $src_dir/nginx.conf $install_base/nginx/conf/



#nginx 子配置文件
[ -d $install_base/nginx/conf/extra/ ] &&>/dev/null || mkdir -p $install_base/nginx/conf/extra/

cp $src_dir/proxy.conf $install_base/nginx/conf/
cp $src_dir/tomcat.conf $install_base/nginx/conf/extra/



chown -R nginx:nginx  $install_path
chown -R nginx:nginx  ${install_base}/nginx


chown -R nginx:nginx  $install_path
chown -R nginx:nginx  ${install_base}/nginx

else

 echo  "configure is error "
 exit 1

fi

cat >/lib/systemd/system/nginx.service<<EOF

[Unit]
Description=The nginx HTTP and reverse proxy server
After=network.target remote-fs.target nss-lookup.target

[Service]
Type=forking
PIDFile=$install_base/nginx/logs/nginx.pid
ExecStartPre=/bin/rm -f $install_base/nginx/logs/nginx.pid
ExecStartPre=$install_base/nginx/sbin/nginx -t
ExecStart=$install_base/nginx/sbin/nginx
ExecReload=/bin/kill -s HUP \$MAINPID
KillSignal=SIGQUIT
TimeoutStopSec=5
KillMode=process
PrivateTmp=true
LimitNOFILE=1000000
[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl restart  nginx&& systemctl enable  nginx


}

#client_max_body_size  8m;
#proxy_set_header Host  $host;
#只添加客户端IP到请求报文头部,转发至后端服务器
#proxy_set_header X-Real-IP $remote_addr;
#添加客户端IP和反向代理服务器IP到请求报文头部
#proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

#proxy_set_header X-Forwarded-For $remote_addr;

#fastcgi_hide_header X-Powered-By;  #添加此行 隐藏php版本信息

main() {

install_package
install_nginx

}

main
#nginx.conf
[root@centos7-templates ~]# cat nginx.conf 

#worker进程以哪个用户运行 ,编译安装时候可以指定用户 组 , master 进程以 root 用户运行
user nginx nginx;

#work进程数量, 最佳值取决于许多因素,包括(但不限于)CPU内核数,auto可以自动检测cpu 核心数量
worker_processes  auto;

#将worker进程绑定到指定的cpu核心, 有几个cpu就写几位数字  8个cpu 与8个work 依次绑定,减少进程切换造成的cpu缓存失效,提高cpu缓存命中率

worker_cpu_affinity 0001 0010 0100 1000 ;

##worker 进程的优先级别 -20到19 默认优先级为 0
# Default:  worker_priority 0; worker_priority 0;

worker_priority -10;

# 所有 worker 进程能够打开的文件数量的上限, 并发量的上限 ,默认无配置 
worker_rlimit_nofile 65536;


#前台执行 或者后台执行 默认 后台执行 写成容器需要配置为前台执行 daemon on | off;
#daemon off;
## master_process on|off 是否已 master/worker模式运行 nginx 默认on 
#master_process off;


#错误日志
error_log  logs/error.log error;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

##nginx master 进程pid 
pid        logs/nginx.pid;


events {
    
##epoll 模型 IO多路复用 事件驱动 回调
use epoll;
#一个worker 进程能够处理的最大并发连接数, 该数目包括所有连接(例如,与代理服务器的连接等),而不仅包括与客户端的连接。另一个需要考虑的因素是,并发连接的实际数量不能超过打开文件最大数量的当前限制,可以通过worker_rlimit_nofile进行更改 
worker_connections  10240;

#如果accept_mutex启用,则工作进程将依次接受新的连接。否则,将通知所有工作进程有关新连接的信息,如果新连接的数量很少,则某些工作进程可能会浪费系统资源.
#惊群: 来了少量的请求把大家都唤醒,可能只要一个worker来处理这些请求
#accept_mutex on | off; 默认值 off 
accept_mutex on; 


## multi_accept on | off; 默认值 off,设置为on后,多个worker按串行方式来处理连接,也就是一个连接只有一个worker被唤醒,其他的处于休眠状态
#设置为off后,多个worker按并行方式来处理连接,也就是一个连接会唤醒所有的worker,直到连接分配完毕,没有取得连接的继续休眠。
## 当你的服务器连接数不多时,开启这个参数会让负载有一定程度的降低。但是当服务器的吞吐量很大时,为了效率,请关闭这个参数
multi_accept on ;


}


http {

    #导入支持的文件类型,相对于/app/nginx/conf的目录
    include       mime.types;
    #mime.types 中文件类型外,设置其他文件默认类型,访问其他类型时会提示下载不匹配的文件类型
    default_type  application/octet-stream;

    ##为了快速寻找到相应MIME type Nginx使用散列表来存储 MIME type与文件扩展名。types_hash_bucket_size 设置了每个散列桶占用的内存大小
    #types_hash_max_size影响散列表的冲突率。types_hash_max_size越大,就会消耗更多的内存,但散列key的冲突率会降低,检索速度就更快。types_hash_max_size越小,消耗的内存就越小,但散列key的冲突率可能上升。
    types_hash_max_size 2048;


    ## 日志配置 log_format 为日志指令 main 为日志格式指定的标签,记录日志时候通过这个main 标签选择指定的格式
    #
    log_format  main 
                     '$remote_addr '  
                     '$remote_user '
                     '[$time_local] - $request '
                     '$status  $body_bytes_sent  '
                     '$http_referer '
                     '$http_user_agent ' 
                     '$http_x_forwarded_for '  
                     '$request_time '
                     '$upstream_response_time ' 
                     '$upstream_addr '
                     '$upstream_status' ;

    ## 设置缓冲日志写入的路径,格式和配置 连接日志的路径,上面指定的日志格式放在最后 
    # access_log off 关闭日志
    #  如果使用该gzip参数,则缓冲的数据将在写入文件之前被压缩。压缩级别可以设置为1(最快,更少压缩)和9(最慢,最佳压缩)之间。默认情况下,缓冲区大小等于64K字节,并且压缩级别设置为1 
    #  为了使gzip压缩正常工作,必须使用zlib库构建nginx
    #  使用buffer或gzip 参数,将缓冲对日志的写入 
    access_log  logs/access.log  main    gzip  buffer=1024K flush=10s;
   
    ## 定义一个缓存,该缓存存储名称中包含变量的常用日志的文件描述符
    #对于每一条日志记录,都将是先打开文件,再写入日志,然后关闭。可以使用open_log_file_cache来设置日志文件缓存(默认是off)
    #open_log_file_cache max=N [inactive=time] [min_uses=N] [valid=time];
    #open_log_file_cache off;
    #http://nginx.org/en/docs/http/ngx_http_log_module.html#access_log
    open_log_file_cache max=1000 inactive=20s valid=1m min_uses=2;
   
     #charset koi8-r;
     #是否在响应报文中的content-Type 显示指定的字符集,默认 off  不显示 charset charset | off;
     charset utf-8;

     #是否在响应报文的 server首部显示nginx 版本         server_tokens on | off | build | string;  默认 on 
     server_tokens off;            


    ## 启用或禁用的使用 sendfile() 函数 来传输文件 默认 off
    # 当应用程序传输文件时,内核首先缓冲数据,然后将数据发送到应用程序缓冲区。 应用程序反过来将数据发送到目的地。 Sendfile方法是一种改进的数据传输方法,其中数据在操作系统内核空间内的文件描述符之间复制,而不将数据传输到应用程序缓冲区。 这使操作系统资源的利用率提高。
    sendfile        on;

   #在开启了 sendfile 的情况下,合并请求后统一发送给客户端, 把响应报文首部和整合文件的起始内容放到一个报文中发送,利用完整的报文发送文件
   #默认 tcp_nopush off
    tcp_nopush          on;


   #TCP/IP网络存在“小包”问题,其中单字符消息可能在高负载网络上导致网络拥塞。 例如分组大小为41字节,其中40字节用于TCP报头,只有1字节是有用信息。 这些小包占用了大约4000%的巨大开销并且使得网络饱和。
   #ohn Nagle通过不立即发送小包来解决 "小包"  问题(Nagle的算法)。 所有这样的分组被收集一定量的时间,然后作为单个分组一次发送。 这改进了底层网络的的效率。 因此,典型的TCP/IP协议栈在将数据包发送到客户端之前需要等待200毫秒。
   #在打开套接字时可以使用TCP_NODELAY选项来禁用Nagle的缓冲算法,并在数据可用时立即发送 不延迟发送。 NGINX提供了tcp_nodelay指令来启用此选项。
   # tcp_nodelay on  会增加小包的数量,但是可以提高响应速度。在及时性高的通信场景中应该会有不错的效果 
   #默认 tcp_nodelay on  关闭nagle 算法(会产生严重的延时效果) 不延迟 立刻发送 
    tcp_nodelay         on;


   #用于设置客户端连接保持会话的超时时间  默认keepalive_timeout 75s; 
    keepalive_timeout  65;

   #启用或禁用响应的gzip压缩    gzip on | off 默认 off
    gzip  on;
    gzip_types text / html; ##   除了“ text/html”外,还对指定的MIME类型启用响应的压缩。特殊值“ *”与任何MIME类型(0.8.29)匹配。“ text/html”类型的响应始终被压缩
    gzip_min_length    1K; #设置将被压缩的响应的最小长度。长度仅从“ Content-Length”响应头字段中确定 
    gzip_buffers     4 16K; # 设置用于压缩响应的number和size的缓冲区。默认情况下,缓冲区大小等于一个内存页。根据平台的不同,它可以是4K或8K。
    gzip_http_version 1.1; #设置压缩响应所需的最低HTTP版本请求
    gzip_comp_level    2; #设置level响应的gzip压缩。可接受的值范围是1到9
    gzip_vary          on; # 增加响应头”Vary: Accept-Encoding” 如果指令gzip, gzip_static或gunzip 处于活动状态, 则启用或禁用插入“ Vary:Accept-Encoding”响应标头字段 。
    
    
     #设置将响应传输到客户端的超时时间。超时仅在两个连续的写操作之间设置,而不用于整个响应的传输。如果客户端在此时间内未收到任何信息,则连接将关闭 
     ## 默认:  send_timeout 60s; 
     send_timeout          25;

     # 定义用于读取客户端请求标头的超时。如果客户端在此时间内未传输整个标头,则请求会因408(请求超时)错误而终止
     ##默认client_header_timeout 60
     client_header_timeout 15;

     ##
     #设置用于读取客户端请求标头的缓冲区大小。对于大多数请求,一个1K字节的缓冲区就足够了。但是,如果请求中包含长Cookie或来自WAP客户端,则该请求可能不适合1K。如果请求行或请求标头字段不适合此缓冲区,则将分配由large_client_header_buffers指令配置的较大缓冲区
     #默认client_header_buffer_size 1k
     client_header_buffer_size 32k;

   ## 设置用于阅读客户端请求主体的缓冲大小
   #client_body_buffer_size 8k|16k
   client_body_buffer_size 16k;


     #设置客户端请求正文的最大允许大小。如果请求中的大小超过配置的值,则会向客户端返回413(请求实体太大)错误。请注意,浏览器无法正确显示此错误。设置size为0将禁用对客户端请求主体大小的检查
     #默认client_max_body_size 1m
     client_max_body_size  8m;


     ## 设置服务器名称哈希表的存储桶大小。默认值取决于处理器的缓存行的大小。
     #默认: server_names_hash_bucket_size 32 | 64 | 128
     server_names_hash_bucket_size 128;

     #设置size服务器名称哈希表的最大值
     #默认: server_names_hash_max_size 512;
     server_names_hash_max_size 512;
     

    ##include  指明包含进来的配置文件 include extra/conf.d/vhosts/*.conf 
    #include extra/mg.conf;     
    #include extra/xueit.conf;     


    ## 定义一组服务器。服务器可以在不同的端口上侦听。另外,可以混合使用侦听TCP和UNIX域套接字的服务器
    #默认情况下,使用加权循环平衡方法在服务器之间分配请求
     upstream tomcatpools { 
      #源地址hash 实现会话粘性,不用
      # ip_hash;
      # server   192.168.80.100:8080 weight=2  max_fails=2 fail_timeout=20s; 
       #server   192.168.80.101:8080 weight=1  max_fails=2 fail_timeout=20s  ;       
       #server   192.168.80.103:8080 weight=1  max_fails=2 fail_timeout=20s ;       
       server   192.168.80.101:8080 weight=1  max_fails=2 fail_timeout=20s ;       
       server   192.168.80.102:8080 weight=1  max_fails=2 fail_timeout=20s ;       
         
    }
    
   
    include  proxy.conf;
    include extra/*.conf;
    #include  extra/jg.conf;

}
#proxy.conf
[root@centos7-templates ~]# cat proxy.conf

 ## 设置http请求header项传给后服务器节点,例如: 可实现让代理后端的服务器节点获取访问客户端用户的真实IP地址
 ## 允许在传递给代理服务器的请求标头中重新定义或附加字段 。该value可以包含文本,变量,以及它们的组合
## 当反向代理服务器向后重新发起请求时候,要携带主机头信息,以明确告诉节点服务器要找哪个虚拟主机
## 这是节点服务器多虚拟主机时候的关键配置 

 proxy_set_header Host  $host;

 ## 在反向代理请求后端节点服务器的请求头中增加获取客户端IP的字段信息 然后节点后端服务器可以通过程序或者相关配置接收 X-Forwarded-For 传过来的客户zheng'shi真实IP 
 #在代理向后端服务器发送的 http 请求头中加入 X-Forwarded-For 字段信息,用于后端服务器程序 日志等接收 记录真实的用户IP 而不是代理服务器的IP
 proxy_set_header X-Forwarded-For $remote_addr;


 ##proxy_set_header X-Real-IP $remote_addr;
## proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;


##表示反向代理与后端节点服务器链接的超时时间,即发起握手等候相应的超时间
##默认: proxy_connect_timeout 60s;
 proxy_connect_timeout 60 ;


##设置nginx 从代理后端服务器获取信息的时间,连接建立成功后,nginx 等待后端服务器响应的时间,其实是nginx 已经进入后端的排队之中等待处理的时间
##默认  proxy_send_timeout 60s 
 proxy_read_timeout 60 ;

##表示代理后端服务器的数据回传时间,即在规定的时间之内后端服务器必须传完所有的数据,否则,nginx 将断开这个连接
##默认 proxy_send_timeout 60s
 proxy_send_timeout 60 ;

 #启用或禁用代理服务器响应的缓冲
#启用缓冲后,nginx会尽快从代理服务器收到响应,并将其保存到proxy_buffer_size和proxy_buffers指令设置的缓冲区中, 如果整个响应都无法容纳到内存中,则可以将一部分响应保存到磁盘上的临时文件中。写入临时文件由 proxy_max_temp_file_size和 proxy_temp_file_write_size指令控制 
## 禁用缓冲后,响应一收到就立即同步传递到客户端。nginx不会尝试从代理服务器读取整个响应。nginx一次可以从服务器接收的最大数据大小由proxy_buffer_size指令设置
 # 默认proxy_buffering on;
 proxy_buffering on;


## 设置缓冲区大小 默认该缓冲区大小等于指令 proxy_buffers设置的大小 
## 设置size用于读取从代理服务器接收到的响应的第一部分的缓冲区的。这部分通常包含一个小的响应头。默认情况下,缓冲区大小等于一个内存页。根据平台的不同,它可以是4K或8K。但是,它可以做得更小
## proxy_buffer_size 4k | 8k;
 proxy_buffer_size 8k;

#设置缓冲区的数量和大小,nginx 从代理的后端服务器获取的响应信息 会放到缓冲区
#默认proxy_buffers 8 4k | 8k
 proxy_buffers 8 32k ;
 
#当缓冲启用从代理的服务器响应,限制总size的缓冲区,可以是正忙于发送到客户端的响应,而响应尚不充分阅读的。同时,其余的缓冲区可用于读取响应,并在需要时将响应的一部分缓冲到临时文件中。默认情况下,size受proxy_buffer_size和proxy_buffers指令设置的两个缓冲区的大小限制 
#默认proxy_busy_buffers_size 8k | 16k;
 proxy_busy_buffers_size 64k;


## size启用从代理服务器到临时文件的响应的缓冲时,一次 限制写入临时文件的数据的数量。默认情况下,size受proxy_buffer_size和proxy_buffers指令设置的两个缓冲区的限制 。临时文件的最大大小由proxy_max_temp_file_size指令设置 。
##默认 proxy_temp_file_write_size 8k | 16k;

 proxy_temp_file_write_size 64k;
#tomcat.conf
[root@centos7-templates ~]# cat tomcat.conf 

server {

        listen 80;
        server_name www.magedu.org;

        location / {
            
            root webapps/sample/;
            index  index.html index.htm;
           

            proxy_pass http://tomcatpools;   
             
            include  proxy.conf;
        }

          
        access_log logs/www.mgedu.com.log main;
        error_page  404              /404.html;

        error_page   500 502 503 504  /50x.html;

        location = /50x.html {

            root   html;
        }

}
#执行脚本install_nginx_v00.sh
[root@centos7-templates ~]# sh install_nginx_v00.sh
....

#监听
[root@centos7-templates ~]# ss -lnt | grep 80 
LISTEN     0      511          *:80                       *:* 

测试负载均衡

nginx做负载均衡调度,默认使用轮询调度算法,将请求调度至后端两台tomcat服务器,同时后端2台tomcat做了session复制集群,基于tomcat 实现多个服务器内共享同步所有session,此方法可以保证任意一台后端服务器故障,其余各服务器上还都存有全部 session ,对业务无影响

基本的负载均衡

nginx 默认轮询调度效果如下:

20211224_221918.png20211224_221947.png

session复制

nginx 轮询调度到后端不同的的 tomcat  服务器 sessionID 保持不变。

Snap4.pngSnap5.png

5.简述memcached的工作原理

Memcached 了解即可,重点在 redis
Memcached 官网:http://memcached.org/
Memcached 只支持能序列化的数据类型,不支持持久化,基于Key-Value的内存缓存系统

Memcached 工作机制
应用程序运行需要使用内存存储数据,但对于一个缓存系统来说,申请内存、释放内存将十分频繁,非
常容易导致大量内存碎片,最后导致无连续可用内存可用。
Memcached 支持最大的内存存储对象为1M,超过1M的数据可以使用客户端压缩或拆分报包放到多个key中,比较大的数据在进行读取的时候需要消耗的时间比较长,memcached 最适合保存用户的session实现session共享。
Memcached 支持多种开发语言,包括:JAVA,C,Python,PHP,C#,Ruby,Perl等
Memcached 官网:http://memcached.org/

Memcached存储数据时, Memcached会去申请1MB的内存, 把该块内存称为一个slab, 也称为一个page
memcached 采用了 slab allocator 机制来分配、管理内存
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为class1,chunk 120B为class2,如果有100bytes 要存,那么 memcached 会选择下图中 slab class2 存储,因为他是120bytes的chunk 。 slab 之间的差异可以使用 growth factor 控制,默认1.25 

懒过期 lazy expiration 
memcached 不会监视数据是否过期,而是在取数据时候才看是否过期,如果过期,把数据有效期限标识为0,并不清除该数据。以后可以覆盖该位置存储的其他数据。
LRU 当内存不足时,memcached 会使用LRU(least recently used)机制来查找可用空间,分配在给新记录使用

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

Snap9.pngSnap10.pngSnap11.png

Memcached Redis 比较

比较类型 redis memcached
支持的数据结构 哈希、列表、集合、有序集合 单纯的key-value
持久化支持 有rdb和aof两种模式 无,只是在内存中
高可用支持 redis 支持集群功能,可以实现主动复制、读写分离。官方也提供了 sentinel 集群管理工具,能够实现主从服务监控,故障转移,这一切,对于客户端是透明的,无需程序改动,也无需人工介入 需要二次开发
存储value容量 最大512M 最大1M
内存分配 临时申请空间,可能导致碎片 预先分配内存池的方式管理内存,能够省去内存分配时间
虚拟内存使用 有自己的vm机制,理论上能够存储比物理内存更多的数据,当数据超量时,会引发 swap 、把冷数据刷到磁盘 所有数据存储在物理内存中
网络模型 非阻塞IO复用模型,提供一些非kv存储之外的排序,聚合功能,在执行这些功能时,复杂的CPU计算,会阻塞整个IO调度 非阻塞IO复用模型
水平扩展的支持 redis cluster 可以横向扩展 没有
多线程 redis6.0之前只支持单线程 memcached 支持多线程、cup利用方面memcache优于redis
过期策略 有专门线程、清除缓存数据 懒淘汰机制:每次往缓存放入数据的时候,都会存一个时间。在读取的时候要和设置的时间做TTL 比较来判断是否过期
单机QPS 约为10w 约为60w
源代码可读性 代码清爽简洁 可能是考虑了提多的扩展性、多系统的兼容性、代码不清爽
适用场景 复杂数据结构、有持久化、高可用需求、value存储内容较大 纯key/value、数据量非常大、并发量非常大的业务

源码编译安装memcached

官方网站:http://memcached.org/
http://www.memcached.org/files/memcached-1.6.12.tar.gz
文件:
install_memcached_v00.sh
memcached-1.6.12.tar.gz


[root@centos7-templates ~]# cat install_memcached_v00.sh 
#!/bin/bash
#=====================================================================================================
#File Name:           install_memcached_v00.sh
#Date:                2021-12-26 13-37-09
#Author:              Create by ok and ko
#Description:         This script function is
#Shell Version:       GNU bash version 4.1.2(2)-release x86_64-redhat-linux-gnu
#Copyright (C):       2021 All rights reserved
#=====================================================================================================

src_dir=`pwd`
cpus=`lscpu|awk -F: '/^CPU\(s\):/ {print $2}'`
memcached_file="memcached-1.6.12.tar.gz"
memcached_base=/app
memcached_home=/app/memcached

for i in   gcc libevent-devel
do
rpm -q $i &>/dev/null || yum install -y -q $i &>/dev/null
done

id memcached &>/dev/null || useradd -r -s /sbin/nologin memcached


#tar xvf memcached-1.6.12.tar.gz  -C $memcached_base
tar xvf memcached-1.6.12.tar.gz

cd memcached-1.6.12

./configure --prefix=$memcached_home

make -j $cpus && make install 

cat>/etc/profile.d/memcached.sh<<EOF
PATH=$memcached_home/bin:\$PATH
EOF

source /etc/profile.d/memcached.sh


#memcached运行参数
#memcached 常见选项

#-u username memcached 运行的用户身份 必须普通用户
#-p 绑定的端口,默认 11211
#-m num 最大内存,单位MB,默认64M
#-c num 最大连接数,缺省1024 
#-d 守护进程方式运行
#-f 增长因子 growth factor 默认1.25 
#-v 详细信息 -vv 能看到详细信息
#-M 使用内存直到耗尽 不允许 LRU
#-U 设置UDP监听端口 0 表示禁止用 UDP

#范例 查看Slab Class
#[root@centos7-templates ~]# memcached -u memcached -p 11211 -f 2 -vv
#slab class   1: chunk size        96 perslab   10922
#slab class   2: chunk size       192 perslab    5461
#slab class   3: chunk size       384 perslab    2730
#slab class   4: chunk size       768 perslab    1365
#slab class   5: chunk size      1536 perslab     682
#slab class   6: chunk size      3072 perslab     341
#slab class   7: chunk size      6144 perslab     170
#slab class   8: chunk size     12288 perslab      85
#slab class   9: chunk size     24576 perslab      42
#slab class  10: chunk size     49152 perslab      21
#slab class  11: chunk size     98304 perslab      10
#slab class  12: chunk size    196608 perslab       5
#slab class  13: chunk size    524288 perslab       2



cat>/etc/sysconfig/memcached<<EOF 
PORT="11211"
USER="memcached"
MAXCONN="1024"
CACHESIZE="500"
OPTIONS=""
EOF

cat>/usr/lib/systemd/system/memcached.service<<EOF
[Unit]
Description=memcached daemon
Before=httpd.service
After=network.target
[Service]
EnvironmentFile=/etc/sysconfig/memcached
ExecStart=$memcached_home/bin/memcached -p  \${PORT}  -u \${USER} -m \${CACHESIZE} -c \${MAXCONN} \$OPTIONS
PrivateTmp=true
ProtectSystem=full
NoNewPrivileges=true
PrivateDevices=true
CapabilityBoundingSet=CAP_SETGID CAP_SETUID CAP_SYS_RESOURCE
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable --now memcached.service &> /dev/null
systemctl is-active memcached.service &> /dev/null &&  echo  "memcached 安装完成" || { echo "memcached 安装失败" ; exit; }


[root@centos7-templates ~]# systemctl status memcached.service 
● memcached.service - memcached daemon
   Loaded: loaded (/usr/lib/systemd/system/memcached.service; enabled; vendor preset: disabled)
   Active: active (running) since Sun 2021-12-26 13:35:04 CST; 9min ago
 Main PID: 3674 (memcached)
   CGroup: /system.slice/memcached.service
           └─3674 /app/memcached/bin/memcached -p 11211 -u memcached -m 500 -c 1024

Dec 26 13:35:04 centos7-templates systemd[1]: Started memcached daemon.
[root@centos7-templates ~]# memcached --version 
memcached 1.6.12