一、为什么要选压缩算法?先搞懂核心作用

很多做后端开发的朋友,尤其是做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

从结果能看出来几个明显的规律:

  1. 压缩率:brotli比gzip高,压缩后更小,能省更多带宽;
  2. 吞吐量:无压缩最高,gzip次之,brotli最低;
  3. CPU占用:brotli最高,gzip次之,无压缩最低;
  4. 响应时间:brotli最长,gzip次之,无压缩最短。

4.2 结果分析

为什么会出现这样的结果?核心原因是压缩是CPU密集型操作,brotli的压缩算法比gzip更复杂,压缩的时候需要更多的CPU计算,所以服务端的吞吐量会下降,CPU占用会上升,响应时间也会变长。

举个例子:如果你的服务器是4核8G,原来无压缩能扛8000QPS,开了brotli可能只能扛4800QPS,要是你的业务本身CPU占用就很高,比如有大量的计算、数据库查询,开brotli可能会让CPU直接跑满,导致服务不可用。

五、应用场景与选型建议

选gzip还是brotli,不能一概而论,得看你的业务场景和服务器配置。

5.1 优先选gzip的场景

  1. 服务器配置低:比如用的是1核2G的云服务器,或者业务本身CPU占用就很高(比如有大量的计算、视频转码、复杂的SQL查询),这时候gzip的CPU消耗更低,能保证服务的吞吐量和稳定性;
  2. 兼容要求高:比如你的用户群体里有大量用旧浏览器的(比如IE11、Safari 14以下),这些浏览器不支持brotli,只能用gzip;
  3. 小文件压缩:如果你的服务返回的文件很小(比如小于1KB),gzip的压缩速度更快,brotli的压缩优势不明显,反而会增加CPU消耗。

5.2 优先选brotli的场景

  1. 服务器配置高:比如用的是8核16G以上的服务器,或者业务CPU占用很低(比如只是简单的CRUD接口),这时候brotli的高压缩率能省很多带宽,降低CDN成本,用户体验也更好;
  2. 带宽成本高:如果你的服务是按带宽收费的,比如用的是云服务器的公网带宽,或者CDN流量,brotli压缩后文件更小,能省很多钱;
  3. 大文件压缩:如果你的服务返回的文件很大(比如大于100KB),brotli的压缩率优势很明显,能让用户更快的拿到数据;
  4. 静态资源多:如果你的服务有大量的静态资源(比如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。如果拿不准,可以先做小范围测试,再根据测试结果调整。