首页 » 网站推广 » K8sphp优化技巧_CTO偷偷传我的系统机能优化十大年夜绝招万字干货

K8sphp优化技巧_CTO偷偷传我的系统机能优化十大年夜绝招万字干货

duote123 2024-11-16 0

扫一扫用手机浏览

文章目录 [+]

弁言:取与舍

软件设计开拓某种意义上是“取”与“舍”的艺术。

K8sphp优化技巧_CTO偷偷传我的系统机能优化十大年夜绝招万字干货

关于性能方面,就像建筑设计成抗震9度须要额外的本钱一样,高性能软件系统也意味着更高的实现本钱,有时候与其他质量属性乃至会冲突,比如安全性、可扩展性、可不雅观测性等等。

K8sphp优化技巧_CTO偷偷传我的系统机能优化十大年夜绝招万字干货
(图片来自网络侵删)

大部分时候我们须要的是:在业务碰着瓶颈之前,利用常见的技能手段将系统优化到预期水平。

那么,性能优化有哪些技能方向和手段呢?

性能优化常日是“韶光”与“空间”的互换与取舍。

本篇分两个部分,在上篇,讲解六种通用的“韶光”与“空间”互换取舍的手段:

索引术压缩术缓存术预取术削峰填谷术批量处理术

不才篇,先容四种进阶性的内容,大多与提升并行能力有关:

八门遁甲 —— 榨干打算资源影分身术 —— 水平扩容奥义 —— 分片术秘术 —— 无锁术

1、索引术

索引的事理是拿额外的存储空间换取查询韶光,增加了写入数据的开销,但使读取数据的韶光繁芜度一样平常从O(n)降落到O(logn)乃至O(1)。

索引不仅在数据库中广泛利用,前后真个开拓中也在不知不觉利用。

在数据集比较大时,不用索引就像从一本没有目录而且内容乱序的新华字典查一个字,得一页一页全翻一遍才能找到;

用索引之后,就像用拼音先在目录中先找到要查到字在哪一页,直接翻过去就行了。

书本的目录是范例的树状构造,那么软件天下常见的索引有哪些数据构造,分别在什么场景利用呢?

哈希表(Hash Table):哈希表的事理可以类比银行办业务取号,给每个人一个号(打算出的Hash值),叫某个号直接对应了某个人,索引效率是最高的O(1),花费的存储空间也相对更大。
K-V存储组件以及各种编程措辞供应的Map/Dict等数据构造,多数底层实现是用的哈希表。

二叉搜索树(Binary Search Tree):有序存储的二叉树构造,在编程措辞中广泛利用的红黑树属于二叉搜索树,确切的说是“不完备平衡的”二叉搜索树。
从C++、Java的TreeSet、TreeMap,到Linux的CPU调度,都能看到红黑树的影子。
Java的HashMap在创造某个Hash槽的链表长度大于8时也会将链表升级为红黑树,而比较于红黑树“更加平衡”的AVL树反而实际用的更少。

平衡多路搜索树(B-Tree):这里的B指的是Balance而不是Binary,二叉树在大量数据场景会导致查找深度很深,办理办法便是变成多叉树,MongoDB的索引用的便是B-Tree。

叶节点相连的平衡多路搜索树(B+ Tree):B+ Tree是B-Tree的变体,只有叶子节点存数据,叶子与相邻叶子相连,MySQL的索引用的便是B+树,Linux的一些文件系统也利用的B+树索引inode。
实在B+树还有一种在枝桠上再加链表的变体:B树,暂时没想到实际运用。

日志构造合并树(LSM Tree):Log Structured Merge Tree,大略理解便是像日志一样顺序写下去,多层多块的构造,上层写满压缩合并到下层。
LSM Tree实在本身是为了优化写性能捐躯读性能的数据构造,并不能算是索引,但在大数据存储和一些NoSQL数据库中用的很广泛,因此这里也列进去了。

字典树(Trie Tree):又叫前缀树,从树根串到树叶便是数据本身,因此树根到枝桠便是前缀,枝桠下面的所有数据都是匹配该前缀的。
这种构造能非常方便的做前缀查找或词频统计,范例的运用有:自动补全、URL路由。
其变体基数树(Radix Tree)在Nginx的Geo模块处理子网掩码前缀用了;Redis的Stream、Cluster等功能的实现也用到了基数树(Redis中叫Rax)。

跳表(Skip List):是一种多层构造的有序链表,插入一个值时有一定概率“晋升”到上层形成间接的索引。
跳表更适宜大量并发写的场景,不存在红黑树的再平衡问题,Redis强大的ZSet底层数据构培养是哈希加跳表。

倒排索引(Inverted index):这样翻译不太直不雅观,可以叫“关键词索引”,比如书本末页列出的术语表便是倒排索引,标识出了每个术语涌如今哪些页,这样我们要查某个术语在哪用的,从术语表一查,翻到所在的页数即可。
倒排索引在全文索引存储中常常用到,比如ElasticSearch非常核心的机制便是倒排索引;Prometheus的时序数据库按标签查询也是在用倒排索引。

数据库主键之争:自增长 vs UUID。
主键是很多数据库非常主要的索引,尤其是MySQL这样的RDBMS会常常面临这个难题:是用自增长的ID还是随机的UUID做主键?

自增长ID的性能最高,但不好做分库分表后的全局唯一ID,自增长的规律可能透露业务信息;而UUID不具有可读性且太占存储空间。

争执的结果便是找一个兼具二者的优点的折衷方案:

用雪花算法天生分布式环境全局唯一的ID作为业务表主键,性能尚可、不那么占存储、又能担保全局单调递增,但引入了额外的繁芜性,再次表示了取舍之道。

再回到数据库中的索引,建索引要把稳哪些点呢?

定义好主键并只管即便利用主键,多数数据库中,主键是效率最高的聚簇索引;

在Where或Group By、Order By、Join On条件中用到的字段也要按需建索引或联合索引,MySQL中搭配explain命令可以查询DML是否利用了索引;

类似列举值这样重复度太高的字段不适宜建索引(如果有位图索引可以建),频繁更新的列不太适宜建索引;

单列索引可以根据实际查询的字段升级为联合索引,通过部分冗余达到索引覆盖,以避免回表的开销;

只管即便减少索引冗余,比如建A、B、C三个字段的联合索引,Where条件查询A、A and B、A and B and C

都可以利用该联合索引,就无需再给A单独建索引了;根据数据库特有的索引特性选择适宜的方案,比如像MongoDB,还可以建自动删除数据的TTL索引、不索引空值的稀疏索引、地理位置信息的Geo索引等等。

数据库之外,在代码中也能运用索引的思维,比如对付凑集中大量数据的查找,利用Set、Map、Tree这样的数据构造,实在也是在用哈希索引或树状索引,比直接遍历列表或数组查找的性能高很多。

2、缓存术

缓存优化性能的事理和索引一样,是拿额外的存储空间换取查询韶光。
缓存无处不在,设想一下我们在浏览器打开这篇文章,会有多少层缓存呢?

首先解析DNS时,浏览器一层DNS缓存、操作系统一层DNS缓存、DNS做事器链上层层缓存;

发送一个GET要求这篇文章,做事端很可能早已将其缓存在KV存储组件中了;

纵然没有击中缓存,数据库做事器内存中也缓存了最近查询的数据;

纵然没有击中数据库做事器的缓存,数据库从索引文件中读取,操作系统已经把热点文件的内容放置在Page Cache中了;

纵然没有击中操作系统的文件缓存,直接读取文件,大部分固态硬盘或者磁盘本身也自带缓存;

数据取到之后做事器用模板引擎渲染出HTML,模板引擎早已解析好缓存在做事端内存中了;

历经数十毫秒之后,终于做事器返回了一个渲染后的HTML,浏览器端解析DOM树,发送要求来加载静态资源;

须要加载的静态资源可能因Cache-Control在浏览器本地磁盘和内存中已经缓存了;

纵然本地缓存到期,也可能因Etag没变做事器见告浏览器304 Not Modified连续缓存;

纵然Etag变了,静态资源做事器也因其他用户访问过早已将文件缓存在内存中了;

加载的JS文件会丢到JS引擎实行,个中可能涉及的各类缓存就不再展开了;

全体过程中链条上涉及的所有的打算机和网络设备,实行的热点代码和数据很可能会载入CPU的多级高速缓存。

这里列举的仅仅是一部分常见的缓存,就有多种多样的形式:从廉价的磁盘到昂贵的CPU高速缓存,终极目的都是用来换取宝贵的韶光。

既然缓存那么好,那么问题就来了:缓存是“银弹”吗?

不,Phil Karlton 曾说过:

There are only two hard things in Computer Science: cache invalidation and naming things.

打算机科学中只有两件困难的事情:缓存失落效和命名规范。

缓存的利用除了带来额外的繁芜度以外,还面临如何处理缓存失落效的问题。

多线程并发编程须要用各种手段(比如Java中的synchronized volatile)防止并发更新数据,一部分缘故原由便是防止线程本地缓存的不一致;

缓存失落效衍生的问题还有:缓存穿透、缓存击穿、缓存雪崩。
办理用不存在的Key来穿透攻击,须要用空值缓存或布隆过滤器;办理单个缓存过期后,瞬间被大量恶意查询击穿的问题须要做查询互斥;办理某个韶光点大量缓存同时过期的雪崩问题须要添加随机TTL;

热点数据如果是多级缓存,在发生修正时须要打消或修正各级缓存,这些操作每每不是原子操作,又会涉及各种不一致问题。

除了常日意义上的缓存外,工具重用的池化技能,也可以看作是一种缓存的变体。

常见的诸如JVM,V8这类运行时的常量池、数据库连接池、HTTP连接池、线程池、Golang的sync.Pool工具池等等。

在须要某个资源时从现有的池子里直接拿一个,稍作修正或直接用于其余的用场,池化重用也是性能优化常见手段。

3、压缩术

说完了两个“空间换韶光”的,我们再看一个“韶光换空间”的办法——压缩。

压缩的事理花费打算的韶光,换一种更紧凑的编码办法来表示数据。

为什么要拿韶光换空间?韶光不是最宝贵的资源吗?

举一个视频网站的例子,如果不对视频做任何压缩编码,由于带宽有限,巨大的数据量在网络传输的耗时会比编码压缩的耗时多得多。

对数据的压缩虽然花费了韶光来换取更小的空间存储,但更小的存储空间会在另一个维度带来更大的韶光收益。

这个例子实质上是“操作系统内核与网络设备处理包袱 vs 压缩解压的CPU/GPU包袱”的权衡和取舍。

我们在代码中常日用的是无损压缩,比如下面这些场景:

HTTP协议中Accept-Encoding添加Gzip/deflate,做事端对接管压缩的文本(JS/CSS/HTML)要求做压缩,大部分图片格式本身已经是压缩的无需压缩;

HTTP2协议的头部HPACK压缩;

JS/CSS文件的稠浊和压缩(Uglify/Minify);

一些RPC协议和队列传输的中,采取二进制编码和压缩(Gzip、Snappy、LZ4等等);

缓存做事存过大的数据,常日也会事先压缩一下再存,取的时候解压;

一些大文件的存储,或者不常用的历史数据存储,采取更高压缩比的算法存储;

JVM的工具指针压缩,JVM在32G以下的堆内存情形下默认开启“UseCompressedOops”,用4个byte就可以表示一个工具的指针,这也是JVM只管即便不要把堆内存设置到32G以上的缘故原由;

MongoDB的二进制存储的BSON相对付纯文本的JSON也是一种压缩,或者说更紧凑的编码。
但更紧凑的编码也意味着更差的可读性,这一点也是须要取舍的。
纯文本的JSON比二进制编码要更占存储空间但却是REST API的主流,由于数据交流的场景下的可读性是非常主要的。

信息论见告我们,无损压缩的极限是信息熵。
进一步减小体积只能以丢失部分信息为代价,也便是有损压缩。

那么,有损压缩有哪些运用呢?

预览和缩略图,低速网络下视频降帧、降清晰度,都是对信息的有损压缩;

音视频等多媒体数据的采样和编码大多是有损的,比如MP3是利用傅里叶变换,有损地存储音频文件;jpeg等图片编码也是有损的。
虽然有像WAV/PCM这类无损的音频编码办法,但多媒体数据的采样本身便是有损的,相称于只截取了真实天下的极小一部分数据;

散列化,比如K-V存储时Key过长,先对Key实行一次“傻”系列(SHA-1、SHA-256)哈希算法变成固定长度的短Key。
其余,散列化在文件和数据验证(MD5、CRC、HMAC)场景用的也非常多,无需耗费大量算力比拟完全的数据。

除了有损/无损压缩,但还有一个办法,便是压缩的极度——从根本上减少数据或彻底删除。

能减少的就减少:

JS打包过程“摇树”,去掉没有利用的文件、函数、变量;

开启HTTP/2和高版本的TLS,减少了Round Trip,节省了TCP连接,自带大量性能优化;

减少不必要的信息,比如Cookie的数量,去掉不必要的HTTP要求头;

更新采取增量更新,比如HTTP的PATCH,只传输变革的属性而不是整条数据;

缩短单行日志的长度、缩短URL、在具有可读脾气况下用短的属性名等等;

利用位图和位操作,用风骚的位操作最小化存取的数据。
范例的例子有:用Redis的位图来记录统计海量用户登录状态;布隆过滤器用位图打消不可能存在的数据;大量开关型的设置的存储等等。

能删除的就删除:

删掉不用的数据;删掉不用的索引;删掉不该打的日志;删掉不必要的通信代码,不去发不必要的HTTP、RPC要求或调用,轮询改发布订阅;终极方案:砍掉全体功能。

毕竟有位叫做 Kelsey Hightower 的大佬曾经说过:

No code is the best way to write secure and reliable applications. Write nothing; deploy nowhere

不写代码,是编写安全可靠的运用程序的最佳办法。
什么都不写;哪里都不支配。

4、预取术

预取常日搭配缓存一起用,其事理是在缓存空间换韶光根本上更进一步,再加上一次“韶光换韶光”,也便是:用事先预取的耗时,换取第一次加载的韶光。

当可以预测出往后的某个韶光很有可能会用到某种数据时,把数据预先取到须要用的地方,能大幅度提升用户体验或做事端相应速率。

是否用预取模式就像自助餐餐厅与厨师现做的差异,在自助餐餐厅可以直接拿做好的菜品,一样平常餐厅须要坐下来等菜品现做。

那么,预取在哪些实际场景会用呢?

视频或直播类网站,在播放前先缓冲一小段韶光,便是预取数据。
有的在播放时不仅预取这一条数据,乃至还会预测下一个要看的其他内容,提前把数据取到本地;

HTTP/2 Server Push,在浏览器要求某个资源时,做事器顺带把其他干系的资源一起推回去,HTML/JS/CSS险些同时到达浏览器端,相称于浏览器被动预取了资源;

一些客户端软件会用常驻进程的形式,提前预取数据或实行一些代码,这样可以极大提高第一次利用的打开速率;

做事端同样也会用一些预热机制,一方面热点数据预取到内存提前形成多级缓存;另一方面也是对运行环境的预热,载入CPU高速缓存、热点函数JIT编译成机器码等等;

热点资源提前预分配到各个实例,比如:秒杀、售票的库存性子的数据;分布式唯一ID等等

天上不会掉馅饼,预取也是有副浸染的。

正如烤箱预热须要花费韶光和额外的电费,在软件代码中做预取/预热的副浸染常日是启动慢一些、占用一些闲时的打算资源、可能取到的不一定是后面须要的。

5、削峰填谷术

削峰填谷的事理也是“韶光换韶光”,谷时换峰时。

削峰填谷与预取是反过来的:预取是事先花韶光做,削峰填谷是事后花韶光做。
就像三峡大坝可以抗住短期巨量大水,事后雨停再逐步开闸防水。
软件天下的“削峰填谷”是类似的,只是不是用三峡大坝实现,而是用行列步队、异步化等办法。

常见的有这几类问题,我们分别来看每种对应的办理方案:

针对前端、客户真个启动优化或首屏优化:代码和数据等资源的延时加载、分批加载、后台异步加载、或按需背压掌握 - 限流、节流、去抖等等。
一夫当关,万夫莫开,从入口处削峰,防止一些恶意重复要求以及要求过于频繁的爬虫,乃至是一些DDoS攻击。
大略做法有网关层根据单个IP或用户用漏桶掌握要求速率和上限;前端做按钮的节流去抖防止重复点击;网络层开启TCP SYN Cookie防止恶意的SYN大水攻击等等。
彻底杜绝爬虫、黑客手段的恶意大水攻击是很难的,DDoS这类属于网络安全范畴了。

针对正常的业务要求洪峰,用行列步队暂存再异步化处理:常见的后端行列步队Kafka、RocketMQ乃至Redis等等都可以做缓冲层,第一层业务处理直接校验后丢到行列步队中,在洪峰过去后逐步消费行列步队中的,实行详细的业务。
其余实行过程中的耗时和耗打算资源的操作,也可以丢到行列步队或数据库中,等到谷时处理。

捋平毛刺:有时候洪峰不一定来自外界,如果系统内部大量定时任务在同一韶光实行,或与业务高峰期重合,很随意马虎在监控中看到“毛刺”——短韶光负载极高。
一样平常办理方案便是错峰实行定时任务,或者分配到其他非核心业务系统中,把“毛刺”摊平。
比如很多数据剖析型任务都放在业务低谷期去实行,大量定时任务在创建时只管即便加一些随机性来分散实行韶光。

避免缺点风暴带来的次生洪峰:有时候网络抖动或短暂宕机,业务会涌现各种非常或缺点。
这时处理不好很随意马虎带来次生磨难,比如:很多代码都会做缺点重试,不加掌握的大量重试乃至会导致网络抖动规复后的瞬间,积压的大量要求再次冲毁全体系统;还有一些代码没有做超时、降级等处理,可能导致大量的等待耗尽TCP连接,进而导致全体系统被冲毁。
办理之道便是做限定次数、间隔指数级增长的Back-Off重试,设定超时、降级策略。

6、批量处理术

批量处理同样可以算作“韶光换韶光”,其事理是减少了重复的事情,是一种对实行流程的压缩。
以个别批量操作更长的耗时为代价,在整体上换取了更多的韶光。

批量处理的运用也非常广泛,我们还是从前端开始讲:

打包合并的JS文件、雪碧图等等,将一批资源集中到一起,一次性传输;

前端动画利用requestAnimationFrame在UI渲染时批量处理积压的变革,而不是有变革急速更新,在游戏开拓中也有类似的运用;

前后端中利用行列步队暂存临时产生的数据,积压到一定数量再批量处理;在不影响可扩展脾气况下,一个接口传输多种须要的数据,减少大量ajax调用(GraphQL在这一点就做到了极致);

系统间通信只管即便发送整批数据,比如行列步队的发布订阅、存取缓存做事的数据、RPC调用、插入或更新数据库等等,能批量做尽可能批量做,由于这些系统间通信的I/O韶光开销已经很昂贵了;

数据积压到一定程度再落盘,操作系统本身的写文件便是这么做的,Linux的fwrite只是写入缓冲区暂存,积压到一定程度再fsync刷盘。
在运用层,很多高性能的数据库和K-V存储的实现都表示了这一点:一些NoSQL的LSM Tree的第一层便是在内存中先积压到一定大小再往下层合并;Redis的RDB结合AOF的落盘机制;Linux系统调用也供应了批量读写多个缓冲区文件的系统调用:readv/writev;

延迟地批量回收资源,比如JVM的Survivor Space的S0和S1区互换、Redis的Key过期的打消策略。

批量处理如此好用,那么问题来了,每一批放多大最得当呢?

这个问题实在没有定论,有一些个人履历可以分享。

前端把所有文件打包成单个JS,大部分时候并不是最优解。
Webpack供应了很多分块的机制,CSS和JS分开、JS按业务分更小的Chunk结合Redis的MGET、MSET来批量存取数据时,每批大小不宜过大,由于Redis主线程只有一个,如果一批太大实行期间会让其他命令无法相应。
履历上一批50-100个Key性能是不错的,但最好在真实环境下用真实大小的数据量化度量一下,做Benchmark测试才能确定一批大小的最优值。

MySQL、Oracle这类RDBMS,最优的批量Insert的大小也视数据行的特性而定。
我之前在2U8G的Oracle上用一些普遍的业务数据做过测试,批量插入时每批5000-10000条数据性能是最高的,每批过大会导致DML的解析耗时过长,乃至单个SQL语句体积超限,单批太多反而得不偿失落。

行列步队的发布订阅,每批的长度只管即便掌握在1MB以内,有些云做事商供应的行列步队限定了最大长度,那这个长度可能便是性能拐点,比如AWS的SQS做事对单条的限定是256KB。

总之,多大一批可以确保单批相应韶光不太长的同时让整体性能最高,是须要在实际情形下做基准测试的,不能一概而论。
而批量处理的副浸染在于:处理逻辑会更加繁芜,尤其是一些涉及事务、并发的问题;须要用数组或行列步队用来存放缓冲一批数据,花费了额外的存储空间。

中篇

弁言

前面我们总结了六种普适的性能优化方法,包括索引、压缩、缓存、预取、削峰填谷、批量处理,大略讲解了每种技能手段的事理和实际运用。

在开启末了一篇前,我们先须要搞清楚:

在程序运行期间,韶光和空间都耗在哪里了?

韶光都去哪儿了?

人眨一次眼大约100毫秒,而当代1核CPU在一眨眼的功夫就可以实行数亿条指令。

当代的CPU已经非常厉害了,频率已经达到了GHz级别,也便是每秒数十亿个指令周期。

纵然一些CPU指令须要多个时钟周期,但由于有流水线机制的存在,均匀下来大约每个时钟周期能实行1条指令,比如一个3GHz频率的CPU核心,每秒大概可以实行20亿到40亿旁边的指令数量。

程序运行还须要RAM,也可能用到持久化存储,网络等等。
随着新的技能和工艺的涌现,这些硬件也越来越厉害,比如CPU高速缓存的提升、NVMe固态硬盘相对SATA盘读写速率和延迟的飞跃等等。
这些硬件详细有多强呢?

有一个非常棒的网站“Latency Numbers Every Programmer Should Know”,可以直不雅观地查看从1990年到现在,高速缓存、内存、硬盘、网络韶光开销的详细数值。

https://colin-scott.github.io/personal_website/research/interactive_latency.html

下图是2020年的截图,的确是“每个开拓者该当知道的数字”。

这里有几个非常关键的数据:

存取一次CPU多级高速缓存的韶光大约1-10纳秒级别;存取一次主存(RAM)的韶光大概在100纳秒级别;固态硬盘的一次随机读写大约在10微秒到1毫秒这个数量级;网络包在局域网传输一个来回大约是0.5毫秒。

看到不同硬件之间数量级的差距,就很随意马虎理解性能优化的一些技能手段了。

比如一次网络传输的韶光,是主存访问的5000倍,明白这点就不难明得写for循环发HTTP要求,为什么会被扣人为了。

放大到我们随意马虎感知的韶光范围,来理解5000倍的差距:如果一次主存访问是1天的话,一趟局域网数据传输就要13.7年。

如果要传输更多网络数据,每两个网络帧之间还有固定的间隔(Interpacket Gap),在间隔期间传输Idle旗子暗记,数据链路层以此来区分两个数据包,详细数值在链接Wiki中有,这里截取几个我们熟习的网络来感想熏染一下:

百兆以太网: 0.96 µs千兆以太网:96 ns万兆以太网:9.6 ns

不过,纯挚看硬件的上限意义不大,从代码到机器指令中间有许多层抽象,仅仅是在TCP连接上发一个字节的数据包,从操作系统内核到网线,涉及到的根本举动步伐级别的软硬件不计其数。
到了运用层,单次操作耗时虽然没有非常精确的数字,但履历上的范围也值得参考:

用Memcached/Redis存取缓存数据:1-5 ms实行一条大略的数据库查询或更新操作:5-50ms在局域网中的TCP连接上收发一趟数据包:1-10ms;广域网中大约10-200ms,视传输间隔和网络节点的设备而定从用户态切换到内核态,完成一次系统调用:100ns - 1 μs,视不同的系统调用函数和硬件水平而定,少数系统调用可能远超此范围。

空间都去哪儿了?

在打算机历史上,非易失落存储技能的发展速率超过了摩尔定律。
除了嵌入式设备、数据库系统等等,现在大部分场景已经不太须要优化持久化存储的空间占用了,这里紧张讲的是另一个相对稀缺的存储形式 —— RAM,或者说主存/内存。

以JVM为例,在堆里面有很多我们创建的工具(Object)。

每个Object都有一个包含Mark和类型指针的Header,占12个字节每个成员变量,根据数据类型的不同占不同的字节数,如果是另一个工具,其工具指针占4个字节数组会根据声明的大小,占用N倍于其类型Size的字节数成员变量之间须要对齐到4字节,每个工具之间须要对齐到8字节

如果在32G以上内存的机器上,禁用了工具指针压缩,工具指针会变成8字节,包括Header中的Klass指针,这也就不难明得为什么堆内存超过32G,JVM的性能直线低落了。

举个例子,一个有8个int类型成员的工具,须要占用48个字节(12+32+4),如果有十万个这样的Object,就须要占用4.58MB的内存了。
这个数字彷佛看起来不大,而实际上一个Java做事的堆内存里面,各种各样的工具占用的内存常日比这个数字多得多,大部分内存耗在char[]这类数组或凑集型数据类型上。

举个例子,一个有8个int类型成员的工具,须要占用48个字节(12+32+4),如果有十万个这样的Object,就须要占用4.58MB的内存了。
这个数字彷佛看起来不大,而实际上一个Java做事的堆内存里面,各种各样的工具占用的内存常日比这个数字多得多,大部分内存耗在char[]这类数组或凑集型数据类型上。

堆内存之外,又是另一个天下了。

从操作系统进程的角度去看,也有不少耗内存的大户,不管什么Runtime都逃不开这些空间开销:每个线程须要分配MB级别的线程栈,运行的程序和数据会缓存下来,用到的输入输出设备须要缓冲区……

代码“写出来”的内存占用,仅仅是冰山之上的部分,真正的内存占用比“写出来”的要更多,到处都存在空间利用率的问题。

比如,纵然我们在Java代码中只是写了 response.getWriter().print(“OK”),给浏览器返回2字节,网络协议栈的层层封装,协议头部不断增加的额外数据,让终极返回给浏览器的字节数远超原始的2字节,像IP协议的报头部就至少有20个字节,而数据链路层的一个以太网帧头部至少有18字节。

如果传输的数据过大,各层协议还有最大传输单元MTU的限定,IPv4一个报文最大只能有64K比特,超过此值须要分拆发送并在吸收端组合,更多额外的报头导致空间利用率降落(IPv6则供应了Jumbogram机制,最大单包4G比特,“摧残浪费蹂躏”就减少了)。

这部分的“摧残浪费蹂躏”有多大呢?下面的链接有个表格,传输1460个字节的载荷,经由有线到无线网络的转换,至少再添120个字节,空间利用率<92.4%。

https://en.wikipedia.org/wiki/Jumbo_frame

这种征象非常普遍,利用抽象层级越高的技能平台,平台供应高等能力的同时,其底层实现的“信息密度”常日越低。

像Java的Object Header便是利用JVM的代价,而更进一步利用动态类型措辞,要为灵巧性付出空间的代价则更大。
哈希表的自动扩容,强大的反射能力等等,背后也付出了空间的代价。

再比如,二进制数据交流协议常日比纯文本协议更加节约空间。
但多数厂家我们仍旧用JSON、XML等纯文本协议,用信息的冗余来换取可读性。
即便是二进制的数据交互格式,也会存在信息冗余,只能通过更好的协议和压缩算法,只管即便去逼近压缩的极限 —— 信息熵。

小结

理解了韶光和空间的花费在哪后,还不能完备阐明软件为何方向于耗尽硬件资源。
有一条定律可以阐明,正是它锤爆了摩尔定律。

它便是安迪-比尔定律。

“安迪给什么,比尔拿走什么”。

安迪指的是Intel前CEO安迪·葛洛夫,比尔指的是比尔·盖茨。

这句话的意思便是:软件发展比硬件还快,总能吃得下硬件。

20年前,在最强的打算机也不见得可以玩赛车游戏;

10年前,个人电脑已经可以玩画质还可以的3D赛车游戏了;

现在,自动驾驶+5G云驾驶已经快成为现实。

在这背后,是无数的硬件技能飞跃,以及吃掉了这些硬件的各种软件。

因此,纵然当代的硬件水平已经刁悍到如此田地,性能优化仍旧是有必要的。

软件日益繁芜,抽象层级越来越高,就越须要底层根本举动步伐被充分优化。

对付大部分开拓者而言,高层代码逐步走向低代码化、可视化,“一行代码”能产生的影响也越来越大,写出低效代码则会吃掉更多的硬件资源。

下篇

弁言

本篇也是本系列最硬核的一篇,本人技能水平有限,可能存在疏漏或缺点之处,望斧正。
仍旧选取了《火影忍者》的配图和命名办法帮助理解:

八门遁甲 —— 榨干打算资源影分身术 —— 水平扩容奥义 —— 分片术秘术 —— 无锁术

(注:这些“中二”的前缀仅是用《火影》中的一些术语,形象地描述技能方案)

7、八门遁甲 —— 榨干打算资源

让硬件资源都在处理真正有用的逻辑打算,而不是做无关的事情或空转。

从晶体管到集成电路、驱动程序、操作系统、直到高等编程措辞的层层抽象,每一层抽象带来的更强的通用性、更高的开拓效率,多因此丢失运行效率为代价的。

但我们可以在用高等编程措辞写代码的时候,在保障可读性、可掩护性根本上用运行效率更高、更适宜运行时环境的办法去写,减少额外的性能损耗《Effective XXX》、《More Effective XXX》、《高性能XXX》这类书本所通报的知识和思想。

落到技能细节,下面用四个小节来解释如何减少“无用功”、避免空转、榨干硬件。

1)聚焦

减少系统调用与高下文切换,让CPU聚焦。

可以看看两个 stackoverflow 上的帖子:

https://stackoverflow.com/questions/21887797/what-is-the-overhead-of-a-context-switch

https://stackoverflow.com/questions/23599074/system-calls-overhead

大部分互联网运用做事,耗时的部分不是打算,而是I/O。

减少I/O wait, 各司其职,专心干I/O,专心干打算,epoll批量捞任务,(refer: event driven)

利用DMA减少CPU包袱 - 零拷贝 NewI/O Redis SingleThread (even 6.0), Node.js

避免不必要的调度 - Context Switch

CPU亲和性,让CPU更加聚焦

2)蜕变

用更高效的数据构造、算法、第三方组件,让程序本身蜕变。

从逻辑短路、Map代替List遍历、减少锁范围、这样的编码技巧,到运用FisherYates、Dijkstra这些经典算法,把稳每一行代码细节,量变会发生质变。
更何况某个算法就足以让系统性能产生一两个数量级的提升。

3)适应

因时制宜,适应特定的运行环境

在浏览器中紧张是优化方向是I/O、UI渲染引擎、JS实行引擎三个方面。

I/O越少越好,能用WebSocket的地方就不用Ajax,能用Ajax的地方就不要刷全体页面;

UI渲染方面,减少重排和重绘,比如Vue、React等MVVM框架的虚拟DOM用额外的打算换取最精简的DOM操作;

JS实行引擎方面,少用动态性极高的写法,比如eval、随意修正工具或工具原型的属性。

前真个优化有个神器:Light House,在新版本Chrome已经嵌到开拓者工具中了,可以一键天生性能优化报告,按照优化建议改就完了。

与浏览器环境颇为相似的Node.js环境:

https://segmentfault.com/a/1190000007621011#articleHeader11

Java

C1 C2 JIT编译器栈上分配

Linux

各种参数优化内存分配和GC策略Linux内核参数 Brendan Gregg内存区块配置(DB,JVM,V8,etc.)

利用措辞特性和运行时环境 - 比如写出利于JIT的代码

多静态少动态 - 舍弃动态特性的灵巧性 - hardcode/if-else,强类型,弱类型措辞避免类型转换 AOT/JIT vs 阐明器, 汇编,机器码 GraalVM

减少内存的分配和回收,少对列表做增加或删除

对付RAM有限的嵌入式环境,有时候韶光不是问题,反而要拿韶光换空间,以节约RAM的利用。

4)运筹

把眼界放宽,跳出程序和运行环境本身,从整体上进行系统性剖析最高性价比的优化方案,剖析潜在的优化切入点,以及能够调配的资源和技能,运筹帷幄。

个中最大略易行的几个办法,便是费钱,买更好或更多的硬件根本举动步伐,这每每是开拓职员随意马虎忽略的,这里供应一些妙招:

做事器方面,云做事厂商供应各种类型的实例,每种类型有不同的属性侧重,带宽、CP、磁盘的I/O能力,选适宜的而不是更贵的

舍弃虚拟机 - Bare Mental,比如神龙做事器

用ARM架构CPU的做事器,同等价格可以买到更多的做事器,对付多数可以跨平台运行的做事端系统来说与x86差异并不大,ARM做事器的数据中央也是技能发展趋势使然

如果必须用x86系列的做事器,AMD也Intel的性价比更高。

第一点非常主要,软件性能遵照木桶事理,一定要找到瓶颈在哪个硬件资源,把钱花在刀刃上。

如果是做事端带宽瓶颈导致的性能问题,升级再多核CPU也是没有用的。

我有一次性能优化案例:把一个跑繁芜业务的Node.js做事器从AWS的m4类型换成c4类型,内存只有原来的一半,但CPU利用率反而低落了20%,同时价格还比之前更便宜,一石二鸟。

这是由于Node.js主线程的打算任务只有一个CPU核心在干,通过CPU Profile的火焰图,可以定位到该业务的瓶颈在主线程的打算任务上,因此提高单核频率的浸染是吹糠见米的。
而该业务对内存的花费并不多,套用一些定制v8引擎内存参数的方案,起不了任何浸染。

毕竟这样的例子不多,大部分时候还是要多费钱买更高配的做事器的,除了这条费钱能直接办理问题的办法,剩下的办法难度就大了:

利用更底层的特性实现功能,比如FFI WebAssembly调用其他措辞,Java Agent Instrument,字节码天生(BeanCopier, Json Lib),乃至汇编等等利用硬件供应的更高效的指令各种提升TLB命中率的机制,减少内存的大页表魔改Runtime,Facebook的PHP,阿里腾讯定制的JDK网络设备参数,MTU专用硬件:GPU加速(cuda)、AES硬件卡和高等指令加速加解密过程,比如TLS可编程硬件:地狱级难度,FPGA硬件设备加速特定业务NUMA更宏不雅观的调度,VM层面的共享vCPU,K8S集群调度,总体上的优化

5)小结

有些手段,是凭空换出来更多的空间和韶光了吗?

天下没有免费的午餐,纵然那些看起来空手套白狼的优化技能,也须要额外的人力成本来做,副浸染可能便是专家级的发际线吧。
还好很多繁芜的性能优化技能我也不会,以是我本人发际线还可以。

这一小节总结了一些方向,有些技能细节非常深,这里也无力展开。
不过,纵然榨干了单机性能,也可能不敷以支撑业务,这时候就须要分布式集群出场了,因此后面先容的3个技能方向,都与并行化有关。

8、影分身术 —— 水平扩容

本节的水平扩容以及下面一节的分片,可以算整体的性能提升而不是单点的性能优化,会由于引入额外组件反而降落了处理单个要求的性能。

但当业务规模大到一定程度时,再好的单机硬件也无法承受流量的洪峰,就得水平扩容了,毕竟”众人拾柴火焰高”。

在这背后的理论根本是,硅基半导体已经靠近物理极限,随着摩尔定律的减弱,阿姆达尔定律的浸染显现出来:

https://en.wikipedia.org/wiki/Amdahl%27s_law

水平扩容一定引入负载均衡

多副本水平扩容的条件是无状态读>>写, 多个读实例副本 (CDN)自动扩缩容,根据常用的或自定义的metrics,剖断扩缩容的条件,或根据CRON负载均衡策略的选择

9、奥义 —— 分片术

水平扩容针对无状态组件,分片针对有状态组件。
二者事理都是提升并行度,但分片的难度更大。

负载均衡也不再是大略的加权轮询了,而是进化成了各个分片的折衷器

Java1.7的及之前的 ConcurrentHashMap分段锁有状态数据的分片如何选择Partition/Sharding Key负载均衡难题热点数据,增强缓存等级,办理分散的缓存带来的同等性难题数据冷热分离,SSD - HDD分开随意马虎合并难区块链的优化,分区域

10、秘术 —— 无锁术

有些业务场景,比如库存业务,按照正常的逻辑去实现,水平扩容带来的提升非常有限,由于须要锁住库存,扣减,再解锁库存。

票务系统也类似,为了避免超卖,须要有一把锁禁锢了横向扩展的能力。

不管是单机还是分布衰落做事,锁都是制约并行度的一大成分。
比如上篇提到的秒杀场景,库存就那么多,系统超卖了可能导致非常大的经济丢失,但用分布式锁会导致纵然做事扩容了成千上万个实例,终极无数要求仍旧壅塞在分布式锁这个串行组件上了,再多水平扩展的实例也无用武之地。

避免竞争Race Condition 是最完美的办理办法。

上篇说的应对秒杀场景,预取库存便是减轻竞态条件的例子,虽然取到做事器内存之后仍旧有多线程的锁,但锁的粒度更细了,并发度也就提高了。

线程同步锁分布式锁数据库锁 update select子句事务锁顺序与乱序乐不雅观锁/无锁 CAS Java 1.8之后的ConcurrentHashMappipeline技能 - CPU流水线 Redis Pipeline 大数据剖析 并行打算TCP的缓冲区排头壅塞 QUIC HTTP3.0

总结

以ROI的视角看软件开拓,初期人力本钱的投入,后期的掩护本钱,打算资源的用度等等,选一个得当的方案而不是一个性能最高的方案。

本篇结合个人履历总结了常见的性能优化手段,这些手段只是冰山一角。
在初期就设计实现出一个完美的高性能系统是不可能的,随着软件的迭代和体量的增大,利用压测,各种工具(profiling,vmstat,iostat,netstat),以及监控手段,逐步找到系统的瓶颈,因时制宜地选择优化手段才是正道。

有利必有弊,得到一些一定会失落去一些,有一些手段要慎用。
Linux性能优化大师Brendan Gregg几次再三强调的便是:切忌过早优化、过度优化。

持续不雅观测,做80%高投入产出比的优化。

除了这些设计和实现时可能用到的手段,在技能选型时选择高性能的框架和组件也非常主要。

其余,支配根本举动步伐的硬件性能也同样,得当的做事器和网络等根本举动步伐每每会事半功倍,比如云做事厂商供应的各种字母开头的instance,网络设备带宽的速率和稳定性,磁盘的I/O能力等等。

多数时候我们应该利用更高性能的方案,但有时候乃至要故意去违背它们。
末了,以《Effective Java》第一章的一句话结束本文吧。

首先要学会基本的规则,然后才能知道什么时候可以冲破规则。

作者丨Joey Yang

来源丨网址:https://code2life.top/2020/08/15/0055-performance/

dbaplus社群欢迎广大技能职员投稿,投稿邮箱:editor@dbaplus.cn

关于我们

dbaplus社群是环绕Database、BigData、AIOps的企业级专业社群。
资深大咖、技能干货,每天佳构原创文章推送,每周线上技能分享,每月线下技能沙龙,每季度Gdevops&DAMS行业大会。

关注"大众年夜众号【dbaplus社群】,获取更多原创技能文章和精选工具下载

标签:

相关文章

介绍直播新纪元,轻松进入直播的五大步骤

随着互联网技术的飞速发展,直播行业在我国逐渐崛起,越来越多的人选择通过直播这一新兴媒介展示自己、分享生活、传递价值。对于许多新手来...

网站推广 2025-01-03 阅读1 评论0

介绍相机美颜原理,科技与美学的完美结合

随着科技的发展,智能手机的摄像头功能日益强大,美颜相机成为了许多人拍照的首选。美颜相机不仅满足了人们对于美的追求,更在视觉上给人带...

网站推广 2025-01-03 阅读1 评论0

介绍磁铁的制造,科学与艺术的完美结合

磁铁,一种神秘的物质,自古以来就吸引了无数人的目光。它不仅具有独特的磁性,还能在工业、医疗、科研等领域发挥重要作用。磁铁是如何制造...

网站推广 2025-01-03 阅读1 评论0

介绍电瓶激活方法,让电池焕发新生

随着科技的不断发展,电动汽车逐渐成为人们出行的首选。而电瓶作为电动汽车的核心部件,其性能直接影响着车辆的续航里程和行驶体验。新购买...

网站推广 2025-01-03 阅读1 评论0