一、 java简单介绍

1. 历史

上个世纪90年代初期,Sun公司有个项目是为电视机顶盒提供一种编程语言,可以让任何应用程序都可以基于这个语言运行在任何品牌机顶盒上。而这个项目在当时来讲实在是太超前了,并没有取得多大的关注。据听说当时他们办公室窗外有一颗高大的橡树,起名为Oak(橡树)。后来这帮工程师经常端着爪哇岛的咖啡讨论这门编程语言,后来又改名为java

Java并没有在其源出的设计应用场景中取得多大成功,但是无心插柳柳成荫,上世纪90年代web服务技术方兴未艾,基于人们对于多媒体技术的渴望,很多人就尝试着在网页中展示出一些动态效果,但是C、C++其实都不合适,而Java这种编程语言其设计的目的就在于一次编译、到处运行。它结合java的虚拟机实现运行在多种多样的平台之上,正是这种特点引起了web开发方面技术人员的关注,直至后来成为市场占有量第一的编程语言

  • 1995年Java1.0诞生,哲学思想为Write once,Run Anywhere(一次编写,到处运行)
  • 1996年发布JDK1.0(Java开发工具箱),JDK是一个能够让程序员开发、调试、运行应用程序的研发环境(Java Development Kit),包含了类库、API、开发工具(javac)、jvm(sun classis VM)
  • 1997年Sun公司发布了JDK1.1,其中引进现在依旧流行的技术,比如JAR文件格式、JDBC(java数据互联技术)
  • 1998年JDK1.2诞生,Sun公司把java技术分拆为三个方向:J2SE、J2EE、J2ME
    1) J2SE:standard edition,标准版,大多数人写单机应用程序所包含的组件都在J2SE当中,JDK就是一个J2SE
    2) J2EE:enterprise edition,企业级版本,如果要开发分布式大型企业级应用程序,需要在JDK的基础上额外补充便于用户开发的跨机协作应用程序类
    库,可以在J2SE的基础上添加一些类库,如EJB,JSP,Serverlet,这种应用就叫做J2EE
    3) J2ME:mobile edition,为手机开发应用的编程,不流行
  • 2000年JDK1.3诞生,并且将JVM从Sun Classic VM替换为新收购公司研发的虚拟机Hotspot VM
  • 2002年发布JDK1.4
  • 2006年Sun宣布将Java技术开源,并且在随后的一年内,陆续的在GPL协议的规范下公开了JDK各个部分的源码。与此同时建立OpenJDK组织对这些源码进行独立管理,希望通过社区探索新的方向。并且将分拆的三个技术方向再次改名
    1)	J2SE --> Java 2 SE
    2) J2EE --> Java 2 EE
    3) J2ME --> Java 2 ME
  • 2010年:Sun公司拥有很多主流的技术产品,但是其一直缺少一个重量级的产品——关系型数据库,于是花了10亿美元买下了MySql,而这恰恰称为了压垮Sun的最后一颗稻草,后被Oracle以70多亿美元收购
  • 2011年:JDK1.7
  • 2014年:JDK1.8
  • 2016年:JDK1.9

2. JAVA技术体系

在这里插入图片描述
JDK是整个运行开发环境,较小的为JRE,JRE是java运行时环境,如果只是纯粹运行程序,仅仅安装JRE就可以,如果是程序员,需要开发和调试程序,那需要安装JDK。JDK中包含了如下组件

1)	Java language:java编程语言
2) Tools&:各种工具程序
3) Deployment:用来做部署的类库或工具
4) User interface:用户接口类库
5) interation libraries:集成库
6) other base libraries:其他的基础类库
7) lang and utli base libraries
8) Java SE API:java se的api就是由除了部署工具以外的类库实现的

3. Java源码运行过程

Java源码运行过程

  • 写好的Java源码通常存放在以.java为后缀文件中,通过编译器(complier)完成编译。编译后的代码以.class结尾,称之为类文件。类文件必须要遵循类文件规范,以便于运行在不同公司研发的但是遵循类文件规范的虚拟机上

  • 编译后类文件可以被打包为*.jar格式,其中的代码叫做字节码,这种代码也是二进制格式,与C不同是,C编译后直接运行在Cpu上,而字节码运行在Jvm上,由Jvm把字节码翻译成二进制代码并到Cpu上运行。字节码是一种伪二进制代码,意思为不是真正能在Cpu上运行的,而仅仅是为了加速在jvm虚拟机上运行的

  • 类文件运行过程中可能会需要到很多类库,类库中提供了很多常用的功能模块,在类文件运行中可能会一并装载运行

4. jvm的组成部分

请添加图片描述
对于Java而言,整个Java代码大体上先需要编写开发、由编译器编译成类文件、到Jvm上运行。需要注意的是,就像C程序运行一样,如果调用了共享库文件的话,程序运行前先要将被依赖到的库文件加载到内存中才行。同理,Java所开发的代码如果它依赖到公共类库中的类,也需要一个组件负责加载。所以类加载器的功用就在于代码运行前分析这段代码所依赖到的公共库中的类,由它装载程序的将要运行程序的类文件及被依赖的(Java API库中)类文件,只有这些装载完成了才能执行,所以类装器和执行引擎组合起来就是JVM

请添加图片描述
虚线部分为JVM运行内存区域

  • 方法区:java类语言中的方法,也就是所谓的代码,面向对象的编程语言主要就是由类和方法组成,主要存储被jvm加载的类信息、常量、静态变量等,它还有个名字叫做非堆内存空间,也有人将它称为永久代
  • 堆区:主要是用来存放java编程语言运行过程中把类实例化后的对象,对于整个jvm而言,将来可能最占空间的极有可能就是堆内存
  • java栈:用于存放线程自己的局部变量等信息,每一个线程都有其私有的栈,它的生命周期与线程相同
  • PC寄存器(Program counter Register):也称为程序计数器,这段空间通常是一段非常小的内存空间,它的主要作用是记录当前线程所执行字节码的行号指示器。用CPU架构来类比PC寄存器,CPU有个很小的内存空间,里面保存了下个指令所在的内存空间地址,这段内存空间就叫做指令指针寄存器。而对于Java而言,由于是在虚拟机上运行的,所以需要自己组织一段内存空间为一个线程在jvm内部维持程序计数器
  • 本地方法栈:

5. GC(Garbage Collector)

相对于C编程程序而言,程序员自己需要精心组织内存空间是如何存放数据的。如果这个程序用到一个内存空间就申请,用完后不会释放,会导致内存被耗尽。因此,C程序员在编程工作中一个非常重要的环节就是如何组织内存空间实现内存的分配和回收。也就意味着其可以直接操作内存的空间,这虽然是保证代码效率的一个基本途径,但是额外也会有很多其他的风险,同时对于程序员的工作量也带来了不小的挑战。不过呢,java为了避免程序员不得不自己组织内存空间,这个编程语言压根儿就不允许程序员自己去管理内存空间,需使用内存空间只需要向jvm申请,用完以后不用管,由它自动去释放,而这个过程我们叫垃圾回收的过程,实现这个功能的组件叫垃圾收集器(garbage collector,简称为GC)

对于纯对象式编程语言,所有代码都要实例化出对象,一个对象如果正在被调用,他的引用计数就不为0,一旦引用计数器为0就会被标记为垃圾并等待回收,而这一切都是由GC扫描堆中的对象并完成的。对于JVM而言,大量的对象都放在内存heap(堆)中,GC主要是针对堆来进行的

6. java class文件格式

2006年Sun将java技术开源了,为了避免各家公司都按照自己的理解去组织java、开发专有的运行环境,导致一个本来统一的社区极有可能分裂成多个阵营进行对抗。Sun肯定不能坐视不管,接着他们定义了java类文件格式,规定无论哪家公司研发的虚拟机都应该兼容这种类文件格式,就像SQL接口一样。如果不这样做对于使用者来说需要付出的代价太高了,而且用这种方式建立技术壁垒、竞争优势也是不道德的

7. 安装

如果要安装jdk的话,可以访问oracle的Java SE Development Kit网站选择下载,而我使用的是openjdk直接用yum安装即可,不过yum源里包含了3个版本的openjdk,我选择了java-11-openjdk

  • java-11-openjdk.x86_64:openjdk的jre包
  • java-11-openjdk-devel.x86_64:openjdk的jdk包
  • java-11-openjdk-headless.x86_64:这个包也属于运行时环境包,不过不支持audio和video

直接使用~]# yum install java-11-openjdk-devel.x86_64安装即可,安装完成后为其他应用程序定义约定俗称的环境变量

]# vim /etc/profile.d/java.sh

export JAVA_HOME=/usr/java/latest
export PATH=/usr/java-latest/bin:$PATH

二、Tomcat相关的概念

为了让大家都遵循同一种规范去开发Servlet容器,Sun公司创建了第一个Servlet容器,叫做Java Web Server,但是Java Web Server只是个参考模型所以很不稳定。与此同时,ASF创建了JServ项目,直到1999年,Sun把JWS捐给了ASF,于是两个项目合二为一即tomcat的前身。第一个tomcat版本是3.0系列,刚开始并不是叫做tomcat,只因O’Reilly当年出版的介绍这个jsp容器的书封面是tom猫,所以就叫做tomcat

1. Applet与Servlet

java起初是为了解决机顶盒系统不便移植的问题而诞生的,后来随着PC互联网与http协议的发展,人们希望在网页中可以展示一些动态效果,于是就诞生了Applet。比如一个网页是静态的,里面可以内嵌一些java代码,客户端请求一个动态网页时,服务端将网页原封不动的发送到客户端浏览器,浏览器解析html代码内容并基于插件形式的JRE运行网页中的java代码,而后将运行结果予以展示。这种使用java语言编写的小代码叫做Applet,因其是在客户端运行的,一旦有人在其中植入了恶意代码的话会存在一定的风险。后来java开始尝试着让代码运行在服务端,将运行结果发送给客户端即可,这种方式就是Servlet

2. JSP容器

Servlet是为了处理服务端动态网页而诞生的,但是其有一个问题。如果在服务端运行java代码,那么程序员在开发代码时不光要考虑java代码,还要考虑html代码,比如其中的层级结构、格式、字体等等,这极大的增加了开发难度,于是jsp就诞生了,它的全称是java server page(java服务器页面),可以实现将java代码直接内嵌到html代码中,而后通过其内部组件jasper翻译成纯java程序文件,然后调用javac编译成类,就能在jvm上运行

FILE.jsp(网页后缀) --> jasper(能够转换为.java的jsp组件) --> .java --> javac (编译转换为类)--> .class(类文件) --> jvm

一旦服务端可以提供Server类和jsp类,那么就叫做一个jsp容器,其能够将对应的代码装入完成翻译并执行

  • jws:sun公司最早为了定义的JSP容器的参考实现,后因ASF的JServ非常标准,就捐给了ASF
  • JServ:ASF(阿帕奇软件基金会)根据jws实现的JSP容器
  • tomcat:ASF收到jws后,结合Jserv并将两个项目融合,于是tomcat就这么由来的,

3. tomcat的组件

  • server:tomcat运行起来之后叫做一个server或者一个tomcat实例,但是tomcat运行起来后表现的是一个java进程
  • connector:任何请求都要通过连接器进入tomcat实例,连接器种类有三种,一个连接器仅能将请求发送给一个引擎
    1. http
    2. https
    3. ajp:阿帕奇jserv 协议一直流传到今天,只有httpd支持
  • service:实现把连接器与引擎建立连接关系
  • engine:一个server中的engine可以不止一个,一个engine可以对应多个连接器
  • host:每一个host用来标识一个虚拟主机
  • context:每个context用来配置一个应用程序及其环境

4. 安装

可以访问tomcat官网下载tar包安装,也可以直接使用yum安装 ~]# yum install tomcat tomcat-lib tomcat-admin-webapps tomcat-docs-webapp tomcat-webapps,需要了解下面几个包的作用

  • tomcat.noarch:主程序包
  • tomcat-lib:tomcat需要的库文件
  • tomcat-admin-webapps:网页输出的管理接口包
  • tomcat-docs-webapps:文档包
  • tomcat-webapps:测试页面
  • tomcat-jsp:jsp类库
  • tomcat-servlet:servlet类库

如果使用tar包安装的话,约定俗成解压到/usr/local/下,为了便于日后升级再创建一个名为tomcat的符号链接,最后导出环境变量

vim /etc/profile.d/tomcat.sh
export CATALINA_BASE=/usr/local/tomcat
export PATH=$PATH:$CATALINA_BASE/bin

启动时如果-security选项,表示安全启动,也就是调用tomcat/conf/catalina.policy文件中定义的安全策略。另外,为安全运行,最好使用tomcat用户运行,并要注意给tomcat用户授权

5. tomcat的目录结构

  • bin:脚本及启动时用到的类
  • conf:配置文件目录
  • lib:类库文件,这里面的类库文件一般都打包为jar格式的
  • logs:日志文件目录
  • temp:临时文件目录
  • webapps:webapp的默认目录
  • work:工作目录,存放编译后的字节码文件

使用rpm包安装的tomcat目录组织方式同解压,但是有些文件会保存到/var/lib/tomcat

6. tomcat相关的配置文件

  • server.xml:主配置文件
server.xml文件中的层级结构示例
<Server>
<Service>
<connector/>
<connextor/>
...
<Engine>
<Host>
<Context/>
<Context/>
...
</Host>
<Host>
...
</Host>
...
<Engine>
</Service>
</Server>

1. Server:最顶级的叫做Server,tomcat实例,监听8005端口,接受SHUTDOWN指令,在同一物理机可启用多个实例,需要修改器监听端口为不同端口。

2. service:关联connector与engine

3. connector:连接器组件有三个,http、https、ajp
(1) port="":监听端口,如果小于1024需要用管理员启动
(2) protocll="":使用的协议版本
(3) connectionTimeout="":链接超时时长,单位是毫秒
(4) maxThreads="":最大并发链接
(5) minSpareThreads="":最小进程空闲数
(6) redirectPort="":默认收到https请求会重定向哪个端口
(7) enableLookups="":是否启用DNS反解主机名,建议显示定义
(8) address="":监听的地址,没定义表示监听本机的所有地址
(9) acceptCount="":等待队列的最大长度

4. engine:引擎
(1) name="":引擎名称
(2) defaultHost="":默认主机的主机名
(3) jvmRoute="":与cookie类似的实现会话粘性

5. host:主机
(1) name="":主机名称
(2) appbase="":根主页,documentroot,如果是相对路径,则应该是相对于$CATALINA_BASE定义的路径
(3) unpackWARs="":是否自动展开WAR格式的文件
(4) autoDeploy="":自动部署

6. context:<Context path="bbs" docBase="/data/webapps/bbs/" reloadable="ture" />
(1) path="":网页访问路径
(2) docBase="":应用程序路径,定义某个应用程序的访问路径,路径如果是相对路径,则是相对于host的appbase,如果使用的是绝对路径,则像是alias
(3) reloadable="":如果这个属性设为true,tomcat服务器在运行状态下会监视在WEB-INF/classes和WEB-INF/lib目录下class文件的改动,如果监测到有class文件被更新的,服务器会自动重新加载Web应用

7. valve:一般定义日志文件

  • web.xml:每个webapp只有部署后才能被访问,部署的过程就是指示类加载器去加载这个应用程序和应用程序所依赖到的各类库的过程,所以该怎么部署、如何加载通常由web.xml进行定义,每个应用程序都有各自的文件,如果没有的话就加载这个默认的配置文件,其存放位置为WEB-INF/中,此文件为所有webapps提供默认部署相关配置
  • context.xml:每个webapp都应该有自己专用的context.xml配置文件, 这个文件主要用来定义应用程序部署时的背景属性,如果应用程序没有自己专用的,则使用这个默认的配置文件,而每个webapp都可以使用的配置文件,它通常由专用的配置文件context.xml来定义,其存放位置为WEB-INF/中 ,此文件为所有webapps提供默认部署相关配置
  • tomcat-user.xml:用户认证的账号密码文件,定义方式为定义用户、角色,将角色与用户关联,用户就拥有了角色的权限,此文件在tomcat启动时在装入内存,因此修改后的新配置只有重启tomcat才会生效
  • catalina.policy:当使用-security选项启动tomcat时,用于为tomcat设置安全策略的文件
  • catalina.properties:用于定义java各种属性值,意为启动tomcat时jvm使用何种类加载器,以及一些与tomcat JVM初始化时调优参数
  • logging.properties:日志相关配置文件,java自带的日志管理系统名为log4j

7. webapp的根目录组织结构

  • WEB-INF/:当前webapp的私有资源目录,通常存放当前webapp自用的web.xml
  • META-INF/:当前webapp的私有资源目录,通常存放当前webapp自用的context.xml
  • classes:此webapp的私有类文件
  • lib:此webapp的私有类库,存放被打包为jar格式
  • index.jsp:webapp的主页文件

8. webapp归档格式

  • .war:webapp 归档文件,不需要展开,直接放到站点路径下,自动展开
  • .jar:EJB的打包类库文件
  • .rar:资源适配器打包文件
  • .ear:企业级应用程序

9. 部署

部署就是将webapp的源文件放置于目标目录,并配置tomcat服务器能够基于context.xml文件中定义的路径来访问此webapp,将其特有类通过class loader装载至JVM

(1)自动部署:auto deploy

(2) 手动部署

  • 冷部署:把webapp复制到指定位置,在启动tomcat
  • 热部署:在不停止tomcat的前提下进行部署,部署工具:manager、ant脚本、tcd
  • undeploy:反部署,停止webapp,并从tomcat实例拆除其部分文件和部署名
  • stop:停止,不在向用户提供服务,但是类文件仍在java虚拟机上;
  • start,启动处于停止状态的webapp程序;
  • redeploy:重新部署;

10. 简单配置

(1) 简单测试

  1. 在指定的目录下创建myapp及特有的目录结构:lib、classes、WEB-INF、META-INF、ROOT
  2. 在myapp下编辑index.jsp
<%@ page language="java" %>
<%@ page import="java.util.*" %>
<html>
<head>
<title> JSP Test Page</title>
</head>
<body>
<% out.println("hello,world."); %>
</body>
</html>

目录结构
在这里插入图片描述
配置文件
在这里插入图片描述

  1. 单个虚拟主机需要自定义网页目录,而tomcat自带的webapps则只需创建需要目录,不用创建ROOT,ROOT为网站默认应用目录;
  2. 如果没有配置文件,会读取conf目录下的context.xml、web.xml,要想修改配置文件,建议不要修改默认提供的。

(2) 开启网页管理权限

如果想开启tomcat的网页管理界面,需要在tomcat-users.xml文件事先进行定义,这个文件默认没有任何生效选项。定义方式也很简单,tomcat内建了几个角色,如manager-gui、manager-script、manager-jmx、manager-status。自己在文件中定义一个用户使其属于哪个角色,就拥有哪个角色的权限。

  • manager-gui角色:允许通过图形界面访问manager和status
  • manager-script:允许用户通过文本接口访问状态页面和管理界面
  • manager-jmx:允许通过jmx代理访问状态页面
  • manager-status:允许查看状态信息

编辑tomcat根目录下的tomcat-users.xml,在其中加入定义角色与用户的配置

<role rolename="manager-gui"/>
<user username="tomcat" password="123456" roles="manager-gui"/>

因我使用的是tomcat 9,默认manager是只能使用本机访问的,所以还需要改变网页授权,需要在webapps/manager/META-INF/context.xml文件中将限制设定注释掉

<!--  <Valve className="org.apache.catalina.valves.RemoteAddrValve"
allow="127\.\d+\.\d+\.\d+|::1|0:0:0:0:0:0:0:1" />
-->

(3) 定义虚拟主机:方法一

  1. 准备虚拟主机的工作目录,如下
    在这里插入图片描述
  2. 配置文件如下
    在这里插入图片描述
  3. 测试页面代码
<%@ page language="java" %>
<html>
<head>
<title> TomcatA </title>
</head>
<body>
<h1><font color="red"> TomcatA.busyops.com </font></h1>
<table align="centre" border="1">
<tr>
<td> Session ID </td>
<% session.setAttribute("busyops.com","busyops.com"); %>
<td><%= session.getId() %></td>
</tr>
<tr>
<td> Created on </td>
<td><%= session.getCreationTime() %></td>
</tr>
</table>
</body>
</html>

(4) 定义虚拟主机:方法二

网站首页放置ROOT目录下, 其他应该单独建立一个目录

  1. 目录结构如下
    在这里插入图片描述
  2. 配置文件如下
    在这里插入图片描述
  3. 测试页面
<%@ page language="java" %>
<html>
<head>
<title> TomcatB </title>
</head>
<body>
<h1><font color="blue"> TomcatB.busyops.com </font></h1>
<table align="centre" border="1">
<tr>
<td> Session ID </td>
<% session.setAttribute("busyops.com","busyops.com"); %>
<td><%= session.getId() %></td>
</tr>
<tr>
<td> Created on </td>
<td><%= session.getCreationTime() %></td>
</tr>
</table>
</body>
</html>

(5) 代理

做代理集群时,首先要同步时间,时间可以不准确,但一定要一致

① nginx代理

  1. 实现对网页的动静分离,tomcat响应动态内容,nginx本机响应静态内容
location / {
root /nginx/pic;
}

location ~* \.(jsp|do)$ {
proxy_pass http://busyops.com:8080;
}

使用proxy_pass代理至后端主机时,如果使用主机名称向后端代理,需要代理主机可以解析这个主机名称

② httpd代理

httpd代理tomcat,可以使用两种连接器:http和ajp

  1. 首先使用httpd -M查看proxy_module,之后根据向后端代理请求的方法,看清对应模块是否启用,http|ajp模块
    http协议反代,在conf.d/下建立虚拟主机
<VirtualHost *:80>
ServerName busyops.com
ProxyVia On
ProxyRequests Off
ProxyPreserveHost On
<Proxy *>
Require all granted
</Proxy>
ProxyPass / http://172.16.100.100:8080/
ProxyPassReverse / http://172.16.100.100:8080/
<Location />
Require all granted
</Location>
</VirtualHost>

ProxyRequests Off:明确指定关闭正向代理
ProxyPreserveHost On:如果开启这一项,会保留客户端请求的主机名称
  1. 使用httpd -t检查配置文件,没问题后直接启动服务;
  2. 注意结尾斜线
  3. ajp协议反带:与http反带很接近,只不过把向后端请求的协议改成ajp,并且端口不是8080,而是ajp协议的端口8009
<VirtualHost *:80>
ServerName busyops.com
ProxyVia On
ProxyRequests Off
ProxyPreserveHost On
<Proxy *>
Require all granted
</Proxy>
ProxyPass /status !
ProxyPass / ajp://172.16.100.100:8009/
ProxyPassReverse / ajp://172.16.100.100:8009/
<Location />
Require all granted
</Location>
</VirtualHost>

在实验过程中总是报503错误,后来在tomcat配置文件连接器配置段加入secretRequired=""配置指令,才能正常访问,原因不清

<Connector protocol="AJP/1.3"
port="8009"
address="0.0.0.0"
secretRequired=""
redirectPort="8443" />

11. tomcat集群

(1) nginx 反代 tomcat 集群

1.准备2台tomcat主机,配置如下

tomcat主机一
<Host name="node1.busyops.com" appBase="/data"
unpackWARS="true" autoDeploy="true">

<Context path="" docBase="ROOT" reloadable="yes" />
<Context path="/bbs" docBase="/data/bbs" reloadable="yes" />

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

tomcat主机二
<Host name="node2.busyops.com" appBase="/data"
uppachWARS="true" autoDeploy="true">
<Context path="/" docBase="ROOT" reloadable="yes" />
<Context path="/bbs" docBase="/data/bbs" reloadable="yes" />
</Host>

2台主机也只是主机名称不同,主机二没有启用单独的访问日志,提供不同的网页文件以便于区分
  1. 再起一台nginx主机,添加集群配置
http { 
upstream tls {
ip_hash;
server node1.busyops.com:8080;
server node2.busyops.com:8080;
}
}
server {
location ~* \.(jsp|do)$ {
proxy_pass http://tls;
}
}

需要在nginx主机中添加解析记录,并把2台tomcat的默认主机调整为需要代理的主机

(2) httpd 反代 tomcat 集群

  1. 如nginx反代一样准备2台tomcat主机,配置同上
  2. 实现普通负载均衡代理的配置
<Proxy balancer://tls>
BalancerMember http://node1.busyops.com:8080 loadfactor=1
BalancerMember http://node2.busyops.com:8080 loadfactor=2
</Proxy>

<VirtualHost *:80>
ServerName busyops.com
ProxyVia On
ProxyRequests Off
ProxyPreserveHost On
ProxyPass /status !
ProxyPass / balancer://tls/
ProxyPassReverse / balancer://tls/
<Location />
Require all granted
</Location>
<Proxy *>
Require all granted
</Proxy>
</VirtualHost>
  1. 实现会话粘性的配置
Header add Set-Cookie "ROUTEID=.%{BALANCER_WORKER_ROUTE}e; path=/" env=BALANCER_ROUTE_CHANGED
<Proxy balancer://tls>
BalancerMember http://node1.busyops.com:8080 route=TomcatA
BalancerMember http://node2.busyops.com:8080 route=TomcatB
ProxySet stickysession=ROUTEID
ProxySet lbmethod=byrequests
</Proxy>

<VirtualHost *:80>
ServerName busyops.com
ProxyVia On
ProxyRequests Off
ProxyPreserveHost On
ProxyPass /status !
ProxyPass / balancer://tls/
ProxyPassReverse / balancer://tls/
<Location />
Require all granted
</Location>
<Proxy *>
Require all granted
</Proxy>
</VirtualHost>
  • ProxyRequests Off:如果httpd做反向代理,就必须显示关闭正向代理
  • proxyvia {On|Off|Full|Block}:是否加入via header,full为详细信息,带有服务器的版本号,block表示为每个代理请求报文中清除via,代理服务器一手托两家,能修改自己作为客户端向后端tomcat的请求报文,也可以修改后端tomcat回应给客户端的响应报文,这里的via是在后端tomcat响应客户端的响应的报文中加入via首部,意为经过谁代理
  • proxypreservehost:保留客户端http请求中的host首部,这样后端就知道前端请求的是哪个虚拟主机,不像nginx,需要在配置文件中显示指定像后端代理的域名;
  • ProxyPassReverse:如果后端重写了某个url,那么这个返回的新url要不要代理,代理位置

12. 定义访问日志

配置文件中有默认的配置实例,直接复制到自己定义的host中即可

- directory:日志输出目录
- prefix:日志文件名的前缀
- suffix:日志文件的后缀
- pattern:日志内容记录格式
%h:远程主机主机地址
%l:远程登录用户名
%u:basic认证用户名
%t:本地时间
%r:http请求首行
%s:状态码
%b:字节数
&quot;:表示双引号""

三、tomcat 会话管理器

tomcat有个内置的组件,类似<server> <connector>,这里所说的组件叫做<session manager>,我们称之为会话管理器,它能自动实现本地tomcat节点之间的会话管理

1. 标准会话管理器

标准会话管理器(Standard Manager),默认会话保存于$CATALINA_HOME/work/Catalina/<hostname>/<webapp-name>/下的SESSIONS.ser文件中,如果要使用这个会话管理器, 则需要在<host><context>

将如下代码加入所需位置

<Manager className="org.apache.catalina.session.StandardManager"
maxInactiveInterval="7200"/>

如上配置可以保证每个用户的最大非活动时间为7200秒(120分钟),这个会话管理器是将会话周期性的保存于上述文件中,并不是用户来访问的第一时间就进行保存,那么务必会带来丢失部分对话,而且由于会话是持久在当前节点的,如果想访问建立的会话,只能赶紧修复重新读取会话文件

2. 持久会话管理器

持久会话管理器(Persistent Manager),将会话数据保存至持久存储中,并且能够在服务器意外终止后重新启动时加载这些会话信息。持久会话管理器支持将会话保存至文件存储(FileStore)或jdbc存储(JDBCStore)中

保存在文件中的示例:
<Manager className="org.apache.catalina.session.PersistentManager"
saveOnRestart="true">
<Store className="org.apache.catalina.session.FileStore"
directory="/data/tomcat-sessions"/>
</Mavager>
保存至JDBCStore中的示例:
<Mavager className="org.apache.catalina.session.PersistentManager"
saveOnRestart="true">
<Store className="org.apache.catalina.session.JDBCStore"
driverName="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mydb?user=jb;password=pw"/>
</Manager>

3. Delta Manager

实现session集群会话管理,将多个tomcat主机构建成一个通过多播信道进行通信的会话集群,其可以在前端使用任何的调度器做负载均衡,任何一台宕机了也不影响其中的会话。但其不适合太大规模的集群,会浪费更多的带宽传输集群信息,浪费更多的存储空间保存会话信息

详细配置可以查看官方文档,当然tomcat本地自带的文档中也次序有相关配置内容,位于http://<localhost_ip>:8080 --> Documentation --> Apache Tomcat Clustering,如下配置就是从中摘出的

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

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

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

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

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

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

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

上述配置解析:

  • <manager/>:表示指明使用的是DeltaManager
  • <Channel/>:信道,专门定义会话传递信道,这里定义的信道叫做GroupChannel
  • <Membership>:用来决定集群成员关系,用McastService进行通信,使用的地址为228.0.0.4,端口为45564,每500毫秒发一次心跳,如果在3000毫秒内不在接受到那个节点的心跳了,就把它从集群中剔除
  • <Receiver>:表示自己如何接受别人的session信息,监听的地址为auto,表示自动找一个本地地址监听,如果你当前主机有多个地址的话,很有可能是错误的,端口为4000,自动绑定,挑选器的超时时间我5000毫秒,最多可以启动6个线程,每个线程只能接受一个节点传过来的信息,如果少于节点数量,可能会在性能上有影响
  • <Sender/>:表示自己的session传递给别人

4. Backup Manager

能够实现在一个集群内部,某台主机不会把自己的会话通知给其他所有节点,而是通知给在集群内部的专用备用节点。比如,一共有三个节点,节点一不会像Delta Manager的管理方式那样,将所有会话通知给其他节点,而是在节点二或三中选一个做自己的会话备份节点。不过这种管理方式听上去好,但其逻辑拓扑结构配置起来极为不便,而且很难标准化

四、JVM虚拟机优化相关

1. java的运行时数据区域

  • 方法区:Method Area
  • java栈:JVM Stack
  • 本地栈:Native Stack
  • PC寄存器:Program counter Register
  • java堆:Java Heap

2. jvm heap内存空间

基本上对jvm而言,堆内存运行起来后所占据的空间一般是最大的,大体上可以分为三个组成部分

(1)新生代

新生代实现垃圾回收的是 Minor GC

  • Eden:新生区,存放刚刚创建的对象
  • survivor:存活区,存放新生区成熟后的实例对象,存活区又分为两个区域(to和from)

(2) 老年代

如果说步入成熟区的对象仍然有利用价值,则将把这一部分内容存放到老年代。老年代实现垃圾回收的Full GC

(3)持久代

1.8jdk后持久代取消了,不用设置

3. 垃圾回收机制

  • 新生代回收:Minor GC,任何被新创建的对象首先放在Eden中,每过一段时间对eden区的内容进行扫描,发现还在被引用的对象就提升至新生区(ss1),不在被引用的对象就会被回收,往复如此。一直被引用的对象会慢慢被提升到老年区,等待老年区被占满了,就会发生full gc(stop the world)
  • 老年代回收:Major GC(Full GC):老年代使用的算法为标记清除算法,主要分为两个阶段。第一阶段遍历整个堆内存,标记不在被引用的对象。第二阶段:把不在被引用的对象打包清理

4. 堆内存可调参数

  • -server:运行为服务器模式
  • -Xmx:新生代和老年代总共可用的最大空间之和
  • -Xms:新生代和老年代的最大使用空间通过-Xmx指定后,系统会在其中保留一部分空间作为将来的扩展空间, 这里指定除去扩展空间外,新生代和老年代可用的空间
  • -XX:NewSize=:新生代的初始空间
  • -XX:MaxNewSize=:新生代的最大的空间,包括可扩展空间
  • -XX:MaxPerMSize=:持久带最大空间
  • -XX:PermSize=:持久代的初始化空间

tomcat的启动脚本为catalina.sh,在里面可以设置相关参数

  • CATALINA_OPTS:传递参数仅对启动运行tomcat实例的java虚拟机有效
  • JAVA_OPTS:传递参数对所有的java虚拟机有效

一般初始化堆内存空间和最大堆内存空间应该设置成相同,尤其是在主机主要运行java程序的时候,整个主机的资源就应该向tomcat主机倾斜,加入以下配置JAVA_OPTS="-server -Xms32g -Xms32g -XX:NewSize= -XX:MaxNewSize= -XX:PermSize= -XX:MaxPermSize=",设置后用jsp查看进程号,使用cat /proc/$PID/cmdline查看java进程启动时参数

5. 性能监控工具

(1)运行过程中可能遇到的问题

  • OutOfMemoryError:内存不足
  • 内存泄漏:未必是内存过小的原因,也有可能是java代码中内存泄漏
  • 线程死锁
  • 锁竞争(Lock Contention)
  • java消耗过多的CPU

(2)jps命令

jps,全称为java virtual machine process status tool,主要是用来监控jvm运行中的进程状态信息

命令格式:jps [options] [hostid]

-m:输出传入main方法的参数,也可以理解为主程序是什么
-l:显示main类或jar的完全限定名称,也就是显示完整类名
-v:显示jvm虚拟机启动时指定的参数

(3)jstack命令

查看某个java进程内的线程栈信息
命令格式:jstack [options] [pid]

option:

-l:输出完整的锁信息,long listings
-m:混合模式,输出java堆栈及本地方法堆栈信息

(4)jmp命令

jvm memory map,java堆内存使用情况
命令格式:jmp [optinos] [pid]

-heap:查看堆内存详细
-histo[:live]:查看堆内存中对象数目、大小统计结果

(5)jstat命令

jvm统计监测工具
命令格式:jstat <option> pid [时间间隔[次数]]

option:
-gc:查看内存情况
s0c:存活区容量
suc:存活区已用空间大小
ec:eden区域容量
eu:eden区域已用量
oc:old区域容量
ou:old区以用量
pc:持久容量
pu:持久已用量
ygc:新生代发生gc次数
ygct:新生代发生gc耗时
fgc:老年代发生gc次数,这里次数过多通常意味着要增大内存空间
fgct:老年代发生gc耗时
gct:总的gc总耗时
-option:查看支持哪些option

(6)jinfo命令

命令格式: jinfo [option] <pid>

  1. -flags:查看启动参数
  2. -sysprops
  3. -flag

(7)图形化工具

jconsole:图形化工具
jvisualvm:图形化工具

6. 线程池优化

<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" />
常用属性:
connectionTimeout:超时时长
maxThreads:最大并发连接数
minSpareThreads:最小空闲线程数
maxSpareThreads:最大空闲线程数
acceptCount:等待队列的最大长度
URIEncoding:URI地址编码格式,建议使用UTF-8
enableLookups:是否启用dns解析,建议禁用
compression:是否启用传输压缩机制,建议on
compressionMinSize:启用压缩的最小阈值,单位字节
compressableMimeType:定义启用的压缩类型 text/html、text/xml、text/scc、text/javascript

7. 禁用8005端口

<Server prot="-1" shutdown="SHUTDOWN" >
关闭管理端口可以把SHUTDOWN该成别的命令,或者把端口改成-1

8. 隐藏版本信息

在connector中加入<Connector Server="SOME STRING"

9. 更改tomcat运行时用户,确保tomcat用户对目录有访问权限