1、硬件/网络优化

  1. 按照 服务器推荐配置 检查服务器硬件配置

    http://doc.ankepower.com:8181/docs/iform-deploy/iform-deploy-1f9lj17agfhbk

  2. 数据库服务器磁盘使用SSD固态硬盘,经测试发现使用SSD固态硬盘可以提升5~10倍性能;

2、操作系统优化

  1. linux系统修改open files参数

    1. ulimit -a查看open files的值是多少
    2. vi /etc/security/limits.conf
      添加以下内容
       * soft nofile 65535
       * hard nofile 524288
    3. 重新登陆会话即可使其生效
  2. linux系统内核优化CentOS为例完整

    vi /etc/sysctl.conf

      # 其值为0时表示禁止进行IP转发;如果是1,则说明IP转发功能已经打开。
      net.ipv4.ip_forward = 0 #如何值为0出现ip无法连接,则改为1
      net.ipv4.conf.all.send_redirects = 0
      net.ipv4.conf.default.send_redirects = 0
      # 确保无人能修改路由表
      net.ipv4.conf.all.accept_redirects = 0
      net.ipv4.conf.default.accept_redirects = 0
      net.ipv4.conf.all.secure_redirects = 0
      net.ipv4.conf.default.secure_redirects = 0
      #处理无源路由的包
      net.ipv4.conf.all.accept_source_route = 0
      net.ipv4.conf.default.accept_source_route = 0
      # 启用源路由核查功能/反向路径过滤
      net.ipv4.conf.all.rp_filter = 1
      net.ipv4.conf.default.rp_filter = 1
      # 关闭组合键了解系统运行状态功能
      kernel.sysrq = 0
      # 控制core文件的文件名是否添加pid作为扩展
      kernel.core_uses_pid = 1
      #开启SYN Cookies,当出现SYN等待队列溢出时,启用cookies来处理
      net.ipv4.tcp_syncookies = 1
      #每个消息队列的大小(单位:字节)限制
      kernel.msgmnb = 65536
      #整个系统最大消息队列数量限制
      kernel.msgmax = 65536
      #单个共享内存段的大小(单位:字节)限制,计算公式64G*1024*1024*1024(字节)
      kernel.shmmax = 68719476736
      #所有内存大小(单位:页,1页 = 4Kb),计算公式16G*1024*1024*1024/4KB(页)
      kernel.shmall = 4294967296
      #timewait的数量,默认是180000
      net.ipv4.tcp_max_tw_buckets = 6000
      #开启有选择的应答
      net.ipv4.tcp_sack = 1
      #支持更大的TCP窗口. 如果TCP窗口最大超过65535(64K), 必须设置该数值为1
      net.ipv4.tcp_window_scaling = 1
      #TCP读buffer
      net.ipv4.tcp_rmem = 4096        87380   4194304
      #TCP写buffer
      net.ipv4.tcp_wmem = 4096        16384   4194304
      #为TCP socket预留用于发送缓冲的内存默认值(单位:字节)
      net.core.wmem_default = 8388608
      #为TCP socket预留用于发送缓冲的内存最大值(单位:字节)
      net.core.wmem_max = 16777216
      #为TCP socket预留用于接收缓冲的内存默认值(单位:字节)  
      net.core.rmem_default = 8388608
      #为TCP socket预留用于发送缓冲的内存最大值(单位:字节)
      net.core.rmem_max = 16777216
      #每个网络接口接收数据包的速率比内核处理这些包的速率快时,允许送到队列的数据包的最大数目
      net.core.netdev_max_backlog = 262144
      #系统中最多有多少个TCP套接字不被关联到任何一个用户文件句柄上。这个限制仅仅是为了防止简单的DoS攻击,不能过分依靠它或者人为地减小这个值,更应该增加这个值(如果增加了内存之后)
      net.core.somaxconn = 262144
      #系统中最多有多少个TCP套接字不被关联到任何一个用户文件句柄上。这个限制仅仅是为了防止简单的DoS攻击,不能过分依靠它或者人为地减小这个值,更应该增加这个值(如果增加了内存之后)
      net.ipv4.tcp_max_orphans = 3276800
      #记录的那些尚未收到客户端确认信息的连接请求的最大值。对于有128M内存的系统而言,缺省值是1024,小内存的系统则是128
      net.ipv4.tcp_max_syn_backlog = 262144
      #时间戳可以避免序列号的卷绕。一个1Gbps的链路肯定会遇到以前用过的序列号。时间戳能够让内核接受这种“异常”的数据包。这里需要将其关掉
      net.ipv4.tcp_timestamps = 0
      #为了打开对端的连接,内核需要发送一个SYN并附带一个回应前面一个SYN的ACK。也就是所谓三次握手中的第二次握手。这个设置决定了内核放弃连接之前发送SYN+ACK包的数量
      net.ipv4.tcp_synack_retries = 1
      #在内核放弃建立连接之前发送SYN包的数量
      net.ipv4.tcp_syn_retries = 1
      #开启TCP连接中time_wait sockets的快速回收
      net.ipv4.tcp_tw_recycle = 1
      #开启TCP连接复用功能,允许将time_wait sockets重新用于新的TCP连接(主要针对time_wait连接)
      net.ipv4.tcp_tw_reuse = 1
      #1st低于此值,TCP没有内存压力,2nd进入内存压力阶段,3rdTCP拒绝分配socket(单位:内存页)
      net.ipv4.tcp_mem = 94500000 915000000 927000000
      #如果套接字由本端要求关闭,这个参数决定了它保持在FIN-WAIT-2状态的时间。对端可以出错并永远不关闭连接,甚至意外当机。缺省值是60 秒。2.2 内核的通常值是180秒,你可以按这个设置,但要记住的是,即使你的机器是一个轻载的WEB服务器,也有因为大量的死套接字而内存溢出的风险,FIN- WAIT-2的危险性比FIN-WAIT-1要小,因为它最多只能吃掉1.5K内存,但是它们的生存期长些。
      net.ipv4.tcp_fin_timeout = 1
      #表示当keepalive起用的时候,TCP发送keepalive消息的频度(单位:秒)
      net.ipv4.tcp_keepalive_time = 30
      #对外连接端口范围
      net.ipv4.ip_local_port_range = 1024    65000
      #表示文件句柄的最大数量
      fs.file-max = 102400
      # 允许更多的PIDs (减少滚动翻转问题); may break some programs 32768
      kernel.pid_max = 1000000
      # 最大监听文件数
      fs.inotify.max_user_watches = 524288
      #修改防火墙表大小,默认65536
      net.netfilter.nf_conntrack_max = 655350
      net.netfilter.nf_conntrack_tcp_timeout_established = 1200

    执行命令 sysctl -p 使其生效

3、中间件优化

  1. 调整JVM参数,增大内存、调整新生代内存比例等;如:-XX:SurvivorRatio=8 -Xmx4g -Xms4g -Xmn1536m -Xss300kJVM参数说明

  2. 前端启用多域名访问,多域名方案

  3. Web容器参数调优

    • Undertow
      • UNDERTOW_IO_THREADS=20 #与服务器CPU线程数一致
      • UNDERTOW_WORKER_THREADS=256 #可以根据机器情况增大
      • UNDERTOW_BUFFER_SIZE=1024
      • UNDERTOW_DIRECT_BUFFERS=true
  4. MySQL5.7参数优化

     [client]
     default-character-set = utf8
     [mysqld]
     port = 3306
     default-storage-engine = INNODB
     character-set-server = utf8
     collation-server = utf8_general_ci
     max_connections = 1024
     max_allowed_packet = 1024M #影响大SQL执行,仅供参考
     lower_case_table_names = 1
     innodb_buffer_pool_size = 50G #影响性能,仅供参考
     join_buffer_size = 256M #影响性能,仅供参考
     read_rnd_buffer_size = 16M #影响性能,仅供参考
     sort_buffer_size = 16M #影响性能,仅供参考
    
     innodb_read_io_threads = 10 #影响性能,仅供参考
     innodb_write_io_threads = 10 #影响性能,仅供参考
    
     query_cache_type = 1 #影响性能,仅供参考
     query_cache_size = 10G #影响性能,仅供参考
    
     #sata/sas硬盘这个值在200
     #sas raid10: 2000
     #ssd硬盘:8000
     #fusion-io(闪存卡):25,000-50,000
     innodb_io_capacity=2000
     innodb_io_capacity_max=4000
    
     sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'
  5. MySQL8.0参数优化

     [client]
     default-character-set = utf8
     [mysqld]
     port = 3306
     default-storage-engine = INNODB
     character-set-server = utf8
     collation-server = utf8_bin
     max_connections = 1024
     max_allowed_packet = 64M
     lower_case_table_names = 1
     innodb_buffer_pool_size = 30G
     innodb_buffer_pool_instances = 10
     join_buffer_size = 256M
     read_rnd_buffer_size = 16M
     read_buffer_size = 16M
     sort_buffer_size = 16M
     open_files_limit = 65535
     innodb_read_io_threads = 20
     innodb_write_io_threads = 16
     innodb_thread_concurrency = 0
     innodb_flush_log_at_trx_commit = 2
     binlog_format = mixed
     tmp_table_size = 256M
     max_heap_table_size = 256M
     thread_cache_size = 64
     disable-log-bin
     log_slave_updates = 0
     sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
     datadir=/var/lib/mysql
     socket=/var/lib/mysql/mysql.sock
     symbolic-links=0
     pid-file=/var/run/mysqld/mysqld.pid
  6. 开启nginx的gzip压缩功能,生产部署Nginx

    http {
     # 开启GZIP
     gzip  on;
     gzip_min_length  1k;
     gzip_buffers     4 16k;
     gzip_http_version 1.1;
     gzip_comp_level 9;
     gzip_types       text/plain application/x-javascript text/css application/xml text/javascript application/x-httpd-php application/javascript application/json;
     gzip_disable "MSIE [1-6]\.";
     gzip_vary on;
    }

4、平台程序优化

  1. 遵循分布式应用方式部署

    • 每个应用都独立部署,将IO分摊到不同机器;
    • 特别是数据库的IO瓶颈问题,将不同数据库都独立服务器部署,将IO分摊到不同机器;
  2. 将应用日志输出级别调整为warn或以上级别,项目中的log4j2.yml文件中修改相关配置;

  3. application.yml文件中添加authorization.permission-filter-enable: false,关闭接口权限校验,减少请求处理时间;此处只是关闭接口权限,并不是关闭令牌校验,访问系统还是需要令牌的关闭该参数风险:会出现越权问题

  4. 业务表(由业务对象创建的数据表)自行添加关键索引;

  5. application.yml文件中添加authorization.log-api-invoke.enable: false,关闭接口日志记录,减少数据库压力;

  6. 使用Spring-jdbc NamedParameterJdbcTemplate和mysql-connector-java,并且我在几乎每一个时间都会发出大量的“额外”SQL声明全部看起来像”SHOW FULL COLUMNS FROM …”

    • 在数据库链接后面添加参数useDynamicCharsetInfo=false
    • 例:jdbc:mysql://192.168.1.202:3306/iform_business?useDynamicCharsetInfo=false&useUnicode=true&characterEncoding=utf-8&autoReconnect=true&useSSL=false&serverTimezone=CTT
  7. 高并发场景下一级缓存会造成FullGC频繁,可以选择禁用一级缓存,但会给二级缓存造成更大压力(可以提供redis性能来弥补),不一定能提升性能(慎重,已知影响:流程接口性能会下降)

  8. Oauth2服务开启本地缓存com.ak.context.user.enabled: ${CONTEXT_USER_ENABLED:true}

  9. Platform服务开启本地缓存com.ak.context.party.enabled: ${CONTEXT_PARTY_ENABLED:true}

  10. Platform服务开启J2cache二次缓存

    com.ak.context.dictionary.enabled: ${CONTEXT_DICTIONARY_ENABLED:true}
    com.ak.context.role.resources.enabled: ${CONTEXT_ROLE_RESOURCES_ENABLED:true}
  11. Business服务开启J2cache二次缓存com.ak.context.datatemplate.data.builder.enabled: ${CONTEXT_DATATEMPLATE_DATA_BUILDER_ENABLED:true}

  12. 开启缓存初始化加载器,http://doc.ankepower.com:8181/docs/iform-deploy/iform-deploy-1f9ljsgvr4mq8

  13. 二次缓存配置cache.short.enabled: ${CACHE_SHORT_ENABLED:true}

  14. 修改文件application-common.yml中的配置aop.method.signature.open: false

  15. 开启web容器的gzip压缩功能,在文件application-common.yml中的配置server.compression.enabled: true

  16. 后端服务使用HikariCP连接池,相关配置在application-common.yml文件中;

    com.ak.db.datasources.druid-enabled: ${DATASOURCES_DRUID_ENABLED:false}
    com.ak.db.datasources.hikari-enabled: ${DATASOURCES_HIKARI_ENABLED:true}
  17. mysql判断为空不为空的表达式优化

    TASK_ID_ is null #效率较低
    length(TASK_ID_) is null #效率较高,建议使用这种方式判断
    TASK_ID_ is not null #效率较低
    length(TASK_ID_) is not null #效率较高,建议使用这种方式判断
  18. 远程服务调用合并,将同一个资源的多次请求合并成一次,需要全面分析代码。

  19. 流程脚本使用预编译方式

     public void execute(Map<String, Object> vars) {
     vars.get("cmd");
     vars.get("execution");
     vars.get("businessKey_");
     vars.get("instanceId_");
     vars.get("flowKey_");
    }
  20. 设置流程快捷查询

    • business服务设置启动参数AUTO_SYNC_TASK_PENDING_OPEN=true

5、跨服务调用时合并请求

已完成
对流程性能提升显著;

  • 流程用户插件中有大量用户查询,且每条数据都进行服务调用查询;
  • 将该类型的请求合并为一个远程请求统一处理;
  • 示例:com.ak.iform.bpmn.plugin.task.userassign.plugin.UserQueryPlugin.filterUser(List<BpmIdentity>, List<BpmIdentity>)中调用com.ak.iform.org.api.IPartyEntityService.trans(PartyEntityTransVo),有效减少网络消耗;
  • 合并处理后使用多线程并行计算用户数据,逻辑在com.ak.iform.org.api.IPartyEntityService.trans(PartyEntityTransVo)中处理;

##5.1、org.slf4j.Logger对象静态化

已完成
影响全平台日志输出性能,积少成多的瓶颈;

  • iform中大量使用org.slf4j.LoggerFactory.getLogger(getClass())获取日志对象并输出日志;
  • com.ak.iform.base.framework.repository.AbstractRepository.get(PK)中日志对象创建耗时的影响尤为突出;
  • 将Logger操作对象静态化,如:private static final Logger LOGGER = LoggerFactory.getLogger(DataTemplateRepositoryImpl.class);,可减少方法执行时创建时间;

5.2、动态获取系统配置属性优化

已完成
影响全平台配置读取性能,积少成多的瓶颈;

  • 框架中频繁调用Environment.getProperty,导致累计耗时增加,对业务的影响直线上升,使用线程上下文隔离配置属性的获取,同一个线程中只从Environment中获取一次,之后都从线程上下文获取,从而减少Environment.getProperty的调用次数;
  • Environment.getProperty对功能的影响:所有业务功能,包括服务发现(eureka)、网关(zuul)、认证服务、业务服务;

5.3、JSON工具优化

已完成
影响流程、表单性能,有效提升性能;

  • JSON框架json-lib框架老旧、性能较低,因此需要替换该框架,将切换到Jackson
  • JSON框架对功能的影响:表单(所有功能都受影响)、流程(受影响功能:审批意见);

5.4、BeanUtils.copy调用优化

进行中…
平台级提升;

  • 缓存对象读取后全局调用BeanUtils.copy,导致很多不必要的损耗;
  • 将ObjectStream方式改为其Kryo方式复制;
  • 去掉只读场景的BeanUtils.copy调用;

5.5、Domain这层的原型对象创建过程优化

完成
平台级提升;

  • Domain这层大对象的创建耗时在150ms左右,再加上频繁调用导致累计耗时增加,对业务的影响直线上升,目前尝试修改创建方式;
  • 框架级问题,影响所有业务功能;
  • 优化方案:将Domain改为单例对象,内部参数改为形参传递;

5.6、流程定义JAXB优化

未开始

  • 流程定义中使用JAXB做xml与对象转换,效率有点低;
  • com.ak.iform.bpmn.core.xml.BpmnXmlUtil.unmarshall

5.6、慢SQL优化

进行中…

  • 流程引擎慢SQL查找、分析、解决;

5.7、缓存设计优化

进行中…

  • 用户上下文缓存;完成
  • 表单定义计算结果缓存;完成
  • 数据字典缓存;完成
  • 菜单资源缓存;完成
  • 接口权限缓存;未开始
  • 持续中…

5.8、优化Web容器参数

  • Undertow
    • UNDERTOW_IO_THREADS=20 #与服务器CPU线程数一致
    • UNDERTOW_WORKER_THREADS=256 #可以根据机器情况增大
    • UNDERTOW_BUFFER_SIZE=1024
    • UNDERTOW_DIRECT_BUFFERS=true

5.9、MySQL参数调优

进行中…

binlog_cache_size = 64M
binlog_format = mixed
max_binlog_cache_size = 128M
max_binlog_size = 200M
sync_binlog = 10
binlog_group_commit_sync_delay = 1
binlog_group_commit_sync_no_delay_count = 100

5.10、日志输出前判断日志级别是否开启

if(logger.isTraceEnabled()) {
    logger.trace("Handle method argument not valid exception...");
}

if(logger.isDebugEnabled()) {
    logger.debug("Handle method argument not valid exception...");
}

if(logger.isInfoEnabled()) {
    logger.info("Handle method argument not valid exception...");
}

if(logger.isErrorEnabled()) {
    logger.error("Handle method argument not valid exception...", e);
}

5.11、字符串拼接禁止使用“+”来处理

使用StringBuilder或StringBuffer替换

5.12、配置属性获取增加适当的静态处理

如:用户验证码获取,captcha配置对象获取消耗CPU资源过高,需要做静态化处理,赋值给内部变量,不要每次都去解析生成新的属性对象。

5.13、加密算法使用性能更高的工具实现

使用java security或spring security

5.14、Bean注入方式不能使用字段注入方式

应该是使用构造函数注入或setter注入方式
注:
1、继承关系不能重复定义字段、重复setter,会造成属性为空
2、慎用Lazy注解

5.15、减少频繁计算

例1:减少频繁读取某个配置属性,可以使用本地变量来控制每个方法读一次

6、Jvm介绍

  • JVM内存区域的划分,年轻代和老年代,他们都发生gc行为,对应young gc(ygc)和full gc(fgc)

  • 年轻代-ygc,程序不会挂起

    eden-伊甸园,变量的出生地
    s0-幸存代0,
    s1-幸存代1,
    以上三个都是年轻代的区域,无论哪个满了都会发生ygc行为,
    eden是变量的出生地,发生ygc就会把还存在引用的变量转移到s0或s1,同时变量还有个类似年龄的值加1
    s0或s1是变量的生活区,两者空间一样大,eden发生gc后的变量就转移到这里,但是两者有且只有一个在工作,
    每当发生ygc,无论当前区域是否满都会把所有变量转移到另一区域,幸存代的所有变量年龄加1,当年龄增加到一定程度(默认为15岁),就将会被晋升到老年代中

  • 老年代-fgc,程序会挂起,等待gc完成
    老年代只有一个区域,满了就会发生fgc,因为fgc的过程中导致程序挂起,必须降低发生次数

6.1、JVM调试

JVM调试工具-jvisulVM

jvisualVM,jdk自带工具,位置在${JAVA_HOME}/bin下,服务器和本地都要安装jdk,注意jre不包含该工具,jdk环境变量JAVA_HOME需要设置好,jdk9及以上的需要另外下载

调试模式

远程调试模式有两种,jmx和jstat模式,jmx稍微麻烦点,但能可以在jvisualVm中看到CPU的使用情况

jstat模式简介:

  1. 服务器上需要安装jdk,并在jdk的bin目录下创建jstatd.all.policy文件,需要全局变量设置有JAVA_HOME
    文件内容如下:
    grant codebase “file:${java.home}/lib/tools.jar” {
    permission java.security.AllPermission “*”, “read,write”;
    };
  2. 在bin目录下执行以下命令,hostname填入服务器ip
    jstatd -J-Djava.security.policy=jstatd.all.policy -J-Djava.rmi.server.hostname=192.168.1.201 &
    jstatd -J-Djava.security.policy=${JAVA_HOME}/bin/jstatd.all.policy -J-Djava.rmi.server.hostname=192.168.1.202 &
  3. 启动jvisualVM,然后添加远程主机,输入服务器ip点击确定,最后选择目标应用

更加具体调试的信息请参考以下链接:
https://www.cnblogs.com/jhxxb/p/13279201.html

jmx模式简介:

  1. jvm启动添加参数”-Djava.rmi.server.hostname=192.168.1.202 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=8050 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false”

  2. 启动jvisualVM,然后添加远程主机,输入服务器ip点击确定,右键主机添加JMX连接

  3. 双击”192.168.1.202:8050”进入应用监控界面

导出jvm Head Dump

jmap -dump:format=b,file=文件名.hprof 进程号
jmap -dump:format=b,file=business.20220104.hprof 899071

调试方式:控制变量法

通过执行同一段压力测试脚本,保持每次变量一致,然后改动一个内存参数,查看内存和gc时间的变化

  • 调试建议:
  1. eden区域一直都会ygc,所以eden和gc time的图形应该是山峰式波形;
  2. eden、s0、s1无论是哪个满了都会发生ygc,导致年轻代变量的年龄加一;
  3. s0和s1应该关注最大值和当前使用最大值,如果当前没有达到最大值,而老年代一直增加,说明gc过于频繁导致变量年龄过快增长,那需要检查年轻代空间过小或者事务过长导致;
  4. 老年代的full gc不应该很频繁甚至应该没有,每次发生的间隔最好以天为单位。

6.2、Jvm参数

以下是常用的参数,加在jar的运行命令中

性能参数

• -XX:NewRatio=2 新生代内存容量与老生代内存容量的比例
• -XX:SurvivorRatio=8 新生代eden和s0内存容量的比例,jdk8默认是3
• -Xms等价于-XX:InitialHeapSize初始化堆内存大小
• -Xmx等价于-XX:MaxHeapSize最大的堆内存大小
• -Xmn等价于-XX:NewSize=size设置初始的年轻代的大小

调试参数

• -XX:-CITime 打印消耗在JIT编译的时间
• -XX:ErrorFile=./hs_err_pid.log 保存错误日志或者数据到文件中
• -XX:HeapDumpPath=./java_pid.hprof 指定导出堆信息时的路径或文件名
• -XX:-HeapDumpOnOutOfMemoryError 当首次遭遇OOM时导出此时堆中相关信息
• -XX:-PrintGC 每次GC时打印相关信息
• -XX:-PrintGC Details 每次GC时打印详细信息
• -XX:-PrintGCTimeStamps 打印每次GC的时间戳

更详细参数请看以下链接:
https://docs.oracle.com/javase/8/docs/technotes/tools/windows/java.html

作者:黄春艳  创建时间:2025-01-23 13:19
最后编辑:黄春艳  更新时间:2025-01-23 13:52