1、haproxy https实现
- 配置haproxy,执行SSL加解密
[root@centos8mini ~]# cat /etc/haproxy/haproxy.cfg
...
frontend www
log global
option httplog
bind 192.168.156.11:80
#需要加上ssl选项
bind 192.168.156.11:443 ssl crt /root/mxx.pem
#http到https重定向
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 }
use_backend aaa
mode http
#capture request header Host len 256
#capture request header User-Agent len 512
#capture request header Referer len 15
#capture request header X-Forwarded-For len 15
backend aaa
cookie Dserver insert nocache indirect
server rs1 192.168.156.202:80 cookie web1 send-proxy
server rs2 192.168.156.204:80 cookie web2 send-proxy
...
- 配置nginx日志,记录x-forwarded-port和x-forwarded-proto信息:
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" "$host" "$proxy_protocol_addr" "$http_x_forwarded_port" "$http_x_forwarded_proto"';
- 测试结果
#后端服务器可以看到https,haproxy加上去的请求头部字段
192.168.156.208 - - [21/Jan/2022:21:11:36 +0800] "GET / HTTP/1.1" 200 110 "-" "curl/7.58.0" "192.168.156.100" "www.mxx.com" "192.168.156.100" "443" "https"
- 抓包可以看到前端是80切443,后端一直是80,ssl的加解密由haproxy完成
#客户端100去访问VIP 156.11的443端口
21:24:23.882974 IP 192.168.156.11.443 > 192.168.156.100.37330: Flags [P.], seq 1353:1624, ack 695, win 235, options [nop,nop,TS val 3578864071 ecr 3498970570], length 271
21:24:23.883118 IP 192.168.156.11.443 > 192.168.156.100.37330: Flags [P.], seq 1624:1895, ack 695, win 235, options [nop,nop,TS val 3578864071 ecr 3498970570], length 271
21:24:23.883393 IP 192.168.156.100.37330 > 192.168.156.11.443: Flags [.], ack 1895, win 501, options [nop,nop,TS val 3498970571 ecr 3578864071], length 0
#haproxy 208去访问后端的204的80端口
21:24:23.883424 IP 192.168.156.208.46246 > 192.168.156.204.80: Flags [S], seq 3363885263, win 29200, options [mss 1460,sackOK,TS val 3269997331 ecr 0,nop,wscale 7], length 0
21:24:23.883460 IP 192.168.156.204.80 > 192.168.156.208.46246: Flags [S.], seq 3613891712, ack 3363885264, win 28960, options [mss 1460,sackOK,TS val 1974460156 ecr 3269997331,nop,wscale 7], length 0
21:24:23.884099 IP 192.168.156.208.46246 > 192.168.156.204.80: Flags [.], ack 1, win 229, options [nop,nop,TS val 3269997332 ecr 1974460156], length 0
21:24:23.884167 IP 192.168.156.208.46246 > 192.168.156.204.80: Flags [P.], seq 1:231, ack 1, win 229, options [nop,nop,TS val 3269997332 ecr 1974460156], length 230: HTTP
21:24:23.884177 IP 192.168.156.204.80 > 192.168.156.208.46246: Flags [.], ack 231, win 235, options [nop,nop,TS val 1974460157 ecr 3269997332], length 0
2、总结tomcat的核心组件以及根目录结构
tomcat核心组件
- Server组件:一个server就是一个tomcat实例,不同server使用不同的端口
- service组件:包含了Engine和Connector的对应关系
- Connector组件:可以简单理解为一个监听端口,包含HTTP、HTTPS、AJP协议的连接器
- 容器类组件:在Engine内创建多个Host,Host内可以配置本地磁盘路径和URL路径的关联关系
- Engine:接收用户通过某个接口发送的处理请求;Server里只有一个Engine
- Host:一个Engine可以包含多个虚拟主机,和Apache、Nginx的虚拟主机是一样的概念,通过端口、IP、域名等方式区分虚拟主机
- Context:执行路径映射,类似Apache或Nginx里的Location,配置磁盘目录和URL路径的关联关系
- 内嵌类:可以内嵌到其他组件内,如valve、logger、realm、loader、manager等
- 集群类组件:listener、cluster
根目录结构
- bin/:包含服务启动、停止的程序和文件,如shutdown.sh、startup.sh、catalina.sh等
- conf/:包含全局配置文件,比如启动service时,EnvironmentFile=tomcat.conf文件、web.xml、server.xml、context.xml、tomcat-users.xml等
- lib/:java库目录
- logs/:日志文件的存放位置
- webapps/:类似apache的/var/www/html,是tomcat默认页面的存放路径
- work/:jsp编译后的字节码和servlet文件存放位置,通常jsp文件需要被提前预热访问,以加快用户的访问速度;
- 该目录中的缓存文件可能会影响新版本资源的发布,需要删除这些自动生成的文件,让新版本的jsp可以重新生成
3、tomcat实现多虚拟主机
#!/bin/bash
#****************************************************************************************#
#Author: Yabao11
#QQ: what QQ,no QQ
#Date: 2022-01-04
#FileName: nginx.sh
#URL: https://github.com/yabao11
#Description: Test Script
#Copyright (C): 2022 All rights reserved
#*******************************定义颜色*************************************************#
RED="\e[1;31m"
GREEN="\e[1;32m"
SKYBLUE="\e[1;36m"
YELLOW="\e[1;43m"
BLUE="\e[1;44m"
END="\e[0m"
RandomColor="\e[1;32m"
#****************************************************************************************#
function Ostype {
if grep -i -q "release 6" /etc/centos-release;then
echo Centos6
elif grep -i -q Centos-8 /etc/os-release;then
echo Centos
elif grep -i -q Centos-7 /etc/os-release;then
echo Centos7
elif grep -i -q Ubuntu /etc/os-release;then
echo Ubuntu
elif grep -i -q "RedHat" /etc/os-release;then
echo Redhat
fi
}
function color {
RES_COL=60
MOVE_TO_COL="echo -en \E[${RES_COL}G"
SETCOLOR_SUCCESS="echo -en \E[1;32m"
SETCOLOR_FAILURE="echo -en \E[1;31m"
SETCOLOR_WARNING="echo -en \E[1;33m"
SETCOLOR_NORMAL="echo -en \E[0m"
echo -n "$1" && $MOVE_TO_COL
echo -n "["
if [[ $2 = "success" || $2 = "0" ]]; then
${SETCOLOR_SUCCESS}
echo -n " OK "
elif [[ $2 = "failure" || $2 = "1" ]]; then
${SETCOLOR_FAILURE}
echo -n "FAILED"
else
${SETCOLOR_WARNING}
echo -n "WARNING"
fi
${SETCOLOR_NORMAL}
echo -n "]"
echo
}
function jdk_install {
read -p "输入希望二进制安装的jdk版本,直接回车默认安装'/root/jdk-8u321-linux-x64'" jdk
oracle_jdk=${jdk:-jdk-8u321-linux-x64}
[ -e /root/${oracle_jdk}.tar.gz ] && tar xvf /root/${oracle_jdk}.tar.gz -C /usr/local/src/ || { color "文件不存在" 2;exit; }
mv /usr/local/src/jdk* /usr/local/src/jdk
cat > /etc/profile.d/jdk.sh <<\EOF
export JAVA_HOME=/usr/local/src/jdk
export PATH=$PATH:$JAVA_HOME/bin
export JRE_HOME=$JAVA_HOME/jre
export CLASSPATH=$JAVA_HOME/lib/:$JRE_HOME/lib/
EOF
. /etc/profile.d/jdk.sh
java -version > /dev/null && color "jdk安装成功!" 0
}
read -p "输入希望二进制安装的tomcat版本,直接回车默认安装'apache-tomcat-8.5.75'" tomcat
read -p "输入JAVA_HOME路径,直接回车使用默认路径:'/usr/local/src/jdk'" pjdk
read -p "输入tomcat路径,直接回车使用默认路径:'/usr/local/src/tomcat'" ptomcat
tomcat_version=${tomcat:-apache-tomcat-8.5.75}
java_path=${pjdk:-/usr/local/src/jdk}
tomcat_path=${ptomcat:-/usr/local/src/tomcat}
function tomcat_install {
java -version > /dev/null || { color "缺少java环境!" 1;exit; }
[ -e /root/${tomcat_version}.tar.gz ] || { yum -y install wget;wget https://mirrors.tuna.tsinghua.edu.cn/apache/tomcat/tomcat-8/v8.5.75/bin/${tomcat_version}.tar.gz; }
[ -e /root/${tomcat_version}.tar.gz ] || { color "找不到tomcat文件" 1;exit; }
tar xf ${tomcat_version}.tar.gz -C /usr/local/src/
[ -e ${tomcat_path} ] && rm -rf ${tomcat_path}
ln -s /usr/local/src/${tomcat_version} ${tomcat_path}
echo 'PATH=${tomcat_path}/bin:$PATH' > /etc/profile.d/tomcat.sh
. /etc/profile.d/tomcat.sh
useradd -r -s /sbin/nologin tomcat
chmod +x ${tomcat_path}/bin/*
catalina.sh version > /dev/null && color "安装成功!" 0 || color "catalina命令执行失败,请检查权限或路径" 1
cat > ${tomcat_path}/conf/tomcat.conf <<EOF
JAVA_HOME=${java_path}
CATALINA_HOME=${tomcat_path}
EOF
chown -R tomcat.tomcat ${tomcat_path}/ && color "tomcat目录权限修改成功" 0 || color "tomcat目录权限修改失败" 1
echo -e $GREEN"创建tomcat.service文件"$END
[ -e /lib/systemd/system/tomcat.service ] || 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_path}/conf/tomcat.conf
ExecStart=${tomcat_path}/bin/startup.sh
ExecStop=${tomcat_path}/bin/shutdown.sh
PrivateTmp=true
User=tomcat
Group=tomcat
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now tomcat && color "tomcat启动成功!" 0 || color "tomcat启动失败,请执行journalctl -u tomcat查看日志详情" 1
}
function tomcat_config {
sed -i.bak -r -e 's|(pattern.*)("[[:space:]]+\/>)|\1 %{JSESSIONID}c %{HOST}i %{X-Forwarded-For}i %{X-Via}o\2|' \
-e 's|shutdown="SHUTDOWN"|shutdown="44ba3c71d57f494992641b258b965f28"|' \
-e '/<\/Engine>/i <Host name="www.mxx.com" appBase="mxx"\n unpackWARs="true" autoDeploy="true">\n <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"\n prefix="mxx_access_log" suffix=".txt"\n pattern="%h %l %u %t "%r" %s %b %{JSESSIONID}c %{HOST}i %{X-Forwarded-For}i %{X-Via}o" />\n </Host>\n' \
${tomcat_path}/conf/server.xml && color "配置日志格式,修改server参数" 0
sed -i.bak -r '/<\/tomcat-users>/i<role rolename="manager-gui"/>\n<role rolename="admin-gui"/>\n<user username="tomcat" password="tomcat" roles="manager-gui,admin-gui"/>\n' ${tomcat_path}/conf/tomcat-users.xml && color "配置允许访问tomcat的status等管理页面" 0
sed -i.bak -r 's|(allow.*)("[[:space:]]+\/>)|\1\|\\d+\\.\\d+\\.\\d+\\.\\d+\2|' ${tomcat_path}/webapps/manager/META-INF/context.xml && color "配置允许所有IP地址访问status(测试环境)" 0
sed -i.bak -r 's|(allow.*)("[[:space:]]+\/>)|\1\|\\d+\\.\\d+\\.\\d+\\.\\d+\2|' ${tomcat_path}/webapps/host-manager/META-INF/context.xml && color "配置允许所有IP地址访问host-manager(测试环境)" 0
echo -e $GREEN"创建mxx虚拟主机"$END
mkdir ${tomcat_path}/mxx/ROOT -p
chown tomcat.tomcat ${tomcat_path}/mxx -R
cat > ${tomcat_path}/mxx/ROOT/index.jsp <<\EOF
<%@ page import="java.util.*" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" http-equiv=Content-Type content="text/html;charset=utf-8">
<title>tomcat test</title>
</head>
<body>
<h1> Tomcat Website </h1>
<div>On <%=request.getServerName() %></div>
<div><%=request.getLocalAddr() + ":" + request.getLocalPort() %></div>
<div>SessionID = <span style="color:blue"><%=session.getId() %></span></div>
<%=new Date()%>
<h1>这是一个测试页面</h1>
</body>
</html>
EOF
echo -e $GREEN"创建多个虚拟主机"$END
while true;do
read -p "是否需要继续创建虚拟主机?(yes or no)" askuser1
askuser1=`echo $askuser1 | tr 'A-Z' 'a-z'`
case $askuser1 in
y|yes)
let i=++i
;;
n|no)
break
;;
*)
inputerror
;;
esac
read -p "输入虚拟主机的域名" vhost
if [ -z ${vhost} ];then
while [ -z ${vhost} ];do
read -p "请输入虚拟主机的域名" vhost
done
fi
sed -i.bak -r -e '/<\/Engine>/i <Host name="'${vhost}'" appBase="mxx'$i'"\n unpackWARs="true" autoDeploy="true">\n <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"\n prefix="mxx'$i'_access_log" suffix=".txt"\n pattern="%h %l %u %t "%r" %s %b %{JSESSIONID}c %{HOST}i %{X-Forwarded-For}i %{X-Via}o" />\n </Host>\n' \
${tomcat_path}/conf/server.xml && color "虚拟主机配置成功" 0
mkdir ${tomcat_path}/mxx${i}/ROOT -p
chown tomcat.tomcat ${tomcat_path}/mxx$i -R
cat > ${tomcat_path}/mxx$i/ROOT/index.jsp <<\EOF
<%@ page import="java.util.*" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" http-equiv=Content-Type content="text/html;charset=utf-8">
<title>tomcat test</title>
</head>
<body>
<h1> Tomcat Website </h1>
<div>On <%=request.getServerName() %></div>
<div><%=request.getLocalAddr() + ":" + request.getLocalPort() %></div>
<div>SessionID = <span style="color:blue"><%=session.getId() %></span></div>
<%=new Date()%>
<h1>这是一个测试页面</h1>
</body>
</html>
EOF
systemctl restart tomcat.service
done
}
#exec
jdk_install && tomcat_install && tomcat_config
测试结果
4、nginx实现后端tomcat的负载均衡调度
Nginx作为反向代理配置
配置nginx反向代理,将域名全部调度给后端tomcat服务器组,因为后端tomcat配置了虚拟主机,因此调度时修改了Host字段的值:
[root@centos8mini ~]# cat /data/nginx/conf/conf.d/server1.conf
server {
listen 80;
server_name m1.mxx.com;
default_type text/html;
root /data/server1;
# location ~* \.jsp$ {
# proxy_pass http://www.mxx.com:8080;
# #proxy_set_header Host $http_host;
# }
# location ~* \.html$ {
# proxy_pass http://blog.mxx.com:8080;
# #proxy_set_header Host $http_host;
# }
# location /blog {
# index index.jsp;
# proxy_pass http://www.mxx.com:8080/blog;
# }
location / {
index index.jsp;
proxy_set_header Host www.mxx.com;
proxy_pass http://webserver;
}
}
在http语句块下定义服务器组,指向后端的两台tomcat服务器,调度算法也是在这里指定:
[root@centos8mini ~]# cat /data/nginx/conf/nginx.conf
...
http {
upstream webserver {
#hash $cookie_jsessionid consistent;
server 192.168.1.12:8080 max_fails=3 fail_timeout=30s;
server 192.168.1.14:8080 max_fails=3 fail_timeout=30s;
....
}
...
测试结果
修改windows的hosts文件,解析m1.mxx.com域名,指向nginx代理服务器:
192.168.1.13 m1.mxx.com m2.mxx.com
没做session的会话保持前,调度到不同服务器会生成不同的session ID:
5、简述memcached的工作原理
Memcached采用Slab Allocator机制来分配、管理内存。根据提前定义好的Growth factor等参数,memcached启动后自动按照定义的参数分配内存空间并执行数据的存储。
memcached使用c/s架构,服务器维护key-value关联数组,客户端输入数据到数组中,并使用key来查询。
key可以有250字节长度,value可以存最大1M。
memcached内存空间是启动时提前分配好的。内存被分成大小相等的slab,然后每个slab被等分成chunk,slab按照chunk的大小进行分组,组成不同的slab class。
- 一个1M大小内存空间作为一个slab,slab会等分成固定大小的chunk,但不同slab等分的chunk是不同的。处于相同层次的slab分配的chunk大小是相同的,不同层次按照growth factor来确定下一层次slab中chunk的大小。
当执行value存储时,memcached会根据value的大小,找到合适的chunk进行存储。
当内存不足时,memcached使用LRU(least recently used)机制来查找可用空间。memcached不会监视数据是否过期,而是采取Lazy Expiration懒过期机制,在取数据时才查看数据是否过期,过期就把数据有效期标识为0,但并不清理该数据,而是在以后直接覆盖该位置来存储其他数据。
memcached集群功能是基于客户端的分布式集群,客户端在生成数据时决定应该在哪里存放数据,memcached不会去执行数据的集群同步。