35-tomcat

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,如下图

35-tomcat_第1张图片
image.png
名称 说明
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中的下面 标签 内容(默认页),复制到/usr/local/tomcat/webapps/ROOT/WEB-INF/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。
如果新版上线后,出现问题,重新修改软链接到上一个版本的目录,并重启,就可以实现回滚。

常见部署方式

35-tomcat_第2张图片
image.png
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 firewalld

    IP 主机名 服务
    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持久机制
  1. session绑定,基于IP或session cookie的。其部署简单,尤其基于session黏性的方式,粒度小,
    对负载均衡影响小。但一旦后端服务器有故障,其上的session丢失。
  2. session复制集群,基于tomcat实现多个服务器内共享同步所有session。此方法可以保证任意一
    台后端服务器故障,其余各服务器上还都存有全部session,对业务无影响。但是它基于多播实现
    心跳,TCP单播实现复制,当设备节点过多,这种复制机制不是很好的解决方案。且并发连接多的
    时候,单机上的所有session占据的内存空间非常巨大,甚至耗尽内存。
  3. 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垃圾收集器

堆内存里面经常创建、销毁对象,内存也是被使用、被释放。如果不妥善处理,一个使用频繁的进程, 将可能有内存容量,但是无法分配出可用内存空间,因为没有连续成片的内存了,内存全是碎片化的数 据。

回收基本算法
  1. 引用计数
    每一个堆内对象上都与一个私有引用计数器,记录着被引用的次数,引用计数清零,该对象所占用
    堆内存就可以被回收。循环引用的对象都无法引用计数归零,就无法清除。
  2. 标记-清除 Mark-Sweep
    分垃圾标记阶段和内存释放阶段。标记阶段,找到所有可访问对象打个标记。清理阶段,遍历整个
    堆,对未标记对象清理
  3. 复制 Copying
    先将可用内存分为大小相同两块区域A和B,每次只用其中一块,比如A。当A用完后,则将A中存活的对象复制到B。复制到B的时候连续的使用内存,最后将A一次性清除干净。缺点是比较浪费内存,能使用原来一半的内存,因为内存对半划分了,复制过程毕竟也是有代价。好处是没有碎片,复制过程中保证对象使用连续空间。
  4. 标记-压缩 Mark-Compact
    分垃圾标记阶段和内存整理阶段。标记阶段,找到所有可访问对象打个标记。内存清理阶段时,整理时将对象向内存一端移动,整理后存活对象连续的集中在内存一端。
  5. 分代收集算法
    既然上述垃圾回收算法都有优缺点,能不能对不同数据进行区分管理,不同分区对数据实施不同回收策略,分而治之。
    1.7及以前,堆内存分为新生代、老年代、持久代。
    1.8开始,持久代没有了,取而代之MetaSpace。
STW

对于大多数垃圾回收算法而言,GC线程工作时,需要停止所有工作的线程,称为Stop The World。GC完成时,恢复其他工作线程运行。这也是JVM运行中最头疼的问题。

分代堆内存
35-tomcat_第3张图片
image.png

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

你可能感兴趣的:(35-tomcat)