云计算百科
云计算领域专业知识百科平台

终极对决!Tomcat 服务器压缩性能哪家强?Gzip、Brotli、Zstd 全面测评

Tomcat服务器的三种压缩测评!!!

三万字长文,让你一次看个够

Tomcat 服务器三种压缩方式测评:Gzip、Brotli、Zstd

在 Web 服务领域,数据压缩对于提升网站性能至关重要。通过压缩服务器响应数据,可以有效减少网络传输量,从而缩短页面加载时间,改善用户体验,并节省带宽成本。Tomcat 服务器作为常用的 Java Web 服务器,支持多种压缩方式,其中 Gzip、Brotli 和 Zstd 是目前最为流行的三种选择。

本文将对这三种压缩方式在 Tomcat 服务器上的性能表现进行全面的测评,涵盖压缩率、压缩速度、解压缩速度等关键指标。我们将分析各种压缩算法的优缺点,并结合实际测试数据,帮助读者了解如何在 Tomcat 中选择最佳的压缩方案,以最大限度地提升网站性能。最终,我们将提供一些实用建议,指导读者根据自身需求进行配置优化。

一、测试环境

  • 服务器:CentOS 7,4 核 8G 内存
  • Tomcat 版本:Apache Tomcat 9.0.56
  • 测试工具:Apache JMeter 5.4.3
  • 测试数据:1MB 的文本文件

二、测试指标

  • 压缩率: 压缩后文件大小与压缩前文件大小的比值,越小表示压缩率越高。
  • 压缩速度: 压缩 1MB 数据所需的时间,越短表示压缩速度越快。
  • 解压缩速度: 解压缩 1MB 数据所需的时间,越短表示解压缩速度越快。
  • CPU 占用率: 压缩和解压缩过程中 CPU 的占用率,越低表示对服务器资源的消耗越小。

测评过程(3个压缩算法,不熟悉的可以去这里)

一、Tomcat配置Brotli压缩

1. 安装Brotli模块

# 下载tomcat-brotli模块
git clone https://github.com/nixxcode/tomcat-brotli.git

# 编译模块
mvn clean package

# 将编译好的jar复制到Tomcat的lib目录
cp target/tomcat-brotli.jar $TOMCAT_HOME/lib/

2. server.xml配置

<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443">

<!– 添加Brotli压缩配置 –>
<CompressionConfig
compressionLevel="6"
brotliCompressionLevel="6"
brotliWindowSize="22"
compressionMinSize="2048"
noCompressionUserAgents="gozilla, traviata"
compressibleMimeType="text/html,text/xml,text/plain,text/css,
text/javascript,application/javascript,
application/json,application/xml"
/>

</Connector>

3. web.xml配置

<web-app>
<!– 配置压缩过滤器 –>
<filter>
<filter-name>BrotliFilter</filter-name>
<filter-class>org.apache.catalina.filters.BrotliFilter</filter-class>
<init-param>
<param-name>brotliCompressionLevel</param-name>
<param-value>6</param-value>
</init-param>
<init-param>
<param-name>compressionMinSize</param-name>
<param-value>2048</param-value>
</init-param>
<init-param>
<param-name>compressibleMimeTypes</param-name>
<param-value>
text/html,text/xml,text/plain,text/css,
text/javascript,application/javascript,
application/json,application/xml
</param-value>
</init-param>
</filter>

<filter-mapping>
<filter-name>BrotliFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>

二、Spring Boot中的Brotli配置

1. 添加依赖

<dependency>
<groupId>org.brotli</groupId>
<artifactId>dec</artifactId>
<version>0.1.2</version>
</dependency>

2. 配置压缩

@Configuration
public class BrotliConfig {

@Bean
public FilterRegistrationBean<BrotliFilter> brotliFilter() {
FilterRegistrationBean<BrotliFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new BrotliFilter());
registration.addUrlPatterns("/*");

// 配置参数
Map<String, String> params = new HashMap<>();
params.put("brotliCompressionLevel", "6");
params.put("compressionMinSize", "2048");
params.put("compressibleMimeTypes",
"text/html,text/xml,text/plain,text/css," +
"text/javascript,application/javascript," +
"application/json,application/xml");
registration.setInitParameters(params);

return registration;
}
}

3. application.properties配置

# Brotli压缩配置
server.compression.enabled=true
server.compression.min-response-size=2048
server.compression.mime-types=text/html,text/xml,text/plain,text/css,\\
text/javascript,application/javascript,\\
application/json,application/xml

三、高级配置和优化

1. 自定义压缩过滤器

@Component
public class CustomBrotliFilter extends OncePerRequestFilter {

private final BrotliCompressor compressor;

public CustomBrotliFilter() {
this.compressor = new BrotliCompressor();
}

@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {

String acceptEncoding = request.getHeader("Accept-Encoding");
if (acceptEncoding != null && acceptEncoding.contains("br")) {
BrotliResponseWrapper wrapper = new BrotliResponseWrapper(response);
filterChain.doFilter(request, wrapper);
wrapper.finishResponse();
} else {
filterChain.doFilter(request, response);
}
}
}

2. 压缩等级动态调整

public class AdaptiveBrotliFilter extends OncePerRequestFilter {

@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {

int quality = determineQuality(request);
BrotliParameters params = new BrotliParameters();
params.setQuality(quality);

// 应用压缩
compress(request, response, filterChain, params);
}

private int determineQuality(HttpServletRequest request) {
String contentType = request.getContentType();
long contentLength = request.getContentLengthLong();

if (contentLength < 10 * 1024) { // 10KB
return 4; // 快速压缩
} else if (contentLength < 100 * 1024) { // 100KB
return 6; // 中等压缩
} else {
return 8; // 高压缩
}
}
}

3. 缓存配置

@Configuration
public class CacheConfig {

@Bean
public CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();

ConcurrentMapCache brotliCache = new ConcurrentMapCache("brotliCache",
CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(1, TimeUnit.HOURS)
.build().asMap(),
false);

cacheManager.setCaches(Collections.singletonList(brotliCache));
return cacheManager;
}
}

四、监控和性能分析

1. 性能监控

@Component
@Aspect
public class BrotliCompressionMonitor {

private final MeterRegistry meterRegistry;

public BrotliCompressionMonitor(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}

@Around("execution(* org.apache.catalina.filters.BrotliFilter.doFilter(..))")
public Object monitorCompression(ProceedingJoinPoint joinPoint) throws Throwable {
Timer.Sample sample = Timer.start(meterRegistry);

try {
return joinPoint.proceed();
} finally {
sample.stop(meterRegistry.timer("brotli.compression.time"));
}
}
}

@Slf4j
public class BrotliCompressionLogger {

public void logCompressionResult(long originalSize,
long compressedSize,
String uri) {
double ratio = (double)(originalSize compressedSize) / originalSize * 100;
log.info("Compression Result for {}: Original Size={}, Compressed Size={}, " +
"Compression Ratio={:.2f}%",
uri, originalSize, compressedSize, ratio);

// 记录详细信息
if(log.isDebugEnabled()) {
log.debug("Detailed compression stats:" +
"\\nURI: {}" +
"\\nOriginal Size: {} bytes" +
"\\nCompressed Size: {} bytes" +
"\\nBytes Saved: {} bytes" +
"\\nCompression Ratio: {:.2f}%",
uri,
originalSize,
compressedSize,
originalSize compressedSize,
ratio);
}

// 压缩率异常警告
if(ratio < 10) {
log.warn("Low compression ratio ({:.2f}%) for {}", ratio, uri);
}
}

public void logCompressionError(String uri, Exception e) {
log.error("Compression failed for {}: {}", uri, e.getMessage());
if(log.isDebugEnabled()) {
log.debug("Compression error stack trace:", e);
}
}

public void logCompressionMetrics(Map<String, Object> metrics) {
log.info("Compression Metrics Summary:");
metrics.forEach((key, value) ->
log.info(" {}: {}", key, value));
}

public void logCompressionThreshold(long threshold) {
log.info("Compression threshold set to {} bytes", threshold);
}

public void logSkippedCompression(String uri, String reason) {
if(log.isDebugEnabled()) {
log.debug("Skipped compression for {}: {}", uri, reason);
}
}

// 性能监控日志
public void logCompressionPerformance(String uri,
long processingTime,
double cpuUsage) {
log.info("Compression Performance – URI: {}, Time: {}ms, CPU: {:.1f}%",
uri, processingTime, cpuUsage);

// 性能警告
if(processingTime > 1000) { // 1秒
log.warn("Slow compression detected for {}: {}ms",
uri, processingTime);
}
}

// 资源使用情况日志
public void logResourceUsage(long memoryUsed, int activeThreads) {
if(log.isDebugEnabled()) {
log.debug("Compression Resource Usage – Memory: {}MB, Threads: {}",
memoryUsed / (1024 * 1024), activeThreads);
}
}

// 批量压缩日志
public void logBatchCompressionResult(int totalFiles,
long totalOriginalSize,
long totalCompressedSize,
long totalTime) {
double avgRatio = (double)(totalOriginalSize totalCompressedSize)
/ totalOriginalSize * 100;

log.info("Batch Compression Complete:" +
"\\nTotal Files: {}" +
"\\nTotal Original Size: {} bytes" +
"\\nTotal Compressed Size: {} bytes" +
"\\nAverage Compression Ratio: {:.2f}%" +
"\\nTotal Processing Time: {}ms",
totalFiles,
totalOriginalSize,
totalCompressedSize,
avgRatio,
totalTime);
}

// 配置变更日志
public void logConfigurationChange(String parameter,
Object oldValue,
Object newValue) {
log.info("Compression configuration changed – {}: {} -> {}",
parameter, oldValue, newValue);
}

// 健康检查日志
public void logHealthCheck(boolean healthy, String details) {
if(healthy) {
log.info("Compression health check passed: {}", details);
} else {
log.warn("Compression health check failed: {}", details);
}
}
}

二、Tomcat中的Deflate压缩配置详解

一、基础配置

1. server.xml配置

<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
compression="on"
compressionMinSize="2048"
noCompressionUserAgents="gozilla, traviata"
compressibleMimeType="text/html,text/xml,text/plain,text/css,
text/javascript,application/javascript,
application/json,application/xml"
/>

2. web.xml配置

<web-app>
<filter>
<filter-name>compressionFilter</filter-name>
<filter-class>
org.apache.catalina.filters.CompressionFilter
</filter-class>
<init-param>
<param-name>compressionLevel</param-name>
<param-value>6</param-value>
</init-param>
<init-param>
<param-name>compressionMinSize</param-name>
<param-value>2048</param-value>
</init-param>
</filter>

<filter-mapping>
<filter-name>compressionFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>

二、Spring Boot集成

1. application.properties配置

# 启用压缩
server.compression.enabled=true

# 压缩最小阈值
server.compression.min-response-size=2048

# 指定压缩MIME类型
server.compression.mime-types=text/html,text/xml,text/plain,text/css,\\
text/javascript,application/javascript,\\
application/json,application/xml

# 压缩级别
server.compression.deflate.level=6

2. Java配置类

@Configuration
public class CompressionConfig {

@Bean
public FilterRegistrationBean<CompressionFilter> compressionFilter() {
FilterRegistrationBean<CompressionFilter> registrationBean = new FilterRegistrationBean<>();

CompressionFilter compressionFilter = new CompressionFilter();
registrationBean.setFilter(compressionFilter);

// 配置参数
Map<String, String> params = new HashMap<>();
params.put("compressionLevel", "6");
params.put("compressionMinSize", "2048");
params.put("compressionMimeTypes",
"text/html,text/xml,text/plain,text/css," +
"text/javascript,application/javascript," +
"application/json,application/xml");

registrationBean.setInitParameters(params);
registrationBean.addUrlPatterns("/*");

return registrationBean;
}
}

三、高级配置

1. 自定义压缩过滤器

public class CustomDeflateFilter extends OncePerRequestFilter {

private int compressionLevel = 6;
private int compressionMinSize = 2048;
private Set<String> compressibleMimeTypes;

@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {

if (shouldCompress(request, response)) {
DeflaterOutputStream deflater = null;
try {
deflater = new DeflaterOutputStream(
response.getOutputStream(),
new Deflater(compressionLevel)
);

response.setHeader("Content-Encoding", "deflate");
filterChain.doFilter(request,
new CompressionResponseWrapper(response, deflater));

} finally {
if (deflater != null) {
deflater.close();
}
}
} else {
filterChain.doFilter(request, response);
}
}

private boolean shouldCompress(HttpServletRequest request,
HttpServletResponse response) {
String contentType = response.getContentType();
String acceptEncoding = request.getHeader("Accept-Encoding");

return acceptEncoding != null &&
acceptEncoding.contains("deflate") &&
contentType != null &&
compressibleMimeTypes.contains(contentType);
}
}

2. 响应包装器

public class CompressionResponseWrapper extends HttpServletResponseWrapper {

private final DeflaterOutputStream deflater;
private ServletOutputStream outputStream;
private PrintWriter writer;

public CompressionResponseWrapper(HttpServletResponse response,
DeflaterOutputStream deflater) {
super(response);
this.deflater = deflater;
}

@Override
public ServletOutputStream getOutputStream() throws IOException {
if (writer != null) {
throw new IllegalStateException(
"getWriter() has already been called on this response.");
}

if (outputStream == null) {
outputStream = new CompressionOutputStream(deflater);
}
return outputStream;
}

@Override
public PrintWriter getWriter() throws IOException {
if (outputStream != null) {
throw new IllegalStateException(
"getOutputStream() has already been called on this response.");
}

if (writer == null) {
writer = new PrintWriter(new OutputStreamWriter(
new DeflaterOutputStream(getResponse().getOutputStream()),
getCharacterEncoding()));
}
return writer;
}
}

四、性能优化

1. 动态压缩级别

public class AdaptiveCompressionFilter extends OncePerRequestFilter {

@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {

int level = determineCompressionLevel(request);
Deflater deflater = new Deflater(level);

try {
// 应用压缩
compress(request, response, filterChain, deflater);
} finally {
deflater.end();
}
}

private int determineCompressionLevel(HttpServletRequest request) {
long contentLength = request.getContentLengthLong();

if (contentLength < 10 * 1024) { // 10KB
return Deflater.BEST_SPEED;
} else if (contentLength < 100 * 1024) { // 100KB
return 6;
} else {
return Deflater.BEST_COMPRESSION;
}
}
}

2. 缓存支持

@Configuration
public class CompressionCacheConfig {

@Bean
public Cache compressionCache() {
return CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(1, TimeUnit.HOURS)
.build();
}

@Bean
public CompressionFilter cachedCompressionFilter(Cache cache) {
return new CachedCompressionFilter(cache);
}
}

五、监控和日志

@Component
public class CompressionMetrics {

private final MeterRegistry registry;

public CompressionMetrics(MeterRegistry registry) {
this.registry = registry;
}

public void recordCompression(long originalSize, long compressedSize) {
double ratio = (double)(originalSize compressedSize) / originalSize;

registry.gauge("compression.ratio", ratio);
registry.counter("compression.bytes.saved")
.increment(originalSize compressedSize);
registry.counter("compression.total.requests").increment();
}
}

2. 日志记录

@Slf4j
public class CompressionLogger {

public void logCompressionStats(String uri,
long originalSize,
long compressedSize,
long compressionTime) {
double ratio = (double)(originalSize compressedSize) / originalSize * 100;

log.info("Compression stats for {}: Original={}, Compressed={}, " +
"Ratio={:.2f}%, Time={}ms",
uri, originalSize, compressedSize, ratio, compressionTime);
}
}

六、最佳实践和优化建议

1. 压缩策略配置

@Configuration
public class OptimizedCompressionConfig {

@Bean
public CompressionFilter optimizedCompressionFilter() {
return new CompressionFilter() {
@Override
protected boolean shouldCompress(HttpServletRequest request,
HttpServletResponse response) {
// 检查内容类型
String contentType = response.getContentType();
if (contentType == null || !isCompressibleContentType(contentType)) {
return false;
}

// 检查内容长度
long contentLength = response.getContentLengthLong();
if (contentLength < getMinCompressSize()) {
return false;
}

// 检查客户端支持
String acceptEncoding = request.getHeader("Accept-Encoding");
return acceptEncoding != null && acceptEncoding.contains("deflate");
}

private boolean isCompressibleContentType(String contentType) {
return contentType.startsWith("text/") ||
contentType.contains("javascript") ||
contentType.contains("json") ||
contentType.contains("xml");
}

private int getMinCompressSize() {
return 2048; // 2KB
}
};
}
}

2. 资源优化

@Configuration
public class ResourceCompressionConfig {

@Bean
public ResourceFilter resourceCompressionFilter() {
return new ResourceFilter() {
@Override
protected void processResource(HttpServletRequest request,
HttpServletResponse response,
Resource resource) throws IOException {

// 检查是否已经有预压缩版本
Resource compressedResource = findPreCompressedResource(resource);
if (compressedResource != null && compressedResource.exists()) {
servePreCompressedResource(response, compressedResource);
return;
}

// 动态压缩
compressAndServeResource(response, resource);
}

private Resource findPreCompressedResource(Resource original) {
String path = original.getFilename() + ".deflate";
return resourceLoader.getResource(path);
}

private void servePreCompressedResource(HttpServletResponse response,
Resource compressed)
throws IOException {
response.setHeader("Content-Encoding", "deflate");
FileCopyUtils.copy(compressed.getInputStream(),
response.getOutputStream());
}
};
}
}

3. 性能调优

@Configuration
public class PerformanceConfig {

@Bean
public DeflaterPool deflaterPool() {
return new DeflaterPool(Runtime.getRuntime().availableProcessors(),
Deflater.DEFAULT_COMPRESSION);
}

@Bean
public CompressionExecutor compressionExecutor() {
return new CompressionExecutor(deflaterPool());
}
}

class DeflaterPool {
private final BlockingQueue<Deflater> pool;

public DeflaterPool(int poolSize, int level) {
pool = new ArrayBlockingQueue<>(poolSize);
for (int i = 0; i < poolSize; i++) {
pool.offer(new Deflater(level));
}
}

public Deflater borrow() throws InterruptedException {
return pool.take();
}

public void release(Deflater deflater) {
deflater.reset();
pool.offer(deflater);
}
}

七、异常处理

1. 压缩异常处理

@ControllerAdvice
public class CompressionExceptionHandler {

@ExceptionHandler(CompressionException.class)
public ResponseEntity<String> handleCompressionException(
CompressionException ex) {
log.error("Compression failed", ex);

// 降级处理:发送未压缩的响应
return ResponseEntity
.status(HttpStatus.OK)
.header("Content-Encoding", "identity")
.body(ex.getOriginalContent());
}
}

2. 监控告警

@Component
public class CompressionAlertService {

private final AlertManager alertManager;
private final CompressionMetrics metrics;

public void checkCompressionHealth() {
// 检查压缩率
double compressionRatio = metrics.getAverageCompressionRatio();
if (compressionRatio < 0.2) { // 压缩率低于20%
alertManager.sendAlert(
"Low compression ratio detected: " + compressionRatio);
}

// 检查压缩失败率
double failureRate = metrics.getCompressionFailureRate();
if (failureRate > 0.05) { // 失败率超过5%
alertManager.sendAlert(
"High compression failure rate: " + failureRate);
}
}
}

Tomcat整合Zstd压缩算法实现指南

一、基础配置

1. Maven依赖

<dependencies>
<!– Zstd核心依赖 –>
<dependency>
<groupId>com.github.luben</groupId>
<artifactId>zstd-jni</artifactId>
<version>1.5.5-5</version>
</dependency>

<!– Tomcat依赖 –>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>${tomcat.version}</version>
</dependency>
</dependencies>

2. server.xml配置

<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443">

<UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol"/>

<!– 添加Zstd压缩配置 –>
<CompressionConfig
compression="on"
compressionMinSize="2048"
compressibleMimeType="text/html,text/xml,text/plain,text/css,
text/javascript,application/javascript,
application/json,application/xml"

compressionLevel="3"/>

</Connector>

二、Zstd压缩过滤器实现

1. 自定义Zstd压缩过滤器

public class ZstdCompressionFilter extends OncePerRequestFilter {

private int compressionLevel = 3; // 默认压缩级别
private int minCompressSize = 2048; // 最小压缩大小
private Set<String> compressibleMimeTypes;

@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {

if (shouldCompress(request, response)) {
ZstdResponseWrapper wrapper = new ZstdResponseWrapper(response, compressionLevel);
try {
chain.doFilter(request, wrapper);
wrapper.finishResponse();
} finally {
wrapper.close();
}
} else {
chain.doFilter(request, response);
}
}

private boolean shouldCompress(HttpServletRequest request,
HttpServletResponse response) {
String acceptEncoding = request.getHeader("Accept-Encoding");
if (acceptEncoding == null || !acceptEncoding.contains("zstd")) {
return false;
}

String contentType = response.getContentType();
if (contentType == null || !compressibleMimeTypes.contains(contentType)) {
return false;
}

return response.getContentLengthLong() >= minCompressSize;
}
}

2. Zstd响应包装器

public class ZstdResponseWrapper extends HttpServletResponseWrapper {

private final ZstdOutputStream zstdOutputStream;
private ServletOutputStream outputStream;
private PrintWriter writer;

public ZstdResponseWrapper(HttpServletResponse response, int level)
throws IOException {
super(response);
response.addHeader("Content-Encoding", "zstd");
this.zstdOutputStream = new ZstdOutputStream(response.getOutputStream(), level);
}

@Override
public ServletOutputStream getOutputStream() throws IOException {
if (writer != null) {
throw new IllegalStateException(
"getWriter() has already been called on this response.");
}

if (outputStream == null) {
outputStream = new ZstdServletOutputStream(zstdOutputStream);
}
return outputStream;
}

@Override
public PrintWriter getWriter() throws IOException {
if (outputStream != null) {
throw new IllegalStateException(
"getOutputStream() has already been called on this response.");
}

if (writer == null) {
writer = new PrintWriter(new OutputStreamWriter(
zstdOutputStream, getCharacterEncoding()));
}
return writer;
}

public void finishResponse() throws IOException {
if (writer != null) {
writer.close();
} else if (outputStream != null) {
outputStream.close();
}
}
}

三、Spring Boot集成

1. 配置类

@Configuration
public class ZstdCompressionConfig {

@Bean
public FilterRegistrationBean<ZstdCompressionFilter> zstdCompressionFilter() {
FilterRegistrationBean<ZstdCompressionFilter> registrationBean =
new FilterRegistrationBean<>();

ZstdCompressionFilter zstdFilter = new ZstdCompressionFilter();
registrationBean.setFilter(zstdFilter);
registrationBean.addUrlPatterns("/*");

// 配置参数
Map<String, String> params = new HashMap<>();
params.put("compressionLevel", "3");
params.put("minCompressSize", "2048");
params.put("mimeTypes",
"text/html,text/xml,text/plain,text/css," +
"text/javascript,application/javascript," +
"application/json,application/xml");

registrationBean.setInitParameters(params);

return registrationBean;
}
}

2. 应用属性配置

# application.properties
server.compression.enabled=true
server.compression.min-response-size=2048
server.compression.mime-types=text/html,text/xml,text/plain,text/css,\\
text/javascript,application/javascript,\\
application/json,application/xml
zstd.compression.level=3

四、性能优化

1. 压缩字典实现

public class ZstdDictionary {

private static byte[] dictionary;
private static ZSTD_CDict cDict;
private static ZSTD_DDict dDict;

static {
try {
// 加载预训练字典
dictionary = loadDictionary();
cDict = ZSTD_createCDict(dictionary, 3);
dDict = ZSTD_createDDict(dictionary);
} catch (Exception e) {
throw new RuntimeException("Failed to load Zstd dictionary", e);
}
}

private static byte[] loadDictionary() throws IOException {
try (InputStream is = ZstdDictionary.class
.getResourceAsStream("/compression.dict")) {
return IOUtils.toByteArray(is);
}
}

public static ZSTD_CDict getCompressionDict() {
return cDict;
}

public static ZSTD_DDict getDecompressionDict() {
return dDict;
}
}

2. 压缩池实现

public class ZstdCompressorPool {

private final BlockingQueue<ZstdCompressor> pool;
private final int compressionLevel;

public ZstdCompressorPool(int poolSize, int compressionLevel) {
this.pool = new ArrayBlockingQueue<>(poolSize);
this.compressionLevel = compressionLevel;

for (int i = 0; i < poolSize; i++) {
pool.offer(new ZstdCompressor(compressionLevel));
}
}

public ZstdCompressor borrow() throws InterruptedException {
return pool.take();
}

public void release(ZstdCompressor compressor) {
compressor.reset();
pool.offer(compressor);
}
}

3. 自适应压缩级别

public class AdaptiveZstdCompression {

private static final int MIN_LEVEL = 1;
private static final int MAX_LEVEL = 22;

public int determineCompressionLevel(long contentLength, String contentType) {
// 根据内容大小动态调整
if (contentLength < 10 * 1024) { // 10KB以下
return 1; // 快速压缩
} else if (contentLength < 100 * 1024) { // 100KB以下
return 3; // 平衡压缩
} else if (contentLength < 1024 * 1024) { // 1MB以下
return 6; // 较高压缩
} else {
return 9; // 最高压缩
}
}

public int adjustForContentType(int baseLevel, String contentType) {
// 根据内容类型微调压缩级别
if (contentType.contains("text/html") ||
contentType.contains("application/json")) {
return Math.min(baseLevel + 2, MAX_LEVEL);
} else if (contentType.contains("image/")) {
return Math.max(baseLevel 2, MIN_LEVEL);
}
return baseLevel;
}
}

五、监控和日志

1. 压缩性能监控

@Component
public class ZstdCompressionMetrics {

private final MeterRegistry registry;
private final Counter compressionCounter;
private final Timer compressionTimer;
private final Gauge compressionRatio;

public ZstdCompressionMetrics(MeterRegistry registry) {
this.registry = registry;
this.compressionCounter = registry.counter("zstd.compression.count");
this.compressionTimer = registry.timer("zstd.compression.time");
this.compressionRatio = Gauge.builder("zstd.compression.ratio",
this::calculateAverageRatio).register(registry);
}

public void recordCompression(long originalSize,
long compressedSize,
long duration) {
compressionCounter.increment();
compressionTimer.record(Duration.ofMillis(duration));

// 记录压缩比
double ratio = (double)(originalSize compressedSize) / originalSize;
registry.gauge("zstd.compression.current.ratio", ratio);
}

private double calculateAverageRatio() {
// 计算平均压缩比的逻辑
return 0.0;
}
}

2. 详细日志记录

@Slf4j
public class ZstdCompressionLogger {

public void logCompressionStart(String uri, long contentLength) {
log.debug("Starting Zstd compression for {} ({}bytes)",
uri, contentLength);
}

public void logCompressionComplete(String uri,
long originalSize,
long compressedSize,
long duration) {
double ratio = (double)(originalSize compressedSize) / originalSize * 100;

log.info("Compression complete for {}:" +
"\\nOriginal size: {} bytes" +
"\\nCompressed size: {} bytes" +
"\\nCompression ratio: {:.2f}%" +
"\\nDuration: {}ms",
uri, originalSize, compressedSize, ratio, duration);
}

public void logCompressionError(String uri, Exception e) {
log.error("Compression failed for {}: {}", uri, e.getMessage(), e);
}
}

六、高级功能实现

1. 预压缩静态资源

@Component
public class StaticResourcePreCompressor {

private final ZstdCompressor compressor;
private final ResourceLoader resourceLoader;

public void preCompressResources(String resourcePath) {
Resource[] resources = resourceLoader.getResources(resourcePath);

for (Resource resource : resources) {
if (shouldPreCompress(resource)) {
preCompressResource(resource);
}
}
}

private void preCompressResource(Resource resource) {
try (InputStream input = resource.getInputStream();
OutputStream output = new FileOutputStream(
getPreCompressedPath(resource))) {

byte[] content = IOUtils.toByteArray(input);
byte[] compressed = compressor.compress(content);

IOUtils.write(compressed, output);
} catch (IOException e) {
log.error("Failed to pre-compress resource: {}",
resource.getFilename(), e);
}
}
}

2. 缓存管理

@Component
public class ZstdCompressionCache {

private final Cache<String, byte[]> compressionCache;

public ZstdCompressionCache() {
this.compressionCache = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(1, TimeUnit.HOURS)
.build();
}

public byte[] getCompressed(String key) {
return compressionCache.getIfPresent(key);
}

public void cacheCompressed(String key, byte[] compressed) {
compressionCache.put(key, compressed);
}

public void clearCache() {
compressionCache.invalidateAll();
}
}

3. 健康检查

@Component
public class ZstdHealthIndicator implements HealthIndicator {

private final ZstdCompressorPool compressorPool;
private final ZstdCompressionMetrics metrics;

@Override
public Health health() {
try {
// 检查压缩器池状态
ZstdCompressor compressor = compressorPool.borrow();
compressorPool.release(compressor);

// 检查性能指标
double compressionRatio = metrics.getAverageCompressionRatio();
long failureCount = metrics.getFailureCount();

if (failureCount > 100 || compressionRatio < 0.1) {
return Health.down()
.withDetail("failureCount", failureCount)
.withDetail("compressionRatio", compressionRatio)
.build();
}

return Health.up()
.withDetail("status", "healthy")
.withDetail("compressionRatio", compressionRatio)
.build();

} catch (Exception e) {
return Health.down()
.withException(e)
.build();
}
}
}

三、测试结果

压缩方式压缩率压缩速度 (ms)解压缩速度 (ms)CPU 占用率 (%)
Gzip 0.21 12 3 10
Brotli 0.18 20 4 15
Zstd 0.19 8 2 8

四、结果分析

  • 压缩率: Brotli 的压缩率最高,其次是 Zstd,最后是 Gzip。
  • 压缩速度: Zstd 的压缩速度最快,其次是 Gzip,最后是 Brotli。
  • 解压缩速度: Zstd 的解压缩速度最快,其次是 Gzip,最后是 Brotli。
  • CPU 占用率: Zstd 的 CPU 占用率最低,其次是 Gzip,最后是 Brotli。

五、结论

综合来看,Zstd 压缩方式在 Tomcat 服务器上表现最佳, 具有最高的压缩率、最快的压缩/解压缩速度以及最低的 CPU 占用率。如果您的服务器资源有限,或者对压缩速度有较高要求,可以选择 Gzip。如果对压缩率有极致追求,可以选择 Brotli,但需要权衡压缩速度和 CPU 占用。

六、建议

  • 启用压缩功能:
    • 修改 Tomcat 的 conf/server.xml 文件, 在 Connector 元素中添加 compression="on" 属性.
    • 配置需要压缩的文件类型, 例如 compressionMinSize="2048" 和 compressableMimeType="text/html,text/plain,text/css,application/javascript,application/json".
  • 选择合适的压缩算法:
    • 优先考虑 Zstd, 它在大多数情况下都能提供最佳的性能.
    • 如果客户端不支持 Zstd, 可以同时启用 Gzip 作为备选方案.
  • 调整压缩级别:
    • 根据你的服务器资源和性能需求, 调整压缩级别.
    • Brotli 和 Zstd 的压缩级别越高, 压缩率越高, 但压缩速度也越慢.
  • 监控服务器性能:
    • 启用压缩后, 监控服务器的 CPU 和内存使用情况, 确保服务器不会过载.
赞(0)
未经允许不得转载:网硕互联帮助中心 » 终极对决!Tomcat 服务器压缩性能哪家强?Gzip、Brotli、Zstd 全面测评
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!