性能优化漫谈(一):无代码调优
当你接手一个老项目,可能发现程序在服务器上运行性能低得可怕,与此同时现网流量还在逐渐增长。也许运用最新框架、微服务容器化、异步协程等方法来次彻底的重构,能够挽狂澜于既倒。可惜时不我待,运维已经在要求加机器了,而坏消息是,原有框架还不支持水平扩展,没法通过堆机器解决。有没有办法在不进行大改的情况下,度过难关呢?
这个时候你需要好好审视下你的服务器上到底发生了什么。否则,即使你重构完,也依然会再度面临这些问题。
CPU
你的服务器上CPU使用率如何?使用top
命令观察下。
如果你的工作进程负载很高,进程数量少,但是系统整体空闲,说明你需要提高工作进程的数量了(前提是程序支持多进程,最好有选项配置)。反之,如果工作进程很多,系统整体负载高,单个进程负载低,说明你的工作进程太多了,需要减少进程数量。一般推荐服务器上有多少核,就起多少进程,过少的进程导致浪费硬件资源,过多的进程导致频繁切换调度引起不必要的损耗。对于Nginx等程序,甚至支持绑核,能够进一步减少进程切换引起的损耗。
磁盘
你的服务器上磁盘使用率如何?使用iostat
命令观察下。
如果磁盘IO等待时间长,读写耗时长,读写量很大甚至接近硬盘读写上限,则需要定位下到底是哪些进程在进行大量磁盘读写。使用iotop -o
命令观察下,可以按<>
按键选定列进行排序。
如果发现有工作进程一直在大量读写,或者周期性地大量读写,可以通过lsof -p 进程号
观察进程在读写哪些文件,注意周期性的读写需要多次执行才可能捕获到。
大量的磁盘写操作可能是进程的日志打印太多了,例如生产环境打印了DEBUG日志,甚至有大量的请求和返回原始数据被写入日志。通常都会允许设置日志级别,建议至少设置为INFO,推荐设置为ERROR级别。
如果你的服务托管在web服务器,例如Apache或者Nginx,要小心是否有额外的日志被打印。例如有问题的Nginx配置将打印完整的请求access log,并且不设置滚动。随着时间推移,甚至可能导致磁盘被耗尽。
HTTPS
你的服务提供的是HTTPS连接,但是工作在内网,又或者并无安全需要,那么可以考虑提供HTTP连接,对于静态资源还能利用CDN进行加速。相比HTTP,HTTPS会耗费较大的连接时间用于SSL握手,并消耗一定的CPU用于对数据加解密,增加响应时延。但是如果你的服务有安全性要求,则务必酌情开启。
长连接
如果你的服务提供的是短连接场景,响应延迟较低但吞吐量较高,那么反复的建立并迅速关闭连接也会损耗较大的性能。如果服务本身支持长连接,且没有被不可信任的客户端攻击的风险,则可以酌情考虑开启。注意设置请求次数上限和超时时间自动断开连接。
系统参数调整
Linux系统有很多网络参数例如默认最大连接数,以及其他配置例如最大打开的文件句柄数。如果你的服务在响应大量的请求,但是有用户报告经常连接超时,那么需要注意排查下内核参数设置了。使用命令观察丢包数量:netstat -s | egrep "listen|LISTEN"
,例如如下输出表明服务器上出现了大量的SYNC丢包
123456 times the listen queue of a socket overflowed(全连接队列丢包)
132456 SYNs to LISTEN sockets dropped (半连接队列丢包)
原因是TCP在三次握手建立连接的时候,服务器端会维护半连接队列和全连接队列两个队列,如果队列长度过小,则根据其他系统参数可能直接丢弃发送RST给客户端,也可能等待重试此时客户端可能报连接超时。
跟队列长度和行为有关的系统参数如下,可以使用sysctl
命令进行修改,具体数值根据服务QPS设计规格进行调整:
net.core.somaxconn = 4096
net.ipv4.tcp_max_syn_backlog = 4096
net.ipv4.tcp_abort_on_overflow = 1
Nginx配置也需要对应调整,重启生效
listen 80 backlog=4096;
listen 443 backlog=4096;
数据库
数据库未必和服务部署在同一个城市,会导致访问延迟加大。如果服务依赖直接读取数据库,可以考虑在服务器所在的地域部署只读从库。如果同时需要读写,但没有强一致的要求,则可以考虑异地双活部署。
其他
以上几点如果都仔细推敲,正确设置,在不修改代码的情况下,也能实现一定程度的性能提升。除此之外,还有其他的方法例如添加缓存等,但可能需要修改代码编译发布,这里就不做展开了。
延伸阅读
1、https://juejin.im/entry/6844903506953011213
- 06-图2 Saving James Bond - Easy Version
- 06-图1 列出连通集
- Bootstrap快速入门
- 常用工具(Windows版本)
- Hadoop快速入门
- Lake Counting(POJ-2386)
- Vue快速入门
- 04-树6. Huffman Codes--优先队列(堆)在哈夫曼树与哈夫曼编码上的应用
- SpringAOP实战应用
- 04-树5. File Transfer--并查集
- React快速入门
- 04-树4. Root of AVL Tree-平衡查找树AVL树的实现
- Java并发编程快速学习
- Stanford机器学习笔记-7. Machine Learning System Design
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 【论文解读】无需额外数据、Tricks、架构调整,CMU开源首个将ResNet50精度提升至80%+新方法
- 机器学习模型调参指南(附代码)
- Flink SQL 自定义函数指南 - 以读取 GBK 编码的数据库为例
- 光照不均匀图像分割技巧1——分块阈值
- MySQL 8.0新特性 — 不可见索引
- 【小白学PyTorch】9.tensor数据结构与存储结构
- 【Python相关】jupyter平台最强插件没有之一
- 基于 OpenCV 的图像分割
- 再见,可视化!你好,Pandas!
- 40000字 Matplotlib 实操干货,真的全!
- Python自动化(二十) | 聊聊 Python 操作PDF的几种方法(合并、拆分、水印、加密)
- C语言发展史的点点滴滴
- 我写了一个R包,简化芯片的差异分析
- 【收藏】万字解析Scipy的使用技巧!
- Python 如何使用 HttpRunner 做接口自动化测试