Java 基础 WEB架构 Web资源和访问
PC 端或移动端浏览器访问
从静态服务器请求HTML、CSS、JS等文件发送到浏览器端,浏览器端接收后渲染在浏览器上
从图片服务器请求图片资源显示
从业务服务器访问动态内容,动态内容是请求后有后台服务访问数据库后得到的,最终返回到浏览器端
手机 App 访问
内置了HTML和JS文件,不需要从静态WEB服务器下载 JS 或 HTML。为的就是减少文件的发送,现代前端开发使用的JS文件太多或太大了
有必要就从图片服务器请求图片,从业务服务器请求动态数据
客户需求多样,更多的内容还是需要由业务服务器提供,业务服务器往往都是由一组服务器组成。
后台应用架构
单体架构
传统架构(单机系统),一个项目一个工程:比如商品、订单、支付、库存、登录、注册等等,统一部署,一个进程
all in one的架构方式,把所有的功能单元放在一个应用里。然后把整个应用部署到一台服务器上。如果负载能力不行,将整个应用进行水平复制,进行扩展,然后通过负载均衡实现访问。
Java实现:JSP、Servlet,打包成一个jar、war文件部署
易于开发和测试:也十分方便部署;当需要扩展时,只需要将war复制多份,然后放到多个服务器上,再做个负载均衡就可以了。
如果某个功能模块出问题,有可能全站不可访问,修改Bug后、某模块功能修改或升级后,需要停掉整个服务,重新整体重新打包、部署这个应用war包,功能模块相互之间耦合度高,相互影响,不适合当今互联网业务功能的快速迭代。
特别是对于一个大型应用,我们不可能吧所有内容都放在一个应用里面,我们如何维护、如何分工合作都是问题。如果项目庞大,管理难度大
web应用服务器:开源的tomcat、jetty、glassfish。商用的有weblogic、websphere、Jboss
微服务 1 https://www.martinfowler.com/microservices/
1 2 3 4 5 6 7 8 9 10 In short, the microservice architectural style is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery. There is a bare minimum of centralized management of these services, which may be written in different programming languages and use different data storage technologies. 简而言之,微服务架构风格是一种将单个应用程序开发为一组小服务的方法,每个小服务都在自己的进程中运行,并与轻量级 机制(通常是 HTTP 资源 API)进行通信。 这些服务是围绕业务能力构建的,并且可以通过完全自动化的部署机制独立部 署。 这些服务的集中管理极少,可以用不同的编程语言编写并使用不同的数据存储技术。 -- James Lewis and Martin Fowler (2014)
属于SOA(Service Oriented Architecture)的子集,SOA可以认为面向服务的1.0版本,微服务可以认为是面向服务的2.0版本,SOA的服务之间通过ESB(Enterprise Service Bus)进行通信,则ESB的单点依赖和商业ESB的费用问题反而成为了所有服务的瓶颈
微服务化的核心就是将传统的一站式应用,根据业务拆分成一个一个的服务,彻底去掉耦合,每一个微服务提供单个业务功能,一个服务只做一件事。每个服务都围绕着具体业务进行构建,并且能够被独立地部署到生产环境、类生产环境等
从技术角度讲就是一种小而独立的处理过程,类似与进程的概念,能够自行单独启动或销毁
微服务架构(分布式系统),各个模块/服务,各自独立出来,”让专业的人干专业的事”,独立部署。分布式系统中,不同的服务可以使用各自独立的数据库。
服务之间采用轻量级的通信机制(通常是基于HTTP的RESTful API)。
微服务设计的思想改变了原有的企业研发团队组织架构。传统的研发组织架构是水平架构,前端、后端、DBA、测试分别有自己对应的团队,属于水平团队组织架构。而微服务的设计思想对团队的划分有着一定的影响,使得团队组织架构的划分更倾向于垂直架构,比如用户业务是一个团队来负责,支付业务是一个团队来负责。但实际上在企业中并不会把团队组织架构拆分得这么绝对,垂直架构只是一种理想的架构
微服务的实现框架有多种,不同的应用架构,部署方式也有不同
Java
Java历史 Java原指的是印度尼西亚的爪哇岛,人口众多,盛产咖啡、橡胶等。
Java语言最早是在1991年开始设计的,最初叫Oak项目,它初衷是跑在不同机顶盒设备中的。
1993年网景公司成立。Oak项目组很快他们发现了浏览器和动态网页技术这个巨大的市场,转向WEB方向。并首先发布了可以让网页动起来的Applet技术(浏览器中嵌入运行Java字节码的技术)。
在1995年,一杯爪哇岛咖啡成就了Java这个名字。
Sun公司第一个Java公开版本1.0发布于1996年。口号是”一次编写,到处运行”(Write once,Run anywhere),跨平台运行。
1999年,SUN公司发布了第二代Java平台(Java2)。
2009年4月20日,Oracle甲骨文公司宣布将以每股9.50美元,总计74亿美金收购SUN(计算机系统)公司。2010年1月成功收购。
2010年,Java创始人之一的 James Gosling 离开了Oracle,去了Google。
2010年8月13日,Oracle在加利福尼亚地方法院起诉Google侵犯版权和专利权。Oracle声称Google侵犯了Java 37个API和部分专利。地方法院的陪审团认为未侵犯专利,且API无版权。
2016年5月26日,地方法院二审陪审团认定未侵犯版权,对37个JAVA API的重新实现受到合理使用的保护。
2017年Oracle上诉美国联邦巡回上诉法院,2018年3月27日判决Oracle胜诉,Google应赔偿近90 亿美金。
2019年1月Google想让美国最高法院撤销联邦法院裁决。谷歌表示裁决是”对软件业的毁灭性一击”。现任特朗普政府支持Oracle公司,但微软、Mozilla、 红帽支持Google。目前案件已经受理,但由于疫情推迟。有更多的企业和组织加入进来,包括IBM、计算机和通信协会、互联网协会、超过150名学者和教授。
此案如果Oracle胜诉,将在美国形成判例,将深远广泛影响软件业。例如: POSIX接口, 是商用系统UNIX的兼容接口规范。
2021 年 4 月 5 日,美国最高法院就 Oracle 起诉 Google 知识产权侵权案作出了判决,这场两大科技巨头之间长达十几年的官司终于“一锤定音”:谷歌胜,甲骨文败。最高法院的判决中包含两项关键的决议:最高法院维持下级法院的原判,并裁定 API 受版权保护。 Google 从 Java 复制 11,500 行代码的行为属于“合理使用”。
Java 组成 Java 包含下面部分:
语言、语法规范。关键字,如: if、for、class等
源代码 source code
依赖库,标准库(基础)、第三方库(针对某些应用)。底层代码太难使用且开发效率低,封装成现成的库
JVM虚拟机。将源代码编译为中间码即字节码后,再运行在JVM之上
由于各种操作系统ABI不一样,采用编译方式,需要为不同操作系统编译成相应格式的二进制程序才能运行。
1995年,Java发布Applet技术,Java程序在后台编译成字节码,发送到浏览器端,在浏览器中运行一个Applet程序,这段程序是运行在另外一个JVM进程中的。
但是这种在客户端运行Java代码的技术,会有很大的安全问题。1997年CGI技术发展起来,动态网页技术开始向后端开发转移,在后端将动态内容组织好,拼成HTML发回到浏览器端。
Java动态网页技术 servlet 本质就是一段Java程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import java.io.*;import javax.servlet.*;import javax.servlet.http.*;public class HelloWorld extends HttpServlet { private String message; public void init () throws ServletException { message = "Hello World" ; } public void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html" ); PrintWriter out = response.getWriter(); out.println("<h1>" + message + "</h1>" ); out.println("<p><a href=http://www.magedu.com>马哥教育</a>欢迎你</p>" ); } public void destroy () { } }
在Servlet中最大的问题是,HTML输出和Java代码混在一起,如果网页布局要调整,Java源代码就需要随之进行调整,对于开发人员来说就是个噩梦。
jsp(Java Server Pages) JSP本质是提供一个HTML模板,也就是在网页中预留以后填充的空,后续将Java程序运行生成的数据对HTML进行填空就可以了。如果网页布局需要调整,JAVA源代码不需要很大的调整
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <!DOCTYPE html> <html> <head> <meta charset="utf-8" > <title>jsp例子</title> </head> <body> 本行后面的内容是服务器端动态生成字符串,最后拼接在一起 <% out.println("你的 IP 地址 " + request.getRemoteAddr()); %> </body> </html>
JSP是基于Servlet实现,JSP将表现和逻辑分离,这样页面开发人员更好的注重页面表现力更好服务客户。
不过最终 JSP 还需要先转换为 Servlet的源代码.java文件(Tomcat中使用Jasper转换),只不过这个转换过程无需人工完成,是通过工具自动实现的,然后再编译成.class文件,最后才可以在JVM中运行。
比如: 浏览器第一次请求test.jsp时, Tomcat服务器会自动将test.jsp转化成test.jsp.java这么一个类,并将该文件编译成class文件。编译完毕后再运行class文件来响应浏览器的请求。如果以后访问test.jsp就不再重新编译jsp文件了,直接调用class文件来响应浏览器。后续如果Tomcat检测到JSP页面改动了的话,会重新编译
JSP类似于PHP和ASP,前端代码和后端JAVA代码混写在一起,需要前端和后端工程师在一起协作才能完成,无法做到真正的前后端分离开发
在web早期的开发中,通常采用的分为两层,视图层和模型层。
优点:架构简单,比较适合小型项目开发
缺点:JSP职责不单一,职责过重,不便于维护
MVC 如果过度使用jsp技术,jsp中既写有大量的java代码,也有html,甚至还有javascript等,造成难以维护,难以实现前后端分工协作,后来java 的web开发借鉴了MVC(Model View Controller )开发模式,
MVC是模型(Model)、视图(View)、控制器(Controller)的简写,是一种软件设计规范。是将业务逻辑、数据、显示分离的方法来组织代码。MVC主要作用是降低了视图与业务逻辑间的双向偶合。
MVC不是一种设计模式,MVC是一种架构模式。当然不同的MVC存在差异。
Model(模型):数据模型,提供要展示的数据,因此包含数据和行为,可以认为是领域模型或JavaBean组件(包含数据和行为),不过现在一般都分离开来:Value Object(数据Dao) 和 服务层(行为Service)。也就是模型提供了模型数据查询和模型数据的状态更新等功能,包括数据和业务。
View(视图):负责进行模型的展示,一般就是我们见到的用户界面,客户想看到的东西。可通过JSP实现
Controller(控制器):接收用户请求,委托给模型进行处理(状态改变),处理完毕后把返回的模型数据返回给视图,由视图负责展示。也就是说控制器做了个调度员的工作。最终表现为Servlet
最典型的MVC就是JSP + servlet + javabean的模式。
职责分析 :
Controller:控制器
Model:模型
View:视图
处理流程
用户发请求
Servlet接收请求数据,并调用对应的业务逻辑方法
业务处理完毕,返回更新后的数据给servlet
servlet转向到JSP,由JSP来渲染页面
响应给前端更新后的页面
MVC模式也有以下不足:
每次请求必须经过”控制器->模型->视图”这个流程,用户才能看到最终的展现界面,这个过程似乎有些复杂
实际上视图是依赖于模型的,换句话说,如果没有模型,视图也无法呈现出最终的效果
渲染视图过程是在服务端来完成的,最终呈现给浏览器的是带有模型的视图页面,性能无法得到很好的优化
REST 为了使数据展现过程更加直接,并且提供更好的用户体验,对MVC模式进行改进。首先从浏览器发送AJAX( Asynchronous JavaScript and XML 异步的 JavaScript 和 XML)请求,然后服务端接受该请求并返回JSON数据返回给浏览器,最后在浏览器中进行界面渲染。改进后的MVC模式如下图所示:
也就是说,我们输入的是AJAX请求,输出的是JSON数据,REST技术实现这样的功能。
REST(Representational State Transfer表述性状态转移,全称是 Resource Representational State Transfer
即资源在网络中以某种表现形式进行状态转移。
分解开来:Resource:资源,即数据
Representational:某种表现形式,比如用JSON,XML,JPEG等
State Transfer:状态变化。通过HTTP动词实现
它是Roy Fielding博士在2000年写的一篇关于软件架构风格的论文,后来国内外许多知名互联网公司纷纷开始采用这种轻量级的Web服务,大家习惯将其称为RESTful Web Services,或简称REST服务。
如果将浏览器这一端视为前端,而服务器那一端视为后端的话,可以将以上改进后的MVC模式简化为以下前后端分离模式,如下图所示:
可见,采用REST风格的架构可以使得前端关注界面展现,后端关注业务逻辑,分工明确,职责清晰。
在设计web接口的时候,REST主要是用于定义接口名,接口名一般是用名次写,不用动词,那怎么表达“获取”或者“删除”或者“更新”这样的操作呢——用请求类型来区分。
比如,我们有一个friends接口,对于“朋友”我们有增删改查四种操作,怎么定义REST接口?
增加一个朋友,uri: generalcode.cn/v1/friends 接口类型:POST
删除一个朋友,uri: generalcode.cn/va/friends 接口类型:DELETE
修改一个朋友,uri: generalcode.cn/va/friends 接口类型:PUT
查找朋友,uri: generalcode.cn/va/friends 接口类型:GET
上面我们定义的四个接口就是符合REST协议的,请注意,这几个接口都没有动词,只有名词friends,都是通过Http请求的接口类型来判断是什么业务操作。
REST就是一种设计API的模式。最常用的数据格式是JSON。由于JSON能直接被JavaScript读取,所以,以JSON格式编写的REST风格的API具有简单、易读、易用的特点。
编写API有什么好处呢?由于API就是把Web App的功能全部封装了,所以,通过API操作数据,可以极大地把前端和后端的代码隔离,使得后端代码易于测试,前端代码编写更简单。前端拿到数据只负责展示和渲染,不对数据做任何处理。后端处理数据并以JSON格式传输出去,定义这样一套统一的接口,在web,ios,android三端都可以用相同的接口,通过客户端访问API,就可以完成通过浏览器页面提供的功能,而后端代码基本无需改动。
JDK JDK和JRE JDK 和 JRE 关系
Java SE API : Java 基础类库开发接口
JRE :Java Runtime Environment缩写,指Java运行时环境, 包含 JVM + Java核心类库
JDK :Java Development Kit,即 Java 语言的软件开发工具包,JDK协议基于 JRL(JavaResearch License)协议
JVM 的各种版本 参考链接:
https://en.wikipedia.org/wiki/List_of_Java_virtual_machines
https://en.wikipedia.org/wiki/Comparison_of_Java_virtual_machines
各个公司和组织基于标准规范,开发了不同的JVM版本
SUN HotSpot
IBM J9VM
BEA JRockit
JVM 市场份额
2018年12月,由 Snyk 和 The Java Magazine 联合推出发布的 2018 JVM 生态调查报告
Oracle JDK版本
JDK也就是常说的J2SE,在1999年,正式发布了Java第二代平台,发布了三个版本:
J2SE:标准版,适用于桌面平台
J2EE:企业版,java在企业级开发所有规范的总和,共有13个大的规范,Servlet、Jsp都包含在JavaEE规范中
J2ME:微型版,适用于移动、无线、机顶盒等设备环境
2005年,Java的版本又更名为JavaSE、JavaEE、JavaME
**JDK7、JDK8、JDK11是LTS(Long Term Support)
JDK 历史版本
https://en.wikipedia.org/wiki/Java_version_history
时间-事件轴
1995年5月23日,Java语言诞生
1996年1月,第一个JDK-JDK1.0诞生
1996年4月,10个最主要的操作系统供应商申明将在其产品中嵌入JAVA技术
1996年9月,约8.3万个网页应用了JAVA技术来制作
1997年2月18日,JDK1.1发布
1997年4月2日,JavaOne会议召开,参与者逾一万人,创当时全球同类会议规模之纪录
1997年9月,JavaDeveloperConnection社区成员超过十万
1998年2月,JDK1.1被下载超过2,000,000次
1998年12月8日,JAVA2企业平台J2EE发布
1999年6月,SUN公司发布Java的三个版本:标准版、企业版和微型版(J2SE、J2EE、J2ME)
2000年5月8日,JDK1.3发布
2000年5月29日,JDK1.4发布
2001年6月5日,NOKIA宣布,到2003年将出售1亿部支持Java的手机
2001年9月24日,J2EE1.3发布
2002年2月13日,J2SE1.4发布,自此Java的计算能力有了大幅提升。
2004年9月30日18:00PM,J2SE1.5发布,是Java语言的发展史上的又一里程碑事件。为了表示这个版本的重要性,J2SE1.5更名为J2SE5.0
2005年6月,JavaOne大会召开,SUN公司公开Java SE 6。此时,Java的各种版本已经更名以取消其中的数字”2”:J2EE更名为Java EE, J2SE更名为Java SE,J2ME更名为Java ME。
2006年11月13日,SUN公司宣布Java全线采纳GNU General Public License Version 2,从而公开了Java的源代码。
JDK 版本使用情况
数据来源
1 https://www.baeldung.com/java-in-2019
收费
从2019年1月份开始,Oracle JDK 开始对 Java SE 8 之后的版本开始进行商用收费,确切的说是8u201/202 之后的版本。如果你用 Java 开发的功能如果是用作商业用途的,如果还不想花钱购买的话,能免费使用的最新版本是 8u201/202。当然如果是个人客户端或者个人开发者可以免费试用 Oracle JDK 所有的版本。
发版方式
在 JDK 9 发布之前,Oracle 的发版策略是以特性驱动的,只有重大的特性改变才会发布大版本,比如 JDK 7 到 JDK 8,中间会发多个更新版本。而从 JDK 9 开始变为以时间驱动的方式。发布周期为6个月一个大版本,比如 JDK 9 到 JDK 10,3个月一次补丁版,3年一个 LTS(长期支持版本)。
OpenJDK OpenJDK 介绍
OpenJDK是Sun公司采用GPL v2协议发布的JDK开源版本,于2009年正式发布。
官方网站:https://openjdk.java.net/projects/jdk6/
OpenJDK 7是基于JDK7的beta版开发,但为了也将Java SE 6开源,从OpenJDK7的b20构建反向分支开发,从中剥离了不符合Java SE 6规范的代码,发布OpenJDK 6。所以OpenJDK6和JDK6没什么关系,只是API兼容而已
OpenJDK使用GPL v2可以用于商业用途。目前由红帽维护。OpenJDK也有在其基础上的众多发行版,比如阿里的Dragonwell。
相对来说,Oracle jDK具有更好的响应能力和JVM性能,更加稳定。
安装 openjdk
在CentOS中,可以使用 yum 仓库安装 openjdk
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 [root@centos8 ~] [root@centos8 ~] [root@centos8 ~] openjdk version "1.8.0_232" OpenJDK Runtime Environment (build 1.8.0_232-b09) OpenJDK 64-Bit Server VM (build 25.232-b09, mixed mode) [root@centos8 ~] /usr/bin/java [root@centos8 ~] lrwxrwxrwx 1 root root 22 Feb 8 20:03 /usr/bin/java -> /etc/alternatives/java [root@centos8 ~] lrwxrwxrwx 1 root root 73 Feb 8 20:03 /etc/alternatives/java -> /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.232.b09-0.el8_0.x86_64/jre/bin/java [root@centos8 ~] java-1.8.0-openjdk-headless-1.8.0.232.b09-0.el8_0.x86_64
范例: 安装 java-11-openjdk
1 2 3 4 5 OpenJDK 64-Bit Server VM 18.9 (build 11.0.7+10-LTS, mixed mode, sharing) [root@centos8 ~] [root@centos8 ~] openjdk 11.0.7 2020-04-14 LTS OpenJDK Runtime Environment 18.9 (build 11.0.7+10-LTS)
范例: 编译运行java程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 [root@centos8 ~] [root@centos8 ~] class Hello{ public static void main(String[] args) { System.out.println("hello, world" ); } } [root@centos8 ~] [root@centos8 ~] -rw-r--r-- 1 root root 416 Oct 24 13:00 Hello.class -rw-r--r-- 1 root root 130 Aug 22 23:38 Hello.java [root@centos8 ~] Hello.class: compiled Java class data, version 52.0 (Java 1.8) [root@centos8 ~] hello, world
范例: ubuntu 安装 openjdk
1 2 3 4 5 6 7 8 9 10 11 12 [root@ubuntu1804 ~] [root@ubuntu1804 ~] [root@ubuntu1804 ~] openjdk version "1.8.0_242" OpenJDK Runtime Environment (build 1.8.0_242-8u242-b08-0ubuntu3~18.04-b08) OpenJDK 64-Bit Server VM (build 25.242-b08, mixed mode) root@ubuntu2004:~ root@ubuntu2004:~ openjdk version "11.0.9.1" 2020-11-04 OpenJDK Runtime Environment (build 11.0.9.1+1-Ubuntu-0ubuntu1.20.04) OpenJDK 64-Bit Server VM (build 11.0.9.1+1-Ubuntu-0ubuntu1.20.04, mixed mode, sharing)
安装 Oracle 官方 JDK 官方下载链接:
1 2 3 4 #注意需要注册登录后,才能下载JDK https://www.oracle.com/java/technologies/downloads/#java8 https://www.oracle.com/java/technologies/downloads/#java11 https://www.oracle.com/java/technologies/javase/javase-jdk8-downloads.html
Oracle JDK 的 rpm安装 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 [root@centos8 ~] -rw-r--r-- 1 root root 171M Feb 8 18:29 jdk-8u241-linux-x64.rpm [root@centos8 ~] [root@centos8 ~] java version "1.8.0_241" Java(TM) SE Runtime Environment (build 1.8.0_241-b07) Java HotSpot(TM) 64-Bit Server VM (build 25.241-b07, mixed mode) [root@centos8 ~] [root@centos8 ~] export JAVA_HOME=/usr/java/defaultexport PATH=$JAVA_HOME /bin:$PATH export JRE_HOME=$JAVA_HOME /jre export CLASSPATH=$JAVA_HOME /lib/:$JRE_HOME /lib/ [root@centos8 ~] [root@centos8 ~] /usr/bin/java [root@centos8 ~] lrwxrwxrwx 1 root root 22 Feb 8 18:35 /usr/bin/java -> /etc/alternatives/java [root@centos8 ~] lrwxrwxrwx 1 root root 41 Feb 8 18:35 /etc/alternatives/java -> /usr/java/jdk1.8.0_241-amd64/jre/bin/java [root@centos8 ~] [root@centos8 ~] warning: jdk-8u241-linux-x64.rpm: Header V3 RSA/SHA256 Signature, key ID ec551f03: NOKEY /usr /usr/java /usr/java/jdk1.8.0_241-amd64 /usr/java/jdk1.8.0_241-amd64/.java /usr/java/jdk1.8.0_241-amd64/.java/.systemPrefs /usr/java/jdk1.8.0_241-amd64/.java/.systemPrefs/.system.lock /usr/java/jdk1.8.0_241-amd64/.java/.systemPrefs/.systemRootModFile /usr/java/jdk1.8.0_241-amd64/.java/init.d ...... [root@centos8 ~] total 0 lrwxrwxrwx 1 root root 16 Feb 8 18:35 default -> /usr/java/latest drwxr-xr-x 8 root root 258 Feb 8 18:35 jdk1.8.0_241-amd64 lrwxrwxrwx 1 root root 28 Feb 8 18:35 latest -> /usr/java/jdk1.8.0_241-amd64
Oracle JDK的二进制文件安装 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 [root@centos8 ~] [root@centos8 ~] [root@centos8 ~] [root@centos8 ~] [root@centos8 ~] export JAVA_HOME=/usr/local/jdkexport PATH=$PATH :$JAVA_HOME /bin[root@centos8 ~] [root@centos8 ~] Neither the JAVA_HOME nor the JRE_HOME environment variable is defined At least one of these environment variable is needed to run this program [root@centos8 ~] Neither the JAVA_HOME nor the JRE_HOME environment variable is defined At least one of these environment variable is needed to run this program [root@centos8 ~] java version "1.8.0_241" Java(TM) SE Runtime Environment (build 1.8.0_241-b07) Java HotSpot(TM) 64-Bit Server VM (build 25.241-b07, mixed mode) [root@centos8 ~] /usr/local/jdk/bin/java
一键安装二进制的JDK 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 [root@ubuntu1804 ~] JDK_FILE="jdk-11.0.17_linux-x64_bin.tar.gz" JDK_DIR="/usr/local" DIR=`pwd ` 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 jdk* jdk cat > /etc/profile.d/jdk.sh <<EOF export JAVA_HOME=$JDK_DIR/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 && color 0 "JDK 安装完成" || { color 1 "JDK 安装失败" ; exit ; } } install_jdk
Tomcat 基础功能
Tomcat历史和介绍 WEB应用服务器 Web 应用服务器的使用
数据来源
1 https://www.baeldung.com/java-in-2019
商用:IBM WebSphere、Oracle WebLogic(原属于BEA公司)、Oracle Oc4j、RedHat JBoss等
开源:Tomcat、Jetty、Resin、Glassfish
Tomcat 介绍 Tomcat 服务器是一个免费的开放源代码的Web 应用服务器,属于轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,Tomcat 具有处理HTML静态资源页面的功能,它还是一个Servlet和JSP容器
起始于SUN 公司的一个Servlet的参考实现项目 Java Web Server,开发者是 James Duncan Davidson,在1999年,将项目贡献给了apache软件基金会(ASF),和ASF现有的项目 JServ 合并,并 开源成为顶级项目
Tomcat 仅仅实现了Java EE规范中与Servlet、JSP相关的类库,是JavaEE不完整实现。
著名图书出版商O’Reilly约稿该项目成员Davidson希望使用一个公猫作为封面,但是公猫已经被使用,书出版后封面是一只雪豹。
《Tomcat权威指南》封面如下
1999年发布初始版本是Tomcat 3.0,实现了Servlet 2.2 和 JSP 1.1规范。
Tomcat 4.x发布时,内建了Catalina(Servlet容器)和 Jasper(JSP engine)等
当前 Tomcat 的正式版本已经更新到 9.0.x 版本,但当前企业中主流版本为 8.x 和 7.x
官网: http://tomcat.apache.org/
官网文档: https://tomcat.apache.org/tomcat-8.5-doc/index.html
帮助文档:
https://cwiki.apache.org/confluence/display/tomcat/
https://cwiki.apache.org/confluence/display/tomcat/FAQ
Tomcat 各版本区别 官方文档:https://tomcat.apache.org/whichversion.html
Servlet Spec
JSP Spec
EL Spec
WebSocket Spec
Authentication Spec (JASPIC)
Apache Tomcat Version
Final1 Released Version
Supported Java Versions
EOL Date
5.0
3.0
4.0
2.0
2.0
10.0.x (superseded)
10.0.27 (superseded)
8 and later
2022-10-31
3.1
2.3
3.0
1.1
1.1
8.5.x (EOL )
8.5.100 (EOL )
7 and later
2024-03-31
3.1
2.3
3.0
1.1
N/A
8.0.x (superseded)
8.0.53 (superseded)
7 and later
2018-06-30
3.0
2.2
2.2
1.1
N/A
7.0.x (archived)
7.0.109 (archived)
6 and later (7 and later for WebSocket)
2021-03-31
2.5
2.1
2.1
N/A
N/A
6.0.x (archived)
6.0.53 (archived)
5 and later
2016-12-31
2.4
2.0
N/A
N/A
N/A
5.5.x (archived)
5.5.36 (archived)
1.4 and later
2012-09-31
2.3
1.2
N/A
N/A
N/A
4.1.x (archived)
4.1.40 (archived)
1.3 and later
2009-06-25
2.2
1.1
N/A
N/A
N/A
3.3.x (archived)
3.3.2 (archived)
1.1 and later
2004-03-09
Tomcat 安装 基于包安装 Tomcat CentOS 包安装 tomcat 范例: Rocky 基于EPEL源安装 tomcat
1 2 3 4 [root@rocky8 ~] [root@rocky8 ~] [root@rocky8 ~]
CentOS 8 包仓库中目前还没有提供tomcat相关包
1 2 3 [root@centos8 ~] Last metadata expiration check: 1:25:35 ago on Wed 15 Jul 2020 09:01:28 AM CST. Error: No matching Packages to list
CentOS 7 yum仓库源中自带的Tomcat 7.0版本安装,此方式安装tomcat版本较低,不推荐
范例:在CentOS 7 上安装 tomcat
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 [root@centos7 ~] [root@centos7 ~] [root@centos7 ~] Created symlink from /etc/systemd/system/multi-user.target.wants/tomcat.service to /usr/lib/systemd/system/tomcat.service. [root@centos7 ~] LISTEN 0 1 [::ffff:127.0.0.1]:8005 [::]:* LISTEN 0 100 [::]:8080 [::]:* [root@centos7 ~] tomcat:x:53:53:Apache Tomcat:/usr/share/tomcat:/sbin/nologin [root@centos7 ~] tomcat 1328 0.4 11.2 2298188 112004 ? Ssl 21:32 0:11 /usr/lib/jvm/jre/bin/java -classpath ···
打开浏览器访问:http://tomcat:8080/
Ubuntu 包安装 tomcat 范例: Ubuntu22.04 安装 tomcat9
1 2 3 4 5 [root@ubuntu2204 ~] [root@ubuntu2204 ~] [root@ubuntu2204 ~] LISTEN 0 100 *:8080 *:*
范例:Ubuntu20.04安装tomcat9
1 2 3 [root@ubuntu2004 ~] [root@ubuntu2004 ~] [root@ubuntu2004 ~]
范例: Ubuntu18.04安装 tomcat8
1 2 [root@ubuntu1804 ~] [root@ubuntu1804 ~]
二进制安装 Tomcat CentOS 7 的yum源的tomcat版本老旧,而CentOS8 yum源里无tomcat 目前比较主流的Tomcat是8.5.X版本,推荐从Apache官网下载二进制tomcat包进行安装,此为生产常用方式
下载并安装 注意: 安装tomcat 前必须先部署JDK
官方和镜像站点下载:
1 2 https://tomcat.apache.org/download-80.cgi https://mirrors.tuna.tsinghua.edu.cn/apache/tomcat/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 [root@centos8 ~] [root@centos8 ~] [root@centos8 ~] [root@centos8 local ] [root@centos8 ~] [root@centos8 ~] [root@centos8 ~] /usr/local/tomcat/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/usr/local/jdk/bin:/root/bin [root@centos8 ~] Using CATALINA_BASE: /usr/local/tomcat Using CATALINA_HOME: /usr/local/tomcat Using CATALINA_TMPDIR: /usr/local/tomcat/temp Using JRE_HOME: /usr/local/jdk Using CLASSPATH: /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar Usage: catalina.sh ( commands ... ) commands: debug Start Catalina in a debugger debug -security Debug Catalina with a security manager jpda start Start Catalina under JPDA debugger run Start Catalina in the current window run -security Start in the current window with security manager start Start Catalina in a separate window start -security Start in a separate window with security manager stop Stop Catalina, waiting up to 5 seconds for the process to end stop n Stop Catalina, waiting up to n seconds for the process to end stop -force Stop Catalina, wait up to 5 seconds and then use kill -KILL if still running stop n -force Stop Catalina, wait up to n seconds and then use kill -KILL if still running configtest Run a basic syntax check on server.xml - check exit code for result version What version of tomcat are you running? Note: Waiting for the process to end and use of the -force option require that $CATALINA_PID is defined [root@centos8 ~] Using CATALINA_BASE: /usr/local/tomcat Using CATALINA_HOME: /usr/local/tomcat Using CATALINA_TMPDIR: /usr/local/tomcat/temp Using JRE_HOME: /usr/local/jdk/jre Using CLASSPATH: /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar Using CATALINA_OPTS: Server version: Apache Tomcat/8.5.59 Server built: Oct 6 2020 16:57:18 UTC Server number: 8.5.59.0 OS Name: Linux OS Version: 4.18.0-193.el8.x86_64 Architecture: amd64 JVM Version: 1.8.0_261-b12 JVM Vendor: Oracle Corporation [root@centos8 ~] Using CATALINA_BASE: /usr/local/tomcat Using CATALINA_HOME: /usr/local/tomcat Using CATALINA_TMPDIR: /usr/local/tomcat/temp Using JRE_HOME: /usr/local/jdk/jre Using CLASSPATH: /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar Tomcat started. [root@centos8 ~] State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 128 0.0.0.0:22 0.0.0.0:* LISTEN 0 100 *:8080 *:* LISTEN 0 128 [::]:22 [::]:* LISTEN 0 1 [::ffff:127.0.0.1]:8005 *:* LISTEN 0 100 *:8009 *:* [root@centos8 ~] root 12994 34.1 9.4 2155140 76912 pts/0 Sl 22:38 0:02 /usr/local/jdk/jre/bin/java [root@centos8 ~] Using CATALINA_BASE: /usr/local/tomcat Using CATALINA_HOME: /usr/local/tomcat Using CATALINA_TMPDIR: /usr/local/tomcat/temp Using JRE_HOME: /usr/local/jdk/jre Using CLASSPATH: /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar [root@centos8 ~] [root@centos8 ~] State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 128 0.0.0.0:22 0.0.0.0:* LISTEN 0 128 [::]:22 [::]:* [root@centos8 ~] Using CATALINA_BASE: /usr/local/tomcat Using CATALINA_HOME: /usr/local/tomcat Using CATALINA_TMPDIR: /usr/local/tomcat/temp Using JRE_HOME: /usr/local/jdk/jre Using CLASSPATH: /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar Tomcat started. [root@centos8 ~] State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 128 0.0.0.0:22 0.0.0.0:* LISTEN 0 100 *:8080 *:* LISTEN 0 128 [::]:22 [::]:* LISTEN 0 1 [::ffff:127.0.0.1]:8005 *:* LISTEN 0 100 *:8009 *:* [root@centos8 ~] Using CATALINA_BASE: /usr/local/tomcat Using CATALINA_HOME: /usr/local/tomcat Using CATALINA_TMPDIR: /usr/local/tomcat/temp Using JRE_HOME: /usr/local/jdk/jre Using CLASSPATH: /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar
打开浏览器访问:http://tomcat:8080/,正常可以看到以下界面
扩展知识:tomcat 和 catalina 关系
1 2 3 4 5 6 7 8 9 10 11 12 13 Tomcat的servlet容器在4.X版本中被Craig McClanahan(Apache Struts项目的创始人,也是Tomcat 的 Catalina 的架构师)重新设计为Catalina.即Catalina就是servlet容器。 Tomcat的核心分为3个部分: (1)Web容器:处理静态页面; (2)JSP容器:把jsp页面翻译成一般的 servlet (3)catalina: 是一个servlet容器,用于处理servlet Catalina是美国西海岸靠近洛杉矶22英里的一个小岛,因为其风景秀丽而著名,曾被评为全美最漂亮的小 岛。Servlet运行模块的最早开发者Craig McClanahan因为喜欢Catalina岛,故以Catalina命名他所开 这个模块,另外在开发的早期阶段,Tomcat是被搭建在一个叫Avalon的服务器框架上,而Avalon则是 Catalina岛上的一个小镇的名字,于是想一个与小镇名字相关联的单词也是自然而然。设计者估计是想把 tomcat设计成最美的轻量级容器吧。下图为该小岛。
配置 tomcat 自启动的 service 文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 [root@centos8 ~] [root@centos8 ~] [root@centos8 ~] JAVA_HOME=/usr/local/jdk Mar 15 14:30:09 centos8 startup.sh[1530]: Neither the JAVA_HOME nor the JRE_HOME environment variable is defined Mar 15 14:30:09 centos8 startup.sh[1530]: At least one of these environment variable is needed to run this program [root@centos8 ~] [root@centos8 ~] [root@centos8 ~] [Unit] Description=Tomcat After=syslog.target network.target [Service] Type=forking EnvironmentFile=/usr/local/tomcat/conf/tomcat.conf Environment=JAVA_HOME=/usr/local/jdk 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 [root@centos8 ~] [root@centos8 ~] Created symlink /etc/systemd/system/multi-user.target.wants/tomcat.service → /usr/lib/systemd/system/tomcat.service. [root@centos8 ~] ● tomcat.service - Tomcat Loaded: loaded (/usr/lib/systemd/system/tomcat.service; enabled; vendor preset: disabled) Active: active (running) since Sat 2020-02-08 23:37:02 CST; 5s ago Process: 14312 ExecStart=/usr/local/tomcat/bin/startup.sh (code=exited, status=0/SUCCESS) Main PID: 14320 (java) Tasks: 43 (limit : 4895) Memory: 64.2M CGroup: /system.slice/tomcat.service └─14320 /usr/local/jdk/jre/bin/java - Djava.util.logging.config.file=/usr/local/tomcat/conf/logging> Feb 08 23:37:02 centos8.localdomain systemd[1]: Starting Tomcat... Feb 08 23:37:02 centos8.localdomain systemd[1]: Started Tomcat. [root@centos8 ~] Mar 15 14:32:13 centos8 systemd[1]: Reloading. Mar 15 14:32:23 centos8 systemd[1]: Starting Tomcat... Mar 15 14:32:23 centos8 startup.sh[1575]: Tomcat started. Mar 15 14:32:23 centos8 systemd[1]: Started Tomcat.
实战案例: 一键安装 tomcat 脚本 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 #!/bin/bash TOMCAT_VERSION=9.0.69 JDK_VERSION=8u351 TOMCAT_FILE="apache-tomcat-${TOMCAT_VERSION} .tar.gz" JDK_FILE="jdk-${JDK_VERSION} -linux-x64.tar.gz" JDK_DIR="/usr/local" TOMCAT_DIR="/usr/local" DIR=`pwd ` 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 jdk* jdk cat > /etc/profile.d/jdk.sh <<EOF export JAVA_HOME=$JDK_DIR/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 && 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-*/ tomcatecho "PATH=$TOMCAT_DIR /tomcat/bin:" '$PATH' > /etc/profile.d/tomcat.shid tomcat &> /dev/null || useradd -r -s /sbin/nologin tomcatcat > $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
Tomcat 的文件结构和组成 目录结构
目录
说明
bin
服务启动、停止等相关程序和文件
conf
配置文件
lib
库目录
logs
日志目录
webapps
应用程序,应用部署目录
work
jsp编译后的结果文件,建议提前预热访问,升级应用后,删除此目录数据才能更新
范例:查看tomcat相关目录和文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 [root@centos8 tomcat] /usr/local/tomcat [root@centos8 tomcat] bin conf lib logs README.md RUNNING.txt webapps BUILDING.txt CONTRIBUTING.md LICENSE NOTICE RELEASE-NOTES temp work [root@centos8 tomcat] bootstrap.jar commons-daemon-native.tar.gz makebase.sh tomcat-juli.jar catalina.bat configtest.bat setclasspath.bat tomcat-native.tar.gz catalina.sh configtest.sh setclasspath.sh tool-wrapper.bat catalina-tasks.xml daemon.sh shutdown.bat tool-wrapper.sh ciphers.bat digest.bat shutdown.sh version.bat ciphers.sh digest.sh startup.bat version.sh commons-daemon.jar makebase.bat startup.sh [root@centos8 tomcat] Catalina context.xml logging.properties tomcat-users.xml catalina.policy jaspic-providers.xml server.xml tomcat-users.xsd catalina.properties jaspic-providers.xsd tomcat.conf web.xml [root@centos8 tomcat] annotations-api.jar jasper-el.jar tomcat-i18n-cs.jar tomcat-jdbc.jar catalina-ant.jar jasper.jar tomcat-i18n-de.jar tomcat-jni.jar catalina-ha.jar jaspic-api.jar tomcat-i18n-es.jar tomcat-util.jar catalina.jar jsp-api.jar tomcat-i18n-fr.jar tomcat-util-scan.jar catalina-ssi.jar servlet-api.jar tomcat-i18n-ja.jar tomcat-websocket.jar catalina-storeconfig.jar tomcat-api.jar tomcat-i18n-ko.jar websocket-api.jar catalina-tribes.jar tomcat-coyote-ffm.jar tomcat-i18n-pt-BR.jar ecj-4.20.jar tomcat-coyote.jar tomcat-i18n-ru.jar el-api.jar tomcat-dbcp.jar tomcat-i18n-zh-CN.jar [root@centos8 tomcat] catalina.2025-03-05.log host-manager.2025-03-05.log localhost_access_log.2025-03-05.txt catalina.out localhost.2025-03-05.log manager.2025-03-05.log [root@centos8 tomcat] docs examples host-manager manager ROOT [root@centos8 tomcat] Catalina [root@centos8 tomcat] localhost [root@centos8 tomcat] docs examples host-manager manager ROOT [root@centos8 tomcat] total 0 68039883 drwxr-x--- 2 tomcat tomcat 6 Feb 9 11:02 docs 135579640 drwxr-x--- 2 tomcat tomcat 6 Feb 9 11:02 examples 202681358 drwxr-x--- 2 tomcat tomcat 6 Feb 9 11:02 host-manager 571365 drwxr-x--- 2 tomcat tomcat 6 Feb 9 11:02 manager 571364 drwxr-x--- 2 tomcat tomcat 6 Feb 9 11:02 ROOT [root@centos8 tomcat] total 4 202681088 drwxr-x--- 15 tomcat tomcat 4096 Feb 9 11:02 docs 202681094 drwxr-x--- 6 tomcat tomcat 83 Feb 9 11:02 examples 571165 drwxr-x--- 5 tomcat tomcat 87 Feb 9 11:02 host-manager 68039687 drwxr-x--- 5 tomcat tomcat 103 Feb 9 11:02 manager 68039663 drwxr-x--- 3 tomcat tomcat 283 Feb 9 11:02 ROOT [root@centos8 tomcat] work/Catalina/localhost/ ├── docs ├── examples ├── host-manager ├── manager └── ROOT 5 directories, 0 files [root@centos8 tomcat] [root@centos8 tomcat] work/Catalina/localhost/ ├── docs ├── examples ├── host-manager ├── manager └── ROOT └── org └── apache └── jsp ├── index_jsp.class └── index_jsp.java 8 directories, 2 files [root@centos8 tomcat] work/Catalina/localhost/ROOT/org/apache/jsp/index_jsp.java /* * Generated by the Jasper component of Apache Tomcat * Version: Apache Tomcat/8.5.50 * Generated at: 2020-02-09 03:20:20 UTC * Note: The last modified time of this file was set to * the last modified time of the source file after * generation to assist with modification tracking. */ package org.apache.jsp; import javax.servlet.*; import javax.servlet.http.*; import javax.servlet.jsp.*; public final class index_jsp extends org.apache.jasper.runtime.HttpJspBase implements org.apache.jasper.runtime.JspSourceDependent, org.apache.jasper.runtime.JspSourceImports { private static final javax.servlet.jsp.JspFactory _jspxFactory = javax.servlet.jsp.JspFactory.getDefaultFactory(); private static java.util.Map<java.lang.String,java.lang.Long> _jspx_dependants;
配置文件 配置文件说明 官方帮助文档:http://tomcat.apache.org/tomcat-8.5-doc/index.html
在tomcat安装目录下的 conf 子目录中,有以下的 tomcat 的配置文件
文件名
说明
server.xml
主配置文件
web.xml
每个webapp只有“部署”后才能被访问,它的部署方式通常由web.xml进行定义,其存放位置为WEB-INF/目录中;此文件为所有的webapps提供默认部署相关的配置,每个web应用也可以使用专用配置文件,来覆盖全局文件
context.xml
用于定义所有web应用均需加载的Context配置,此文件为所有的webapps提供默认配置,每个web应用也可以使用自已专用的配置,它通常由专用的配置文件context.xml来定义,其存放位置为WEB-INF/目录中,覆盖全局的文件
tomcat-users.xml
用户认证的账号和密码文件
catalina.policy
当使用security选项启动tomcat时,用于为tomcat设置安全策略
catalina.properties
Tomcat 环境变量的配置,用于设定类加载器路径,以及一些与JVM调优相关参数
logging.properties
Tomcat 日志系统相关的配置,可以修改日志级别和日志路径等
注意:配置文件大小写敏感
范例:查看配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 [root@centos8 conf] /usr/local/tomcat/conf [root@centos8 conf] Catalina context.xml logging.properties tomcat-users.xml catalina.policy jaspic-providers.xml server.xml tomcat-users.xsd catalina.properties jaspic-providers.xsd tomcat.conf web.xml [root@centos8 conf] 167 server.xml 4726 web.xml 30 context.xml 44 tomcat-users.xml 271 catalina.policy 214 catalina.properties 75 logging.properties 5527 total
日志文件 参考文档: https://cwiki.apache.org/confluence/display/TOMCAT/Logging
日志格式: https://tomcat.apache.org/tomcat-9.0-doc/config/valve.html#Access_Logging
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 %a - 远程 IP 地址。 另请参阅下文。%{xxx}a %A - 本地 IP 地址 %b - 发送的字节数,不包括 HTTP 标头,如果为零,则为“-” %B - 发送的字节数,不包括 HTTP 标头 %D - 处理请求所用的时间(以毫秒为单位)。注意:在 httpd %D 是微秒。行为将与 httpd 保持一致 在 Tomcat 10 及更高版本中。 %F - 提交响应所用的时间(以毫秒为单位) %h - 远程主机名(如果连接器为 false ,则为 IP 地址)enableLookups %H - 请求协议 %I - 当前请求线程名称(稍后可以与堆栈跟踪进行比较) %l - 来自 identd 的远程逻辑用户名(始终返回 '-' ) %m - 请求方法(GET、POST 等) %p - 收到此请求的本地端口。 另请参阅下文。%{xxx}p %q - 查询字符串(如果存在,则以 '?' 开头) %r - 请求的第一行(方法和请求 URI) %s - 响应的 HTTP 状态代码 %s - 用户会话 ID %t - 日期和时间,采用通用日志格式 %T - 处理请求所用的时间(以秒为单位)。注意:这个 值具有毫秒级分辨率,而在 httpd 中,它具有 第二个分辨率。行为将与 httpd 保持一致 在 Tomcat 10 及更高版本中。 %u - 已验证的远程用户(如果有),否则为“-”(如果需要,则转义) %U - 请求的 URL 路径 %v - 本地服务器名称 %X - 响应完成时的连接状态: X= 连接在响应完成之前中止。 += 发送响应后,连接可能会保持活动状态。 -= 发送响应后,连接将关闭。 %{xxx}a 写入远程地址(客户端)() 或 连接对等体地址 (xxx==remotexxx=peer) %{xxx}我写入带有 name 的传入标头的值(如果需要,请转义)xxx %{xxx}o 写入带有 name 的传出标头的值(如果需要,请转义)xxx %{xxx}c 写入名称为 (如果需要,请以逗号分隔和转义)xxx %{xxx}r 写入带有 name 的 ServletRequest 属性的值(如果需要,则转义,如果 request 为 null,则为 value)xxx?? %{xxx}s 写入带有 name 的 HttpSession 属性的值(如果需要,则转义,如果 request 为 null,则为 value)xxx?? %{xxx}p 写入本地(服务器)端口 () 或 远程(客户端)端口 (xxx==localxxx=remote) %{xxx}t 写入时间戳,并使用 增强的 SimpleDateFormat 模式xxx
范例: tomcat的访问日志格式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 [root@centos8 ~] Documentation at: /docs/config/valve.html Note: The pattern used is equivalent to using pattern="common" --> <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="localhost_access_log" suffix=".txt" pattern="%h %l %u %t "%r" %s %b" /> </Host> </Engine> </Service> </Server> [root@centos8 ~] 10.0.0.1 - - [14/Jul/2020:09:08:46 +0800] "GET / HTTP/1.1" 200 11215 10.0.0.1 - - [14/Jul/2020:09:08:46 +0800] "GET /tomcat.css HTTP/1.1" 200 5581 10.0.0.1 - - [14/Jul/2020:09:08:46 +0800] "GET /tomcat.png HTTP/1.1" 200 5103 10.0.0.1 - - [14/Jul/2020:09:08:46 +0800] "GET /bg-nav.png HTTP/1.1" 200 1401 10.0.0.1 - - [14/Jul/2020:09:08:46 +0800] "GET /bg-upper.png HTTP/1.1" 200 3103 10.0.0.1 - - [14/Jul/2020:09:08:46 +0800] "GET /asf-logo-wide.svg HTTP/1.1" 200 27235 10.0.0.1 - - [14/Jul/2020:09:08:46 +0800] "GET /bg-middle.png HTTP/1.1" 200 1918 10.0.0.1 - - [14/Jul/2020:09:08:46 +0800] "GET /bg-button.png HTTP/1.1" 200 713 10.0.0.1 - - [14/Jul/2020:09:08:46 +0800] "GET /favicon.ico HTTP/1.1" 200 21630
范例: tomcat日志实现json格式的访问日志
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 [root@centos8 ~] ....... <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="localhost_access_log" suffix=".txt" pattern="{"clientip":"%h","ClientUser":"%l","authenticated":"%u","AccessTime":"%t","method":"%r","status":"%s","SendBytes":"%b","Query?string":"%q","partner":"%{Referer}i","AgentVersion":"%{User-Agent}i"}" /> <!-- pattern="%h %l %u %t "%r" %s %b" /> --> </Host> </Engine> </Service> </Server> [root@centos8 ~] [root@centos8 ~] {"clientip" :"10.0.0.100" ,"ClientUser" :"-" ,"authenticated" :"-" ,"AccessTime" :"[18/Aug/2020:18:03:46 +0800]" ,"method" :"GET / HTTP/1.0" ,"status" :"200" ,"SendBytes" :"11136" ,"Query?string" :"" ,"partner" :"-" ,"AgentVersion" :"ApacheBench/2.3" } [root@rocky8 ~] [root@rocky8 ~] { "clientip" : "10.0.0.1" , "ClientUser" : "-" , "authenticated" : "-" , "AccessTime" : "[11/Mar/2022:09:26:53 +0800]" , "method" : "GET / HTTP/1.1" , "status" : "200" , "SendBytes" : "11165" , "Query?string" : "" , "partner" : "-" , "AgentVersion" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36 Edg/99.0.1150.36" }
组件 组件分层和分类 顶级组件
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
Tomcat 内部组成 由上述组件就构成了Tomcat,如下图
名称
说明
Server
服务器,Tomcat 运行的进程实例,一个Server中可以有多个service,但通常就一个
Service
服务,用来组织Engine和Connector的对应关系,一个service中只有一个Engine
Connector
连接器,负责客户端的HTTP、HTTPS、AJP等协议连接。一个Connector只属于某一个Engine
Engine
即引擎,用来响应并处理用户请求。一个Engine上可以绑定多个Connector
Host
即虚拟主机,可以实现多虚拟主机,例如使用不同的主机头区分
Context
应用的上下文,配置特定url路径映射和目录的映射关系:url => directory
每一个组件都由一个Java“类”实现,这些组件大体可分为以下几个类型:
1 2 3 4 5 6 顶级组件:Server 服务类组件:Service 连接器组件:http, https, ajp(apache jserv protocol) 容器类:Engine, Host, Context 被嵌套类:valve, logger, realm, loader, manager, ... 集群类组件:listener, cluster, ...
范例: 查看类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 [root@centos8 ~] <Listener className="org.apache.catalina.startup.VersionLoggerListener" /> <Listener className="org.apache.catalina.security.SecurityListener" /> <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" /> <UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" /> <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster" /> <Realm className="org.apache.catalina.realm.LockOutRealm" > <Realm className="org.apache.catalina.realm.UserDatabaseRealm" <Valve className="org.apache.catalina.authenticator.SingleSignOn" /> <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
核心组件
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?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 port ="8009" protocol ="AJP/1.3" redirectPort ="8443" /> <Engine name ="Catalina" defaultHost ="localhost" > <Host name ="localhost" appBase ="webapps" unpackWARs ="true" autoDeploy ="true" > <Context > <Context /> </Host > </Engine > </Service > </Server >
tomcat 处理请求过程
假设来自客户的请求为:http://localhost:8080/test/index.jsp
浏览器端的请求被发送到服务端端口8080,Tomcat进程监听在此端口上。通过侦听的HTTP/1.1 Connector获得此请求。
Connector把该请求交给它所在的Service的Engine来处理,并等待Engine的响应
Engine获得请求localhost:8080/test/index.jsp,遍历它所有虚拟主机Host
Engine匹配到名为localhost的Host。如果匹配不到,就把请求交给该Engine中的defaultHost处理
localhost Host获得请求/test/index.jsp,匹配它所拥有的所有Context
Host匹配到路径为/test的Context
path=/test的Context获得请求index.jsp,在它的mapping table中寻找对应的servlet
Context匹配到URL PATTERN为 *.jsp 的servlet,对应于JspServlet类构造HttpServletRequest对象和HttpServletResponse对象,作为参数调用JspServlet的doGet或doPost方法。
Context把执行完了之后的HttpServletResponse对象返回给Host
Host把HttpServletResponse对象返回给Engine
Engine把HttpServletResponse对象返回给Connector
Connector把HttpServletResponse对象返回给浏览器端
Java 应用部署 tomcat的根目录结构 Tomcat中默认网站根目录是$CATALINA_BASE/webapps/
在Tomcat中部署主站应用程序和其他应用程序,和之前WEB服务程序不同。
nginx
假设在nginx中部署2个网站应用eshop、forum,假设网站根目录是/data/nginx/html,那么部署可以是这样的。
eshop解压缩所有文件放到 /data/nginx/html/ 目录下,forum 的文件放在 /data/nginx/html/forum/ 下。
最终网站链接有以下对应关系
1 2 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
最终网站链接有以下对应关系
1 2 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目录作为 主站目录。
范例: 主页目录和编码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 [root@centos8 ~] <h1>马哥教育</h1> [root@centos8 ~] HTTP/1.1 200 Accept-Ranges: bytes ETag: W/"22-1594212097000" Last-Modified: Wed, 08 Jul 2020 12:41:37 GMT Content-Type: text/html Content-Length: 22 Date: Wed, 08 Jul 2020 13:06:03 GMT [root@centos8 ~] HTTP/1.1 200 OK Date: Wed, 08 Jul 2020 13:07:57 GMT Server: Apache/2.4.37 (centos) Last-Modified: Wed, 08 Jul 2020 12:59:55 GMT ETag: "16-5a9edaf39d274" Accept-Ranges: bytes Content-Length: 22 Content-Type: text/html; charset=UTF-8
1 2 3 4 5 6 7 8 [root@centos8 ~] <html> <head > <meta http-equiv=Content-Type content="text/html;charset=utf-8" > <title>tomcat</title> </head> <h1>马哥教育</h1>
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包
主页设置 全局配置实现修改默认主页文件 默认情况下 tomcat 会在$CATALINA_BASE/webapps/ROOT/目录下按以下次序查找文件,找到第一个则进行显示
index.html
index.htm
index.jsp
可以通过修改 $CATALINA_BASE/conf/web.xml 中的下面 标签 内容修改默认页文件
范例: 修改默认主页文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 [root@centos8 tomcat] /usr/local/tomcat [root@centos8 tomcat] [root@centos8 tomcat] <h1>www.magedu.org</h1> [root@centos8 tomcat] <!-- here, so be sure to include any of the default values that you wish --> <!-- to use within your application. --> <welcome-file-list> <welcome-file>index.html</welcome-file> <welcome-file>index.htm</welcome-file> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app> [root@centos8 tomcat] [root@centos8 tomcat] <!-- here, so be sure to include any of the default values that you wish --> <!-- to use within your application. --> <welcome-file-list> <welcome-file>index.jsp</welcome-file> <welcome-file>index.htm</welcome-file> <welcome-file>index.html</welcome-file> </welcome-file-list> </web-app> [root@centos8 tomcat] [root@centos8 tomcat]
WebApp的专用配置文件 将上面主配置文件conf/web.xml中的标签内容,复制 到/usr/local/tomcat/webapps/ROOT/WEB-INF/web.xml中,如下所示:
范例: 针对主站点根目录设置专用配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 [root@centos8 tomcat] [root@centos8 tomcat] <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1" metadata-complete="true" > <display-name>Welcome to Tomcat</display-name> <description> Welcome to Tomcat </description> <welcome-file-list> <welcome-file>index.html</welcome-file> <welcome-file>index.htm</welcome-file> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app> [root@centos8 tomcat]
范例: 针对特定APP目录设置专用配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 [root@centos8 tomcat] [root@centos8 tomcat] [root@centos8 tomcat] webapps/magedu/ ├── index.htm ├── index.html ├── test.html └── WEB-INF └── web.xml 1 directory, 4 files [root@centos8 tomcat] ...... <description> Welcome to Tomcat </description> <welcome-file-list> <welcome-file>test.html</welcome-file> <welcome-file>index.htm</welcome-file> <welcome-file>index.html</welcome-file> </welcome-file-list> </web-app> [root@centos8 tomcat] [root@centos7 ~] /usr/local/tomcat/webapps/magedu/test.html
配置规则:
webApp的专有配置优先于系统的全局配置
修改系统的全局配置文件,需要重新启动服务生效
修改 webApp的专有配置,无需重启即可生效
应用部署实现 WebApp应用的归档格式
.war:WebApp打包,类zip格式文件,通常包括一个应用的所有资源,比如jsp,html,配置文件等
.jar:EJB类文件的打包压缩类zip格式文件,,包括很多的class文件, 网景公司发明
.rar:资源适配器类打包文件,目前已不常用
.ear:企业级WebApp打包,目前已不常用
传统应用开发测试后,通常打包为war格式,这种文件部署到Tomcat的webapps目录下,并默认会自动解包展开和部署上线。
1 2 <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true" >
部署方式
部署Deploy:将webapp的源文件放置到目标目录,通过web.xml和context.xml文件中配置的路径就可以访问该webapp,通过类加载器加载其特有的类和依赖的类到JVM上,即:最终用户可以通过浏览器访问该应用
自动部署:Tomcat一旦发现多了一个web应用APP.war包,默认会自动把它解压缩,加载并启动起来
手动部署
冷部署:将webapp放到指定目录,才去启动Tomcat服务
热部署:Tomcat服务不停止,需要依赖manager、ant脚本、tcd(tomcat client deployer)等工具
反部署 undeploy:停止webapp运行,并从JVM上清除已经加载的类,从Tomcat应用目录中移除部署的文件
启动 start:启用 webapp 能够访问
停止 stop:禁用 webapp 不能访问,不能提供服务,但是JVM并不清除它
部署WebApp的目录结构 常见开发项目目录组成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 mkdir projects/myapp/{WEB-INF,META-INF,classes,lib} -pvmkdir : 已创建目录 "projects" mkdir : 已创建目录 "projects/myapp" mkdir : 已创建目录 "projects/myapp/WEB-INF" mkdir : 已创建目录 "projects/myapp/META-INF" mkdir : 已创建目录 "projects/myapp/classes" mkdir : 已创建目录 "projects/myapp/lib" vi projects/myapp/index.jsp cp -r projects/myapp/ /usr/local/tomcat/webapps/chown -R tomcat.tomcat /usr/local/tomcat/webapps/myapp
实战案例:手动的应用部署 部署主页目录下的应用WebApp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 [root@centos8 tomcat] [root@centos8 tomcat] <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <!DOCTYPE html> <html> <head > <meta charset="utf-8" > <title>jsp例子</title> </head> <body> 后面的内容是服务器端动态生成字符串,最后拼接在一起 <% out.println("hello jsp" ); %> <br> <%=request.getRequestURL()%> </body> </html> [root@centos8 tomcat] <!DOCTYPE html> <html> <head > <meta charset="utf-8" > <title>jsp例子</title> </head> <body> 后面的内容是服务器端动态生成字符串,最后拼接在一起 hello jsp <br> http://127.0.0.1:8080/test.jsp </body> </html> [root@centos8 tomcat] work/Catalina/localhost/ROOT/ └── org └── apache └── jsp ├── test_jsp.class └── test_jsp.java 3 directories, 2 files
部署一个子目录的应用WebApp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 [root@centos8 tomcat] /usr/local/tomcat [root@centos8 tomcat] [root@centos8 tomcat] [root@centos8 tomcat] [root@centos8 tomcat] webapps/testapp1/ └── test.jsp 0 directories, 1 file [root@centos8 tomcat] <!DOCTYPE html> <html> <head > <meta charset="utf-8" > <title>jsp例子</title> </head> <body> 后面的内容是服务器端动态生成字符串,最后拼接在一起 hello jsp <br> <%=request.getRequestURL()%> </body> </html> [root@centos8 tomcat] work/Catalina/localhost/app1/ └── org └── apache └── jsp ├── test_jsp.class └── test_jsp.java 3 directories, 2 files [root@centos8 tomcat] [root@centos8 tomcat] docs examples host-manager manager ROOT [root@centos8 tomcat] docs examples host-manager manager ROOT
实战案例:自动的应用部署war包 制作应用的war包文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 [root@centos8 ~] test.html test.jsp [root@centos8 ~] <h1>This is test html </h1> [root@centos8 ~] <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <!DOCTYPE html> <html> <head > <meta charset="utf-8" > <title>jsp例子</title> </head> <body> 后面的内容是服务器端动态生成字符串,最后拼接在一起 <% out.println("test jsp" ); %> <br> <%=request.getRequestURL()%> </body> </html> [root@centos8 ~] [root@centos8 app2] added manifest adding: test.html(in = 28) (out= 27)(deflated 3%) adding: test.jsp(in = 329) (out= 275)(deflated 16%) [root@centos8 app2] [root@centos8 app2] test.html test.jsp [root@centos8 app2] [root@centos8 data] app2 app2.war [root@centos8 data] app2.war: Java archive data (JAR) [root@centos8 data]
自动应用部署上面的war包 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 [root@centos8 tomcat] /usr/local/tomcat [root@centos8 tomcat] docs examples host-manager manager ROOT [root@centos8 tomcat] docs examples host-manager manager ROOT [root@centos8 tomcat] [root@centos8 tomcat] total 8 drwxr-x--- 15 tomcat tomcat 4096 Feb 9 11:02 docs drwxr-x--- 6 tomcat tomcat 83 Feb 9 11:02 examples drwxr-x--- 5 tomcat tomcat 87 Feb 9 11:02 host-manager drwxr-x--- 5 tomcat tomcat 103 Feb 9 11:02 manager drwxr-x--- 3 tomcat tomcat 300 Feb 9 19:59 ROOT drwxr-x--- 3 tomcat tomcat 55 Feb 9 20:14 app2 -rw-r--r-- 1 tomcat tomcat 862 Feb 9 20:05 app2.war [root@centos8 tomcat] total 8 drwxr-x--- 2 tomcat tomcat 44 Feb 9 20:14 META-INF -rw-r----- 1 tomcat tomcat 28 Feb 9 20:03 test.html -rw-r----- 1 tomcat tomcat 329 Aug 30 02:30 test.jsp [root@centos8 tomcat] work/Catalina/localhost/app2/ 0 directories, 0 files [root@centos8 tomcat] <!DOCTYPE html> <html> <head > <meta charset="utf-8" > <title>jsp例子</title> </head> <body> 后面的内容是服务器端动态生成字符串,最后拼接在一起 hello jsp </body> </html> [root@centos8 tomcat] work/Catalina/localhost/app2/ └── org └── apache └── jsp ├── test_jsp.class └── test_jsp.java 3 directories, 2 files [root@centos8 tomcat] <h1>This is test html </h1> [root@centos8 tomcat] work/Catalina/localhost/estapp2/ └── org └── apache └── jsp ├── test_jsp.class └── test_jsp.java 3 directories, 2 files [root@centos8 tomcat] docs examples host-manager manager ROOT app2 [root@centos8 tomcat] docs examples host-manager manager ROOT [root@centos8 tomcat] docs examples host-manager manager ROOT [root@centos8 tomcat] docs examples host-manager manager ROOT
实站案例: 部署基于JAVA的博客系统 JPress
JPress 是一个使用Java开发的类似WordPress的产品的建站神器,目前已有超过10万+网站使用 JPress 搭建,其中包括多个政府机构,200+上市公司,中科院、红十字会等。
和JPress 相类似的基于 java 开发的博客系统还有zrlog,Halo等
开源协议: LGPL-3.0
官方网站: http://www.jpress.io/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 [root@centos8 ~] [root@centos8 webapps] docs examples host-manager jpress-v3.2.1.war manager ROOT [root@centos8 webapps] docs examples host-manager jpress-v3.2.1 jpress-v3.2.1.war manager ROOT [root@centos8 webapps] [root@centos8 webapps] docs examples host-manager jpress jpress-v3.2.1 jpress-v3.2.1.war manager ROOT [root@centos8 ~] [root@centos8 ~] [root@centos8 ~] mysql> create user jpress@'10.0.0.%' identified with mysql_native_password by '123456' ; mysql> create database jpress; mysql> grant all on jpress.* to jpress@'10.0.0.%' ;
上传的图片存放路径
1 2 3 4 5 6 [root@centos8 webapps] jpress/attachment/ └── 20210110 └── 2169440a4e75459d8a420f4b245565d4.jpg 1 directory, 1 file
实战案例: 部署基于JAR包的博客系统 Halo
Halo 是一款现代化的基于JAVA实现的博客/CMS系统
Halo 官网: https://halo.run/
Halo 部署: https://docs.halo.run/install/linux
Docker 部署: https://docs.halo.run/install/docker
Halo 下载: https://dl.halo.run/
范例: 部署 Halo 博客系统
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 [root@ubuntu1804 ~] [root@ubuntu1804 ~] [root@ubuntu2204 ~] [root@ubuntu1804 ~] [root@ubuntu1804 ~] [root@ubuntu1804 ~]
范例: docker 启动 halo
基于WEB的管理Server status和Manager APP实现应用部署 tomcat 提供了基于WEB的管理页面,默认由 tomcat-admin-webapps.noarch包提供相关文件
实现WEB的管理Server status和Manager APP 打开浏览器可以访问tomcat管理的默认管理页面,点击下图两个按钮都会出现下面提示403的错误提示
默认的管理页面被禁用,启用方法如下
修改conf/conf/tomcat-users.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 [root@centos8 tomcat] Catalina context.xml logging.properties tomcat-users.xml catalina.policy jaspic-providers.xml server.xml tomcat-users.xsd catalina.properties jaspic-providers.xsd tomcat.conf web.xml [root@centos8 tomcat] <GlobalNamingResources> <!-- Editable user database that can also be used by UserDatabaseRealm to authenticate users --> <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>
用户认证,配置文件是conf/tomcat-users.xml。打开tomcat-users.xml,我们需要一个角色manager-gui。
1 2 3 4 5 6 7 8 9 10 11 12 [root@centos8 tomcat] <tomcat-users xmlns="http://tomcat.apache.org/xml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd" version="1.0" > <role rolename="manager-gui" /> <user username="admin" password="123456" roles="manager-gui" /> </tomcat-users> [root@centos8 tomcat]
修改webapps/manager/META-INF/context.xml
1 2 3 4 5 6 7 8 <?xml version="1.0" encoding="UTF-8" ?> <Context antiResourceLocking ="false" privileged ="true" > <Valve className ="org.apache.catalina.valves.RemoteAddrValve" allow ="127\.\d+\.\d+\.\d+|::1|0:0:0:0:0:0:0:1" /> <Manager sessionAttributeValueClassNameFilter ="java\.lang\. (?:Boolean|Integer|Long|Number|String)|org\.apache\.catalina\.filters\.CsrfPreve ntionFilter\$LruCache(?:\$1)?|java\.util\.(?:Linked)?HashMap" /></Context >
查看正则表达式就知道是本地访问了,由于当前访问地址是192.168.x.x,可以修改正则表达式为
1 allow="127\.\d+\.\d+\.\d+|::1|0:0:0:0:0:0:0:1|192\.168\.\d+\.\d+"
范例:
1 2 3 4 5 6 7 8 9 10 11 [root@centos8 tomcat] <Context antiResourceLocking="false" privileged="true" > <Valve className="org.apache.catalina.valves.RemoteAddrValve" allow="127\.\d+\.\d+\.\d+|::1|0:0:0:0:0:0:0:1|10\.0\.0\.\d+" /> <Manager sessionAttributeValueClassNameFilter="java\.lang\. (?:Boolean|Integer|Long|Number|String)|org\.apach e\.catalina\.filters\.CsrfPreventionFilter\$LruCache(?:\$1)?|java\.util\.(?:Linked)?HashMap" /></Context> [root@centos7 ~]
再次通过浏览器访问两个按钮Server Status和Manager App,可以看到以下管理界面,输入前面的用户和密码进行登录
基于WEB应用程序管理器实现APP的部署 Web 应用程序管理界面可以实现以下功能
Applications 应用程序管理,可以启动、停止、重加载、反部署、清理过期session
Deploy 可以热部署,也可以部署war文件。
方式1: 指定目录部署软件
1 2 3 4 5 [root@centos8 ~] [root@centos8 ~] http://10.0.0.8:8080/test1/
1 2 Context Path (required): 指定通过浏览器访问的虚拟目录 WAR or Directory URL:指定真正存放文件的实际磁盘目录路径
1 2 3 4 5 6 7 8 [root@centos8 ~] /usr/local/tomcat/webapps/test1/ └── index.html 0 directories, 1 file [root@centos8 ~] /data/myapp/index.html
方式2: 部署war包文件
常见配置详解 端口8005/tcp 安全配置管理 在conf/server.xml 有以下内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?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 port ="8009" protocol ="AJP/1.3" redirectPort ="8443" /> <Engine name ="Catalina" defaultHost ="localhost" > <Host name ="localhost" appBase ="webapps" unpackWARs ="true" autoDeploy ="true" > </Host > </Engine > </Service > </Server >
1 <Server port ="8005" shutdown ="SHUTDOWN" >
8005是Tomcat的管理端口,默认监听在127.0.0.1上。无需验证就可发送SHUTDOWN (大小写敏感)这个字符串,tomcat接收到后就会关闭此Server。
此管理功能建议禁用,可将SHUTDOWN改为一串猜不出的字符串实现
或者port修改成 0, 会使用随机端口,如:36913
port设为-1等无效端口,将关闭此功能,注意:-2等不支持
此行不能被注释,否则无法启动tomcat服务
范例:
1 <Server port ="8005" shutdown ="44ba3c71d57f494992641b258b965f28" >
范例:修改8005/tcp端口管理命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 [root@centos8 ~] State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 128 0.0.0.0:22 0.0.0.0:* LISTEN 0 100 *:8080 *:* LISTEN 0 128 [::]:22 [::]:* LISTEN 0 1 [::ffff:127.0.0.1]:8005 *:* LISTEN 0 100 *:8009 *:* [root@centos8 ~] Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]' . SHUTDOWN Connection closed by foreign host. [root@centos8 ~] State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 128 0.0.0.0:22 0.0.0.0:* LISTEN 0 128 [::]:22 [::]:* [root@centos8 tomcat] <Server port="8005" shutdown="magedu" > [root@centos8 tomcat] [root@centos8 tomcat] Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]' . SHUTDOWN Connection closed by foreign host. [root@centos8 tomcat] State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 128 0.0.0.0:22 0.0.0.0:* LISTEN 0 100 *:8080 *:* LISTEN 0 128 [::]:22 [::]:* LISTEN 0 1 [::ffff:127.0.0.1]:8005 *:* LISTEN 0 100 *:8009 *:* [root@centos8 tomcat] Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]' . magedu Connection closed by foreign host. [root@centos8 ~] State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 128 0.0.0.0:22 0.0.0.0:* LISTEN 0 128 [::]:22 [::]:*
显示指定的http服务器版本信息 默认不显示tomcat的http的Server头信息, 可以指定tomcat的http的Server头信息为相应的值
1 2 3 #conf/server.xml <Connector port ="8080" protocol ="HTTP/1.1" connectionTimeout ="20000" redirectPort ="8443" Server ="SOME STRING" />
范例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 [root@centos8 ~] HTTP/1.1 200 Content-Type: text/html;charset=UTF-8 Transfer-Encoding: chunked Date: Fri, 17 Jul 2020 08:32:52 GMT [root@centos8 ~] ...... <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" Server="WangServer" /> ...... [root@centos8 ~] [root@centos8 ~] HTTP/1.1 200 Content-Type: text/html;charset=UTF-8 Transfer-Encoding: chunked Date: Fri, 17 Jul 2020 08:34:17 GMT Server: WangServer
其它配置 conf/server.xml中可以配置service,connector, Engine,Host等
一般情况下,一个Server实例配置一个Service,name属性相当于该Service的ID。
1 <Service name ="Catalina" >
1 2 3 <Connector port ="8080" protocol ="HTTP/1.1" connectionTimeout ="20000" redirectPort ="8443" />
redirectPort,如果访问HTTPS协议,自动转向这个连接器。但大多数时候,Tomcat并不会开启HTTPS,因为Tomcat往往部署在内部,HTTPS性能较差
1 <Engine name ="Catalina" defaultHost ="localhost" >
defaultHost指向内部定义某虚拟主机。缺省虚拟主机可以改动,默认localhost。
1 <Host name ="localhost" appBase ="webapps" unpackWARs ="true" autoDeploy ="true" >
多虚拟主机配置 多虚拟主机配置说明
name 必须是主机名,用主机名来匹配
appBase 当前主机的网页根目录,是相对于 $CATALINA_HOME ,也可以使用绝对路径
unpackWARs 是否自动解压war格式
autoDeploy 热部署,自动加载并运行应用
虚拟主机配置过程
再添加和配置一个新的虚拟主机,并将myapp部署到/data/webapps目录下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 vim conf/server.xml #在文件最后面增加下面内容 <Host name ="web1.magedu.org" appBase ="/data/webapps/" unpackWARs ="True" autoDeploy ="false" > #虚拟主机专有访问日志 <Valve className ="org.apache.catalina.valves.AccessLogValve" directory ="logs" prefix ="web1_access_log" suffix =".txt" pattern ="%h %l %u %t " %r" %s %b" /></Host > #以下行是自带的不需要修改 </Engine > </Service > </Server > #或者如果不加日志也可以用下面简化写法 <Host name ="web1.magedu.org" appBase ="/data/webapps/" unpackWARs ="True" autoDeploy ="false" />
刚才在虚拟主机中主机名定义node1.magedu.org,所以需要主机在本机手动配置一个域名解析。
如果是windows,修改在C:\Windows\System32\drivers\etc下的hosts文件,需要管理员权限。
使用http://web1.magedu.org:8080/访问查看
实战案例:tomcat实现多虚拟主机 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 [root@centos8 tomcat] /usr/local/tomcat [root@centos8 tomcat] [root@centos8 tomcat] pattern="%h %l %u %t "%r" %s %b" /> </Host> <Host name="node1.magedu.org" appBase="/data/webapps1/" unpackWARs="true" autoDeploy="true" > <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="node1_access_log" suffix=".txt" pattern="%h %l %u %t "%r" %s %b" /> </Host> <Host name="node2.magedu.org" appBase="/data/webapps2/" unpackWARs="true" autoDeploy="true" > <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="node2_access_log" suffix=".txt" pattern="%h %l %u %t "%r" %s %b" /> </Host> </Engine> </Service> </Server> [root@centos8 ~] mkdir : created directory '/data/webapps1/ROOT' mkdir : created directory '/data/webapps2/ROOT' [root@centos8 ~] <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <!DOCTYPE html> <html> <head > <meta charset="utf-8" > <title>jsp例子</title> </head> <body> 后面的内容是服务器端动态生成字符串,最后拼接在一起 <br> <%=request.getRequestURL()%> </body> </html> [root@centos8 ~] <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <!DOCTYPE html> <html> <head > <meta charset="utf-8" > <title>jsp例子</title> </head> <body> 后面的内容是服务器端动态生成字符串,最后拼接在一起 <br> <%=request.getRequestURL()%> </body> </html> [root@centos8 ~] [root@centos8 ~] 10.0.0.8 node1.magedu.org node2.magedu.org [root@centos8 ~] <!DOCTYPE html> <html> <head > <meta charset="utf-8" > <title>jsp例子</title> </head> <body> 后面的内容是服务器端动态生成字符串,最后拼接在一起 http://node1.magedu.org:8080/ </body> </html> [root@centos8 ~] <!DOCTYPE html> <html> <head > <meta charset="utf-8" > <title>jsp例子</title> </head> <body> 后面的内容是服务器端动态生成字符串,最后拼接在一起 http://node2.magedu.org:8080/ </body> </html>
实战案例:修改tomcat实现多虚拟主机的端口为80 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 [root@centos8 ~] <Connector port="80" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" /> [root@centos8 ~] Caused by: java.net.SocketException: Permission denied [root@centos8 ~] [Service] ..... [root@centos8 ~] [root@centos8 ~]
基于web方式的Host Manager虚拟主机管理 可以通过tomcat的管理页面点下面Host Manager按钮进入管理虚拟主机的页面
默认Host Manager 管理页被禁用,会出现下面提示
允许本机访问 配置如下
1 2 3 4 5 6 7 8 9 10 11 12 [root@centos8 tomcat]# vim conf/tomcat-users.xml <tomcat-users xmlns ="http://tomcat.apache.org/xml" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://tomcat.apache.org/xml tomcat-users.xsd" version ="1.0" > <role rolename ="manager-gui" /> #3.4.4.6添加的内容 <role rolename ="admin-gui" /> #添加新的role <user username ="admin" password ="123456" roles ="manager-gui,admin-gui" /> #再加新role </tomcat-users > [root@centos8 tomcat]# systemctl restart tomcat
重启Tomcat后,点击”Host Manager”按钮
允许远程主机访问 但通过远程访问地址仍无法访问Host Manager管理页面
默认无法通过网络远程访问Host Manager管理页面,默认会出现以下界面
1 2 3 4 5 6 7 8 [root@centos8 tomcat] <Context antiResourceLocking="false" privileged="true" > <Valve className="org.apache.catalina.valves.RemoteAddrValve" allow="127\.\d+\.\d+\.\d+|::1|0:0:0:0:0:0:0:1|10\.0\.0\.\d+" /> <Manager sessionAttributeValueClassNameFilter="java\.lang\. (?:Boolean|Integer|Long|Number|String)|org\.apach e\.catalina\.filters\.CsrfPreventionFilter\$LruCache(?:\$1)?|java\.util\.(?:Linked)?HashMap" /></Context>
无需重启服务,直接访问,输入前面的用户和密码,即可登录成功
创建新的虚拟主机 可以管理虚拟主机
1 2 3 4 [root@centos8 ~] [root@centos8 ~] [root@centos8 ~]
Context 配置 Centext 配置方式 Context作用:
路径映射:将url映射至指定路径,而非使用appBase下的物理目录,实现虚拟目录功能
应用独立配置,例如单独配置应用日志、单独配置应用访问控制
1 2 3 4 5 6 7 8 9 10 11 12 #映射指定路径 <Context path ="/test" docBase ="/data/test" reloadable ="true" /> #映射站点的根目录 <Context path ="/" docBase ="/data/website" reloadable ="true" /> #还可以添加日志等独立的配置 <Context path ="/test" docBase ="/data/test" reloadable ="true" > <Valve className ="org.apache.catalina.valves.AccessLogValve" directory ="logs" prefix ="localhost_test_log" suffix =".txt" pattern ="%h %l %u %t " %r" %s %b" /> </Context >
说明:
path:指的是访问的URL路径,如果path与appBase下面的子目录同名,context的docBase路径优先级更高
docBase:可以是磁盘文件的绝对路径,也可以是相对路径(相对于Host的appBase)
reloadable:true表示如果WEB-INF/classes或META-INF/lib目录下.class文件有改动,就会将WEB应用重新加载。生产环境中,建议使用false来禁用。
Centext实现过程
将~/projects/myapp/下面的项目文件复制到/data/下,可以修改一下index.jsp 区别一下
注意: 这里特别使用了软链接,原因方便后期版升级或回滚,如是是版本升级,需要将软链接指向myappv2,重新启动。如果新版上线后,出现问题,重新修改软链接到上一个版本的目录,并重启,就可以实现回滚
修改conf/server.xml设置context
Tomcat的配置文件server.xml中修改如下,重启Tomcat生效
1 2 3 4 <Host name ="node1.magedu.com" appBase ="/data/webapps" unpackWARs ="true" autoDeploy ="true" > <Context path ="/test" docBase ="/data/test" reloadable ="true" /> </Host >
Valve组件 日志格式说明
1 http://tomcat:8080/docs/config/valve.html#Access_Logging
valve(阀门)组件可以定义日志
1 2 3 <Valve className ="org.apache.catalina.valves.AccessLogValve" directory ="logs" prefix ="localhost_access_log" suffix =".txt" pattern ="%h %l %u %t " %r" %s %b" />
valve存在多种类型:
1 2 定义访问日志:org.apache.catalina.valves.AccessLogValve 定义访问控制:org.apache.catalina.valves.RemoteAddrValve
示例:
1 2 <Valve className ="org.apache.catalina.valves.RemoteAddrValve" deny ="10\.0\.0\.\d+" />
实战案例 范例:虚拟主机上利用context实现虚拟目录
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 [root@centos8 ~] [root@centos8 ~] [root@centos8 ~] /data/webapps1/app1/index.html [root@centos8 tomcat] [root@centos8 tomcat] </Host> <Host name="node1.magedu.org" appBase="/data/webapps1" > <Context path="/app1" docBase="/data/app1" reloadable="true" > <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="node1.magedu.org_app1" suffix=".log" pattern="%h %l %u %t "%r" %s %b" /> <Valve className="org.apache.catalina.valves.RemoteAddrValve" deny="10\.0\.0\.7" /> </Context> </Host> <Host name="node2.magedu.org" appBase="/data/webapps2" > </Host> </Engine> </Service> </Server> [root@centos8 tomcat] [root@centos8 tomcat] curl: (7) Failed to connect to node1.magedu.org port 8080: Connection refused [root@centos8 tomcat] [root@centos8 tomcat] [root@centos8 tomcat] [root@centos8 tomcat] curl: (7) Failed to connect to node1.magedu.org port 8080: Connection refused [root@centos8 tomcat] [root@centos8 tomcat] /data/app1-v1/index.html [root@centos7 ~] HTTP/1.1 403 Content-Type: text/html;charset=utf-8 Content-Language: en Transfer-Encoding: chunked Date: Tue, 14 Jul 2020 06:48:54 GMT [root@centos8 ~] 10.0.0.8 - - [14/Jul/2020:14:36:01 +0800] "GET /app1/ HTTP/1.1" 200 330 10.0.0.7 - - [14/Jul/2020:14:48:07 +0800] "GET /app1/ HTTP/1.1" 403 618
范例: 基于前面环境,实现软件升级和回滚功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 [root@centos8 tomcat] [root@centos8 tomcat] [root@centos8 tomcat] [root@centos8 tomcat] /data/app1-v1/index.html [root@centos8 tomcat] [root@centos8 tomcat] curl: (7) Failed to connect to node1.magedu.org port 8080: Connection refused [root@centos8 tomcat] [root@centos8 tomcat] curl: (7) Failed to connect to node1.magedu.org port 8080: Connection refused [root@centos8 tomcat] [root@centos8 tomcat] /data/app1-v2/index.html [root@centos8 tomcat] [root@centos8 tomcat] [root@centos8 tomcat] [root@centos8 tomcat] /data/app1-v1/index.html
结合反向代理实现 Tomcat 部署 常见部署方式介绍
standalone模式,Tomcat单独运行,直接接受用户的请求,不推荐。
反向代理,单机运行,提供了一个Nginx作为反向代理,可以做到静态由nginx提供响应,动态jsp代理给Tomcat
LNMT:Linux + Nginx + MySQL + Tomcat
LAMT:Linux + Apache(Httpd)+ MySQL + Tomcat
前置一台Nginx,给多台Tomcat实例做反向代理和负载均衡调度,Tomcat上部署的纯动态页面更适合
LNMT:Linux + Nginx + MySQL + Tomcat
多级代理
LNNMT:Linux + Nginx + Nginx + MySQL + Tomcat
利用 nginx 反向代理实现全部转发置指定同一个虚拟主机 配置说明
利用nginx反向代理功能,实现上图的代理功能,将用户请求全部转发至指定的同一个tomcat主机
利用nginx指令proxy_pass 可以向后端服务器转发请求报文,并且在转发时会保留客户端的请求报文中的host首部
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 location / { proxy_pass http://node1.magedu.com:8080; } proxy_set_header Host $http_host ;
实战案例1 环境说明:
1 2 一台主机,实现nginx和tomcat tomcat上有两个虚拟主机node1和node2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 [root@centos8 ~] 127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 centos8.localdomain ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 10.0.0.100 node1.magedu.org node2.magedu.org [root@centos8 ~] [root@centos8 ~] ...... location / { proxy_pass http://node1.magedu.org:8080; } ...... [root@centos8 ~] [root@centos8 ~] <!DOCTYPE html> <html> <head > <meta charset="utf-8" > <title>jsp例子</title> </head> <body> 后面的内容是服务器端动态生成字符串,最后拼接在一起 node1.magedu.org </body> </html> [root@centos8 ~] <!DOCTYPE html> <html> <head > <meta charset="utf-8" > <title>jsp例子</title> </head> <body> 后面的内容是服务器端动态生成字符串,最后拼接在一起 node2.magedu.org </body> </html> [root@centos8 ~] <!DOCTYPE html> <html> <head > <meta charset="utf-8" > <title>jsp例子</title> </head> <body> 后面的内容是服务器端动态生成字符串,最后拼接在一起 node1.magedu.org </body> </html> [root@centos8 ~] <!DOCTYPE html> <html> <head > <meta charset="utf-8" > <title>jsp例子</title> </head> <body> 后面的内容是服务器端动态生成字符串,最后拼接在一起 node1.magedu.org </body> </html> [root@centos8 ~] [root@centos8 ~] ...... location / { proxy_pass http://node2.magedu.org:8080; } ...... [root@centos8 ~] [root@centos8 ~] [root@centos8 ~] [root@centos8 ~] <!DOCTYPE html> <html> <head > <meta charset="utf-8" > <title>jsp例子</title> </head> <body> 后面的内容是服务器端动态生成字符串,最后拼接在一起 node2.magedu.org </body> </html>
实战案例2 tomcat 实现 https的参考文档
1 https://help.aliyun.com/document_detail/98576.html?spm=5176.b657008.0.0.5a471b48Cyahpi
范例: 实现 http自动跳转至 tomcat的https
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 server { listen 80; server_name blog.magedu.org; return 302 https://$host$request_uri ; } server { listen 443 ssl; server_name blog.magedu.org; ssl_certificate /etc/nginx/ssl/www.magedu.org.pem; ssl_certificate_key /etc/nginx/ssl/www.magedu.org.key; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $http_host ; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for ; } }
利用nginx实现动静分离代理 配置说明 可以利用nginx实现动静分离
1 2 3 4 5 6 7 8 9 10 11 vim nginx.conf root /usr/share/nginx/html; location ~* \.jsp$ { proxy_pass http://node1.magedu.com:8080; }
以上设置,可以将jsp的请求反向代理到tomcat,而其它文件仍由nginx处理,从而实现所谓动静分离。但由于jsp文件中实际上是由静态资源和动态组成,所以无法彻底实现动静分离。实际上Tomcat不太适合做动静分离,用它来管理程序的图片不好做动静分离部署
实战案例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 [root@centos8 ~] [root@centos8 ~] [root@centos8 ~] [root@centos8 ~] ...... root /usr/share/nginx/html; location ~* \.jsp$ { proxy_pass http://127.0.0.1:8080; } ...... [root@centos8 ~] [root@centos8 ~] [root@centos8 ~] [root@centos8 ~] [root@centos8 ~] /usr/share/nginx/html/test.html [root@centos8 ~] [root@centos8 ~] [root@centos8 ~] [root@centos8 ~] /usr/local/tomcat/webapps/ROOT/test.jsp
范例: 实现单机的tomcat的服务器的动静分离
1 2 3 4 5 6 7 8 9 10 11 listen 80; server name java.wang.org; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $http_host ; } lcation ~* \.(png|jpg|jpeg|svg)${ root /data/www/images; expires 1d; } }
范例: 实现多主机的负载均衡的动静分离
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 upstream static { server 10.0.0.8:80; } upstream tomcat { server 10.0.0.18:8080; } server { listen 80; server_name www.wang.org; location / { proxy_pass http://tomcat; proxy_set_header Host $http_host ; } location ~* .*\.(png|jpg|jpeg|gif)$ { proxy_pass http://static; proxy_set_header Host $http_host ; } }
利用 httpd 实现基于AJP协议的反向代理至后端Tomcat服务器
AJP 协议说明 AJP(Apache JServ Protocol)是定向包协议,是一个二进制的TCP传输协议,相比HTTP这种纯文本的协议来说,效率和性能更高,也做了很多优化。但是浏览器并不能直接支持AJP13协议,只支持HTTP协议。所以实际情况是,通过Apache的proxy_ajp模块进行反向代理,暴露成http协议给客户端访问
启用和禁用 AJP 注意: Tomcat/8.5.51之后版本基于安全需求默认禁用AJP协议
范例: Tomcat/8.5.51之后版启用支持AJP协议
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 [root@centos8 tomcat] <Connector protocol="AJP/1.3" address="0.0.0.0" port="8009" redirectPort="8443" secretRequired="" /> [root@centos8 tomcat] [root@centos8 tomcat] State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 128 0.0.0.0:22 0.0.0.0:* LISTEN 0 100 127.0.0.1:25 0.0.0.0:* LISTEN 0 128 [::]:22 [::]:* LISTEN 0 100 [::1]:25 [::]:* LISTEN 0 1 [::ffff:127.0.0.1]:8005 *:* LISTEN 0 100 [::ffff:127.0.0.1]:8009 *:* LISTEN 0 100 *:8080 *:* LISTEN 0 128 *:80 *:*
注意: secretRequired=”” 必须加上,否则出现以下错误提示
1 2 3 4 [root@centos8 tomcat] Caused by: java.lang.IllegalArgumentException: The AJP Connector is configured with secretRequired="true" but the secret attribute is either null or "" . This combination is not valid.
除httpd外,其它支持AJP代理的服务器非常少,比如Nginx就不支持AJP,所以目前一般都禁用AJP协议端口
范例:禁用AJP协议
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 [root@centos8 tomcat] State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 128 0.0.0.0:22 0.0.0.0:* LISTEN 0 128 [::]:22 [::]:* LISTEN 0 1 [::ffff:127.0.0.1]:8005 *:* LISTEN 0 100 [::ffff:127.0.0.1]:8009 *:* LISTEN 0 100 *:8080 *:* LISTEN 0 128 *:80 *:* [root@centos8 ~] <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" /> [root@centos8 ~] [root@centos8 ~] State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 128 0.0.0.0:22 0.0.0.0:* LISTEN 0 128 [::]:22 [::]:* LISTEN 0 1 [::ffff:127.0.0.1]:8005 *:* LISTEN 0 100 *:8080 *:* LISTEN 0 128 *:80 *:*
httpd 实现 AJP 反向代理 配置说明 相对来讲,AJP协议基于二进制比使用HTTP协议的连接器效率高些。
proxy_ajp_module模块代理配置
1 2 3 4 5 6 7 <VirtualHost *:80 > ServerName node1.magedu.org ProxyRequests Off ProxyVia On ProxyPreserveHost On ProxyPass / ajp://127.0.0.1:8009/ </VirtualHost >
查看Server Status可以看到确实使用的是ajp连接了。
实战案例 范例:启用httpd的AJP反向代理功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 [root@centos8 ~] [root@centos8 ~] <VirtualHost *:80> ServerName node1.magedu.org ProxyRequests Off ProxyVia On ProxyPreserveHost On ProxyPass / ajp://127.0.0.1:8009/ </VirtualHost> [root@centos8 ~] [root@centos8 ~] /data/node1/ROOT/test.html [root@centos8 ~] /data/node2/ROOT/test.html [root@centos8 ~] /usr/local/tomcat/webapps/ROOT/test.html [root@centos8 ~] /usr/local/tomcat/webapps/ROOT/test.html [root@centos8 ~] ProxyPreserveHost Off [root@centos8 ~] /data/node1/ROOT/test.html [root@centos8 ~] /data/node2/ROOT/test.html [root@centos8 ~] /usr/local/tomcat/webapps/ROOT/test.html [root@centos8 ~] /usr/local/tomcat/webapps/ROOT/test.html
可以通过status页面看到下面AJP的信息
1 2 3 4 5 6 7 8 9 10 11 12 [root@centos8 ~] [root@centos8 ~] <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN" > <html><head > <title>503 Service Unavailable</title> </head><body> <h1>Service Unavailable</h1> <p>The server is temporarily unable to service your request due to maintenance downtime or capacity problems. Please try again later.</p> </body></html>
实现 tomcat 负载均衡 动态服务器的问题,往往就是并发能力太弱,往往需要多台动态服务器一起提供服务。如何把并发的压力分摊,这就需要调度,采用一定的调度策略,将请求分发给不同的服务器,这就是Load Balance负载均衡。
当单机Tomcat,演化出多机多级部署的时候,一个问题便凸显出来,这就是Session。而这个问题的由来,都是由于HTTP协议在设计之初没有想到未来的发展。
HTTP的无状态,有连接和短连接
无状态:指的是服务器端无法知道2次请求之间的联系,即使是前后2次请求来自同一个浏览器,也没有任何数据能够判断出是同一个浏览器的请求。后来可以通过cookie、session机制来判断。
浏览器端第一次HTTP请求服务器端时,在服务器端使用session这种技术,就可以在服务器端产生一个随机值即SessionID发给浏览器端,浏览器端收到后会保持这个SessionID在Cookie当中,这个Cookie值一般不能持久存储,浏览器关闭就消失。浏览器在每一次提交HTTP请求的时候会把这个SessionID传给服务器端,服务器端就可以通过比对知道是谁了
Session通常会保存在服务器端内存中,如果没有持久化,则易丢失
Session会定时过期。过期后浏览器如果再访问,服务端发现没有此ID,将给浏览器端重新发新的SessionID
更换浏览器也将重新获得新的SessionID
有连接:是因为它基于TCP协议,是面向连接的,需要3次握手、4次断开。
短连接:Http 1.1之前,都是一个请求一个连接,而Tcp的连接创建销毁成本高,对服务器有很大的影响。所以,自Http 1.1开始,支持keep-alive,默认也开启,一个连接打开后,会保持一段时间(可设置),浏览器再访问该服务器就使用这个Tcp连接,减轻了服务器压力,提高了效率。
服务器端如果故障,即使Session被持久化了,但是服务没有恢复前都不能使用这些SessionID。
如果使用HAProxy或者Nginx等做负载均衡器,调度到了不同的Tomcat上,那么也会出现找不到SessionID的情况。
会话保持方式 Session sticky 会话黏性 Session绑定
nginx:source ip, cookie
HAProxy:source ip, cookie
优点:简单易配置
缺点:如果目标服务器故障后,如果没有做sessoin持久化,就会丢失session,此方式生产很少使用
Session 复制集群 Tomcat自己的提供的多播集群,通过多播将任何一台的session同步到其它节点。 缺点
Tomcat的同步节点不宜过多,互相即时通信同步session需要太多带宽
每一台都拥有全部session,内存损耗太多
Session Server session 共享服务器,使用memcached、redis做共享的Session服务器,此为推荐方式
负载均衡规划和准备 负载均衡主机和网络地址规划
ip
主机名
服务
软件
10.0.0.100
proxy.magedu.org
调度器
Nginx、HTTPD
10.0.0.101
t1.magedu.org
tomcat1
JDK8、Tomcat8
10.0.0.102
t2.magedu.org
tomcat2
JDK8、Tomcat8
1 2 3 4 5 6 vim /etc/hosts 10.0.0.100 proxy.magedu.org proxy 10.0.0.101 t1.magedu.org t1 10.0.0.102 t2.magedu.org t2
负载均衡tomcat主机准备 修改tomcat的虚拟机主机为自定义的主机名,并设为默认的虚拟主机
t1虚拟主机配置conf/server.xml
1 2 3 4 <Engine name ="Catalina" defaultHost ="t1.magedu.org" > <Host name ="t1.magedu.org" appBase ="/data/webapps" autoDeploy ="true" > </Host > </Engine >
t2虚拟主机配置conf/server.xml
1 2 3 4 <Engine name ="Catalina" defaultHost ="t2.magedu.org" > <Host name ="t2.magedu.org" appBase ="/data/webapps" autoDeploy ="true" > </Host > </Engine >
准备负载均衡规划测试用的jsp文件 在t1和 t2节点创建相同的文件/data/webapps/ROOT/index.jsp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 mkdir -pv /data/webapps/ROOTvim /data/webapps/ROOT/index.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> chown -R tomcat.tomcat /data/webapps/
Nginx 实现后端 tomcat 的负载均衡调度 Nginx 实现后端 tomcat 的负载均衡 nginx 配置如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 vim /etc/nginx/nginx.conf upstream tomcat-server { server t1.magedu.org:8080; server t2.magedu.org:8080; } server { location ~* \.(jsp|do )$ { proxy_pass http://tomcat-server; } }
测试 http://proxy.magedu.com/index.jsp,可以看到轮询调度效果,每次刷新后端主机和SessionID都会变化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 [root@proxy ~] <!DOCTYPE html> <html lang="en" > <head > <meta charset="UTF-8" > <title>tomcat test </title> </head> <body> <h1> tomcat website </h1> <div>On tomcat-server</div> <div>10.0.0.101:8080</div> <div>SessionID = <span style="color:blue" >2E4BFA5135497EA3628F1EBDAE62493E</span></div> Thu Jul 09 17:58:06 CST 2020 </body> </html> [root@proxy ~] <!DOCTYPE html> <html lang="en" > <head > <meta charset="UTF-8" > <title>tomcat test </title> </head> <body> <h1> tomcat website </h1> <div>On tomcat-server</div> <div>10.0.0.102:8080</div> <div>SessionID = <span style="color:blue" >C5CC437BC05EE5A8620822CB07E71B7C</span></div> Thu Jul 09 17:58:07 CST 2020 </body> </html>
使用抓包wireshark工具可以看到下面信息
实现 session 黏性 在upstream中使用ip_hash指令,使用客户端IP地址Hash。
1 2 3 4 5 6 7 8 [root@proxy ~] upstream tomcat-server { ip_hash; server t1.magedu.org:8080; server t2.magedu.org:8080; }
配置完reload nginx服务。curl 测试一下看看效果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 [root@proxy ~] <!DOCTYPE html> <html lang="en" > <head > <meta charset="UTF-8" > <title>tomcat test </title> </head> <body> <h1> tomcat website </h1> <div>On tomcat-server</div> <div>10.0.0.102:8080</div> <div>SessionID = <span style="color:blue" >C471641C26865B08B2FDA970BE7C71A6</span></div> Thu Jul 09 18:02:48 CST 2020 </body> </html> [root@proxy ~] <!DOCTYPE html> <html lang="en" > <head > <meta charset="UTF-8" > <title>tomcat test </title> </head> <body> <h1> tomcat website </h1> <div>On tomcat-server</div> <div>10.0.0.102:8080</div> <div>SessionID = <span style="color:blue" >3F61232DFD791A94D60D0D2E9561309A</span></div> Thu Jul 09 18:02:52 CST 2020 </body> </html>
通过图形浏览器看到主机不变,sessionID不变
关闭Session对应的Tomcat服务,再重启启动它,看看Session的变化。
通过浏览器看到主机不变,但sessionID和上一次变化,但后续刷新不再变化
实现 https 的负载均衡 范例: 实现 https 的负载均衡
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 [root@rocky8 ~] upstream blog { ip_hash; server 10.0.0.101:8080; server 10.0.0.102:8080; } server { listen 80; server_name blog.wangxiaochun.com; return 302 https://$host$request_uri ; } server { listen 443 ssl; server_name blog.wangxiaochun.com; ssl_certificate /etc/nginx/ssl/blog.wangxiaochun.com.pem; ssl_certificate_key /etc/nginx/ssl/blog.wangxiaochun.com.key; client_max_body_size 20m; location / { proxy_pass http://blog; proxy_set_header Host $http_host ; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for ; } } [root@rocky8 ~] ... <Host name="blog.wangxiaochun.com" appBase="/data/blog" unpackWARs="true" autoDeploy="true" > </Host> </Engine> </Service> </Server> [root@rocky8 ~] [root@rocky8 ~] [root@rocky8 ~] [root@rocky8 ~] [root@rocky8 ~] mysql> create user blog@'10.0.0.%' identified by '123456' ; mysql> create database jpress; mysql> grant all on jpress.* to blog@'10.0.0.%' ; [root@rocky8 ~] [root@rocky8 ~] [root@rocky8 ~] [root@rocky8 ~] [root@rocky8 ~] [root@rocky8 ~] /data/blog/ROOT/attachment *(rw,all_squash,anonuid=80,anongid=80) [root@rocky8 ~] [root@rocky8 ~] [root@rocky8 ~] NFS服务器IP:/data/blog/ROOT/attachment /data/blog/ROOT/attachment nfs _netdev 0 0 [root@rocky8 ~] [root@rocky8 ~]
Tomcat Session Replication Cluster Tomcat 官方实现了 Session 的复制集群,将每个Tomcat的Session进行相互的复制同步,从而保证所有Tomcat都有相同的Session信息.
配置说明 1 2 3 4 https://tomcat.apache.org/tomcat-10.0-doc/cluster-howto.html https://tomcat.apache.org/tomcat-9.0-doc/cluster-howto.html https://tomcat.apache.org/tomcat-8.5-doc/cluster-howto.html http://tomcat.apache.org/tomcat-7.0-doc/cluster-howto.html
修改 conf/server.conf 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 <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" #45564 /UDP frequency ="500" #间隔500ms发送 dropTime ="3000" /> #故障阈值3s <Receiver className ="org.apache.catalina.tribes.transport.nio.NioReceiver" address ="auto" #监听地址 ,此项建议修改为当前主机的IP port ="4000" #监听端口 autoBind ="100" #如果端口冲突 ,自动绑定其它端口 ,范围是4000-4100 selectorTimeout ="5000" #自动绑定超时时长5s 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 > #注意:tomcat7的官方文档此处有错误 http://tomcat.apache.org/tomcat-7.0-doc/cluster-howto.html ...... <ClusterListener className ="org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener" > <ClusterListener className ="org.apache.catalina.ha.session.ClusterSessionListener" > </Cluster >
配置说明
Cluster 集群配置
Manager 会话管理器配置
Channel 信道配置
Membership 成员判定。使用什么多播地址、端口多少、间隔时长ms、超时时长ms。同一个多播地址和端口认为同属一个组。使用时修改这个多播地址,以防冲突
Receiver 接收器,多线程接收多个其他节点的心跳、会话信息。默认会从4000到4100依次尝试可用端口
address=”auto”,auto可能绑定到127.0.0.1上,所以一定要改为当前主机可用的IP
Sender 多线程发送器,内部使用了tcp连接池。
Interceptor 拦截器
Valve
ReplicationValve 检测哪些请求需要检测Session,Session数据是否有了变化,需要启动复制过程
ClusterListener
ClusterSessionListener 集群session侦听器
使用 <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>
添加到 <Engine>
所有虚拟主机都可以启用Session复制
添加到 <Host>
,该虚拟主机可以启用Session复制
最后,在应用程序内部启用了才可以使用
修改 WEB-INF/web.xml 1 2 3 </description> <distributable/> #添加此行 </web-app>
实战案例: 实现 Tomcat Session 集群
环境准备:
时间同步,确保NTP或Chrony服务正常运行
防火墙规则
ip
主机名
服务
10.0.0.100
proxy.magedu.org
调度器
Nginx、HTTPD
10.0.0.101
t1.magedu.org
tomcat1
JDK8、Tomcat8
10.0.0.102
t2.magedu.org
tomcat2
JDK8、Tomcat8
在 proxy 主机设置 httpd (或nginx)实现后端tomcat主机轮询 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 [root@proxy ~] <Proxy balancer://tomcat-server> BalancerMember http://t1.magedu.org:8080 loadfactor=1 BalancerMember http://t2.magedu.org:8080 loadfactor=1 </Proxy> <VirtualHost *:80> ServerName proxy.magedu.org ProxyRequests Off ProxyVia On ProxyPreserveHost On ProxyPass / balancer://tomcat-server/ ProxyPassReverse / balancer://tomcat-server/ </VirtualHost> [root@proxy ~]
在所有后端tomcat主机上修改conf/server.xml 本次把多播复制的配置放到t1.magedu.org和t2.magedu.org虚拟主机里面, 即Host块中。
特别注意修改Receiver的address属性 为一个本机可对外的IP地址。
修改 t1 主机的 conf/server.xml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 [root@t1 ~] [root@t1 ~] .....以上省略..... <Host name="t1.magedu.org" appBase="/data/webapps" unpackWARs="true" autoDeploy="true" > <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="230.100.100.100" port="45564" frequency="500" dropTime="3000" /> <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver" address="10.0.0.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> </Host> </Engine> </Service> </Server> [root@t1 ~] [root@t1 ~] State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 128 0.0.0.0:22 0.0.0.0:* LISTEN 0 100 127.0.0.1:25 0.0.0.0:* LISTEN 0 128 [::]:22 [::]:* LISTEN 0 100 [::1]:25 [::]:* LISTEN 0 50 [::ffff:10.0.0.101]:4000 *:* LISTEN 0 1 ::ffff:127.0.0.1]:8005 *:* LISTEN 0 100 *:8009 *:* LISTEN 0 100 *:8080 *:*
简化说明
t1的conf/server.xml中,如下
1 2 3 4 5 6 7 8 <Host name ="t1.magedu.com" appBase ="/data/webapps" autoDeploy ="true" > #其他略去 <Receiver className ="org.apache.catalina.tribes.transport.nio.NioReceiver" address ="10.0.0.101" #只改此行 port ="4000" autoBind ="100" selectorTimeout ="5000" maxThreads ="6" />
修改 t2 主机的 conf/server.xml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 [root@t2 ~] [root@t2 ~] .....以上省略..... <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 "%r" %s %b" /> </Host> <Host name="t2.magedu.org" appBase="/data/webapps" autoDeploy="true" > <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="230.100.100.100" port="45564" frequency="500" dropTime="3000" /> <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver" address="10.0.0.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> </Host> </Engine> </Service> </Server> [root@t2 ~] [root@t2 ~] State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 128 0.0.0.0:22 0.0.0.0:* LISTEN 0 100 127.0.0.1:25 0.0.0.0:* LISTEN 0 128 [::]:22 [::]:* LISTEN 0 100 [::1]:25 [::]:* LISTEN 0 50 [::ffff:10.0.0.102]:4000 *:* LISTEN 0 1 [::ffff:127.0.0.1]:8005 *:* LISTEN 0 100 *:8009 *:* LISTEN 0 100 *:8080 *:*
简化说明
t2主机的server.xml中,如下
1 2 3 4 5 6 7 8 <Host name ="t2.magedu.com" appBase ="/data/webapps" autoDeploy ="true" > 其他略去 <Receiver className ="org.apache.catalina.tribes.transport.nio.NioReceiver" address ="10.0.0.102" #只改此行 port ="4000" autoBind ="100" selectorTimeout ="5000" maxThreads ="6" />
尝试使用刚才配置过得负载均衡(移除Session黏性),测试发现Session还是变来变去
修改应用的web.xml文件开启该应用程序的分布式 参考官方说明: https://tomcat.apache.org/tomcat-8.5-doc/cluster-howto.html
1 Make sure your web.xml has the <distributable/> element
为所有tomcat主机应用web.xml的 标签增加子标签 来开启该应用程序的分布式。
修改t1主机的应用的web.xml文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 [root@t1 ~] total 4 -rw-r----- 1 tomcat tomcat 1227 Jul 1 05:53 web.xml [root@t1 ~] [root@t1 ~] /data/webapps/ROOT/ ├── index.jsp └── WEB-INF └── web.xml 1 directory, 2 files [root@t1 ~] [root@t1 ~] </description> <distributable/> </web-app> [root@t1 ~] total 4 -rw-r----- 1 tomcat tomcat 1243 Jan 17 09:37 web.xml [root@t1 ~] [root@t1 ~] 15-Jul-2020 11:29:10.998 INFO [Membership-MemberAdded.] org.apache.catalina.ha.tcp.SimpleTcpCluster.memberAdded Replication member added: [org.apache.catalina.tribes.membership.MemberImpl[tcp://{10, 0, 0, 102}:4000, {10, 0, 0, 102},4000, alive=1022, securePort=-1, UDP Port=-1, id ={89 -26 -30 -99 16 80 65 95 -65 14 -33 124 -55 -123 -30 82 }, payload={}, command ={}, domain={}]]
修改t2主机的应用的web.xml文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 [root@t2 ~] [root@t2 ~] [root@t2 ~] </description> <distributable/> </web-app> [root@t2 ~] total 4 -rw-r----- 1 tomcat tomcat 1243 Jan 17 09:38 web.xml [root@t2 ~] [root@t2 ~] 15-Jul-2020 11:29:12.088 INFO [t2.magedu.org-startStop-1] org.apache.catalina.ha.session.DeltaManager.getAllClusterSessions Manager [], requesting session state from [org.apache.catalina.tribes.membership.MemberImpl[tcp://{10, 0, 0, 101}:4000, {10, 0, 0, 101},4000, alive=208408, securePort=-1, UDP Port=-1, id ={118 -108 -116 119 58 22 73 113 -123 -96 -94 111 -65 -90 -87 -107 }, payload={}, command = {}, domain={}]]. This operation will timeout if no session state has been received within [60] seconds.
测试访问 重启全部Tomcat,通过负载均衡调度到不同节点,返回的SessionID不变了。
用浏览器访问,并刷新多次,发现SessionID 不变,但后端主机在轮询
但此方式当后端tomcat主机较多时,会重复占用大量的内存,并不适合后端服务器众多的场景
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 [root@t1 tomcat] <Engine name="Catalina" defaultHost="t1.magedu.org" > [root@t1 tomcat] [root@centos7 ~] <!DOCTYPE html> <html lang="en" > <head > <meta charset="UTF-8" > <title>tomcat test </title> </head> <body> <h1> tomcat website </h1> <div>On tomcat-server</div> <div>10.0.0.102:8080</div> <div>SessionID = <span style="color:blue" >1A3E7EED14F3E44FAF7469F8693E1CB6</span></div> Wed Jul 15 11:33:09 CST 2020 </body> </html> [root@centos7 ~] proxy.magedu.org/index.jsp <!DOCTYPE html> <html lang="en" > <head > <meta charset="UTF-8" > <title>tomcat test </title> </head> <body> <h1> tomcat website </h1> <div>On tomcat-server</div> <div>10.0.0.101:8080</div> <div>SessionID = <span style="color:blue" >1A3E7EED14F3E44FAF7469F8693E1CB6</span></div> Wed Jul 15 11:33:10 CST 2020 </body> </html>
故障模拟 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 [root@t2 ~] [root@centos7 ~] <!DOCTYPE html> <html lang="en" > <head > <meta charset="UTF-8" > <title>tomcat test </title> </head> <body> <h1> tomcat website </h1> <div>On tomcat-server</div> <div>10.0.0.101:8080</div> <div>SessionID = <span style="color:blue" >1A3E7EED14F3E44FAF7469F8693E1CB6</span></div> Wed Jul 15 12:01:16 CST 2020 </body> </html>
恢复实验环境 本小节结束,为学习后面的内容,删除此节相关配置,为后续内容准备
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 [root@t1 ~] [root@t1 ~] <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="localhost_access_log" suffix=".txt" pattern="%h %l %u %t "%r" %s %b" /> </Host> <Host name="t1.magedu.org" appBase="/data/webapps" unpackWARs="true" autoDeploy="true" > </Host> </Engine> </Service> </Server> [root@t1 ~] [root@t1 ~] [root@t2 ~] [root@t2 ~] prefix="localhost_access_log" suffix=".txt" pattern="%h %l %u %t "%r" %s %b" /> </Host> <Host name="t2.magedu.org" appBase="/data/webapps" autoDeploy="true" > </Host> </Engine> </Service> </Server> [root@t2 ~] [root@t2 ~]
Memcached NoSQL介绍 NoSQL是对 Not Only SQL、非传统关系型数据库的统称。
NoSQL一词诞生于1998年,2009年这个词汇被再次提出指非关系型、分布式、不提供ACID的数据库设计模式。
随着互联网时代的到来,数据爆发式增长,数据库技术发展日新月异,要适应新的业务需求。
而随着移动互联网、物联网的到来,大数据的技术中NoSQL也同样重要。
数据库排名:https://db-engines.com/en/ranking
NoSQL 分类
Key-value Store k/v数据库
性能好 O(1) , 如: redis、memcached
Document Store 文档数据库
Column Store 列存数据库,Column-Oriented DB
HBase、Cassandra,大数据领域应用广泛
Graph DB 图数据库
Time Series 时序数据库
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/
Memcached 和 Redis 比较
比较类别
Redis
Memcached
支持的数据结构
哈希、列表、集合、有序集合
纯kev-value
持久化支持
有
无
高可用支持
redis支持集群功能,可以实现主动复制,读写分离。官方也提供了sentinel集群管理工具,能够实现主从服务监控,故障自动转移,这一切,对于客户端都是透明的,无需程序改动,也无需人工介入
需要二次开发
存储 value 容量
最大512M
最大1M
内存分配
临时申请空间,可能导致碎片
预分配内存池的方式管理内存,能够省去内存分配时间
虚拟内存使用
有自己的VM机制,理论上能够存储比物理内存更多的数据,当数据超量时,会引发swap,把冷数据刷到磁盘上
所有的数据存储在物理内存里
网络模型
非阻塞IO复用模型,提供一些非KV存储之外的排序,聚合功能,在执行这些功能时,复杂的CPU计算,会阻塞整个IO调度
非阻塞IO复用模型
水平扩展的支持
redis cluster 可以横向扩展
暂无
多线程
Redis6.0之前是只支持单线程
Memcached支持多线程,CPU利用方面Memcache优于Redis
过期策略
有专门线程,清除缓存数据
懒淘汰机制:每次往缓存放入数据的时候,都会存一个时间,在读取的时候要和设置的时间做TTL比较来判断是否过期
单机QPS
约10W
约60W
源代码可读性
代码清爽简洁
可能是考虑了太多的扩展性,多系统的兼容性,代码不清爽
适用场景
复杂数据结构、有持久化、高可用需求、value存储内容较大
纯KV,数据量非常大,并发量非常大的业务
Memcached 工作机制 内存分配机制 应用程序运行需要使用内存存储数据,但对于一个缓存系统来说,申请内存、释放内存将十分频繁,非常容易导致大量内存碎片,最后导致无连续可用内存可用。
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为Class 2,如果有100bytes要存,那么Memcached会选择下图中Slab Class 2 存储,因为它是120bytes的Chunk。Slab之间的差异可以使用Growth Factor 控制,默认1.25。
范例:查看Slab Class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 [root@centos8 ~] 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 <27 server listening (auto-negotiate) <28 server listening (auto-negotiate)
懒过期 Lazy Expiration memcached不会监视数据是否过期,而是在取数据时才看是否过期,如果过期,把数据有效期限标识为0,并不清除该数据。以后可以覆盖该位置存储其它数据。
LRU 当内存不足时,memcached会使用LRU(Least Recently Used)机制来查找可用空间,分配给新记录使用。
集群 Memcached集群,称为基于客户端的分布式集群,即由客户端实现集群功能,即Memcached本身不支持集群
Memcached集群内部并不互相通信,一切都需要客户端连接到Memcached服务器后自行组织这些节点,并决定数据存储的节点。
安装和启动 官方安装说明
1 https://github.com/memcached/memcached/wiki/Install
yum安装 范例: CentOS 8 安装 memcached
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 [root@centos8 ~] Last metadata expiration check: 0:16:45 ago on Wed 15 Jul 2020 03:07:47 PM CST. Available Packages Name : memcached Version : 1.5.9 Release : 3.el8 Architecture : x86_64 Size : 132 k Source : memcached-1.5.9-3.el8.src.rpm Repository : AppStream Summary : High Performance, Distributed Memory Object Cache URL : https://www.memcached.org/ License : BSD Description : memcached is a high-performance, distributed memory object caching : system, generic in nature, but intended for use in speeding up dynamic : web applications by alleviating database load. [root@centos8 ~] [root@centos8 ~] [root@t1 ~] PORT="11211" USER="memcached" MAXCONN="1024" CACHESIZE="64" OPTIONS="-l 127.0.0.1,::1" [root@centos8 ~] [Unit] Description=memcached daemon Before=httpd.service After=network.target [Service] EnvironmentFile=/etc/sysconfig/memcached ExecStart=/usr/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 [root@centos8 ~] memcached:x:992:989:Memcached daemon:/run/memcached:/sbin/nologin [root@centos8 ~] [root@centos8 ~] |-memcached(25582)-+-{memcached}(25584) | |-{memcached}(25585) | |-{memcached}(25586) | |-{memcached}(25587) | |-{memcached}(25588) | |-{memcached}(25589) | |-{memcached}(25590) | |-{memcached}(25591) | `-{memcached}(25592) [root@centos8 ~] tcp LISTEN 0 128 127.0.0.1:11211 0.0.0.0:* users :(("memcached",pid=25453 ,fd=27 )) tcp LISTEN 0 128 [::1]:11211 [::]:* users :(("memcached",pid=25453 ,fd=28 )) [root@centos8 ~] [root@centos8 ~] PORT="11211" USER="memcached" MAXCONN="1024" CACHESIZE="64" OPTIONS="" [root@centos8 ~] [root@centos8 ~] Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port udp UNCONN 0 0 127.0.0.1:323 0.0.0.0:* udp UNCONN 0 0 [::1]:323 [::]:* tcp LISTEN 0 128 0.0.0.0:22 0.0.0.0:* tcp LISTEN 0 100 127.0.0.1:25 0.0.0.0:* tcp LISTEN 0 128 0.0.0.0:11211 0.0.0.0:* tcp LISTEN 0 128 [::]:22 [::]:* tcp LISTEN 0 100 [::1]:25 [::]:* tcp LISTEN 0 128 [::]:11211 [::]:*
范例: CentOS 7 安装 memcached
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 [root@centos7 ~] [root@centos7 ~] [root@centos7 ~] memcached:x:997:995:Memcached daemon:/run/memcached:/sbin/nologin [root@centos7 ~] [Service] Type=simple EnvironmentFile=-/etc/sysconfig/memcached ExecStart=/usr/bin/memcached -u $USER -p $PORT -m $CACHESIZE -c $MAXCONN $OPTIONS [root@centos7 ~] PORT="11211" USER="memcached" MAXCONN="1024" CACHESIZE="64" OPTIONS="" [root@centos7 ~] [root@centos7 ~] [root@centos7 ~]
编译安装 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 [root@centos7 ~] [root@ubuntu1804 ~] [root@centos7 ~] [root@centos7 ~] [root@centos7 ~] [root@centos7 memcached-1.6.6] [root@centos7 memcached-1.6.6] [root@centos7 ~] /apps/memcached/ ├── bin │ └── memcached ├── include │ └── memcached │ └── protocol_binary.h └── share └── man └── man1 └── memcached.1 6 directories, 3 files [root@centos7 ~] /etc/profile.d/memcached.sh [root@centos7 ~] [root@centos7 ~] [root@centos7 ~] PORT="11211" USER="memcached" MAXCONN="1024" CACHESIZE="64" OPTIONS="-l 127.0.0.1,::1" [root@centos7 ~] [root@centos7 ~] [root@centos7 ~] [Unit] Description=memcached daemon Before=httpd.service After=network.target [Service] EnvironmentFile=/etc/sysconfig/memcached ExecStart=/apps/memcached/bin/memcached -p ${PORT} -u {USER} -m ${CACHESIZE} -c ${MAXCONN} $OPTIONS [Install] WantedBy=multi-user.target [root@centos7 ~] [root@centos7 ~] [root@centos7 ~] State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 128 127.0.0.1:11211 *:* LISTEN 0 128 *:22 *:* LISTEN 0 100 127.0.0.1:25 *:* LISTEN 0 128 [::1]:11211 [::]:* LISTEN 0 128 [::]:22 [::]:* LISTEN 0 100 [::1]:25 [::]:* [root@centos7 ~] memcached 1.6.6
memcached 启动程序说明 修改memcached 运行参数,可以使用下面的选项修改/etc/sysconfig/memcached文件
memcached 常见选项
1 2 3 4 5 6 7 8 9 -u username memcached运行的用户身份,必须普通用户 -p 绑定的端口,默认11211 -m num 最大内存,单位MB,默认64MB -c num 最大连接数,缺省1024 -d 守护进程方式运行 -f 增长因子Growth Factor,默认1.25 -v 详细信息,-vv能看到详细信息 -M 使用内存直到耗尽,不许LRU -U 设置UDP监听端口,0表示禁用UDP
范例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 [root@centos8 ~] 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 <27 server listening (auto-negotiate) <28 server listening (auto-negotiate) [root@centos8 ~] slab class 1: chunk size 96 perslab 10922 slab class 2: chunk size 120 perslab 8738 slab class 3: chunk size 152 perslab 6898 slab class 4: chunk size 192 perslab 5461 slab class 5: chunk size 240 perslab 4369 slab class 6: chunk size 304 perslab 3449 slab class 7: chunk size 384 perslab 2730 slab class 8: chunk size 480 perslab 2184
使用 memcached memcached 开发库和工具 与memcached通信的不同语言的连接器。libmemcached提供了C库和命令行工具。
范例: 查看memcached相关包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 [root@centos8 ~] Last metadata expiration check: 0:09:46 ago on Thu 13 Feb 2020 07:14:15 PM CST. Installed Packages memcached.x86_64 1.5.9-2.el8 @AppStream Available Packages libmemcached.x86_64 1.0.18-15.el8 AppStream libmemcached-libs.i686 1.0.18-15.el8 AppStream libmemcached-libs.x86_64 1.0.18-15.el8 AppStream perl-Cache-Memcached.noarch 1.30-21.el8 epel python2-memcached.noarch 1.58-8.el8 epel python3-memcached.noarch 1.58-8.el8 epel [root@centos7 ~] Loaded plugins: fastestmirror Loading mirror speeds from cached hostfile * base: Installed Packages memcached.x86_64 1.4.15-10.el7_3.1 @base Available Packages libmemcached.i686 1.0.16-5.el7 base libmemcached.x86_64 1.0.16-5.el7 base libmemcached-devel.i686 1.0.16-5.el7 base libmemcached-devel.x86_64 1.0.16-5.el7 base memcached-devel.i686 1.4.15-10.el7_3.1 base memcached-devel.x86_64 1.4.15-10.el7_3.1 base opensips-memcached.x86_64 1.10.5-4.el7 epel perl-Cache-Memcached.noarch 1.30-8.el7 epel php-ZendFramework-Cache-Backend-Libmemcached.noarch 1.12.20-1.el7 epel php-ZendFramework-Cache-Backend-Memcached.noarch 1.12.20-1.el7 epel php-pecl-memcached.x86_64 2.2.0-1.el7 epel python-memcached.noarch 1.48-4.el7 base uwsgi-router-memcached.x86_64 2.0.17.1-2.el7 epel
协议
查看/usr/share/doc/memcached-1.4.15/protocol.txt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 [root@centos8 ~] [root@centos8 ~] [root@centos8 ~] [root@centos8 ~] 0 [root@centos8 ~] Failed to ping 10.0.0.77:11211 SYSTEM ERROR [root@centos8 ~] Server: 10.0.0.101 (11211) pid: 25582 uptime : 648 time: 1594801017 version: 1.5.9 libevent: 2.1.8-stable pointer_size: 64 rusage_user: 0.051100 rusage_system: 0.079158 max_connections: 1024 curr_connections: 3 total_connections: 4 rejected_connections: 0 connection_structures: 4 reserved_fds: 20
memcached 操作命令 帮助文档:
五种基本 memcached 命令执行最简单的操作。这些命令和操作包括:
set
add
replace
get
delete
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 command <key> <flags> <expiration time> <bytes><value> command set /add/replacekey key 用于查找缓存值 flags 可以包括键值对的整型参数,客户机使用它存储关于键值对的额外信息 expiration time 在缓存中保存键值对的时间长度(以秒为单位,0 表示永远) bytes 在缓存中存储的字节数 value 存储的值(始终位于第二行) add key flags exptime bytes
范例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 [root@centos8 ~] [root@centos8 ~] Trying ::1... Connected to localhost. Escape character is '^]' . stats STAT pid 27208 STAT uptime 242 STAT time 1603035824 STAT version 1.5.9 STAT libevent 2.1.8-stable STAT pointer_size 64 STAT rusage_user 0.004214 STAT rusage_system 0.040611 STAT max_connections 1024 STAT curr_connections 2 STAT total_connections 4 STAT rejected_connections 0 STAT connection_structures 3 STAT reserved_fds 20 ······ stats slabs STAT 1:chunk_size 96 STAT 1:chunks_per_page 10922 STAT 1:total_pages 1 STAT 1:total_chunks 10922 STAT 1:used_chunks 1 STAT 1:free_chunks 10921 STAT 1:free_chunks_end 0 STAT 1:mem_requested 67 ······ add mykey 1 60 4 test STORED get mykey VALUE mykey 1 4 test END set mykey 1 60 5test1 STORED get mykey VALUE mykey 1 5 test1 END delete mykey DELETED get mykey END flush_all OK get mykey END quit
范例: 非交互式取信息
python 语言连接 memcached 范例: python3 测试代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 [root@centos8 ~] [root@centos8 ~] import memcache m = memcache.Client(['127.0.0.1:11211' ], debug=True) for i in range(10): m.set("key%d" % i,"v%d" % i) ret = m.get('key%d' % i) print ("%s" % ret) [root@centos8 ~] [root@centos8 ~] v0 v1 v2 v3 v4 v5 v6 v7 v8 v9
范例: python2 测试代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 [root@centos7 ~] [root@centos7 ~] import memcache m = memcache.Client(['127.0.0.1:11211' ], debug=True) for i in range(10): m.set("key%d" % i,"v%d" % i) ret = m.get('key%d' % i) print ret [root@centos7 ~] v0 v1 v2 v3 v4 v5 v6 v7 v8 v9 [root@centos7 ~] Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]' . get key1 VALUE key1 0 2 v1 END get key9 VALUE key9 0 2 v9 END get key10 END quit Connection closed by foreign host.
Session 共享服务器 msm 介绍 注意:当前MSM不支持 tomcat 10版本
msm(memcached session manager)提供将Tomcat的session保持到memcached或redis的程序,可以实现高可用。
项目早期托管在google code,目前在Github
github网站链接: https://github.com/magro/memcached-session-manager
支持Tomcat的 6.x、7.x、8.x、9.x
Tomcat的Session管理类,Tomcat版本不同
memcached-session-manager-2.3.2.jar
memcached-session-manager-tc8-2.3.2.jar
Session数据的序列化、反序列化类
官方推荐kyro
在webapp中WEB-INF/lib/下
驱动类
memcached(spymemcached.jar)
Redis(jedis.jar)
安装 参考链接: https://github.com/magro/memcached-session-manager/wiki/SetupAndConfiguration
将spymemcached.jar、memcached-session-manage、kyro相关的jar文件都放到Tomcat的lib目录中去,这个目录是 $CATALINA_HOME/lib/ ,对应本次安装就是/usr/local/tomcat/lib。
1 2 3 4 5 6 7 8 9 10 kryo-3.0.3.jar asm-5.2.jar objenesis-2.6.jar reflectasm-1.11.9.jar minlog-1.3.1.jar kryo-serializers-0.45.jar msm-kryo-serializer-2.3.2.jar memcached-session-manager-tc8-2.3.2.jar spymemcached-2.12.3.jar memcached-session-manager-2.3.2.jar
sticky 模式 sticky 模式工作原理 sticky 模式即前端tomcat和后端memcached有关联(粘性)关系
参考文档:https://github.com/magro/memcached-session-manager/wiki/SetupAndConfiguration
1 2 3 4 5 6 7 8 9 10 11 12 13 Tomcat-1 (t1) will primarily store it's sessions in memcached-2 (m2) which is running on another machine (m2 is a regular node for t1). Only if m2 is not available, t1 will store it's sessions in memcached-1 (m1, m1 is the failoverNode for t1). With this configuration, sessions won't be lost when machine 1 (serving t1 and m1) crashes. The following really nice ASCII art shows this setup. Tomcat-1(t1)主要将其会话存储在另一台计算机上运行的memcached-2(m2)中(m2是t1的常规节 点)。 仅当m2不可用时,t1才会将其会话存储在memcached-1中(m1,m1是t1的failoverNode)。 使 用此配置,当计算机1(服务于t1和m1)崩溃时,会话不会丢失。 以下非常好的ASCII艺术显示了此设置。 <t1> <t2> . \ / . . X . . / \ . <m1> <m2>
t1和m1部署可以在一台主机上,t2和m2部署也可以在同一台。
当新用户发请求到Tomcat1时, Tomcat1生成session返回给用户的同时,也会同时发给memcached2备份。即Tomcat1 session为主session,memcached2 session为备用session,使用memcached相当于备份了一份Session
如果Tomcat1发现memcached2 失败,无法备份Session到memcached2,则将Sessoin备份存放在memcached1中
配置过程 下载相关jar包 下载相关jar包,参考下面官方说明的下载链接
https://github.com/magro/memcached-session-manager/wiki/SetupAndConfiguration
tomcat和memcached相关包
序列化相关下载
修改tomcat配置 修改 $CATALINA_HOME/conf/context.xml
特别注意,t1配置中为failoverNodes="n1"
, t2配置为failoverNodes="n2"
1 2 3 4 5 6 7 8 9 10 #以下是sticky的配置 <Context > ... <Manager className ="de.javakaffee.web.msm.MemcachedBackupSessionManager" memcachedNodes ="n1:10.0.0.101:11211,n2:10.0.0.102:11211" failoverNodes ="n1" requestUriIgnorePattern =".*\.(ico|png|gif|jpg|css|js)$" transcoderFactoryClass ="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory" /> </Context >
配置说明
1 2 3 4 memcachedNodes="n1:host1.yourdomain.com:11211,n2:host2.yourdomain.com:11211" memcached的节点: n1、n2只是别名,可以重新命名。 failoverNodes 为故障转移节点,n1是备用节点,n2是主存储节点。另一台Tomcat将此处的n1改为n2,其主节点是n1,备用节点是n2。
如果配置成功,可以在logs/catalina.out中看到下面的内容
1 2 3 4 5 6 7 8 9 12-APR-2020 16:24:08.975 INFO [t1.magedu.com-startStop-1] de.javakaffee.web.msm.MemcachedSessionService.startInternal -------- - finished initialization: - sticky: true - operation timeout : 1000 - node ids: [n2] - failover node ids: [n1] - storage key prefix: null - locking mode: null (expiration: 5s)
配置成功后,网页访问以下,页面中看到了Session。然后运行下面的Python程序,就可以看到是否存储到了memcached中了。
准备测试msm的python脚本 范例:安装python环境准备python程序查看memcached中的SessionID
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 [root@centos8 ~] [root@centos8 ~] [root@centos8 ~] [root@centos8 ~] import memcache mc = memcache.Client(['10.0.0.101:11211' ], debug=True) stats = mc.get_stats()[0] print (stats)for k,v in stats[1].items(): print (k, v) print ('-' * 30)print (mc.get_stats('items' )) print ('-' * 30)print (mc.get_stats('cachedump 5 0' ))
t1、t2、n1、n2依次启动成功,分别使用http://t1.magedu.org:8080/ 和http://t2.magedu.org:8080/观察。
开启负载均衡调度器,通过http://proxy.magedu.com来访问看看效果
1 2 3 4 5 6 7 8 9 On tomcats 10.0.0.102:8080 SessionID = 2A19B1EB6D9649C9FED3E7277FDFD470-n2.Tomcat1 Wed Jun 26 16:32:11 CST 2019 On tomcats 10.0.0.101:8080 SessionID = 2A19B1EB6D9649C9FED3E7277FDFD470-n1.Tomcat2 Wed Jun 26 16:32:36 CST 2019
可以看到浏览器端被调度到不同Tomcat上,但是都获得了同样的SessionID。
停止t2、n2看看效果,恢复看看效果。
范例:访问tomcat,查看memcached中SessionID信息
1 2 3 4 [root@centos8 ~] ..... [('10.0.0.48:11211 (1)' , {'8D0B801640CA0B1EB9AD4A4C221EA81A-n2.Tomcat1' : '[97 b; 1581598952 s]' })]
实战案例 1 : tomcat和memcached集成在一台主机
环境准备:
时间同步,确保NTP或Chrony服务正常运行。
防火墙规则
禁用SELinux
三台主机
ip
主机名
服务
软件
10.0.0.100
proxy
调度器
CentOS8、Nginx、HTTPD
10.0.0.101
t1
tomcat1
CentOS8、JDK8、Tomcat8、memcached
10.0.0.102
t2
tomcat2
CentOS8、JDK8、Tomcat8、memcached
配置nginx充当proxy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 [root@proxy ~] http { ...... upstream tomcat-server { server t1.magedu.org:8080; server t2.magedu.org:8080; } server { ...... location / { } location ~* \.(jsp|do )$ { proxy_pass http://tomcat-server; } [root@proxy ~] 10.0.0.100 proxy.magedu.org proxy 10.0.0.101 t1.magedu.org t1 10.0.0.102 t2.magedu.org t2
配置memcached 在 tomcat1 上配置 memcached 1 2 3 4 5 6 7 8 9 10 11 [root@t1 ~] [root@t1 ~] [root@t1 ~] PORT="11211" USER="memcached" MAXCONN="1024" CACHESIZE="64" [root@t1 ~]
在 tomcat2 上配置 memcached 配置和t1相同
1 2 3 4 5 6 7 8 9 10 11 [root@t2 ~] [root@t2 ~] [root@t2 ~] PORT="11211" USER="memcached" MAXCONN="1024" CACHESIZE="64" [root@t2 ~]
配置 tomcat 配置 tomcat1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 [root@t1 tomcat] <Engine name="Catalina" defaultHost="t1.magedu.org" jvmRoute="Tomcat1" > ...... <Host name="t1.magedu.org" appBase="/data/webapps" autoDeploy="true" > </Host> </Engine> </Service> </Server> [root@t1 tomcat] <Context> ...... <Manager pathname="" /> --> <Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager" memcachedNodes="n1:10.0.0.101:11211,n2:10.0.0.102:11211" failoverNodes="n1" requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$" transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory" /> </Context> asm-5.2.jar kryo-3.0.3.jar kryo-serializers-0.45.jar memcached-session-manager-2.3.2.jar memcached-session-manager-tc8-2.3.2.jar minlog-1.3.1.jar msm-kryo-serializer-2.3.2.jar objenesis-2.6.jar reflectasm-1.11.9.jar spymemcached-2.12.3.jar [root@t1 tomcat] kryo-3.0.3.jar asm-5.2.jar objenesis-2.6.jar reflectasm-1.11.9.jar minlog-1.3.1.jar kryo-serializers-0.45.jar msm-kryo-serializer-2.3.2.jar memcached-session-manager-tc8-2.3.2.jar spymemcached-2.12.3.jar memcached-session-manager-2.3.2.jar [root@t1 tomcat] [root@t1 tomcat] <%@ page import="java.util.*" %> <!DOCTYPE html> <html lang="en" > <head > <meta 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()%> </body> </html>
配置 tomcat2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 [root@t2 tomcat] <Engine name="Catalina" defaultHost="t2.magedu.org" jvmRoute="Tomcat2" > ...... <Host name="t2.magedu.org" appBase="/data/webapps" autoDeploy="true" > </Host> </Engine> </Service> </Server> [root@t2 tomcat] <Context> ...... <Manager pathname="" /> --> <Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager" memcachedNodes="n1:10.0.0.101:11211,n2:10.0.0.102:11211" failoverNodes="n2" requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$" transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory" /> </Context> asm-5.2.jar kryo-3.0.3.jar kryo-serializers-0.45.jar memcached-session-manager-2.3.2.jar memcached-session-manager-tc8-2.3.2.jar minlog-1.3.1.jar msm-kryo-serializer-2.3.2.jar objenesis-2.6.jar reflectasm-1.11.9.jar spymemcached-2.12.3.jar [root@t2 tomcat] kryo-3.0.3.jar asm-5.2.jar objenesis-2.6.jar reflectasm-1.11.9.jar minlog-1.3.1.jar kryo-serializers-0.45.jar msm-kryo-serializer-2.3.2.jar memcached-session-manager-tc8-2.3.2.jar spymemcached-2.12.3.jar memcached-session-manager-2.3.2.jar [root@t2 tomcat] [root@t2 tomcat] <%@ page import="java.util.*" %> <!DOCTYPE html> <html lang="en" > <head > <meta 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()%> </body> </html>
查看tomcat日志 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 [root@t1 tomcat] 2020-07-13 09:00:28.580 INFO net.spy.memcached.MemcachedConnection: Setting retryQueueSize to -1 2020-07-13 09:00:28.581 INFO net.spy.memcached.MemcachedConnection: Added {QA sa=/10.0.0.101:11211, 2020-07-13 09:00:28.581 INFO net.spy.memcached.MemcachedConnection: Added {QA sa=/10.0.0.102:11211, 13-Jul-2020 09:00:28.582 INFO [t1.magedu.org-startStop-1] de.javakaffee.web.msm.RequestTrackingHostValve.<init> Setting ignorePattern to .*\.(ico|png|gif|jpg|css|js)$ 13-Jul-2020 09:00:28.582 INFO [t1.magedu.org-startStop-1] de.javakaffee.web.msm.MemcachedSessionService.setLockingMode Setting lockingMode to null 13-Jul-2020 09:00:28.582 INFO [t1.magedu.org-startStop-1] de.javakaffee.web.msm.MemcachedSessionService.createTranscoderFactory Creating transcoder factory de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory 13-Jul-2020 09:00:28.583 INFO [t1.magedu.org-startStop-1] de.javakaffee.web.msm.serializer.kryo.KryoTranscoder.<init> Starting with initialBufferSize 102400, maxBufferSize 2048000 and defaultSerializerFactory de.javakaffee.web.msm.serializer.kryo.DefaultFieldSerializerFactory 13-Jul-2020 09:00:28.583 INFO [t1.magedu.org-startStop-1] de.javakaffee.web.msm.MemcachedSessionService.startInternal -------- - finished initialization: - sticky: true - operation timeout : 1000 - node ids: [n2] - failover node ids: [n1] - storage key prefix: null - locking mode: null (expiration: 5s) -------- 13-Jul-2020 09:00:28.587 INFO [t1.magedu.org-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [/data/webapps/ROOT] has finished in [160] ms 13-Jul-2020 09:00:28.596 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["http-nio-8080" ] 13-Jul-2020 09:00:28.619 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["ajp-nio-0.0.0.0-8009" ] 13-Jul-2020 09:00:28.621 INFO [main] org.apache.catalina.startup.Catalina.start Server startup in 2097 ms
python测试脚本 在t1 上安装部署python3环境,访问memcached
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 [root@t1 ~] [root@t1 ~] [root@t1 ~] [root@t1 ~] import memcache mc = memcache.Client(['10.0.0.101:11211' ,'10.0.0.102:11211' ], debug=True) print ('-' * 30)for x in mc.get_stats('cachedump 5 0' ): print (x) [root@t1 ~] [root@t1 ~] ('10.0.0.101:11211 (1)' , {}) ('10.0.0.102:11211 (1)' , {})
浏览器访问 第一次刷新页面,可以看到下面显示
第一次运行脚本查看结果
1 2 3 4 5 6 [root@t1 ~] ------------------------------ ('10.0.0.101:11211 (1)' , {}) ('10.0.0.102:11211 (1)' , {'3B4E6FB0934692150DD9944845830FCC-n2.Tomcat1' : '[97 b; 1603970381 s]' })
第二次刷新页面后,运行脚本可以查看到下面显示
第二次执行脚本
1 2 3 4 [root@t1 ~] ------------------------------ ('10.0.0.101:11211 (1)' , {'3B4E6FB0934692150DD9944845830FCC-n1.Tomcat2' : '[97 b; 1603970442 s]' }) ('10.0.0.102:11211 (1)' , {})
第三次刷新页面
第三次执行脚本
1 2 3 4 [root@t1 ~] ------------------------------ ('10.0.0.101:11211 (1)' , {'3B4E6FB0934692150DD9944845830FCC-n1.Tomcat2' : '[97 b; 1603970442 s]' }) ('10.0.0.102:11211 (1)' , {'3B4E6FB0934692150DD9944845830FCC-n2.Tomcat1' : '[97 b; 1603970381 s]' })
第四次刷新页面
第四次执行脚本
1 2 3 4 [root@t1 ~] ------------------------------ ('10.0.0.101:11211 (1)' , {'3B4E6FB0934692150DD9944845830FCC-n1.Tomcat2' : '[97 b; 1603970442 s]' }) ('10.0.0.102:11211 (1)' , {'3B4E6FB0934692150DD9944845830FCC-n2.Tomcat1' : '[97 b; 1603970381 s]' })
之后多次刷新页面,执行脚本后,session信息不在变化
故障访问 模拟tomcat故障 1 [root@t2 ~]# systemctl stop tomcat
刷新几次页面,看到下面SessionID显示不变
运行脚本看到下面结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 [root@t1 ~] ------------------------------ ('10.0.0.101:11211 (1)' , {'items:5:number' : '1' , 'items:5:number_hot' : '0' , 'items:5:number_warm' : '0' , 'items:5:number_cold' : '1' , 'items:5:age_hot' : '0' , 'items:5:age_warm' : '0' , 'items:5:age' : '1531' , 'items:5:evicted' : '0' , 'items:5:evicted_nonzero' : '0' , 'items:5:evicted_time' : '0' , 'items:5:outofmemory' : '0' ,'items:5:tailrepairs' : '0' , 'items:5:reclaimed' : '0' , 'items:5:expired_unfetched' : '0' ,'items:5:evicted_unfetched' : '0' , 'items:5:evicted_active' : '0' , 'items:5:crawler_reclaimed' : '0' ,'items:5:crawler_items_checked' : '9' , 'items:5:lrutail_reflocked' : '0' , 'items:5:moves_to_cold' : '2' , 'items:5:moves_to_warm' : '0' , 'items:5:moves_within_lru' : '0' , 'items:5:direct_reclaims' : '0' , 'items:5:hits_to_hot' : '0' , 'items:5:hits_to_warm' : '0' , 'items:5:hits_to_cold' : '1' , 'items:5:hits_to_temp' : '0' })('10.0.0.102:11211 (1)' , {}) ------------------------------ ('10.0.0.101:11211 (1)' , {'4E92417BEFB38ED09E50433EF6F96DDF-n1.Tomcat2' : '[97 b; 1594562924 s]' }) ('10.0.0.102:11211 (1)' , {})
模拟memcached故障
刷新几次页面可以看到下面显示
再次运行脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 [root@t1 ~] ------------------------------ MemCached: MemCache: inet:10.0.0.102:11211: connect: [Errno 111] Connection refused. Marking dead. ('10.0.0.101:11211 (1)' , {'items:5:number' : '2' , 'items:5:number_hot' : '0' , 'items:5:number_warm' : '0' , 'items:5:number_cold' : '2' , 'items:5:age_hot' : '0' , 'items:5:age_warm' : '0' , 'items:5:age' : '60' , 'items:5:evicted' : '0' , 'items:5:evicted_nonzero' : '0' , 'items:5:evicted_time' : '0' , 'items:5:outofmemory' : '0' , 'items:5:tailrepairs' : '0' , 'items:5:reclaimed' : '1' , 'items:5:expired_unfetched' : '1' , 'items:5:evicted_unfetched' : '0' , 'items:5:evicted_active' : '0' , 'items:5:crawler_reclaimed' : '0' , 'items:5:crawler_items_checked' : '11' , 'items:5:lrutail_reflocked' : '0' , 'items:5:moves_to_cold' : '13' , 'items:5:moves_to_warm' : '0' , 'items:5:moves_within_lru' : '0' , 'items:5:direct_reclaims' : '0' , 'items:5:hits_to_hot' : '0' , 'items:5:hits_to_warm' : '0' , 'items:5:hits_to_cold' : '5' , 'items:5:hits_to_temp' : '0' })------------------------------ ('10.0.0.101:11211 (1)' , {'63B8F0CE41AF8943351AB9B71DE174C3-n1.Tomcat1' : '[97 b; 1594564819 s]' , '63B8F0CE41AF8943351AB9B71DE174C3-n1.Tomcat2' : '[97 b; 1594564785 s]' })
恢复memcached
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 [root@t2 ~] [root@t1 ~] ------------------------------ ('10.0.0.101:11211 (1)' , {'items:5:number' : '2' , 'items:5:number_hot' : '0' , 'items:5:number_warm' : '0' , 'items:5:number_cold' : '2' , 'items:5:age_hot' : '0' , 'items:5:age_warm' : '0' , 'items:5:age' : '23' , 'items:5:evicted' : '0' , 'items:5:evicted_nonzero' : '0' , 'items:5:evicted_time' : '0' , 'items:5:outofmemory' : '0' , 'items:5:tailrepairs' : '0' , 'items:5:reclaimed' : '1' , 'items:5:expired_unfetched' : '1' , 'items:5:evicted_unfetched' : '0' , 'items:5:evicted_active' : '0' , 'items:5:crawler_reclaimed' : '0' , 'items:5:crawler_items_checked' : '13' , 'items:5:lrutail_reflocked' : '0' , 'items:5:moves_to_cold' : '20' , 'items:5:moves_to_warm' : '0' , 'items:5:moves_within_lru' : '0' , 'items:5:direct_reclaims' : '0' , 'items:5:hits_to_hot' : '0' , 'items:5:hits_to_warm' : '0' , 'items:5:hits_to_cold' : '5' , 'items:5:hits_to_temp' : '0' })('10.0.0.102:11211 (1)' , {'items:5:number' : '1' , 'items:5:number_hot' : '0' , 'items:5:number_warm' : '0' , 'items:5:number_cold' : '1' , 'items:5:age_hot' : '0' , 'items:5:age_warm' : '0' , 'items:5:age' : '20' , 'items:5:evicted' : '0' , 'items:5:evicted_nonzero' : '0' , 'items:5:evicted_time' : '0' , 'items:5:outofmemory' : '0' , 'items:5:tailrepairs' : '0' , 'items:5:reclaimed' : '0' , 'items:5:expired_unfetched' : '0' , 'items:5:evicted_unfetched' : '0' , 'items:5:evicted_active' : '0' , 'items:5:crawler_reclaimed' : '0' , 'items:5:crawler_items_checked' : '0' , 'items:5:lrutail_reflocked' : '0' , 'items:5:moves_to_cold' : '2' , 'items:5:moves_to_warm' : '0' , 'items:5:moves_within_lru' : '0' , 'items:5:direct_reclaims' : '0' , 'items:5:hits_to_hot' : '0' , 'items:5:hits_to_warm' : '0' , 'items:5:hits_to_cold' : '0' , 'items:5:hits_to_temp' : '0' })------------------------------ ('10.0.0.101:11211 (1)' , {'63B8F0CE41AF8943351AB9B71DE174C3-n1.Tomcat2' : '[97 b; 1594564785 s]' , '63B8F0CE41AF8943351AB9B71DE174C3-n1.Tomcat1' : '[97 b; 1594564819 s]' }) ('10.0.0.102:11211 (1)' , {'63B8F0CE41AF8943351AB9B71DE174C3-n2.Tomcat1' : '[97 b; 1594564796 s]' })
实战案例 2 : tomcat和memcached 分别处于不同主机
环境准备:
时间同步,确保NTP或Chrony服务正常运行。
防火墙规则
禁用SELinux
五台主机
ip
主机名
服务
软件
10.0.0.100
proxy
调度器
CentOS8、Nginx、HTTPD
10.0.0.101
t1
tomcat1
CentOS8、JDK8、Tomcat8
10.0.0.102
t2
tomcat2
CentOS8、JDK8、Tomcat8
10.0.0.103
m1
memcached1
CentOS8、memcached
10.0.0.104
m2
memcached2
CentOS8、memcached
准备proxy主机的配置,利用nginx作为反向代理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 [root@proxy ~] http { ...... upstream tomcat-server { server t1.magedu.org:8080; server t2.magedu.org:8080; } server { ...... location / { } location ~* \.(jsp|do )$ { proxy_pass http://tomcat-server; } [root@proxy ~] 10.0.0.100 proxy.magedu.org proxy 10.0.0.101 t1.magedu.org t1 10.0.0.102 t2.magedu.org t2 [root@centos7 ~] 127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 centos7.wangxiaochun.com ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 10.0.0.100 proxy.magedu.org [root@centos7 ~] 10.0.0.7
在m1和m2上分别配置memcached 1 2 3 4 5 6 7 8 9 10 11 12 13 14 [root@m1 ~] [root@m1 ~] [root@m1 ~] PORT="11211" USER="memcached" MAXCONN="1024" CACHESIZE="64" OPTIONS="" [root@m1 ~]
在t1和t2上准备tomcat t1 的配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 [root@t1 tomcat] <Engine name="Catalina" defaultHost="t1.magedu.org" jvmRoute="Tomcat1" > ...... <Host name="t1.magedu.org" appBase="/data/webapps" autoDeploy="true" > </Host> </Engine> </Service> </Server> [root@t1 tomcat] ..... <Context> ...... <Manager pathname="" /> --> <Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager" memcachedNodes="n1:10.0.0.103:11211,n2:10.0.0.104:11211" failoverNodes="n1" requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$" transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory" /> </Context> asm-5.2.jar kryo-3.0.3.jar kryo-serializers-0.45.jar memcached-session-manager-2.3.2.jar memcached-session-manager-tc8-2.3.2.jar minlog-1.3.1.jar msm-kryo-serializer-2.3.2.jar objenesis-2.6.jar reflectasm-1.11.9.jar spymemcached-2.12.3.jar [root@t1 tomcat] kryo-3.0.3.jar asm-5.2.jar objenesis-2.6.jar reflectasm-1.11.9.jar minlog-1.3.1.jar kryo-serializers-0.45.jar msm-kryo-serializer-2.3.2.jar memcached-session-manager-tc8-2.3.2.jar spymemcached-2.12.3.jar memcached-session-manager-2.3.2.jar [root@t1 tomcat] <%@ 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> [root@t1 ~]
t2参考上面t1做类似的配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 [root@t2 tomcat] <Engine name="Catalina" defaultHost="t2.magedu.org" jvmRoute="Tomcat2" > ...... <Host name="t2.magedu.org" appBase="/data/webapps" autoDeploy="true" > </Host> </Engine> </Service> </Server> [root@t2 tomcat] ..... <Context> ...... <Manager pathname="" /> --> <Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager" memcachedNodes="n1:10.0.0.103:11211,n2:10.0.0.104:11211" failoverNodes="n2" requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$" transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory" /> </Context> asm-5.2.jar kryo-3.0.3.jar kryo-serializers-0.45.jar memcached-session-manager-2.3.2.jar memcached-session-manager-tc8-2.3.2.jar minlog-1.3.1.jar msm-kryo-serializer-2.3.2.jar objenesis-2.6.jar reflectasm-1.11.9.jar spymemcached-2.12.3.jar [root@t2 tomcat] <%@ 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> [root@t2 ~]
查看日志
1 2 3 4 5 6 7 8 9 10 11 [root@t1 ~] ....... ...... INFO [t1.magedu.org-startStop-1] de.javakaffee.web.msm.MemcachedSessionService.startInternal -------- - finished initialization: - sticky: true - operation timeout : 1000 - node ids: [n2] - failover node ids: [n1] - storage key prefix: null - locking mode: null (expiration: 5s)
测试的python脚本 在proxy 上安装部署python3环境,访问memcached
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 [root@proxy ~] [root@proxy ~] import memcache mc = memcache.Client(['10.0.0.103:11211' ,'10.0.0.104:11211' ], debug=True) print ('-' * 30)for x in mc.get_stats('cachedump 5 0' ): print (x) [root@proxy ~] [root@proxy ~] ------------------------------ ('10.0.0.103:11211 (1)' , {}) ('10.0.0.104:11211 (1)' , {})
浏览器访问测试 第一次刷新浏览器可以看到以下显示
运行脚本,可以看到只有m2上有SessionID信息
1 2 3 4 [root@proxy ~] ------------------------------ ('10.0.0.103:11211 (1)' , {}) ('10.0.0.104:11211 (1)' , {'8F9B4782FFFE6F1840728C344A179EE8-n2.Tomcat1' : '[97 b; 1594812395 s]' })
第二次刷新页面,可以看到主机轮询,SessionID不变,但SessionID来自别一个tomcat
多次刷新页面,可以看到主机轮询,SessionID不变,发现以下规律
当tomcat为t1,发现SessionID为n1.Tomcat2
当tomcat为t2,发现SessionID为n2.Tomcat1
运行脚本,可以看到m1和m2上都有了SessionID信息
1 2 3 4 [root@proxy ~] ------------------------------ ('10.0.0.103:11211 (1)' , {'8F9B4782FFFE6F1840728C344A179EE8-n1.Tomcat2' : '[97 b; 1594812654 s]' }) ('10.0.0.104:11211 (1)' , {'8F9B4782FFFE6F1840728C344A179EE8-n2.Tomcat1' : '[97 b; 1594812396 s]' })
模拟故障 1 [root@t2 ~]# systemctl stop tomcat
多次刷新页面,SessionID不变
多次刷新页面,SessionID不变
运行脚本
1 2 3 4 5 [root@proxy ~] ------------------------------ MemCached: MemCache: inet:10.0.0.104:11211: connect: [Errno 111] Connection refused. Marking dead. ('10.0.0.103:11211 (1)' , {'F82C3A7C84B2E0D6B05E6E6F496F9FBC-n1.Tomcat2' : '[97 b; 1594815951 s]' , 'F82C3A7C84B2E0D6B05E6E6F496F9FBC-n1.Tomcat1' : '[97 b; 1594815949 s]' })
non-sticky 模式 non-sticky 模式工作原理 non-sticky 模式即前端tomcat和后端memcached**无关联(无粘性)**关系
从msm 1.4.0之后版本开始支持non-sticky模式。
Tomcat session为中转Session,对每一个SessionID随机选中后端的memcached节点n1(或者n2)为主session,而另一个memcached节点n2(或者是n1)为备session。产生的新的Session会发送给主、备memcached,并清除本地Session。
后端两个memcached服务器对一个session来说是一个是主,一个是备,但对所有session信息来说每个memcached即是主同时也是备
如果n1下线,n2则转正。n1再次上线,n2依然是主Session存储节点。
memcached配置 放到 $CATALINA_HOME/conf/context.xml
中
1 2 3 4 5 6 7 8 9 10 11 12 13 <Context > ... <Manager className ="de.javakaffee.web.msm.MemcachedBackupSessionManager" memcachedNodes ="n1:10.0.0.101:11211,n2:10.0.0.102:11211" sticky ="false" sessionBackupAsync ="false" lockingMode ="uriPattern:/path1|/path2" requestUriIgnorePattern =".*\.(ico|png|gif|jpg|css|js)$" transcoderFactoryClass ="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory" /> </Context >
redis 配置
支持将session存放在Redis中,但当前对Redis的支持不允许连接到多个Redis节点,可以通过Redis的集群服务实现防止redis的单点失败
参考文档 :
https://github.com/ran-jit/tomcat-cluster-redis-session-manager/wiki
https://github.com/magro/memcached-session-manager/wiki/SetupAndConfiguration#example-for-non-sticky-sessions--kryo--redis
下载 jedis.jar
,放到 $CATALINA_HOME/lib/
,对应本次安装就是/usr/local/tomcat/lib
。
放到 $CATALINA_HOME/conf/context.xml
中
1 2 3 4 5 6 7 8 9 10 11 12 13 <Context > ... <Manager className ="de.javakaffee.web.msm.MemcachedBackupSessionManager" memcachedNodes ="redis://:password@redis.example.com:portnumber" sticky ="false" sessionBackupAsync ="false" lockingMode ="uriPattern:/path1|/path2" requestUriIgnorePattern =".*\.(ico|png|gif|jpg|css|js)$" transcoderFactoryClass ="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory" /> </Context >
浏览器访问,使用redis相关工具可以观察到redis中的信息
实战案例: memcached 实现non-sticky模式
环境准备:
时间同步,确保NTP或Chrony服务正常运行。
防火墙规则
禁用SELinux
三台主机
ip
主机名
服务
10.0.0.100
proxy
调度器
Nginx、HTTPD
10.0.0.101
t1
tomcat1
JDK8、Tomcat8,memcached
10.0.0.102
t2
tomcat2
JDK8、Tomcat8,memcached
修改tomcat配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 [root@t1 tomcat] <Context> ... <Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager" memcachedNodes="n1:10.0.0.101:11211,n2:10.0.0.102:11211" sticky="false" sessionBackupAsync="false" lockingMode="uriPattern:/path1|/path2" requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$" transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory" /> </Context> [root@t1 tomcat] [root@t1 ~] ..... INFO [t1.magedu.org-startStop-1] de.javakaffee.web.msm.MemcachedSessionService.startInternal -------- - finished initialization: - sticky: false - operation timeout : 1000 - node ids: [n1, n2] - failover node ids: [] - storage key prefix: null - locking mode: uriPattern:/path1|/path2 (expiration: 5s) ....... [root@t2 tomcat] [root@t2 tomcat] [root@t1 ~] import memcache mc = memcache.Client(['10.0.0.101:11211' ,'10.0.0.102:11211' ], debug=True) print ('-' * 30)for x in mc.get_stats('cachedump 5 0' ): print (x) [root@t1 ~] ------------------------------ ('10.0.0.101:11211 (1)' , {}) ('10.0.0.102:11211 (1)' , {})
通过浏览器访问
1 2 3 4 5 [root@t1 ~] ------------------------------ ('10.0.0.101:11211 (1)' , {'bak:D65D49C8BB068B0854F11BD1A7BFAE88-n2.Tomcat2' : '[97 b; 1594609275 s]' }) ('10.0.0.102:11211 (1)' , {'D65D49C8BB068B0854F11BD1A7BFAE88-n2.Tomcat2' : '[97 b; 1594609274 s]' })
模拟故障 模拟t1的memcached 故障
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 [root@t1 ~] [root@t1 ~] ------------------------------ MemCached: MemCache: inet:10.0.0.101:11211: connect: [Errno 111] Connection refused. Marking dead. ('10.0.0.102:11211 (1)' , {'D65D49C8BB068B0854F11BD1A7BFAE88-n2.Tomcat2' : '[97 b; 1594609274 s]' }) [root@t1 ~] [root@t1 ~] ------------------------------ ('10.0.0.101:11211 (1)' , {}) ('10.0.0.102:11211 (1)' , {'D65D49C8BB068B0854F11BD1A7BFAE88-n2.Tomcat2' : '[97 b; 1594609274 s]' }) [root@t1 ~] ------------------------------ ('10.0.0.101:11211 (1)' , {'bak:D65D49C8BB068B0854F11BD1A7BFAE88-n2.Tomcat2' : '[97 b; 1594609854 s]' }) ('10.0.0.102:11211 (1)' , {'D65D49C8BB068B0854F11BD1A7BFAE88-n2.Tomcat2' : '[97 b; 1594609274 s]' })
模拟t2的memcached 故障
1 2 3 4 5 6 [root@t2 ~] [root@t1 ~] ------------------------------ MemCached: MemCache: inet:10.0.0.102:11211: connect: [Errno 111] Connection refused. Marking dead. ('10.0.0.101:11211 (1)' , {'bak:D65D49C8BB068B0854F11BD1A7BFAE88-n2.Tomcat2' : '[97 b; 1594609854 s]' })
再刷新页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 [root@t1 ~] ------------------------------ MemCached: MemCache: inet:10.0.0.102:11211: connect: [Errno 111] Connection refused. Marking dead. ('10.0.0.101:11211 (1)' , {'D65D49C8BB068B0854F11BD1A7BFAE88-n1.Tomcat2' : '[97 b; 1594610030 s]' , 'bak:D65D49C8BB068B0854F11BD1A7BFAE88-n2.Tomcat2' : '[97 b; 1594609854 s]' }) [root@t2 ~] [root@t1 ~] ------------------------------ ('10.0.0.101:11211 (1)' , {'D65D49C8BB068B0854F11BD1A7BFAE88-n1.Tomcat2' : '[97 b; 1594610030 s]' , 'bak:D65D49C8BB068B0854F11BD1A7BFAE88-n2.Tomcat2' : '[97 b; 1594609854 s]' }) ('10.0.0.102:11211 (1)' , {}) [root@t1 ~] ------------------------------ ('10.0.0.101:11211 (1)' , {'D65D49C8BB068B0854F11BD1A7BFAE88-n1.Tomcat2' : '[97 b; 1594610030 s]' , 'bak:D65D49C8BB068B0854F11BD1A7BFAE88-n2.Tomcat2' : '[97 b; 1594609854 s]' }) ('10.0.0.102:11211 (1)' , {'bak:D65D49C8BB068B0854F11BD1A7BFAE88-n1.Tomcat2' : '[97 b; 1594610367 s]' })
模拟t1的tomcat故障,刷新页面,可以看到SessionID不变
实战案例: redis 实现 non-sticky 模式的msm
环境准备:
时间同步,确保NTP或Chrony服务正常运行
防火墙规则
禁用SELinux
四台主机
IP
主机名
服务
软件包
10.0.0.100
proxy
调度器
Nginx、HTTPD
10.0.0.101
t1.magedu.org
tomcat1
JDK8、Tomcat8
10.0.0.102
t2.magedu.org
tomcat2
JDK8、Tomcat8
10.0.0.103
redis.magedu.org
redis
Redis
上传redis库到tomcat服务器 1 2 3 4 [root@t1 ~] -rw-r--r-- 1 root root 586620 Jun 26 2019 /usr/local/tomcat/lib/jedis-3.0.0.jar [root@t2 ~] -rw-r--r-- 1 root root 586620 Jun 26 2019 /usr/local/tomcat/lib/jedis-3.0.0.jar
安装并配置 Redis 服务 1 2 3 4 5 6 7 8 9 10 [root@redis ~] [root@redis ~] [root@redis ~] [root@redis ~] State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 128 0.0.0.0:22 0.0.0.0:* LISTEN 0 100 127.0.0.1:25 0.0.0.0:* LISTEN 0 128 0.0.0.0:6379 0.0.0.0:* LISTEN 0 128 [::]:22 [::]:* LISTEN 0 100 [::1]:25 [::]:*
修改tomcat 配置指定redis服务器地址 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 [root@t1 ~] <Context> ... <Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager" memcachedNodes="redis://10.0.0.103:6379" sticky="false" sessionBackupAsync="false" lockingMode="uriPattern:/path1|/path2" requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$" transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory" /> </Context> [root@t1 ~] [root@t1 ~] ....., INFO [t1.magedu.org-startStop-1] de.javakaffee.web.msm.MemcachedSessionService.startInternal -------- - finished initialization: - sticky: false - operation timeout : 1000 - node ids: [] - failover node ids: [] - storage key prefix: null - locking mode: uriPattern:/path1|/path2 (expiration: 5s) ....... [root@t2 ~] [root@t2 ~]
测试访问 浏览器刷新访问多次,主机轮询,但SessionID不变
使用redis工具连接redis 查看SessionID
1 2 3 4 5 6 7 8 9 [root@redis ~] 127.0.0.1:6379> KEYS * 1) "4245D5D28B2CDAB292623A01DD5D2C83.Tomcat2" 2) "validity:4245D5D28B2CDAB292623A01DD5D2C83.Tomcat2" 127.0.0.1:6379> GET 4245D5D28B2CDAB292623A01DD5D2C83.Tomcat2 "\x00\x02\x00\\\x00\x00\x01sR\xf6\x8b@\x00\x00\x01sR\xf6\x8b@\x00\x00\a\b11\x00\ x00\x01sR\xf6\x8b@\x00\x00\x01sR\xf6\x8bA\x00(4245D5D28B2CDAB292623A01DD5D2C83.T omcat2\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00" 127.0.0.1:6379>
模拟故障 模拟tomcat故障,查看是否SessionID变化
刷新页面,可以看到SessinID不变
模拟Redis 故障 模拟Redis 故障,再刷新页面,可以看到SessionID不断变化,说明redis存在单点故障
1 2 [root@t1 ~] [root@redis ~]
使用 redisson 利用 redis 实现session共享 Redisson是Redis官方推荐的Java版的Redis客户端。它提供的功能非常多,也非常强大
redisson 是基于redis的扩展库,使得redis除了应用于缓存以外,还能做队列等数据结构,直接使用的分布式锁,以及事务调度器等。
官网:
1 https://github.com/redisson/redisson
tomcat 配置Redisson的文档链接
1 https://github.com/redisson/redisson/tree/master/redisson-tomcat
相关jar包下载链接
1 2 3 https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson-all&v=3.16.1&e=jar https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson-tomcat-8&v=3.16.1&e=jar https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson-tomcat-9&v=3.16.1&e=jar
范例: 使用redisson实现session共享
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 [root@centos8 ~] <Manager className="org.redisson.tomcat.RedissonSessionManager" configPath="${catalina.base} /conf/redisson.conf" readMode="MEMORY" updateMode="DEFAULT" /> </Context> [root@centos8 ~] { "singleServerConfig" :{ "idleConnectionTimeout" :10000, "connectTimeout" :10000, "timeout" :3000, "retryAttempts" :3, "retryInterval" :1500, "password" :null, "subscriptionsPerConnection" :5, "clientName" :null, "address" : "redis://10.0.0.100:6379" , "subscriptionConnectionMinimumIdleSize" :1, "subscriptionConnectionPoolSize" :50, "connectionMinimumIdleSize" :32, "connectionPoolSize" :64, "database" :0, "dnsMonitoringInterval" :5000 }, "threads" :0, "nettyThreads" :0, "codec" :{ "class" :"org.redisson.codec.JsonJacksonCodec" }, "transportMode" :"NIO" } [root@centos8 ~] /usr/local/tomcat/lib/redisson-all-3.16.1.jar /usr/local/tomcat/lib/redisson-tomcat-9-3.16.1.jar systemctl restart tomcat [root@centos8 ~] <%@ page import="java.util.*" %> <!DOCTYPE html> <html lang="en" > <head > <meta 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()%> </body> </html> [root@centos8 ~] <!DOCTYPE html> <html lang="en" > <head > <meta charset="UTF-8" > <title>tomcat test </title> </head> <body> <h1> Tomcat Website </h1> <div>On 127.0.0.1</div> <div>127.0.0.1:8080</div> <div>SessionID = <span style="color:blue" >F14D14DF86AD76D55789269190950393</span></div> Fri Aug 06 14:44:18 CST 2021 </body> </html> [root@ubuntu1804 ~] 127.0.0.1:6379> keys * 1) "redisson:tomcat_session:4B5FC68977FC980E3C32564C357A2785" s127.0.0.1:6379> type "redisson:tomcat_session:4B5FC68977FC980E3C32564C357A2785" hash 127.0.0.1:6379> hgetall redisson:tomcat_session:4B5FC68977FC980E3C32564C357A2785 1) "session:thisAccessedTime" 2) "[\"java.lang.Long\",1628231049422]" 3) "session:isNew" 4) "false" 5) "session:lastAccessedTime" 6) "[\"java.lang.Long\",1628231049422]" 7) "session:maxInactiveInterval" 8) "1800" 9) "session:isValid" 10) "true" 11) "session:creationTime" 12) "[\"java.lang.Long\",1628231049358]"
利用 Tomcat Clustering Redis Session Manager 利用 redis 实现session共享 Redis session manager 是一个插件。 它将会话存储到 Redis 中,以便在 Tomcat 服务器集群中轻松分发 HTTP 请求。
在这里,会话被实现为非粘性(意味着,每个请求都可以转到集群中的任何服务器,这与 Apache 提供的 Tomcat 集群设置不同。)
请求Sessions会立即存入Redis(Session属性必须是Serializable),供其他服务器使用。 当 tomcat 收到客户端的请求时,Sessions 直接从 Redis 加载。从而无需在负载均衡器中启用粘性会话(JSESSIONID)。
支持Redis默认、哨兵和集群模式,基于配置。
参考文档
1 https://github.com/ran-jit/tomcat-cluster-redis-session-manager
范例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 wget https://github.com/ran-jit/tomcat-cluster-redis-session-manager/releases/download/3.0.1.1/tomcat-cluster-redis-session-manager.zip unzip /opt/tomcat-cluster-redis-session-manager.zip -d /opt cp /opt/tomcat-cluster-redis-session-manager/lib/* /usr/local/tomcat/lib/chown -R tomcat.tomcat /usr/local/tomcat/libcp /opt/tomcat-cluster-redis-session-manager/conf/redis-data-cache.properties /usr/local/tomcat/conf/vim /usr/local/tomcat/conf/redis-data-cache.properties redis.hosts=10.0.0.100:6379 redis.password=123456 vim /usr/local/tomcat/conf/context.xml <Valve className="tomcat.request.session.redis.SessionHandlerValve" /> <Manager className="tomcat.request.session.redis.SessionManager" /> </Context> vim /usr/local/tomcat/conf/web.xml <session-config> <session-timeout>60</session-timeout> </session-config> systemctl restart tomcat vim /etc/nginx/conf.d/session.conf upstream tomcat-server { server t1.magedu.org:8080; server t2.magedu.org:8080; } server { listen 80; server_name www.magedu.org; location / { proxy_pass http://tomcat-server; proxy_set_header Host $http_host ; } } cat /data/webapps/ROOT/test.jsp <%@ page import="java.util.*" %> <!DOCTYPE html> <html lang="en" > <head > <meta charset="UTF-8" > <title>tomcat test </title> </head> <body> <h1> Tomcat1 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()%> </body> </html>
Tomcat 性能优化 在目前流行的互联网架构中,Tomcat在目前的网络编程中是举足轻重的,由于Tomcat的运行依赖于 JVM,从虚拟机的角度把Tomcat的调整分为外部环境调优 JVM 和 Tomcat 自身调优两部分
JVM组成 1 2 3 4 [root@t1 ~] java version "1.8.0_271" Java(TM) SE Runtime Environment (build 1.8.0_271-b09) Java HotSpot(TM) 64-Bit Server VM (build 25.271-b09, mixed mode)
JVM组成
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平台部署就有了问题
虚拟机 目前Oracle官方使用的是HotSpot, 它最早由一家名为”Longview Technologies”公司设计,使用了很多优秀的设计理念和出色的性能,1997年该公司被SUN公司收购。后来随着JDK一起发布了HotSpot VM。目前HotSpot是最主要的 JVM。
安卓程序需要运行在JVM上,而安卓平台使用了Google自研的Java虚拟机——Dalvid,适合于内存、处理器能力有限系统。
GC (Garbage Collection) 垃圾收集器
在堆内存中如果创建的对象不再使用,仍占用着内存,此时即为垃圾.需要即使进行垃圾回收,从而释放内存空间给其它对象使用
其实不同的开发语言都有垃圾回收问题,C,C++需要程序员人为回收,造成开发难度大,容易出错等问题,但执行效率高,而JAVA和Python中不需要程序员进行人为的回收垃圾,而由JVM或相关程序自动回收垃圾,减轻程序员的开发难度,但可能会造成执行效率低下
堆内存里面经常创建、销毁对象,内存也是被使用、被释放。如果不妥善处理,一个使用频繁的进程, 可能会出现虽然有足够的内存容量,但是无法分配出可用内存空间,因为没有连续成片的内存了,内存全是碎片化的空间。
所以需要有合适的垃圾回收机制,确保正常释放不再使用的内存空间,还需要保证内存空间尽可能的保持一定的连续
对于垃圾回收,需要解决三个问题
Garbage 垃圾确定方法
引用计数: 每一个堆内对象上都与一个私有引用计数器,记录着被引用的次数,引用计数清零,该对象所占用堆内存就可以被回收。循环引用的对象都无法将引用计数归零,就无法清除。Python中即使用此种方式
根搜索(可达)算法 Root Searching
垃圾回收基本算法 标记-清除 Mark-Sweep 分垃圾标记阶段和内存释放两个阶段。
标记阶段,找到所有可访问对象打个标记。清理阶段,遍历整个堆
对未标记对象(即不再使用的对象)逐一进行清理。
特点:
优点:算法简单
缺点:标记-清除最大的问题会造成内存碎片,但是不浪费空间,效率较高(如果对象较多时,逐一删除效率也会受到影响)
标记-压缩 (压实)Mark-Compact 分垃圾标记阶段和内存整理两个阶段。
标记阶段,找到所有可访问对象打个标记。
内存清理阶段时,整理时将对象向内存一端移动,整理后存活对象连续的集中在内存一端。
特点:
标记-压缩算法好处是整理后内存空间连续分配,有大段的连续内存可分配,没有内存碎片。
缺点是内存整理过程有消耗,效率相对低下
复制 Copying
先将可用内存分为大小相同两块区域A和B,每次只用其中一块,比如A。当A用完后,则将A中存活的对象复制到B。复制到B的时候连续的使用内存,最后将A一次性清除干净。
特点
好处是没有碎片,复制过程中保证对象使用连续空间,且一次性清除所有垃圾,所以即使对象很多,收回效率也很高
缺点是比较浪费内存 ,只能使用原来一半内存,因为内存对半划分了,复制过程毕竟也是有代价。
多种算法总结 没有最好的算法,在不同场景选择最合适的算法
效率: 复制算法>标记清除算法> 标记压缩算法
内存整齐度: 复制算法=标记压缩算法> 标记清除算法
内存利用率: 标记压缩算法=标记清除算法>复制算法
STW 对于大多数垃圾回收算法而言,GC线程工作时,停止所有工作的线程,称为Stop The World。GC 完成时,恢复其他工作线程运行。这也是JVM运行中最头疼的问题。
分代堆内存GC策略 上述垃圾回收算法都有优缺点,能不能对不同数据进行区分管理,不同分区对数据实施不同回收策略,分而治之。
堆内存分代 将heap内存空间分为三个不同类别: 年轻代、老年代、持久代
Heap堆内存分为
年轻代Young:Young Generation
伊甸园区eden: 只有一个,刚刚创建的对象
幸存(存活)区Servivor Space:有2个幸存区,一个是from区,一个是to区。大小相等、地位相同、可互换。
from 指的是本次复制数据的源区
to 指的是本次复制数据的目标区
老年代Tenured:Old Generation, 长时间存活的对象
默认空间大小比例:
默认JVM试图分配最大内存的总内存的1/4,初始化默认总内存为总内存的1/64,年青代中heap的1/3,老年代占2/3
永久代 :JDK1.7之前使用, 即Method Area方法区,保存JVM自身的类和方法,存储JAVA运行时的环境信息, JDK1.8后 改名为 MetaSpace,此空间不存在垃圾回收,关闭JVM会释放此区域内存,此空间物理上不属于heap内存,但逻辑上存在于heap内存
永久代必须指定大小限制,字符串常量JDK1.7存放在永久代,1.8后存放在heap中
MetaSpace 可以设置,也可不设置,无上限
规律: 一般情况99%的对象都是临时对象
范例: 在tomcat 状态页可以看到以下的内存分代
范例: 查看JVM内存分配情况
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 [root@centos8 ~] public class Heap { public static void main(String[] args){ //返回JVM试图使用的最大内存,字节单位 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@centos8 ~] [root@centos8 ~] max=243269632字节 232.0MB total=16252928字节 15.5MB [root@centos8 ~] max=243269632字节 232.0MB total=16252928字节 15.5MB Heap def new generation total 4928K, used 530K [0x00000000f1000000, 0x00000000f1550000, 0x00000000f6000000) eden space 4416K, 12% used [0x00000000f1000000, 0x00000000f1084a60, 0x00000000f1450000) from space 512K, 0% used [0x00000000f1450000, 0x00000000f1450000, 0x00000000f14d0000) to space 512K, 0% used [0x00000000f14d0000, 0x00000000f14d0000, 0x00000000f1550000) tenured generation total 10944K, used 0K [0x00000000f6000000, 0x00000000f6ab0000, 0x0000000100000000) the space 10944K, 0% used [0x00000000f6000000, 0x00000000f6000000, 0x00000000f6000200, 0x00000000f6ab0000) Metaspace used 2525K, capacity 4486K, committed 4864K, reserved 1056768K class space used 269K, capacity 386K, committed 512K, reserved 1048576K [root@centos8 ~] 15.50
年轻代回收 Minor GC
起始时,所有新建对象(特大对象直接进入老年代)都出生在eden,当eden满了,启动GC 。这个称为Young GC 或者 Minor GC 。
先标记 eden存活对象,然后将存活对象复制 到s0(假设本次是s0,也可以是s1,它们可以调换),eden剩余所有空间都清空。GC完成 。
继续新建对象,当eden再次满了,启动GC 。
先同时标记 eden和s0中存活对象,然后将存活对象复制 到s1。将eden和s0清空,此次GC完成
继续新建对象,当eden满了,启动GC 。
先标记 eden和s1中存活对象,然后将存活对象复制 到s0。将eden和s1清空,此次GC完成
以后就重复上面的步骤。
通常场景下,大多数对象都不会存活很久,而且创建活动非常多,新生代就需要频繁垃圾回收。
但是,如果一个对象一直存活,它最后就在from、to来回复制,如果from区中对象复制次数达到阈值(默认15次,CMS为6次,可通过java的选项 -XX:MaxTenuringThreshold=N 指定),就直接复制到老年代。
老年代回收 Major GC 进入老年代的数据较少,所以老年代区被占满的速度较慢,所以垃圾回收也不频繁。
如果老年代也满了,会触发老年代GC,称为Old GC或者 Major GC。
由于老年代对象一般来说存活次数较长,所以较常采用标记-压缩算法。
当老年代满时,会触发 Full GC,即对所有”代”的内存进行垃圾回收
Minor GC比较频繁,Major GC较少。但一般Major GC时,由于老年代对象也可以引用新生代对象,所以先进行一次Minor GC,然后在Major GC会提高效率。可以认为回收老年代的时候完成了一次Full GC。
所以可以认为 MajorGC = FullGC
GC 触发条件
Minor GC 触发条件:当eden区满了触发
Full GC 触发条件:
老年代满了
System.gc()手动调用。不推荐
年轻代:
老年代:
java 内存调整相关参数 JVM 内存常用相关参数 Java 命令行参考文档: https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
帮助:man java
选项分类
-选项名称 此为标准选项,所有HotSpot都支持
-X选项名称 此为稳定的非标准选项
-XX:选项名称 非标准的不稳定选项,下一个版本可能会取消
参数
说明
举例
-Xms
设置应用程序初始使用的堆内存大小(年轻代+老年代)
-Xms2g
-Xmx
设置应用程序能获得的最大堆内存 早期JVM不建议超过32G,内存管理效率下降
-Xmx4g
-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:S0:S1=6:1:1
-Xss
设置每个线程私有的栈空间大小,依据具体线程大小和数量
-Xss256k
范例: 查看java的选项帮助
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 [root@centos ~] Usage: java [-options] class [args...] (to execute a class) or java [-options] -jar jarfile [args...] (to execute a jar file) where options include: -d32 use a 32-bit data model if available -d64 use a 64-bit data model if available -server to select the "server" VM The default VM is server. ······ -X print help on non-standard options ······ [root@centos8 ~] -Xmixed mixed mode execution (default) -Xint interpreted mode execution only -Xbootclasspath:<directories and zip/jar files separated by :> set search path for bootstrap classes and resources -Xbootclasspath/a:<directories and zip/jar files separated by :> append to end of bootstrap class path -Xbootclasspath/p:<directories and zip/jar files separated by :> prepend in front of bootstrap class path -Xdiag show additional diagnostic messages -Xnoclassgc disable class garbage collection -Xincgc enable incremental garbage collection -Xloggc:<file> log GC status to a file with time stamps -Xbatch disable background compilation -Xms<size> set initial Java heap size -Xmx<size> set maximum Java heap size -Xss<size> set java thread stack size -Xprof output cpu profiling data -Xfuture enable strictest checks, anticipating future default -Xrs reduce use of OS signals by Java/VM (see documentation) -Xcheck:jni perform additional checks for JNI functions -Xshare:off do not attempt to use shared class data -Xshare:auto use shared class data if possible (default) -Xshare:on require using shared class data, otherwise fail. -XshowSettings show all settings and continue -XshowSettings:all show all settings and continue -XshowSettings:vm show all vm related settings and continue -XshowSettings:properties show all property settings and continue -XshowSettings:locale [root@centos8 ~] [Global flags] intx ActiveProcessorCount = -1 {product} uintx AdaptiveSizeDecrementScaleFactor = 4 {product} uintx AdaptiveSizeMajorGCDecayTimeScale = 10 {product} uintx AdaptiveSizePausePolicy = 0 {product} uintx AdaptiveSizePolicyCollectionCostMargin = 50 {product} uintx AdaptiveSizePolicyInitializingSteps = 20 {product} uintx AdaptiveSizePolicyOutputInterval = 0 {product} uintx AdaptiveSizePolicyWeight = 10 {product} uintx AdaptiveSizeThroughPutPolicy = 0 {product} ....... [root@centos8 ~] [Global flags] intx ActiveProcessorCount = -1 {product} uintx AdaptiveSizeDecrementScaleFactor = 4 {product} uintx AdaptiveSizeMajorGCDecayTimeScale = 10 {product} uintx AdaptiveSizePausePolicy = 0 {product} uintx AdaptiveSizePolicyCollectionCostMargin = 50 {product} uintx AdaptiveSizePolicyInitializingSteps = 20 {product} ....... [root@centos8 ~] -XX:InitialHeapSize=15598528 -XX:MaxHeapSize=249576448 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC
范例: 查看和指定 JVM 内存分配
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 [root@centos8 ~] public class Heap { public static void main(String[] args){ //返回JVM试图使用的最大内存,字节单位 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@centos8 ~] [root@centos8 ~] /usr/local/jdk/lib/:/usr/local/jdk/jre/lib/ [root@centos8 ~] [root@centos8 ~] max=243269632字节 232.0MB total=16252928字节 15.5MB Heap def new generation total 4928K, used 530K [0x00000000f1000000, 0x00000000f1550000, 0x00000000f6000000) eden space 4416K, 12% used [0x00000000f1000000, 0x00000000f1084a60, 0x00000000f1450000) from space 512K, 0% used [0x00000000f1450000, 0x00000000f1450000, 0x00000000f14d0000) to space 512K, 0% used [0x00000000f14d0000, 0x00000000f14d0000, 0x00000000f1550000) tenured generation total 10944K, used 0K [0x00000000f6000000, 0x00000000f6ab0000, 0x0000000100000000) the space 10944K, 0% used [0x00000000f6000000, 0x00000000f6000000, 0x00000000f6000200, 0x00000000f6ab0000) Metaspace used 2525K, capacity 4486K, committed 4864K, reserved 1056768K class space used 269K, capacity 386K, committed 512K, reserved 1048576K [root@centos8 ~] max=1037959168字节 989.875MB total=1037959168字节 989.875MB Heap def new generation total 314560K, used 11185K [0x00000000c0000000, 0x00000000d5550000, 0x00000000d5550000) eden space 279616K, 4% used [0x00000000c0000000, 0x00000000c0aec408, 0x00000000d1110000) from space 34944K, 0% used [0x00000000d1110000, 0x00000000d1110000, 0x00000000d3330000) to space 34944K, 0% used [0x00000000d3330000, 0x00000000d3330000, 0x00000000d5550000) tenured generation total 699072K, used 0K [0x00000000d5550000, 0x0000000100000000, 0x0000000100000000) the space 699072K, 0% used [0x00000000d5550000, 0x00000000d5550000, 0x00000000d5550200, 0x0000000100000000) Metaspace used 2525K, capacity 4486K, committed 4864K, reserved 1056768K class space used 269K, capacity 386K, committed 512K, reserved 1048576K [root@centos8 ~] 1037959168
范例: 查看 OOM
1 2 3 4 5 6 7 8 9 10 11 12 13 [root@centos8 ~] import java. util. Random; public class HeapOom2 { public static void main(String[] args) { String str = "I am wangxiaochun" ; while (true ){ str += str + new Random().nextInt(88888888); //生成0到88888888之间的随机数字 } } } [root@centos8 ~] [root@centos8 ~]
JDK 工具监控使用情况 案例1: jvisualvm工具 范例: 指定参数运行Java程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 javac HelloWorld.java java -classpath . -Xms512m -Xmx1g HelloWorld [root@tomcat ~] public class HelloWorld extends Thread { public static void main(String[] args) { try { while (true ) { Thread.sleep (2000); System.out.println("hello magedu" ); } } catch (InterruptedException e) { e.printStackTrace(); } } } [root@tomcat ~] [root@tomcat ~] -rw-r--r-- 1 root root 590 Jul 13 16:31 HelloWorld.class [root@tomcat ~] HelloWorld.class: compiled Java class data, version 52.0 (Java 1.8) [root@tomcat ~] hello magedu hello magedu hello magedu [root@tomcat ~] /usr/local/jdk/lib/:/usr/local/jdk/jre/lib/ [root@tomcat ~] [root@tomcat ~] [root@tomcat ~] [root@tomcat ~] HelloWorld hello magedu [root@tomcat ~] 21299 Main 21418 Jps 21407 HelloWorld [root@tomcat ~] [root@tomcat ~] [root@tomcat ~] [roottomcat ~] /usr/local/jdk/bin/jvisualvm [root@tomcat ~]
案例2: 使用 jvisualvm的 Visual GC 插件 范例: 使用 jvisualvm的 Visual GC 插件 观察 java程序的OOM
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 [root@centos8 ~]#cat HeapOom.java import java.util.ArrayList;import java.util.List;public class HeapOom { public static void main (String[] args) { List<byte []> list =new ArrayList <byte []>(); int i = 0 ; boolean flag = true ; while (flag){ try { i++; list.add(new byte [1024 * 1024 ]); Thread.sleep(1000 ); } catch (Throwable e){ e.printStackTrace(); flag =false ; System.out.println("count=" +i); } } } }
安装VirtualGC插件
运行上面的OOM程序,重新运行jvisualm, 可以看到多了一下VisualGC页标,下面的图示
当OOM时退出JAVA程序时,会显示下面图示
1 2 3 4 [root@centos8 ~] java.lang.OutOfMemoryError: Java heap space at HeapOom.main(HeapOom.java:12) count=229
Jprofiler定位OOM的问题原因 JProfiler是一款功能强大的Java开发分析工具,它可以快速的帮助用户分析出存在的错误,软件还可对需要的显示类进行标记,包括了内存的分配情况和信息的视图等
JProfiler官网:http://www.ej-technologies.com/products/jprofiler/overview.html
范例: 安装jprofiler工具定位OOM原因和源码问题的位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 [root@centos8 ~] [root@centos8 ~] import java.util.ArrayList; import java.util.List; public class HeapOom { public static void main(String[] args) { List<byte[]> list =new ArrayList<byte[]>(); int i = 0; boolean flag = true ; while (flag){ try{ i++; list.add(new byte[1024* 1024]);//每次增加一个1M大小的数组对象 Thread.sleep (1000); } catch(Throwable e){ e.printStackTrace(); flag = false ; System.out.println("count=" +i);//记录运行的次数 } } } } [root@centos8 ~] [root@centos8 ~] java.lang.OutOfMemoryError: Java heap space Dumping heap to java_pid96271.hprof ... Heap dump file created [8134070 bytes in 0.031 secs] java.lang.OutOfMemoryError: Java heap space at HeapOom.main(HeapOom.java:12) count=8 [root@centos8 ~] import java. util. Random; public class HeapOom2 { public static void main(String[] args) { String str = "I am lao wang" ; while (true ){ str += str + new Random().nextInt(88888888); } } } [root@centos8 ~] [root@centos8 ~] java.lang.OutOfMemoryError: Java heap space Dumping heap to java_pid96339.hprof ... Heap dump file created [4925877 bytes in 0.016 secs] Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOf(Arrays.java:3332) at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124) at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:674) at java.lang.StringBuilder.append(StringBuilder.java:208) at HeapOom2.main(HeapOom2.java:6)
下载并安装Jprofiler
下载并安装JProfiler(傻瓜式安装略)后,双击打开前面生成的两个文件java_pid96271.hprof和java_pid96339,可以看到下面显示,从中分析OOM原因
Tomcat的JVM参数设置 默认不指定,-Xmx大约使用了1/4的内存,当前本机内存指定约为1G。
在bin/catalina.sh中增加一行
1 2 3 4 5 6 JAVA_OPTS="-server -Xms128m -Xmx512m -XX:NewSize=48m -XX:MaxNewSize=200m" cygwin=false darwin=false
1 2 -server:VM运行在server模式,为在服务器端最大化程序运行速度而优化 -client:VM运行在Client模式,为客户端环境减少启动时间而优化
重启 Tomcat 观察
1 2 [root@tomcat ~] tomcat 22194 5.1 15.1 2497008 123480 ? Sl 13:31 0:03 /usr/local/jdk/jre/bin/java -
浏览器访问server status页面,可以看到以下页面
垃圾收集方式 按工作模式 不同:指的是GC线程和工作线程是否一起运行
独占垃圾回收器:只有GC在工作,STW 一直进行到回收完毕,工作线程才能继续执行
并发垃圾回收器:让GC线程 垃圾回收某些阶段可以和工作线程 一起进行,如:标记阶段并行,回收阶段仍然串行
按回收线程 数:指的是GC线程是否串行或并行执行
串行垃圾回收器:一个GC线程完成回收工作
并行垃圾回收器:多个GC线程同时一起完成回收工作,充分利用CPU资源
调整策略 对JVM调整策略应用极广
在WEB领域中Tomcat等
在大数据领域Hadoop生态各组件
在消息中间件领域的Kafka等
在搜索引擎领域的ElasticSearch、Solr等
注意: 在不同领域和场景对JVM需要不同的调整策略
减少 STW 时长,串行变并行
减少 GC 次数,要分配合适的内存大小
一般情况下,大概可以使用以下原则:
客户端或较小程序,内存使用量不大,可以使用串行回收
对于服务端大型计算,可以使用并行回收
大型WEB应用,用户端不愿意等待,尽量少的STW,可以使用并发回收
垃圾回收器 常用垃圾回收器
按分代设置不同垃圾回收器 新生代
新生代串行收集器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之后才开始提供,从HotSpot虚拟机的垃圾收集器的图中也可以看出,Parallel Scavenge 收集器无法与CMS收集器配合工作,因为一个是为了吞吐量,一个是为了客户体验(也就是暂停时间的缩短)
此为默认的新老年代的垃圾回收器
CMS (Concurrent Mark Sweep并发标记清除算法) 收集器
在某些阶段尽量使用和工作线程一起运行,减少STW时长(200ms以内), 提升响应速度,是互联网服务端BS系统上较佳的回收算法
分为4个阶段:初始标记、并发标记、重新标记、并发清除,在初始标记、重新标记时需要 STW。
初始标记:此过程需要STW(Stop The Word),只标记一下GC Roots能直接关联到的对象,速度很快。
并发标记:就是GC Roots进行扫描可达链的过程,为了找出哪些对象需要收集。这个过程远远慢于初始标记,但它是和用户线程一起运行的,不会出现STW,所有用户并不会感受到。
重新标记:为了修正在并发标记期间,用户线程产生的垃圾,这个过程会比初始标记时间稍微长一点,但是也很快,和初始标记一样会产生STW。
并发清理:在重新标记之后,对现有的垃圾进行清理,和并发标记一样也是和用户线程一起运行的,耗时较长(和初始标记比的话),不会出现STW。
由于整个过程中,耗时最长的并发标记和并发清理都是与用户线程一起执行的,所以总体上来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。
以下收集器不再按明确的分代单独设置
G1(Garbage First)收集器
是最新垃圾回收器,从JDK1.6实验性提供,JDK1.7发布,其设计目标是在多处理器、大内存服务器端提供优于CMS收集器的吞吐量和停顿控制的回收器。JDK9将G1设为默认的收集器,建议 JDK9版本以后使用。
基于标记压缩 算法,不会产生大量的空间碎片,有利于程序的长期执行。
分为4个阶段:初始标记、并发标记、最终标记、筛选回收。并发标记并发执行,其它阶段STW只有GC线程并行执行。
G1收集器是面向服务端的收集器,它的思想就是首先回收尽可能多的垃圾(这也是Garbage-First名字的由来)
G1能充分的利用多CPU,多核环境下的硬件优势,使用多个CPU来缩短STW停顿的时间**(10ms以内)**。
可预测的停顿:这是G1相对于CMS的另一大优势,G1和CMS一样都是关注于降低停顿时间,但是G1能够让使用者明确的指定在一个M毫秒的时间片段内,消耗在垃圾收集的时间不得超过N毫秒。
通过此选项指定: +UseG1GC
ZGC收集器: 减少STW时长(1ms 以内), 可以PK C++的效率,目前实验阶段
Shenandoah收集器: 和ZGC竞争关系,目前实验阶段
Epsilon收集器: 调试 JDK 使用,内部使用,不用于生产环境
JVM 1.8 默认的垃圾回收器:PS + ParallelOld,所以大多数都是针对此进行调优
垃圾收集器设置 优化调整Java 相关参数的目标: 尽量减少FullGC和STW
通过以下选项可以单独指定新生代、老年代的垃圾收集器
-server 指定为Server模式,也是默认值,一般使用此工作模式
-XX:+UseSerialGC
运行在Client模式下,新生代是Serial, 老年代使用SerialOld
-XX:+UseParNewGC
新生代使用ParNew,老年代使用SerialOld
-XX:+UseParallelGC
运行于server模式下,新生代使用Parallel Scavenge, 老年代使用 Parallel Old
-XX:+UseParallelOldGC
新生代使用Paralell Scavenge, 老年代使用Paralell Old,和上面 -XX:+UseParallelGC相同
-XX:ParallelGCThreads=N,在关注吞吐量的场景使用此选项增加并行线程数
-XX:+UseConcMarkSweepGC
新生代使用ParNew, 老年代优先使用CMS,备选方式为Serial Old
响应时间要短,停顿短使用这个垃圾收集器
-XX:CMSInitiatingOccupancyFraction=N,N为0-100整数表示达到老年代的大小的百分比多少触发回收
-XX:+UseCMSCompactAtFullCollection 开启此值,在CMS收集后,进行内存碎片整理
-XX:CMSFullGCsBeforeCompaction=N 设定多少次CMS后,进行一次内存碎片整理
-XX:+CMSParallelRemarkEnabled 降低标记停顿
范例:
1 2 -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=80 -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=5
范例:查看默认垃圾回收器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 [root@rocky8 ~] -XX:InitialHeapSize=31964352 -XX:MaxHeapSize=511429632 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC java version "1.8.0_301" Java(TM) SE Runtime Environment (build 1.8.0_301-b09) Java HotSpot(TM) 64-Bit Server VM (build 25.301-b09, mixed mode) C:\Users\wang>java -XX:+PrintCommandLineFlags -version -XX:InitialHeapSize=1071317952 -XX:MaxHeapSize=17141087232 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC java version "1.8.0_281" Java(TM) SE Runtime Environment (build 1.8.0_281-b09) Java HotSpot(TM) 64-Bit Server VM (build 25.281-b09, mixed mode)
范例:查看垃圾回收器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 [root@rocky8 ~] [GC (Allocation Failure) [DefNew: 2376K->320K(3072K), 0.0004583 secs] 2376K- >657K(9920K), 0.0004984 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [DefNew: 2374K->0K(3072K), 0.0003771 secs] 2711K- >1257K(9920K), 0.0003866 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [DefNew: 2447K->0K(3072K), 0.0004045 secs] 3704K- >2857K(9920K), 0.0004243 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [DefNew: 1643K->0K(3072K), 0.0001604 secs] 4500K- >4457K(9920K), 0.0001758 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [DefNew: 1600K->0K(3072K), 0.0002896 secs][Tenured: 6057K->2856K(6848K), 0.0009171 secs] 6057K->2856K(9920K), [Metaspace: 2480K- >2480K(1056768K)], 0.0012276 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [root@rocky8 ~] Java HotSpot(TM) 64-Bit Server VM warning: Using the ParNew young collector with the Serial old collector is deprecated and will likely be removed in a future release [GC (Allocation Failure) [ParNew: 2373K->265K(3072K), 0.0008588 secs] 2373K- >664K(9920K), 0.0008885 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] [GC (Allocation Failure) [ParNew: 2316K->99K(3072K), 0.0007617 secs] 2716K- >1559K(9920K), 0.0007802 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [ParNew: 2543K->24K(3072K), 0.0006585 secs] 4002K- >3082K(9920K), 0.0006758 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [root@rocky8 ~] [GC (Allocation Failure) [PSYoungGen: 1953K->491K(2560K)] 1953K->755K(9728K), 0.0009754 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [PSYoungGen: 2512K->492K(2560K)] 2775K->755K(9728K), 0.0005462 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [PSYoungGen: 2512K->96K(2560K)] 4359K->2735K(9728K), 0.0006484 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [PSYoungGen: 888K->96K(2560K)] 5111K->4319K(9728K), 0.0004700 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [PSYoungGen: 96K->96K(2560K)] 4319K->4319K(9728K), 0.0004402 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (Allocation Failure) [PSYoungGen: 96K->0K(2560K)] [ParOldGen: 4223K- >2632K(7168K)] 4319K->2632K(9728K), [Metaspace: 2480K->2480K(1056768K)], 0.0020238 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [root@rocky8 ~] [GC (Allocation Failure) [PSYoungGen: 1933K->486K(2560K)] 1933K->748K(9728K), 0.0009954 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [PSYoungGen: 2482K->486K(2560K)] 2744K->748K(9728K), 0.0005175 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [PSYoungGen: 2482K->96K(2560K)] 4308K->2703K(9728K), 0.0006469 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [PSYoungGen: 878K->96K(2560K)] 5049K->4267K(9728K), 0.0005057 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [root@rocky8 ~] [GC (Allocation Failure) [ParNew: 2365K->273K(3072K), 0.0004709 secs] 2365K- >674K(9920K), 0.0005774 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [ParNew: 2324K->100K(3072K), 0.0008058 secs] 2726K- >1563K(9920K), 0.0008263 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [ParNew: 2543K->25K(3072K), 0.0004260 secs] 4006K- >3085K(9920K), 0.0004505 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [ParNew: 1665K->6K(3072K), 0.0004862 secs] 4726K- >4664K(9920K), 0.0005126 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
范例: 查看默认模式
1 2 3 4 5 6 7 [root@centos8 ~] -server to select the "server" VM The default VM is server. [root@centos8 ~] -server KNOWN -client IGNORE
范例: 指定垃圾回收设置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 [root@tomcat ~] ...... JAVA_OPTS="-server -Xmx512m -Xms128m -XX:NewSize=48m -XX:MaxNewSize=200m -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=5" cygwin=false darwin=false os400=false ....... [root@tomcat ~] [root@tomcat ~]
开启垃圾回收输出统计信息,适用于调试环境的相关选项
-XX:+PrintGC 输出GC信息
-XX:+PrintGCDetails 输出GC详细信息
-XX:+PrintGCTimeStamps 与前两个组合使用,在信息上加上一个时间戳
-XX:+PrintHeapAtGC 生成GC前后椎栈的详细信息,日志会更大
注意: 以上适用调试环境,生产环境请移除这些参数,否则有非常多的日志输出
JVM相关工具 JVM 工具概述 $JAVA_HOME/bin下
命令
说明
jps
查看所有JVM进程的PID和主类名称
jinfo
实时查看或修改JVM进程的运行环境参数(支持-dump生成堆配置快照)
jstat
监控JVM资源和性能(类加载、GC、JIT编译等),支持定时采样
jstack
查看JVM进程内所有线程的堆栈状态,支持生成线程快照用于死锁分析
jmap
生成堆内存使用详情报告(包括直方图、对象统计等),支持导出堆转储文件
jhat
分析Java堆转储文件(需配合jmap使用),内置HTTP服务器展示分析结果
jconsole
基于JMX的图形化监控工具(支持内存、线程、类、MBean等实时监控)
jvisualvm
多合一图形化诊断工具(包含内存/CPU分析、堆转储分析、方法级性能采样等)
jps JVM 进程状态工具
格式
1 2 3 4 5 6 7 8 jps:Java virutal machine Process Status tool, jps [-q] [-mlvV] [<hostid >] -q:静默模式; -v:显示传递给jvm的命令行参数; -m:输出传入main方法的参数; -l:输出main类或jar完全限定名称; -v:显示通过flag文件传递给jvm的参数; [<hostid >]:主机id ,默认为localhost;
范例:
1 2 3 4 5 6 7 8 9 10 11 [root@tomcat ~] 22357 Jps 21560 Main 21407 HelloWorld [root@tomcat ~] 21560 org.netbeans.Main -Djdk.home=/usr/local/jdk - Dnetbeans.default_userdir_root=/root/.visualvm - Dnetbeans.dirs =/usr/local/jdk/lib/visualvm/visualvm:/usr/local/jdk/lib/visualvm/
jinfo 输出给定的java进程的所有配置信息
格式:
1 2 3 4 jinfo [option] <pid> -flags:打印 VM flags -sysprops:to print Java system properties -flag <name>:to print the value of the named VM flag
范例:
1 2 3 4 5 6 7 8 9 10 11 12 [root@tomcat ~] 22357 Jps 21560 Main 21407 HelloWorld [root@tomcat ~] Attaching to process ID 21407, please wait ... Debugger attached successfully. Server compiler detected. JVM version is 25.241-b07 Java System Properties:
jstat 输出指定的java进程的统计信息
格式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 jstat -help |-options jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]] [<interval> [<count>]] interval:时间间隔,单位是毫秒; count:显示的次数; -class:class loader -compiler:JIT -gc:gc -gccapacity:统计堆中各代的容量 -gccause: -gcmetacapacity -gcnew:新生代 -gcnewcapacity -gcold:老年代 -gcoldcapacity -gcutil -printcompilation
范例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 [root@tomcat ~] S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT 8704.0 8704.0 1563.6 0.0 69952.0 58033.0 174784.0 0.0 9344.0 8830.0 1152.0 1013.0 2 0.050 0 0.000 0.050 S0C:S0区容量 YGC:新生代的垃圾回收次数 YGCT:新生代垃圾回收消耗的时长; FGC:Full GC的次数 FGCT:Full GC消耗的时长 GCT:GC消耗的总时长 [root@tomcat ~] S0C S1C S0U S1U TT MTT DSS EC EU YGC YGCT 8704.0 8704.0 1563.6 0.0 15 15 4352.0 69952.0 62794.3 2 0.050 8704.0 8704.0 1563.6 0.0 15 15 4352.0 69952.0 62794.3 2 0.050 8704.0 8704.0 1563.6 0.0 15 15 4352.0 69952.0 63074.5 2 0.050
jstack 程序员常用堆栈情况查看工具
jstack:查看指定的java进程的线程栈的相关信息
格式:
1 2 3 4 5 jstack [-l] <pid> jstack -F [-m] [-l] <pid> -l:long listings,会显示额外的锁信息,因此,发生死锁时常用此选项 -m:混合模式,既输出java堆栈信息,也输出C/C++堆栈信息 -F:当使用"jstack -l PID" 无响应,可以使用-F强制输出信息
范例:
1 2 3 4 5 6 7 8 9 [root@tomcat ~] 2020-02-15 13:49:56 Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.241-b07 mixed mode): "RMI TCP Connection(4)-10.0.0.101" tid=0x00007f279c29e800 nid=0x5753 runnable [0x00007f279b181000] java.lang.Thread.State: RUNNABLE at java.net.SocketInputStream.socketRead0(Native Method)
jmap Memory Map, 用于查看堆内存的使用状态
1 2 3 4 5 6 7 8 9 [root@tomcat ~] Attaching to process ID 21407, please wait ... Debugger attached successfully. Server compiler detected. JVM version is 25.241-b07 using thread-local object allocation. Mark Sweep Compact GC ·····
jhat Java Heap Analysis Tool 堆分析工具
格式
1 2 3 jmap [option] <pid> jmap -heap <pid>
范例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 [root@t1 ~] Attaching to process ID 96334, please wait ... Debugger attached successfully. Server compiler detected. JVM version is 25.251-b08 using thread-local object allocation. Mark Sweep Compact GC Heap Configuration: MinHeapFreeRatio = 40 MaxHeapFreeRatio = 70 MaxHeapSize = 251658240 (240.0MB) NewSize = 5570560 (5.3125MB) MaxNewSize = 83886080 (80.0MB) OldSize = 11206656 (10.6875MB) jmap -histo[:live] <pid> live:只统计活动对象; jmap -dump:<dump-options> <pid> dump-options: live dump only live objects; if not specified, all objects in the heap are dumped. format=b binary format file=<file> dump heap to <file>
jconsole 和 JMX 图形化工具,可以用来查看Java进程信息
JMX(Java Management Extensions,即Java管理扩展)是一个为JAVA应用程序、设备、系统等植入管理功能的框架。JMX可以跨越一系列异构操作系统平台、系统体系结构和网络传输协议,灵活的开发无缝集成的系统、网络和服务管理应用。
JMX最常见的场景是监控Java程序的基本信息和运行情况,任何Java程序都可以开启JMX,然后使用JConsole或Visual VM进行预览。
1 2 3 4 5 6 7 8 java \ -Dcom.sun.management.jmxremote \ -Djava.rmi.server.hostname=10.0.0.100 \ -Dcom.sun.management.jmxremote.port=12345 \ -Dcom.sun.management.jmxremote.authenticate=false \ -Dcom.sun.management.jmxremote.ssl=false \ -jar app.jar|app.war
在 tomcat 开启远程 JMX 支持 Zabbix 监控,如下配置
1 2 3 4 5 6 7 vim /usr/local/tomcat/bin/catalina.sh CATALINA_OPTS="$CATALINA_OPTS -Dcom.sun.management.jmxremote #启用远程监控JMX -Dcom.sun.management.jmxremote.port=XXXXX #默认启动的JMX端口号,要和zabbix添加主机时候的端口一致即可 -Dcom.sun.management.jmxremote.authenticate=false #不使用用户名密码 -Dcom.sun.management.jmxremote.ss1=false #不使用ssl认证 -Djava.rmi.server.hostname=<JAVA主机IP>"
下图是使用Jconsle通过JMX查看Java程序的运行信息
范例: 开启远程JMX功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 [root@tomcat ~] ....... CATALINA_OPTS="$CATALINA__OPTS \ -Dcom.sun.management.jmxremote \ -Djava.rmi.server.hostname=10.0.0.101 \ -Dcom.sun.management.jmxremote.port=12345 \ -Dcom.sun.management.jmxremote.authenticate=false \ -Dcom.sun.management.jmxremote.ssl=false" ....... [root@tomcat ~] [root@tomcat ~] State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 128 0.0.0.0:22 0.0.0.0:* LISTEN 0 100 127.0.0.1:25 0.0.0.0:* LISTEN 0 128 [::]:22 [::]:* LISTEN 0 50 *:12345 *:* LISTEN 0 100 [::1]:25 [::]:* LISTEN 0 50 *:38563 *:* LISTEN 0 1 [::ffff:127.0.0.1]:8005 *:* LISTEN 0 50 *:40613 *:* LISTEN 0 100 *:8080 *:*
Tomcat 性能优化常用配置 内存空间优化 1 2 3 4 5 6 7 JAVA_OPTS="-server -Xms4g -Xmx4g -XX:NewSize= -XX:MaxNewSize= " -server:服务器模式 -Xms:堆内存初始化大小 -Xmx:堆内存空间上限 -XX:NewSize=:新生代空间初始化大小 -XX:MaxNewSize=:新生代空间最大值
生产案例:
1 2 3 4 5 6 7 8 9 [root@centos8 ~] 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"
线程池调整 1 2 3 4 [root@centos8 ~] ...... <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" maxThreads="2000" />
常用属性:
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
Java 程序编译 源代码克隆与编译过程说明 以github 上 java 开源项目dubbo-admin 为例
github 说明:
1 https://github.com/apache/dubbo-admin/
官方部署过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Production Setup Clone source code on develop branch git clone https://github.com/apache/dubbo-admin.git Specify registry address in dubbo-admin-server/src/main/resources/application.properties Build mvn clean package -Dmaven.test.skip=true Start mvn --projects dubbo-admin-server spring-boot:run OR cd dubbo-admin-distribution/target; java -jar dubbo-admin-0.1.jar Visit http://localhost:8080 Default username and password is root
Maven 部署准备 Maven 介绍
Maven 翻译为”专家”、”内行”,是 Apache 基金会旗下的一个纯 Java 开发的开源项目
Maven 是一个项目管理工具,可以对 Java项目进行构建、解决打包依赖等。
它为开发者提供了一套完整的构建生命周期框架,开发团队稍微投入一些时间就能够自动完成工程的基础构建配置;在有多个开发团队环境的情况下,Maven 能够在很短的时间内使得每项工作都按照标准进行;那是因为大部分的工程配置操作都非常简单并且可复用
Maven基干项目对象模型(POM project object model),它可以通过一小段描述信息(配置)来管理项目的构建;因而,每个maven项目都有一个pom.xml文件
POM( Project Object Model,项目对象模型 ) 是 Maven 工程的基本工作单元,是一个 XML 文件,包含了项目的基本信息,用于描述项目如何构建,声明项目依赖等,在执行任务或目标时,Maven 会在当前目录中查找 pom 文件,通过读取pom文件获取所需的配置信息,然后执行目标。
pom.xml 文件中可以指定以下配置:
1 2 3 4 5 6 7 项目依赖 插件 执行目标 项目构建 profile 项目版本 项目开发者列表 相关邮件列表信息 用<packaging> 指定项目打包形式,jar或者war
maven 官网
1 https://maven.apache.org/
maven 官方仓库 :
1 http://repo.maven.apache.org
maven 下载
1 2 3 4 5 6 7 8 https://maven.apache.org/download.cgi http://mirrors.tuna.tsinghua.edu.cn/apache/maven https://archive.apache.org/dist/maven/maven-3
安装maven前必须安装java 环境:
1 2 3 Maven 3.3 要求 JDK 1.7 或以上 Maven 3.2 要求 JDK 1.6 或以上 Maven 3.0/3.1 要求 JDK 1.5 或以上
注意: Maven所安装JDK的版本不影响编译应用的所依赖的JDK版本,比如Maven安装JDK11,而java应用Jpress 可以使用JDK8
maven 安装 安装方式1: 包安装 范例:Ubuntu20.04安装maven
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 [root@ubuntu2004 ~] [root@ubuntu2004 ~] openjdk version "11.0.15" 2022-04-19 OpenJDK Runtime Environment (build 11.0.15+10-Ubuntu-0ubuntu0.20.04.1) OpenJDK 64-Bit Server VM (build 11.0.15+10-Ubuntu-0ubuntu0.20.04.1, mixed mode, sharing) [root@ubuntu2004 ~] Apache Maven 3.6.3 Maven home: /usr/share/maven Java version: 11.0.15, vendor: Private Build, runtime: /usr/lib/jvm/java-11-openjdk-amd64 Default locale: zh_CN, platform encoding: UTF-8 OS name: "linux" , version: "5.4.0-110-generic" , arch : "amd64" , family: "unix" [root@ubuntu2004 ~] ...... <mirrors> <!--阿里云镜像--> <mirror> <id >nexus-aliyun</id> <mirrorOf>*</mirrorOf> <name>Nexus aliyun</name> <url>http://maven.aliyun.com/nexus/content/groups/public</url> </mirror> </mirrors> ......
范例:Ubuntu18.04安装maven
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 [root@ubuntu1804 ~] Listing... Done maven/bionic-security,bionic-security,bionic-updates,bionic-updates,now 3.6.0-1~18.04.1 all [installed] maven/bionic,bionic 3.5.2-2 all [root@ubuntu1804 ~] [root@ubuntu1804 ~] Apache Maven 3.6.0 Maven home: /usr/share/maven Java version: 11.0.11, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64 Default locale: en_HK, platform encoding: UTF-8 OS name: "linux" , version: "4.15.0-112-generic" , arch : "amd64" , family: "unix" [root@ubuntu1804 ~] <mirrors> <!--阿里云镜像--> <mirror> <id >nexus-aliyun</id> <mirrorOf>*</mirrorOf> <name>Nexus aliyun</name> <url>http://maven.aliyun.com/nexus/content/groups/public</url> </mirror> </mirrors>
范例: Rocky/CentOS/RHEL安装maven
1 2 3 4 5 6 7 8 9 10 11 12 13 14 [root@rocky8 ~] [root@rocky8 ~] [root@rocky8 ~] <mirrors> <!--阿里云镜像--> <mirror> <id >nexus-aliyun</id> <mirrorOf>*</mirrorOf> <name>Nexus aliyun</name> <url>http://maven.aliyun.com/nexus/content/groups/public</url> </mirror> </mirrors>
安装方式2: 二进制安装 官方说明:
1 http://maven.apache.org/install.html
范例: 二进制安装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 [root@ubuntu1804 ~] [root@ubuntu1804 ~] [root@ubuntu1804 ~] openjdk version "1.8.0_282" OpenJDK Runtime Environment (build 1.8.0_282-8u282-b08-0ubuntu1~18.04-b08) OpenJDK 64-Bit Server VM (build 25.282-b08, mixed mode) [root@ubuntu1804 ~] [root@ubuntu1804 ~] [root@ubuntu1804 ~] [root@ubuntu1804 ~] [root@ubuntu1804 ~] [root@ubuntu1804 ~] [root@ubuntu1804 ~] Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f) Maven home: /usr/local/maven Java version: 1.8.0_282, vendor: Private Build, runtime: /usr/lib/jvm/java-8-openjdk-amd64/jre Default locale: en_HK, platform encoding: UTF-8 OS name: "linux" , version: "4.15.0-112-generic" , arch : "amd64" , family: "unix" [root@ubuntu1804 ~] <mirrors> <!--阿里云镜像--> <mirror> <id >nexus-aliyun</id> <mirrorOf>*</mirrorOf> <name>Nexus aliyun</name> <url>http://maven.aliyun.com/nexus/content/groups/public</url> </mirror> </mirrors>
Maven 的打包命令说明 Maven工程构建的各个环节
clean: 以前编译得到的旧文件class字节码文件删除
compile:将java源程序编译成class字节码文件
test:自动测试,例如,自动调用junit程序
report:报告测试程序执行的结果
package:应用打包,动态Web工程打成war包,java工程打成jar包
install: 是指将打包得到的文件复制到仓库中指定的位置
deploy:将动态Web工程生成的war包复制到Servlet容器下,使其可以运行
范例:
1 mvn clean install package
有的时候受到测试的干扰,导致无法正在进行编译,这时候可以选择跳过测试:
1 2 3 mvn clean install package -Dmaven.test.skip=true
如果需要编译的代码很庞大,需要考虑对编译环境做一些处理,提成编译效率:
1 2 3 4 5 6 mvn -T 4 clean install package -Dmaven.test.skip=true mvn -T 2C clean install package -Dmaven.test.skip=true mvn clean install package -Dmaven.test.skip=true -Dmaven.compile.fork=true
所有的 Maven 都是建立在 JVM 上的,所以进行编译的时候还需要考虑JVM 参数优化:
1 2 3 4 5 6 7 8 9 10 11 找到“maven/bin/mvn.cmd”,如果 linux 找到“maven/bin/mvn”,配置参数是:“MAVEN_OPTS” vim /etc/profile export MAVEN_OPTS="-Xmx6g -Xms6g" 注意不要超过物理内存一半source /etc/profile
执行 java 代码编译实战案例 编译安装 spring-boot 项目 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 [root@ubuntu1804 ~] [root@ubuntu1804 ~] <mirror> <id >nexus-aliyun</id> <mirrorOf>*</mirrorOf> <name>Nexus aliyun</name> <url>http://maven.aliyun.com/nexus/content/groups/public</url> </mirror> [root@ubuntu1804 ~] [root@ubuntu1804 data] [root@ubuntu1804 data] [root@ubuntu1804 spring-boot-helloWorld] deploy Dockerfile Jenkinsfile LICENSE pom.xml README.md src [root@ubuntu1804 spring-boot-helloWorld] main test [root@ubuntu1804 spring-boot-helloWorld] ...... [INFO] --- maven-jar-plugin:3.1.1:jar (default-jar) @ spring-boot-helloworld --- [INFO] Building jar: /root/spring-boot-helloWorld/target/spring-boot-helloworld-0.9.0-SNAPSHOT.jar [INFO] [INFO] --- spring-boot-maven-plugin:2.1.3.RELEASE:repackage (repackage) @ spring-boot-helloworld --- [INFO] Replacing main artifact with repackaged archive [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 57.183 s (Wall Clock) [INFO] Finished at: 2022-06-15T09:53:14+08:00 [INFO] ------------------------------------------------------------------------ [root@ubuntu1804 spring-boot-helloWorld] classes maven-archiver spring-boot-helloworld-0.9.0-SNAPSHOT.jar.original generated-sources maven-status surefire-reports generated-test-sources spring-boot-helloworld-0.9.0-SNAPSHOT.jar test-classes [root@ubuntu1804 spring-boot-helloWorld]
1 2 3 4 5 6 7 8 9 10 11 12 13 [root@ubuntu1804 ~] State Recv-Q Send-Q Local Address:Port Peer Address:Port Process LISTEN 0 128 0.0.0.0:22 0.0.0.0:* LISTEN 0 100 127.0.0.1:25 0.0.0.0:* LISTEN 0 128 [::]:22 [::]:* LISTEN 0 100 *:8888 *:* LISTEN 0 100 [::1]:25 [::]:* [root@ubuntu1804 ~] Hello Spring Boot 2.0! [root@ubuntu1804 ~] Hello World
编译 java 程序 Jpress 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 [root@rocky8 ~] [root@rocky8 ~] [root@rocky8 ~] [root@rocky8 ~] [root@rocky8 jpress-v4.2.0] changes.txt docker Dockerfile jpress-commons jpress-service jpress-template module-article pom.xml starter-tomcat codegen docker-build.sh install.sh jpress-core jpress-service-provider jpress-web module-page README.md upgrade.sh doc docker-compose.yml jpress-addons jpress-model jpress.sql LICENSE module-product starter [root@rocky8 jpress-v4.2.0] ... [INFO] module-product-search-db ........................... SUCCESS [ 0.095 s] [INFO] module-product-search-lucene ....................... SUCCESS [ 0.101 s] [INFO] module-product-search-es ........................... SUCCESS [ 0.160 s] [INFO] module-product-search-opensearch ................... SUCCESS [ 0.120 s] [INFO] module-product-service-provider .................... SUCCESS [ 0.228 s] [INFO] module-product-web ................................. SUCCESS [ 0.274 s] [INFO] starter ............................................ SUCCESS [01:20 min] [INFO] starter-tomcat 4.0 ................................. SUCCESS [ 8.736 s] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 03:00 min [INFO] Finished at: 2021-12-15T18:18:06+08:00 [INFO] ------------------------------------------------------------------------ [root@rocky8 jpress-v4.2.0]
私有仓库 Nexus Nexus 介绍 Nexus 是一个强大的 Maven 和其它仓库的管理器,它极大地简化了自己内部仓库的维护和外部仓库的访问。
Nexus 官网:
1 https://www.sonatype.com/
官方下载说明
1 https://help.sonatype.com/repomanager3/download
官方下载页面
1 https://help.sonatype.com/repomanager3/download/download-archives---repository-manager-3
官方安装文档链接
1 https://help.sonatype.com/repomanager3/installation
安装要求
1 2 https://help.sonatype.com/repomanager3/installation/system-requirements #官方要求内存8G以上,太小比如4G以下会导致无法启动
部署 Nexus 下载并启动 Nexus 注意: : 安装Nexus建议内存至少4G以上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 [root@ubuntu1804 ~] [root@ubuntu1804 ~] openjdk version "1.8.0_282" OpenJDK Runtime Environment (build 1.8.0_282-8u282-b08-0ubuntu1~18.04-b08) OpenJDK 64-Bit Server VM (build 25.282-b08, mixed mode) [root@ubuntu1804 ~] [root@ubuntu1804 ~] -rw-r--r-- 1 root root 155M Feb 18 17:09 nexus-3.29.2-02-unix.tar.gz [root@ubuntu1804 ~] [root@ubuntu1804 ~] [root@ubuntu1804 ~] [root@ubuntu1804 ~] bin/nexus: POSIX shell script, ASCII text executable, with very long lines [root@ubuntu1804 ~] run_as_user="root" [root@ubuntu1804 ~] application-port=8081 application-host=0.0.0.0 nexus-args=${jetty.etc} /jetty.xml,${jetty.etc} /jetty-http.xml,${jetty.etc} /jetty-requestlog.xml nexus-context-path=/ nexus-edition=nexus-pro-edition nexus-features=\ nexus-pro-feature nexus.hazelcast.discovery.isEnabled=true [root@ubuntu1804 ~] -Xms2703m -Xmx2703m ...... [root@ubuntu1804 ~] WARNING: ************************************************************ WARNING: Detected execution as "root" user. This is NOT recommended! WARNING: ************************************************************ ..... 2021-02-18 17:20:21,580+0800 INFO [jetty-main-1] *SYSTEM org.eclipse.jetty.server.Server - Started @37755ms 2021-02-18 17:20:21,581+0800 INFO [jetty-main-1] *SYSTEM org.sonatype.nexus.bootstrap.jetty.JettyServer - ------------------------------------------------- Started Sonatype Nexus OSS 3.29.2-02 ------------------------------------------------- [root@ubuntu1804 ~] WARNING: ************************************************************ WARNING: Detected execution as "root" user. This is NOT recommended! WARNING: ************************************************************ OpenJDK 64-Bit Server VM warning: INFO: os::commit_memory(0x0000000717000000, 1890582528, 0) failed; error='Cannot allocate memory' (errno=12) [root@ubuntu1804 ~] [root@ubuntu1804 ~] [root@ubuntu1804 ~] [root@ubuntu1804 ~] LISTEN 0 50 0.0.0.0:8081 0.0.0.0:* users :(("java",pid=20564 ,fd=798 )) LISTEN 0 1 127.0.0.1:38147 0.0.0.0:* users :(("java",pid=20564 ,fd=125 )) [root@ubuntu1804 ~] [Unit] Description=nexus service After=network.target [Service] Type=forking LimitNOFILE=65536 ExecStart=/usr/local/nexus/bin/nexus start ExecStop=/usr/local/nexus/bin/nexus stop User=root Restart=on-abort [Install] WantedBy=multi-user.target [root@ubuntu1804 ~] [root@ubuntu1804 ~] [root@ubuntu1804 ~] Aug 4 12:20:23 ubuntu1804 systemd[1]: Starting nexus service... Aug 4 12:20:23 ubuntu1804 nexus[9534]: WARNING: ************************************************************ Aug 4 12:20:23 ubuntu1804 nexus[9534]: WARNING: Detected execution as "root" user. This is NOT recommended! Aug 4 12:20:23 ubuntu1804 nexus[9534]: WARNING: ************************************************************ Aug 4 12:20:23 ubuntu1804 nexus[9534]: Starting nexus Aug 4 12:20:23 ubuntu1804 systemd[1]: Started nexus service. Aug 4 12:21:58 ubuntu1804 systemd[1]: Reloading.
登录 web 界面初始化 通过浏览器访问
默认打开无法进行管理
需要在右上角点Sign in 进行登录才能进行管理
查看默认密码
1 2 [root@ubuntu1804 ~] f88248fc-2589-44da-af76-4b01855591b8
设置密码,使用admin用户和上面的密码登录
重设密码
启用匿名访问将默认允许未经授权的下载,浏览和搜索存储库内容,可以通过编辑分配给匿名用户的角色来更改匿名用户的权限。
生产建议打开匿名访问功能,无需登录就可以下载资源
配置完成
验证默认仓库 默认仓库有以下 type 类型
1 2 3 Hosted:本地仓库,通常我们会部署自己的构件到这一类型的仓库,比如公司的第三方库 Proxy:代理仓库,它们被用来代理远程的公共仓库,如maven 中央仓库(官方仓库) Group:仓库组,用来合并多个 hosted/proxy 仓库,当你的项目希望在多个repository 使用资源时就不需要多次引用了,只需要引用一个 group 即可
Maven的仓库优化配置 默认仓库maven-central使用国外仓库地址,可修改为如下的国内镜像地址进行加速
1 http://maven.aliyun.com/nexus/content/groups/public
可以修改为国内镜像,参考阿里云以下说明
1 https://maven.aliyun.com/mvn/guide
配置邮件
使用 Nexus 构建私有 Yum 和 Apt 仓库 通过 Nexus 构建为公司内网 Yum 和 Apt 仓库,代理至阿里云镜像安装包
使用 Nexus 构建私有 Yum 仓库 创建 Blob Store 阿里云镜像地址
1 https://mirrors.aliyun.com/zabbix/zabbix/5.0/rhel/7/x86_64/
创建自定义存储存放仓库数据
创建 Yum 仓库 1 setting--Create repository—yum(proxy)
默认路径下没有文件
将nexus 服务仓库配置为yum仓库 准备一台CentOS7的系统,测试将nexus服务器做为yum仓库源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 [root@centos7 ~] > [zabbix-nexus] > name=zabbix-nexus > baseurl=http://10.0.0.100:8081/repository/zabbix-5.0-yum-centos7/ > gpgcheck=0 > EOF [root@centos7 ~] Loaded plugins: fastestmirror Loading mirror speeds from cached hostfile * base: repo id repo name status base/7/x86_64 CentOS 10,070 epel/7/x86_64 EPEL 13,523 extras/7/x86_64 extras 451 zabbix-nexus zabbix-nexus 100 repolist: 24,144 [root@centos7 ~] [root@centos7 ~] [root@centos7 ~] sl-5.02-1.el7.x86_64.rpm [root@ubuntu1804 ~] 899D2379-D9D78DB2-4B816C59-1418D257-7A0183F0-deletions.index content 899D2379-D9D78DB2-4B816C59-1418D257-7A0183F0-metrics.properties metadata.properties
查看nexus服务器的仓库路径下可以看到下面文件和目录
使用 Nexus 构建私有 Apt 仓库 阿里云镜像地址
1 https://mirrors.aliyun.com/ubuntu/
创建 Blob Store
创建 Apt 仓库
生成仓库路径
配置客户端的 apt 源指向私有仓库路径 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 [root@ubuntu1804 ~] deb http://10.0.0.100:8081/repository/ubuntu1804-apt/ bionic main restricted universe multiverse deb-src http://10.0.0.100:8081/repository/ubuntu1804-apt/ bionic main restricted universe multiverse deb http://10.0.0.100:8081/repository/ubuntu1804-apt/ bionic-security main restricted universe multiverse deb-src http://10.0.0.100:8081/repository/ubuntu1804-apt/ bionic-security main restricted universe multiverse deb http://10.0.0.100:8081/repository/ubuntu1804-apt/ bionic-updates main restricted universe multiverse deb-src http://10.0.0.100:8081/repository/ubuntu1804-apt/ bionic-updates main restricted universe multiverse deb http://10.0.0.100:8081/repository/ubuntu1804-apt/ bionic-proposed main restricted universe multiverse deb-src http://10.0.0.100:8081/repository/ubuntu1804-apt/ bionic-proposed main restricted universe multiverse deb http://10.0.0.100:8081/repository/ubuntu1804-apt/ bionic-backports main restricted universe multiverse deb-src http://10.0.0.100:8081/repository/ubuntu1804-apt/ bionic-backports main restricted universe multiverse [root@ubuntu1804 ~] Get:1 http://10.0.0.100:8081/repository/ubuntu1804-apt bionic InRelease [242 kB] Get:2 http://10.0.0.100:8081/repository/ubuntu1804-apt bionic-security InRelease [88.7 kB] Get:3 http://10.0.0.100:8081/repository/ubuntu1804-apt bionic-updates InRelease [88.7 kB] Get:4 http://10.0.0.100:8081/repository/ubuntu1804-apt bionic-proposed InRelease [242 kB] Get:5 http://10.0.0.100:8081/repository/ubuntu1804-apt bionic-backports InRelease [74.6 kB] [root@ubuntu1804 ~] [root@ubuntu1804 ~] /data/blobs/blob-ubuntu1804/ ├── B1F301F8-B40ABB49-1FAEB7F0-43883B27-6A28E1C4-deletions.index ├── B1F301F8-B40ABB49-1FAEB7F0-43883B27-6A28E1C4-metrics.properties ├── content │ ├── tmp │ ├── vol-01 │ │ ├── chap-22 │ │ │ ├── 8e4192c2-1840-4f2c-8a79-e4b1e32b59cd.bytes │ │ │ └── 8e4192c2-1840-4f2c-8a79-e4b1e32b59cd.properties │ │ └── chap-38 │ │ ├── 7c73ca13-eda6-4f8a-8047-bbdc363eeeab.bytes │ │ └── 7c73ca13-eda6-4f8a-8047-bbdc363eeeab.properties │ ├── vol-02 │ │ ├── chap-12 │ │ │ ├── 028770ba-00c8-4291-a98e-619ec77ffb7f.bytes │ │ │ └── 028770ba-00c8-4291-a98e-619ec77ffb7f.properties [root@ubuntu1804 ~] @BlobStore.created-by=anonymous size=9050880 @Bucket.repo-name=ubuntu1804-apt creationTime=1628060733295 @BlobStore.created-by-ip=10.0.0.106 @BlobStore.content-type=application/x-xz @BlobStore.blob-name=dists/bionic/universe/source/by-hash/SHA256/b779bf46d2471b11c40d91975bf6d0e088a6c4491223afe4fe688d8062511368 sha1=780fec92a0c16cb41a661de664ec0222105a5bce