java动态网页技术
servlet
本质就是一段Java程序
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("" + message + "
");
}
public void destroy()
{
}
}
在Servlet中最大的问题是,HTML输出和Java代码混在一起,如果网页布局要调整,就是个噩梦。
jsp(Java Server Pages)
提供一个HTML,把它变成一个模板,也就是在网页中预留以后填充的空,以后就变成了填空了。
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
jsp例子
后面的内容是服务器端动态生成字符串,最后拼接在一起
<%
out.println("你的 IP 地址 " + request.getRemoteAddr());
%>
JSP是基于Servlet实现,JSP将表现和逻辑分离,这样页面开发人员更好的注重页面表现力更好服务客户。
JSP 先转换为 Servlet的源代码.java文件(Tomcat中使用Jasper转换),然后再编译成.class文件,最后就可以在JVM中运行了。
JDK
JRE:它是Java Runtime Environment缩写,指Java运行时环境, 包含 JVM + Java核心类库
JDK:它是Java Development Kit,即 Java 语言的软件开发工具包。
-
JDK也就是常说的J2SE,在1999年,正式发布了Java第二代平台,发布了三个版本:
J2SE:标准版,适用于桌面平台
J2EE:企业版,适用于企业级应用服务器开发
J2ME:微型版,适用于移动、无线、机顶盒等设备环境2005年,Java的版本又更名为JavaSE、JavaEE、JavaME。
Servlet、Jsp都包含在JavaEE规范中。
JDK7、JDK8、JDK11是LTS(Long Term Suppot)版本 项目名称 发行日期 JDK 1.1.4 Sparkler(宝石) 1997-09-12 JDK 1.1.5 Pumpkin(南瓜) 1997-12-13 JDK 1.1.6 Abigail(阿比盖尔–女子名) 1998-04-24 JDK 1.1.7 Brutus(布鲁图–古罗马政治家和将军) 1998-09-28 JDK 1.1.8 Chelsea(切尔西–城市名) 1999-04-08 J2SE 1.2 Playground(运动场) 1998-12-04 J2SE 1.2.1 none(无) 1999-03-30 J2SE 1.2.2 Cricket(蟋蟀) 1999-07-08 J2SE 1.3 Kestrel(美洲红隼) 2000-05-08 J2SE 1.3.1 Ladybird(瓢虫) 2001-05-17 J2SE 1.4.0 Merlin(灰背隼) 2002-02-13 J2SE 1.4.1 grasshopper(蚱蜢) 2002-09-16 J2SE 1.4.2 Mantis(螳螂) 2003-06-26 Java SE 5.0 (1.5.0) Tiger(老虎) 2004-09-30 Java SE 6.0 (1.6.0) Mustang(野马) 2006-04 Java SE 7.0 (1.7.0) Dolphin(海豚) 2011-07-28 Java SE 8.0 (1.8.0) Spider(蜘蛛) 2014-03-18 Java SE 9 2017-09-21 Java SE 10 2018-03-14 [3] JDK协议是JRL(JavaResearch License)协议
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没什么关系。
OpenJDK使用GPL v2可以用于商业用途。
安装JDK
在Centos中,可以使用yum安装openjdk。
# yum install java-1.8.0-openjdk
# java -version
openjdk version "1.8.0_212"
OpenJDK Runtime Environment (build 1.8.0_212-b04)
OpenJDK 64-Bit Server VM (build 25.212-b04, mixed mode)
本次使用Oracle官网的JDK 8的rpm安装
# yum install jdk-8u191-linux-x64.rpm
# java
# java -version
java version "1.8.0_191"
Java(TM) SE Runtime Environment (build 1.8.0_191-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.191-b12, mixed mode)
安装目录为/user/java下
Java全局配置
# ll /usr/java
lrwxrwxrwx 1 root root 16 1月 13 01:22 default -> /usr/java/latest
drwxr-xr-x 8 root root 258 1月 13 01:22 jdk1.8.0_191-amd64
lrwxrwxrwx 1 root root 28 1月 13 01:22 latest -> /usr/java/jdk1.8.0_191-amd64
# vi /etc/profile.d/jdk.sh
export JAVA_HOME=/usr/java/default
export PATH=$JAVA_HOME/bin:$PATH
# . /etc/profile.d/jdk.sh
Tomcat
历史
- 起始于SUN的一个Servlet的参考实现项目Java Web Server,作者是James Duncan Davidson,后将项 目贡献给了ASF。和ASF现有的项目合并,并开源成为顶级项目,官网http://tomcat.apache.org/。
- Tomcat仅仅实现了Java EE规范中与Servlet、JSP相关的类库,是JavaEE不完整实现。
- 著名图书出版商O'Reilly约稿该项目成员,Davidson希望使用一个公猫作为封面,但是公猫已经被另一本书使用,书出版后封面是一只雪豹。
- 1999年发布初始版本是Tomcat 3.0,实现了Servlet 2.2和JSP1.1规范。
- Tomcat 4.x发布时,内建了Catalina(Servlet容器)和Jasper(JSP engine)等。
- 商用的有IBM WebSphere、Oracle WebLogic(原属于BEA公司)、Oracle Oc4j、Glassfish、JBoss 等。
- 开源实现有Tomcat、Jetty、Resin。
安装
可以使用CentOS7 yum源自带的安装。yum源中是Tomcat 7.0版本。安装完通过浏览器可以观察一下首页。
# yum install tomcat tomcat-admin-webapps tomcat-webapps
# systemctl start tomcat.service
# ss -tanl
LISTEN 0 100 :::8009
LISTEN 0 100 :::8080
采用Apache官网下载,下载8.x.x
# tar xf apache-tomcat-8.5.42.tar.gz -C /usr/local
# cd /usr/local
# ln -sv apache-tomcat-8.5.42/ tomcat
"tomcat" -> "apache-tomcat-8.5.42/"
# cd tomcat
# cd bin
# ./catalina.sh --help
# ./catalina.sh version
# ./catalina.sh start
# ss -tanlp
# ./catalina.sh stop
# ./startup.sh
# ./shutdown.sh
useradd -r java 建立系统账号
上例中,启动身份是root,如果使用普通用户启动可以使用
# useradd -r java
# chown -R java.java ./*
# su - java -c '/usr/local/tomcat/bin/catalina.sh start'
# ps -aux | grep tomcat
目录结构
目录 | 说明 |
---|---|
bin | 服务启动、停止等相关 |
conf | 配置文件 |
lib | 库目录 |
logs | 日志目录 |
webapps | 应用程序,应用部署目录 |
work | jsp编译后的结果文件 |
配置文件
文件名 | 说明 |
---|---|
server.xml | 主配置文件 |
web.xml | 每个webapp只有“部署”后才能被访问,它的部署方式通常由web.xml进 行定义,其存放位置为WEB-INF/目录中;此文件为所有的webapps提供 默认部署相关的配置 |
context.xml | 每个webapp都可以专用的配置文件,它通常由专用的配置文件 context.xml来定义,其存放位置为WEB-INF/目录中;此文件为所有的 webapps提供默认配置 |
tomcat-users.xml | 用户认证的账号和密码文件 |
catalina.policy | 当使用-security选项启动tomcat时,用于为tomcat设置安全策略 |
catalina.properties | Java属性的定义文件,用于设定类加载器路径,以及一些与JVM调优相关 参数 |
logging.properties | 日志系统相关的配置。log4j |
组件分类
顶级组件
Server,代表整个Tomcat容器
服务类组件
Service,组织Engine和Connector,里面只能包含一个Engine
连接器组件
Connector,有HTTP、HTTPS、A JP协议的连接器
容器类
Engine、Host、Context都是容器类组件,可以嵌入其它组件,内部配置如何运行应用程序。
内嵌类
可以内嵌到其他组件内,valve、logger、realm、loader、manager等。以logger举例,在不同容器组件内定义。
集群类组件
listener、cluster
小笔记:tomcat安装
#二进制安装
tar xf apache-tomcat-8.5.42.tar.gz -C /usr/local
cd /usr/local
ln -sv apache-tomcat-8.5.42/ tomcat
"tomcat" -> "apache-tomcat-8.5.42/"
useradd -r java
chown -R java.java ./*
yum install jdk-8u191-linux-x64.rpm
vi /etc/profile.d/jdk.sh
export JAVA_HOME=/usr/java/default
export PATH=$JAVA_HOME/bin:$PATH
. /etc/profile.d/jdk.sh
#启动
su - java -c '/usr/local/tomcat/bin/catalina.sh start'
ps -aux | grep tomcat
#./startup.sh
Tomcat内部组成
由上述组件就构成了Tomcat,如下图
名称 | 说明 |
---|---|
Server | Tomcat运行的进程实例 |
Connector | 负责客户端的HTTP、HTTPS、AJP等协议的连接。一个Connector只属于某一个 Engine |
Service | 用来组织Engine和Connector的关系 |
Engine | 响应并处理用户请求。一个引擎上可以绑定多个Connector |
Host | 虚拟主机 |
Context | 应用的上下文,配置路径映射path => directory |
AJP(Apache Jserv protocol)是一种基于TCP的二进制通讯协议。
核心组件
Tomcat启动一个Server进程。可以启动多个Server,但一般只启动一个
创建一个Service提供服务。可以创建多个Service,但一般也只创建一个
每个Service中,是Engine和其连接器Connector的关联配置可以为这个Server提供多个连接器Connector,这些Connector使用了不同的协议,绑定了不同的端口。其作用就是处理来自客户端的不同的连接请求或响应
Service内部还定义了Engine,引擎才是真正的处理请求的入口,其内部定义多个虚拟主机Host
Engine对请求头做了分析,将请求发送给相应的虚拟主机
如果没有匹配,数据就发往Engine上的defaultHost缺省虚拟主机
Engine上的缺省虚拟主机可以修改Host定义虚拟主机,虚拟主机有name名称,通过名称匹配
-
Context定义应用程序单独的路径映射和配置
举例:
假设来自客户的请求为: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。即使匹配不到也把请求交给该Host处理,因为该Host被定义为该Engine的默认主机
- 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对象返回给浏览器端
应用部署
根目录
Tomcat中默认网站根目录是CATALINA_BASE/webapps/
在Tomcat中部署主站应用程序和其他应用程序,和之前WEB服务程序不同。
nginx
假设在nginx中部署2个网站应用eshop、bbs,假设网站根目录是/var/www/html,那么部署可以是这样的。
eshop解压缩所有文件放到/var/www/html/目录下。
bbs的文件放在/var/www/html/bbs下。
Tomcat
Tomcat中默认网站根目录是CATALINA_BASE/webapps/
在Tomcat的webapps目录中,有个非常特殊的目录ROOT,它就是网站默认根目录。
将eshop解压后的文件放到这个ROOT中。
bbs解压后文件都放在CATALINA_BASE/webapps/bbs目录下。
每一个虚拟主机的目录都可以使用appBase配置自己的站点目录,里面都可以使用ROOT目录作为主站目录。
JSP WebApp目录结构
主页配置:一般指定为index.jsp或index.html
WEB-INF/:当前WebApp的私有资源路径,通常存储当前应用使用的web.xml和context.xml配置文件
META-INF/:类似于WEB-INF
classes/:类文件,当前webapp需要的类
lib/:当前应用依赖的jar包
主页实验
默认情况下,/usr/local/tomcat/webapps/ROOT/下添加一个index.html文件,观察访问到了什么?
将/usr/local/tomcat/conf/web.xml中的下面
Welcome to Tomcat
Welcome to Tomcat
index.jsp
index.htm
index.html
配置修改后,观察首页变化
webapp归档格式
.war:WebApp打包
.jar:EJB类打包文件
.rar:资源适配器类打包文件
-
.ear:企业级WebApp打包
传统应用开发测试后,通常打包为war格式,这种文件部署到了Tomcat的webapps下,还可以自动展开。
部署Deploy
- 部署:将webapp的源文件放置到目标目录,通过web.xml和context.xml文件中配置的路径就可以访问该webapp,通过类加载器加载其特有的类和依赖的类到JVM上。
- 自动部署Auto Deploy:Tomcat发现多了这个应用就把它加载并启动起来
- 手动部署
冷部署:将webapp放到指定目录,才去启动Tomcat
热部署:Tomcat服务不停止,需要依赖manager、ant脚本、tcd(tomcat client deployer)等工具
- 反部署undeploy:停止webapp的运行,并从JVM上清除已经加载的类,从Tomcat应用目录中移 除部署的文件
- 启动start:是webapp能够访问
- 停止stop:webapp不能访问,不能提供服务,但是JVM并不清除它
实验
1、添加一个文件,test.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
jsp例子
后面的内容是服务器端动态生成字符串,最后拼接在一起
<%
out.println("hello jsp");
%>
<%=request.getRequestURL()%>
先把test.jsp放到ROOT下去,试试看,访问 http://YourIP:8080/test.jsp 。
立即可以看到,这是通过路径映射找到相应的test.jsp后,转换成test_jsp.java,在编译成test_jsp.class。
/usr/local/tomcat/work/Catalina/localhost/ROOT/org/apache/jsp下有转换后的文件。
2、添加一个应用
模拟部署一个应用
# cd
常见开发项目目录组成
# mkdir projects/myapp/{WEB-INF,classes,lib} -pv
mkdir: 已创建目录 "projects"
mkdir: 已创建目录 "projects/myapp"
mkdir: 已创建目录 "projects/myapp/WEB-INF"
mkdir: 已创建目录 "projects/myapp/classes"
mkdir: 已创建目录 "projects/myapp/lib"
常见应用首页,内容就用上面的test.jsp
# vi projects/myapp/index.jsp
手动复制项目目录到webapps目录下去
# cp -r projects/myapp/ /usr/local/tomcat/webapps/
使用http://YourIP:8080/myapp/访问试试看
配置详解
server.xml
8005是Tomcat的管理端口,默认监听在127.0.0.1上。SHUTDOWN这个字符串接收到后就会关闭此Server。
# telnet 127.0.0.1 8005
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
SHUTDOWN
这个管理功能建议禁用,改shutdown为一串猜不出的字符串。
server.xml
用户认证,配置文件是conf/tomcat-users.xml
打开tomcat-users.xml,我们需要一个角色manager-gui。
Tomcat启动加载后,这些内容是常驻内存的。如果配置了新的用户,需要重启Tomcat。
访问manager的时候告诉403,提示中告诉去manager的context.xml中修改
文件路径/usr/local/tomcat/webapps/manager/META-INF/context.xml
看正则表达式就知道是本地访问了,由于当前访问地址是192.168.x.x,可以修改正则为
allow="127\.\d+\.\d+\.\d+|::1|0:0:0:0:0:0:0:1|192\.168\.\d+\.\d+"
再次测试,成功。
小笔记:添加admin-gui角色
vim /usr/local/tomcat/conf/tomcat-users.xml
vim /usr/local/tomcat/webapps/manager/META-INF/context.xml
vim /usr/local/tomcat/webapps/host-manager/META-INF/context.xml
allow="127\.\d+\.\d+\.\d+|::1|0:0:0:0:0:0:0:1|192\.168\.\d+\.\d+"
#重启服务
/usr/local/tomcat/bin/shutdown.sh && /usr/local/tomcat/bin/start.sh
一般情况下,一个Server实例配置一个Service,name属性相当于该Service的ID
连接器配置。
redirectPort,如果访问HTTPS协议,自动转向这个连接器。但大多数时候,Tomcat并不会开启
HTTPS,因为Tomcat往往部署在内部,HTTPS性能较差。
引擎配置
defaultHost指向内部定义某虚拟主机。缺省虚拟主机可以改动,默认localhost。
虚拟主机配置。
name必须是主机名,用主机名来匹配。
appBase,当前主机的网页根目录,是相对于CATALINA_HOME,也可以使用绝对路径
unpackWARs是否自动解压war格式
autoDeploy 热部署,自动加载并运行应用
虚拟主机配置实验
尝试再配置一个虚拟主机,并将myapp部署到/data/webapps目录下
vim /usr/local/tomcat/conf/server.xml
常见虚拟主机根目录
# mkdir /data/webapps -pv
mkdir: 已创建目录 "/data"
mkdir: 已创建目录 "/data/webapps"
# cp -r ~/projects/myapp/ /data/webapps/ROOT
# pwd
/usr/local/tomcat
# bin/shutdown.sh
# bin/startup.sh
刚才在虚拟主机中主机名定义node1.magedu.com,所以需要主机在本机手动配置一个域名解析。
如果是windows,修改在C:\Windows\System32\drivers\etc下的hosts文件,需要管理员权限。
使用http://node1.magedu.com:8080/访问试试看。
也可以在tomcat的host-manager中观察。
Context配置
Context作用:
路径映射
-
应用独立配置,例如单独配置应用日志、单独配置应用访问控制
path指的是访问的路径
docBase,可以是绝对路径,也可以是相对路径(相对于Host的appBase)
reloadable,true表示如果WEB-INF/classes或META-INF/lib目录下.class文件有改动,就会将WEB应用
重新加载,性能消耗很大。生成环境中,会使用false来禁用。
将~/projects/myapp/下面的项目文件复制到/data/下
# cp -r ~/projects/myapp /data/myappv1
# cd /data
# ln -sv myappv1 test
可以修改一下index.jsp好区别一下。
Tomcat的配置文件server.xml中修改如下
使用http://node1.magedu.com:8080/test/
注意:这里特别使用了软链接,原因就是以后版本升级,需要将软链接指向myappv2,重启Tomcat。
如果新版上线后,出现问题,重新修改软链接到上一个版本的目录,并重启,就可以实现回滚。
常见部署方式
Nginx和Tomcat实践
从epel源安装nginx
# wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo
# yum install nginx -y
# cd /etc/nginx
# vim nginx.conf
# nginx -t
全部反向代理测试
# 全部反向代理测试
location / {
# proxy_pass http://127.0.0.1:8080; # 不管什么请求,都会访问后面的localhost虚拟主机
proxy_pass http://node1.magedu.com:8080; # 修改服务器的/etc/hosts
}
http://192.168.142.151/或者http://node1.magedu.com/全部代理给了自定义的虚拟主机注意:node1.magedu.com需要配置解析,可以通过nginx -t测试。
动静分离代理
location / {
root /data/webapps/ROOT;
index index.html;
}
# ~* 不区分大小写
location ~* \.jsp$ {
proxy_pass http://node1.magedu.com:8080; # /etc/hosts
}
在/data/webapps/ROOT目录下增加一个index.html。
http://192.168.142.151/和http://192.168.142.151/index.jsp测试一下看看。
但是实际上Tomcat不太适合做动静分离,用它来管理程序的图片不好做动静分离部署。
应用管理
# 全部反向代理
location / {
proxy_pass http://127.0.0.1:8080; # 不管什么请求,都会访问后面的localhost虚拟主机
}
点击Tomcat首页的右上角的“Manager App”按钮,弹出登录对话框。
管理界面
Applications 应用程序管理,可以启动、停止、重加载、反部署、清理过期session
Deploy 可以热部署,也可以部署war文件。
[图片上传失败...(image-6eed46-1592277670825)]
Host Manager虚拟主机管理
重启Tomcat,点击“Host Manager”按钮
可以新增虚拟主机。
httpd和Tomcat实践
# yum install httpd -y
# httpd -M
# httpd -M | grep proxy
proxy_module (shared)
proxy_ajp_module (shared)
proxy_balancer_module (shared)
proxy_http_module (shared)
httpd配置
proxy_http_module模块代理配置
ServerName node1.magedu.com
ProxyRequests Off
ProxyVia On
ProxyPreserveHost On
ProxyPass / http://127.0.0.1:8080/
ProxyPassReverse / http://127.0.0.1:8080/
ProxyRequests:Off关闭正向代理。
ProxyPass:反向代理指令
ProxyPassReverse:保留代理的response头不重写(个别除外)
ProxyPreserveHost:On开启。让代理保留原请求的Host首部
ProxyVia:On开启。代理的请求响应时提供一个response的via首部
# vim /etc/httpd/conf.d/http-tomcat.conf
# httpd -t
# systemctl start httpd
# /usr/local/tomcat/bin/startup.sh
http://192.168.142.151/
http://node1.magedu.com/
http://node1.magedu.com/index.jsp
以上3个URL看到了不同的页面,说明 ProxyPreserveHost On 起了作用。
设置 ProxyPreserveHost Off 再看效果,说明什么?
proxy_ajp_module模块代理配置
ServerName node1.magedu.com
ProxyRequests Off
ProxyVia On
ProxyPreserveHost On
ProxyPass / ajp://127.0.0.1:8009/
查看Server Status可以看到确实使用的是ajp连接了
相对来讲,A JP协议基于二进制比使用HTTP协议的连接器效率高些。
负载均衡
动态服务器的问题,往往就是并发能力太弱,往往需要多台动态服务器一起提供服务。如何把并发的压力分摊,这就需要调度,采用一定的调度策略,将请求分发给不同的服务器,这就是Load Balance负载均衡。
当单机的Tomcat,演化出多机多级部署的时候,一个问题便凸显出来,这就是Session。而这个问题的由来,都是由于HTTP协议在设计之初没有想到未来的发展。
HTTP的无状态,有连接和短连接
- 无状态:指的是服务器端无法知道2次请求之间的联系,即使是前后2次请求来自同一个浏览器,也没有任何数据能够判断出是同一个浏览器的请求。后来可以通过cookie、session机制来判断。
- 浏览器端第一次HTTP请求服务器端时,在服务器端使用session这种技术,就可以在服务器端产生一个随机值即SessionID发给浏览器端,浏览器端收到后会保持这个SessionID在Cookie当中,这个Cookie值一般不能持久存储,浏览器关闭就消失。浏览器在每一次提交HTTP请求的时候会把这个SessionID传给服务器端,服务器端就可以通过比对知道是谁了
- Session通常会保存在服务器端内存中,如果没有持久化,则易丢失
- Session会定时过期。过期后浏览器如果再访问,服务端发现没有此ID,将给浏览器端重新发 新的SessionID
- 更换浏览器也将重新获得新的SessionID
- 有连接:是因为HTTP1.x基于TCP协议,是面向连接的,需要3次握手、4次断开。
- 短连接:Http 1.1之前,都是一个请求一个连接,而Tcp的连接创建销毁成本高,对服务器有很大
的影响。所以,自Http 1.1开始,支持keep-alive,默认也开启,一个连接打开后,会保持一段时
间(可设置),浏览器再访问该服务器就使用这个Tcp连接,减轻了服务器压力,提高了效率。
会话保持方式
1、session sticky会话黏性
- Session绑定
nginx:source ip
HAProxy:cookie - 优点:简单易配置
- 缺点:如果目标服务器故障后,如果没有做sessoin持久化,就会丢失session
2、session复制集群
Tomcat自己的提供的多播集群,通过多播将任何一台的session同步到其它节点。
缺点
Tomcat的同步节点不宜过多,互相即时通信同步session需要太多带宽
每一台都拥有全部session,内存占用太多
3、session server
session 共享服务器,使用memcached、redis做共享的Session服务器。
规划
IP | 主机名 | 服务 | |
---|---|---|---|
192.168.142.151 | t0 | 调度器 | Nginx、HTTPD |
192.168.142.152 | t1 | tomcat1 | JDK8、Tomcat8 |
192.168.142.153 | t2 | tomcat2 | JDK8、Tomcat8 |
每台主机的域名解析
192.168.142.151 t0.magedu.com t0
192.168.142.152 t1.magedu.com t1
192.168.142.153 t2.magedu.com t2
环境变量配置
# vim /etc/profile.d/tomcat.sh
export CATALINA_HOME=/usr/local/tomcat
export PATH=$CATALINA_HOME/bin:$PATH
项目路径配置
# mkdir -pv /data/webapps/ROOT
编写测试jsp文件,内容在下面
# vim /data/webapps/ROOT/index.jsp
# scp -r server.xml 192.168.142.153:/usr/local/tomcat
启动Tomcat服务
# startup.sh
测试用jsp
t1和t2节点的/data/webapps/ROOT/index.jsp
<%@ page import="java.util.*" %>
lbjsptest
On <%=request.getServerName() %>
<%=request.getLocalAddr() + ":" + request.getLocalPort() %>
SessionID = <%=session.getId() %>
<%=new Date()%>
t1虚拟主机配置
t2虚拟主机配置
Nginx调度
# yum install nginx
# cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak
# vim /etc/nginx/nginx.conf
# nginx -t
# systemctl start nginx.service
nginx配置如下
upstream tomcats {
#ip_hash; # 先禁用看看轮询,之后开启开黏性
server t1.magedu.com:8080;
server t2.magedu.com:8080;
}
server {
location ~* \.(jsp|do)$ {
proxy_pass http://tomcats;
}
}
#小笔记:
vim /etc/nginx/nginx.conf
http {
...
upstream tomcats {
ip_hash;
server t1.magedu.com:8080;
server t2.magedu.com:8080;
}
server {
location / {
proxy_pass http://tomcats;
}
}
...
}
测试http://t0.magedu.com/index.jsp,可以看到轮询调度效果。
在upstream中使用ip_hash指令,使用客户端IP地址Hash。这个hash值使用IP v4地址的前24位或全部的IP v6地址。
配置完reload nginx服务。测试一下看看效果。关闭Session对应的Tomcat服务,再重启启动它,看看Session的变化。
Httpd调度
使用 httpd -M 可以看到proxy_balancer_module,用它来实现负载均衡。
方式 | 依赖模块 |
---|---|
http负载均衡 | mod_proxy mod_proxy_http mod_proxy_balancer |
ajp负载均衡 | mod_proxy mod_proxy_ajp mod_proxy_balancer |
关闭httpd默认主机
# cd /etc/httpd/conf
# vim httpd.conf
注释掉 #DocumentRoot "/var/www/html"
# cd ../conf.d
# vim vhosts.conf
# httpd -t
# systemctl start httpd
负载均衡配置说明
配置代理到balancer
ProxyPass [path] !|url [key=value [key=value ...]]
Balancer成员
BalancerMember [balancerurl] url [key=value [key=value ...]]
设置Balancer或参数
ProxySet url key=value [key=value ...]
ProxyPass和BalancerMember指令参数
参数 | 缺省值 | 说明 |
---|---|---|
min | 0 | 连接池最小容量 |
max | 1 - n | 连接池最大容量 |
retry | 60 | apache请求发送到后端服务器错误后等待的时间秒数。0表示立即重试 |
Balancer参数
参数 | 缺省值 | 说明 |
---|---|---|
loadfactor | 定义负载均衡后端服务器权重,取值范围1 - 100 | |
lbmethod | byrequests | 负载均衡调度方法。 byrequests 基于权重的统计请求个数进行调度; bytrafficz 执行基于权重的流量计数调度; bybusyness 通过考量每个后端服务器当前负载进行调度 |
maxattempts | 1 | 放弃请求前实现故障转移的次数,默认为1,其最大值不应该大 于总的节点数 |
nofailover | Off | 如果后端服务器没有Session副本,可以设置为On不允许故障 转移。 Off故障可以转移 |
stickysession | 调度器的sticky session名字,根据web后台编程语言不同,可 以设置为JSESSIONID或PHPSESSIONID |
ProxySet指令也可以使用上面的参数
conf.d/vhosts.conf内容如下
ProxyRequests Off
ProxyVia On
ProxyPreserveHost On
ProxyPass / balancer://lbtomcats/
ProxyPassReverse / balancer://lbtomcats/
BalancerMember http://t1.magedu.com:8080 loadfactor=1
BalancerMember http://t2.magedu.com:8080 loadfactor=2
loadfactor设置为1:2,便于观察。观察调度的结果是轮询的
小笔记
#tomcat-server1
vim /usr/local/src/tomcat/conf/server.xml
...
#tomcat-server2
vim /usr/local/src/tomcat/conf/server.xml
...
使用session黏性
修改conf.d/vhosts.conf
Header add Set-Cookie "ROUTEID=.%{BALANCER_WORKER_ROUTE}e; path=/" env=BALANCER_ROUTE_CHANGED
ProxyRequests Off
ProxyVia On
ProxyPreserveHost On
ProxyPass / balancer://lbtomcats/
ProxyPassReverse / balancer://lbtomcats/
BalancerMember http://t1.magedu.com:8080 loadfactor=1 route=Tomcat1
BalancerMember http://t2.magedu.com:8080 loadfactor=2 route=Tomcat2
ProxySet stickysession=ROUTEID
发现Session不变了,一直找的同一个Tomcat服务器。
ajp调度
修改conf.d/vhosts.conf
ProxyRequests Off
ProxyVia On
ProxyPreserveHost On
ProxyPass / balancer://lbtomcats/
ProxyPassReverse / balancer://lbtomcats/
BalancerMember ajp://t1.magedu.com:8009 loadfactor=1 route=Tomcat1
BalancerMember ajp://t2.magedu.com:8009 loadfactor=2 route=Tomcat2
#ProxySet stickysession=ROUTEID
ProxySet stickysession=ROUTEID 先禁用看看切换效果,开启后看看黏住效果。
开启后,发现Session不变了,一直找的同一个Tomcat服务器。虽然,上面的做法实现客户端在一段时间内找同一台Tomcat,从而避免切换后导致的Session丢失。但是如果Tomcat节点挂掉,那么Session依旧丢失。
假设有A、B两个节点,都把Session做了持久化。如果Tomcat A服务下线期间用户切换到了Tomcat B上,就获得了Tomcat B的Session,就算持久化Session的Tomcat A上线了,也没用了。
Tomcat Session集群
参考:https://tomcat.apache.org/tomcat-8.5-doc/cluster-howto.html
配置说明
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侦听器使用
添加到所有虚拟主机都可以启用Session复制
添加到,该虚拟主机可以启用Session复制
最后,在应用程序内部启用了才可以使用前提:
时间同步,确保NTP或Chrony服务正常运行。 # systemctl status chronyd
防火墙规则。 # systemctl stop firewalldIP 主机名 服务 192.168.142.151 t0 调度器 Nginx、HTTPD 192.168.142.152 t1 tomcat1 JDK8、Tomcat8 192.168.142.153 t2 tomcat2 JDK8、Tomcat8 本次把多播复制的配置放到缺省虚拟主机里面, 即Host之下。
特别注意修改Receiver的address属性为一个本机可对外的IP地址t1的server.xml中,如下
其他略去 t2的server.xml中,如下
其他略去 Tomcat重启后,ss命令能看到tomcat监听在4000端口上
尝试使用刚才配置过得负载均衡(移除Session黏性),测试发现Session还是变来变去。准备web.xml
在应用中增加WEB-INF,从全局复制一个web.xml过来# cp /usr/local/tomcat/conf/web.xml /data/webapps/ROOT/WEB-INF/
为web.xml的
标签增加子标签 来开启该应用程序的分布式。
重启全部Tomcat,通过负载均衡调度到不同节点,返回的SessionID不变了。
小笔记;复制集群
1、apache
#tomcat1
vim conf/server.xml
#tomcat2
vim conf/server.xml
bin/shutdown.sh
bin/startup.sh
2、nginx
#tomcat1
vim conf/server.xml
cp /usr/local/tomcat/conf/web.xml /data/webapps/ROOT/WEB-INF/
vim conf/web.xml #添加标签" "
#开启集群
NoSQL
NoSQL是对非SQL、非传统关系型数据库的统称。
NoSQL一词诞生于1998年,2009年这个词汇被再次提出指非关系型、分布式、不提供ACID的数据库设计模式。
随着互联网时代的到来,数据爆发式增长,数据库技术发展日新月异,要适应新的业务需求。
随着移动互联网、物联网的到来,大数据的技术中NoSQL也同样重要。
https://db-engines.com/en/ranking
分类
- Key-value Store
redis、memcached - Document Store
mongodb、CouchDB - Column Store列存数据库,Column-Oriented DB
HBase、Cassandra - Graph DB
Neo4j - Time Series 时序数据库
InfluxDB
Memcached
Memcached只支持能序列化的数据类型,不支持持久化,基于Key-Value的内存缓存系统。
内存分配机制
应用程序运行需要使用内存存储数据,但对于一个缓存系统来说,申请内存、释放内存将十分频繁,非
常容易导致大量内存碎片,最后导致无连续可用内存可用。
Memcached采用了Slab Allocator机制来分配、管理内存 。
- Page:分配给Slab的内存空间,默认为1MB,分配后就得到一个Slab。Slab分配之后内存按照固定字节大小等分成chunk。
- Chunk:用于缓存记录kv值的内存空间。Memcached会根据数据大小选择存到哪一个chunk中,假设chunk有128bytes、64bytes,数据只有100bytes存储在128bytes中,存在些浪费。
Chunk最大就是Page的大小,即一个Page中就一个Chunk - Slab Class:Slab按照大小分组,就组成不同的Slab Class
- 如果有100bytes要存,那么Memcached会选择上图中Slab Class 2存储,因为它是120bytes的Chunk。
- Slab之间的差异可以使用Growth Factor控制,默认1.25。
懒过期Lazy Expiration
memcached不会监视数据是否过期,而是在取数据时才看是否过期,过期的把数据有效期限标识为0,并不清除该数据。以后可以覆盖该位置存储其它数据。
LRU
当内存不足时,memcached会使用LRU(Least Recently Used)机制来查找可用空间,分配给新纪录使用。
集群
Memcached集群,称为基于客户端的分布式集群。
Memcached集群内部并不互相通信,一切都需要客户端连接到Memcached服务器后自行组织这些节点,并决定数据存储的节点。
安装
# yum install memcached
# rpm -ql memcached
/etc/sysconfig/memcached
/usr/bin/memcached
/usr/bin/memcached-tool
/usr/lib/systemd/system/memcached.service
/usr/share/doc/memcached-1.4.15
/usr/share/doc/memcached-1.4.15/AUTHORS
/usr/share/doc/memcached-1.4.15/CONTRIBUTORS
/usr/share/doc/memcached-1.4.15/COPYING
/usr/share/doc/memcached-1.4.15/ChangeLog
/usr/share/doc/memcached-1.4.15/NEWS
/usr/share/doc/memcached-1.4.15/README.md
/usr/share/doc/memcached-1.4.15/protocol.txt
/usr/share/doc/memcached-1.4.15/readme.txt
/usr/share/doc/memcached-1.4.15/threads.txt
/usr/share/man/man1/memcached-tool.1.gz
/usr/share/man/man1/memcached.1.gz
# cat /usr/lib/systemd/system/memcached.service
[Service]
Type=simple
EnvironmentFile=-/etc/sysconfig/memcached
ExecStart=/usr/bin/memcached -u $USER -p $PORT -m $CACHESIZE -c $MAXCONN $OPTIONS
# cat /etc/sysconfig/memcached
PORT="11211"
USER="memcached"
MAXCONN="1024"
CACHESIZE="64"
OPTIONS=""
前台显示看看效果
# memcached -u memcached -p 11211 -f 1.25 -vv
# systemctl start memcached
-
修改memcached运行参数,可以使用下面的选项修改/etc/sysconfig/memcached文件
-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
# yum list all | grep memcached
memcached.x86_64 1.4.15-10.el7_3.1 @base
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
php-ZendFramework-Cache-Backend-Libmemcached.noarch
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
与memcached通信的不同语言的连接器。
libmemcached提供了C库和命令行工具。
协议
查看/usr/share/doc/memcached-1.4.15/protocol.txt
# yum install telnet
# telnet locahost 11211
stats
add mykey 1 60 4
test
STORED
get mykey
VALUE mykey 1 4
test
END
set mykey 1 60 5
test1
STORED
get mykey
VALUE mykey 1 5
test1
END
add key flags exptime bytes , 增加key,过期时间为秒,bytes为存储数据的字节数
session共享服务器
msm
msm(memcached session manager)提供将Tomcat的session保持到memcached或redis的程序,可以实现高可用。
目前项目托管在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。
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
sticky模式
原理
当请求结束时Tomcat的session会送给memcached备份。即Tomcat session为主session,memcached session为备session,使用memcached相当于备份了一份Session。
查询Session时Tomcat会优先使用自己内存的Session,Tomcat通过jvmRoute发现不是自己的Session,便从memcached中找到该Session,更新本机Session,请求完成后更新memcached。
部署
. \ / .
. X .
. / \ .
t1和m1部署在一台主机上,t2和m2部署在同一台。
配置
放到 $CATALINA_HOME/conf/context.xml 中
特别注意,t1配置中为failoverNodes="n1", t2配置为failoverNodes="n2"
以下是sticky的配置
...
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中看到下面的内容
信息 [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中了。
import memcache # pip install python-memcached
mc = memcache.Client(['192.168.142.152:11211', '192.168.142.153:11211'], debug=True)
stats = mc.get_stats()[0]
print(stats)
for k,v in stats[1].items():
print(k, v)
print('-' * 30)
# 查看全部key
print(mc.get_stats('items')) # stats items 返回 items:5:number 1
print('-' * 30)
for x in mc.get_stats('cachedump 5 0'):# stats cachedump 5 0 # 5和上面的items返回的值有关;0表示全部
print(x)
t1、t2、n1、n2依次启动成功,分别使用http://t1.magedu.com:8080/ 和http://t2.magedu.com:8080/ 观察。
看起负载均衡调度器,通过http://t0.magedu.com来访问看看效果
On tomcats
192.168.142.153:8080
SessionID = 2A19B1EB6D9649C9FED3E7277FDFD470-n2.Tomcat1
Wed Jun 26 16:32:11 CST 2019
On tomcats
192.168.142.152:8080
SessionID = 2A19B1EB6D9649C9FED3E7277FDFD470-n1.Tomcat2
Wed Jun 26 16:32:36 CST 2019
可以看到浏览器端被调度到不同Tomcat上,但是都获得了同样的SessionID。
停止t2、n2看看效果,恢复看看效果。
小笔记:测试msm
systemctl stop redis #停一台查看
systemctl memcached #都停用查看
#查看使用python程序
non-sticky模式
原理
从msm 1.4.0之后开始支持non-sticky模式。
Tomcat session为中转Session,如果n1为主session,n2为备session。产生的新的Session会发送给主、备memcached,并清除本地Session。
n1下线,n2转正。n1再次上线,n2依然是主Session存储节点。
memcached配置
放到 $CATALINA_HOME/conf/context.xml 中
...
redis配置
下载jedis.jar,放到 $CATALINA_HOME/lib/ ,对应本次安装就是/usr/local/tomcat/lib。
# yum install redis
# vim /etc/redis.conf
bind 0.0.0.0
# systemctl start redis
放到 $CATALINA_HOME/conf/context.xml 中
...
python程序
import redis
client = redis.Redis(host='192.168.142.140', port=6379 db=1)
print(client.keys())
print(client.get, get('abc'))
client.set('abc',0b1100010)
print(client.get, get('abc'))
client.set(0b11,'啊'.encode())
print(client.get('3'))
总结
- 通过多组实验,使用不同技术实现了session持久机制
- session绑定,基于IP或session cookie的。其部署简单,尤其基于session黏性的方式,粒度小,
对负载均衡影响小。但一旦后端服务器有故障,其上的session丢失。 - session复制集群,基于tomcat实现多个服务器内共享同步所有session。此方法可以保证任意一
台后端服务器故障,其余各服务器上还都存有全部session,对业务无影响。但是它基于多播实现
心跳,TCP单播实现复制,当设备节点过多,这种复制机制不是很好的解决方案。且并发连接多的
时候,单机上的所有session占据的内存空间非常巨大,甚至耗尽内存。 - session服务器,将所有的session存储到一个共享的内存空间中,使用多个冗余节点保存
session,这样做到session存储服务器的高可用,且占据业务服务器内存较小。是一种比较好的解
决session持久的解决方案。
-
以上的方法都有其适用性。生产环境中,应根据实际需要合理选择。
不过以上这些方法都是在内存中实现了session的保持,可以使用数据库或者文件系统,把session数据存储起来,持久化。这样服务器重启后,也可以重新恢复session数据。不过session数据是有时效性的,是否需要这样做,视情况而定。
JVM
Java目前是最流行的编程语言。
目前主要应用在企业级WEB开发和大数据领域。
JVM组成
使用Java语言编写.java Source Code文件,通过javac编译成.class Byte Code文件。
class loader类加载器:将所需的类加载到内存,必要时将类实例化成实例。
图中中间部分是进程的内存逻辑结构,称为Jvm运行时区域,由下面几部分构成:
方法区:所有线程共享的内存空间,存放已加载的类信息、常量和静态变量。
heap堆:所有线程共享的内存空间,存放创建的所有对象。堆是靠GC垃圾回收器管理的。
Java栈:每个线程会分配一个栈,存放线程用的本地变量、方法参数和返回值等。
PC寄存器:PC, 即Program Counter,每一个线程用于记录当前线程正在执行的字节码指令地址。因为线程需要切换,当一个线程被切换回来需要执行的时候,知道执行到哪里了。
本地方法栈:为本地方法执行构建的内存空间,存放本地方法执行时的局部变量、操作数等。
所谓本地方法,简单的说是非Java实现的方法,例如操作系统的C编写的库提供的本地方法,Java调用这些本地方法接口执行。但是要注意,本地方法应该避免直接编程使用,因为Java可能跨平台使用,如果用了Windows API,换到了Linux平台部署就有了问题。
虚拟机
目前Oracle官方使用的是HotSpot, 它最早由一家名为"Longview Technologies"公司设计,使用了很 多优秀的设计理念和出色的性能,1997年该公司被SUN公司收购。后来随着JDK一起发布了HotSpot VM。目前HotSpot是最主要的VM。
安卓程序需要运行在JVM上,而安卓平台使用了Google自研的Java虚拟机——Dalvid,适合于内存、处 理器能力有限系统。
GC垃圾收集器
堆内存里面经常创建、销毁对象,内存也是被使用、被释放。如果不妥善处理,一个使用频繁的进程, 将可能有内存容量,但是无法分配出可用内存空间,因为没有连续成片的内存了,内存全是碎片化的数 据。
回收基本算法
- 引用计数
每一个堆内对象上都与一个私有引用计数器,记录着被引用的次数,引用计数清零,该对象所占用
堆内存就可以被回收。循环引用的对象都无法引用计数归零,就无法清除。 - 标记-清除 Mark-Sweep
分垃圾标记阶段和内存释放阶段。标记阶段,找到所有可访问对象打个标记。清理阶段,遍历整个
堆,对未标记对象清理 - 复制 Copying
先将可用内存分为大小相同两块区域A和B,每次只用其中一块,比如A。当A用完后,则将A中存活的对象复制到B。复制到B的时候连续的使用内存,最后将A一次性清除干净。缺点是比较浪费内存,能使用原来一半的内存,因为内存对半划分了,复制过程毕竟也是有代价。好处是没有碎片,复制过程中保证对象使用连续空间。 - 标记-压缩 Mark-Compact
分垃圾标记阶段和内存整理阶段。标记阶段,找到所有可访问对象打个标记。内存清理阶段时,整理时将对象向内存一端移动,整理后存活对象连续的集中在内存一端。 - 分代收集算法
既然上述垃圾回收算法都有优缺点,能不能对不同数据进行区分管理,不同分区对数据实施不同回收策略,分而治之。
1.7及以前,堆内存分为新生代、老年代、持久代。
1.8开始,持久代没有了,取而代之MetaSpace。
STW
对于大多数垃圾回收算法而言,GC线程工作时,需要停止所有工作的线程,称为Stop The World。GC完成时,恢复其他工作线程运行。这也是JVM运行中最头疼的问题。
分代堆内存
Heap堆内存分为
- 新生代:刚刚创建的对象
伊甸园区
存活区Servivor Space:有2个存活区,一个是from区,一个是to区。它们大小相等、地位相同、可互换。 - to指的是本次复制数据的目标区
- 老年代:长时间存活的对象
- 持久代:JVM的类和方法
新生代回收
起始时,所有新建对象都出生在eden,当eden满了,启动GC。这个称为Young GC,Minor GC。
先标记eden存活对象,然后将存活对象复制到s0(假设本次是s0,也可以是s1,它们可以调换),eden剩余所有空间都“清空”。GC完成。
继续新建对象,当eden满了,启动GC。
先标记eden和s0中存活对象,然后将存活对象复制到s1。将eden和s0“清空”。
继续新建对象,当eden满了,启动GC。
先标记eden和s1中存活对象,然后将存活对象复制到s0。将eden和s1“清空”。
以后就重复上面的步骤。
大多数对象都不会存活很久,而且创建活动非常多,新生代就需要频繁垃圾回收。
但是,如果一个对象一直存活,它最后就在from、to来回复制,如果from区中对象复制次数达到阈值,就直接复制到老年代。
老年代回收
进入老年代的数据较少,所以老年代区被占满的速度较慢,所以垃圾回收也不频繁。老年代GC称为OldGC,Major GC。
由于老年代对象一般来说存活次数较长,所有较常采用标记-压缩算法。
Full GC:对所有“代”的内存进行垃圾回收Minor GC比较频繁,Major GC较少。但一般Major GC时,由于老年代对象也可以引用新生代对象,所以先进行一次Minor GC,然后在Major GC会提高效率。可以认为回收老年代的时候完成了一次Full
GC。
GC触发条件
Minor GC触发条件:当eden区满了触发
Full GC触发条件:
- 老年代满了
- 新生代搬向老年代,老年代空间不够
- 持久代满了
- System.gc()手动调用。不推荐
调整策略
减少STW时长,串行变并行
减少GC次数,要分配合适的内存大小
对JVM调整策略应用极广
- 在WEB领域中Tomcat等
- 在大数据领域Hadoop生态各组件
- 在消息中间件领域的Kafka等
- 在搜索引擎领域的ElasticSearch、Solr等
在不同领域对JVM需要不同的调整策略
垃圾收集器类型
按回收线程数:
- 串行垃圾回收器:一个GC线程完成回收工作
- 并行垃圾回收器:多个GC线程同时一起完成回收工作,充分利用CPU资源
指的是GC线程是否串并行
按工作模式不同:
- 并发垃圾回收器:让GC线程垃圾回收某些阶段可以和工作线程一起进行。
- 独占垃圾回收器:只有GC在工作,STW一直进行到回收完毕,工作线程才能继续执行。
指的是GC线程和工作线程是否一起运行
一般情况下,我们大概可以使用以下原则:
客户端或较小程序,内存使用量不大,可以使用串行回收;
对于服务端大型计算,可以使用并行回收;
大型WEB应用,用户端不愿意等,尽量少的STW,可以使用并发回收;
内存调整
参数 | 说明 | 举例 |
---|---|---|
-Xms | 设置应用程序初始使用的堆内存大小(新生代+老 年代) | -Xms2g |
-Xmx | 设置应用程序能获得的最大堆内存 早期JVM不建议超过32G,内存管理效率下降 | -Xms4g |
-XX:NewSize | 设置初始新生代大小 | |
-XX:MaxNewSize | 设置最大新生代内存空间 | |
-XX:NewRatio | 以比例方式设置新生代和老年代 | -XX:NewRatio=2 new/old=1/2 |
- XX:SurvivorRatio | 以比例方式设置eden和survivor | - XX:SurvivorRatio=6 eden/survivor=6/1 survivor/new=1/8 |
-Xss | 设置线程的栈大小 |
$ java -Xms512m -Xmx1g HelloWorld
// 测试用java程序
// javac HelloWorld.java
// java -Xms512m -Xmx1g HelloWorld
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();
}
}
}
Tomcat设置
默认不指定,-Xmx大约使用了1/4的内存,当前本机内存指定约为1G。
在bin/catalina.sh中增加
JAVA_OPTS="-server -Xmx512m -Xms128m -XX:NewSize=48m -XX:MaxNewSize=200m"
-server:VM运行在server模式,为在服务器端最大化程序运行速度而优化
-client:VM运行在Client模式,为客户端环境减少启动时间而优化
重启Tomcat,观察
# ps aux | grep java
root 8980 2.7 12.3 3050788 123216 pts/0 Sl 21:53 0:05
/usr/java/default/bin/java -
Djava.util.logging.config.file=/usr/local/tomcat/conf/logging.properties -
Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Xmx512m -Xms128m -
XX:NewSize=48m -XX:MaxNewSize=200m -Djdk.tls.ephemeralDHKeySize=2048 -
Djava.protocol.handler.pkgs=org.apache.catalina.webresources -
Dorg.apache.catalina.security.SecurityListener.UMASK=0027 -Dignore.endorsed.dirs= -
classpath /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar -
Dcatalina.base=/usr/local/tomcat -Dcatalina.home=/usr/local/tomcat -
Djava.io.tmpdir=/usr/local/tomcat/temp org.apache.catalina.startup.Bootstrap start
垃圾回收器
新生代
- 新生代串行收集器:单线程、独占式串行,回收算法标记-复制
- 新生代并行收集器:将单线程的串行收集器变成了多线程并行、独占式
- 新生代并行回收收集器:多线程并行、独占式,使用复制算法,关注调整吞吐量
老年代
- 老年代串行收集器:单线程、独占式串行,回收算法使用标记-压缩
- 老年代并行回收收集器:多线程、独占式并行,回收算法使用标记-压缩,关注调整吞吐量
CMS收集器
- Concurrent Mark Sweep并发标记清除算法
- 在某些阶段尽量使用和工作线程一起运行,减少停顿时长。是互联网站点服务端BS系统上较 佳的回收算法
- 分为4个阶段:初始标记、并发标记、重新标记、并发清除,在初始标记、重新标记时需要 STW。
G1收集器
- Garbage First是最新垃圾回收器,从JDK1.6实验性提供,JDK1.7发布,其设计目标是在多处理器、大内存服务器端提供优于CMS收集器的吞吐量和停顿控制的回收器。建议JDK8再考虑它。
- 基于标记-压缩算法。+UseG1GC
- 分为4个阶段:初始标记、并发标记、最终标记、筛选回收。并发标记并发执行,其它阶段STW只有GC线程并行执行。
垃圾收集器设置
可以单独指定新生代、老年代的垃圾收集器
-XX:+UseSerialGC
运行在Client模式下,新生代、老年代都启用 串行收集器-XX:+UseParNewGC
新生代使用 并行收集器 ,老年代使用 串行收集器-XX:+UseParallelGC
新生代使用 并行回收收集器 ,老年代使用 串行收集器-XX:+UseParallelOldGC
新生代、老年代都是用 并行回收收集器
-XX:ParallelGCThreads=8,在关注吞吐量的场景使用它增加并行线程数-
XX:+UseConcMarkSweepGC
- 新生代使用 并行收集器 ,老年代使用 CMS收集器
- 响应时间要短,停顿短使用这个垃圾收集器
- -XX:CMSInitiatingOccupancyFraction=N,N为0-100整数表示达到老年代的大小的百分比多少触发回收
默认68 - 由于CMS算法有碎片产生,还可以将-XX:+UseCMSCompactAtFullCollection开启,在CMS收集后,进行一次内存碎片整理。-XX:CMSFullGCsBeforeCompaction=N设定多少次CMS后,进行一次整理
-XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=5
将参数加入到bin/catalina.sh中,重启观察Tomcat status。老年代已经使用CMS
开启垃圾回收统计信息
-XX:+PrintGC 输出GC信息
-XX:+PrintGCDetails 输出GC详细信息
-XX:+PrintGCTimeStamps 与前两个组合使用,在信息上加上一个时间戳
-XX:+PrintHeapAtGC 生成更多信息供分析,日志会更大
以上调试完成后,请移除这些参数,否则有非常多的日志输出
工具
$JAVA_HOME/bin下
命令 | 说明 |
---|---|
jps | 查看所有jvm进程 |
jinfo | 查看进程的运行环境参数,主要是jvm命令行参数 |
jstat | 对jvm应用程序的资源和性能进行实时监控 |
jstack | 查看所有线程的运行状态 |
jmap | 查看jvm占用物理内存的状态 |
jhat | +UseParNew |
jconsole | |
jvisualvm |
jps:Java virutal machine Process Status tool,
jps [-q] [-mlvV] []
-q:静默模式;
-v:显示传递给jvm的命令行参数;
-m:输出传入main方法的参数;
-l:输出main类或jar完全限定名称;
-v:显示通过flag文件传递给jvm的参数;
[]:主机id,默认为localhost;
详细列出当前Java进程信息
# jps -l -v
jinfo:输出给定的java进程的所有配置信息;
jinfo [option]
-flags:打印 VM flags
-sysprops:to print Java system properties
-flag :to print the value of the named VM flag
先获得一个java进程ID,然后jinfo
# jps
# jinfo 6822
# jinfo -flags 6822
jstat:输出指定的java进程的统计信息
jstat -help|-options
jstat -
程序员常用堆栈情况查看工具
jstack:查看指定的java进程的线程栈的相关信息;
jstack [-l]
jstack -F [-m] [-l]
-l:long listings,会显示额外的锁信息,因此,发生死锁时常用此选项;
-m:混合模式,既输出java堆栈信息,也输出C/C++堆栈信息;
-F:当使用“jstack -l PID"无响应,可以使用-F强制输出信息;
先获得一个java进程ID,然后jinfo
# jps
# jstack -l 6822
jmap:Memory Map, 用于查看堆内存的使用状态;
查看进程堆内存情况
# jmap -heap 6822
jhat:Java Heap Analysis Tool
jmap [option]
查看堆空间的详细信息:
jmap -heap
查看堆内存中的对象的数目:
jmap -histo[:live]
live:只统计活动对象;
保存堆内存数据至文件中,而后使用jvisualvm或jhat进行查看:
jmap -dump:
dump-options:
live dump only live objects; if not specified, all objects in the heap are dumped.
format=b binary format
file= dump heap to
Tomcat常用配置
1、内存空间优化
JAVA_OPTS="-server -Xms32g -Xmx32g -XX:NewSize= -XX:MaxNewSize= "
-server:服务器模式
-Xms:堆内存初始化大小;
-Xmx:堆内存空间上限;
-XX:NewSize=:新生代空间初始化大小;
-XX:MaxNewSize=:新生代空间最大值;
2、线程池调整
常用属性:
- 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