3万字聊聊什么是RocketMQ(四)
大家好,我是Leo。
上一篇我们介绍了
消息积压问题如何处理 阅读源码的小技巧 异步方案提升系统性能 MQ的缓存策略
继上篇RocketMQ技术总结三,这篇主要聊一下
如何正常用锁保护共享数据 中间件中常见的时间换空间的算法
本章概括
如何正确用锁保护数据
我们知道,使用异步和并发的设计可以大幅提升程序的性能,但我们为此付出的代价是,程序比原来更加复杂了,多线程在并行执行的时候,带来了很多不确定性。特别是对于一些需要多个线程并发读写的共享数据,如果处理不好,很可能会产出不可预期的结果,这肯定不是我们想要的。
分享一下李玥老师使用锁的第一条原则:如果能不用锁,就不用锁;如果你不确定是不是应该用锁,那也不要用锁。为什么这么说呢?因为, 虽然说使用锁可以保护共享资源,但是代价还是不小的。
加锁和解锁过程都是需要 CPU 时间的,这是一个性能的损失。另外,使用锁就有可能导致线程等待锁,等待锁过程中线程是阻塞的状态,过多的锁等待会显著降低程序的性能。 如果对锁使用不当,很容易造成死锁,导致整个程序“卡死”,这是非常严重的问题。本来多线程的程序就非常难于调试,如果再加上锁,出现并发问题或者死锁问题,你的 程序将更加难调试。
锁的用法
在访问共享资源之前,先获取锁。 如果获取锁成功,就可以访问共享资源了。 最后,需要释放锁,以便其他线程继续访问共享资源。
用完锁,一定要释放它。一定要考虑好所有的分支情况。确保不管发生任何情况,都会释放锁
死锁的产生: 很多语言都有异常机制,当抛出异常的时候,不再执行后面的代码。如果在访问共享资源时 抛出异常,那后面释放锁的代码就不会被执行,这样,锁就一直无法释放,形成死锁。
避免死锁的建议:
再次强调一下,避免滥用锁,程序里用的锁少,写出死锁 Bug 的几率自然就低。 对于同一把锁,加锁和解锁必须要放在同一个方法中,这样一次加锁对应一次解锁,代码清晰简单,便于分析问题。 尽量避免在持有一把锁的情况下,去获取另外一把锁,就是要尽量避免同时持有多把锁。 如果需要持有多把锁,一定要注意加解锁的顺序,解锁的顺序要和加锁顺序相反。比如,获取三把锁的顺序是 A、B、C,释放锁的顺序必须是 C、B、A。 给你程序中所有的锁排一个顺序,在所有需要加锁的地方,按照同样的顺序加解锁。比如我刚刚举的那个例子,如果两个线程都按照先获取 lockA 再获取 lockB 的顺序加 锁,就不会产生死锁。
数据压缩:时间换空间
数据压缩不仅能节省存储空间,还可以用于提升网络传输性能。这种使用压缩来提升 系统性能的方法,不仅限于在消息队列中使用,我们日常开发的应用程序也可以使用。
比如,我们的程序要传输大量的数据,或者要在磁盘、数据库中存储比较大的数据,这些情况 下,都可以考虑使用数据压缩来提升性能,还能节省网络带宽和存储空间。
什么情况适合使用数据压缩?
在使用压缩之前,首先你需要考虑,当前这个场景是不是真的适合使用数据压缩。
比如,进程之间通过网络传输数据,这个数据是不是需要压缩呢?我们可以对比一下
不压缩直接传输需要的时间是:传输未压缩数据的耗时。 使用数据压缩需要的时间是:压缩耗时 + 传输压缩数据耗时 + 解压耗时
到底是压缩快,还是不压缩快呢?其实不好说。影响的因素非常多,比如数据的压缩率、网 络带宽、收发两端服务器的繁忙程度等等。
压缩和解压的操作都是计算密集型的操作,非常耗费 CPU 资源。如果你的应用处理业务逻 辑就需要耗费大量的 CPU 资源,就不太适合再进行压缩和解压。
又比如说,如果你的系统的瓶颈是磁盘的 IO 性能,CPU 资源又很闲,这种情况就非常适 合在把数据写入磁盘前先进行压缩。
但是,如果你的系统读写比严重不均衡,你还要考虑,每读一次数据就要解压一次是不是划 算。
压缩它的本质是资源的置换,是一个时间换空间,或者说是 CPU 资源换存储资源的游戏。
就像木桶的那个短板一样,每一个系统它都有一个性能瓶颈资源,可能是磁盘 IO,网络带 宽,也可能是 CPU。如果使用压缩,能用长板来换一些短板,那总体上就能提升性能,这 样就是划算的。如果用了压缩之后,短板更短了,那就不划算了,不如不用。
如果通过权衡,使用数据压缩确实可以提升系统的性能,接下来就需要选择合适的压缩算法。
应该选择什么压缩算法?
压缩算法可以分为有损压缩和无损压缩。有损压缩主要是用来压缩音视频,它压缩之后是会 丢失信息的。我们这里讨论的全都是无损压缩,也就是说,数据经过压缩和解压过程之后, 与压缩之前相比,是 100% 相同的。
数据为什么可以被压缩呢?各种各样的压缩算法又是怎么去压缩数据的呢?我举个例子来简 单说明一下。
比如说,下面这段数据:
00000000000000000000
我来给你人肉压缩一下:
20 个 0
20 个字符就被压缩成了 4 个字符,并且是可以无损还原的。当然,我举的例子比较极端, 我的压缩算法也几乎没什么实用性,但是,这确实是一个压缩算法,并且和其他的压缩算法 本质是没什么区别的。
目前常用的压缩算法包括:ZIP,GZIP,SNAPPY,LZ4 等等。选择压缩算法的时候,主要需要考虑数据的压缩率和压缩耗时。一般来说,压缩率越高的算法,压缩耗时也越高。如果 是对性能要求高的系统,可以选择压缩速度快的算法,比如 LZ4;如果需要更高的压缩 比,可以考虑 GZIP 或者压缩率更高的 XZ 等算法。
压缩样本对压缩速度和压缩比的影响也是比较大的,同样大小的一段数字和一段新闻的文本,即使是使用相同的压缩算法,压缩率和压缩时间的差异也是比较大的。所以,有的时候 在选择压缩算法的之前,用系统的样例业务数据做一个测试,可以帮助你找到最合适的压缩 算法。
在这里,我不会去给你讲某一种压缩算法,因为压缩算法都很复杂,一般来说也不需要我们 来实现某种压缩算法,如果你感兴趣的话,可以去学习一下最经典压缩算法:哈夫曼编码 (也叫霍夫曼编码,Huffman Coding)。
如何选择合适的压缩分段?
大部分的压缩算法,他们的区别主要是,对数据进行编码的算法,压缩的流程和压缩包的结 构大致一样的。而在压缩过程中,你最需要了解的就是如何选择合适的压缩分段大小。
在压缩时,给定的被压缩数据它必须有确定的长度,或者说,是有头有尾的,不能是一个无 限的数据流,如果要对流数据进行压缩,那必须把流数据划分成多个帧,一帧一帧的分段压 缩。
主要原因是,压缩算法在开始压缩之前,一般都需要对被压缩数据从头到尾进行一次扫描, 扫描的目的是确定如何对数据进行划分和编码,一般的原则是重复次数多、占用空间大的内 容,使用尽量短的编码,这样压缩率会更高。
另外,被压缩的数据长度越大,重码率会更高,压缩比也就越高。这个很好理解,比如我们 这篇文章,可能出现了几十次“压缩”这个词,如果将整篇文章压缩,这个词的重复率是几 十次,但如果我们按照每个自然段来压缩,那每段中这个词的重复率只有二三次。显然全文 压缩的压缩率肯定高于分段压缩。
当然,分段也不是越大越好,实际上分段大小超过一定长度之后,再增加长度对压缩率的贡献就不太大了,这是一个原因。另外,过大的分段长度,在解压缩的时候,会有更多的解压 浪费。比如,一个 1MB 大小的压缩文件,即使你只是需要读其中很短的几个字节,也不得不把整个文件全部解压缩,造成很大的解压浪费。
所以,你需要根据你的业务,选择合适的压缩分段,在压缩率、压缩速度和解压浪费之间找 到一个合适的平衡。
确定了如何对数据进行划分和压缩算法之后,就可以进行压缩了,压缩的过程就是用编码来 替换原始数据的过程。压缩之后的压缩包就是由这个编码字典和用编码替换之后的数据组成 的。
这就是数据压缩的过程。解压的时候,先读取编码字典,然后按照字典把压缩编码还原成原始的数据就可以了。
充电分享
低谷时,用乐观的态度,渡己。
困境时,用豁达的态度,悦人。
每个人都有一段异常艰难的时光,生活的压力,工作的失意,学业的压力,爱的惶惶不可终日。
挺过来的,人生就会豁然开朗,挺不过来的,时间也会教你,怎么与他们握手言和,所以不必害怕。
决定上限的,是情绪价值的高低。当我们在心中播下一颗积极情绪的种子,便会收获欣欣向荣的生活。
结尾
有些不懂的地方或者不对的地方,麻烦各位指出,一定修改优化!
非常欢迎大家加我个人微信有关后端方面的问题我们在群内一起讨论! 我们下期再见!
长按上方扫码二维码,加我微信,拉你进群