优雅使用Windows 10
jmap
概述
jmap命令是一个可以输出所有内存中对象的工具,甚至可以将JVM中的heap,以二进制输出成文本。打印出某个java进程(使用pid)内存内的所有对象的情况(如:产生哪些对象,及其数量)。
命令格式
1 | jmap [option] <pid> |
基本命令
输出jvm的heap内容到文件
jmap -dump:live,format=b,file=[outputFileName.hprof] [pid]
使用hprof二进制形式输出jvm的heap内容到文件。live子选项是可选的,只输出活的对象到文件。
打印正等候回收的对象的信息
jmap -finalizerinfo [pid]
打印heap的概要信息
JDK 8: jmap -heap [pid]
JDK 9往后:jhsdb jmap --heap --pid [pid]
1 | root@PC:~# jhsdb jmap --heap --pid 51266 |
打印每个class的实例数目、内存占用、类名信息
jmap -histo:live [pid]
如何快速定位OOM问题
OOM的原因
一次性申请过多的对象
更改申请对象的数量。(分页)
内存资源耗尽 未释放
找到未释放的对象进行释放。
本身资源不够
查看堆信息:
JDK 8: jmap -heap [pid]
JDK 11往后:jhsdb jmap --heap --pid [pid]
通过dump定位
系统已经OOM挂掉了
提前设置
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=[指定的存放路径]
需要保证服务器的硬盘空间够大,因为该设置会记录系统在整个运行过程中所有的对象信息,占用空间。
发生OOM后,会生成
java_pidxxxxx.hprof
的heap dump文件。使用JProfiler进行查看
找到与业务相关的类 - 右键,使用选定对象
选择 传入引用,确定。
显示到GC根(GC Root)的路径,确定
根据显示的线程堆栈,查看代码进行分析。
系统运行中,还未OOM
方式一 导出dump文件
jmap -dump:format=b,file=[outputFileName.hprof] [pid]
在系统运行阶段导出dump文件,会造成一次FullGC、STW(Stop The World,所有线程中断)。但不导出dump文件的话,时间成本要增高很多
方式二 Arthas
RabbitMQ
简介
消息队列提供一个异步通信机制,消息的发送者不必一直等待到消息被成功处理才返回,而是立即返回。消息中间件负责处理网络通信,如果网络连接不可用,消息被暂存于队列当中,当网络畅通时,将消息转发给相应的应用程序或者服务。
如果在商品服务和订单服务之间使用消息中间件,既可以提高并发量,又降低服务之间的耦合度。
RabbitMQ是一个开源的消息代理的队列服务器,用来通过普通协议在完全不同的应用之间共享数据。
Docker下相关命令
下载
1
2# 要选择带management的版本,带管理页面
docker pull rabbitmq:3.12-management创建用户
1
2
3
4rabbitmqctl add_user [username] [password]
# 赋予管理员权限
rabbitmqctl set_user_tags [username] administrator
架构
交换机 exchange
发布订阅模式 fanout
发布一次,消费多个。
JMeter
下载
Apache JMeter - Download Apache JMeter
选择二进制文件包。
Windows下
windows下,解压后运行”\apache-jmeter-5.6.2\bin\jmeter.bat”。
Options- choose language - 选择中文。
Linux下
解压后运行apache-jmeter-5.6.2/bin/jmeter.sh -n -t first.jmx -l result.jtl
-n
命令行下执行-t
要运行的测试脚本文件-l
记录结果的文件
查看result.jtl文件,要将文件拷出到windows,在监听器 - 聚合报告 下,打开文件浏览
配置不同用户进行测试
cookie管理器中,通过${}
获取CSV中设置的变量值
Springboot全局异常统一处理
背景
以往的Java Web开发中,异常处理通常是通过try-catch语句块来实现。这种方法在应用程序规模较小的情况下还可以,但是在大型应用中,可能存在大量的代码重复和不一致问题。此外,在抛出未处理的异常时,用户会看到系统生成的默认错误页面,用户体验差。
全局异常统一处理
优点
有助于保持代码整洁和规模化
如果没有全局异常处理,每个Controller的方法都需要实现自己的异常处理,当程序变得越来越复杂时,这种代码会导致代码冗余和混乱的异常处理逻辑。
提升用户体验
全局异常处理允许应用程序捕获未处理的异常,并提供更友好的异常提示信息。
便于日志记录和监控
全局异常处理可以帮助应用程序捕获和记录异常信息,在出现问题时快速定位。此外,还可以和监控系统继承,以实时跟踪应用程序中出现的异常情况。
增强安全性
全局异常处理可以防止应用程序出现潜在的安全漏洞,例如SQL注入和XSS攻击。
实操
1 | <!-- web组件 --> |
自定义一个异常类
1 | @Data |
编写统一异常处理类,统一捕获处理返回
1 | @RestControllerAdvice // 表明统一处理异常 |
SQL优化手段
优化慢SQL
慢查询日志记录了执行时间超过long_query_time
(默认10s,通常设置为1s)的所有查询语句,在解决SQL慢查询问题时经常会用到。
查询慢查询日志是否开启 (默认关闭)
1
show variables like "slow_query_log;
开启慢查询日志
1
SET GLOBAL slow_query_log=ON;
查看慢查询的超时时间
1
show variables like "%long_query_time%";
修改
long_query_time
参数:1
SET GLOBAL long_query_time=1;
查询当前慢查询语句的个数
1
show global status like "%Slow_queries%";
查询慢查询日志存放位置
1
SHOW VARIABLES LIKE "slow_query_log_file";
无论是否超时,未被索引的记录也被记录
1
SET GLOBAL log_queries_not_using_indexes = "ON";
慢查询仅记录扫描行数>此参数的SQL
1
SET SESSION min_examined_row_limit = 100;
设置完成后,可以用SHOW VARIABLES LIKE "slow%";
命令查看。
故意执行一条慢查询并查看日志
1 | # 故意在百万数据量的表中执行一条 未使用索引的排序语句 |
查看日志路径,并去实际查看日志记录
1 | SHOW VARIABLES LIKE "slow_query_log_file"; |
1 | # Time: 2023-09-25T15:30:40.620921Z |
Query_time
这段代码的运行时长。Lock_time
执行这段代码时,锁定了多久。Rows_sent
慢查询返回的记录数量。Rows_examined
慢查询扫描过的行数。
实际项目中,慢查询日志通常会比较复杂,需要借助一些工具对其进行分析。比如MySQL内置的**mysqldumpslow
工具**,可以将相同的SQL归为一类,并统计出归类项的执行次数和每次执行的耗时等一系列对应的情况。
执行计划:一条语句在经过MySQL查询优化器的优化后,具体的执行方式。执行计划通常用于SQL性能分析、优化等场景。
通过EXPAIN
命令,可以获取执行计划的相关信息。可以了解到如数据表的查询顺序、数据查询操作的操作类型、哪些索引可以被命中、哪些索引实际会命中、每个数据表有多少行记录被查询等信息。
找到了慢SQL后,可以通过EXPLAIN
命令分析对应的语句。
1 | EXPLAIN SELECT score, name |
select_type
查询的类型SIMPLE
: 普通查询(即没有联合查询、子查询)PRIMARY
:主查询UNION
(UNION中后面的查询)SUBQUERY
table
查询涉及的表或衍生表type
执行方式,判断查询是否高效的重要参考指标,结果值从差到好:- ALL
- index
- range~index_range
- ref
- eq_ref
- const
- system
rows
SQL要查找到结果集需要扫描读取的数据行数,原则上rows越少越好。
避免使用 SELECT *
- 需要解析更多的对象、字段、权限、属性等相关内容,会消耗更多的CPU
- 无用字段增加网络带宽资源消耗、增加数据传输时间,尤其是大字段(varchar、blob、text)
- 无法使用 MySQL优化器覆盖索引 的优化
分页优化
1 | SELECT score, `name` |
使用延迟关联来优化这个分页查询语句:
1 | SELECT score, name |
- 先提取主键
- 再将这个主键表与原数据表关联
尽量避免多表做join
join的效率比较低,主要是因为使用嵌套循环(Nested Loop)来实现关联查询,效率都不是很高。实际业务场景避免多表join常见的作法有2种:
单表查询后,在内存中自己做关联。
对数据库做单表查询,再根据查询结进行二次查询。以此类推,最后再进行关联。
- 拆分后的单表查询代码可复用性更高。
- 单表查询更利于后续的维护。
数据冗余
把一些重要的数据在表中做冗余,尽可能避免关联查询。很笨的一种做法,表结构比较稳定的清苦那个下才会考虑。
建议不要使用外键与级联
不建议使用外键的主要原因是对分库分表不友好,性能方面影响较小。
选择合适的字段类型
某些字符串可以转换成数字类型存储,比如IP地址。
数字是连续的,性能更好,占用空间叶更小。
MySQL提供了两个方法来处理IP地址:
INET_ATON()
:把ip转为无符号整型(4-8位)INET_NTOA()
:把整型的ip转为地址
对于非负的数据(自增ID、整型IP、年龄)来说,要优先使用无符号整型来存储
无符号(SIGNED INT)相对于有符号(UNSIGNED INT)多出了一倍的存储空间
小数值类型(年龄、状态表示如0/1)优先使用
TINYINT
类型日期类型一定不要用字符串存储,可以考虑
DATETIME
、TIMESTAMP
和数值型时间戳类型 存储空间 日期格式 日期范围 是否携带时区 DATETIME 5-8字节 YYYY-MM-DD hh:mm:ss[.fraction] 1000-01-01 00:00:00[.000000] ~ 9999-12-31 23:59:59[.999999] 否 TIMESTAMP 4-7字节 YYYY-MM-DD hh:mm:ss[.fraction] 1970-01-01 00:00:01[.000000] ~2038-01-19 03:14:07[.999999] 是 数值型时间戳 4字节 全数字 1970-01-01 00:00:01之后的时间 否 金额字段用decimal,避免精度丢失
Java中,对应
BigDecimal
尽量使用自增id作为主键(分布式场景不建议)
主键如果是自增id,每次都会将数据加在B+树尾部(本质是双向链表),时间复杂度为
O(1)
。在写满一个数据页的时候,直接申请另一个新数据页接着写就可以了。如果主键是非自增id,为了让新加入数据后,B+树的叶子节点还能保持有序,需要往叶子节点的中间找,查找过程的时间复杂度是O($ \log n$)。如果数据页被写满,需要进行页分裂。页分裂操作需要加悲观锁,性能非常低。
但是像分库分表这类场景就不建议使用自增id作为主键,应该使用分布式id,比如uuid。
不建议使用NULL作为列默认值
NULL需要占用空间,空字符串
''
反而不用占用空间。NULL会影响聚合函数的结果。
SUM
、AVG
等函数会忽略NULL值,COUNT(列名)
也会忽略NULL,但COUNT(*)
会统计所有的记录数。
尽量用UNION ALL 代替UNION
UNION会把两个结果集放在一个临时表中再进行去重操作,更耗时、消耗CPU资源。
批量操作
批量操作能减少请求数据库的次数,提高性能。
1 | INSERT INTO cus_order(id, score, name) |
Show PROFILE 分析SQL执行性能
SHOW PROFILE
和SHOW PROFILES
已经被弃用,未来的MySQL版本中可能会删除,取代它的是Performance Schema。
SHOW PROFILE
和SHOW PROFILES
展示SQL语句的资源使用情况,展示的消息包括CPU的使用、CPU上下文切换、IO等待、内存使用等。
查看是否支持
profiling
1
SELECT @@have_profiling;
查看profiling是否开启
1
2
3
4# 0是关闭,1是开启
SELECT @@profiling;
SET @@profiling = 1;SHOW PROFILES
展示当前Session下所有SQL语句的简要信息(Query_ID、Duration)SHOW PROFILE
展示一个SQL语句的执行耗时细节1
2
3
4
5
6
7
8
9
10
11
12
13
14
15SHOW PROFILE [type [, type] ...]
[FOR QUERY n] # 展示Query_ID为n的执行情况,默认展示最新的一次SQL执行情况
[LIMIT row_count [OFFSET offset]]
type: { # 具体某类资源的消耗情况
ALL
BLOCK IO
CONTEXT SWITCHES
CPU
IPC
MEMORY
PAGE FAULTS
SOURCE
SWAPS
}
正确使用索引
正确的索引可以大大加快数据的检索速度。
选择合适的字段创建索引
不为NULL的字段
被频繁查询的字段
被作为条件查询的字段
频繁需要排序的字段(索引已经排序)
被频繁用于连接的字段
提高多表连接查询的效率。
被频繁更新的字段应该慎重建立索引
维护索引的成本不小。
尽可能考虑建立 联合索引 而不是单列索引
可以简单理解为每个索引都对着一棵B+树。如果一个表的字段过多,索引过多,当表数据达到一个体量之后,索引占用的空间也会很多,且修改索引的耗时也会很多。如果是联合索引,会节约很大的磁盘空间,且修改数据的效率也会提升。
避免冗余索引
冗余索引指的是索引功能相同,**能够命中索引(a,b)就肯定能命中索引(a)**。在大多数情况下,都应该尽量扩展已有的索引,而不是创建新索引。
考虑在 字符串类型 的字段上使用 前缀索引 代替普通索引
避免索引失效
索引失效也是慢查询的主要原因之一。常见的导致索引失效的情况:
SELECT *
- 创建了组合索引,但查询条件未遵守最左匹配原则 (查询中未使用了该索引的第一列)
- 在索引列上进行计算、函数、类型转换等操作
- 以
%
开头的LIKE查询 - 查询条件中使用OR,且OR的前后条件中有一个列没有索引,涉及的索引都不会被使用到
- 发生隐式转换 (操作符左右两边的数据类型不一致时,左边为字符类型时发生转换,会导致索引失效,效率极低)
删除长期未使用的索引
可以通过查询sys
库的schema_unused_indexes
视图来查询哪些索引从未被使用。
MySQL存储引擎
概述
MySQL支持多种存储引擎,可以通过SHOW ENGINES
命令查看MySQL所支持的所有存储引擎。
MySQL 当前默认的存储引擎是 InnoDB。并且,所有的存储引擎中只有 InnoDB 是事务性存储引擎,也就是说只有 InnoDB 支持事务。
MySQL 5.5.5 之前,MyISAM 是 MySQL 的默认存储引擎。5.5.5 版本之后,InnoDB 是 MySQL 的默认存储引擎。
MySQL存储引擎架构
MySQL存储引擎采用的是插件式架构,支持多种存储引擎。存储引擎是基于表的,不是基于数据库的。
MyISAM和InnoDB有什么区别
MySQL 5.5之前,MyISAM引擎是默认存储引擎,但是MyISAM不支持事务和行级锁,且崩溃后无法安全恢复。
InnoDB | MyISAM | |
---|---|---|
行级锁 | 支持 | 只有表级锁,在并发写的情况下,性能极差 |
事务 | 支持(默认使用可重读隔离级别,可以解决幻读问题的发生) | 不支持 |
外键 | 支持(外键对于维护数据一致性非常有帮助,但是对性能有一定的损耗,因此通常情况下,不建议在实际生产项目中使用外键,而在业务代码中进行约束) | 不支持 |
安全恢复 | 支持(数据库在异常崩溃后,**重启时会保证数据库恢复到崩溃前的状态,这个过程依赖于redo log **) |
不支持 |
索引实现 | 使用B+树作为索引结构,数据文件本身就是索引文件。 | 使用B+树,索引文件和数据文件分离。 |
性能 | 随着CPU核数的增加,InnoDB的读写能力呈线性增长。 | 读写不能并发,所以处理能力和CPU核数无关。 |
InnoDB存储引擎对MVCC的实现
MVCC, Multi-Version Concurrency Control 多版本并发控制。MVCC是一种用于多个并发事务同时读写数据库时,保持数据的一致性和隔离性的机制。它通过在每个数据行上维护多个版本的数据来实现。当一个事务要对数据库中的数据进行修改时,MVCC会为该事务创建一个数据快照,而不是直接修改实际的数据行。