本文为博主原创,未经允许不得转载:
目录
1.1.SQL优化 1.2.慢sql或索引失效分析 1.4 SQL分析优化 1.5.连接池调优 1.6.架构层面
2.1.避免缓存失效 2.2.缓存热点数据 2.3.数据不一致性 2.4.缓存可用性 2.5. 缓存预热 2.6.缓存穿透
6.1 GC 性能衡量指标 6.2 GC 优化策略 6.3 FullGC 优化 6.4 CMS 垃圾回收器参数配置 6.5 G1垃圾回收器参数配置
9.其他常见优化项
正文:
性能测试的主要指标与方法
一般来说,衡量系统的性能,主要有以下几个指标:
响应时间
可以从端到端的响应时间细分下去:比如数据库的响应时间,IO的响应时间,HTTPClient的响应时间。当我们优化系统的时候,通过收集这些响应时间可以精确定位性能问题出现在哪。
并发数
并发数是指系统能够同时处理请求的数量,这个数字也反映了系统的负载承受能力。
吞吐量
吞吐量是指单位时间内系统处理的请求数量,体现的是系统的处理能力。在 Web 系统中,常常用 TPS ( 每秒事务处理量 ) 或者 QPS ( 每秒查询量 ) 来衡量系统的吞吐量。在不考虑网卡等网络设备限制的情况下,可以使用下面的公式来大致估算系统的吞吐量:
吞吐量 = (1000/响应时间 ms) x 并发数
1.MySQL调优
1.1.SQL优化
这里以MySQL为例,最常见的方式是,由自带的慢查询日志或者开源的慢查询系统定位到具体的出问题的SQL,然后使用explain、profile等工具来逐步调优,最后经过测试达到效果后上线。
这里举几个优化的例子:
1查询优化
对查询进行优化,要尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
2.避免null判断
应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:
select id from t where num is null
3.合理使用索引
索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。
一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有 必要。
4.多使用数字型字段
尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。
这是因为引擎在处理查询和连 接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。
5.避免大数量
尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。
1.2.慢sql或索引失效分析
常见慢sql 或 索引失效的场景:
- 无索引或添加索引的字段区分性很差
- 复合索引时不满足最左前缀规则
- 应尽量避免在 where 子句中使用 != 或 <> 操作符,否则引擎将放弃使用索引而进行全表扫描。
- 应尽量避免在 where 子句中使用 or 来连接条件,如果一个字段有索引,一个字段没有索引,将导致引擎放弃使用索引而进行全表扫描。
- in和 not in 也要慎用,否则会导致全表扫描,如:
select id from t where num in(1,2,3)
对于连续的数值,能用 between就不要用 in 了:
select id from t where num between 1 and 3
- 优化 SELECT * ; 查询具体需要的列:如
select a,b from table where id=1
,假设存在a和b的联合索引,那么 select * 会导致回表
1.4 SQL分析优化
通过 EXPLAIN 分析 SQL 执行计划
图中每个字段说明
- 「id」:每个执行计划都有一个 id,如果是一个联合查询,这里还将有多个 id。id 越大优先级越高
- 「select_type」:表示 SELECT 查询类型,常见的有 SIMPLE(普通查询,即没有联合查询、子查询)、PRIMARY(主查询, 子查询中最外层查询 )、UNION(UNION 中后面的查询)、SUBQUERY(子查询)等。
- 「table」:当前执行计划查询的表,如果给表起别名了,则显示别名信息。
- 「partitions」:访问的分区表信息。
- 「type」:表示从表中查询到行所执行的方式,查询方式是 SQL 优化中一个很重要的指标,结果值从好到差依次是:system > const > eq_ref > ref > range > index > ALL。
- 「system/const」:表中只有一行数据匹配,根据索引查询一次就能找到对应的数据。
- 「eq_ref」:使用唯一索引扫描,常见于多表连接中使用主键和唯一索引作为关联条件。
- 「ref」:非唯一索引扫描,还可见于唯一索引最左原则匹配扫描。
- 「range」:索引范围扫描,比如,<,>,between 等操作。
- 「index」:索引全表扫描,此时遍历整个索引树。
- 「ALL」:表示全表扫描,需要遍历全表来找到对应的行。
- 「possible_keys」:可能使用到的索引。
- 「key」:实际使用到的索引。
- 「key_len」:当前使用的索引的长度。
- 「ref」:关联 id 等信息。
- 「rows」:查找到记录所扫描的行数。
- 「filtered」:查找到所需记录占总扫描记录数的比例。
- 「Extra」:额外的信息。
- Using where:不用读取表中所有信息,仅通过索引就可以获取所需数据,这发生在对表的全部的请求列都是同一个索引的部分的时候,表示mysql服务器将在存储引擎检索行后再进行过滤
- Using temporary:表示MySQL需要使用临时表来存储结果集,常见于排序和分组查询,常见 group by ; order by
- Using filesort:当Query中包含 order by 操作,而且无法利用索引完成的排序操作称为“文件排序”
- Using index:表示相应的select操作中使用了覆盖索引(covering index),避免访问了表的数据行,效率不错!
1.5.连接池调优
我们的应用为了实现数据库连接的高效获取、对数据库连接的限流等目的,通常会采用连接池类的方案,即每一个应用节点都管理了一个到各个数据库的连接池。
随着业务访问量或者数据量的增长,原有的连接池参数可能不能很好地满足需求,这个时候就需要结合当前使用连接池的原理、具体的连接池监控数据和当前的业务量作一个综合的判断,通过反复的几次调试得到最终的调优参数。
1.6.架构层面
这一类调优包括读写分离、多从库负载均衡、水平和垂直分库分表等方面,一般需要的改动较大,但是频率没有SQL调优高。
2.分布式缓存
缓存可以称的上是性能优化的利器,缓存主要用来存放那些读写比很高、很少变化的数据。
什么情况适合用缓存?考虑以下两种场景:
- 短时间内相同数据重复查询多次且数据更新不频繁,这个时候可以选择先从缓存查询,查询不到再从数据库加载并回设到缓存的方式。此种场景较适合用单机缓存。
- 高并发查询热点数据,后端数据库不堪重负,可以用缓存来扛。
使用缓存需要注意的问题:
2.1.避免缓存失效
把频繁修改的数据放入缓存,容易出现数据写入缓存后,应用还来不及读取缓存,数据就已经失效的情形,徒增系统负担。
2.2.缓存热点数据
缓存使用的内存资源非常宝贵,只能将最新访问的数据缓存起来,而把历史数据清理出缓存,即缓存资源应该留给20%的热点数据。
2.3.数据不一致性
一般会对缓存设置失效时间,超过失效时间,就要从数据库重新加载。
因此应用要忍受一定时间的数据不一致,另一种策略是数据更新时立即更新缓存,不过这也会带来更多的系统开销和事务一致性的问题。
2.4.缓存可用性
业务发展到一定阶段时,缓存会承担大部分数据访问的压力,数据库已经习惯了有缓存的日子,所以当缓存服务器崩溃时,数据库会因为完全不能承受如此大的压力而宕机,进而导致整个网站不可用,这种情况被称作缓存雪崩,发生这种故障,甚至不能简单地重启缓存服务器和数据库服务器来恢复网站访问。
2.5. 缓存预热
缓存中存放的是热点数据,热点数据是缓存系统用LRU对不断访问的数据筛选出来的,这个过程需要较长的时间。
新启动的缓存系统没有任何数据,此时系统的性能和数据库负载都不太好。因此可以选择在启动缓存是就把热点数据预加载好。
2.6.缓存穿透
因为不恰当的业务或恶意攻击,持续高并发地访问某一个不存在的数据,如果缓存不保存该数据,就会有大量的请求压力落在数据库上。
简单的解决方式是把请求的不存在的数据也放进缓存,其value是null。
3.异步化(消息中间件)
针对某些客户端的请求,在服务端可能需要针对这些请求做一些附属的事情,这些事情其实用户并不关心或者用户不需要立即拿到这些事情的处理结果,这种情况就比较适合用异步的方式处理这些事情。
异步化的作用:
- 缩短接口响应时间,使用户的请求快速返回,用户体验更好。
- 避免线程长时间处于运行状态,这样会引起服务线程池的可用线程长时间不够用,进而引起线程池任务队列长度增大,从而阻塞更多请求任务,使得更多请求得不到技术处理。
- 线程长时间处于运行状态,可能还会引起系统Load、CPU使用率、机器整体性能下降等一系列问题,甚至引发雪崩。异步的思路可以在不增加机器数和CPU数的情况下,有效解决这个问题。
比如:使用消息队列(MQ)中间件服务,MQ天生就是异步的。
复杂查询以及一些聚合计算不适合在数据库中做,可以利用搜索引擎来实现,另外搜索引擎还可以帮我们很好的解决跨库、跨数据源检索的场景。
4.Web前段
Web前端指网站业务逻辑之前的部分,包括:
- 浏览器加载
- 网站视图模型
- 图片服务
- CDN服务等
4.1.浏览器访问优化
1减少http请求
HTTP协议是无状态的应用层协议,意味着每次HTTP请求都需要简历通信链路,进行数据传输,而在服务器端,每个HTTP都需要启动独立的线程去处理,这些通信和服务的开销都很昂贵,减少HTTP请求的数目可有效提高访问性能。
减少HTTP请求的主要手段是:
- 合并CSS,以及压缩CSS大小
- 合并JavaScript,以及压缩JS大小
- 合并图片
4.2.CDN加速
CDN(Content Distribute Network,内存分发网络)的本质上仍然是一个缓存,而且将数据缓存在离用户最近的地方,是用户以最快速度获取数据,即所谓网络访问第一跳。
CDN一般缓存的是静态资源,如图片,文件,CSS,Script脚本,静态网页等,但是这些文件访问频率很高,将其缓存在CDN可极大改善网页的打开速度。
5.服务化架构拆分
做服务化最基础的是按业务做服务拆分,避免跨业务间的互相影响,数据和服务同时拆分。同一个业务内部我们还按计算密集型/IO密集型的服务拆分、C端/B端服务拆分、核心/非核心服务拆分、高频服务单独部署等原则做拆分。
6. JVM 优化
6.1 GC 性能衡量指标
「吞吐量」:这里的吞吐量是指应用程序所花费的时间和系统总运行时间的比值。我们可以按照这个公式来计算 GC 的吞吐量:系统总运行时间 = 应用程序耗时 +GC 耗时。如果系统运行了 100 分钟,GC 耗时 1 分钟,则系统吞吐量为 99%。GC 的吞吐量一般不能低于 95%。
「停顿时间」:指垃圾收集器正在运行时,应用程序的暂停时间。对于串行回收器而言,停顿时间可能会比较长;而使用并发回收器,由于垃圾收集器和应用程序交替运行,程序的停顿时间就会变短,但其效率很可能不如独占垃圾收集器,系统的吞吐量也很可能会降低。
「垃圾回收频率」:多久发生一次垃圾回收呢?通常垃圾回收的频率越低越好,增大堆内存空间可以有效降低垃圾回收发生的频率,但同时也意味着堆积的回收对象越多,最终也会增加回收时的停顿时间。所以我们只要适当地增大堆内存空间,保证正常的垃圾回收频率即可。
-XX:+PrintGC 输出 GC 日志 -XX:+PrintGCDetails 输出 GC 的详细日志 -XX:+PrintGCTimeStamps 输出 GC 的时间戳(以基准时间的形式) -XX:+PrintGCDateStamps 输出 GC 的时间戳(以日期的形式,如 2013-05-04T21:53:59.234+0800) -XX:+PrintHeapAtGC 在进行 GC 的前后打印出堆的信息 -Xloggc:../logs/gc.log 日志文件的输出路径
6.2 GC 优化策略
- 「降低 Young GC 频率」
- 由于新生代空间较小,Eden 区很快被填满,会导致频繁 Young GC,可以通过增大新生代空间来降低 Young GC 的频率。
- 通常在虚拟机中,复制对象的成本要远高于扫描成本。
- 如果在堆中存在较多的长期存活的对象,此时增加年轻代空间,反而会增加 Young GC 的时间。
- 如果堆中的短期对象很多,那么扩容新生代,单次 Young GC 时间不会显著增加。因此,单次 Young GC 时间更多「取决于 GC 后存活对象的数量」,而非 Eden 区的大小。
- 「降低 Full GC 的频率」(频繁的 Full GC 会带来上下文切换,增加系统的性能开销)
- 减少创建大对象
- 增大堆内存空间
- 「结合业务场景选择合适的 GC 回收器」
- 对响应时间有要求的场景,可以选择响应速度较快的 GC 回收器,CMS 或 G1
- 对系统吞吐量有要求时,可以选择 Parallel Scavenge
6.3 FullGC 优化
频繁FullGC,会导致服务不可用,应为jvm 垃圾回收器在进行FullGC时,会存在 stop the world 的现象,暂停jvm 中的服务进程进行垃圾回收,会出现服务短暂的不可用,所以需要对fullGC 或 JVM 的参数进行优化,以减小fullGC 的次数。fullGC 优化的方法如下
1.使用top 命令查看 服务器的cpu使用情况
top
2.获取 top 中cpu 占用率最高的进程的pid ,通过 top -H -P pid 获取该进程对应所有线程的使用情况
top -H -p pid
3.通过上面命令得到使用cpu 最高的线程号 threadId ,将线程号通过命令转换为十六进制
printf "%x\n" threadId
4.通过以上命令获取到jvm中对应的 nid , 通过 jstack 查看该 threadId 线程的堆栈信息
jstack -l pid| grep -10 nid
通过以上命令判断该线程 执行任务的内容,从而推断导致cpu飙升的原因。
5.通过 jstat 命令查看 FGC 的频率
jstat -gc pid 3000
需要定位 jvm 内存中的堆栈内容与线程。通过 Visualm 远程监控服务的jvm 性能,jvisualm 使用可参考这篇文章 (https://www.cnblogs.com/zjdxr-up/p/14916455.html),通过 jvisualm 查看服务当前存在的线程和堆内容。
6.4 CMS 垃圾回收器参数配置
-Xmn3072m -Xms8192m -Xmx8192m -XX:NewSize=3072M -XX:MaxNewSize=3072M -XX:-UseAdaptiveSizePlicy -XX:ParallelGCThreads=16 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:MaxTenuringThreshold=15
6.5 G1垃圾回收器参数配置
java -server -Xms2048 -Xmx4096m -Xss512k -XX:MetaspaceSize=128m -XX:MaxNewSize=512m -XX:MaxMetaspaceSize=512m -XX:+PrintGCDataStamps -Xloggc:/opt/app/gc/logs/gc.log -XX:UseG1GC -XX:+PrintGCApplicationConcurrentTime -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M -XX:+HeapDumpOnOutOfMemoryError -XX:+DisableExplicitGC -XX:+PrintGCDetails -javaagent:/opt/app/common/sky-walking/sky-walking-javaagent.jar
7.硬件升级
硬件问题对性能的影响不容忽视。
举一个例子:一个DB集群经常有慢SQL报警,业务排查下来发现SQL都很简单,该做的索引优化也都做了,后来DBA同学帮忙定位到问题是硬件过旧导致,将机械硬盘升级成固态硬盘之后报警立马消失了,效果立竿见影!
8.兜底策略
性能优化做得再好,系统总会存在极限,因此,兜底的策略也是性能优化的一部分,常见的兜底策略有限流、降级和熔断。很多中间件都有这样的功能,我们应当合理使用。还有我们可以通过减少涌入服务器的流量来避免高流量对我们服务器的冲击,比如接入CDN,利用CDN的节点优化和缓存能力能很好的优化我们的性能,当然能使用更高级的边缘计算技术那么在某些场景下会有质的飞跃。
9.其他常见优化项
1.服务层面的性能优化:接口限流,熔断器,降级,池化思想(连接池,http连接池等),日志打印,减少调用链路
2.接口代码层面的性能优化:异步方法,线程池,多线程
3.数据库层面的性能优化:sql优化,索引优化,数据库连接池,redis 缓存的应用
标签: 经验总结