乐趣区

关于美团:MJDK-如何实现压缩速率的-5-倍提升

MJDK 是基于 OpenJDK 构建的美团 JDK 发行版。本文次要介绍 MJDK 是如何在保障 java.util.zip.* API 及压缩格局兼容性的前提下,实现压缩 / 解压缩速率晋升 5-10 倍的成果。心愿相干的教训可能帮忙到更多的技术同学。

1 前言

数据压缩技术 [1] 因可无效升高数据存储及传输老本,在计算机领域有十分宽泛的利用(包含网络传输、文件传输、数据库、操作系统等场景)。支流压缩技术按其原理可划分为无损压缩 [2]、有损压缩[3] 两类,工作中咱们最罕用的压缩工具 zip 和 gzip,压缩函数库 zlib,都是无损压缩技术的利用。Java 利用中对压缩库的应用包含:解决 HTTP 申请时对 body 的压缩 / 解压缩操作、应用音讯队列服务时对大音讯体(如 >1M)的压缩 / 解压缩、数据库写入前及读取后对大字段的压缩 / 解压缩操作等。常见于监控、广告等波及大数据传输 / 存储的业务场景。

美团根底研发平台已经开发过一种基于 Intel 的 isa-l 库优化的 gzip 压缩工具及 zlib[4] 压缩库(又称:mzlib[5] 库),优化后的压缩速度可晋升 10 倍,解压缩速度能晋升 2 倍,并已在镜像散发、图片解决等场景长期稳固应用。遗憾的是,受限于 JDK[6] 对压缩库调用的底层设计,公司 Java8 服务始终无奈应用优化后的 mzlib 库,也无奈享受压缩 / 解压缩速率晋升带来的收益。为了充分发挥 mzlib 的性能劣势为业务赋能,在 MJDK 的最新版本中,咱们革新并集成了 mzlib 库,实现了 JDK 中 java.util.zip.* 原生类库的优化,可实现在保障 API 及压缩格局兼容性的前提下,将内存数据压缩速率晋升 5-10 倍的成果。本文次要介绍该个性的技术原理,心愿相干的教训给大家带来一些启发或者帮忙。

2 数据压缩技术

计算机领域的数据压缩技术的倒退大抵可分为以下三个阶段:

具体工夫节点如下:

  • 20 世纪 50~80 年代,香农创建信息论,为数据压缩技术奠定了实践根底。期间呈现多种经典算法,如 Huffman 编码、LZ 系列编码等。
  • 1989 年,Phil Katz 推出文件归档软件 PKZIP(zip 前身),并公开文件归档格局 zip 及其应用的数据压缩算法 deflate(Huffman 与 LZ77 的组合算法)的所有技术参数。
  • 1990 年,Info-ZIP 小组基于公开的 deflate 算法编写了可移植的、收费的、开源实现 zip 和 unzip,极大地扩大了 .zip 格局的应用。
  • 1992 年,Info-ZIP 小组基于 zip 的 deflate 算法代码,推出了文件压缩工具 gzip(GUN zip),用于代替 Unix 下的 compress(有专利纠纷)。通常 gzip 会与归档工具 tar 联合应用来生成压缩的归档格局,文件扩大名为 .tar.gz。
  • 1995 年,Info-ZIP 小组成员 Jean-loup Gailly 和 Mark Adler 基于 gzip 源码中的 deflate 算法实现,推出了压缩库:zlib。通过库函数调用的形式,为其余场景(如 PNG 压缩)提供通用的压缩 / 解压缩能力。同年,在 RFC 中公布了 DEFLATE、ZLIB、GZIP 三种数据压缩格局。其中 DEFLATE 是原始压缩数据流格局,ZLIB、GZIP 则是在前者的根底上包装数据头及校验逻辑等。尔后随着 zip、gzip 工具及 zlib 库的广泛应用,DEFLATE 成为互联网时代数据压缩格局的事实标准。
  • 2010 年后,各大型互联网公司陆续开源了新的压缩算法及实现,如:LZFSE(Apple)、Brotli(Google)、Zstandard(Facebook)等,在压缩速度和压缩比方面均有不同水平的晋升。常见的压缩库如下(须要留神的是:因为压缩算法协定的差别,这些函数库不能穿插应用,数据压缩 / 解压缩必须应用同一种算法操作):

3 压缩技术在 Java 中的利用及优化思路

后面咱们介绍了压缩技术的基础知识,本章节次要介绍 MJDK8_mzlib 版本实现压缩速率 5 倍晋升的技术原理。分两局部进行论述:第一局部,介绍原生 JDK 中压缩 / 解压缩 API 的底层原理;第二局部,分享 MJDK 的优化思路。

3.1 Java 语言中压缩 / 解压缩 API 实现原理

Java 语言中,咱们能够应用 JDK 原生压缩类库(java.util.zip.*)或第三方 Jar 包提供的压缩类库两种形式来实现数据压缩 / 解压缩,其底层原理是通过 JNI (Java Native Interface) 机制,调用 JDK 源码或第三方 Jar 包中提供的共享库函数。具体比照如下:

其中在应用形式上,两者区别可参考如下代码。

(1)JDK 原生压缩类库(zlib 压缩库)

zip 文件压缩 / 解压缩代码 demo(Java)

public class ZipUtil {
      // 压缩
    public void compress(File file, File zipFile) {byte[] buffer = new byte[1024];
        try {InputStream     input  = new FileInputStream(file);
            ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(zipFile));
            zipOut.putNextEntry(new ZipEntry(file.getName()));
            int length = 0;
            while ((length = input.read(buffer)) != -1) {zipOut.write(buffer, 0, length);
            }
            input.close();
            zipOut.close();} catch (Exception e) {e.printStackTrace();
        }
    }

  // 解压缩
    public void uncompress(File file, File outFile) {byte[] buffer = new byte[1024];
        try {ZipInputStream input  = new ZipInputStream(new FileInputStream(file));
            OutputStream   output = new FileOutputStream(outFile);
            if (!outFile.getParentFile().exists()) {outFile.getParentFile().mkdir();}
            if (!outFile.exists()) {outFile.createNewFile();
            }

            int length = 0;
            while ((length = input.read(buffer)) != -1) {output.write(buffer, 0, length);
            }
            input.close();
            output.close();} catch (Exception e) {e.printStackTrace();
        }
    }
}

gzip 文件压缩 / 解压缩代码 demo(Java)

public class GZipUtil {public void compress(File file, File outFile) {byte[] buffer = new byte[1024];
        try {InputStream      input  = new FileInputStream(file);
            GZIPOutputStream gzip   = new GZIPOutputStream(new FileOutputStream(outFile));
            int              length = 0;
            while ((length = input.read(buffer)) != -1) {gzip.write(buffer, 0, length);
            }
            input.close();
            gzip.finish();
            gzip.close();} catch (Exception e) {e.printStackTrace();
        }
    }

    public void uncompress(File file, File outFile) {
        try {FileOutputStream out    = new FileOutputStream(outFile);
            GZIPInputStream  ungzip = new GZIPInputStream(new FileInputStream(file));
            byte[]           buffer = new byte[1024];
            int              n;
            while ((n = ungzip.read(buffer)) > 0) {out.write(buffer, 0, n);
            }
            ungzip.close();
            out.close();} catch (Exception e) {e.printStackTrace();
        }
    }
}

(2)第三方压缩类库(此处以 Google 推出的 snappy 压缩库举例,其余第三方类库原理根本相似)分成两步。

第一步:pom 文件中增加依赖 Jar 包(C 语言)

          <dependency>
            <groupId>org.xerial.snappy</groupId>
            <artifactId>snappy-java</artifactId>
            <version>1.1.8.4</version>
        </dependency>

第二步:第二步,调用接口进行压缩 / 解压缩操作(C 语言)

public class SnappyDemo {public static void main(String[] args) {
        String input = "Hello snappy-java! Snappy-java is a JNI-based wrapper of"
                + "Snappy, a fast compresser/decompresser.";
        byte[] compressed = new byte[0];
        try {compressed = Snappy.compress(input.getBytes("UTF-8"));
            byte[] uncompressed = Snappy.uncompress(compressed);
            String result = new String(uncompressed, "UTF-8");
            System.out.println(result);
        } catch (IOException e) {e.printStackTrace();
        }
    }

综上所述,JDK 中默认应用的压缩库是 zlib,尽管业务能够通过第三方 Jar 包的形式应用其余的压缩库算法,然而因为 Snappy 等算法的压缩数据格式与 zlib 反对的 DEFLATE、ZLIB、GZIP 不同,混合应用会有兼容性问题。

除此之外,zlib 库(1995 年推出)自身的迭代速度十分迟缓(起因:利用范围广且稳固、无商业组织保护),这里应用测试集 Silesia corpus 测试了 OpenJDK 7u76(2014 年发行)、8u45(2015 年发行)、8u312(2022 年发行)中内置压缩类库的性能,从图表中可看出,三者在压缩耗时、压缩比两方面均未有显著的优化成果,难以满足业务日益增长的压缩性能需求场景。因而,咱们抉择在 MJDK 中集成 zlib 优化,实现既兼容原生接口实现,又能晋升压缩性能的成果。

Silesia corpus 是压缩办法性能基准测试集,提供一套涵盖现时应用的典型材料类别的档案资料。文件的大小在 6 MB 到 51 MB 之间,文件格式包含 text、exe、html、picture、database、bin data 等。测试数据类别如下:

3.2 MJDK 优化计划

通过 3.1 章节,咱们晓得 Java 原生的 java.util.zip.* 类库中的数据压缩 / 解压缩能力最终是调用 zlib 库实现的,因而 JDK 的压缩性能晋升问题就可转换为对 JDK 应用的 zlib 库的优化。

3.2.1 优化思路

除原生 zlib 外,同样应用 deflate 算法的压缩库有 Intel ISA-L、Intel IPP、Zopfli,间接基于 zlib 源码优化的我的项目有 zlib-cloudflare,它们与 zlib 间的比照如下:

综上,咱们抉择基于 Intel 开源的 ISA-L(原理是应用 intel sse/avx/avx2/avx256 的扩大指令,并行运算多个流来晋升底层函数的执行性能)来实现 zlib 的革新优化。

1. zlib 革新流程(重点在 API 的兼容性革新)

优化后的 mzlib 库在线上稳固运行 3 年以上,压缩速率晋升在 5 倍以上,无效解决了上文提到根底研发平台曾在镜像构建、图片解决等场景面临过压缩 / 解压缩耗时较高的问题。

2. JDK 层面变更

3.2.2 优化成果

测试阐明

  • 测试集:Silesia corpus
  • 测试内容:GZip 压缩 / 解压缩文件、Zip 压缩 / 解压缩文件

测试论断

  • 兼容性测试(通过):革新后的 Java 类库的 Zip、Gzip 压缩 / 解压缩接口可失常应用,与原生 JDK 中的接口穿插进行压缩 / 解压缩操作验证通过。
  • 性能测试(通过):在同一基准 update 版本下,MJDK8_mzlib 数据压缩耗时比 OpenJDK8 升高 5-10 倍,压缩比无较大稳定(减少 3% 左右)。

目前,美团外部的文档协同服务已应用该 MJDK 版本,进行用户协同编辑记录数据(> 6M)的压缩存储,验证了该性能在线上的稳固运行,压缩性能晋升在 5 倍以上。

4 本文作者

艳梅,来自美团根底研发平台。

5 参考文献

  • [1] Comparison of Brotli, Deflate, Zopfli, LZMA, LZHAM and Bzip2 Compression Algorithms
  • [2] zip、gzip、zlib 的区别

正文

  • [1] 数据压缩技术:在不失落有用信息的前提下,通过相应的算法缩减信源数据冗余,从而进步数据存储、传输和解决效率的技术。
  • [2] 无损压缩:利用数据的统计冗余进行压缩,常见的无损压缩编码方法有 Huffman 编码,算术编码,LZ 编码(字典压缩)等。数据统计冗余度的实践限度为 2:1 到 5:1,所以无损压缩的压缩比个别比拟低。这类办法广泛应用于文本数据、程序等须要准确存储数据的压缩,
  • [3] 有损压缩:利用了人类视觉、听觉对图像、声音中的某些频率成分不敏感的个性,容许压缩的过程中损失肯定的信息,以此换来更大的压缩比。广泛应用于语音、图像和视频数据的压缩。
    -[4] zlib:zlib 是基于 DEFLATE 算法实现的,一套齐全开源、通用的无损数据压缩库。也是目前利用最宽泛的压缩库。在网络传输、操作系统、图像处理等畛域均有大量应用。比方:

    • Linux kernel:应用 zlib 以实作网路协定的压缩、档案零碎的压缩以及开机时解压缩本身的外围。
    • libpng—:用于 PNG 图形格局的一个实现,对 bitmap 数据规定了 DEFLATE 作为流压缩办法。
    • HTTP 协定:应用 zlib 对 HTTP 响应头数据进行压缩 / 解压缩。
    • OpenSSH、OpenSSL:以 zlib 达到最佳化加密网路传输。
    • Subversion、Git 和 CVS 等版本控制系统,应用 zlib 来压缩和远端仓库的通信流量。
    • dpkg 和 RPM 等包管理软件:以 zlib 解压缩 RPM 或者其余封包。
  • [5] mzlib:美团基于 Intel 的 isa-l 库优化的 zlib 压缩库。
  • [6] JDK:Java Development Kit,是 Sun 公司针对 Java 开发人员公布的免费软件开发工具包,是 Java 开发的外围组件之一,蕴含了 Java 编译器、Java 虚拟机、Java 类库等开发工具和资源。
  • [7] JNI (Java Native Interface):JNI 是一个本地编程接口。它容许在 Java 虚拟机中运行的 Java 代码与用其余编程语言(如 C、C++ 和汇编)编写的应用程序和库进行互操作。

| 在美团公众号菜单栏对话框回复【2022 年货】、【2021 年货】、【2020 年货】、【2019 年货】、【2018 年货】、【2017 年货】等关键词,可查看美团技术团队历年技术文章合集。

| 本文系美团技术团队出品,著作权归属美团。欢送出于分享和交换等非商业目标转载或应用本文内容,敬请注明“内容转载自美团技术团队”。本文未经许可,不得进行商业性转载或者应用。任何商用行为,请发送邮件至 tech@meituan.com 申请受权。

退出移动版