企业级web应用服务器TOMCAT入门详解
一. web
1.1 为什么“网页里的应用”≈“Tomcat 里的应用”?
浏览器只认识 HTTP。
你在地址栏敲http://xxx.com/login
,浏览器发出去的就是一段纯文本 HTTP 报文。Tomcat 天生吃 HTTP。
它实现了 JavaEE 里的 Servlet 规范:把 HTTP 请求封装成HttpServletRequest
→ 交给程序员写的LoginServlet
→ 生成 HTML → 再按 HTTP 格式返回。
于是“浏览器 ↔ HTTP ↔ Tomcat ↔ 你的业务代码”这一条路就打通了。因此,只要最终是通过浏览器访问的,几乎都可以说“由 Tomcat(或同类 Web 服务器)实现”。
1.2 B/S 与 C/S 的“空间之争”
维度 | C/S | B/S |
---|---|---|
客户端 | 必须安装专门软件(QQ、Steam、银行 U 盾) | 只用浏览器 |
升级 | 每台电脑都要重新装包 | 改服务器就行 |
磁盘占用 | 客户端几百 MB 起步 | 0 MB(浏览器不算) |
网络协议 | 随意(TCP/UDP、自定义) | 只用 HTTP/HTTPS |
代表场景 | 游戏、实时交易 | 论坛、OA、博客、后台管理 |
结论:
“节省磁盘空间”只是 B/S 最直观的优势,真正让它统治世界的是 零安装、零维护、跨平台。
1.3、三大核心技术到底各干什么?
HTML(骨架)
纯文本的标签积木:<form>
、<input>
、<table>
……告诉浏览器“这里有个登录框,那里放个按钮”。CSS(皮肤)
css
同一段 HTML,可以换不同 CSS 立刻变成“极简风”或“赛博朋克”。
例:复制
input[type=text] {border-radius: 8px;box-shadow: 0 0 10px #00f; }
JavaScript(肌肉)
让页面“动起来”:表单实时校验(失焦就提示“密码太短”)
Ajax 异步拉数据(下面细讲)
动画、游戏、可视化……
浏览器拿到 .html
→ 边解析边向服务器要 .css
、.js
→ 渲染引擎把三者拼成你看到的像素。
1.4、同步交互:整页刷新之痛
经典场景:注册页面
流程:
填 8 个字段 → 点【提交】
浏览器整页 POST 给服务器
服务器发现“两次密码不一致”
返回一个全新 HTML 页面
浏览器重新渲染 → 前 7 个字段全清空
这就是“同步”——一次 HTTP 请求对应一次完整页面刷新。
缺点:
带宽浪费(CSS/JS/图片全部重传)
体验差(用户想骂人)
1.5、异步交互:Ajax 的魔法
关键技术:XMLHttpRequest(浏览器内置 API)。
浏览器悄悄再开一条 HTTP 连接,只拿回 需要的那一小块数据(JSON/HTML 片段),然后用 JS 把页面局部更新。
用户无感刷新。流程对比:
同步:填表单 → 提交 → 等待 → 整页刷新
异步:输完邮箱 → JS 立即发请求
/checkEmail?email=xxx
→ 返回{"ok":false,"msg":"已注册"}
→ 页面只把提示字变红,其他不动
关键词:
Ajax(Asynchronous JavaScript And XML)
现在多用 JSON 代替 XML
衍生:Fetch API、Axios、jQuery.ajax()
1.6、从“代码”到“网页”的完整链路
你在 IDEA 写
LoginServlet.java
,继承HttpServlet
,处理POST /login
。mvn package
打成war
→ 扔到 Tomcat 的webapps
。浏览器访问
http://域名/login.html
。Tomcat 把
login.html
吐给浏览器(HTML+CSS+JS)。用户填完账号密码,JS 用 Ajax POST
/login
。LoginServlet
查询数据库 → 返回 JSON{status:0,token:xxx}
。JS 把 token 存
localStorage
,页面跳转到/index.html
,全程无整页刷新。
二 tomcat
#安装java环境
[root@tomcat ~]# yum install java-1.8.0-openjdk.x86_64 -y#查看java版本
[root@tomcatA ~]# java -versionopenjdk version "1.8.0_402"OpenJDK Runtime Environment (build 1.8.0_402-b06)OpenJDK 64-Bit Server VM (build 25.402-b06, mixed mode)#查看java的环境目录
[root@tomcatA ~]# which java/usr/bin/java[root@tomcatA ~]# ll /usr/bin/javalrwxrwxrwx 1 root root 22 Jul 30 10:41 /usr/bin/java -> /etc/alternatives/java#java的运行环境
[root@tomcatA ~]# cd /etc/alternatives/jre[root@tomcatA jre]# lsASSEMBLY_EXCEPTION bin lib LICENSE THIRD_PARTY_README
#解压并生成tomcat的程序目录
[root@tomcatA ~]# tar zxf apache-tomcat-9.0.107.tar.gz -C /usr/local/[root@tomcatA ~]# cd /usr/local/[root@tomcatA local]# lsapache-tomcat-9.0.107 bin etc games include lib lib64 libexec sbin share
src[root@tomcatA local]# mv apache-tomcat-9.0.107/ tomcat[root@tomcatA local]# ls
bin etc games include lib lib64 libexec sbin share src tomcat#启动tomcat[root@tomcatA local]# cd tomcat/[root@tomcatA tomcat]# cd bin/[root@tomcatA bin]# lsbootstrap.jar
commons-daemon-native.tar.gz makebase.sh
juli.jarcatalina.bat
native.tar.gzcatalina.sh
configtest.bat
configtest.sh
wrapper.batcatalina-tasks.xml daemon.sh
wrapper.shciphers.bat
ciphers.sh
digest.bat
digest.sh
commons-daemon.jar makebase.bat
tomcat
setclasspath.bat tomcat
setclasspath.sh
shutdown.bat
shutdown.sh
startup.bat
startup.sh[root@tomcatA bin]# ./startup.shUsing CATALINA_BASE: /usr/local/tomcatUsing CATALINA_HOME: /usr/local/tomcatUsing CATALINA_TMPDIR: /usr/local/tomcat/tempUsing JRE_HOME:
/usrtool
tool
version.batversion.shUsing CLASSPATH:
/usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jarUsing CATALINA_OPTS:Tomcat started.
[root@tomcat ~]# netstat -antlupe | grep java
安装JAVA环境
java版本查看
查看java真正的环境目录
jre真正的运行环境
要把java的运行环境指定给Tomcat
安装tomcat:
解压并重命名
进入目录并启动它
启动成功后查看端口
测试:
在另一台主机上也要做
简单一点就直接复制了
编写启动程序
创建专用系统用户
useradd -r -s /sbin/nologin -M tomcat
统一目录权限
chown -R tomcat:tomcat /usr/local/tomcat
写入环境变量(Tomcat 专用)
文件:/usr/local/tomcat/conf/tomcat.conf
内容只有一行:
JAVA_HOME=/etc/alternatives/jre
生成 systemd 单元文件
文件:/lib/systemd/system/tomcat.service
[Unit]
Description=Apache Tomcat
After=syslog.target network.target[Service]
Type=forking
EnvironmentFile=/usr/local/tomcat/conf/tomcat.conf
ExecStart=/usr/local/tomcat/bin/startup.sh
ExecStop=/usr/local/tomcat/bin/shutdown.sh
PrivateTmp=true
User=tomcat
Group=tomcat[Install]
WantedBy=multi-user.target
启动并设为开机自启
systemctl daemon-reload
systemctl enable --now tomcat # 立即启动 + 开机自启
systemctl status tomcat # 查看运行状态
一键验证
ss -lntp | grep java # 应看到 8080 端口
curl -I http://localhost:8080 # 返回 200 OK
内容中制定用户了,所以这里要建立用户
要给tomcat指定java的运行环境
报错情况:
此时是没有运行这个文件的权限
查看权限
加权限
测试:
2.2 结合反向代理实现tomcat部署
浏览器里看到的“网页应用”≈ Nginx + Tomcat 这对黄金搭档在干活,而 Nginx 负责反向代理 / 负载均衡,Tomcat 负责跑 Java 代码。
为了不丢登录态,还要解决 Session 一致性 问题。
什么是“反向代理”?
正反向区别一句话:
正向代理:帮 客户端 去访问外网(翻墙)。
反向代理:帮 服务端 接收外网请求(统一入口)。
为什么要反向代理 Tomcat?
端口统一:浏览器只认 80/443,Tomcat 默认 8080。
动静分离:Nginx 处理静态文件(css/js/png)速度是 Tomcat 的 10 倍。
安全:Tomcat 不再直接暴露,可放在内网。
指令拆解
nginx复制
location ~ \.jsp$ {proxy_pass http://172.25.254.10:8080; # 把请求原封不动转给 Tomcatproxy_set_header Host $host; # 保留浏览器里的域名,否则 Tomcat 重定向会出错 }
浏览器发的是
Host: lee.timinglee.org
,Tomcat 也收到同样的 Host,就不会跳错地址。
为什么要“负载均衡”?
单台 Tomcat 的并发瓶颈:
纯 Java 业务,QPS 大约几百~一两千。用户量一上来就卡死。思路:把同样的应用复制到多台机器,把压力 分摊,术语叫 Load Balance。
Nginx 的 3 种常用调度算法
轮询(默认)——每人一次,最公平。
ip_hash ——同一 IP 固定到一台,解决 会话保持(后面细说坑)。
hash $cookie_JSESSIONID ——按 SessionID 选机器,比 ip_hash 更精准。
HTTP 的无状态 & Session 机制
无状态:HTTP 协议本身不带“记忆”。
第一次请求登录成功,第二次刷新页面,Tomcat 压根不知道你是刚才那个人。解决思路:
服务器给浏览器发一张“身份证”—— SessionID(随机字符串)。
浏览器以后每次请求都在 Cookie 里带上
JSESSIONID=xxx
。Tomcat 收到后,从自己 内存 里找对应的
SessionMap
,取出用户信息。
生命周期
默认 30 min 没交互就失效(
web.xml
可调)。关闭浏览器 Cookie 消失,再开就重新领身份证。
负载均衡后出现的新问题:Session 丢失
场景:
浏览器第一次请求被分到 Tomcat-A,SessionID=1001 存在 A 的内存。
第二次请求被分到 Tomcat-B,B 内存里没 1001 → 强制跳回登录页。
这就是“找不到 SessionID”的根本原因:HTTP 无状态 + 多台机器内存不共享。
三种 Session 一致性方案对比
表格
复制
方案 | 原理 | 优点 | 缺点 | 适用 |
---|---|---|---|---|
1. ip_hash | Nginx 按客户端 IP 固定到同一台机器 | 零改动 | NAT 场景下严重倾斜;单点故障需重新登录 | 内部后台、用户量小 |
2. Tomcat Cluster | 多台 Tomcat 组播同步 Session | 官方原生 | 组播受网络限制;同步风暴 | 小规模内网 |
3. MSM+Memcached | Session 序列化后存 Memcached,多台 Tomcat 共享 | 无状态、高可用、扩展好 | 需额外部署 Memcached;首次配置稍复杂 | 生产最强 |
MSM+Memcached 工作流程(图+文字)
复制
浏览器││ 1. 登录请求 → Nginx → Tomcat-A│ ││ └─ 2. 创建 SessionID=1001│ 同时 3. 序列化 Session 到 Memcached││ 4. 第二次请求 → Nginx → Tomcat-B│ ││ └─ 5. Tomcat-B 发现 Cookie:JSESSIONID=10016. 去 Memcached 取 Session → 继续业务
如果 Tomcat-A 挂了,Tomcat-B/C/D 任何一台都能从 Memcached 拿到同一份 Session,用户无感知。
配置小结(回顾)
反向代理(单机)
nginx复制
location ~ \.jsp$ {proxy_pass http://172.25.254.10:8080;proxy_set_header Host $host; }
负载均衡(多机)
nginx复制
upstream tomcat {hash $cookie_JSESSIONID; # 会话保持最稳server 172.25.254.10:8080;server 172.25.254.20:8080; }
MSM 共享 Session
把相关 jar 扔进
$CATALINA_HOME/lib
context.xml 指定 Memcached 节点
重启 Tomcat 即可生效单机
现在需要一个测试页面
测试页面不能直接在root里要放到默认发布目录里
直接下载nginx
在配置文件里写下子配置命令 include
错误情况:
访问超时
直接访问没问题
域名解析有误,之前实验的影响
测试:
之前的信息也会保留
这个代码,nginx不能写,是tomcat来写的
tomcat挂了,这个实验就不能用了
单机情况下,tomcat挂了,就不能用了
要解决这个问题,用多机来做
测试:
会话绑定
缺点:如果同一个路由器过来的路由都会跑向一个路由器上了
cookie客户端生成的会话 session服务器生成的会话
会话:以用户的身份与服务器对话
此时的情况时,来一个服务器就重新对话
所以解决办法是生成sessionIP要与cookieIP一致,cookie值变了,就访问不同服务器
写cookie哈希
对键值进行哈希
测试:
ID是一样的就会访问同一个服务器上
只有挂了浏览器才会改变,此时ID也变了
前端还在做数据存储,但是后端的tomcat却挂了,前端是不知道的,该怎么办?
无论是10还是20,之前的数据该怎么保存?
三、Memcached
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等操作系统上发挥其高性能 Memcached 支持最大的内存存储对象为1M,超过1M的数据可以使用客户端压缩或拆分报包放到多个 key中,比较大的数据在进行读取的时候需要消耗的时间比较长,
memcached 最适合保存用户的 session实现session共享 Memcached存储数据时, Memcached会去申请1MB的内存, 把该块内存称为一个slab, 也称为一个page Memcached 支持多种开发语言,包括:JAVA,C,Python,PHP,C#,Ruby,Perl等
Memcached 官网: http://memcached.org/
它本身不支持持久化,只支持能序列化的数据类型
没有客户端,是个键值
[root@tomcat ~]# yum install memcached -y[root@tomcat ~]# vim /etc/sysconfig/memcachedPORT="11211"USER="memcached"MAXCONN="1024"CACHESIZE="64"OPTIONS="-l 0.0.0.0,::1"0
980
[root@tomcat ~]# systemctl enable --now memcached[root@tomcat ~]# netstat -antlupe | grep memcachetcp
0 0.0.0.0:11211
97815
34711/memcached
[root@tomcat ~]# telnet localhost 11211Trying ::1...Connected to localhost.Escape character is '^]'.#增加
add leekey 0 60 4
#0 是否压缩 60 过期时间 4 字长
testSTOREDadd leekey1 0 60 3leeSTORED#查看
get leekeyVALUE leekey 0 4testget leekey1VALUE leekey1 0 3lee#改
set leekey 0 60 5test1STOREDget leekeyVALUE leekey 0 5test1ENDadd leekey1 0 60 4test#删除
delete leekeyDELETEDget leekeyEND
get leekey1VALUE leekey1 0 3lee#清空
flush_allOKget leekey1END
实验:
修改接口,打开全部
再次查看端口信息
要把接口都打开
测试(长连接实验也用过):
这里的2是字长 :规定后面输入的字数只能是两个
查看刚刚储存的内容
修改储存的内容
删除并退出
企业一般不用
使用插件,让tomcat往memcached里存储,实际上,两者是没有关系的。
四、msm
[root@tomcat-1 ~]# vim /usr/local/tomcat/conf/context.xml@@@@内容省略@@@@<Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"memcachedNodes="n1:172.25.254.10:11211,n2:172.25.254.20:11211"failoverNodes="n1"requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$"transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory"/>[root@tomcat-2 tomcat]# vim /usr/local/tomcat/conf/context.xml@@@@内容省略@@@@<Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"memcachedNodes="n1:172.25.254.10:11211,n2:172.25.254.20:11211"failoverNodes="n2"requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$"transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory"/>
修改nginx配置
[root@Nginx ~]# vim /usr/local/nginx/conf.d/vhosts.confupstream tomcat {hash $cookie_JSESSIONID;server 172.25.254.10:8080;server 172.25.254.20:8080;}server {listen 80;server_name lee.timinglee.org;root /webdataw/nginx/timinglee.org/lee;access_log /var/log/nginx/access.log;error_log /var/log/nginx/error.log;try_files $uri $uri.html $uri/index.html /error/default.html;}location ~ \.jsp$ {proxy_pass http://tomcat;}
查看memcached的版本
要去找合适版本的插件
安装
要把插件放进tomcat库里
查看tomcat的插件存储位置
服务重启成功后IP会发生变化
failoverNodes:当一个服务器的tomcat出故障时,另一台会要找自己的memcached的,因为自己的里面也是有对端的数据的
重启服务
测试:
20tomcat挂了
这里会有之前数据的原因是,20往10的memcached里存的数据,20挂了,10会在memcached里读取数据