后台开发必知必会日常开发性能优化总结
Posted 在路上的德尔菲
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了后台开发必知必会日常开发性能优化总结相关的知识,希望对你有一定的参考价值。
“现代大规模关键性系统中的Java性能调优,是一项富有挑战的任务。需要关注各种问题,包括算法结构、内存分配模式及磁盘和文件I/O使用方式。性能调优最困难的是找出问题,即便是经验丰富的人也会被他们的自觉所误导。性能杀手总是隐藏在最意想不到的地方”
—James Gosling
性能优化
性能优化一般指降低响应时间和提高系统吞吐量两个方面,但在流量高峰时期,服务的可用性才是应该关注的重点,此时性能优化也包括提高服务可用性。而以上提出的降低响应时间、提高系统吞吐量和提高服务可用性三者是相互矛盾的,不可兼得。比如增加缓存可以降低平均响应时间,但是处理线程数量会因为缓存过大而有所限制,从而降低系统吞吐量。所以需要在三者中找到平衡,实现业务需求。
如何量化性能,我们针对不同的端有以下性能指标:
-
Web端:首屏时间、白屏时间、可交互时间、完全加载时间
首屏时间即用户打开网页开始到浏览器第一屏渲染完成的时间,一般说页卡就是指首屏时间长,用户体验不好。
首屏时间 = DNS时间 + 建立连接时间 + 后端响应时间 + 网络传输时间 + 首屏页面渲染时间
-
移动端:Crash率、内存使用率、FPS(FramesPer Second,每秒传输帧数)、端到端响应时间
端到端响应时间是衡量一个API性能的关键指标,比纯后端响应时间更全面,它会受到DNS、网络带宽、网络路由等多个因素的影响。
-
后端: RT(Response Time,响应时间)、TPS(Throught Per Second,吞吐量)、并发数
后端系统响应时间是指系统对请求做出响应的时间(应用延迟时间),对于面向用户的web服务,响应时间会受到数据库查询、RPC调用、网络IO、JVM垃圾回收等多反面因素的影响。对于高并发的应用和系统,吞吐量是关键指标,与Request对应的CPU、内存资源消耗、调用外部接口及IO紧密关联。
下面主要侧重于后端的性能优化,提供以下几个方面值得思考:
代码
for循环次数过多、递归调用导致栈溢出:是否可以在满足结果后break。
很多无谓条件判断:增加前置判断,不符合条件直接返回结果。
算法复杂度:是否有更优的算法,是否可优化当前算法。
业务逻辑优化
业务逻辑经常被忽略,但效果往往比数据库调优、JVM调优效果更明显。
举个栗子,淘宝中B端商家有个全部订单功能,如果是导出用户的全部订单,数据量过大导致加载过慢,可以更改业务逻辑,默认只会导出最近半年的订单,也通过在导出时增加时间段选择项,超时问题即可解决。
再举个栗子,12306抢票场景下,由于访问人数过多,用户点击“查票”之后系统会非常卡顿,作为用户会习惯再去点击“查票”,可能会连续点击好几次,假设每个人点击5次,后端系统负载会增加5倍,而其中80%的请求是重复请求。通过逻辑优化,将用户点击查询后将“查看”按钮置灰或通过JS控制xx秒内只能提交一次请求等,有效拦截80%的无效流量。
硬件升级
如机械硬盘升级为固态硬盘,提高服务器内存等
数据库
-
SQL调优(频率较高):自带慢查询日志或开源慢查询系统定位出问题的SQL,然后explain、profile等工具逐步优化。也要注意包装好的底层SQL语句的合理性,如避免对查询条件计算、模糊查询LIKE使用避免%%、避免创建的索引失效等。
-
添加索引,加快数据的检索速度和表和表之间的连接,减少在使用分组和排序子句进行数据检索时间。常见的索引有唯一索引、主键索引、组合索引、聚集索引,也不是创建越多的索引越好,创建索引会占用资源空间而维护索引增加工作复杂度。
-
架构层面调优:
A. 读写分离:读数据操作一般都多于写操作,保证读可以任意扩展
B. 多从库负载均衡:避免单个从库负载过高
C. 水平和垂直分库分表:提升中心服务写入容量
-
连接池调优:为了数据库连接的高效获取、对数据库连接的限流,需要结合当前使用连接池的原理、具体的连接池监控数据和当前业务量作判断
缓存
缓存分类:
-
本地缓存:HashMap/ConcurrentHashMap、Guava Cache,适合数据量小,并且不会频繁增长又清空
-
缓存服务:Redis、Memcache、Tair,适合数据量偏大,这几种具有不同的特点,比如Redis支持多种数据类型,Memcache全部存储在内存中。
使用缓存的目的:
-
快速响应,减少响应时间
-
防止数据库在高并发访问中不堪重负
优化关键点:
-
什么时候更新(删除)缓存?保障可靠性与实时性:
A.设置缓存时间,一定时间后expire失效,重新从数据库加载数据,因此要容忍一定时间的数据不一致。
B.选择缓存服务模型,同时更新缓存和数据库(Cache Aside);先更新缓存,缓存负责同步更新数据库(Read/Write Through);先更新缓存,缓存定时异步更新数据库(Write Behind Caching)。三种模式各有优势,根据业务场景选择使用,会带来更多系统开销和事务一致性问题。
-
缓存是否会满?满了使用什么策略?:
A.LRU(Least Recently Used)最近最少使用原则,如在Redis中有6种淘汰策略allkeys-lru/random、volatile-lru/random/ttl、noeviction.
B.到达一定容量(快满时)发出警报以扩容
C.不同数据设置不同expire时间,使不常访问数据过期较快
-
缓存允许丢失吗?丢失了怎么办?
根据业务要求设定,不允许丢失可以使用缓存服务的持久化功能,如redis的全量持久化(RDB)和增量持久化(AOF),丢失了可以加载已持久化的数据,但还是会有一定数据丢失(AOF会有一秒的丢失数据)
-
缓存击穿/雪崩?
缓存服务一旦宕机或全部丢失,可能一瞬间所有的流量都直接打到了后 端数据库上,可能造成连锁反应,瞬间的请求高峰极有可能导致数据库无法承载。
使用缓存对提高系统性能有很多好处,但是不合理的缓存使用会为系统造成负担,甚至风险。比如频繁修改得数据,没有热点的访问都是值得考虑的。
异步
使用异步的目的:
-
缩短接口响应时间,使用户请求快速返回
-
避免线程长时间处于运行状态,线程池可用线程减少,线程池任务队列长度增大,阻塞等多请求。
-
线程长时间处于运行状态会引起系统负载、CPU使用率、机器整体性能下降。
优化方式:
-
开辟一个线程或线程池,如果线程处理的数据量很大,可以引入阻塞队列BlockingQueue,让一批异步线程不断往阻塞队列中扔数据,然后额外开辟一个线程从阻塞队列中拿数据,进行批处理。根据场景需求选择不同的阻塞队列,如ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue、SynchronousQueue等
-
使用消息队列MQ中间件,如RocketMQ、ActiveMQ、RabbitMQ、Kafka等,有些消息不需要这个系统处理,将消息通过消息中间件传送到其他系统处理。
-
使用NIO,一切网络IO皆可异步:RPC框架、Servlet 3.0提供的异步技术、Apache HttpAsyncClient、缓存异步接口等。
JVM调优
关键指标如gc time、gc count、各个分代(Eden、S0、S1、Old、Perm)的内存大小变化、机器的Load值和CPU使用率、JVM的线程数等
常见调优方法:
-
高峰期CPU使用率和Load使用率偏高,观察一些JVM的thread count和gc count(主要是young gc count),如果这两个值都偏高,基本判定是young gc频率过高导致,可以通过适当增大young区大小或者占比来解决。
-
如果发生full gc和old cms gc非常频繁,诱发STW(stop the world)时间相应加长,导致接口响应变慢。可能是出现了“内存泄漏”即应该释放的内存空间没有被释放掉,解决办法是找到这些应该被释放的对象,可以使用jdk中提供的工具如jmap、MAT定位具体代码。
-
减少gc次数或单次gc时间,来降低整个应用的STW时间,以加快接口响应。
多线程与分布式
使用场景:IO任务、离线任务、异步任务、大数据任务、耗时较长任务
注意不是多线程就一定比单线程效率高,由于多线程情况下CPU还要花时间维护,CPU处理各线程的请求时在线程间切换也要花时间,用了有时反而会得不偿失。
-
单机多线程:通过引入线程池
A. 更好管理线程,节省线程创建和销毁的开销,提高性能
B. 限流,给线程池一个固定的容量,保障机器在极限压力下稳定处理的能力
C. 根据业务场景设定调整核心参数如corePoolSize、maxPool Size、keepAliveTime、workerQueue等
-
多机多线程:多个节点组成一个分布式系统,协同工作,更加复杂。
服务器优化
-
nginx作为一个高性能的HTTP和反相代理服务器,常用作静态内容服务和负载均衡服务,提供更快、更可靠、单机支持10万以上并发连接的优势。如sendfile指令对于普通应用可设为on,IO重负载应用可设置为off,以平衡磁盘和网络IO处理速度;请求黑名单拦截、控制最大连接数、连接超时时间等设置都可以帮助更好优化服务性能。
-
Tomcat应用服务器优化可提高网站的并发能力,运行方式的选择BIO、NIO、ARP,线程池属性设置如maxThreads、maxIdleTime、maxQueueSize、minSpareThreads,合理的设置能够使用最少的资源来调度;连接器作为Tomcat接受请求的入口,属性设置如protocol、enableLookups、asyncTimeout等。
小结:以上讲了很多性能优化可供参考的方向,从DNS连接到接入层再到逻辑层,存储由缓存到数据库,但不能盲目的去优化,最重要是找到性能瓶颈,再进行优化,在硬件、网络、资源一定的情况下以实现性能最大化。
以上是关于后台开发必知必会日常开发性能优化总结的主要内容,如果未能解决你的问题,请参考以下文章