北京网站模板下载,公司网站免费自建,如何有效的推广网站,企业官网免费制作背景
最近针对一个单点登录认证项目进行性能优化#xff0c;在 8核 16G 环境下的认证并发能力从每秒800次提升至每秒1600次#xff0c;性能提升一倍#xff0c;整理此次优化过程中的相关性能优化操作总结和大家分享一下。
Nginx配置优化
在并发认证场景下#xff0c;Ngi…背景
最近针对一个单点登录认证项目进行性能优化在 8核 16G 环境下的认证并发能力从每秒800次提升至每秒1600次性能提升一倍整理此次优化过程中的相关性能优化操作总结和大家分享一下。
Nginx配置优化
在并发认证场景下Nginx 是系统的第一个请求处理层优化其配置可以有效提高系统的吞吐量和响应速度。
基本配置
工作进程数
工作进程数worker_processes配置为服务器 CPU 核心数使用 auto 可以自动匹配 CPU 核心数。
# worker_processes 4;
worker_processes auto;设置为auto后在4核CPU环境下会启动5个进程4个工作进程
1 个 master process Nginx 的主进程用于启动、关闭和管理 Nginx 的工作进程。
4 个 worker process Nginx 的 4 个工作进程Worker Process用于实际处理请求。
最大连接数
Nginx 的默认配置中每个工作进程的最大连接数worker_connections的默认值通常是 1024。
events {worker_connections 65535; # 工作进程的最大连接数multi_accept on; # 一次性接受尽可能多的连接use epoll; # 使用 epoll 模型适合高并发
}Nginx 服务器最大能够支持的并发连接数是由 worker_processes 和 worker_connections 两者共同决定的。最大连接数 worker_processes * worker_connections / 2 读和写会分别占用连接数。
注意 增大 worker_connections 值时需要确保系统的文件描述符ulimit -n设置足够高否则会出现 Too many open files 错误。
文件描述符
文件描述符限制Nginx 进程能打开的最大文件描述符数量建议配置为系统最大值通常与 ulimit -n 保持一致。
worker_rlimit_nofile 65535;在高并发环境中Nginx 作为反向代理服务器或负载均衡器需要同时处理大量的客户端请求。这些请求可能涉及到以下几类文件描述符
客户端连接每一个客户端连接HTTP 请求会占用一个文件描述符。代理到后端服务器的连接Nginx 作为反向代理时与后端服务器的连接也会占用文件描述符。静态资源文件当 Nginx 作为 Web 服务器提供静态资源HTML、CSS、JS 等时打开文件资源也会使用文件描述符。日志文件Nginx 需要记录访问日志和错误日志这些日志文件也会占用文件描述符。
测试3000个请求过程中各个工作进程下文件描述符情况如下
可以看到各个工作进程都在进行请求有一定数量的文件描述符创建。
如果文件描述符数量配置过少在高并发时Nginx 会因为没有足够的文件描述符而拒绝新的连接出现 too many open files 的错误。Nginx文件描述符配置和系统文件描述符配置区别
Nginx文件描述符 worker_rlimit_nofile系统文件描述符配置对象Nginx worker 进程操作系统、Shell 用户配置范围仅限于 Nginx worker 进程整个系统或单个用户 Shell 环境
Gzip 压缩
Nginx 开启 Gzip 压缩减少传输数据量提升响应速度。
gzip on;
gzip_disable msie6;
gzip_vary on; # 启用 Vary 响应头适配不同浏览器
gzip_proxied expired no-cache no-store private auth;
gzip_comp_level 6; # 设置压缩级别为 5-6平衡性能和压缩率
gzip_buffers 16 8k; # 设置缓冲区大小
gzip_http_version 1.1; # 使用 HTTP/1.1 协议进行压缩
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xmlrss text/javascript;浏览器会自动处理 Gzip 解压缩。当服务器返回的响应数据是经过 Gzip 压缩的浏览器会根据 HTTP 响应头中的 Content-Encoding: gzip 自动识别并解压缩数据然后再进行解析和渲染用户无需做任何额外操作。
Gzip 解压缩流程
浏览器发送请求
浏览器发送请求时默认会在 Accept-Encoding 请求头中加入 gzip 或 brBrotli告知服务器它支持 Gzip 或 Brotli 压缩。
GET /web/login HTTP/1.1
Host: example.com
Accept-Encoding: gzip, deflate, br服务器响应请求
服务器检测到浏览器支持 Gzip 压缩并且符合压缩条件就会对返回的数据进行 Gzip 压缩并在响应头中加入 Content-Encoding: gzip。
HTTP/1.1 200 OK
Content-Type: text/html
Content-Encoding: gzip浏览器处理响应
浏览器接收到 Content-Encoding: gzip 的响应后会自动进行解压缩然后渲染页面或解析数据。
Gzip 解压缩测试
可以使用 curl 来测试是否启用了 Gzip 压缩
curl -k -I -H Accept-Encoding: gzip https://your-nginx-server.com如果返回头中包含 Content-Encoding: gzip则说明 Gzip 压缩已启用。
日志配置
高并发下频繁的日志写入可能会成为性能瓶颈。可以调整日志级别、禁用日志或者使用异步记录日志。
access_log off;
# access_log /var/log/nginx/access.log combined buffer32k flush5s aioon;
error_log /var/log/nginx/error.log error;配置解释
禁用日志access_log off日志记录级别设置error日志级别debug info notice warn error crit alert emergaioon 开启异步 I/O 操作结合 buffer 提高日志写入性能。日志缓冲 ( buffered logging ) 和 flush 选项最简单的提升性能方式。syslog 异步记录更为彻底的异步处理方式通过系统日志服务进行处理。
Tomcat配置优化
配置 Tomcat 的连接数和线程池优化设置提高认证服务对大量请求的处理能力和响应效率。
server:tomcat:max-connections: 20000 #Tomcat可以接受的最大连接数10000accept-count: 400 #请求队列200threads:max: 400 #最大线程数200min-spare: 200 #最小空闲线程数10连接数与请求队列优化
max-connections: 20000 Tomcat 最大连接数设置为 20000允许同时维护的客户端连接数增加适用于需要大量并发连接的场景。 避免因连接数不足导致的连接拒绝问题。提高高并发时的吞吐量。
accept-count: 400 设置请求队列的最大长度。当连接数达到 max-connections 时超过此队列长度的请求将被拒绝。 适当增加队列长度减少高负载下的连接拒绝几率。
线程池优化
threads.max: 400 设置最大线程数为 400允许最多同时处理 400 个请求。 提高服务并发处理能力减少请求阻塞时间。
threads.min-spare: 200 设置最小空闲线程数为 200预留线程以应对突发请求流量。 缩短高并发场景下的线程初始化时间。
通过测试逐步调整连接数和线程数的数量以找到适合的配置参数。
Redis操作优化
选择合适的数据结构
设计时选择合理的数据结构存储目标数据例如用户资源账号授权关系使用 Redis 的 Hash 结构进行存储。
避免Redis中keys操作
注意避免keys *操作在大数据量会阻塞 Redis严重影响 Redis 性能。可使用SCAN命令代替它是非阻塞的可以分批次扫描数据。
避免Redis的isExist冗余查询
如果能够通过返回是否null即可判断的情况就不再去isExist冗余查询
通过减少Redis连接查询次数可减少网络资源消耗。
本地缓存优化二级缓存
将一些热点的 key 存储在本地缓存中是为了加速访问频率较高的数据减轻 Redis 服务的压力提升整体系统性能。
热点 Key 的特性
高访问频率某些 key 被频繁访问占用了 Redis 的网络 I/O 和处理资源。数据变化少热点数据通常是读取操作较多写入频率较低因此适合缓存。时效性可控热点数据一般对实时性要求不高可以接受短时间的延迟更新。
批量操作与管道 (Pipelining)
Redis 的每个请求都会涉及一次客户端和服务端的网络通信。传统模式下发送 1000 个请求需要 1000 次往返而使用 Pipeline 后可以一次性发送所有请求减少了网络延迟开销从而提升性能。
private void pubMessages(ListMessage? messages, String channel) {redisTemplate.executePipelined((RedisCallbackListObject) connection - {for (Message message : messages) {// 发布日志消息 通过一批次处理100条connection.publish(channel.getBytes(), JsonUtils.toJson(message).getBytes());}return null;});}Redis连接池
配置 Redis 连接池减少连接创建开销。调整系统环境上合适的连接池的参数如最大连接数、最小空闲连接数。
redisson:threads: 0 #Redisson使用的线程池大小可设置CPU核心数nettyThreads: 0 #Netty线程池的大小决定Netty I/O线程数处理Redis网络请求可设置CPU核心数connectPoolSize: 1000 #Redis连接池的最大连接数connectPoolIdleSize: 100 #连接池中最小的空闲连接数应对突发请求能够迅速响应而不需要创建新的连接connectTimeout: 10000 #客户端在等待 Redis 服务端响应连接请求的最长时间public RedissonClient redisson() throws IOException {Config config new Config();threads (threads 0 ? Runtime.getRuntime().availableProcessors() : threads);nettyThreads (nettyThreads 0 ? Runtime.getRuntime().availableProcessors() : nettyThreads);config.setThreads(threads);config.setNettyThreads(nettyThreads);config.setTransportMode(TransportMode.NIO);// ...return Redisson.create(config);}合理配置 Redis
禁用 AOF 持久化
AOFAppend-Only File是 Redis 的持久化方式按顺序记录每次写操作命令并追加到日志文件中支持精确的数据恢复。其性能消耗主要来自磁盘 I/O受 appendfsync 配置影响always 性能最差但最安全everysec 性能与可靠性折中no 性能最好但可能宕机时丢失数据。
如果业务需要更高性能时禁用 AOF 持久化Redis 不会将写操作日志追加到 AOF 文件中减少磁盘 IO 开销。
appendonly no # 禁用 AOF 持久化如果需要数据持久化场景当配置appendonly yes时通过设置appendfsync来控制日志写入频率。
appendfsync everysecRDB 快照频率
RDBRedis Database是 Redis 的一种快照持久化方式它会按照指定的触发条件如时间或键的变化数将内存中的数据保存到磁盘文件中。RDB 的特点是文件小适合全量备份但在保存快照的过程中会消耗 CPU 和磁盘 I/O 资源特别是对于大数据量或高并发场景。
可以通过禁用 RDB 持久化或者调整持久化策略来提升性能。
#save # 禁用 RDB 持久化
save 3600 1000
# 每隔 3600 秒1小时 检查一次是否需要保存快照。
# 操作条件1000 次 如果在这段时间内有 至少 1000 次数据修改操作Redis 就会触发一次快照保存。Redis I/O 多线程Redis 6.0 及以上版本
Redis I/O 多线程功能主要针对网络 I/O 的性能瓶颈通过多线程并发处理 Redis 的读写请求将网络 I/O 操作分配给多线程处理提升吞吐量和响应速度适合高性能服务器场景。Redis 默认是单线程处理网络请求即使开启了多线程功能也是默认只用于写操作。设置 io-threads-do-reads yes 后Redis 会同时使用多线程处理读请求和写请求适用于 CPU 核心数较多并且CPU没有充分占满的场景。
io-threads 4 # 配置 I/O 线程数量
io-threads-do-reads yes # 是否启用多线程处理读操作可部署时通过脚本动态判断当前系统CPU核心数来决定是否启用I/O多线程。如果CPU核心数大于 16表示这是一个高性能服务器适合启用 Redis 的 I/O 多线程功能。
对于 CPU 核心较少的服务器开启 I/O 多线程可能导致性能下降。I/O 多线程的性能提升更多地体现在网络 I/O而非 Redis 内部操作因此适用于客户端数量多、网络 I/O 密集的场景。通过测试逐步调整 io-threads 的数量以找到适合的线程数避免线程过多带来的上下文切换开销。
避免冗余查询
合并一些配置类的缓存到一个对象中或者同一个配置类的缓存在一次认证方法调用中只需要通过一次查询后以参数的形式进行传递减少Redis的冗余查询次数。
一些通用优化
异步操作
能异步的都尽量异步执行比如日志的记录消息的发送等。
批量操作
1. Kafka消息批量处理
批量消息消费
KafkaListener(topics user, groupId user, containerFactory batchKafkaListenerContainerFactory)
public void userLogListener(ConsumerRecordsString, Message? records) {
}描述这个方法是针对批量 Kafka 消息的消费即每次从 Kafka 消息队列中拉取一批消息 (ConsumerRecords)然后对这批消息进行处理。特点 一次性处理多条消息ConsumerRecords 是 Kafka 提供的批量消费的数据结构包含多条 Message? 消息。Kafka 消费者可以配置 max.poll.records 参数指定每次最多拉取多少条消息。 性能 高吞吐量批量处理能够减少 Kafka 拉取请求的次数并行处理多条消息提高吞吐量。可能增加延迟在消息量较小时Kafka 需要等待足够多的消息到达才会形成一个批次这可能会导致消息延迟增加。
从性能角度来看批量消费方式的吞吐量要远高于单条消费方式但可能会带来稍微高一些的延迟。因此具体选择需要根据业务需求和负载特点进行权衡。
数据库批量保存
认证日志等操作进行Kafka批量订阅后批量保存可使用mybatisplus的批量保存saveBatch方法。
public ResultString batchAdd(ListLogLogin logLoginList) {// ...logLoginService.saveBatch(loginList);// ...}日志打印
减少系统中不必要的LOG日志后台输出或调高日志级别。
Java线程池配置
合理配置Java执行任务线程池大小核心线程数、最大线程数、工作队列大小。
Bean(executeTaskExecutor)public Executor taskExecutor(){ThreadPoolTaskExecutor executornew ThreadPoolTaskExecutor();executor.setThreadPriority(8);executor.setCorePoolSize(Runtime.getRuntime().availableProcessors()*3);executor.setMaxPoolSize(Runtime.getRuntime().availableProcessors()*3);executor.setQueueCapacity(100);executor.setKeepAliveSeconds(120);executor.setThreadGroupName(global-thd-);executor.setThreadNamePrefix(custom-task-);executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());executor.setWaitForTasksToCompleteOnShutdown(true);executor.initialize();return TtlExecutors.getTtlExecutor(executor);}JVM优化
Java8默认垃圾回收器是-XX:UseParallelGC尝试设置 G1 垃圾收集器G1 是一种低延迟、高吞吐量的垃圾回收器适合大内存的应用
指定 JVM 内存大小动态根据服务器内存自动配置设置固定值避免内存收缩。垃圾回收器设置 -XX:UseG1GC 并设置垃圾回收暂停时间目标 -XX:MaxGCPauseMillis300
系统参数优化
Linux操作系统相关可优化操作。
文件描述符
文件描述符是操作系统用来表示打开的资源如文件、套接字对于网络连接文件描述符主要用于表示 TCP/UDP 套接字。操作系统对每个进程的文件描述符数量有限制通常是 1024 或更高。如果连接数超过限制将导致无法接收新连接。为了提高系统网络连接性能可通过ulimit -n命令配置系统的文件描述符限制。
ulimit -n 65535TCP协议连接队列大小
在Linux系统高并发场景下半连接队列SYN 队列和全连接队列accept 队列的大小限制可能成为性能瓶颈。
通过调高这两个队列的大小可以
增强瞬时抗压能力服务器可以容纳更多未处理的连接减少丢包和连接失败。在流量高峰期增加缓冲能力使得连接能够被逐步处理而不是直接丢弃。 通过模拟网络连接测试发现当服务端并发处理大量请求时如果TCP全连接队列过小就容易溢出。发生TCP全连接队溢出的时候后续的请求就会被丢弃这样就会出现服务端请求数量上不去的现象。
半连接队列
服务器来不及处理握手完成队列被填满导致连接失败。
查看半连接队列大小
半连接队列的大小由 tcp_max_syn_backlog 参数决定。默认大小1024如果队列满了多余的连接会被丢弃。
[rootcentos ~]# sysctl net.ipv4.tcp_max_syn_backlog
net.ipv4.tcp_max_syn_backlog 256调整半连接队列大小
sysctl -w net.ipv4.tcp_max_syn_backlog256将参数写入 /etc/sysctl.conf 配置文件永久生效。
echo net.ipv4.tcp_max_syn_backlog 256 /etc/sysctl.conf# 当执行sysctl -p命令时系统会读取/etc/sysctl.conf文件中的配置并应用这些设置
sysctl -p全连接队列
应用层的处理速度跟不上握手完成速度队列满载连接被拒绝。
查看全连接队列大小
全连接队列的大小由 tcp_max_syn_backlog 参数决定。默认大小128如果队列满了多余的连接会被丢弃。
[rootcentos ~]# sysctl net.core.somaxconn
net.core.somaxconn 512调整全连接队列大小
sysctl -w net.core.somaxconn512将参数写入 /etc/sysctl.conf 配置文件永久生效。
echo net.core.somaxconn 512 /etc/sysctl.confss -s 命令查询系统网络连接情况
Total: 1473 (kernel 2330) 总共的网络连接数量为 1473其中内核维护的连接数为 2330。这个数字表示当前系统在内核层面维护的所有连接包括已建立的连接和其他状态的连接。
传输层协议统计主要是 TCP
TCP: 926 (estab 881, closed 3, orphaned 0, synrecv 0, timewait 2/0) estab 881表示当前有 881个 TCP 连接是已建立的连接ESTABLISHED 状态。closed 3表示当前有 3个 TCP 连接已经关闭。orphaned 0表示当前有 0 个连接处于“孤儿”状态通常是异常连接。synrecv 0表示当前没有处于 SYN_RECV 状态的连接。SYN_RECV 状态表示接收到连接请求并且正在等待确认。timewait 2/0表示当前有 2个连接处于 TIME_WAIT 状态0 表示没有正在进行的连接处于半关闭状态。TIME_WAIT 状态表示连接已关闭但仍需要保持一段时间以确保双方都知道连接关闭。
ss -lnt 命令可查询系统的TCP连接情况
ss -lnt
# -l 显示正在监听的socketlistening
# -n 不解析服务名称
# -t 只显示tcp socket在「LISTEN 状态」时Recv-Q/Send-Q 表示的含义如下
Recv-Q:当前全连接队列的大小,也就是当前已完成三次握手并等待服务端 accept()的TCP连接;
Send-Q:当前全连接最大队列长度,上面的输出结果说明监听8443端口的TCP服务,最大全连接长度为511;
在「非LISTEN状态」时,Recv-Q/Send-Q表示的含义如下:
Recv-Q已收到但未被应用进程读取的字节数Send-Q已发送但未收到确认的字节数
总结
本次优化从 Nginx、Redis、Tomcat、TCP、批量操作、二级缓存等多个角度入手最终在 8核16G 环境下将认证系统的并发性能翻倍。 你的支持是我持续创作的动力欢迎点赞、收藏、分享