首页 > 其他分享 >终极对决!Tomcat 服务器压缩性能哪家强?Gzip、Brotli、Zstd 全面测评

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

时间:2024-10-31 22:20:02浏览次数:3  
标签:return Tomcat Zstd 压缩 private response Brotli new public

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

三万字长文,现在这同样的文章在csdn不多了,家人们点点赞!!!

欢迎订阅专栏,永不收费,hacker精神,更快获得第一手优质博文!!!

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 占用率 (%)
Gzip0.2112310
Brotli0.1820415
Zstd0.19828

四、结果分析

  • 压缩率: 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 和内存使用情况, 确保服务器不会过载.

标签:return,Tomcat,Zstd,压缩,private,response,Brotli,new,public
From: https://blog.csdn.net/jsjbrdzhh/article/details/143418139

相关文章

  • Tomcat
    tomcat是apache旗下的一个开源项目是一个常用的服务器可以用来部署我们javaweb的项目我们可以在官网下载对应版本的tomcat,其中我使用的是11,对于tomcat可以直接下载然后绿色安装即可,安装之后再文件中bin目录中为可执行文件,conf为配置文件,lib中存放依赖的jar包,在webapp文......
  • Tomcat 8 报错:FAIL - Application at context path /xxxx could not be started
    一、问题描述在本地Tomcat部署项目后,在浏览器中打开项目报错Theoriginserverdidnotfindacurrentrepresentationforthetargetresourceorisnotwillingtodisclosethatoneexists造成这个错误的原因有很多,可能是路径错误,也可能是项目编译包错误,亦或是版本不......
  • Linux安装Tomcat
    Linux安装Tomcat下载Tomcat打开浏览器,进入Tomcat官网选择要下载的Tomcat版本,下载.tar.gz安装Tomcat将下载的.tar.gz上传至linux服务器,并进行解压tarxzfapache-tomcat-9.0.XX.tar.gz-C/opt/tomcat配置环境变量编辑环境变量文件:vim/etc/profile在文件末尾添加以下......
  • tomcat自启动
    为了让Tomcat自启动,你可以将其作为服务安装在你的操作系统上。以下是在Windows和Linux上设置自启动的方法。Windows确保你已经安装了Tomcat。打开命令提示符(以管理员身份)。导航到Tomcat的bin目录。运行service.batinstall以安装Tomcat作为服务。现在,Tomc......
  • springmvc-servlet.xml和web.xml文件的存放路径是哪里?项目添加到Tomcat上运行后就报错
        用eclipse写了一个简单的web项目,springmvc-servlet.xml文件和web.xml文件都配置好了,运行起来能看见hello的web页面,但是有一堆报错,不知道是什么原因                                     ......
  • 禁用tomcat缓存过滤器
    <!--去掉tomcat的etag和Last-Modified响应头的过滤器--> <filter> <filter-name>noetag</filter-name> <filter-class>com.epoint.basic.filter.EpointNoETagFilter</filter-class> </filter> <filter-mapping> <f......
  • Tomcat弱口令上传war包
    Tomcat弱口令上传war包思路:​ 利用弱口令登录管理页面--->部署war包--->getshell环境:​ vulhub靶场:tomcat/tomcat8​ 启动:sudodocker-composeup-dtomcat弱口令:​ 默认页面,访问manager​​ 随便输入用户名+密码,bp抓包,可以看到用户名+密码被base64加密放在......
  • 【Spring-boot】项目部署到tomcat容器中
    一、pom.xml文件配置1.1在pom.xml里设置 <packaging>war</packaging>1.2移除嵌入式tomcat插件<!--排除tomcat--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId&g......
  • Tomcat部署
    五个步骤关闭startup.dat使用Ctrl+c。更改默认端口找到8080端口更改即可更改startup.bat输出乱码问题原来此位置是utf-8改为GBK即可......
  • Netty、Go、Apache Tomcat、grpc-go、jetty、nghttp2、Apache Traffic Server是什么
    这些都是与网络编程和服务器应用相关的技术,下面我将分别简要介绍它们:Netty:Netty是一个异步事件驱动的网络应用程序框架,用于快速开发高性能、高可靠性的网络服务器和客户端程序。它支持多种协议,包括HTTP、HTTPS、FTP、SMTP等,广泛应用于游戏、移动、物联网、大数据等领域。......