在分布式系统开发中,数据传输的压缩与解压缩是常见的优化手段,尤其是使用 GZIP 压缩可以显著减少网络传输的数据量。然而,如果客户端和服务端在压缩/解压缩的处理上不一致,就可能引发各种异常,例如日志中出现的 java.util.zip.ZipException: Not in GZIP format。
本文将通过一个实际的 广告请求处理异常案例,深入分析该问题的原因、排查思路,并提供完整的解决方案。同时,我们还会讨论如何在代码层面增强系统的健壮性,以避免类似问题的发生。
在广告投放系统中,客户端(如媒体服务器)通常会向广告服务发送请求,并可能使用 GZIP 压缩请求体以减少网络开销。服务端在接收到请求后,会根据 Content-Encoding: gzip 头自动解压缩数据,然后进行业务处理。
然而,在某个线上环境中,出现了如下错误日志:
2025-05-14 17:39:38.985 ysx-ad-api [http-nio-8066-exec-120] ERROR c.y.w.controller.RequestAdController - buf获取广告请求失败:Not in GZIP format,媒体openApiParam:{"encrypt":false,"isSupportDp":false,"userInfoParam":{"gender":0}}
2025-05-14 17:39:38.986 ysx-ad-api [http-nio-8066-exec-120] ERROR c.y.w.controller.RequestAdController - buf获取广告请求失败堆栈
java.util.zip.ZipException: Not in GZIP format
at java.util.zip.GZIPInputStream.readHeader(GZIPInputStream.java:165)
at java.util.zip.GZIPInputStream.<init>(GZIPInputStream.java:79)
at java.util.zip.GZIPInputStream.<init>(GZIPInputStream.java:91)
at cn.ysx.common.util.HttpUtil.decompress(HttpUtil.java:1426)
at cn.ysx.web.controller.RequestAdController.mediaAd2(RequestAdController.java:218)
...从日志可以看出,服务端在尝试解压缩请求数据时,发现数据并不是合法的 GZIP 格式,导致 ZipException。
在 RequestAdController.mediaAd2 方法中,服务端首先检查请求头是否包含 Content-Encoding: gzip,如果是,则调用 decompress(data) 进行解压缩:
@PostMapping(value = "/test")
public ResponseEntity<byte[]> mediaAd2(@RequestBody byte[] data, @RequestHeader HttpHeaders headers, HttpServletRequest request) throws IOException {
if (headers.containsKey("Content-Encoding") &&
Objects.requireNonNull(headers.get("Content-Encoding")).contains("gzip")) {
data = decompress(data); // 这里抛出 ZipException
}
// 其他业务逻辑...
}Content-Encoding: gzip
客户端应确保:
Content-Encoding: gzip。// 客户端压缩示例(使用 Java GZIPOutputStream)
public byte[] compressData(byte[] data) throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try (GZIPOutputStream gzipOutputStream = new GZIPOutputStream(byteArrayOutputStream)) {
gzipOutputStream.write(data);
}
return byteArrayOutputStream.toByteArray();
}服务端可以在解压缩前增加数据校验,并在解压缩失败时提供更友好的错误处理:
decompress 方法public byte[] decompress(byte[] compressedData) throws IOException {
if (compressedData == null || compressedData.length < 2) {
throw new IllegalArgumentException("Invalid GZIP data: too short");
}
// 检查 GZIP 魔数(0x1F8B)
if (compressedData[0] != (byte) 0x1F || compressedData[1] != (byte) 0x8B) {
throw new ZipException("Not in GZIP format (invalid header)");
}
try (ByteArrayInputStream bis = new ByteArrayInputStream(compressedData);
GZIPInputStream gzipInputStream = new GZIPInputStream(bis);
ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
int len;
while ((len = gzipInputStream.read(buffer)) > 0) {
bos.write(buffer, 0, len);
}
return bos.toByteArray();
}
}@PostMapping(value = "/test")
public ResponseEntity<byte[]> mediaAd2(@RequestBody byte[] data, @RequestHeader HttpHeaders headers, HttpServletRequest request) throws IOException {
try {
if (headers.containsKey("Content-Encoding") &&
Objects.requireNonNull(headers.get("Content-Encoding")).contains("gzip")) {
try {
data = decompress(data);
} catch (ZipException e) {
log.warn("Received malformed GZIP data, treating as uncompressed. Error: {}", e.getMessage());
// 可以选择继续处理原始数据,或返回错误响应
// 这里示例返回 400 Bad Request
return ResponseEntity.badRequest().body("Invalid GZIP data".getBytes());
}
}
// 正常业务逻辑...
} catch (Exception e) {
log.error("Failed to process request", e);
return ResponseEntity.internalServerError().build();
}
}在解压缩前记录关键信息,便于排查问题:
log.debug("Received data length: {}, headers: {}", data.length, headers);
if (data.length >= 2) {
log.debug("First 2 bytes: 0x{} 0x{}",
String.format("%02X", data[0] & 0xFF),
String.format("%02X", data[1] & 0xFF));
}为了避免类似问题,可以采取以下措施:
gzip_decompress_errors 指标。本次问题是由于 客户端错误地设置了 Content-Encoding: gzip 但未真正压缩数据,导致服务端解压缩失败。通过以下方式解决:
在分布式系统中,类似的 协议不一致问题 很常见,因此 代码的鲁棒性 和 清晰的接口约定 至关重要。