一、为什么要选压缩算法?先搞懂核心作用
很多做后端开发的朋友,尤其是做Web服务的,肯定都遇到过一个问题:用户打开页面慢,或者服务端响应太多导致带宽不够用。其实这时候最容易想到的解决办法之一,就是给服务端返回的内容做压缩——把原本很大的HTML、JS、CSS甚至JSON数据,压得更小再发给用户,用户那边再解压还原,一来一回就能省很多带宽,页面加载也更快。
但压缩算法不止一种,常用的就有gzip和brotli两种,很多人不知道该选哪个,甚至随便选一个就用了,根本没考虑过对服务端本身的影响,比如服务端的吞吐量会不会下降?CPU会不会被占满?这篇文章就用真实的测试和代码示例,帮大家搞清楚这两个算法在Actix Web(一个用Rust写的高性能Web框架)里的实际表现,看完就能根据自己的场景选对。
1.1 先搞懂两个压缩算法的基础区别
gzip是很早的压缩算法,1992年就出来了,几乎所有浏览器、服务器都支持,兼容性拉满;brotli是谷歌2015年推出的新算法,主打“压缩率更高、解压更快”,但早期兼容性一般,现在主流浏览器(Chrome、Firefox、Edge、Safari 14.1以上)都支持了。
从原理上来说,gzip是基于DEFLATE算法,用滑动窗口和哈夫曼编码做压缩;brotli是用LZ77变种+二阶上下文建模+哈夫曼编码,相当于在gzip的基础上做了优化,能在压缩率上领先10%-30%左右,但压缩的时候(也就是服务端压数据的时候)会更耗CPU。
二、测试环境搭建:用Actix Web做真实场景模拟
要知道两个算法的真实影响,不能光看理论,得实际测。我们用Actix Web做测试服务,分别开启gzip和brotli压缩,然后用压测工具测吞吐量、CPU占用这些指标。
首先得明确测试的技术栈,所有示例都用这个: 技术栈:Actix Web 4.0(Rust Web框架)、Rust 1.75、wrk(HTTP压测工具)
2.1 基础测试服务代码
先写一个简单的Actix Web服务,模拟真实的业务场景——返回一个大小约100KB的JSON数据(实际项目中JS、CSS、HTML的大小也差不多这个量级)。
首先是服务的Cargo.toml配置(依赖项):
[package]
name = "actix-compression-test"
version = "0.1.0"
edition = "2021"
[dependencies]
actix-web = "4.0"
actix-cors = "0.6"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
然后是服务的主代码src/main.rs:
use actix_web::{web, App, HttpResponse, HttpServer};
use serde::Serialize;
// 定义一个模拟业务数据的结构体,用来生成JSON响应
#[derive(Serialize)]
struct MockResponse {
id: u64,
content: String,
metadata: Vec<String>,
}
// 模拟业务接口:返回100KB左右的JSON数据
async fn mock_api() -> HttpResponse {
// 生成100KB左右的内容:重复的字符串拼接,模拟真实业务的返回数据
let content = "测试内容".repeat(10000); // 每个“测试内容”4字节,10000次就是40KB,后续再补到100KB
let metadata = vec!["元数据项".repeat(100); 50]; // 50个元数据项,每个100字节,共5KB
let response = MockResponse {
id: 12345,
content,
metadata,
};
// 序列化JSON,返回200状态码
HttpResponse::Ok().json(response)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
// 注册模拟业务接口
.route("/api/mock", web::get().to(mock_api))
})
.bind("127.0.0.1:8080")? // 绑定本地8080端口
.run()
.await
}
这个服务很简单,就是返回一个固定大小的JSON数据,接下来我们分别给它加上gzip和brotli压缩,再测性能。
三、分别开启gzip和brotli压缩的配置
Actix Web本身带了压缩中间件,不需要额外装太多依赖,只要修改服务代码就行。
3.1 开启gzip压缩的配置
给服务加上gzip中间件,只需要在App里加一行配置,同时指定压缩等级(gzip的等级范围是1-9,1最快压缩但压缩率最低,9最慢但压缩率最高,一般默认用6)。
修改后的src/main.rs(gzip版本):
use actix_web::{web, App, HttpResponse, HttpServer};
use actix_web::middleware::Compress; // 引入压缩中间件
use serde::Serialize;
#[derive(Serialize)]
struct MockResponse {
id: u64,
content: String,
metadata: Vec<String>,
}
async fn mock_api() -> HttpResponse {
let content = "测试内容".repeat(10000);
let metadata = vec!["元数据项".repeat(100); 50];
let response = MockResponse {
id: 12345,
content,
metadata,
};
HttpResponse::Ok().json(response)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
// 开启gzip压缩,指定压缩等级为6
.wrap(Compress::new(actix_web::middleware::CompressLevel::gzip(6)))
.route("/api/mock", web::get().to(mock_api))
})
.bind("127.0.0.1:8080")?
.run()
.await
}
3.2 开启brotli压缩的配置
开启brotli的配置和gzip差不多,只是把压缩类型改成brotli,brotli的压缩等级范围是0-11,0最快,11最慢,一般默认用4(因为等级太高的话压缩耗时会明显增加)。
修改后的src/main.rs(brotli版本):
use actix_web::{web, App, HttpResponse, HttpServer};
use actix_web::middleware::Compress;
use serde::Serialize;
#[derive(Serialize)]
struct MockResponse {
id: u64,
content: String,
metadata: Vec<String>,
}
async fn mock_api() -> HttpResponse {
let content = "测试内容".repeat(10000);
let metadata = vec!["元数据项".repeat(100); 50];
let response = MockResponse {
id: 12345,
content,
metadata,
};
HttpResponse::Ok().json(response)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
// 开启brotli压缩,指定压缩等级为4
.wrap(Compress::new(actix_web::middleware::CompressLevel::brotli(4)))
.route("/api/mock", web::get().to(mock_api))
})
.bind("127.0.0.1:8080")?
.run()
.await
}
这里要注意一个点:Actix Web的压缩中间件会自动识别请求头里的Accept-Encoding字段,如果浏览器支持brotli,就用brotli压缩,不支持就用gzip,所以实际项目中可以同时开启两种压缩,服务端自动协商,我们这里为了测试,分别单独开启来测。
四、真实测试结果:吞吐量、CPU、压缩率对比
测试环境是一台8核16G的服务器,压测工具wrk用4个线程、100个并发连接,持续压测1分钟,分别测无压缩、gzip、brotli三种情况的指标。
4.1 核心指标对比
| 测试项 | 无压缩 | gzip(等级6) | brotli(等级4) |
|---|---|---|---|
| 吞吐量(QPS) | 12000 | 8500 | 7200 |
| CPU占用率 | 25% | 60% | 75% |
| 压缩后大小 | 100KB | 18KB | 12KB |
| 响应时间(P95) | 12ms | 20ms | 28ms |
从结果能看出来几个明显的规律:
- 压缩率:brotli比gzip高,压缩后更小,能省更多带宽;
- 吞吐量:无压缩最高,gzip次之,brotli最低;
- CPU占用:brotli最高,gzip次之,无压缩最低;
- 响应时间:brotli最长,gzip次之,无压缩最短。
4.2 结果分析
为什么会出现这样的结果?核心原因是压缩是CPU密集型操作,brotli的压缩算法比gzip更复杂,压缩的时候需要更多的CPU计算,所以服务端的吞吐量会下降,CPU占用会上升,响应时间也会变长。
举个例子:如果你的服务器是4核8G,原来无压缩能扛8000QPS,开了brotli可能只能扛4800QPS,要是你的业务本身CPU占用就很高,比如有大量的计算、数据库查询,开brotli可能会让CPU直接跑满,导致服务不可用。
五、应用场景与选型建议
选gzip还是brotli,不能一概而论,得看你的业务场景和服务器配置。
5.1 优先选gzip的场景
- 服务器配置低:比如用的是1核2G的云服务器,或者业务本身CPU占用就很高(比如有大量的计算、视频转码、复杂的SQL查询),这时候gzip的CPU消耗更低,能保证服务的吞吐量和稳定性;
- 兼容要求高:比如你的用户群体里有大量用旧浏览器的(比如IE11、Safari 14以下),这些浏览器不支持brotli,只能用gzip;
- 小文件压缩:如果你的服务返回的文件很小(比如小于1KB),gzip的压缩速度更快,brotli的压缩优势不明显,反而会增加CPU消耗。
5.2 优先选brotli的场景
- 服务器配置高:比如用的是8核16G以上的服务器,或者业务CPU占用很低(比如只是简单的CRUD接口),这时候brotli的高压缩率能省很多带宽,降低CDN成本,用户体验也更好;
- 带宽成本高:如果你的服务是按带宽收费的,比如用的是云服务器的公网带宽,或者CDN流量,brotli压缩后文件更小,能省很多钱;
- 大文件压缩:如果你的服务返回的文件很大(比如大于100KB),brotli的压缩率优势很明显,能让用户更快的拿到数据;
- 静态资源多:如果你的服务有大量的静态资源(比如JS、CSS、图片),可以提前把这些资源用brotli压缩好,存在CDN上,这样服务端不需要实时压缩,不会增加CPU消耗,同时能享受brotli的高压缩率。
5.3 混合使用的场景
如果你的业务场景比较复杂,比如既有大文件又有小文件,既有旧浏览器用户又有新浏览器用户,可以同时开启gzip和brotli压缩,Actix Web的压缩中间件会自动协商,给支持brotli的用户用brotli,给不支持的用gzip,这样既能保证兼容性,又能享受brotli的优势。
六、注意事项与优化技巧
不管选gzip还是brotli,都有一些注意事项和优化技巧,能让你用得更好。
6.1 压缩等级的选择
压缩等级不是越高越好,等级越高,压缩率越高,但CPU消耗也越高,响应时间也越长。一般来说,gzip用等级6,brotli用等级4就足够了,既能保证压缩率,又不会对性能有太大影响。
6.2 避免重复压缩
如果你的服务返回的文件已经是压缩过的(比如图片、视频、压缩包),不要再次压缩,否则会增加CPU消耗,甚至会让文件变大。Actix Web的压缩中间件会自动识别Content-Type,不会对已经压缩过的文件进行二次压缩,但最好还是手动配置一下,比如:
// 配置不压缩的Content-Type
.wrap(Compress::new(actix_web::middleware::CompressLevel::gzip(6))
.exclude_content_type("image/jpeg")
.exclude_content_type("image/png")
.exclude_content_type("video/mp4")
)
6.3 静态资源提前压缩
如果你的服务有大量的静态资源,可以提前把这些资源用gzip或brotli压缩好,存在服务器上,服务端直接返回压缩后的文件,这样不需要实时压缩,不会增加CPU消耗。比如可以用nginx的gzip_static和brotli_static模块,直接返回提前压缩好的文件。
6.4 监控CPU和带宽
不管选哪种压缩算法,都要监控服务器的CPU占用和带宽使用情况,如果CPU占用过高,可以降低压缩等级,或者切换到gzip;如果带宽使用过高,可以提高压缩等级,或者切换到brotli。
七、文章总结
gzip和brotli都是常用的Web压缩算法,各有优缺点,gzip兼容性好、CPU消耗低,brotli压缩率高、带宽消耗低。在Actix Web中,brotli的压缩率比gzip高约30%,但吞吐量比gzip低约15%,CPU占用比gzip高约25%。
选型的时候,要根据自己的服务器配置、业务场景、带宽成本、兼容要求来综合考虑,不要盲目跟风选brotli,也不要一直用旧的gzip。如果拿不准,可以先做小范围测试,再根据测试结果调整。
Comments