Java性能调优实战
2022-06-14 23:04:34 2 举报
AI智能生成
Java性能调优实战
作者其他创作
大纲/内容
参考资料
性能工具
https://www.processon.com/view/5dd5fc06e4b06375041a20eb?fromnew=1#map
性能原则
首先,性能优化一定不能盲目,一定是问题导向的
脱离了问题,盲目地提早优化会增加系统的复杂度,浪费开发人员的时间,
也因为某些优化可能会对业务上有些折中的考虑,所以也会损伤业务
也因为某些优化可能会对业务上有些折中的考虑,所以也会损伤业务
其次,性能优化也遵循“八二原则”
即你可以用 20% 的精力解决 80% 的性能问题。所以我们在优化过程中一定要抓住主要矛盾,优先优化主要的性能瓶颈点
再次,性能优化也要有数据支撑
在优化过程中,你要时刻了解你的优化让响应时间减少了多少,提升了多少的吞吐量
最后,性能优化的过程是持续的
性能评估
响应时间
客户端响应时间:对于普通的 Web、App 客户端来说,消耗时间是可以忽略不计的,但
如果你的客户端嵌入了大量的逻辑处理,消耗的时间就有可能变长,从而成为系统的瓶颈。
如果你的客户端嵌入了大量的逻辑处理,消耗的时间就有可能变长,从而成为系统的瓶颈。
网络响应时间:这是网络传输时,网络硬件需要对传输的请求进行解析等操作所消耗的时间;
服务端响应时间:服务端包括 Nginx 分发的请求所消耗的时间以及服务端程序执行所消耗的时间
数据库响应时间:数据库操作所消耗的时间,往往是整个请求链中最耗时的;
吞吐量
磁盘吞吐量
一种是 IOPS(Input/Output Per Second),即每秒的输入输出量(或读写次数),这种
是指单位时间内系统能处理的 I/O 请求数量,I/O 请求通常为读或写数据操作请求,
是指单位时间内系统能处理的 I/O 请求数量,I/O 请求通常为读或写数据操作请求,
关注的是随机读写性能。适应于随机读写频繁的应用,
如小文件存储(图片)、OLTP 数据库、邮件服务器。
如小文件存储(图片)、OLTP 数据库、邮件服务器。
另一种是数据吞吐量,这种是指单位时间内可以成功传输的数据量。
对于大量顺序读写频繁的应用,传输大量连续数据,例如,电视台的视频编辑、视频点播 VOD(Video On
Demand),数据吞吐量则是关键衡量指标
Demand),数据吞吐量则是关键衡量指标
网络吞吐量
这个是指网络传输时没有帧丢失的情况下,设备能够接受的最大数据速率。
网络吞吐量不仅仅跟带宽有关系,还跟 CPU 的处理能力、网卡、防火墙、外部接口
以及 I/O 等紧密关联。而吞吐量的大小主要由网卡的处理能力、内部程序算法以及带宽大小决定
以及 I/O 等紧密关联。而吞吐量的大小主要由网卡的处理能力、内部程序算法以及带宽大小决定
资源分配使用率
CPU 占用率
内存使用率
磁盘 I/O
网络 I/O
负载承受能力
性能调优策略
问题发现
测试
测试类型
1. 微基准性能测试
可以精准定位到某个模块或者某个方法的性能问题,特别适合做一个功能模
块或者一个方法在不同实现方式下的性能对比
块或者一个方法在不同实现方式下的性能对比
2. 宏基准性能测试
测试环境
需要模拟线上的真实环境
测试场景
相关联系统需要平稳运行,不干扰被测对象
测试目标
通过吞吐量以及响应时间来衡量系统是否达标,并观测服务器的CPU/内存/IO使用率的变化
测试目的
找到系统的拐点
注意事项
1. 被测对象的预热
2. 性能测试结果不稳定
3. 多 JVM 情况下的影响
监控
日志监控
应用日志
系统日志
分析
输出测试报告
测试结果需要包含测试接口的平均、最大和最小吞吐量,响应时间,服务器的 CPU、
内存、I/O、网络 IO 使用率,JVM 的 GC 频率等。
内存、I/O、网络 IO 使用率,JVM 的 GC 频率等。
分析测试结果
第一步:查看系统的 CPU、内存、I/O、网络的使用率是否存在异常
第二步:通过异常日志分析,找到性能瓶颈
第三部:从JVM层分析垃圾回收频率,内存分配,分析日志找到性能瓶颈
调优
业务层优化
1. 优化代码
2. 优化设计
3. 优化算法
4. 时间换空间
5. 空间换时间
JVM优化
系统层优化
1. 参数调优
JVM参数优化
Web容器参数优化
中间件参数优化
性能兜底
限流
熔断
降级
性能优化
单机性能调优
硬件资源调优
操作系统调优
JVM调优
应用层调优
Java性能优化
String性能分析
String基础知识
JDK中String设计的演进
String对象创建赋值
String str = “abc”
String str = new String("abc")
第一步
第二步
public class Location{
private String city;
private String region;
}
private String city;
private String region;
}
String city = getCity().intern()
自动复用常量池,可以减少内存占用。
字符串分割优化
String.split()
使用了正则表达式实现了其强大的分割功能,而正则表达式的性能是非常不稳定的,
使用不恰当会引起回溯问题,很可能导致 CPU 居高不下。
使用不恰当会引起回溯问题,很可能导致 CPU 居高不下。
String.indexOf()
性能比较高
字符串拼接调优
StringBuilder
StringBuffer
"aaa"+"bbb'
正则表达式调优
如果使用正则表达式能使你的代码简洁方便,那么在做好性能排查的前提下,可以去使用
如果不能,那么正则表达式能不用就不用,以此避免造成更多的性能问题
Java集合性能分析
ArrayList&LinkedList高性能原则
集合类中的关系
ArrayList
ArrayList 实现了 List 接口,继承了 AbstractList 抽象类,底层是数组实现的,并且实现了自增扩容数组大小
ArrayList 还实现了 Cloneable 接口和 Serializable 接口,所以他可以实现克隆和序列化。
类中transient作用
防止对象被序列化
ArrayList本身是可以被序列化的,是通过writeObject和readObject接口来实现
LinkedList
类中transient作用
防止对象被序列化
LinkedList本身是可以被序列化的,是通过writeObject和readObject接口来实现
Stream高性能原则
在单核 CPU 服务器配置环境中,也是常规迭代方式更有优势
在大数据循环迭代中,如果服务器是多核 CPU 的情况下,Stream 的并行迭代优势明显
Map高性能原则
基础知识
基本数据结构
数组
链表
哈希表
Hash冲突解决方案
开放定址法
再哈希函数法
链地址法
HashMap属性
加载因子(loadFactor)
边界值(threshold)
数据存储
JDK8
数组
链表或红黑树
Hash扩容
JDK7
JDK8
I/O性能分析
BIO(阻塞IO)
读写操作会导致线程阻塞,引入了线程切换开销
采用多线程解决同时处理多个I/O请求,引入了多线程调度和内存开销
频繁的内核态与用户态切换,引入性能开销
编程模型简单
新IO模型
图解
使用缓冲区优化读写流操作
NIO 与传统 I/O 不同,它是基于块(Block)的,它以块为基本单位处理数据
在 NIO中,最为重要的两个组件是缓冲区(Buffer)和通道(Channel)
Buffer 是一块连续的内存块,是 NIO 读写数据的中转地。
Channel 表示缓冲数据的源头或者目的地,它用于读取缓冲或者写入数据,是访问缓冲的接口
使用 DirectBuffer 减少内存复制
提供了一个可以直接访问物理内存的类DirectBuffer
普通的 Buffer 分配的是 JVM 堆内存,而 DirectBuffer 是直接分配物理内存
必须先从用户空间复制到内核空间,再复制到输出设备,
而 DirectBuffer 则是直接将步骤简化为从内核空间复制到外部设备,减少了数据拷贝
而 DirectBuffer 则是直接将步骤简化为从内核空间复制到外部设备,减少了数据拷贝
避免阻塞,优化 I/O 操作
阻塞问题,就是传统 I/O 最大的弊端。NIO 发布后,通道和多路复用器这两个基本组件实现了 NIO 的非阻塞
以前的IO实现中,在应用程序调用操作系统 I/O 接口时,是由 CPU 完成分配,这种方式最大的问题是“发生大量 I/O 请求时,非常消耗 CPU“
之后,操作系统引入了 DMA(直接存储器存储),内核空间与磁盘之间的存取完全由 DMA 负责,
Channel 有自己的处理器,可以完成内核空间和磁盘之间的
I/O 操作。在 NIO 中,我们读取和写入数据都要通过 Channel,由于 Channel 是双向
的,所以读、写可以同时进行。
I/O 操作。在 NIO 中,我们读取和写入数据都要通过 Channel,由于 Channel 是双向
的,所以读、写可以同时进行。
多路复用器(Selector)
Selector是Java NIO编程的基础。用于检查一个或多个 NIO Channel 的状态是否处于可读、可写
NIO分类
非阻塞式 I/O
频繁的内核态与用户态切换,引入性能开销
用户线程不会发生阻塞
采用多线程解决同时处理多个I/O请求,引入了多线程调度和内存开销
I/O 复用
用户线程不会发生阻塞
采用多线程解决同时处理多个I/O请求,降低多线程的开销
信号驱动式 I/O
异步 I/O
多线程优化
Java并发调优-Synchronized与Lock原理解析
设计模式调优
单例模式调优
饿汉模式
线程安全
初始化阶段就要分配内存,存在一定程度的内存浪费
懒汉模式
存在线程安全问题
懒汉模式 + synchronized 同步锁
double check
通过内部类实现
Builder模式
枚举模式
原型模式
通过clone来创建对象,效率比new 高
注意事项
覆盖clone方法
深拷贝,浅拷贝
享元模式
定义
运用共享技术有效地最大限度地复用细粒度对象的一种模式
该模式中,以对象的信息状态划分,可以分为内部数据和外部数据。内部数据是对象可以共享出来的信息,
这些信息不会随着系统的运行而改变;外部数据则是在不同运行时被标记了不同的值
这些信息不会随着系统的运行而改变;外部数据则是在不同运行时被标记了不同的值
适用场景
线程池就是享元模式的一种实现
String中共享变量池的形式
共享线程变量
将线程的共享变量放到ThreadLocal中
Thread-Per-Message 设计模式
Worker-Thread 设计模式
中间件调优
消息队列
作用
异步处理
异步处理可以简化业务流程中的步骤,提升系统性能
解耦合
解耦合可以将秒杀系统和数据系统解耦开,这样两个系统的任何变更都不会影响到另一个系统
削峰填谷
削峰填谷可以削去到达秒杀系统的峰值流量,让业务逻辑的处理更加缓和
引入问题的解决方案
如何保证消息不丢失
网络层性能优化
TCP协议优化
基础知识
TCP三次握手
TCP四次握手
TCP状态图
TCP三次握手优化
客服端的优化
优化客户端重发SYN的次数
重试的次数由tcp_syn_retries参数控制
设置tcp_syn_retries策略
比如内网中通讯时,就可以适当调低重试次数,尽快把错误暴露给应用程序
服务器端的优化
SYN_RCV状态下需要维护一个队列保存半连接的socket
tcp_max_syn_backlog
tcp_max_syn_backlog
根据实际情况调整 tcp_max_syn_backlog参数
syncookies功能就可以在不使用SYN队列的情况下成功建立连接
tcp_syncookies开启,设置1,在队列满的情况下应对
syncookies应对 syn攻击
服务端重发SYN+ACN的次数
调整tcp_synack_retries参数
服务器维护一个accept队列用来保存 established状态的socket
tcp_abort_on_overflow设置为0可以提供tcp连接建立的成功率
TCP实现层面
TFO技术
第一阶段为初次建立连接,这时走正常的三次握手
在客户端的SYN报文会明确地告诉服务器它想使用TFO功能
这样服务器会把客户端IP地址用只有自己知道的密钥加密(比如AES加密算法),作为Cookie携带在返回的SYN+ACK报文中,客户端收到后会将Cookie缓存在本地。
Linux如何开启
net.ipv4.tcp_fastopen = 3
TCP四次握手优化
序列化性能选择原则
序列化的工具
FastJson
Kryo
Protobuf
支持Java、Python、Golang、C++、Javascript等多种⾯向对象编程语言
编码速度快
编码后字符体积小,能节省网络带宽
Hessian
Java 序列化
Java 序列化的缺陷
1. 无法跨语言
2. 易被攻击
3. 序列化后的流太大
4. 序列化性能太差
序列化性能分析
压缩比
压缩时间
可读性
RPC性能选择原则
SpringCloud
基于Feign组件实现的RPC 通信(基于 Http+Json 序列化实现)
Dubbo
基于SPI 扩展了很多RPC通信框架,包括RMI、Dubbo、Hessian等RPC通
信框架(默认是 Dubbo+Hessian 序列化)
信框架(默认是 Dubbo+Hessian 序列化)
存储层调优
MySQL调优
MySQL基本原理
MySQL架构
MySQL性能基准
4 核 8G 的云服务器
500TPS 和 10000QPS
InnoDB 体系架构
1.buffer pool
数据操作原则
数据读
在数据库中进行读取操作,首先将从磁盘中读到的页放在缓冲池中,下次再读相同的页中时,首先判断该页是否在缓冲池中。若在缓冲池中,称该页在缓冲池中被命中,直接读取该页。否则,读取磁盘上的页。
数据修改
对于数据库中页的修改操作,则首先修改在缓冲池中的页,然后再通过Master Thread 线程以一定的频率刷新到磁盘上。页从缓冲池刷新回磁盘的操作并不是在每次页发生更新时触发,而是通过一种称为CheckPoint的机制刷新回磁盘。
缓冲池中缓存的数据页类型
索引页
数据页
undo页
插入缓冲(insert buffer)
先将数据插入到insert buffer
再按照一定的频率将insert buffer中的数据merge到数据页中
自适应哈希索引(adaptive hash index)
InnoDB存储的锁信息(lock info)
数据字典信息(data dictionary)
重做日志缓冲(redo log buffer)
double write
保证数据写入的可靠性
流程
4.1 脏数据写磁盘成功
这种情况是最常见的,脏页刷磁盘99.9%都会成功,但是即使有0.1失败可能也要做处理,不然数据丢了,数据库就不安全了,没有公司愿意天天提心吊胆的抱着个定时炸弹。刷盘成功,找检查点,redo log前滚、回滚就行了。
4.2 共享表空间写失败
如果是写共享表空间失败,那么这些数据不会被写到数据文件,数据库会认为这次刷盘从没发生过,MySQL此时会从磁盘载入原始的数据,然后找检查点,redo log前滚、回滚就行了。
4.3 脏数据刷数据文件失败
写共享表空间成功,但是写数据文件失败,在恢复的时候,MySQL直接比较页面的checksum,如果不对的话,直接从共享表空间的double write中找到该页的一个最近的副本,将其复制到表空间文件,再应用redo log,就完成了恢复过程。因为有副本所以也不担心表空间中数据页是否损坏。
InnoDB 逻辑存储结构
1. 表空间(Tablespace)
InnoDB 提供了两种表空间存储数据的方式
一种是共享表空间
一种是独占表空间
通过设置 innodb_file_per_table 参数为 1(1 代表独占方式)开启独占表空间模式。
表空间是由各个段组成的
2. 段 (Segment)
分类
数据段
而数据段则是 B + 树的叶子节点
索引段
这里的索引段则是指的 B + 树的非叶子节点
回滚段
而回滚段则指的是回滚数据
3. 区 (Extent) / 页(Page)
区是表空间的单元结构,每个区的大小为 1MB
而页是组成区的最小单元,页也是InnoDB 存储引擎磁盘管理的最小单元,每个页的大小默认为 16KB
为了保证页的连续性,InnoDB 存储引擎每次从磁盘申请 4-5 个区。
4. 行(Row)
InnoDB 存储引擎是面向列的(row-oriented),也就是说数据是按行进行存放的,每个页
存放的行记录也是有硬性定义的,最多允许存放 16KB/2-200 行,即 7992 行记录
存放的行记录也是有硬性定义的,最多允许存放 16KB/2-200 行,即 7992 行记录
连接池优化
MySQL连接池
参数
最小连接数
最大连接数
连接池使用注意事项
最大,最小连接数设置要合理
任务队列,不要使用无界队列
线程池,连接池的初始化,考虑预热
慢 SQL 语句优化
导致慢SQL的原因
1. 无索引、索引失效导致慢查询
2. 锁等待
3. 不恰当的 SQL 语句
优化 SQL 语句的步骤
1. 通过 EXPLAIN 分析 SQL 执行计划
2. 通过 Show Profile 分析 SQL 执行性能
常用的 SQL 优化
1. 优化分页查询
2. 优化 SELECT COUNT(*)
MySQL调优之事务
MySQL锁机制
锁分类
悲观锁
共享锁
排他锁
粒度
行锁
record lock
专门对索引项加锁
gap lock
对索引项之间的间隙加锁
next-key lock
对索引项以其之间的间隙加锁
乐观锁
MVVC无锁方案
优化方法
1. 结合业务场景,使用低级别事务隔离
2. 避免行锁升级表锁
在 InnoDB 中,行锁是通过索引实现的,如果不通过索引条件检索数据,行锁将会升级到表锁。
我们知道,表锁是会严重影响到整张表的操作性能的,所以我们应该避免他。
我们知道,表锁是会严重影响到整张表的操作性能的,所以我们应该避免他。
3. 控制事务的大小,减少锁定的资源量和锁定时间长度
MySQL调优之索引
优化方法
1. 覆盖索引优化查询
2. 自增字段作主键优化查询
3. 前缀索引优化
减小索引字段大小,可以增加一个页中存储的索引项,有效提高索引的查询速度
在一些大字符串的字段作为索引时,使用前缀索引可以帮助我们减小索引项的大小
不过,前缀索引是有一定的局限性的,例如 order by 就无法使用前缀索引,无法把前缀索引用作覆盖索引
4. 防止索引失效
如果是以 % 开头的 LIKE 查询将无法利用节点查询数据:
当我们在使用复合索引时,需要使用索引中的最左边的列进行查询,才能使用到复合索引
如果查询条件中使用 or,且 or 的前后条件中有一个列没有索引,那么涉及的索引都不会被使用到
在回表的数据项比较多的情况下,如30%的数据需要回表,MySQL优化器不会使用索引
MySQL避免锁死
避免死锁的措施
避免死锁最直观的方法就是在两个事务相互等待时,当一个事务的等待时间超过设置的某一
阈值,就对这个事务进行回滚,另一个事务就可以继续执行了
阈值,就对这个事务进行回滚,另一个事务就可以继续执行了
参数 innodb_lock_wait_timeout 是用来设置超时时间的
还可以使用其它的方式来代替数据库实现幂等性校验,使用 Redis 以及
ZooKeeper 来实现,运行效率比数据库更佳。
ZooKeeper 来实现,运行效率比数据库更佳。
读多写少场景主从分离
优点
主从复制在主机故障时,读操作相关的业务可以继续运行。
主从复制架构的从机提供读操作,发挥了硬件的性能。
当从库比较多的情况下,主库上的IO线程比较多,这样导致主库资源消耗比较高,一般建议主库最多挂3~5个从库
缺点
主从复制架构中,客户端需要感知主从关系,并将不同的操作发给不同的机器进行处理,复杂度比主备复制要高。
主从复制架构中,从机提供读业务,如果主从复制延迟比较大,
业务会因为数据不一致出现问题。主从数据同步延迟大概在毫秒级别
业务会因为数据不一致出现问题。主从数据同步延迟大概在毫秒级别
解决方案
第一种
当从从库查不到数据后,再重新查询一次主库
第二种
对于insert/update的数据如果马上要查询,走主库查询
第三种
在数据写入主库之后,同步双写Redis,这样从库中可以才缓存中读取到数据
应用场景
综合主从复制的优缺点,一般情况下,写少读多的业务使用主从复制的存储架构比较多。例
如,论坛、BBS、新闻网站这类业务,此类业务的读操作数量是写操作数量的 10 倍甚至
100 倍以上。
如,论坛、BBS、新闻网站这类业务,此类业务的读操作数量是写操作数量的 10 倍甚至
100 倍以上。
主从复制流程
MySQL扩展导致客服端路由复杂的解决方案
流行的做法是业务发展到一定阶段后,就会将这部分功能独立成中间件
百度的 DBProxy
淘宝的 TDDL
MySQL 官方推荐的 MySQL Router
360 开源的数据库中间件 Atlas
Sharding-JDBC
分库分表优化
基本原则
能不分表分库就不要分表分库
在单表的情况下,当业务正常时,我们使用单表即可,而当业务出现了性能瓶颈时,我们首先考虑用分区的方式来优化,
如果分区优化之后仍然存在后遗症,此时我们再来考虑分表分库。
如果分区优化之后仍然存在后遗症,此时我们再来考虑分表分库。
什么时候分库分表
当数据库表的数据量逐渐累积到一定的数量时(5000W 行或 100G 以上),操作数据库的性能会出现明显下降,即使我们使用索引优化
或读写库分离,性能依然存在瓶颈。此时,如果每日数据增长量非常大,我们就应该考虑分表,避免单表数据量过大,造成数据库操作性能下降
或读写库分离,性能依然存在瓶颈。此时,如果每日数据增长量非常大,我们就应该考虑分表,避免单表数据量过大,造成数据库操作性能下降
在单表单库的情况下,数据库连接数、磁盘 I/O 以及网络吞吐等资源都是有限的,并发能力也是有限的。所以,在一些大数据量且
高并发的业务场景中,我们就需要考虑分表分库来提升数据库的并发处理能力,从而提升应用的整体性能
高并发的业务场景中,我们就需要考虑分表分库来提升数据库的并发处理能力,从而提升应用的整体性能
如何分表分库
垂直拆分
水平拆分
架构图
分表分库之后面临的问题
1. 分布式事务问题
2. 跨节点 JOIN 查询问题
3. 跨节点分页查询问题
4. 全局主键 ID 问题
在分库分表后,主键将无法使用自增长来实现了,在不同的表中我们需要统一全局主键
ID。因此,我们需要单独设计全局主键,避免不同表和库中的主键重复问题
ID。因此,我们需要单独设计全局主键,避免不同表和库中的主键重复问题
解决方案
UUID
优点
不依赖于任何第三方系统,所以在性能和可用性上都比较好
缺点
生成的 ID 做好具有单调递增性,也就是有序的,而 UUID 不具备这个特点,影响插入性能
UUID 不能作为 ID 的另一个原因是它不具备业务含义
Snowflake ID
原理
实现方案
增加业务含义
1 位兼容位恒为 0 + 41 位时间信息
6 位 IDC 信息(支持 64 个 IDC)
6 位业务信息(支持 64 个业务)
10 位自增信息(每毫秒支持 1024 个号)
6 位 IDC 信息(支持 64 个 IDC)
6 位业务信息(支持 64 个业务)
10 位自增信息(每毫秒支持 1024 个号)
一种是嵌入到业务代码里,也就是分布在业务服务器中
优点
业务代码在使用的时候不需要跨网络调用,性能上会好一些
缺点
需要更多的机器 ID 位数来支持更多的业务服务器
由于业务服务器的数量很多,我们很难保证机器 ID 的唯一性
部署独立的发号器服务
优点
可以减少机器 ID 的位数
缺点
业务在使用发号器的时候就需要多一次的网络调用
缺点
最大的缺点就是它依赖于系统的时间戳,一旦系统时间不准,就有可能生成重复的 ID
如果请求发号器的 QPS 不高,比如说发号器每毫秒只发一个 ID,就会造成生成 ID
的末位永远是 1,那么在分库分表时如果使用 ID 作为分区键就会造成库表分配的不均匀
的末位永远是 1,那么在分库分表时如果使用 ID 作为分区键就会造成库表分配的不均匀
5. 扩容问题
我们在最开始设计表数据量时,尽量使用 2 的倍数来设置表数量。当我们需要扩容时,也
同样按照 2 的倍数来扩容,这种方式可以减少数据的迁移量。
同样按照 2 的倍数来扩容,这种方式可以减少数据的迁移量。
使用NOSQL
在存储选择方面考量的因素
提升存储读写性能
应对大数量量的存储需求
NOSQL
kv存储
Redis
LevelDB
列式存储
Cassandra
Hbase
文档型
MongoDB
CouchDB
倒排序
elasticsearch
优点
弥补了传统关系型数据库的不足
数据库变更方便,不需要更改原来的数据结构
SQL与NoSQL性能差异原因
关系型数据库虽然做了很多的优化,但是还是存在一定概率的随机IO
插入更新时由于采用B+树,存在随机IO
NoSQL采用基于 LSM 树的存储引擎
NoSQL优势
在性能方面,NoSQL 数据库使用一些算法将对磁盘的随机写转换成顺序写,提升了写的性能
在某些场景下,比如全文搜索功能,关系型数据库并不能高效地支持,需要 NoSQL 数据库的支持
在扩展性方面,NoSQL 数据库天生支持分布式,支持数据冗余和数据分片的特性
MongoDB
Replica
Shard
数据库参数设置优化
InnoDB 存储引擎参数设置调优
InnoDB Buffer Pool
作用
它不仅存储了表索引块,还存储了表数据
查询数据时,IBP允许快速返回频繁访问的数据,而无需访问磁盘文件
InnoDB 表空间缓存越多,MySQL访问物理磁盘的频率就越低,这表示查询响应时间更快,系统的整体性能也有所提高
相关参数设置
innodb_buffer_pool_size
将 IBP 大小设置得过大也不好,可能会导致系统发生 SWAP 页交换
MySQL 推荐配置 IBP 的大小为服务器物理内存的 80%
innodb_buffer_pool_instances
InnoDB 中的 IBP 缓冲池被划分为了多个实例,将缓冲池划分为单独的实例可以减少不同线程读取和写入缓存页面时的争用,从而提高系统的并发性
建议 innodb_buffer_pool_instances 的大小不超过innodb_read_io_threads + innodb_write_io_threads 之和,建议实例和线程数量比例为1:1。
线程数设置
MySQL 后台线程包括
主线程
脏页刷新
合并insert_buffer
undo_page_clean
IO 线程
写线程
读线程
insert_buffer_thread
log_thread
锁线程
监控线程
IO 线程参数设置
根据系统中读写比例来设置
innodb_read_io_threads&innodb_write_io_threads
子主题
MySQL日志参数设置
redo log
redo log作用
它来存储服务器处理的每个写请求的重做活动
执行的每个写入查询都会在日志文件中获得重做条目,以便在发生崩溃时可以恢复更改
日志文件大小已经超过我们参数设置的日志文件大小时,InnoDB 会自动切换到另外一个日志文件
日志缓存,是一个循环使用的环
redo log参数设置
日志缓存大小
innodb_log_buffer_size
默认值为 8MB。如果高并发中存在大量的事务,该值设置得太小,
就会增加写入磁盘的 I/O 操作。我们可以通过增大该参数来减少写入磁盘操作,从而提高并发时的事务性能
就会增加写入磁盘的 I/O 操作。我们可以通过增大该参数来减少写入磁盘操作,从而提高并发时的事务性能
日志文件大小
innodb_log_file_size
设置为1GB就足够了
日志文件持久化到磁盘的策略
innodb_flush_log_at_trx_commit
0
InnoDB 每秒种就会触发一次缓存日志写入到文件中并刷新到磁盘的操作,这有可能在数据库崩溃后,丢失 1s 的数据
1
则表示每次事务的 redo log 都会直接持久化到磁盘中,这样可以保证 MySQL 异常重启之后数据不会丢失。
2
当设置该参数为 2 时,每次事务的 redo log 都会直接写入到文件中,再将文件刷新到磁盘
其他参数设置
缓存优化系统性能
缓存概述
定义
凡是位于速度相差较大的两种硬件之间,用于协调两者数据传输速度差异的结构,均可称之为缓存
硬件性能数据
缓存分类
静态缓存
CDN
前端数据静态缓存
分布式缓存
Redis
Memcache
本地缓存
特点
它们和应用程序部署在同一个进程中,优势是不需要跨网络调度,速度极
快,所以可以来阻挡短时间内的热点查询
快,所以可以来阻挡短时间内的热点查询
技术
HashMap
Guava Cache
Ehcache
缓存的不足
首先,缓存比较适合于读多写少的业务场景,并且数据最好带有一定的热点属性
其次,缓存会给整体系统带来复杂度,并且会有数据不一致的风险
解决方案
缓存操作并发问题导致缓存不一致
加锁
一致性高
加过期时间
一致性一般
再次,之前提到缓存通常使用内存作为存储介质,但是内存并不是无限的
最后,缓存会给运维也带来一定的成本
前端缓存技术
作用
前端做缓存,可以缓解服务端的压力,减少带宽的占用,同时也可以提升前端的查询性能
方案
Http缓存
机制
CDN 缓存
通过不同地点的缓存节点缓存资源副本,当用户访问相应的资源时,会调用最
近的 CDN 节点返回请求资源,这种方式常用于视频图片资源的缓存
近的 CDN 节点返回请求资源,这种方式常用于视频图片资源的缓存
CDN流程
如何将用户的请求映射到CDN 节点上
CDN的ip地址发生变化怎么屏蔽这种变化
采用DNS解析来解决
DNS域名解析原理
如何根据用户的地理位置信息选择到比较近的节点
GSLB
服务层缓存
作用
服务端缓存的初衷是为了提升系统性能。例如,数据库由于并发查询压力过大,可以使用缓
存减轻数据库压力;在后台管理中的一些报表计算类数据,每次请求都需要大量计算,消耗
系统 CPU 资源,我们可以使用缓存来保存计算结果
存减轻数据库压力;在后台管理中的一些报表计算类数据,每次请求都需要大量计算,消耗
系统 CPU 资源,我们可以使用缓存来保存计算结果
方案
进程缓存
一般缓存一些数据量不大、更新频率较低的数据
方案
CurrentHashMap
分布式缓存
缓存策略
cache aside
Read/Write Through(读穿 / 写穿)策略
Write Back(写回)策略
高可用
客户端方案
缓存分片
Hash 分片算法
增加节点时会导致原来的hash寻址失效,找不到缓存数据导致缓存失效
一致性 Hash 分片算法
将hash值空间组织一个既定的虚拟圆环,Redis实例挂载这些虚拟节点上
在增加和删除节点时,只有少量的Key 会“漂移”到其它节点上,保证命中率不会大幅度下降
会出现数据分布不均衡
虚拟节点+一致性hash分片算法
解决数据分布不均衡的问题
中间代理层方案
codis
服务端方案
哨兵模式
Redis集群(Redis-Cluster)
Redis高可用方案
主从复制(Replication模式)
哨兵模式
Redis集群(Redis-Cluster)
缓存问题
缓存穿透
方案
缓存Null值
接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截
布隆过滤器
缺点
它在判断元素是否在集合中时是有一定错误几率的,比如它会把不是集合中的元素判断为处在集合中
不支持删除元素
删除元素会导致本来存在的数据,判断为不存在
解决方案:数组中不存储0/1,而是使用累加值
缓存雪崩
缓存击穿
设置热点数据永远不过期。
限流
分布式系统调优
分布式锁
数据库实现分布式锁
性能低
Zookeeper 实现分布式锁
首先,我们需要建立一个父节点,节点类型为持久节点(PERSISTENT)
每当需要访问共享资源时,就会在父节点下建立相应的顺序子节点,节点类型为临时节点
(EPHEMERAL),且标记为有序性(SEQUENTIAL),并且以临时节点名称 + 父节点名
称 + 顺序号组成特定的名字
(EPHEMERAL),且标记为有序性(SEQUENTIAL),并且以临时节点名称 + 父节点名
称 + 顺序号组成特定的名字
Zookeeper 是集群实现,可以避免单点问题
且能保证每次操作都可以有效地释放锁,这是因为一旦应用服务挂掉了,临时节点会因为 session 连接断开而自动删除掉
Redis 实现分布式锁
实现方式
注意事项
1.1 锁需要具备唯一性
使用redis命令setnx(set if not exist),即只能被一个客户端占坑,如果redis实例存在唯一键(key),如果再想在该键(key)上设置值,就会被拒绝.
1.2 锁需要有超时时间,防止死锁
所以,为了避免客户端挂掉或者说是客户端不能正常释放锁的问题,需要在加锁的同时,给锁加上超时时间.
1.3 锁的创建和设置锁超时时间需要具备原子性
使用set扩展命令
1.4 锁的超时问题
客户端可以在锁设置成功之后,进行定时任务,在锁超时之前使用lua脚本删除锁并重新设置锁和超时时间.
1.5 锁的可重入问题
同样,我们可以选择使用lua脚本的方案,将锁重新删除和设置.
1.6 集群下分布式锁的问题
存在的问题
但如果是在 Redis 集群环境下,依然存在问题。由于 Redis 集群数据同步到各个节点时是异步的,如果在 Master 节点获取到锁后,在没有
同步到其它节点时,Master 节点崩溃了,此时新的 Master 节点依然可以获取锁,所以多个应用服务可以同时获取到锁
同步到其它节点时,Master 节点崩溃了,此时新的 Master 节点依然可以获取锁,所以多个应用服务可以同时获取到锁
解决方案
Redisson 使用了 Redlock 算法,避免在 Master 节点崩溃切换到另外一个 Master 时,多个应用同时获得锁。
1.7 redis分布式锁需要考虑的其他问题
分布式事务
是为了解决在同一个事务下,不同节点的数据库操作数据不一致的问题。
在一个事务操作请求多个服务或多个数据库节点时,要么所有请求成功,要么所有请求都失败回滚回去
在一个事务操作请求多个服务或多个数据库节点时,要么所有请求成功,要么所有请求都失败回滚回去
方案
XA 协议实现的二阶提交(2PC)
第一阶段(prepare)
第二阶段(commit/rollback)
三阶提交 (3PC)
第一阶段(prepare)
第二阶段(预处理阶段)
第三阶段执行提交或回滚操作
TCC 补偿性事务
TCC 补偿性事务也有比较明显的缺点,那就是对业务的侵入性非常大
需要在业务设计的时候考虑预留资源
需要编写大量业务性代码如 Try、Confirm、Cancel 方法
需要为每个方法考虑幂等性
最终一致性方案
消息表
消息队列
业务无侵入方案——Seata(Fescar)
RPC框架
定义
只要是封装了网络调用的细节,能够实现远程调用其他服务,就可以算作是一种 RPC 技术了
实现技术
Dubbo
Grpc
Thrift
RMI
Spring cloud
RPC调用流程
RPC性能优化
网络传输性能
I/O处理流程
第一阶段
I/O 在等待资源的时候选择的处理方式
阻塞。指的是在数据不可用时,I/O 请求一直阻塞,直到数据返回;
非阻塞。指的是数据不可用时,I/O 请求立即返回,直到被通知资源可用为止。
第二阶段
从网络上接收到数据,拷贝到应用程序缓冲区的处理方式
同步处理。指的是 I/O 请求在读取或者写入数据时会阻塞,直到读取或者写入数据完成
选择一种高性能的 I/O 模型
同步阻塞 I/O
同步非阻塞 I/O
同步多路 I/O 复用
信号驱动 I/O
异步 I/O
序列化
考虑因素
时间上的开销
序列化和反序列化的速度
空间上的开销
序列化后的二进制串的大小,过大的二进制串也会占据传输带宽,影响传输效率
是否可以跨语言,跨平台
技术方案
JSON
Thrift
Protobuf
注册中心
服务地址的存储
存储内容变更通知
感知服务变化
检测服务的异常
服务状态管理
服务发送心跳
注册中心主动检测
技术实现方案
Zookeeper
Eureka
Consul
Nacos
负载均衡
服务端负载均衡
架构图
技术方案
LVS
ngnix
haproxy
客服端负载均衡
架构图
技术方案
常用注册中心就支持客服端负载均衡
Ribbon可以实现比较细粒度的客服端负载均衡
负载均衡策略
静态策略
也就是说负载均衡服务器在选择服务节点时,不会参考后端服务的实际运行的状态。
有权重的轮询策略
轮询的策略
动态策略
也就是说负载均衡服务器会依据后端服务的一些负载特性,来决定要选择哪一个服务节点
故障点检测
ngnix检测方式
nginx_upstream_check_module
API网关
网关分类
按照所处位置分
入口网关
它提供客户端一个统一的接入地址
可以承担服务熔断,降级,流量控制和分流的服务治理功能
客户端的认证与授权
白名单机制
请求记录
出口网关
为系统对第三方系统的依赖提供统一的出口
第三方账号登录
第三方支付工具
第三方短信接入
目的对系统对外部API的调用做统一认证,授权,审计与访问控制
按照职责划分
流量网关
全局性流控;
日志统计
防止SQL注入
防止Web攻击
屏蔽工具扫描
黑白名单控制
业务网关
架构
WAF
网关中间件
Zuul1.0
同步阻塞 I/O 模型
Zuul2.0
I/O 多路复用的模型
Kong
Nginx 中运行的 Lua 程序
多机房部署
定义
在不同的 IDC 机房中,部署多套服务,这些服务共享同一份业务数据,并且都可以承接来自用户的流量。
难点
跨机房间的数据延迟
延迟数据同城专线延迟1ms ~3ms
国内异地双机房之间的专线延迟会在 50ms 之内
解决方案
原则
减少跨机房之间RPC调用
避免跨机房的数据库和缓存访问
部署方案
同城双活
架构
异地多活
架构
两个机房不能离得太近,不然难以达到容灾的目的
异地延迟比较大,数据写只能选择写本地机房
数据同步方案
主从复制
消息队列
0 条评论
下一页