应用性能优化列表

应用开发完了,但是随着用户规模的上升,数据量的积累,系统会越来越慢,性能优化将会伴随着项目一直持续下去

1、总原则

  • 可扩展性架构,堆机器能不能解决问题是最最优先考虑的问题
  • 去中心化的点对点通信,优于通过中心代理的通信
  • 池化的长连接,优于短连接
  • 二进制数据,优于文本数据
  • 尽量减少交互,一次调用的粗粒度聚合接口 优于 多次调用的细粒度接口,批量接口优于循环调用
  • 尽量只交互必要的数据
  • 尽量就近访问
  • 尽量使用缓存
  • 总是设定超时
  • 在合适的场景,并行化执行、异步化执行

2、环境准备

保证开发环境、测试环境、验证环境、正式环境的配置一致,包括但不限于以下内容:

  1. 操作系统版本,尽量大版本一致,系统基本配置预估(CPU,硬盘,带宽)
  2. 系统参数配置一致,TCP内核参数,网卡参数及多队列绑定,IO&Swap内核参数,ulimit资源限制,线程栈大小等
  3. DNS指向,host配置一致
  4. 目录配置,应用目录,日志目录,数据目录,依赖包目录
  5. 软件配置一致,python版本,python依赖,Java版本,JVM调优参数,Tomcat版本、配置,NodeJs版本,数据库版本等
  6. 服务器监控,应用监控
  7. 扇入模型:平时与高峰期的流量估算,各接口的流量比例,响应时间要求
  8. 扇出模型:各接口对远程服务、数据库、缓存、消息系统的调用比例,响应时间估算。

3、数据库

  1. 各环境的数据库配置保持一致
  2. 禁用存储过程,函数,触发器,外键约束。
  3. 各个环境的数据库索引保持一致
  4. SQL语句规范
  5. 配置SQL执行的超时
  6. 必须使用prepareStatement,提升性能与防注入
  7. 根据一切皆有超时的原则,配置SQL执行的超时。可在连接池里设置default值,可在MyBatis的Mapper定义里可设置每个请求的超时,可惜规范是秒级的。
  8. 根据尽量少交互与尽量少数据的原则,需使用对SQL完全可控的DAO框架,建议为MyBatis 或 Spring JDBC Template。

3.1 事务

  • 不使用事务,连接池设置autocommit,使用其他方式来保持数据一致性。
  • 通过Transaction Annotation控制事务,事务跨度尽量短,把非事务范围内的业务逻辑剔除到被标注的函数之外。
  • 只读事务可以不加事务标注。

3.2 连接池

  • 在分库分表时,根据点对点通信优先的原则,尽量使用客户端分片的实现。功能不满足时才用MyCat中央代理。
  • 推荐使用性能最高HikariCP,或者Druid,不推荐c3p0与DBCP。

连接池的配置:

  • 配置初始值,再联系DBA获得线上数据库支持的连接数,计算最大连接数。
  • 连接有效性检查,只在连接空闲检测时执行,不在拿出和归还连接时执行,最好是直接使用数据的Ping方案,不要配置检查SQL。
  • 根据总是设置超时的原则,配置获取连接超时的时间。
  • 配置合理的空闲连接回收间隔和空闲时间。
  • 番外篇:在分库分表时,可考虑基于HikariCP二次开发,减少总的空闲连接检查线程数(比如128个分区,可能有256条线程),重用同一个实例上的库的连接等。

4、缓存

4.1 多级缓存

  • 根据缓存原则, 缓存 > 数据库/远程调用
  • 根据就近原则, 堆内缓存 > 堆外缓存 > 集中式缓存
  • 堆内缓存受大小限制,并影响GC
  • 堆内缓存与堆外缓存,分布在每一台应用服务器上,刷新方式比集中式缓存复杂
  • 堆外缓存与集中式缓存,需要序列化/反序列化对象
  • 集中式缓存,有网络传输的成本,特别是数据超过一个网络包的大小。
  • 集中式缓存,一次获取多个键时,在有分区的情况下,需要收发多个网络包。
  • 使用上述条件选择合适的缓存方案,或同时使用多级缓存,逐层回源。

4.2 综述

  • 需要对回源进行并发控制,当key失效时,只有单一线程对该key回源。
  • 基于二进制优于文本数据的原则,JSON的序列化方案较通用与更高的可读性。而对于较大,结构较复杂的对象,基于Kyro,PB,Thrift的二进制序列化方案的性能更高,见后面的序列化方案部分。

4.3 堆内缓存

选型:

  • 推荐Guava Cache。
    • 正确设置并行度等参数。
    • 重载load()参数,实现单一线程回源。
    • Guava Cache能后台定时刷新,在刷新的过程中,依然使用旧数据响应请求,不会造成卡顿,但需要重载实现reload()函数。
    • Guava Cache同时还支持并发安全版的WeakHashMap。
  • Ehcache较重,性能也较差。更不要使用存在严重bug的Jodd Cache。

4.4 堆外缓存

选型:

  • 推荐Cassandra的OHC 或者 OpenHFT的Chronical map2。
  • OHC够简单,其实R大不喜欢Chronical,玩的太深,换个JDK都可能跑不起来。
  • Chronical map3的license则较不友好,复杂度高且要求JDK8。
  • 其他的Ehcache的Terracota Offheap 一向不喜欢。

4.5 Memcached

客户端:

  • 基于点对点通信优于网关的原则,使用客户端一致性哈希分区。
  • 推荐Spymemcached。 XMemcached 太久没更新,Folsom知名度不高。
  • 注意Spymemcached为单线程单连接架构(一个MemcachedClient只有一条IO线程,与每台Memcached只有一条连接),必要时可多建几个MemcachedClient随机选择,但不要用Commons Pool去封装它,把Spy原本的设计一笔抹杀。
  • 根据在合适场景使用并发的原则,Spymemcached支持异步API。
  • 根据一切皆设超时的原则,可在连接工厂中设置最大超时数,默认值两秒半太长。

数据结构:

  • Key必须设置失效时间。
  • Key必须有长度限制。
  • Value长度需要控制,以不超过1个网络包(MTU,千五字节)为佳。
  • Value大小差别较大的缓存类型,建议拆分到不同MC集群,否则会造成低使用率并且产生踢出。

4.6 Redis as Cache

Redis拓扑:

  • 基于点对点通信优于网关的原则,使用如下两种拓扑
    • 无HA的普通分片:由Jedis客户端完成分片路由。
    • Redis Cluster:同样由Jedis客户端封装分区,跳转,重试等逻辑,需要使用最新版的Jedis版本。

服务端:

  • Cache节点与持久化数据节点不要混用。
  • Cache节点是否需要持久化要仔细衡量。
  • 由于Redis是单线程,使用taskset进行cpu绑定后可以有效地利用cpu,并在单机上运行多个redis实例。

+对热键进行监控,发现不合理的热健要进行分拆等处理。

客户端:

  • Jedis基于Apache Commons Pool进行了多连接的封装,正确配置总连接数不超过Redis Server的允许连接数。
  • 性能考虑,空闲连接检查不要过于频繁(建议30秒以上),另不要打开testOnBorrow等测试参数。
  • 根据一切皆有超时的原则,设定统一的调用超时,获取连接的最长等待时间参数,重试次数
  • 根据在合适的地方异步的原则,Jedis本身没有异步API,只在PipleLine模式下支持。

数据结构:

  • 必须对Key设置失效时间。
  • Key必须有长度限制。
  • Value长度需要控制,不要超过一个网络包。另外集合的元素不要超过五千个。
  • 除了使用序列化的String,同样可以考虑用Hash来存储对象,注意内部结构为ZipList与HashTable时,hmget 与hgetall的不同复杂度。

命令:

  • 慎用的命令:LANGE(0, -1), HGETALL, SMEMBER
  • 高复杂度的命令: ZINTERSTORE, SINTERSTORE, ZUNIONSTORE, ZREM
  • 尽量使用多参数的命令:MGET/MSET,HMGET/HMSET, LPUSH/RPUSH, LRANGE
  • 尽量使用pipeline
  • 根据减少交互的原则,必要时可使用Redis的Lua脚本

5、服务调用

5.1 接口设计

  1. 尽量少交互的原则:
  2. 支持批量接口,最大的批量,综合考虑调用者的需求与 后端存储的能力。
  3. 支持粗粒度接口,在支持原子细粒度接口的同时,支持粗粒度接口/聚合层接口,将多个数据源的获取,多个动作,合并成一个粗粒度接口。
  4. 尽量少数据的原则:
  5. 在提供返回所有数据的大接口的同时,提供只提供满足部分调用者需要的轻量接口。
  6. 最好再提供能定制返回字段的接口。
  7. 二进制数据优于文本数据
  8. 同样是一个简单通用性,与性能的选择,特别是大数据量时。

5.2 RESTful

  • 仅以Apache HttpClient为例,大部分Restful框架都是对Apache HttpClient的封装。另外OkHttp也值得看看。
    • 不要重复创建ApacheClient实例,使用连接池,正确配置连接池的连接数。
    • 连接池总是有锁,针对不同的服务,使用不同的Apache HttpClient实例,将锁分散开来。在高并发时比使用全局单例的ApacheClient,有很大的性能提升。
    • 根据一切调用皆有超时的原则,每次调用均设置超时时间。RequestConfig里共有Connect Timeout, Socket Timout 和 从Pool中获取连接的Timeout三种超时。
    • 需要异步或并行的场景,使用Apache AsyncHttpClient项目。但要注意AsyncHttpClient项目,检查调用超时的周期默认为1秒。

5.3 自家RPC框架

每家的RPC框架特性不同,但考虑点都类似。

6、消息异步

6.1 选型

  • 根据就近原则,可以先尝试用JVM内的队列来解决,然后再考虑中央消息系统。
  • 可靠性要求极高的选择RabbitMQ,可支持单条消息确认。
  • 海量消息场景,允许极端情况下少量丢失则使用Kafka。

6.2 Kafka

  • 在同步和异步之间做好权衡,异步批量发送可以极大的提高发送的速度。
  • 关注消费者如下参数:commitInterval(自动提交offset间隔),prefetchSize(指单次从服务器批量拉取消息的大小),过大和过小都会影响性能,建议保持默认。

6.3 RabbitMQ

  • 根据扩展性原则,RabbitMQ本身没有分片功能,但可以在客户端自行分片。
  • 如非必要情况,应该保持默认的同步发送模式。
  • 关注消费者如下参数:autocommit(自动提交确认,默认false) ,在消息拉取到本地即认为消费成功,而不是真正消费成功后提交。prefetchCount(预取消息条数,默认64条)
  • 生产者在必要时也可以临时降级不进行confirm。

7. 日志

7.1 综述

  • Log4j2或logback,不要再使用Log4j。
  • 除了应用启停日志,不允许使用超慢的System.out.println() 或 e.printStack();
  • 严格控制日志量避免过高IO,对海量日志,应该有开关可以动态关停。
  • 如果可能出现海量异常信息,可仿效JDK的优化,用RateLimiter进行限流,丢弃过多的异常日志。

7.2 内容

  • 严格控制日志格式,避免出现消耗较大的输出如类名,方法名,行号等。
  • 业务日志不要滥用toJSONString()来打印对象,尽量使用对象自身的toString()函数,因为JSON转换的消耗并不低。
  • 在生产环境必定输出的日志,不要使用logger.info("hello {}", name)的模式,而是使用正确估算大小的StringBuilder直接拼装输出信息。

7.3 异步日志

  • 同步日志的堵塞非常严重,特别是发生IO的时候,因此尽量使用异步日志。
  • Logback的异步方案存在一定问题,需要正确配置Queue长度,阀值达到多少时丢弃Warn以下的日志,最新版还可以设置如果队列已满,是等待还是直接丢弃日志。
  • 如果觉得Logback的异步日志每次插入都要询问队列容量太过消耗,可重写一个直接入列,不成功则直接丢弃的版本。

8、工具类

8.1 JSON

  • 使用Jackson 或 FastJSON。GSON的性能较前两者为差,尤其是大对象时。
  • 超大对象可以使用Jackson或FastJSON的流式 API进行处理。
  • 将不需要序列化的属性,通过Annotation排除掉。

FastJson:

  • 尽量使用最新的版本。
  • SerializerFeature.DisableCircularReferenceDetect 关闭循环引用检查。

Jackson:

  • 设置参数,不序列化为空的属性,等于默认值的属性。
  • 除了jackson-databinding,可试用简化版没那么多花样的jackon-jr。

8.2 二进制序列化

需要定义IDL的PB与Thrift,不需要定义的Storm等用的Kyro 都可选择,其他一些比较旧就算了。

8.3 Bean复制

在VO,BO之间复制时,使用Orika(生成代码) 或 Dozer(缓存反射),不要使用需要每次进行反射的Apache BeanUitls,Spring BeanUtils。

8.4 日期

  • JDK的日期类与字符串之间的转换很慢且非线程安全。
  • 继续用Java日期不想大动作的,就用CommonsLang的FastDateFormat。
  • 能大动作就用joda time,或者JDK8的新日期API。

9、Java代码优化 与 业务逻辑优化

  • 规则前置,将消耗较大的操作放后面,如果前面的条件不满足时可。
  • 另外前面提到的一堆原则,比如尽量缓存,尽量少交互,尽量少数据,并行,异步等,都可在此使用。
欢迎订阅「K叔区块链」 - 专注于区块链技术学习

博客地址:http://www.jouypub.com

简书主页:https://www.jianshu.com/u/756c9c8ae984

segmentfault主页:https://segmentfault.com/blog/jouypub

腾讯云主页:https://cloud.tencent.com/developer/column/72548

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏JAVA高级架构

Java技术大纲

3713
来自专栏Java技术分享

Dubbo详细介绍与安装使用过程

随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进。

6045
来自专栏Golang语言社区

go语言最快最好运用最广的web框架比较(大多数人不了解的特性)

如果你为自己设计一个小应用程序,你可能不需要一个Web框架,但如果你正在进行生产,那么你肯定需要一个,一个好的应用程序。

4644
来自专栏13blog.site

小测试

可以在 @RequestMapping 注解里面加上 method=RequestMethod.GET 或者使用 @GetMapping 注解

1831
来自专栏后端技术探索

Restful 接口设计最佳事件

本小编这一年是在一家移动互联网公司做App后端接口设计开发工作,最近组内做了一次很大的重构,就是把接口完全根据restful规范进行设计重写。这么做的目的首先是...

1123
来自专栏炉边夜话

RESTful API 设计最佳实践

目前互联网上充斥着大量的关于RESTful API(为了方便,以后API和RESTful API 一个意思)如何设计的文章,然而却没有一个”万能“的设计标准:如...

4584
来自专栏架构师小秘圈

基于dubbo框架,如何进行大型微服务系统架构设计?

一,为什么需要微服务框架 Nginx+tomcat组成的集群,这已经是非常灵活的集群技术,但是当我们的系统遇到更大的瓶颈,全部应用的单点服务器已经不能满足我们的...

5244
来自专栏JAVA高级架构

大型网站系统与 Java 中间件实践

第一章 分布式系统介绍 分布式系统的定义:组件分布在网络计算机上,组件间仅仅通过消息传递来通信并协调行动。 分布式系统的意义: 升级单机处理能力的性价比越来越...

4107
来自专栏Linux驱动

40.Linux应用调试-使用gdb和gdbserver

1.gdb和gdbserver调试原理 通过linux虚拟机里的gdb,来向开发板里的gdbserver发送命令,比如设置断点,运行setp等,然后开发板上的g...

4178
来自专栏我的小碗汤

6个最好的Go语言Web框架

原文:Top 6 web frameworks for Go as of 2017

1791

扫码关注云+社区

领取腾讯云代金券