WEB应用服务器TOMCAT

反向代理中部署 Tomcat

常见部署方式

单应用部署:仅部署单机 Tomcat ,由 Tomcat 直接响应客户端请求

单机反向代理部署:前端部署 Nginx 或 Httpd 来响应客户端请求,将需要由 Tomcat 处理的请求 转发到后端,可实现动静分离

多机反向代理部署:前端部署 Nginx 来响应客户端请求,配合负载均衡将动态请求轮流转发到后端 Tomcat 服务器处理,可以将静态资源直接部署在前端 Nginx 上,实现动静分离

多机多级反向代理部署:可以分别用 Nginx 实现四层和七层的代理

Nginx 单机反向代理实现

在同一台服务器上部署 Nginx 和 Tomcat,Nginx 监听 80 端口,将请求转发至后端 Tomcat 的 8080 端 口进行处理

#tomcat 配置
[root@ubuntu ~]# cd /usr/local/tomcat

[root@ubuntu tomcat]# cat conf/server.xml
......
......
 <Host name="java.m99-baidu.com"  appBase="/data/webapps" unpackWARs="true"
autoDeploy="true">
   </Host>
   
[root@ubuntu tomcat]# tree /data/webapps/ROOT/
/data/webapps/ROOT/
├── index.html
└── test.jsp

[root@ubuntu ~]# systemctl restart tomcat.service 


#nginx 配置
[root@ubuntu ~]# cat /etc/nginx/conf.d/java.m99-baidu.com.conf
server{
 listen 80;
 server_name java.m99-baidu.com;
 
 location / {
 proxy_pass http://127.0.0.1:8080;
 proxy_set_header host $http_host;
 }
}

[root@ubuntu ~]# systemctl restart nginx.service

# 配置域名解析,并在浏览器中测试
# http://java.m99-baidu.com/
# http://java.m99-baidu.com/test.jsp

#在 nginx 中配置动静分离
[root@ubuntu ~]# cat /etc/nginx/sites-enabled/java.m99-baidu.com.conf
server{
 listen 80;
 server_name java.m99-baidu.com;
 root /var/www/html/java.m99-baidu.com;
 # 将 .jsp 转发给 tomcat
 location ~* \.jsp$ {
 proxy_pass http://127.0.0.1:8080;
 proxy_set_header host $http_host;
 }
 
}

[root@ubuntu ~]# systemctl restart nginx.service
[root@ubuntu ~]# tree /var/www/html/java.m99-baidu.com/
/var/www/html/java.m99-baidu.com/
├── index.html
└── test.html

# 在浏览器中测试
# http://java.m99-baidu.com/
# http://java.m99-baidu.com/test.html
# http://java.m99-baidu.com/test.jsp

Nginx 代理多机 Tomcat 实现

服务IP
Nginx10.0.0.206
Tomcat10.0.0.208
Tomcat10.0.0.210
#tomcat 部署,两台配置一样
[root@ubuntu24 ~]# wget https://dlcdn.apache.org/tomcat/tomcat-9/v9.0.95/bin/apache-tomcat-9.0.95.tar.gz
[root@ubuntu24 ~]# tar -xf apache-tomcat-9.0.95.tar.gz -C /usr/local/
[root@ubuntu24 ~]# cd /usr/local/
[root@ubuntu local]# ln -sv apache-tomcat-9.0.95/ tomcat
'tomcat' -> 'apache-tomcat-9.0.95/'

#加环境变量
[root@ubuntu local]# ln -sv /usr/local/tomcat/bin/* /usr/local/bin/

#添加用户并修改文件属主属组
[root@ubuntu ~]# useradd -r -s /sbin/nologin tomcat
[root@ubuntu ~]# chown -R tomcat.tomcat /usr/local/tomcat/

#服务脚本
[root@rocky ~]# cat /lib/systemd/system/tomcat.service
[Unit]
Description=Tomcat
After=syslog.target network.target

[Service]
Type=forking
Environment=JAVA_HOME=/usr/lib/jvm/jdk-11-oracle-x64/
ExecStart=/usr/local/tomcat/bin/startup.sh
ExecStop=/usr/local/tomcat/bin/shutdown.sh
PrivateTmp=true
User=tomcat
Group=tomcat

[Install]
WantedBy=multi-user.target

[root@ubuntu ~]# systemctl daemon-reload

#配置服务
[root@ubuntu tomcat]# cat conf/server.xml
......
......
 <Host name="java.m99-baidu.com"  appBase="/data/webapps" unpackWARs="true"
autoDeploy="true">
</Host> 

[root@ubuntu tomcat]# chown -R tomcat.tomcat /data

[root@ubuntu tomcat]# tree /data/webapps/ROOT/
/data/webapps/ROOT/
├── index.html
└── test.jsp

[root@ubuntu ~]# cat /data/webapps/ROOT/test.jsp 
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
 <head>
 <meta charset="utf-8">
 <title>jsp</title>
 </head>
 <body>
 hello world!<br />
 <% 
 out.println("this is test page");
 %>
 <br />
 <%=request.getRequestURL()%><br />
 <%=request.getLocalAddr() + ":" + request.getLocalPort() %><br />
 SessionID = <span style="color:blue"><%=session.getId() %><br />
 </body>
</html>

[root@ubuntu ~]# systemctl start tomcat.service

#nginx 配置
[root@ubuntu ~]# cat /etc/nginx/sites-enabled/java.m99-baidu.com.conf 
upstream tomcat {
 server 10.0.0.208:8080;
 server 10.0.0.210:8080;
}

server{
 listen 80;
 server_name java.m99-baidu.com;
 root /var/www/html/java.m99-baidu.com;
 location ~* \.jsp$ {
 proxy_pass http://tomcat;
 proxy_set_header host $http_host;
 }
}

# 重启服务,在浏览器中测试
[root@ubuntu ~]# systemctl restart tomcat.service

实现会话保持

#在浏览器中访问 http://java.m99-baidu.com/test.jsp , session 信息无法维持

# 实现客户端会话绑定
[root@ubuntu ~]# cat /etc/nginx/conf.d/java.m99-baidu.com.conf 
upstream tomcat {
 ip_hash;
 hash $remoute_addr; 
 hash $cookie_JSESSION consistent; # 三选一
 
 server 10.0.0.208:8080;
 server 10.0.0.210:8080;
}

配置 Https 监听 转发到后端 Http

#安装软件
[root@ubuntu ~]# apt install easy-rsa

#初始化环境
[root@ubuntu ~]# cd /usr/share/easy-rsa/
[root@ubuntu easy-rsa]# ./easyrsa init-pki

init-pki complete; you may now create a CA or requests.
Your newly created PKI dir is: /usr/share/easy-rsa/pki

#生成CA要构证书
[root@ubuntu easy-rsa]# ./easyrsa build-ca nopass
......
Common Name (eg: your user, host, or server name) [Easy-RSA CA]:baidu
CA creation complete and you may now import and sign cert requests.
Your new CA certificate file for publishing is at:
/usr/share/easy-rsa/pki/ca.crt

#生成私钥和证书申请者文件
[root@ubuntu easy-rsa]# ./easyrsa gen-req java.m99-baidu.com nopass
....
Common Name (eg: your user, host, or server name) [java.m99-baidu.com]:
Keypair and certificate request completed. Your files are:
req: /usr/share/easy-rsa/pki/reqs/java.m99-baidu.com.req
key: /usr/share/easy-rsa/pki/private/java.m99-baidu.com.key

#签发证书
[root@ubuntu easy-rsa]# ./easyrsa sign-req server java.m99-baidu.com
subject=
   commonName                = java.m99-baidu.com
Type the word 'yes' to continue, or any other input to abort.
 Confirm request details: yes
Certificate created at: /usr/share/easy-rsa/pki/issued/java.m99-baidu.com.crt

#合并服务器证书,签发机构证书为一个文件,注意顺序
[root@ubuntu easy-rsa]# cat pki/issued/java.m99-baidu.com.crt pki/ca.crt > 
pki/java.m99-baidu.com.pem

#给私钥加读权限
[root@ubuntu easy-rsa]# chmod +r pki/private/java.m99-baidu.com.key

#配置nginx
[root@ubuntu ~]# cd /etc/nginx/
[root@ubuntu nginx]# cat sites-enabled/java.m99-baidu.com.conf 
upstream tomcat {
 hash $remote_addr;
 server 10.0.0.208:8080;
 server 10.0.0.210:8080;
}

server{
 listen 80;
 server_name java.m99-baidu.com;
 return 302 https://$host$request_uri;
}

server{
 listen 443 ssl;
 server_name java.m99-baidu.com;
 root /var/www/html/java.m99-baidu.com;
 ssl_certificate /usr/share/easy-rsa/pki/java.m99-baidu.com.pem;
 ssl_certificate_key /usr/share/easy-rsa/pki/private/java.m99-baidu.com.key;
 ssl_session_cache shared:sslcache:20m;
 ssl_session_timeout 10m;
 location ~* \.jsp$ {
 proxy_pass http://tomcat;
 proxy_set_header host $http_host;
 }
}

[root@ubuntu nginx]# systemctl restart nginx.service

# 在浏览器中测试
# http://java.m99-baidu.com/
# http://java.m99-baidu.com/test.html
# http://java.m99-baidu.com/test.jsp

实现真实客户端IP地址透传

#nginx 中配置透传
[root@ubuntu nginx]# cat sites-enabled/java.m99-baidu.com.conf 
server{
 listen 443 ssl;
 server_name java.m99-baidu.com;
 root /var/www/html/java.m99-baidu.com;
 ssl_certificate /usr/share/easy-rsa/pki/java.m99-baidu.com.pem;
 ssl_certificate_key /usr/share/easy-rsa/pki/private/java.m99-baidu.com.key;
 ssl_session_cache shared:sslcache:20m;
 ssl_session_timeout 10m;
 location ~* \.jsp$ {
 proxy_pass http://tomcat;
 proxy_set_header host $http_host;
 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 }
}

[root@ubuntu nginx]# systemctl restart nginx.service

#tomcat 中设置单独的访问日志
[root@ubuntu ~]# cat /usr/local/tomcat/conf/server.xml
 <Host name="java.m99-baidu.com"  appBase="/data/webapps" unpackWARs="true"
autoDeploy="true">
       <Valve className="org.apache.catalina.valves.AccessLogValve"
directory="logs"
               prefix="java.m99-baidu.com_access_log" suffix=".txt"
 pattern="%h %l %u %t &quot;%r&quot; %s %b %{x-forwarded-for}i"
/>
   </Host>
   
[root@ubuntu ~]# systemctl restart tomcat.service

#在浏览器中测试并查看日志, 10.0.0.1 为直实客户端IP
[root@ubuntu ~]# tail /usr/local/tomcat/logs/java.m99-baidu.com_access_log.2024-09-20.txt
10.0.0.206 - - [20/Sep/2024:14:38:07 +0800] "GET /test.jsp HTTP/1.0" 200 310
10.0.0.1

Tomcat Session 复制集群

在上述 Nginx 代理多机 Tomcat 的架构中,我们在 Nginx 代理节点通过调度算法实现会话绑定,将来自 于同一客户端的请求调度到同相的后端服务器上,在这种情况下,如果后端 Tomcat 服务不可用, Nginx 在检测后会将请求调度到可用的后端节点,则原来的 Session 数据还是会丢失

我们可以使用 Tomcat 中的 session 复制功能,实现在多台 Tomcat 服务器上复制 Session 的功能,这 种配置下,任何一台 Tomcat 服务器都有全量的 Session 数据

Session 的作用

由于 Http 协议是无状态的,所以客户端和服务端无法维持状态,则服务端也无法识别不同的客户端,从 而也无法给客户端返回正确的数据,目前主流做法是当客户端第一次访问页面时,生成一个 Session 信 息,然后将该 Session 返回给客户端,客户端下次再请求页面时,会将 Session 数据保存在请求头中, 服务端根据请求头中的 Session 数据的值不一样,从而确定不同的客户端

GET /test.jsp HTTP/1.1
Accept: 
text/html,application/xhtml+xml,application/xml;q=0.9,ibai/avif,ibai/webp,imag
e/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Cache-Control: no-cache
Connection: keep-alive
Cookie: JSESSIONID=EF19024D936FD024E1246FE5C64F86ED #客户端会将 session 信息再次发往服务端,用于服务端进行识别
DNT: 1
Host: java.m99-baidu.com
Pragma: no-cache
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, 
like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0
sec-ch-ua: "Chromium";v="122", "Not(A:Brand";v="24", "Microsoft Edge";v="122"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
<!-- 在 server.xml 中 指定域名的 Host 标签内添加下列内容 -->
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
 channelSendOptions="8">
  <Manager className="org.apache.catalina.ha.session.DeltaManager"
   expireSessionsOnShutdown="false"
   notifyListenersOnReplication="true"/>
    
<!-- Channel定义了用于节点间通信的通道 -->
  <Channel className="org.apache.catalina.tribes.group.GroupChannel">
      
   <!-- 定义集群相关配置,集群中每个节点此处配置相同,才表示是同一个集群,此处配置用来做心跳检测 -->   
 <Membership className="org.apache.catalina.tribes.membership.McastService"
 		address="228.0.0.4" <!-- 多播地址,D类,无子网掩码,多台 Tomcat 配置同一个多播地址,表示同一集群 -->
 		port="45564" <!-- 多播端口 UPD 协议-->
 		frequency="500" <!-- 500毫秒发一个广播 -->
 		dropTime="3000"/>   <!-- 3000毫秒如果没有收到某个节点的心跳,则认为该节点不可用 -->
      
   <!-- 定义集群节点接收消息的配置 -->         
 <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
  	address="auto" <!-- 接收数据的地址,auto 会解析当前主机名,使用该IP,使用时请明确指定 -->
 	port="4000" <!-- 接收数据的端口,默认是 4000 -->
	autoBind="100" <!-- 端口冲突,自动使用其它端口,4000-4100 --> 
 	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>
<!-- 在项目的 web.xml 中 添加如下内容 -->
<distributable/>
[root@ubuntu tomcat]# cp -r webapps/ROOT/WEB-INF/ /data/webapps/ROOT/
[root@ubuntu tomcat]# cat /data/webapps/ROOT/WEB-INF/web.xml 
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
  version="4.0" metadata-complete="true">
 <display-name>Welcome to Tomcat</display-name>
 <description>
     Welcome to Tomcat
 </description>
<distributable /> #添加此行
</web-app>

[root@ubuntu tomcat]# chown -R tomcat.tomcat /data/webapps/ROOT/WEB-INF/
[root@ubuntu tomcat]# systemctl restart tomcat.service 
[root@ubuntu tomcat]# ss -tnlp | grep 4000
LISTEN 0      50     [::ffff:10.0.0.210]:4000             *:*   users: (("java",pid=3483,fd=49))

#在nginx 中去掉会话保持配置,恢复轮询,重启后测试,每次请求都会转发到不同的后端服务器,但能保持一个SESSION 信息
#停掉一台后端 Tomcat 服务,SESSION 数据还能保持

Memcached 实现 Session 共享

NoSQL 介绍

NoSQL(Not Only SQL)是一种非关系型数据库管理系统的范畴,它主要是为了解决传统关系型数据库 在处理大数据、高并发、半结构化/非结构化数据等方面的局限性而提出的。与传统的关系型数据库不 同,NoSQL数据库通常采用了更为灵活的数据模型,以适应不同类型的数据处理需求

以下是一些常见的 NoSQL 数据库的类型及其主要特点:

键值存储(Key-Value Stores)

  • Redis:基于内存的数据存储系统,支持丰富的数据类型(字符串、哈希、列表等),常用于缓 存、消息队列、会话存储等场景

  • Amazon DynamoDB:由亚马逊提供的托管键值存储服务,具有高可扩展性、高可用性和低延迟 的特点,适用于云原生应用开发

  • Apache Cassandra:分布式键值存储系统,具有高可扩展性、高可用性和容错性,特别适合处理 大规模数据

  • Memcached:是一种高性能的分布式内存对象缓存系统,主要用于减轻数据库负载和提高动态 Web 应用的性能

列存储(Column-Family Stores)

  • Apache HBase:基于 Hadoop 的列存储数据库,提供高可扩展性和高性能,通常用于实时读写大规模数据

  • Apache Cassandra:虽然 Cassandra 主要被归类为键值存储,但其数据模型也可以视为列存储

文档存储(Document Stores)

  • MongoDB:基于文档的 NoSQL 数据库,采用 JSON 格式存储数据,支持复杂的查询和索引,常用 于Web应用和大数据分析

  • Couchbase:结合了键值存储和文档存储的特点,具有高性能、高可用性和灵活的数据模型

图形数据库(Graph Databases)

  • Neo4j:基于图形理论的 NoSQL 数据库,专注于处理复杂的关系型数据,适用于社交网络、推荐 系统等应用场景

搜索引擎(Search Engines)

  • Elasticsearch:基于 Lucene 的分布式搜索引擎,支持实时搜索、分析和数据可视化,常用于构建 全文搜索、日志分析等系统

时序数据库(Time-Series Databases)

  • InfluxDB:专门用于处理时序数据的开源数据库,适用于监控、物联网、工业传感器等场景

Memcached 基础

Memcached(Memory Cache Daemon)是一种开源的、高性能的分布式内存对象缓存系统,主要用 于减轻数据库负载,提高动态 Web 应用的性能。它最初由 Brad Fitzpatrick 开发,现在由一个开源社区 维护

以下是 Memcached 的一些关键特性:

  • 分布式缓存:Memcached 可以在多台服务器上运行,形成一个分布式缓存系统。这意味着它可以 横向扩展以处理大量的请求和数据存储需求。

  • 内存存储:Memcached 将数据存储在内存中,这使得它能够提供快速的读写操作。内存存储也是 其高性能的主要原因之一。

  • 简单的键值存储:Memcached 是一个键值对存储系统,每个键对应一个值。这使得它非常适合存 储简单的数据结构,如对象、字符串等。

  • 缓存有效期:可以为存储在 Memcached 中的数据设置生存时间,一旦超过该时间,数据将被自动 删除。这有助于确保缓存中的数据不会过期,同时减少了需要手动清理缓存的工作。

  • 高性能:由于数据存储在内存中,Memcached 提供了非常快速的读写操作。这使得它成为处理高 并发、大规模应用的理想选择。

  • 分布式哈希表:Memcached 使用一致性哈希算法来确定数据存储在哪个节点上,这确保了在添加 或删除节点时,数据的迁移量被最小化,同时保持了负载均衡。

  • 支持多种编程语言:Memcached 客户端库支持多种编程语言,包括但不限于 Python、Java、 PHP、Ruby 等,这使得它能够轻松集成到各种类型的应用中。

尽管 Memcached 提供了许多优点,但它也有一些限制。例如,由于其基于内存存储,存储容量受到物 理内存的限制,而且它不具备持久性存储能力,数据一般在服务重启后会丢失。因此,在某些应用场景 下,可能需要与持久性存储结合使用,以确保数据的持久性和可靠性

Memcached 和 Redis 比较

Memcached 和 Redis 都是流行的内存缓存系统,但它们在一些方面有相似之处,也存在一些重要的区 别。以下是它们的比较:

相同点:

内存存储:Redis和Memcached都将数据存储在内存中,这使得它们能够提供快速的读写访问,从 而显著提高了性能

键值存储:两者都采用简单的键值存储模型,每个键对应一个值。这种简单性使得它们易于使用和理解

数据结构支持:Redis和Memcached都支持丰富的数据结构,例如字符串、哈希、列表等,这使得它们能够更灵活地存储和处理不同类型的数据

不同点:

  • 数据持久化:Redis支持数据持久化,可以将数据写入磁盘以防止数据丢失。它提供了多种持久化 选项,包括快照和日志。而Memcached不支持数据持久化,一旦服务器重启或发生故障,缓存中 的数据将会丢失

  • 功能:Redis提供了许多附加功能,例如发布/订阅、事务、Lua脚本等,使得它不仅仅是一个缓存系统,还可以用作消息队列、计数器等。而Memcached更专注于作为分布式缓存的角色,功能相 对较为简单

  • 数据类型处理:Redis提供更丰富的数据类型支持,包括字符串、哈希、列表、集合、有序集合 等,这使得它更适合处理复杂的数据结构。而Memcached主要支持简单的键值对存储,功能相对较为单一

  • 性能:由于Redis提供了更多的功能和数据类型支持,因此在某些方面可能略逊于Memcached。然 而,具体性能取决于使用场景、数据访问模式以及部署配置等因素

Memcached 工作机制

应用程序运行需要使用内存存储数据,但对于一个缓存系统来说,申请内存、释放内存将十分频繁,非常容易导致大量内存碎片,最后导致无连续可用内存可用

  • Memcached 采用了Slab Allocator机制来分配、管理内存

  • Page:分配给 Slab 的内存空间,默认为 1MB,分配后就得到一个 Slab,Slab 分配之后内存按照 固定字节大小等分成 chunk

  • Chunk:用于缓存记录 k/v 值的内存空间,Memcached 会根据数据大小选择存到哪一个 chunk 中,假设 chunk 有 128bytes、64bytes等多种,数据只有 100bytes 存储在 128bytes 中,存在少 许浪费。chunk 增长因子 default: 1.25,Chunk 最大就是一个 Page 大小,所以 Memcached 无 法存储单个 K/V 超过 1M的数据

  • Slab Class:Slab 按照 Chunk 的大小分组,就组成不同的 Slab Class,第一个 Chunk 大小为 96B 的 Slab 为 Class1,Chunk 120B 为Class 2,如果有 100bytes 要存,那么 Memcached 会选择下 图中Slab Class 2 存储,因为它是120 bytes 的 Chunk,Slab 之间的差异可以使用 Growth Factor 控制,默认 1.25

懒过期 Lazy Expiration

memcached 不会监视数据是否过期,而是在取数据时才看是否过期,如果过期,把数据有效期限标识 为 0,并不清除该数据。以后可以覆盖该位置存储其它数据

LRU

当内存不足时,memcached会使用最近最少使用算法 LRU(Least Recently Used)机制来查找可用空 间,分配给新记录使用

集群

Memcached 集群,称为基于客户端的分布式集群,即由客户端实现集群功能,即 Memcached 本身不 支持集群,Memcached集群内部并不互相通信,一切都需要客户端连接到 Memcached 服务器后自行 组织这些节点,并决定数据存储的节点

安装和启动

[root@ubuntu24 ~]# apt update;apt install memcached -y

[root@ubuntu24 ~]# memcached --version
memcached 1.6.24

[root@ubuntu24 ~]# systemctl status memcached.service
● memcached.service - memcached daemon
     Loaded: loaded (/usr/lib/systemd/system/memcached.service; enabled; preset: enabled)
     Active: active (running) since Mon 2024-09-23 20:15:34 CST; 1min 3s ago
       Docs: man:memcached(1)
   Main PID: 2861 (memcached)
      Tasks: 10 (limit: 4556)
     Memory: 1.8M (peak: 2.3M)
        CPU: 44ms
     CGroup: /system.slice/memcached.service
             └─2861 /usr/bin/memcached -m 64 -p 11211 -u memcache -l 127.0.0.1 -l ::1 -P /var/run/memcached/memcached.pid

Sep 23 20:15:34 ubuntu24 systemd[1]: Started memcached.service - memcached daemon.

#启动脚本和配置文件
[root@ubuntu24 ~]# systemctl cat memcached.service | grep Exec
ExecStart=/usr/share/memcached/scripts/systemd-memcached-wrapper 
/etc/memcached.conf


#常用启动选项
-p|--port=<num>         # 指定TCP端口,默认 11211
-U|--udp-port=<num>     # 指定UDP端口
-s|--unix-socket=<file> # 指定SOCKET文件
-A|--enable-shutdown 	# 支持客户端连接使用 shutdown 命令关闭服务
-l|--listen=<addr> 		# 指定监听地址
-d|--daemon 			# 以后台守护进程运行
-r|--enable-coredumps 	# 将核心文件大小限制提高到允许的最大值
-u|--user=<user> 		# 指定运行用户
-m|--memory-limit=<num> # 指定工作内存大小
-M|--disable-evictions 	# 内存不足时,禁止自动删除数据
-c|--conn-limit=<num> 	# 指定最大并发连接数,默认 1024
-k|--lock-memory 		# 锁定所有分页内存
-f|--slab-growth-factor=<num> # 指定增长因子,该值决定了每个 chunk 的大小增长步长,默认
1.25
-v|--verbose 			# 显示详细信息
-vv 					# 显示详细信息
-vvv 					# 显示详细信息
-h|--help 				# 显示帮助
-i|--license 			# 显示许可信息             
-V|--version            # 显示版本
-P|--pidfile=<file>     # 指定PID文件路径
-t|--threads=<num> 		# 指定处理接入请求的线程数,只有自行编译开启了相关功能时此项才生效,默认4



# 主要配置项
[root@ubuntu24 ~]# cat /etc/memcached.conf
-d 				# 以守护进程执行
logfile /var/log/memcached.log # 指定日志文件
# -v 			# 显示详细信息
# -vv 			# 显示更详细的信息
-m 64 			# 指定运行内存大小,默认 64M
-p 11211 		# 监听端口
-u memcache 	# 指定运行用户
-l 127.0.0.1 	# 监听IP
# -c 1024 		# 最大并发连接数,默认1024
# -k 			# 锁定所有分页内存
# -M 			# 内存耗尽时返回错误,而不是删除数据
# -r 			# 最大限度地提高核心文件限制
-P /var/run/memcached/memcached.pid # 指定pid文件路径


#修改配置文件,监听所有IP,并重启服务
[root@ubuntu24 ~]# ss -tnlp | grep mem
LISTEN 0      1024         0.0.0.0:11211      0.0.0.0:*   users:
(("memcached",pid=2335,fd=22))
LISTEN 0      1024           [::]:11211         [::]:*   users:
(("memcached",pid=2335,fd=23))

#memcached 官方并没有提供客户端工具,我们可以在命令行下使用 telnet 或 nc 进行连接
#查看状态
[root@ubuntu24 ~]# nc 10.0.0.206 11211
stats

#非交互式执行
[root@ubuntu24 ~]# echo -e 'stats\nquit' | nc 127.0.0.1 11211

[root@ubuntu24 ~]# systemctl stop memcached.service

# 指定增长因子,每个 chunk 2倍大小增长
[root@ubuntu24 ~]# memcached -p 11211 -f 2 -u memcache -vv
slab class   1: chunk size        96 perslab   10922
slab class   2: chunk size       192 perslab    5461
slab class   3: chunk size       384 perslab    2730
slab class   4: chunk size       768 perslab    1365
slab class   5: chunk size      1536 perslab     682
slab class   6: chunk size      3072 perslab     341
slab class   7: chunk size      6144 perslab     170
slab class   8: chunk size     12288 perslab      85
slab class   9: chunk size     24576 perslab      42
slab class  10: chunk size     49152 perslab      21
slab class  11: chunk size     98304 perslab      10
slab class  12: chunk size    196608 perslab       5
slab class  13: chunk size    524288 perslab       2
<26 server listening (auto-negotiate)

Memcached 常用操作命令

在 memcache 中,根据功能不同可以将命令划分为存储、检索、删除、增加/减少、统计、清空等六个 部分

#命令格式
<command name> <key> <flags> <exptime> <bytes> [noreply]\r\n<data block>\r\n
# <command name> 具体命令
# <key> 任意字符串,用于存取数据的标识
# <flags> 任意一个无符号整型数字,该字段对服务器不透明,客户端可将其作为位域来存储数据特定信息
# <exptime> 缓存有效期,以秒为单位,0表示不过期, -1 表示立即失效,最长30天,超过30天被当作时间戳来处理
# <bytes> 此次写入的数据占用的空间大小
# <cas unique> (compare and set),一个64位现有item的值,客户端可以使用gets命令获取cas的值
# [noreply] 可选,表示服务器不用返回任何数据


#常用命令
# stats 显示当前 memcached 服务状态
# set 设置一个 key/value
# add key 不存在时,新增一个 key/value
# replace 替换一个己存在的 key 中的内容
# append 追加数据到 key 的尾部
# prepend 插入数据到 key 的头部
# get 获取 key 的 flag 及 value
# delete 删除一个 key
# incr 对 key 做自加1,key 中保存的数据必须是整型
# decr 对 key 做自减1,key 中保存的数据必须是整型
# flush_all 清空所有数据


[root@ubuntu ~]# telnet 10.0.0.206 11211
Trying 10.0.0.206...
Connected to 10.0.0.206.
Escape character is '^]'.
set test 1 30 3 # 设置一个 key/value , key=test,flag=1,exp=30S,
bytes=3 
abc 			# 此处只能存3字节,不能多也不能少
STORED
get test 		# 获取数据
VALUE test 1 3
abc
END
get test 		# 30S 后数据消失
END

基于MSM 实现 Session 共享

MSM 介绍和安装

MSM(memcached session manager)提供将Tomcat的session保持到memcached或Redis的程序, 可以实现高可用

基于 Sticky 模式实现 Session 共享集群

sticky 模式即前端 tomcat 和后端 memcached 有关联(粘性)关系

节点Session 写Session 读
Tomcat - 1Tomcat - 1,Memcached - 2(Memcached - 2 无法写入会 写 Memcached - 1)Tomcat - 1, Memcached - 1
Tomcat - 2Tomcat - 2,Memcached - 1(Memcached - 1 无法写入会 写 Memcached - 2)Tomcat - 2, Memcached - 2

如上图所示,Tomcat -1 上生成的 Session 信息,除了在 Tomcat -1 这个节点保存一份之外,还往 Memcached - 2 中写一份,当一个前端请求在 Tomcat - 1 中生成了 Session 信息后,下次被调度到 Tomcat - 2 中,之前的 Session 信息可以在 Memcached - 2 中获取,如果 Memcached - 2 中无法写入 数据,Tomcat -1 会将 Session 数据写入 Memcached - 1 节点,此时,如果一个请求被调度到 Tomcat - 2,则之前在 Tomcat - 1 中生成的 Session 信息无法从 Memcached - 2 中获取,那么,该请求的会话 信息会丢失

服务IP
Nginx10.0.0.206
Tomcat - 1,Memcached - 210.0.0.208
Tomcat - 2,Memcached - 110.0.0.210
#先去掉tomcat 的 session 复制

# nginx 配置如下
[root@ubuntu24 ~]# cat /etc/nginx/sites-enabled/java.m99-baidu.com.conf 
upstream tomcat {
 server 10.0.0.208:8080;
 server 10.0.0.210:8080;
}

server{
 listen 80;
 server_name java.m99-baidu.com;
 
 location ~* \.jsp$ {
               proxy_pass http://tomcat;
               proxy_set_header host $http_host;
               proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
       }
}

#配置域名解析,在浏览器中测试 http://java.m99-baidu.com/test.jsp
#tomcat 安装 memcached,两台 Tomcat 节点均按此配置
[root@ubuntu ~]# apt install memcached
# 修改配置文件,并重启服务,确保可以远程连接 memcached
[root@ubuntu24 ~]# cat /etc/memcached.conf | grep '127.0.0.1'
#-l 127.0.0.1

[root@ubuntu24 ~]# systemctl restart tomcat.service

[root@ubuntu24 ~]# ss -tnlp | grep mem
LISTEN 0      1024         0.0.0.0:11211      0.0.0.0:*    users:(("memcached",pid=3127,fd=26))
#nginx 节点测试 Memcached 远程访问
#安装 Python 客户端工具
[root@ubuntu ~]# pip install python-memcached
[root@ubuntu ~]# cat test.py 
#!/usr/bin/python3
import memcache
client = memcache.Client(['10.0.0.208:11211','10.0.0.210:11211'],debug=True)
for i in client.get_stats('items'):
   print(i)
print('-' * 35)
for i in client.get_stats('cachedump 5 0'):
   print(i)
   
#没有数据
[root@ubuntu ~]# python3 test.py 
('10.0.0.208:11211 (1)', {})
('10.0.0.210:11211 (1)', {})
-----------------------------------
('10.0.0.208:11211 (1)', {})
('10.0.0.210:11211 (1)', {})
#tomcat 节点配置 session 共享

#10.0.0.208

#下载相关jar 包
[root@ubuntu ~]# cd /usr/local/tomcat/lib/

# wget https://repo1.maven.org/maven2/org/ow2/asm/asm/5.2/asm5.2.jar
[root@ubuntu ~]# wget 
https://repo1.maven.org/maven2/com/esotericsoftware/kryo/3.0.3/kryo-3.0.3.jar
[root@ubuntu ~]# wget https://repo1.maven.org/maven2/de/javakaffee/kryoserializers/0.45/kryo-serializers-0.45.jar
[root@ubuntu ~]# wget https://repo1.maven.org/maven2/de/javakaffee/msm/memcachedsession-manager/2.3.2/memcached-session-manager-2.3.2.jar
[root@ubuntu ~]# wget https://repo1.maven.org/maven2/de/javakaffee/msm/memcachedsession-manager-tc9/2.3.2/memcached-session-manager-tc9-2.3.2.jar
[root@ubuntu ~]# wget 
https://repo1.maven.org/maven2/com/esotericsoftware/minlog/1.3.1/minlog1.3.1.jar
[root@ubuntu ~]# wget https://repo1.maven.org/maven2/de/javakaffee/msm/msm-kryoserializer/2.3.2/msm-kryo-serializer-2.3.2.jar
[root@ubuntu ~]# wget 
https://repo1.maven.org/maven2/org/objenesis/objenesis/2.6/objenesis-2.6.jar
[root@ubuntu ~]# wget 
https://repo1.maven.org/maven2/com/esotericsoftware/reflectasm/1.11.9/reflectasm1.11.9.jar
[root@ubuntu ~]# wget 
https://repo1.maven.org/maven2/net/spy/spymemcached/2.12.3/spymemcached2.12.3.jar

#修改配置
[root@ubuntu ~]# cat /usr/local/tomcat/conf/context.xml
......
<Context>
.....
<Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
    memcachedNodes="m1:10.0.0.208:11211,m2:10.0.0.210:11211" # memcached 集群节
点IP
    failoverNodes="m1" # 当前是
10.0.0.208,先写 10.0.0.210,失败写m1
    requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$"
   
transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFact
ory"
   />
    
.....


#10.0.0.210
[root@ubuntu ~]# cat /usr/local/tomcat/conf/context.xml
......
<Context>
.....
<Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
    memcachedNodes="m1:10.0.0.208:11211,m2:10.0.0.210:11211" # memcached 集群节点IP
    failoverNodes="m2" # 当前是10.0.0.210,先写 10.0.0.208,失败写m2
    requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$"
    
    transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFact
ory"
   />
    
.....
#重启tomcat服务,查看日志能看到相关提示,两台tomcat 都要重启
[root@ubuntu tomcat]# systemctl start tomcat.service

[root@ubuntu tomcat]# tail logs/catalina.out
- sticky: true
- operation timeout: 1000
- node ids: [m2]
- failover node ids: [m1]
- storage key prefix: null
- locking mode: null (expiration: 5s)
--------

# 在浏览器中测试 http://java.m99-baidu.com/test.jsp
# 后端IP发生了变化,但SESSION 可以保持

#再次使用py查看,数据在 item 4 里面
[root@ubuntu ~]# python3 test.py 
('10.0.0.208:11211 (1)', {})
('10.0.0.210:11211 (1)', {'items:4:number': '1', 'items:4:number_hot': '0', 
'items:4:number_warm': '0', 'items:4:number_cold': '1', 'items:4:age_hot': '0', 
'items:4:age_warm': '0', 'items:4:age': '94', 'items:4:mem_requested': '183', 
'items:4:evicted': '0', 'items:4:evicted_nonzero': '0', 'items:4:evicted_time': 
'0', 'items:4:outofmemory': '0', 'items:4:tailrepairs': '0', 
'items:4:reclaimed': '0', 'items:4:expired_unfetched': '0', 
'items:4:evicted_unfetched': '0', 'items:4:evicted_active': '0', 
'items:4:crawler_reclaimed': '0', 'items:4:crawler_items_checked': '0', 
'items:4:lrutail_reflocked': '0', 'items:4:moves_to_cold': '1', 
'items:4:moves_to_warm': '0', 'items:4:moves_within_lru': '0', 
'items:4:direct_reclaims': '0', 'items:4:hits_to_hot': '0', 
'items:4:hits_to_warm': '0', 'items:4:hits_to_cold': '1', 
'items:4:hits_to_temp': '0'})
-----------------------------------
('10.0.0.208:11211 (1)', {})
('10.0.0.210:11211 (1)', {})


#修改py脚本,再次执行
[root@ubuntu ~]# cat test.py 
#!/usr/bin/python3
import memcache
client = memcache.Client(['10.0.0.208:11211','10.0.0.210:11211'],debug=True)
for i in client.get_stats('items'):
   print(i)
print('-' * 35)
for i in client.get_stats('cachedump 4 0'): #这里写 4
   print(i)
   
#能看到具体的 session 信息
[root@ubuntu ~]# python3 test.py 
('10.0.0.208:11211 (1)', {})
('10.0.0.210:11211 (1)', {'items:4:number': '1', 'items:4:number_hot': '0', 
'items:4:number_warm': '0', 'items:4:number_cold': '1', 'items:4:age_hot': '0', 
'items:4:age_warm': '0', 'items:4:age': '213', 'items:4:mem_requested': '183', 
'items:4:evicted': '0', 'items:4:evicted_nonzero': '0', 'items:4:evicted_time': 
'0', 'items:4:outofmemory': '0', 'items:4:tailrepairs': '0', 
'items:4:reclaimed': '0', 'items:4:expired_unfetched': '0', 
'items:4:evicted_unfetched': '0', 'items:4:evicted_active': '0', 
'items:4:crawler_reclaimed': '0', 'items:4:crawler_items_checked': '0', 
'items:4:lrutail_reflocked': '0', 'items:4:moves_to_cold': '1', 
'items:4:moves_to_warm': '0', 'items:4:moves_within_lru': '0', 
'items:4:direct_reclaims': '0', 'items:4:hits_to_hot': '0', 
'items:4:hits_to_warm': '0', 'items:4:hits_to_cold': '1', 
'items:4:hits_to_temp': '0'})
-----------------------------------
('10.0.0.208:11211 (1)', {})
('10.0.0.210:11211 (1)', {'E4D1482CDA2D3E38C39961B7ED5EE971-m2': '[89 b; 1711989857 s]'})

#再用不同的客户端请求页面,如果请求被调度到 tomcat-1 上,则 session 会被保存到 memcached-2 上

[root@ubuntu ~]# python3 test.py 
('10.0.0.208:11211 (1)', {'items:4:number': '1', 'items:4:number_hot': '0', 
'items:4:number_warm': '0', 'items:4:number_cold': '1', 'items:4:age_hot': '0', 
'items:4:age_warm': '0', 'items:4:age': '11', 'items:4:mem_requested': '183', 
'items:4:evicted': '0', 'items:4:evicted_nonzero': '0', 'items:4:evicted_time': 
'0', 'items:4:outofmemory': '0', 'items:4:tailrepairs': '0', 
'items:4:reclaimed': '0', 'items:4:expired_unfetched': '0', 
'items:4:evicted_unfetched': '0', 'items:4:evicted_active': '0', 
'items:4:crawler_reclaimed': '0', 'items:4:crawler_items_checked': '0', 
'items:4:lrutail_reflocked': '0', 'items:4:moves_to_cold': '1', 
'items:4:moves_to_warm': '0', 'items:4:moves_within_lru': '0', 
'items:4:direct_reclaims': '0', 'items:4:hits_to_hot': '0', 
'items:4:hits_to_warm': '0', 'items:4:hits_to_cold': '0', 
'items:4:hits_to_temp': '0'})

('10.0.0.210:11211 (1)', {'items:4:number': '1', 'items:4:number_hot': '0', 
'items:4:number_warm': '0', 'items:4:number_cold': '1', 'items:4:age_hot': '0', 
'items:4:age_warm': '0', 'items:4:age': '460', 'items:4:mem_requested': '183', 
'items:4:evicted': '0', 'items:4:evicted_nonzero': '0', 'items:4:evicted_time': 
'0', 'items:4:outofmemory': '0', 'items:4:tailrepairs': '0', 
'items:4:reclaimed': '0', 'items:4:expired_unfetched': '0', 
'items:4:evicted_unfetched': '0', 'items:4:evicted_active': '0', 
'items:4:crawler_reclaimed': '0', 'items:4:crawler_items_checked': '1', 
'items:4:lrutail_reflocked': '0', 'items:4:moves_to_cold': '1', 
'items:4:moves_to_warm': '0', 'items:4:moves_within_lru': '0', 
'items:4:direct_reclaims': '0', 'items:4:hits_to_hot': '0', 
'items:4:hits_to_warm': '0', 'items:4:hits_to_cold': '1', 
'items:4:hits_to_temp': '0'})
-----------------------------------
('10.0.0.208:11211 (1)', {'1194B56CD9FA680D9545D9242924DEE9-m1': '[89 b; 
1711990306 s]'})
('10.0.0.210:11211 (1)', {'E4D1482CDA2D3E38C39961B7ED5EE971-m2': '[89 b; 
1711989857 s]'})
基于非 Sticky 模式实现 Session 共享集群

非 sticky 模式中前端 tomcat 不保存 session 信息,每台 tomcat 上产生的 session 都是双写到后端多台 memcached 服务器上

节点Session 写Session 读
Tomcat - 1Memcached - 1,Memcached - 2Tomcat - 1, Memcached - 1
Tomcat - 2Memcached - 2,Memcached - 1Tomcat - 2, Memcached - 2
服务IP
Nginx10.0.0.206
Tomcat - 1,Memcached - 210.0.0.208
Tomcat - 2,Memcached - 110.0.0.210
#先重启 tomcat,memcached,清空之前的 session 数据
[root@ubuntu ~]# systemctl restart tomcat.service memcached.service


# 查看 memcached ,己经没有数据了
[root@ubuntu ~]# python3 test.py 
('10.0.0.208:11211 (1)', {})
('10.0.0.210:11211 (1)', {})
-----------------------------------
('10.0.0.208:11211 (1)', {})
('10.0.0.210:11211 (1)', {})

#修改后端 tomcat 配置
[root@ubuntu ~]# cat /usr/local/tomcat/conf/context.xml
......
<Context>
.....
 <Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
        memcachedNodes="m1:10.0.0.208:11211,m2:10.0.0.210:11211"
        sticky="false"
        sessionBackupAsync="false"
        lockingMode="uriPattern:/path1|/path2"
        requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$"
       
transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFact
ory"
   />
...


#重启服务
[root@ubuntu ~]# systemctl restart tomcat.service
#查看日志
[root@ubuntu ~]# tail /usr/local/tomcat/logs/catalina.out
- sticky: false
- operation timeout: 1000
- node ids: [m1, m2]
- failover node ids: []
- storage key prefix: null
- locking mode: uriPattern:/path1|/path2 (expiration: 5s)
--------

#在浏览器中访问 http://java.m99-baidu.com/test.jsp
#查看session 信息,同一个 session 写了两台 memcached
[root@ubuntu ~]# python3 test.py 
-----------------------------------
('10.0.0.208:11211 (1)', {'5D68B708C65850C08F7F3FCFEE0625C4-m1': '[89 b; 
1712564454 s]'})
('10.0.0.210:11211 (1)', {'bak:5D68B708C65850C08F7F3FCFEE0625C4-m1': '[89 b; 
1712564453 s]'})

Redis 实现 Session 共享

基于 MSM 实现 Session 共享
节点Session 写Session 读
Tomcat - 1RedisRedis
Tomcat - 2RedisRedis
服务IP
Nginx10.0.0.206
Tomcat - 110.0.0.208
Tomcat - 210.0.0.210
Redis10.0.0.159
#安装 redis
[root@ubuntu ~]# apt install redis-server -y

#修改配置,保证远程可以连接
[root@ubuntu ~]# cat /etc/redis/redis.conf
#bind 127.0.0.1 ::1 
bind 0.0.0.0

#重启服务
[root@ubuntu ~]# systemctl restart redis-server.service

[root@ubuntu ~]# ss -tnlp | grep redis
LISTEN 0      511          0.0.0.0:6379       0.0.0.0:*   users:(("redisserver",pid=25620,fd=6))

#redis 中没有数据
[root@ubuntu ~]# redis-cli
127.0.0.1:6379> keys *
(empty array)

#下载jar 包,修改 tomcat 配置
[root@ubuntu ~]# cd /usr/local/tomcat/lib/
[root@ubuntu lib]# wget 
https://repo1.maven.org/maven2/redis/clients/jedis/3.0.0/jedis-3.0.0.jar
[root@ubuntu ~]# cat /usr/local/tomcat/conf/context.xml
......
<Context>
.....
 <Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
    memcachedNodes="redis://10.0.0.159"
    sticky="false"
    sessionBackupAsync="false"
    lockingMode="uriPattern:/path1|/path2"
    requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$"
   
transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFact
ory"
 />
 
#重启 tomcat,并在浏览器中访问 http://java.m99-baidu.com/test.jsp
[root@ubuntu ~]# systemctl restart tomcat.service
#再次查看 redis
[root@ubuntu ~]# redis-cli

127.0.0.1:6379> keys *
1) "validity:036D22B697CF3BBACBEA55D3E943A041"
2) "036D22B697CF3BBACBEA55D3E943A041"
基于 Redisson 实现 Session 共享
节点Session 写Session 读
Tomcat - 1RedisRedis
Tomcat - 2RedisRedis

服务IP
Nginx10.0.0.206
Tomcat - 110.0.0.208
Tomcat - 210.0.0.210
Redis10.0.0.159
[root@ubuntu ~]# systemctl restart tomcat.service#redis节点配置同上,先清空所有缓存数据
127.0.0.1:6379> FLUSHALL
OK
127.0.0.1:6379> keys *
(empty array)

#下载jar 包,配置 tomcat
[root@ubuntu ~]# cd /usr/local/tomcat/lib/

[root@ubuntu lib]# wget https://repo1.maven.org/maven2/org/redisson/redissonall/3.27.2/redisson-all-3.27.2.jar
[root@ubuntu lib]# wget https://repo1.maven.org/maven2/org/redisson/redissontomcat-9/3.27.2/redisson-tomcat-9-3.27.2.jar


[root@ubuntu ~]# cat /usr/local/tomcat/conf/context.xml
......
<Context>
.....
<Manager className="org.redisson.tomcat.RedissonSessionManager"
  configPath="/usr/local/tomcat/conf/redisson.conf"
  readMode="REDIS" updateMode="DEFAULT" broadcastSessionEvents="false"
  keyPrefix=""/>
  
  
[root@ubuntu ~]# cat /usr/local/tomcat/conf/redisson.conf 
---
singleServerConfig:
 idleConnectionTimeout: 10000
 connectTimeout: 10000
 timeout: 3000
 retryAttempts: 3
 retryInterval: 1500
 password: null
 subscriptionsPerConnection: 5
 clientName: null
 address: "redis://10.0.0.159:6379"
 subscriptionConnectionMinimumIdleSize: 1
 subscriptionConnectionPoolSize: 50
 connectionMinimumIdleSize: 24
 connectionPoolSize: 64
 database: 0
 dnsMonitoringInterval: 5000
threads: 16
nettyThreads: 32
codec: !<org.redisson.codec.Kryo5Codec> {}
transportMode: "NIO"

[root@ubuntu ~]# chown tomcat.tomcat /usr/local/tomcat/conf/redisson.conf

#重启服务,在浏览器中访问 http://java.m99-baidu.com/test.jsp
[root@ubuntu ~]# systemctl restart tomcat.service

#查看缓存数据
127.0.0.1:6379> keys *
1) "redisson:tomcat_session:2AB8112C1BC7F3D1A9FB004DC944EF9E"

Tomcat 性能优化

在目前流行的互联网架构中,Tomcat 在目前的网络编程中是举足轻重的,由于Tomcat的运行依赖于 JVM,从虚拟机的角度把Tomcat的调整分为外部环境调优 JVM 和 Tomcat 自身调优两部分

JVM 组成

JVM 组成

  • 类加载子系统:使用Java语言编写.java Source Code文件,通过javac编译成.class Byte Code文 件。class loader类加载器将所需所有类加载到内存,必要时将类实例化成实例

  • 运行时数据区:最消耗内存的空间,需要优化

  • 执行引擎:包括JIT (JustInTimeCompiler)即时编译器, GC垃圾回收器

  • 本地方法接口:将本地方法栈通过JNI(Java Native Interface)调用Native Method Libraries, 比 如:C,C++库等,扩展Java功能,融合不同的编程语言为Java所用

JVM运行时数据区域由下面部分构成

  • Method Area (线程共享):方法区是所有线程共享的内存空间,存放已加载的类信息(构造方法,接 口定义),常量(final),静态变量(static), 运行时常量池等。但实例变量存放在堆内存中. 从JDK8开 始此空间由永久代改名为元空间 metaspace

  • heap (线程共享):堆在虚拟机启动时创建,存放创建的所有对象信息。如果对象无法申请到可用内 存将抛出OOM异常,堆是靠GC垃圾回收器管理的,通过-Xmx -Xms 指定最大堆和最小堆空间大小

  • Java stack (线程私有):Java栈是每个线程会分配一个栈,存放java中8大基本数据类型,对象引 用,实例的本地变量,方法参数和返回值等,基于FILO(First In Last Out),每个方法为一个栈帧

  • Program Counter Register (线程私有):PC寄存器就是一个指针,指向方法区中的方法字节码,每 一个线程用于记录当前线程正在执行的字节码指令地址。由执行引擎读取下一条指令,因为线程需 要切换,当一个线程被切换回来需要执行的时候,知道执行到哪里了

  • Native Method stack (线程私有):本地方法栈为本地方法执行构建的内存空间,存放本地方法执 行时的局部变量、操作数等

所谓本地方法,使用native 关健字修饰的方法,比如:Thread.sleep方法, 简单的说是非Java实现的方 法,例如操作系统的C编写的库提供的本地方法,Java调用这些本地方法接口执行。但是要注意,本地方法应该避免直接编程使用,因为Java可能跨平台使用,如果用了Windows API,换到了Linux平台部署就有问题了

虚拟机

目前Oracle官方使用的是HotSpot, 它最早由一家名为"Longview Technologies"公司设计,使用了很 多优秀的设计理念和出色的性能,1997年该公司被SUN公司收购。后来随着JDK一起发布了 HotSpotVM。目前HotSpot是最主要的 JVM。

安卓程序需要运行在JVM上,而安卓平台使用了Google自研的Java虚拟机——Dalvid,适合于内存、处理器能力有限系统。

GC 垃圾收集器

GC (Garbage Collection) 垃圾回收机制

当需要分配的内存空间不再使用的时候,JVM将调用垃圾回收机制来回收内存空间

在堆内存中如果创建的对象不再使用,仍占用着内存,此时即为垃圾.需要进行垃圾回收,从而释放内存空 间给其它对象使用

其实不同的开发语言都有垃圾回收问题,C,C++需要程序员人为回收,造成开发难度大,容易出错等问题,但执行效率高,而JAVA和Python中不需要程序员进行人为的回收垃圾,而由JVM或相关程序自动回收 垃圾,减轻程序员的开发难度,但可能会造成执行效率低下

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

所以需要有合适的垃圾回收机制,确保正常释放不再使用的内存空间,还需要保证内存空间尽可能的保持一 定的连续

对于垃圾回收,需要解决三个问题

  1. 哪些是垃圾要回收

  2. 怎么回收垃圾

  3. 什么时候回收垃圾

Garbage 垃圾确定方法

引用计数:每一个堆内对象上都与一个私有引用计数器,记录着被引用的次数,引用计数清零,该 对象所占用堆内存就可以被回收。循环引用的对象都无法将引用计数归零,就无法清除。Python中 即使用此种方式

根搜索 (可达) 算法:Root Searching

垃圾回收基本算法

标记-清除 Mark-Sweep

分垃圾标记阶段和内存释放两个阶段

  • 标记阶段,找到所有可访问对象打个标记。

  • 清理阶段,遍历整个堆,对未标记对象(即不再使用的对象)逐一进行清理

特点:算法简单,不会浪费内存空间,效率较高,但会形成内存碎片

标记-压缩 (压实)Mark-Compact

分垃圾标记阶段和内存整理两个阶段

  • 标记阶段,找到所有可访问对象打个标记

  • 内存清理阶段时,整理时将对象向内存一端移动,整理后存活对象连续的集中在内存一端

特点:整理后的内存空间是连续分配的,有大段的连续内存可分配,没有内存碎片,缺点是内存整理过 程有消耗,效率相对低下

复制 Copying

先将可用内存分为大小相同两块区域A和B,每次只用其中一块,比如A。当A用完后,则将A中存活的对 象复制到B。复制到B的时候连续的使用内存,最后将A一次性清除干净

特点:好处是没有碎片,复制过程中保证对象使用连续空间,且一次性清除所有垃圾,所以即使对象很 多,收回效率也很高,缺点是比较浪费内存,只能使用原来一半内存,因为内存对半划分了,复制过程 毕竟也是有代价

没有最好的算法,在不同场景选择最合适的算法

  • 效率:复制算法>标记清除算法> 标记压缩算法

  • 内存整齐度:复制算法=标记压缩算法> 标记清除算法

  • 内存利用率:标记压缩算法=标记清除算法>复制算法

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

分代堆内存GC策略

上述垃圾回收算法都有优缺点,能不能对不同数据进行区分管理,不同分区对数据实施不同回收策略, 分而治之

堆内存分代

将heap内存空间分为三个不同类别: 年轻代、老年代、持久代

年轻代 Young:Young Generation

伊甸园区 eden:只有一个,刚刚创建的对象

幸存(存活)区 Servivor Space:有2个幸存区,一个是from区,一个是to区。大小相等、地位相同、可互 换

老年代Tenured:Old Generation,

长时间存活的对象

永久代:

JDK1.7之前使用,即Method Area方法区,保存 JVM 自身的类和方法,存储 JAVA 运行时的环境信息, JDK1.8 后改名为 MetaSpace,此空间不存在垃圾回收,关闭JVM会释放此区域内存,此空间物理上不属 于heap内存,但逻辑上存在于heap内存

永久代必须指定大小限制,字符串常量JDK1.7存放在永久代,1.8后存放在heap中

MetaSpace 可以设置,也可不设置,无上限

默认空间大小比例:

默认JVM试图分配最大内存的总内存的1/4,初始化默认总内存为总内存的1/64,年青代中heap的1/3,老年代占2/3

规律:一般情况99%的对象都是临时对象

年轻代回收 Minor GC

  1. 起始时,所有新建对象(特大对象直接进入老年代)都出生在eden,当eden满了,启动GC。这个称 为Young GC 或者 Minor GC

  2. 先标记eden存活对象,然后将存活对象复制到s0(假设本次是s0,也可以是s1,它们可以调 换),eden剩余所有空间都清空。GC完成

  3. 继续新建对象,当eden再次满了,启动GC

  4. 先同时标记eden和s0中存活对象,然后将存活对象复制到s1。将eden和s0清空,此次GC完成

  5. 继续新建对象,当eden满了,启动GC

  6. 先标记eden和s1中存活对象,然后将存活对象复制到s0。将eden和s1清空,此次GC完成

  7. 以后就重复上面的步骤

通常场景下,大多数对象都不会存活很久,而且创建活动非常多,新生代就需要频繁垃圾回收。但是,如 果一个对象一直存活,它最后就在from、to来回复制,如果from区中对象复制次数达到阈值(默认15 次,CMS为6次,可通过java的选项 -XX:MaxTenuringThreshold=N 定),就直接复制到老年代

老年代回收 Major GC

  • 进入老年代的数据较少,所以老年代区被占满的速度较慢,所以垃圾回收也不频繁

  • 如果老年代也满了,会触发老年代GC,称为Old GC或者 Major GC

  • 由于老年代对象一般来说存活次数较长,所以较常采用标记-压缩算法

  • 当老年代满时,会触发 Full GC,即对所有"代"的内存进行垃圾回收

  • Minor GC比较频繁,Major GC较少。但一般Major GC时,由于老年代对象也可以引用新生代对 象,所以先进行一次Minor GC,然后在Major GC会提高效率。可以认为回收老年代的时候完成了 一次FullGC

  • 所以可以认为 MajorGC = FullGC

GC 触发条件

Minor GC 触发条件:当eden区满了触发

Full GC 触发条件:老年代满了;System.gc()手动调用(不推荐)

年轻代:存活时长低;适合复制算法

老年代:区域大,存活时长高;适合标记压缩算法

Minor GC 可能会引起短暂的STW暂停。当进行 Minor GC 时,为了确保安全性,JVM 需要在某些特定 的点上暂停所有应用程序的线程,以便更新一些关键的数据结构。这些暂停通常是非常短暂的,通常在 毫秒级别,并且很少对应用程序的性能产生显著影响

Major GC的暂停时间通常会比Minor GC的暂停时间更长,因为老年代的容量通常比年轻代大得多。这 意味着在收集和整理大量内存时,需要更多的时间来完成垃圾收集操作

尽管Major GC会引起较长的STW暂停,但JVM通常会尽量优化垃圾收集器的性能,以减少这些暂停对应 用程序的影响。例如,通过使用并行或并发垃圾收集算法,可以减少STW时间,并允许一部分垃圾收集 工作与应用程序的线程并发执行

Java 内存调整相关参数

可以在 Java 程序启动时添加选项来设置内存分配

[root@ubuntu24 ~]# ps aux |grep java
tomcat      3213  217  3.4 4628164 136052 ?      Sl   21:08   0:04 /usr/lib/jvm/java-11-openjdk-amd64//bin/java -Djava.util.logging.config.file=/usr/local/tomcat/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -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

选项分类:

-选项名:标准选项,所有HotSpot都支持

-D选项名:系统属性

-X选项名:为稳定的非标准选项

-XX:选项名:非标准的不稳定选项,下一个版本可能会取消

堆内存设置相关参数

参数说明范例
-Xms设置应用程序初始使用的堆内存大小(年轻代 +老年代)-Xms2g
-Xmx设置应用程序能获得的最大堆内存,早期JVM不 建议超过32G,内存管理效率下降-Xmx4g
-XX:NewSize设置初始新生代(年轻化)大小-XX:NewSize=128m
-XX:MaxNewSize设置最大新生代年轻化)内存空间-XX:MaxNewSize=256m
-Xmn同时设置-XX:NewSize 和 -XX:MaxNewSize-Xmn1g
-XX:NewRatio以比例方式设置新生代年轻化)和老年代-XX:NewRatio=2 即:new:old=1:2
-XX:SurvivorRatio以比例方式设置eden和survivor(S0或S1)-XX:SurvivorRatio=6即:Eden:S0:S1=6:1:1
-Xss设置Native Aera 每个线程私有的栈空间大小-Xss256k
#列出所有标准选项
[root@ubuntu ~]# java

#列出所有非标准稳定选项
[root@ubuntu ~]# java -X

查看状态页: 剩余内存: 32.75 MiB 总内存: 112.00 MiB 最大内存 484.00 MiB

#添加启动项,修改内存
[root@ubuntu ~]# cat /usr/local/tomcat/bin/catalina.sh
......
......
#添加此行,使用 JAVA_OPTS 也可以
#堆内存初始值1G,最大值1G,年轻代和老年代内存比例为 1:2,eden区和幸存区的内存比例为6:1:1
CATALINA_OPTS='-Xms1g -Xmx1g -XX:NewRatio=2 -XX:SurvivorRatio=6'
......
......
#重启服务
[root@ubuntu ~]# systemctl restart tomcat.service

#再次查看启动参数
[root@ubuntu ~]# ps aux | grep java
tomcat      4059 52.0 29.1 3892468 574708 ?     Sl   16:18   0:15 
/usr/lib/jvm/jdk-11-oracle-x64//bin/java -
Djava.util.logging.config.file=/usr/local/tomcat/conf/logging.properties -
Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -
Djdk.tls.ephemeralDHKeySize=2048 -
Djava.protocol.handler.pkgs=org.apache.catalina.webresources -
Dorg.apache.catalina.security.SecurityListener.UMASK=0027 -Xms1g -Xmx1g -
XX:NewRatio=2 -XX:SurvivorRatio=6 -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
#刷新状态页,再次查看内存分配情况

#直接在命令行下指定
[root@ubuntu ~]# java -Xms1g -Xmx1g -XX:NewRatio=2 -XX:SurvivorRatio=6 -jar 
abc.jar

[root@ubuntu ~]# ls Heap*
Heap.class Heap.java
[root@ubuntu ~]# java -Xms1g -Xmx1g -XX:NewRatio=2 -XX:SurvivorRatio=6 Heap

#内存不足
[root@ubuntu ~]# cat OOM.java 
import java. util. Random;
public class OOM {
 public static void main(String[] args) {
 String str = "welcome to baidu";
 while (true){
 str += str + new Random().nextInt(88888888); //生成0到88888888之间的随机数字
 }
 }
}
[root@ubuntu ~]# javac OOM.java 
#内存设置不合理,导致 OOM
[root@ubuntu ~]# java -Xms100m -Xmx100m OOM 
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
 at 
java.base/jdk.internal.misc.Unsafe.allocateUninitializedArray0(Unsafe.java:1278)
 at 
java.base/jdk.internal.misc.Unsafe.allocateUninitializedArray(Unsafe.java:1271)
 at 
java.base/java.lang.invoke.StringConcatFactory$MethodHandleInlineCopyStrategy.ne
wArray(StringConcatFactory.java:1633)
 at 
java.base/java.lang.invoke.DirectMethodHandle$Holder.invokeStatic(DirectMethodHa
ndle$Holder)
 at 
java.base/java.lang.invoke.LambdaForm$MH/0x0000000100065040.invoke(LambdaForm$MH
)
 at 
java.base/java.lang.invoke.LambdaForm$MH/0x0000000100065c40.invoke(LambdaForm$MH
)
 at 
java.base/java.lang.invoke.LambdaForm$MH/0x0000000100066040.linkToTargetMethod(L
ambdaForm$MH)
 at OOM.main(OOM.java:7)

#查看详细过程
[root@ubuntu ~]# java -Xms100m -Xmx100m -Xlog:gc* OOM

使用 jvisualvm 监控内存

jvisualvm 一款图形化的内存监控工具,在 jdk-8u361 之前的版本中是内置的组件,但在之后的 JDK 版 本中己经取消了该组件,要单独下载并配置

#安装依赖
[root@ubuntu ~]# apt install libxrender1 libxrender1 libxtst6 libxi6 fontconfig -y

[root@ubuntu ~]# wget https://github.com/oracle/visualvm/releases/download/2.1.8/visualvm_218.zip

[root@ubuntu ~]# unzip visualvm_218.zip

#在windows 中开启 Xmanager - Passive
[root@ubuntu ~]# export DISPLAY=10.0.0.1:0.0

#执行,在Windows中能看到GUI界面,在GUI中点击 Tools 菜单,Plugins,然后安装 VisualGC 插件
[root@ubuntu ~]# ./visualvm_218/bin/visualvm

[root@ubuntu ~]# cat HeapOom.java 
import java.util.ArrayList;
import java.util.List;
public class HeapOom {
 public static void main(String[] args) {
 List<byte[]> list =new ArrayList<byte[]>();
 int i = 0;
 boolean flag = true;
 while(flag){
 try{
 i++;
 list.add(new byte[1024* 1024]);//每次增加一个1M大小的数组对象
 Thread.sleep(1000);
 }catch(Throwable e){
 e.printStackTrace();
 flag =false;
 System.out.println("count="+i);//记录运行的次数
 }
 }
 }
}

#编译源码并执行
[root@ubuntu ~]# javac HeapOom.java
[root@ubuntu ~]# java -Xmn100m -Xmx100m HeapOom 
#在GUI界面中能看到内存情况,看到GC 过程

垃圾收集方式和调整策略

按工作模式不同:指的是GC线程和工作线程是否一起运行

  • 独占垃圾回收器:只有GC在工作,STW 一直进行到回收完毕,工作线程才能继续执行

  • 并发垃圾回收器:让GC线程垃圾回收某些阶段可以和工作线程一起进行,如:标记阶段并行,回 收阶段仍然串行

按回收线程数:指的是GC线程是否串行或并行执行

  • 串行垃圾回收器:一个GC线程完成回收工作

  • 并行垃圾回收器:多个GC线程同时一起完成回收工作,充分利用CPU资源

调整策略

对JVM调整策略应用极广

  • 在WEB领域中Tomcat等

  • 在大数据领域Hadoop生态各组件

  • 在消息中间件领域的Kafka等

  • 在搜索引擎领域的ElasticSearch、Solr等

注意:在不同领域和场景对JVM需要不同的调整策略

  • 减少 STW 时长,串行变并行

  • 减少 GC 次数,要分配合适的内存大小

一般情况下,大概可以使用以下原则:

  • 客户端或较小程序,内存使用量不大,可以使用串行回收

  • 对于服务端大型计算,可以使用并行回收

  • 大型WEB应用,用户端不愿意等待,尽量少的STW,可以使用并发回收

垃圾回收器设置

按分代设置不同的垃圾回收器

新生代串行收集器Serial:单线程、独占式串行,采用复制算法,简单高效但会造成STW

新生代并行回收收集器PS(Parallel Scavenge):多线程并行、独占式,会产生STW, 使用复制算法关注调 整吞吐量,此收集器关注点是达到一个可控制的吞吐量:

  • 吞吐量 = 运行用户代码时间/(运行用户代码时间+垃圾收集时间),比如虚拟机总共运行100分 钟,其中垃圾回收花掉1分钟,那吞吐量就是99%。

  • 高吞吐量可以高效率利用CPU时间,尽快完成运算任务,主要适合在后台运算而不需要太多交互的 任务。

  • 除此之外,Parallel Scavenge 收集器具有自适应调节策略,它可以将内存管理的调优任务交给虚 拟机去完成。自适应调节策略也是Parallel Scavenge与 ParNew 收集器的一个重要区别。

  • 此为默认的新生代的垃圾回收器

和ParNew不同,PS不可以和老年代的CMS组合

新生代并行收集器ParNew:就是Serial 收集器的多线程版,将单线程的串行收集器变成了多线程并行、 独占式,使用复制算法,相当于PS的改进版:

  • 经常和CMS配合使用,关注点是尽可能地缩短垃圾收集时用户线程的停顿时间,适合需要与用户交 互的程序,良好的响应速度能提升用户体验

老年代串行收集器Serial Old:Serial Old是Serial收集器的老年代版本,单线程、独占式串行,回收算法 使用标记压缩

老年代并行回收收集器Parallel Old:多线程、独占式并行,回收算法使用标记压缩,关注调整吞吐量:

  • Parallel Old收集器是Parallel Scavenge 收集器的老年代版本,这个收集器是JDK1.6之后才开始提 供,从HotSpot虚拟机的垃圾收集器的图中也可以看出,Parallel Scavenge 收集器无法与CMS收集 器配合工作,因为一个是为了吞吐量,一个是为了客户体验(也就是暂停时间的缩短)此为默认的新老年代的垃圾回收器

CMS (Concurrent Mark Sweep并发标记清除算法) 收集器:

  • 在某些阶段尽量使用和工作线程一起运行,减少STW时长(200ms以内), 提升响应速度,是互联网服 务端BS系统上较佳的回收算法

  • 分为4个阶段:初始标记、并发标记、重新标记、并发清除,在初始标记、重新标记时需要STW 初始标记:此过程需要STW(Stop The Word),只标记一下GC Roots能直接关联到的对象,速度很快。

  • 并发标记:就是GC Roots进行扫描可达链的过程,为了找出哪些对象需要收集。这个过程远远慢于 初始标记,但它是和用户线程一起运行的,不会出现STW,所有用户并不会感受到。

  • 重新标记:为了修正在并发标记期间,用户线程产生的垃圾,这个过程会比初始标记时间稍微长一 点,但是也很快,和初始标记一样会产生STW。

  • 并发清理:在重新标记之后,对现有的垃圾进行清理,和并发标记一样也是和用户线程一起运行的,耗时较长(和初始标记比的话),不会出现STW。

  • 由于整个过程中,耗时最长的并发标记和并发清理都是与用户线程一起执行的,所以总体上来说, CMS收集器的内存回收过程是与用户线程一起并发执行的

以下收集器不再按明确的分代单独设置

G1(Garbage First)收集器

  • 是最新垃圾回收器,从JDK1.6实验性提供,JDK1.7发布,其设计目标是在多处理器、大内存服务器 端提供优于CMS收集器的吞吐量和停顿控制的回收器。JDK9将G1设为默认的收集器,建议 JDK9版本 以后使用

  • 基于标记压缩算法,不会产生大量的空间碎片,有利于程序的长期执行

  • 分为4个阶段:初始标记、并发标记、最终标记、筛选回收。并发标记并发执行,其它阶段STW只 有GC线程并行执行 G1收集器是面向服务端的收集器,它的思想就是首先回收尽可能多的垃圾(这也是Garbage-First 名字的由来)

  • G1能充分的利用多CPU,多核环境下的硬件优势,使用多个CPU来缩短STW停顿的时间(10ms以 内)

  • 可预测的停顿:这是G1相对于CMS的另一大优势,G1和CMS一样都是关注于降低停顿时间,但是 G1能够让使用者明确的指定在一个M毫秒的时间片段内,消耗在垃圾收集的时间不得超过N毫秒

  • 通过此选项指定:+UseG1GC

ZGC收集器:减少STW时长(1ms以内), 可以PK C++的效率,目前实验阶段

Shenandoah收集器:和ZGC竞争关系,目前实验阶段

Epsilon收集器:调试 JDK 使用,内部使用,不用于生产环境

JVM 1.8 默认的垃圾回收器:PS + ParallelOld,所以大多数都是针对此进行调优

垃圾收集器设置

优化调整 Java 相关参数的目标:尽量减少FullGC和STW

通过以下选项可以单独指定新生代、老年代的垃圾收集器

-server #指定为Server模式,也是默认值,一般使用此工作模式
-XX:+UseSerialGC #运行在Client模式下,新生代是Serial, 老年代使用SerialOld
-XX:+UseParNewGC #新生代使用ParNew,老年代使用SerialOld
-XX:+UseParallelGC #运行于server模式下,新生代使用Parallel Scavenge, 老年代使用 Parallel Old,此为JVM8默认值,关注吞吐量,JDK8默认值,关注吞吐量

-XX:+UseParallelOldGC #新生代使用Paralell Scavenge, 老年代使用Paralell Old,和上面-XX:+UseParallelGC 相同
 -XX:ParallelGCThreads=N #在关注吞吐量的场景使用此选项增加并行线程数
 
 
 
-XX:+UseConcMarkSweepGC #新生代使用ParNew, 老年代优先使用CMS,备选方式为Serial Old,响应时间要短,停顿短使用这个垃圾收集器
 -XX:CMSInitiatingOccupancyFraction=N #N为0-100整数表示达到老年代的大小的百分比多少触发回收,默认 68
 -XX:+UseCMSCompactAtFullCollection #开启此值,在CMS收集后,进行内存碎片整理
 -XX:CMSFullGCsBeforeCompaction=N #设定多少次CMS后,进行一次内存碎片整理
 -XX:+CMSParallelRemarkEnabled #降低标记停顿
 
 
-XX:+UseG1GC #使用GC1的垃圾器器,是JVM11的默认值

开启垃圾回收输出统计信息,适用于调试环境的相关选项,生产环境请移除这些参数,否则有非常多的日 志输出

-XX:+PrintGC #输出GC信息
-XX:+PrintGCDetails #输出GC详细信息
-XX:+PrintGCTimeStamps #与前两个组合使用,在信息上加上一个时间戳
-XX:+PrintHeapAtGC #生成GC前后椎栈的详细信息,日志会更大

如果需要修改垃圾回收器配置,在 /usr/local/tomcat/bin/catalina.sh 文件中使用 JAVA_OPTS 加上 配置项即可

JAVA 参数总结

-Xms<size> #初始堆大小,默认值是物理内存的 1/64
-Xmx<size> #最大堆大小,默认值是物理内存的 1/4
-Xmn<size> #年轻代大小,eden+ 2 survivor space
-XX:NewSize #设置年轻代大
-XX:MaxNewSize #年轻代最大值
-XX:PermSize #设置持久代(perm gen)初始值,默认值为物理内存 1/64
-XX:MaxPermSize #设置持久代最大值,默认值为物理内存 1/4
-Xss<size> #每个线程的堆栈大小
-XX:NewRatio #年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)
-XX:SurvivorRatio #-XX:NewRatio=4表示年轻代与年老代所占比值为1:4,年轻代占整个堆栈1/5Xms=Xmx并且设置了Xmn的情况下,该参数不需要进行设置


-XX:LargePageSizeInBytes #内存页的大小,不可设置过大, 会影响Perm的大小,默认12M
-XX:+UseFastAccessorMethods #原始类型的快速优化
-XX:+DisableExplicitGC #关闭 System.gc()
-XX:MaxTenuringThreshold #垃圾最大年龄,如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代. 对于年老代比较多的应用,可以提高效率.如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活 时间,增加在年轻代即被回收的概率该参数只有在串行GC时才有效


-XX:+AggressiveOpts #加快编译
-XX:+UseBiasedLocking #锁机制的性能改善
-Xnoclassgc #禁用垃圾回收
-XX:SoftRefLRUPolicyMSPerMB #每兆堆空闲空间中SoftReference的存活时间
-XX:PretenureSizeThreshold #对象超过多大是直接在旧生代分配,默认值为 0 
-XX:TLABWasteTargetPercent #TLAB占eden区的百分比,默认值为 1%
-XX:+CollectGen0First #FullGC时是否先YGC,默认值 false


#并行收集器相关参数
-XX:+UseParallelGC #Full GC采用parallelMSC,选择垃圾收集器为并行收集器.此配置仅对年轻代有效.即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集
-XX:+UseParNewGC #设置年轻代为并行收集,可与CMS收集同时使用 JDK5.0以上,JVM会根据系统配置自行设置,所以无需再设置此值
-XX:ParallelGCThreads #并行收集器的线程数,此值最好配置与处理器数目相等 同样适用于CMS
-XX:+UseParallelOldGC #年老代垃圾收集方式为并行收集
(ParallelCompacting),是JAVA 6出现的参数选项
-XX:MaxGCPauseMillis #每次年轻代垃圾回收的最长时间(最大暂停时间)
-XX:+UseAdaptiveSizePolicy #自动选择年轻代区大小和相应的Survivor区比例
-XX:GCTimeRatio #设置垃圾回收时间占程序运行时间的百分比,公式为1/(1+n)
-XX:+ScavengeBeforeFullGC #Full GC前调用YGC,默认值 true

#CMS相关参数
-XX:+UseConcMarkSweepGC #使用CMS内存收集,测试中配置这个以后,-
XX:NewRatio=4的配置可能失效,所以,此时年轻代大小最好用-Xmn设置
-XX:+AggressiveHeap #试图是使用大量的物理内存 长时间大内存使用的优化,能检查计算资源(内存, 处理器数量) 至少需要256MB内存 大量的CPU/内存, (在1.4.1在4CPU的机器上已经显示有提升)


-XX:CMSFullGCsBeforeCompaction #多少次后进行内存压缩,由于并发收集器不对内存空间进行压缩,整理,所以运行一段时间以后会产生"碎片",使得运行效率降低.此值设置运行多少次GC以后对内存空间进行压缩,整理
-XX:+CMSParallelRemarkEnabled #降低标记停顿
-XX+UseCMSCompactAtFullCollection #在FULLGC的时候,对年老代的压缩
-XX:+UseCMSInitiatingOccupancyOnly #使用手动定义初始化定义开始CMS收集,禁止hostspot自行触发CMS GC
-XX:CMSInitiatingOccupancyFraction=70 #使用cms作为垃圾回收使用70%后开始CMS收集,默认值 92
-XX:+UseConcMarkSweepGC #使用CMS内存收集
-XX:CMSInitiatingPermOccupancyFraction #设置PermGen使用到达多少比率时触发,默认值92
-XX:+CMSIncrementalMode #设置为增量模式,用于单CPU情况


#帮助信息
-XX:+PrintGC #输出GC信息
-XX:+PrintGCDetails #输出GC信息,与上一个选项输出格式不同
-XX:+PrintGCTimeStamps #输出时间相关内容
-XX:+PrintGC:PrintGCTimeStamps #输出时间相关内容
-XX:+PrintGCApplicationStoppedTime #打印垃圾回收期间程序暂停的时间.可与上面混合使用
-XX:+PrintGCApplicationConcurrentTime #打印每次垃圾回收前,程序未中断的执行时间.可与上面混合使用
-XX:+PrintHeapAtGC #打印GC前后的详细堆栈信息
-Xloggc:filename #输出到指定文件
-XX:+PrintTLAB #查看TLAB空间的使用情况
XX:+PrintTenuringDistribution #查看每次minor GC后新的存活周期的阈值

JVM相关工具

[root@ubuntu ~]# ls -1 /usr/lib/jvm/jdk-11-oracle-x64/bin/
jar
jarsigner
java
javac
javadoc
javap
jcmd
jconsole #图形工具
jdb
jdeprscan
jdeps
jfr
jhsdb
jibai
jinfo #查看进程的运行环境参数,主要是jvm命令行参数
jjs
jlink
jmap #查看jvm占用物理内存的状态
jmod
jps #查看所有jvm进程
jrunscript
jshell
jstack #查看所有线程的运行状态
jstat #对jvm应用程序的资源和性能进行实时监控
jstatd
keytool
pack200
rmic
rmid
rmiregistry
serialver
unpack200

相关工具范例

#显示java进程
[root@ubuntu ~]# jps
1417 Bootstrap
5950 Jps

#详细列出当前Java进程信息
[root@ubuntu ~]# jps -lv
5970 jdk.jcmd/sun.tools.jps.Jps -Dapplication.home=/usr/lib/jvm/jdk-11-oraclex64 -Xms8m -Djdk.module.main=jdk.jcmd
1417 org.apache.catalina.startup.Bootstrap --add-opens=java.base/java.lang=ALLUNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --addopens=java.base/java.util=ALL-UNNAMED --addopens=java.base/java.util.concurrent=ALL-UNNAMED --addopens=java.rmi/sun.rmi.transport=ALL-UNNAMED -
Djava.util.logging.config.file=/usr/local/tomcat/conf/logging.properties -
Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -
Djdk.tls.ephemeralDHKeySize=2048 -
Djava.protocol.handler.pkgs=org.apache.catalina.webresources -
Dorg.apache.catalina.security.SecurityListener.UMASK=0027 -
Dignore.endorsed.dirs= -Dcatalina.base=/usr/local/tomcat -
Dcatalina.home=/usr/local/tomcat -Djava.io.tmpdir=/usr/local/tomcat/temp


#输出给定的java进程的所有配置信息
[root@ubuntu ~]# jinfo 1417
Java System Properties:
#Tue Apr 16 14:14:56 CST 2024
awt.toolkit=sun.awt.X11.XToolkit
java.specification.version=11
sun.cpu.isalist=
sun.jnu.encoding=UTF-8
......
......


#输出GC相关信息
[root@ubuntu ~]# jinfo -flags 1417
VM Flags:
-XX:CICompilerCount=2 -XX:ConcGCThreads=1 -XX:G1ConcRefinementThreads=2 -
XX:G1HeapRegionSize=1048576 -XX:GCDrainStackTargetSize=64 -
XX:InitialHeapSize=33554432 -XX:MarkStackSize=4194304 -XX:MaxHeapSize=507510784
-XX:MaxNewSize=304087040 -XX:MinHeapDeltaBytes=1048576 -
XX:NonNMethodCodeHeapSize=5825164 -XX:NonProfiledCodeHeapSize=122916538 -
XX:ProfiledCodeHeapSize=122916538 -XX:ReservedCodeCacheSize=251658240 -
XX:+SegmentedCodeCache -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -
XX:+UseFastUnorderedTimeStamps -XX:+UseG1GC

#列出所有可统计的项目
[root@ubuntu ~]# jstat -options
-class
-compiler
-gc
-gccapacity
-gccause
-gcmetacapacity
-gcnew
-gcnewcapacity
-gcold
-gcoldcapacity
-gcutil
-printcompilation

#统计指定进程的GC信息
[root@ubuntu ~]# jstat -gc 1417
 S0C   S1C   S0U   S1U     EC       EU       OC         OU       MC     MU   
CCSC   CCSU   YGC     YGCT   FGC   FGCT   CGC   CGCT     GCT   
0.0   3072.0  0.0   3072.0 71680.0  52224.0   44032.0    14635.9   26240.0 
25199.5 2944.0 2526.2     60    0.214   0      0.000   2      0.004    0.218

#1S统计一次,共3次
[root@ubuntu ~]# jstat -gcnew 1417 1000 3
 S0C   S1C   S0U   S1U   TT MTT DSS     EC       EU     YGC     YGCT  
   0.0 3072.0    0.0 3072.0 15  15 4608.0  71680.0  52224.0     60    0.214

使用图形化工具来查看JAVA进程信息

JMX(Java Management Extensions,即Java管理扩展)是一个为JAVA应用程序、设备、系统等植入管 理功能的框架。JMX可以跨越一系列异构操作系统平台、系统体系结构和网络传输协议,灵活的开发无 缝集成的系统、网络和服务管理应用

JMX最常见的场景是监控Java程序的基本信息和运行情况,任何Java程序都可以开启JMX,然后使用 JConsole或Visual VM进行预览

#为Java程序开启JMX很简单,只要在运行Java程序的命令后面指定如下命令即可
java \
-Dcom.sun.management.jmxremote \ #启用远程监控JMX
-Djava.rmi.server.hostname=10.0.0.100 \ #指定自已监听的主机的IP,不支持0.0.0.0
-Dcom.sun.management.jmxremote.port=12345 \ #指定监听的PORT
-Dcom.sun.management.jmxremote.authenticate=false \ #不需要认证
-Dcom.sun.management.jmxremote.ssl=false \ #不使用ssl加密
-jar app.jar|app.war

Tomcat 性能优化常用配置

内存空间优化

JAVA_OPTS="-server -Xms4g -Xmx4g -XX:NewSize= -XX:MaxNewSize= "
-server #服务器模式
-Xms #堆内存初始化大小
-Xmx #堆内存空间上限
-XX:NewSize= #新生代空间初始化大小
-XX:MaxNewSize= #新生代空间最大值

线程池调整

[root@ubuntu ~]# cat /usr/local/tomcat/conf/server.xml
......
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000"
redirectPort="8443" maxThreads="2000"/>
......

connectionTimeout #连接超时时长,单位ms
maxThreads #最大线程数,默认200
minSpareThreads #最小空闲线程数
maxSpareThreads #最大空闲线程数
acceptCount #当启动线程满了之后,等待队列的最大长度,默认100
URIEncoding #URI 地址编码格式,建议使用 UTF-8
enableLookups #是否启用客户端主机名的DNS反向解析,缺省禁用,建议禁用,就使用客户端IP就行
compression #是否启用传输压缩机制,建议 "on",CPU和流量的平衡
compressionMinSize #启用压缩传输的数据流最小值,单位是字节
compressableMimeType #定义启用压缩功能的MIME类型text/html, text/xml, 
text/css,text/javascript

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值