前言

性能优化要求我们关注整体效果,兼顾可靠性,扩展性,以及极端情况的异常场景。
笔记特点:大部分是已掌握的内容,一些知识只会概括提及。

衡量指标※

性能:有限资源,有限时间完成工作。

体现在横坐标为时间,纵坐标多种指标。

加载性能低,会导致搜索排名下降。

性能指标:

吞吐率 和 响应速度

响应速度是串行的优化,通过优化步骤解决问题;
吞吐率是并行的优化,充分利用计算机资源;

侧重于优化响应速度,就能提高吞吐率

这两点,构成了高并发。

类比,十字路口,开车排队到经过红绿灯时间,就是一个请求的响应时间;如果灯信号时间短一点,一些车可能通过快一点,如果灯信号切换过于频繁(限流力度过大),单位时间内通过的车过少,导致后面的车排队时间更长,这个就是吞吐量减少

吞吐率:

QPS,TPS,
HPS: HTTP请求数

细化指标:

  • 平均响应时间:所有耗时的平均,但不能体现出方差,抖动大小,
  • 响应时间百分位数,TP值:理解为一种阈值,像合格率(水位),反应出应用接口的整体响应情况。指数目标是 干掉严重影响系统的长尾请求。

例若有100个请求, 每一个请求的响应时间分别是 1-100 平均分布 平均响应时间: 1-100 的平均值,即 50.5 95% percentile : 按从小到大排序,累计第95百分位,也就是 95 (即样本里95% 的数据都不高于这个值)

  • 并发量:同时进入的请求数量,指在同一个时间点,同时请求服务的客户数量。

注意:吞吐率和并发数是两个完全独立的概念。拿银行柜台来举个例子,并发数指同时有多少人往银行柜台涌来。吞吐率则指银行柜台在一段时间内可以服务多少个人。

  • 秒开率:APP启动速度,页面加载速度。

    使用CDN加载;用mokey脚本检查白屏,用动画或加载骨架,减少白屏情况;HTTP2的缓存推送机制提前推送JS,CSS;

  • 正确性:接口请求无BUG。

    比如,一个事故,测出的并发量特别高,这是由于项目使用了熔断,压测过程触发熔断,又因为没有对接口正确性判断,造成低级错误的报告。

性能优化:

基准测试,木桶理论,Amdahl定律

基准测试:测试最佳性能。

比如,要排除掉项目启动,进行缓存预热,消除JIT编译器影响。

木桶理论:性能瓶颈取决于最慢的组件。

比如,DB的I/O落盘。这是首先要解决的问题。

性能优化注意点:

一是依据数字不是猜

二是个体数据不足信

三不要过早优化和过度优化

四保持良好编码习惯

7类技术优化手段

复用优化

计算优化

结果集优化

资源冲突优化

算法优化

高效实现

JVM优化


其他:

数据库优化

操作系统优化

架构优化

协议优化

等等

展开:

复用优化

1、代码复用:抽成公共方法,抽成公共模块;

2、数据复用:首先想到缓冲和缓存。

缓冲(Buffer):对数据暂存,然后批量传输或写入;
多使用顺序方式,缓解不同设备频繁,缓慢地随机写。
缓存(Cache):对已读数据的复用;
缓存在相对高速区域,针对读操作。

3、池化复用:对象创建和销毁成本高。

线程池,连接池,把对象预热存储,方便所有后续使用。

4、对象复用:clone思想的原型模式,共享技术的享元模式

计算优化

1、并行优化:想加快任务执行,最快最优就是让它并行执行。

硬件上是:CPU等设备多核,多机。

模式上是:

多机:采用负载均衡,将流量或大计算拆分多个部分,同时处理,比如Hadoop,用MapReduce将任务打散,多机并行计算;

多进程:nginx的NIO进程模型,master进程统一管理worker进程,由worker进程真正代理,利用了CPU多核。

redis的主进程读写,子进程RDB快照写时复制技术;

多线程:Netty的Reactor的NIO模型。boss线程接收请求,worker线程真正计算。

多协程:更轻量,比如GO语言

2、同步变异步:涉及编程模型的改变

同步简单,但对突发的,时间段倾斜的流量,问题大,容易失败。

3、异步方式:请求横向扩容,缓解瞬时压力,使流量平滑。

4、惰性加载:单例模式,代理模式,分页加载

加载图片文件,可以先加载占位符,再通过后台线程慢慢加载所需要资源。

结果集优化

让体积更小,传输效率和解析效率更高

1、protobuf二进制优先于JSON,优先于XML

2、Nginx,开启HTTP的GZIP压缩,保持数据紧凑

3、批量处理的方式:对于时效性要求不高,对业务能力要求高的业务,要减少网络连接的交互,先存缓冲区,再批量交付。

4、数据结构优化:对于要二次使用的结果集,会存入缓存,可以根据使用场景,存储索引,bitmap位图,二进制数,B+树,跳表,压缩列表来加快读速度。大对象优化为只保留有用属性粒度

资源冲突优化

涉及共享资源,如:单机HashMap,mysql的数据行,单资源Redis的某key的setnx,多个资源协调的事务和分布式事务。

就要选择合适的锁。

锁应用各种地方,mysql的行锁表锁,java各种锁,底层cpu锁,jvm锁,操作系统内部锁

按照锁级别,分为乐观锁和悲观锁,乐观锁效率更高

按照锁类型,分为公平锁和非公平锁,任务调度上有差别

锁越重,性能消耗越大,

实现无锁的机制,对性能提升巨大的。

算法优化

提高复杂业务的性能,一般采用空间换时间,加快处理速度。

算法是代码调优,考验开发者的编程技巧和API掌握程度。

常用的递归,二分,快排,动态规划,贪心等。

高效实现

1、技术选型,尽量选择设计良好,性能优越的组件;

Netty作为非阻塞web容器,

语法分析器javacc,效率比正则表达式更好

2、维护性,采用适配器模式,以便在测压找到瓶颈点,用更高性能的组件进行替换。

JVM优化

配置参数,一定程度提高JAVA程序的性能。

如果不当,可能OOM。

1、G1垃圾回收器,内存高效回收,CMS已经被java14移除

2、堆大小调整,minorGC和fullGC

3、大对象阈值设置

4、代码层面优化

5、监控线上堆栈信息

6、开启补齐,避免伪共享


分析哪些资源,容易成为瓶颈

1、系统组件之间的速度不均衡;

CPU,内存,IO组件,容易成为瓶颈。

CPU:

top命令,CPU性能

uptime命令,看负载,评估任务排队情况

vmstat,CPU繁忙程度,上下文切换程度。

缓存行伪共享问题

内存:

top命令,看进程实际占用内存

free命令,看剩余内存

大内存时代,采用HugePage将4kb快表页增大2MB,但竞争加剧会增加性能。

JVM预分配好内存,加快运行速度

IO设备:

IO设备是包括所有外围设备

缓冲区解决差异的唯一工具,但断电容易丢失。

iostat命令工具,查看IO性能。

网络:

iotop命令,看网络流量最高的进程

netstat命令,看机器网络连接汇总

零拷贝技术,kafka,Nginx

优化IO+网络

kafka操作磁盘 吞吐量高的原因?

磁盘慢是慢在寻道操作,磁盘顺序写和随机写的速度差达到6千倍,kafka采用顺序写。

性能工具

nmon获取系统性能数据工具

jvisualvm 获取JVM性能数据

CPU分析:代码执行时长和热度

内存快照分析:内存泄漏

线程分析:死锁情况

HotSpot VM 的JMC,获取java性能详细数据

web容器,线程,内存,锁,socket IO,方法,垃圾回收

JIT,TLAB

arthas 获取单个请求的调用链耗时

trace命令获取调用链

wrk HTTP压测工具,获取web接口性能工具

基准测试

为了测量某一段代码的具体执行情况。

  • 最简单,编写统计执行代码时间。

​ 这不一定正确,代码块频繁时,JVM会有JIT编译和内联优化,要想得到稳定结果,需要执行上万次循环预热;

​ 有大量埋点,统计指标单一

  • JMH基准测试工具。JDK12内置,低版本需要引入。通过注解形式单独配置,OptionSBuilder全局配置

    通过开启多个进程多个线程的子任务完成预热,然后真正的迭代,最终结果合并

    精度高,纳秒级,多指标,可图形化结果

缓冲区

保持各自节奏,顺序不打乱

结合批量处理,减少网络IO操作

优化用户体验,音视频的提前缓冲

  • 文件IO流 是装饰器模式

缓冲区读取一块一块的部分数据

  • 日志缓冲

Logback异步采样,写入缓冲区达到阈值,才持久化。

  • kafka发送缓冲

    生产者发送前有个缓冲区,如果生产者断电,消息丢失;

    解决:

    缓冲区设置非常小,退化到单条,影响网络性能

    发送前持久化消息日志,发送后处理完回调再记录一条日志,扫描对比。

  • Mysql的innodb_buffer调整大小,减少换页;

  • StringBuilder 和StringBuffer,字符缓冲区,提供拼接字符串的性能

  • 磁盘,网络IO,缓冲区提高信息流转效率,可以flush强制刷新数据。半连接队列,全连接队列

  • ID生成器,缓冲一部分ID段,避免频繁耗时的交互。

缓冲区饱和策略:丢弃,异常,等待

缓冲区数据丢失解决:

优雅关闭,没完全解决;

预写日志,故障后重启,根据日志恢复数据。

缓存

  • 让页面秒开
  • 减少数据库压力
  • 处理冷热数据

堆内缓存

大多数对内缓存,会将对象的引用设置为弱引用或软引用,当缓存非常频繁,而且数据量大,如果发生GC回收,缓存空间被释放,又瞬速沾满,从而再次垃圾回收。

设置缓存小一些,减轻JVM负担。

进一步加速

linux文件缓存:

预读算法,从磁盘智能加载到缓存

缓存算法影响命中率和性能,

目前最好的是Caffeine(咖啡呢)使用的W-TinyLFU算法,性能非常高

例子

HTTP 304 Not Modified,请求头if-Modified-Since判断客户端缓存是否最新

CDN,用户最近最快节点,读取静态文件内容,贵

双写缓存不一致问题:延迟双删,binlog-MQ

数据冷热分离

数据双写

冷热库CRUD在统一事务

由于热库和冷库类型不同,事务大概率是分布式,缺陷:难以改造。

写入MQ分发

通过MQ发布订阅,分发到冷热库,逻辑非常清晰

binlog订阅

canal组件获取数据,结合MQ,同步到其他数据源

池化技术

减少创建对象的成本,资源反复利用。

公共对象池化包 Commons Pool 2,Jedis连接池使用到。

maxTotal:对象上限

maxIdle :最大空闲数

maxIdle: 最小空闲数(核心对象数)

maxWaitMillis:资源用尽时,最大等待直到对象空闲时间,-1表示永远不超时。超时快速失败

minEvictableIdleTimeMillis 资源池中资源最小空闲时间(单位为毫秒),达到此值后空闲资源将被移除,默认30min

HikariCP连接池为什么快? (Hi卡乐CP)

1,有效减少数据库连接创建,消耗的资源消耗;

2,FastList代替ArrayList,通过初始化的默认值,减少越界检查。通常情况下,同一个Connection创建了多个Statement时,后打开的Statement会先关闭,FastList从数组的尾部开始遍历删除,更为高效

3,优化并精简字节码,使用javassist,减少动态代理的性能消耗,比如invokestatic指令代替invokevirtual,指令更便于JVM去做更底层的优化。这个优化甚至把栈帧中的栈深度从5降到了4,减少了push和pop指令

4,无锁的ConcurrentBag,减少并发场景下的锁竞争,减少伪共享

​ ConcurrentBag,有三个重要的成员变量:

  • ThreadLocal 缓存,加快本地连接获取速度

  • CopyOnWriteArrayList,sharedList共享写时拷贝List

  • SynchronousQueue,无存储的等待队列

    ConcurrentBag 的优化思路就是CAS尝试从ThreadLocal中找空闲连接来避免锁竞争,如果没有可用元素则再次从共享的CopyOnWriteArrayList中获取,还找不到就放入SynchronousQueue等着。

    通过将连接本地存储化来减少竞争,又根据连接池读多写少的特性用 CopyOnWriteArrayList 来实现 sharedList 。

    这里还有个中途窃取的概念,其实没什么花头,就是充分利用连接。

    根传统锁模型不同,它用了标记模型,抢占资源只是CAS操作进行”标记状态“ STATE_IN_USE

    中途窃取无非就是本来属于某个线程的本地连接,当它归还连接的时,恰巧有另一个线程从 sharedList 遍历找到这个连接,这时候连接的状态是 STATE_NOT_IN_USE,那么这个连接就会被另一个线程也保存到 ThreadLocal 中了。 否则是没有窃取,就唤醒等待队列的线程。

冷门技巧优化:根据业务的类型设置多个连接池,减少连接资源争抢。

  • 快速响应时间,把数据快速返回给用户
  • 可以慢慢执行,耗时比较长,对时效性要求不高

池一般存储的是执行对象,缓存一般存储的是数据对象

结果缓存池:保存某个执行步骤的结果,使得下次无需从头执行;

比如:热点文章页面静态化,无需再从头渲染。

大对象复用与聚焦

substring方法,JDK 8 是new复制出部分字符串,在JDK 6直接获取源字符串,

借鉴意义:在大对象中获取部分信息,根据业务情况,决定是否有原对象的引用关系。

扩容,StringBuilder,StringBuffer,HashMap,ArrayList等:扩容操作需要重新组织数据,注意线程安全问题;

优化:

查询用户的字段,Redis存储的用户的JSON字符串数据打散, 改为Hash对象结构,方便hget

bitmap

java Bitset

potobuf

数据结构优化:对于要二次使用的结果集,会存入缓存,可以根据使用场景,存储索引,bitmap位图,二进制数,B+树,跳表,压缩列表来加快读速度。大对象优化为只保留有用属性粒度

设计模式

大多数设计模式并不能增加性能,下面列举性能相关的设计模式:

代理模式,单例模式,享元模式,原型模式

代理模式

通过一个代理类,控制一个对象的访问

jdk面向接口,CGLib字节码增强,新版本性能差不多。

Spring AOP如果引入CGLIB,就是用CGLIB对java字节码增强,完成一个切面编程。比如权限,日志等切面。

虽然方便代码,但动态代理模式的处理更慢。

可以通过arthas分析慢逻辑来优化。

单例模式

scope注解指定bean的作用域,标识多例还是单例

默认是单例,线程安全。

单例有懒汉和饿汉加载方式:

饿汉会造成空间资源的浪费。

懒汉需要DCL或者静态内部类创建实例。

享元模式

通过共享技术,最大限度复用对象一般使用唯一的标识码进行判断,然后返回对应的对象。

比如,池化对象。

何时使用: 1、系统中有大量对象。 2、这些对象消耗大量内存。 3、这些对象的状态大部分可以外部化。 4、这些对象可以按照内蕴状态分为很多组,当把外蕴对象从对象中剔除出来时,每一组对象都可以用一个对象来代替。 5、系统不依赖于这些对象身份,这些对象是不可分辨的。

很多设计都使用享元模式,只是语境下差别。

原型模式

首先缓存一个实例,然后通过这个实例克隆出新对象,

一般可配合工厂模式。

必须实现 Cloneable 接口,

由于 Object 本身没有实现 Cloneable 接口,所以不重写 clone 方法并且进行调用的话会发生异常。

clone如果只拷贝当前对象,实现的就是浅拷贝。

多线程

IO密集型,计算密集型

线程池

写时复制

StringBuilder 对 StringBuffer

HashMap对CurrentHashMap

ArrayList对CopyOnWriteList

FastThreadLocal

首先ThreadLocalMap没有链表红黑树,使用了开放地址法。

FastThreadLocal 底层是数组,定位数据直接根据数组下标 index 获取;

而且,写了9个多余long类型,对伪共享问题优化

Foin/Join

CompletableFuture

不正确使用问题:

线程池不正确,造成资源分配不可控

IO密集,线程池太小,造成请求频繁失败

线程池用等待线程释放饱和策略,造成业务阻塞

SimpleDateFormat的时间错乱: 多个线程之间共享变量calendar,并修改calendar。因此在多线程环境下,当多个线程同时使用相同的SimpleDateFormat对象(如static修饰)的话,如调用format方法时,多个线程会同时调用calender.setTime方法,导致time被别的线程修改,因此线程是不安全的。

解决方案:

  1、将SimpleDateFormat定义成局部变量

  2、 加一把线程同步锁:synchronized(lock)

  3、使用ThreadLocal,每个线程都拥有自己的SimpleDateFormat对象副本。

锁分层升级

乐观锁

悲观锁

Redis分布式锁,lua脚本,看门狗续期锁

无锁

NIO模型

BIO到NIO,再到AIO

Netty Rector模型

select/epoll

响应式编程:WebFlux底层使用Netty,一种面向数据流和变化传播,表达为生产者消费者之间进行流量控制(背压问题),通过全面操作异步化,来减少无效的等待和资源消耗。

Spring Gateway 的RouteLocator

背压是反应流中的一个重要概念,可以理解为,生产者可以感受到消费者反馈的消费压力,并根据压力进行动态调整生产速率。

Springboot 性能优化

1,首先要暴露数据,比如缓存命中率,连接池参数,业务数据,然后开启监控。

可以用普罗米修斯暴露数据,配合Grafana数据展示。

可以看火焰图找到性能瓶颈;

2,优化请求链路

HTTP优化:使用HTTP2

  • CDN,Cache-control浏览器缓存

  • 减少单页面请求的域名数,4个之内。因为每次请求域名,查DNS找IP,再调用服务器。若没有本地DNS缓存,产生一定的调用链路开销。

  • 开启GZIP,减少传输效率,Nginx或者Okhttp
  • 对JS,CSS等资源压缩,应用在前后端分离模式。
  • 使用keep alive 长连接,减少连接创建和关闭消耗

JVM优化

- G1
- 堆大小
- 堆比例
- 进入老年代阈值调整
- -XX:+AlwaysPreTouch 在服务启动的时候真实的分配物理内存给JVM

访问数据库慢优化

​ - 本地缓存

分布式

​ - skywalking 全链路追踪

Controller

  • 保持结果集精简 JSON

Service

- 单例
- 设计模式组织代码
- 分布式事务,保证最终一致性:

Dao

-  使用合理缓存,避免缓存穿透
-  注意sql在分库分表环境执行的性能

使用HikariCP连接池

常用代码优化法则

  • 多使用局部变量:栈分配,可避免堆上分配,堆是垃圾回收主要区域,也避免过多对象造成GC压力。
  • 减少变量的作用范围:在if分支内,减少创建。
  • 访问静态变量用类名,不要用this,否则导致多了一步找类的寻址指令。
  • StringBuilder或StringBuffer拼接字符串,减少字符串创建
  • 重写HashCode,要重写equals
  • HashMap等集合初始化,指定初始值
  • 不要在多线程下使用同一个Random,否则seed会在并发访问发生竞争,建议ThreadLocalRandom,配置jvm参数使用速度快的urandom随机生成器
  • 自增推荐LongAddr,这是synchronized和volatile组合;AtomicLong是CAS替换,导致自旋。
  • 不要捕捉RuntimeException,要提前判断
  • 能复用的SQL合理PreparedStatement预编译,能对SQL执行提速,对能复用的SQL语句放入缓存执行计划中,下次执行跳过解析动作,所谓预编译语句就是将这类语句的值用占位符替代,可以视为将sql语句模板或者说参数化,防止SQL注入
  • 日志打印优化,使用占位符,避免多余的字符串拼接操作;减少日志打印,减少占用IO资源
  • 减少事务作用范围
  • 位移操作代替乘除法
  • 不要打印大集合
  • 少用反射,它通过解析字节码实现,性能不理想,若使用,可加缓存优化
  • 正则表达式预先编译,初始化一次即可,加快速度;正则解析很慢,可改为状态机
  • invokestatic指令调用静态绑定方法 ,代替invokevirtual调用实例动态绑定方法

JIT编译

inline –方法内联:对于短小方法体,采用直接追加代码的方式。

分层编译层次:

1字节码解释执行

2执行不带profiling的C1代码

3执行仅带方法调用以及循环次数profiling的C1代码

4执行带所有profiling的C1代码

5执行C2代码

profiling是指运行时程序执行状态的数据:循环调用次数,方法调用次数,分支跳转次数,类型转换次数。

逃逸分析:

对象,除了基本类型,一定是在堆上分配吗?错。

经过逃逸分析,可分析出对象的作用范围,来决定是否讲对象分配到堆上。

成员变量可以分配到栈上,方法返回对象可以分配到堆上。

  1. 对象是否被存入堆中(静态字段或堆中对象的实例字段)
  2. 对象是否被传入未知代码中(方法的调用者和参数)

总体分3点:

对象可能分配在栈上

JVM通过逃逸分析,分析出新对象的使用范围,就可能将对象在栈上进行分配。栈分配可以快速地在栈帧上创建和销毁对象,不用再将对象分配到堆空间,可以有效地减少 JVM 垃圾回收的压力。

分离对象或标量替换

当JVM通过逃逸分析,确定要将对象分配到栈上时,即时编译可以将对象打散,将对象替换为一个个很小的局部变量,我们将这个打散的过程叫做标量替换。将对象替换为一个个局部变量后,就可以非常方便的在栈上进行分配了。

同步锁消除

如果JVM通过逃逸分析,发现一个对象只能从一个线程被访问到,则访问这个对象时,可以不加同步锁。如果程序中使用了synchronized锁,则JVM会将synchronized锁消除。

这里,需要注意的是:这种情况针对的是synchronized锁,而对于Lock锁,则JVM并不能消除。

什么是JVM预热

JVM预热 : JVM Warm Up

一旦类加载完成,所有重要的类(在进程启动时使用)都会被推送到JVM缓存(本机代码)中,这使得它们在运行时可以更快地访问。其他类是根据每个请求加载的。
对Java Web应用程序的第一个请求通常比进程的生命周期中的平均响应时间慢得多。这个预热期通常可以归因于延迟类加载和及时编译。
记住,对于低延迟应用程序,我们需要预先缓存所有类,以便在运行时访问时立即可用。
这种调优JVM的过程称为预热。

基于采样的热点探测
周期采样,检测各线程栈顶方法,经常出现的方法即为热点方法。好处是简单高效,缺点是不精确,容易受线程运行状态的影响。

基于计数的热点探测
(包括方法调用计数器和回边计数器)每个方法建立计数器,用来统计调用次数。如果该方法执行次数超过阈值,则该方法被认定为热点方法。好处是足够精确。缺点是空间损耗大,且实现较难。

另外,可以通过如XX:CompileThreshold等参数来修改阈值,不过,没有绝对把握,还是不要动为好。

TLAB的全称是Thread Local Allocation Buffer,即线程本地分配缓存区,这是一个线程专用的内存分配区域。

是对Eden区域继续进行划分,JVM为每个线程分配了一个私有缓存区域,它包含在Eden空间内。

JIT指导代码优化

为什么我们在刚写代码的时候,总是被建议不要写很大的方法体?方法内联的JIT优化策略就是其中一个重要的原因。(还有GC友好等原因)

JVM内的每一次方法调用,都是栈帧在内存中出栈入栈的过程,方法多了性能损耗自然大,所以要进行方法内联,即把方法执行逻辑直接复制到调用方内部,避免方法调用。

但是,方法内联是有方法大小限制的,超过了一定大小的方法,没法做内联优化。所以,平常应该注意,尽量避免写很大很冗长的方法。

方法内联虽然只是一种简单优化,但是,是后续其他优化的基石。

而JVM的分层优化涉及的点非常多[1]:

局部优化:关注局部数据流分析,数组越界检查消除;寄存器优化,优化跳转、循环、异常处理等;代码简化,如公共表达式提取等等等。

控制流优化:专注于代码重排序、循环缩减、循环展开、异常定位优化等等等。

全局优化:主要关注冗余消除,如方法调用、锁;逃逸分析;GC和内存分配优化等等等。

综合思考性能优化

  • 业务需求层面

报表业务,查询缓慢,有时候造成内存溢出

经过分析,查询时间跨度范围太大造成,缩小到1个月,速度快多了。

  • 硬件层面

有一个定时任务,每次将CPU用满,由于系统架构硬伤,无法横向扩容,经过技术评估,如果改成按照数据分片执行的模式,则需要消耗长达1个月工时。

那么,这个时候,增加硬件。

尽可能在效果,手段,工时权衡。

如何找到优化目标?

利用率:一般是瞬时值,属于采样范围,用来判断有没有峰值

饱和度:一般指资源是否被合理利用,能否分担更多工作

错误信息:严重的特别关注

联想信息:靠经验去猜,然后用工具验证。

基本解决方式

CPU问题

top-Hp命令获取CPU占用最高线程,针对性优化

棘手问题经验:线程阻塞在ForkJoin线程池上

代码在等待I/O时,采用并行流(parallelStream)处理,但Java默认所有的方式是所有并行流的地方,共用同一套线程池。这个线程池的并行度只有CPU的两倍。并发量增加,造成任务排队,产生积压。

内存问题

通常是OOM。如果内存资源紧张,CPU利用率低,考虑时间换空间。

一般在高并发应用,会把Swap关掉,因为它很容易引起卡顿。

IO问题

一般情况,磁盘IO小,网络IO大

通过调整日志级别,清理无用日志代码,缓解磁盘IO的压力

通过RPC调用一个远程服务,期望使用NIO减少无效的等待,或者并行方式加快信息读取

类似ES数据库,写入造成繁重的磁盘IO,可以增加硬件的配置,换成SSD固态磁盘,或增加新的磁盘;调整segment块大小,translog的刷新速度等。

对于网络IO,

springboot和OkHttp开启gzip压缩,

结果集合并

使用批量的方式

netstat命令,获取进程网络状态

中间层

加入中间层,缓冲/缓冲,池化,牺牲信息的时效性,加快信息处理的处理速度。

资源同步

切分冲突资源的粒度

减少资源锁定的时间

将读写分开

组织优化

重构–设计模式

异步化

资源利用不足

资源不能合理利用,就是浪费。

轮转:一定压力下系统的最优状态

IO密集:并行

合理利用空闲空间:高位要加大容量

PDCA 循环方法论

管理性能优化的过程

P计划阶段:收集指标,找性能问题,设定改进目标,制定改进措施

D执行阶段:执行计划

C检查阶段:检查优化效果,及时发现改进过程的经验和问题

A处理阶段:将成熟经验推广,有点及面覆盖,为负面形成解决方法,将错误方法形成经验。