learn.lianglianglee.com/专栏/消息队列高手课/19 数据压缩:时间换空间的游戏.md.html
2022-05-11 18:57:05 +08:00

907 lines
26 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>19 数据压缩:时间换空间的游戏.md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/专栏/消息队列高手课/00 开篇词 优秀的程序员,你的技术栈中不能只有“增删改查”.md.html">00 开篇词 优秀的程序员,你的技术栈中不能只有“增删改查”.md.html</a>
</li>
<li>
<a href="/专栏/消息队列高手课/00 预习 怎样更好地学习这门课?.md.html">00 预习 怎样更好地学习这门课?.md.html</a>
</li>
<li>
<a href="/专栏/消息队列高手课/01 为什么需要消息队列?.md.html">01 为什么需要消息队列?.md.html</a>
</li>
<li>
<a href="/专栏/消息队列高手课/02 该如何选择消息队列?.md.html">02 该如何选择消息队列?.md.html</a>
</li>
<li>
<a href="/专栏/消息队列高手课/03 消息模型:主题和队列有什么区别?.md.html">03 消息模型:主题和队列有什么区别?.md.html</a>
</li>
<li>
<a href="/专栏/消息队列高手课/04 如何利用事务消息实现分布式事务?.md.html">04 如何利用事务消息实现分布式事务?.md.html</a>
</li>
<li>
<a href="/专栏/消息队列高手课/05 如何确保消息不会丢失.md.html">05 如何确保消息不会丢失.md.html</a>
</li>
<li>
<a href="/专栏/消息队列高手课/06 如何处理消费过程中的重复消息?.md.html">06 如何处理消费过程中的重复消息?.md.html</a>
</li>
<li>
<a href="/专栏/消息队列高手课/07 消息积压了该如何处理?.md.html">07 消息积压了该如何处理?.md.html</a>
</li>
<li>
<a href="/专栏/消息队列高手课/08 答疑解惑(一) 网关如何接收服务端的秒杀结果?.md.html">08 答疑解惑(一) 网关如何接收服务端的秒杀结果?.md.html</a>
</li>
<li>
<a href="/专栏/消息队列高手课/09 学习开源代码该如何入手?.md.html">09 学习开源代码该如何入手?.md.html</a>
</li>
<li>
<a href="/专栏/消息队列高手课/10 如何使用异步设计提升系统性能?.md.html">10 如何使用异步设计提升系统性能?.md.html</a>
</li>
<li>
<a href="/专栏/消息队列高手课/11 如何实现高性能的异步网络传输?.md.html">11 如何实现高性能的异步网络传输?.md.html</a>
</li>
<li>
<a href="/专栏/消息队列高手课/12 序列化与反序列化:如何通过网络传输结构化的数据?.md.html">12 序列化与反序列化:如何通过网络传输结构化的数据?.md.html</a>
</li>
<li>
<a href="/专栏/消息队列高手课/13 传输协议:应用程序之间对话的语言.md.html">13 传输协议:应用程序之间对话的语言.md.html</a>
</li>
<li>
<a href="/专栏/消息队列高手课/14 内存管理:如何避免内存溢出和频繁的垃圾回收?.md.html">14 内存管理:如何避免内存溢出和频繁的垃圾回收?.md.html</a>
</li>
<li>
<a href="/专栏/消息队列高手课/15 Kafka如何实现高性能IO.md.html">15 Kafka如何实现高性能IO.md.html</a>
</li>
<li>
<a href="/专栏/消息队列高手课/16 缓存策略如何使用缓存来减少磁盘IO.md.html">16 缓存策略如何使用缓存来减少磁盘IO.md.html</a>
</li>
<li>
<a href="/专栏/消息队列高手课/17 如何正确使用锁保护共享数据,协调异步线程?.md.html">17 如何正确使用锁保护共享数据,协调异步线程?.md.html</a>
</li>
<li>
<a href="/专栏/消息队列高手课/18 如何用硬件同步原语CAS替代锁.md.html">18 如何用硬件同步原语CAS替代锁.md.html</a>
</li>
<li>
<a class="current-tab" href="/专栏/消息队列高手课/19 数据压缩:时间换空间的游戏.md.html">19 数据压缩:时间换空间的游戏.md.html</a>
</li>
<li>
<a href="/专栏/消息队列高手课/20 RocketMQ Producer源码分析消息生产的实现过程.md.html">20 RocketMQ Producer源码分析消息生产的实现过程.md.html</a>
</li>
<li>
<a href="/专栏/消息队列高手课/21 Kafka Consumer源码分析消息消费的实现过程.md.html">21 Kafka Consumer源码分析消息消费的实现过程.md.html</a>
</li>
<li>
<a href="/专栏/消息队列高手课/22 Kafka和RocketMQ的消息复制实现的差异点在哪.md.html">22 Kafka和RocketMQ的消息复制实现的差异点在哪.md.html</a>
</li>
<li>
<a href="/专栏/消息队列高手课/23 RocketMQ客户端如何在集群中找到正确的节点.md.html">23 RocketMQ客户端如何在集群中找到正确的节点.md.html</a>
</li>
<li>
<a href="/专栏/消息队列高手课/24 Kafka的协调服务ZooKeeper实现分布式系统的“瑞士军刀”.md.html">24 Kafka的协调服务ZooKeeper实现分布式系统的“瑞士军刀”.md.html</a>
</li>
<li>
<a href="/专栏/消息队列高手课/25 RocketMQ与Kafka中如何实现事务.md.html">25 RocketMQ与Kafka中如何实现事务.md.html</a>
</li>
<li>
<a href="/专栏/消息队列高手课/26 MQTT协议如何支持海量的在线IoT设备.md.html">26 MQTT协议如何支持海量的在线IoT设备.md.html</a>
</li>
<li>
<a href="/专栏/消息队列高手课/27 Pulsar的存储计算分离设计全新的消息队列设计思路.md.html">27 Pulsar的存储计算分离设计全新的消息队列设计思路.md.html</a>
</li>
<li>
<a href="/专栏/消息队列高手课/28 答疑解惑我的100元哪儿去了.md.html">28 答疑解惑我的100元哪儿去了.md.html</a>
</li>
<li>
<a href="/专栏/消息队列高手课/29 流计算与消息通过Flink理解流计算的原理.md.html">29 流计算与消息通过Flink理解流计算的原理.md.html</a>
</li>
<li>
<a href="/专栏/消息队列高手课/30 流计算与消息在流计算中使用Kafka链接计算任务.md.html">30 流计算与消息在流计算中使用Kafka链接计算任务.md.html</a>
</li>
<li>
<a href="/专栏/消息队列高手课/31 动手实现一个简单的RPC框架原理和程序的结构.md.html">31 动手实现一个简单的RPC框架原理和程序的结构.md.html</a>
</li>
<li>
<a href="/专栏/消息队列高手课/32 动手实现一个简单的RPC框架通信与序列化.md.html">32 动手实现一个简单的RPC框架通信与序列化.md.html</a>
</li>
<li>
<a href="/专栏/消息队列高手课/33 动手实现一个简单的RPC框架客户端.md.html">33 动手实现一个简单的RPC框架客户端.md.html</a>
</li>
<li>
<a href="/专栏/消息队列高手课/34 动手实现一个简单的RPC框架服务端.md.html">34 动手实现一个简单的RPC框架服务端.md.html</a>
</li>
<li>
<a href="/专栏/消息队列高手课/35 答疑解惑(三):主流消息队列都是如何存储消息的?.md.html">35 答疑解惑(三):主流消息队列都是如何存储消息的?.md.html</a>
</li>
<li>
<a href="/专栏/消息队列高手课/加餐 JMQ的Broker是如何异步处理消息的.md.html">加餐 JMQ的Broker是如何异步处理消息的.md.html</a>
</li>
<li>
<a href="/专栏/消息队列高手课/结束语 程序员如何构建知识体系?.md.html">结束语 程序员如何构建知识体系?.md.html</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>19 数据压缩:时间换空间的游戏</h1>
<p>你好,我是李玥。</p>
<p>这节课我们一起来聊一聊数据压缩。我在前面文章中提到过,我曾经在一台配置比较高的服务器上,对 Kafka 做过一个极限的性能压测,想验证一下 Kafka 到底有多快。我使用的种子消息大小为 1KB只要是并发数量足够多不开启压缩时可以打满万兆网卡的全部带宽TPS 接近 100 万。开启压缩时TPS 可以达到 2000 万左右,吞吐量提升了大约 20 倍!</p>
<p>算术好的同学可能会立刻反驳我说2000 万 TPS 乘以 1KB 的消息大小,再把字节 Byte 转换成比特 bit换算成网络传输的带宽是 200Gb/s服务器网卡根本达不到这么大的传输带宽</p>
<p>我们的测试服务器的网卡就是普通的万兆网卡,极限带宽也就是 10Gb/s压测时候的实际网络流量大概在 7Gb/s 左右。这里面,最重要的原因就是,我在测试的时候开启了 Kafka 的压缩功能。可以看到,对于 Kafka 来说,使用数据压缩,提升了大概几十倍的吞吐量。当然,在实际生产时,不太可能达到这么高的压缩率,但是合理地使用数据压缩,仍然可以做到提升数倍的吞吐量。</p>
<p>所以,**数据压缩不仅能节省存储空间,还可以用于提升网络传输性能。**这种使用压缩来提升系统性能的方法,不仅限于在消息队列中使用,我们日常开发的应用程序也可以使用。比如,我们的程序要传输大量的数据,或者要在磁盘、数据库中存储比较大的数据,这些情况下,都可以考虑使用数据压缩来提升性能,还能节省网络带宽和存储空间。</p>
<p>那如何在你的程序中使用压缩?应该选择什么样的压缩算法更适合我们的系统呢?这节课,我带你一起学习一下,使用数据压缩来提升系统性能的方法。</p>
<h2>什么情况适合使用数据压缩?</h2>
<p>在使用压缩之前,首先你需要考虑,当前这个场景是不是真的适合使用数据压缩。</p>
<p>比如,进程之间通过网络传输数据,这个数据是不是需要压缩呢?我和你一起来对比一下:</p>
<ul>
<li>不压缩直接传输需要的时间是: 传输<strong>未压缩</strong>数据的耗时。</li>
<li>使用数据压缩需要的时间是: 压缩耗时 + 传输<strong>压缩</strong>数据耗时 + 解压耗时。</li>
</ul>
<p>到底是压缩快,还是不压缩快呢?其实不好说。影响的因素非常多,比如数据的压缩率、网络带宽、收发两端服务器的繁忙程度等等。</p>
<p>压缩和解压的操作都是计算密集型的操作,非常耗费 CPU 资源。如果你的应用处理业务逻辑就需要耗费大量的 CPU 资源,就不太适合再进行压缩和解压。</p>
<p>又比如说,如果你的系统的瓶颈是磁盘的 IO 性能CPU 资源又很闲,这种情况就非常适合在把数据写入磁盘前先进行压缩。</p>
<p>但是,如果你的系统读写比严重不均衡,你还要考虑,每读一次数据就要解压一次是不是划算。</p>
<p><strong>压缩它的本质是资源的置换,是一个时间换空间,或者说是 CPU 资源换存储资源的游戏。</strong></p>
<p>就像木桶的那个短板一样,每一个系统它都有一个性能瓶颈资源,可能是磁盘 IO网络带宽也可能是 CPU。如果使用压缩能用长板来换一些短板那总体上就能提升性能这样就是划算的。如果用了压缩之后短板更短了那就不划算了不如不用。</p>
<p>如果通过权衡,使用数据压缩确实可以提升系统的性能,接下来就需要选择合适的压缩算法。</p>
<h2>应该选择什么压缩算法?</h2>
<p>压缩算法可以分为有损压缩和无损压缩。有损压缩主要是用来压缩音视频,它压缩之后是会丢失信息的。我们这里讨论的全都是无损压缩,也就是说,数据经过压缩和解压过程之后,与压缩之前相比,是 100% 相同的。</p>
<p>数据为什么可以被压缩呢?各种各样的压缩算法又是怎么去压缩数据的呢?我举个例子来简单说明一下。</p>
<p>比如说,下面这段数据:</p>
<blockquote>
<p>00000000000000000000</p>
</blockquote>
<p>我来给你人肉压缩一下:</p>
<blockquote>
<p>20 个 0</p>
</blockquote>
<p>20 个字符就被压缩成了 4 个字符,并且是可以无损还原的。当然,我举的例子比较极端,我的压缩算法也几乎没什么实用性,但是,这确实是一个压缩算法,并且和其他的压缩算法本质是没什么区别的。</p>
<p>目前常用的压缩算法包括ZIPGZIPSNAPPYLZ4 等等。选择压缩算法的时候,主要需要考虑数据的压缩率和压缩耗时。一般来说,压缩率越高的算法,压缩耗时也越高。如果是对性能要求高的系统,可以选择压缩速度快的算法,比如 LZ4如果需要更高的压缩比可以考虑 GZIP 或者压缩率更高的 XZ 等算法。</p>
<p>压缩样本对压缩速度和压缩比的影响也是比较大的,同样大小的一段数字和一段新闻的文本,即使是使用相同的压缩算法,压缩率和压缩时间的差异也是比较大的。所以,有的时候在选择压缩算法的之前,用系统的样例业务数据做一个测试,可以帮助你找到最合适的压缩算法。</p>
<p>在这里我不会去给你讲某一种压缩算法因为压缩算法都很复杂一般来说也不需要我们来实现某种压缩算法如果你感兴趣的话可以去学习一下最经典压缩算法哈夫曼编码也叫霍夫曼编码Huffman Coding</p>
<h2>如何选择合适的压缩分段?</h2>
<p>大部分的压缩算法,他们的区别主要是,对数据进行编码的算法,压缩的流程和压缩包的结构大致一样的。而在压缩过程中,你最需要了解的就是如何选择合适的压缩分段大小。</p>
<p>在压缩时,给定的被压缩数据它必须有确定的长度,或者说,是有头有尾的,不能是一个无限的数据流,<strong>如果要对流数据进行压缩,那必须把流数据划分成多个帧,一帧一帧的分段压缩。</strong></p>
<p>主要原因是,压缩算法在开始压缩之前,一般都需要对被压缩数据从头到尾进行一次扫描,扫描的目的是确定如何对数据进行划分和编码,一般的原则是重复次数多、占用空间大的内容,使用尽量短的编码,这样压缩率会更高。</p>
<p>另外,被压缩的数据长度越大,重码率会更高,压缩比也就越高。这个很好理解,比如我们这篇文章,可能出现了几十次“压缩”这个词,如果将整篇文章压缩,这个词的重复率是几十次,但如果我们按照每个自然段来压缩,那每段中这个词的重复率只有二三次。显然全文压缩的压缩率肯定高于分段压缩。</p>
<p>当然,分段也不是越大越好,实际上分段大小超过一定长度之后,再增加长度对压缩率的贡献就不太大了,这是一个原因。另外,过大的分段长度,在解压缩的时候,会有更多的解压浪费。比如,一个 1MB 大小的压缩文件,即使你只是需要读其中很短的几个字节,也不得不把整个文件全部解压缩,造成很大的解压浪费。</p>
<p>所以,你需要根据你的业务,选择合适的压缩分段,在压缩率、压缩速度和解压浪费之间找到一个合适的平衡。</p>
<p>确定了如何对数据进行划分和压缩算法之后,就可以进行压缩了,压缩的过程就是用编码来替换原始数据的过程。压缩之后的压缩包就是由这个编码字典和用编码替换之后的数据组成的。</p>
<p>这就是数据压缩的过程。解压的时候,先读取编码字典,然后按照字典把压缩编码还原成原始的数据就可以了。</p>
<h2>Kafka 是如何处理消息压缩的?</h2>
<p>回过头来,我们再看一下 Kafka 它是如何来处理数据压缩的。</p>
<p>首先Kafka 是否开启压缩这是可以配置它也支持配置使用哪一种压缩算法。原因我们在上面说过不同的业务场景是否需要开启压缩选择哪种压缩算法是不能一概而论的。所以Kafka 的设计者把这个选择权交给使用者。</p>
<p>在开启压缩时Kafka 选择一批消息一起压缩,每一个批消息就是一个压缩分段。使用者也可以通过参数来控制每批消息的大小。</p>
<p>我们之前讲过,在 Kafka 中,生产者生成一个批消息发给服务端,在服务端中是不会拆分批消息的。那按照批来压缩,意味着,在服务端也不用对这批消息进行解压,可以整批直接存储,然后整批发送给消费者。最后,批消息由消费者进行解压。</p>
<p>在服务端不用解压,就不会耗费服务端宝贵的 CPU 资源,同时还能获得压缩后,占用传输带宽小,占用存储空间小的这些好处,这是一个非常聪明的设计。</p>
<p>在使用 Kafka 时,如果生产者和消费者的 CPU 资源不是特别吃紧,开启压缩后,可以节省网络带宽和服务端的存储空间,提升总体的吞吐量,一般都是个不错的选择。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/消息队列高手课/18 如何用硬件同步原语CAS替代锁.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/消息队列高手课/20 RocketMQ Producer源码分析消息生产的实现过程.md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"7099796f8a203cfa","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>