共计 20695 个字符,预计需要花费 52 分钟才能阅读完成。
文:苏木
0. 开篇
性能优化是一个很简单的工作,且充斥了不确定性。它不像 Java 业务代码,能够一次编写到处运行 (write once, run anywhere),往往一些咱们可能并不能觉察的变动,就会带来惊喜 / 惊吓。可能全面的理解并评估咱们所负责利用的性能,我认为是晋升技术确定性和技术感知能力的十分无效的伎俩。本文尽可能简短的总结我本人在性能优化下面的一些领会和教训,从实际的角度登程尽量避免过于啰嗦和僵硬,但相干的常识切实太多,受限于集体教训和技术深度,有余之外还请大家补充。
第 1 局部是偏背景类常识的介绍,有这方面常识的同学能够间接跳过。
1. 理解运行环境
大多数的编程语言(尤其是 Java)做了十分多的事件来帮忙咱们不必太理解硬件也能很容易的写出正确工作的代码,但你如果要全面理解性能,却须要具备不少的从硬件、操作系统到软件层面的常识。
1.1 服务器
目前咱们大量应用 Intel 64 位架构的 Xeon 处理器,除此之外还会有 AMD x64 处理器、ARM 服务器处理器(如:华为鲲鹏、阿里倚天)、将来还会有 RISC- V 架构的处理器、以及一些专用 FPGA 芯片等等。咱们这里次要聊聊目前咱们大量应用的阿里云 ECS 应用的 Intel 8269CY 处理器。
1.1.1 处理器
Intel Xeon Platinum 8269CY,阿里云应用的这一款处理器是阿里定制款,并不能在 Intel 的官网手册中查问到,不过咱们能够通过下方 Intel 处理器的命名规定理解到不少的信息,它是一款这样的处理器:主频 2.5GHz(睿频 3.2GHz、最大睿频 3.8GHz),26 外围 52 线程(具备超线程技术),6 通道 DDR4-2933 内存,最大配置内存 1T,Cascade Lake 微架构,48 通道 PCI-E 3.0,14nm 光刻工艺,205W TDP。它是这一代至强处理器中性能比拟强的型号了,最大反对 8 路部署。
具备动静主动超频的能力将可能短时间晋升性能,同时在多数外围繁忙的时候还能够让它们放弃长时间的主动超频,这会重大的影响咱们对利用性能的评估(大量测试时性能很好,大规模测试时降落很厉害)。
最大配置内存 1TB,代表处理器具备 48bit 的 VA(虚拟地址),也就是通常须要四级页表(下一代具备 57bit VA 的处理器曾经在设计中了,通常须要五级页表),过深的页表显然是极大的影响内存拜访的性能以及占用内存(页表也是存储在内存中的)的,所以 Intel 设计了大页(2MB、1GB 大页)机制,以缩小过深的页表带来的影响。6 通道 2933MHz 的内存总线代表它具备总计约 137GB/s(内存总线是 64bit 位宽)的宽带,不过须要记住他们是高度并行设计的。
这个微解决的 CPU 核架构如下图所示,采纳 8 发射乱序架构,32KB 指令 +32KB 数据 L1 Cache,1MB L2 Cache。发射单元是 CPU 外部真正的计算单元,它的多少是 CPU 性能的关键因素。8 个发射单元中有 4 个单元都能够进行根本整数运算(ALU 单元),只有 2 个能够进行整数乘除和浮点运算,所以对于大量浮点运算的场景并行效率会偏低。1 个 CPU 核对应的 2 个 HT(这里指超线程技术虚构出的硬件线程)是共享 8 个发射单元的,所以这两个 HT 之间将会有十分大的相互影响(这也会导致操作系统内 CPU 的使用率不再是线性值,具体请查阅相干材料),L1、L2 Cache 同样也是共享的,所以也会相互影响。
Intel Xeon 处理器在分支预测下面花了很多功夫,所以在较多分支代码(通常就是 if else 这类代码)时性能往往也能做的很好,比大多数的 ARM 架构做的都要好。Java 罕用的指针压缩技术也受害于 x86 架构灵便的寻址能力 (如:mov eax, ecx * 8 + 8),能够一条指令实现,同时也不会带来性能的损失,然而这在 ARM、RISC- V 等 RISC(精简指令集架构,Reduced Instruction Set Computing) 处理器上就不实用了。
从牢靠渠道理解道,下一代架构(Sunny Cove)将大幅度的进行架构优化,降级为 10 发射,同时 L1 Cache 将数据局部减少到 48KB,这代表接下来的处理器将更加侧重于晋升 SIMD(单指令多操作数,Signle Instruction Multiple Data)等的数据计算性能。
一颗 8269CY 外部有 26 个 CPU 核,采纳如下的拓扑构造进行连贯。这一代处理器最多有 28 个 CPU 核,8269CY 屏蔽掉了 2 个外围,以升高对产品良率的要求(节约老本)。能够看到总计 35.75MB 的 L3 Cache(图中为 LLC: Last Level Cache)被分为了 13 块(图中是 14 块,屏蔽 2 个外围的同时也屏蔽了与之匹配的 L3 Cache),每块 2.75MB,并不是简略意义了解上的是一大块对立的区域。6 通道的内存控制器也散布在左右两侧,与理论主板上内存插槽的地位关系是对应的。这些信息都通知咱们,这是一颗并行能力十分强的多外围处理器。
1.1.2 服务器
阿里云通常都是采纳双 Intel 处理器的 2U 机型(基于散热、密度、性价比等等的思考),根本都是 2 个 NUMA(非一致性内存拜访,Non Uniform Memory Access)节点。具体到 Intel 8269CY,代表一台服务器具备 52 个物理外围,104 个硬件线程,通常阿里云会称之为 104 核。NUMA 技术的呈现是硬件工程师的斗争(他们切实没有能力做到在多 CPU 的状况下还能实现拜访任何地址的性能一致性),所以做的不好也会重大的升高性能,大多数状况下虚拟机 / 容器调度团队要做的是将 NUMA 关上,同时将一个虚拟机 / 容器部署到同一个 NUMA 节点上。
这几年 AMD 的倒退很好,它的多核架构与 Intel 有很大的不同,不久的未来阿里云将会部署不少采纳 AMD 处理器的机型。AMD 处理器的 NUMA 节点将会更多,而且拓扑关系也会更简单,阿里自研的倚天(采纳 ARM 架构)就更简单了。这意味着虚拟机 / 容器调度团队够得忙了。
少数状况下服务器大都采纳 CPU: 内存为 1:2 或 1:4 的配置,即配置双 Intel 8269CY 的物理机,通常都会装备 192GB 或 384GB 的内存。如果虚拟机 / 容器须要的内存:CPU 过大的状况下,将很难实现内存在 CPU 对应的 NUMA 节点上就近调配了,也就是说性能就不能失去保障。
因为 2U 机型的物理高度是 1U 机型的 2 倍,所以有更多的空间放下更多的 SSD 盘、高性能 PCI- E 设施等。不过云厂商必定是不违心间接将物理机卖给用户(毕竟他们曾经不再是以前的托管物理机公司)的,再怎么也得在下面架一层,也就是做成 ECS 再卖给客户,这样诸如热迁徙、高可用等性能能力实现。前述的 ” 架一层 ” 是通过虚拟化技术来实现的。
1.1.3 虚拟化技术
一台物理机性能很弱小,通常咱们只须要外面的一小块,但咱们又心愿不要感知到其他人在共享这台物理机,所以催生了虚拟化技术(简略来说就是让能够让一个 CPU 工作起来就像多个 CPU 并行运行,从而使得在一台服务器内能够同时运行多个操作系统)。晚期的虚拟机技术是通过软件实现的,老牌厂商如 VMWare,然而性能就义的有点多,硬件厂商也看好虚拟机技术的前景,所以便有了硬件虚拟化技术。
各厂商的实现并不相同,但差别不是很大,好在有专门的虚拟化解决模块去兼容就能够了。Intel 的虚拟化技术叫 Intel VT(Virtualization Technology),它包含 VT-x(处理器的虚拟化反对)、VT-d(间接 I / O 拜访的虚拟化)、VT-c(网络连接的虚拟化),以及在网络性能上的 SR-IOV 技术(Single Root I/O Virtualization)。
这外面一个很重要的事件是,本来咱们拜访内存的一层转换(线性地址 -> 物理地址)会变成二层转换(VM 内线性地址 ->Host 线性地址 -> 物理地址),这会引入更多的内存开销以及页表的转换工作。所以大多数云厂商会在 Host 操作系统上开启大页(Linux 操作系统通常是应用通明大页技术),以缩小内存相干的虚拟化开销。
服务器对网络性能的要求是很高的,当初的网络硬件都反对网卡多队列技术,通常状况下须要将 VM 中的网络中断扩散给不同的 CPU 核来解决,以防止单核转发带来的性能瓶颈。
Host 操作系统须要治理它下面的一个或多个 VM(虚拟机,Virtual Machine),以及前述提及的解决网卡中断,这会带来肯定的 CPU 耗费。阿里云上一代机型,服务器总计是 96 核(即 96 个硬件线程 HT,理论是 48 个物理核),但最多只能调配出 88 核,须要保留 8 个核(相当于物理机 CPU 缩小 8.3%)给 Host 操作系统应用,同时因为 I / O 相干的虚拟化开销,整机性能会降落超过 10%。
阿里云为了最大限度的升高虚拟化的开销,研发了牛逼的“弹性裸金属服务器 – 神龙”,号称岂但不会因为虚拟化升高性能,反而会晋升局部性能(次要是网络转发)。
1.1.4 神龙服务器
为了防止虚拟化对性能的影响,阿里云(相似还有亚马逊等云厂商的相似计划)研发了神龙服务器。简略来说就是设计了神龙 MOC 卡,将大部分虚拟机管理工作、网络中断解决等从 CPU offload 到 MOC 卡进行解决。神龙 MOC 卡是一块 PCI-E 3.0 设施,其内有专门设计的用于网络解决的 FPGA 芯片,以及 2 颗低功耗的 x86 处理器(据传是 Intel Atom),最大限度的接手 Host 操作系统的虚拟化管理工作。通过这样的设计,在网络转发性能上甚至能做到 10 倍于裸物理机,做到了当之无愧的裸金属。104 核的物理机能够间接虚构出一台 104 核的超大 ECS,再也不必保留几个外围给 Host 操作系统应用了。
1.2 VPC
VPC(虚构专有云,Virtual Private Cloud),大多数云上的用户都心愿本人的网络与其它的客户隔离,就像自建机房一样,这外面最重要的是网络虚拟化技术,目前阿里云采纳的是 VxLAN 协定,它底层采纳 UDP 协定进行数据传输,整体数据包构造如下图所示。VxLAN 在 VxLAN 帧头中引入了相似 VLAN ID 的网络标识,称为 VxLAN 网络标识 VNI(VxLAN Network ID),由 24 比特组成,实践上可反对多达 16M 的 VxLAN 段,从而满足了大规模不同网络之间的标识、隔离需要。这一层的引入将会使原始的网络包减少 50 Bytes 的固定长度的头。当然,还须要与之匹配的交换机、路由器、网关等等。
1.3 容器技术
虚拟化技术的极致优化尽管曾经极大解决了 VM 层虚拟化的额定开销问题,但 VM 操作系统层的开销是无奈防止的,同时现在的 Java 利用大多都能够做到单过程部署,VM 操作系统这一层的开销显得有一些节约(以后,它换来了极强的隔离性和安全性)。容器技术构建于操作系统的反对,目前次要应用 Linux 操作系统,容器最有名的是 Docker,它是基于 Linux 的 cgroup 技术构建的。VM 的体验切实是太好了,所以容器的终极目标就是具备 VM 的体验的同时还没有 VM 操作系统层的开销。在容器里,执行 top、free 等命令时,咱们只心愿看到容器视图,同时网络也是容器视图,不得不排查问题须要抓包时能够仅抓容器网络的包。
目前阿里云 ECS 16GB 内存的机型,实际上操作系统内看到的可用内存只有 15GB,32GB 的机型则只有 30.75GB。容器没有这个问题,因为实际上在容器上运行的工作仅仅是操作系统上的一个或多个过程而已。因为容器的这种逻辑隔离个性,所以不同企业的利用基本上是不太可能部署到同一个操作系统上的(即同一台 ECS)。
容器最影响性能的点是容器是否超卖、是否绑核、核调配的策略等等,以及前述的泛滥知识点都会对性能有不小的影响。
通常企业外围利用都会要求绑核(即容器的多个 vCPU 的散布地位是确定以及专用的,同时还得思考 Intel HT、AMD CCD/CCX、NUMA 等问题),这样性能的确定性才能够失去保障。
在 Docker 与 VM 之间,其实还存在别的更平衡的容器技术,大多数公司称之为平安容器,它采纳了硬件虚拟化来实现强隔离,但并不需要一个很重的 VM 操作系统(比方 Linux),取而代之的是一个十分轻的微内核(它仅反对实现容器所必须的局部内核性能,同时大多数工作会转发给 Host 操作系统解决)。这个技术是云厂商很想要的,这是他们售卖牢靠 FaaS(性能即服务,Function as a Service)的根底。
2. 获取性能数据
进行性能优化前,咱们须要做的是收集到足够、精确以及有代表性的性能数据,剖析性能瓶颈,而后能力进行无效的优化。
评估一个利用的性能无疑是一件非常复杂的事,大多数状况下一个利用会有很多个接口,且同一个接品会因为入参的不同或者外部业务逻辑的不同带来十分大的执行逻辑的变动,所以咱们得首先想分明,咱们到底是要优化什么业务场景下的性能(对于订单来说,兴许就是下单)。
在性能测试用例跑起来后,怎么样拿到咱们想要的实在的性能数据就很要害了,因为观测者效应的存在(指“观测”这种行为对被观测对象造成肯定影响的效应,它在生活中极其常见),获取性能数据的同时也会对被测利用产生或多或少的影响,所以咱们须要深刻的理解咱们所应用的性能数据获取工具的工作原理。
具体到 Java 上(其它语言也根本是相似的),咱们想晓得一个利用到底在做什么,次要有两种伎俩:
- Instrumentation(代码嵌入):指的是能够用独立于应用程序之外的代理(Agent)程序来监测运行在 JVM 上的应用程序,包含但不限于获取 JVM 运行时状态,替换和批改类定义等。艰深点了解就是在函数的执行前后插代码,统计函数执行的耗时。理解基本原理后,咱们大略会晓得,这种形式对性能的影响是比拟大的,函数越简短执行的次数越多影响也会越大,不同它的益处也是不言而喻的:能够统计出函数的执行次数以及不漏过任何一个细节。这种形式个别用于利用晚期的优化剖析。
- Sampling(采样):采纳固定的频率打断程序的执行,而后拉取各线程的执行栈进行统计分析。采样频率的大小决定了观测后果的最小粒度和误差,一些执行次数较多的小函数可能会被统计的偏多,一些执行次数较少的小函数可能不会被统计到。支流的操作系统都会从内核层进行反对,所以这种形式对利用的性能影响绝对较少(具体多少和采样频率强相干)。
在性能数据里,工夫也是一个十分重要的指标,次要有两类: - CPU Time(CPU 工夫):占用的 CPU 工夫片的总和。这个工夫次要用来剖析高 CPU 耗费。
- Wall Time(墙上工夫):实在流逝的工夫。除了 CPU 耗费,还有资源期待的工夫等等,这个工夫次要用来剖析 rt(响应工夫,Response time)。
性能数据获取形式 + 工夫指标一共有四种组合形式,每一种都有它们的最实用的场景。不过须要记住,Java 利用通常都须要至多 5 分钟(这是一个经验值,通常 Server 模式的 JVM 须要在办法执行 5000~10000 次后才会进行 JIT 编译)的大流量继续测试能力使利用的性能达到稳固状态,所以除非你要剖析的是利用正在预热时的性能,否则你须要期待 5 分钟以上再开始收集性能数据。
Linux 零碎下面也有不少十分好用的性能监控工具,如下图:
2.1 结构性能测试用例
通常咱们都会剖析一个或多个典型业务场景的性能,而不仅仅是某一个或多个 API 接口。比方对于双 11 大促,咱们要剖析的是导购、交易、发优惠券等业务场景的性能。
好的性能测试用例的须要可能反映典型的用户和零碎行为(并不是所有的。咱们无奈做到 100% 反映实在用户场景,只能逐步靠近它),比方一次下单均匀购买多少个商品(理论的用例里会细分为:购买一个商品的占比多少,二个商品的占比多少,等等)、热点售买商品的数量与成交占比、用户数、商品数、热点库存散布、买家人均有多少张券等等。像淘宝的双 11 的压测用例,像这样要害的参数会多达 200 多个。
理论执行时,咱们冀望测试用例是能够稳固继续的运行的(比方不会跑 30 分钟下单发现库存没了,优惠券也没了),缓存的命中率、DB 流量等等的内部依赖也能够达到一个稳固状态,在秒级工夫(个别不须要更细了)粒度上利用的性能也是稳固的(即申请的计算复杂度在工夫粒度上是均匀分布的,不会一会高一会低的来回抖动)。
为了配合性能测试用例的执行,有的时候还须要利用零碎做一些相应的革新。比方对于会应用到缓存的场景来说,刚开始命中率必定是不高的,但跑了一会儿过后就会缓缓的变为 100%,这显然不是通常实在的状况,所以可能会配合写一些逻辑,来让缓存命中率始终维持在某个特定值上。
综上,一套好的性能测试用例是发展后续工作所必不可少的,值得咱们在它下面花工夫。
2.2 实在的测试环境
放弃测试环境与其实环境的一致性是极其重要的,然而往往也是很难做到的,所以大多数互联网公司的全链路压测计划都是间接应用线上环境来做性能测试。如果咱们无奈做到应用线上环境来做性能测试,那么就须要花上不少的精力来认真比照咱们所应用的环境与线上环境的差别,确保咱们晓得哪一些性能数据是能够值得置信的。
间接应用线上环境来做性能测试也并不是那么简略,这须要咱们有一套整体解决方案来让压测流量与实在流量进行辨别,个别都是在流量中加一个压测标进行全链路的透传。同时基本上所有的根底组件都须要进行革新来反对压测,次要有:
- DB:为业务表建设对应的压测表来存储压测数据。不应用减少字段做逻辑隔离的起因是容易把它们与正式数据搞混,同时也不便于独自清理压测数据。不应用新建压测库的起因是:一方面它违反了咱们应用线上环境做性能压测的根本思考,另一方面也会导致利用端多了一倍的数据库连贯。
- 缓存:为缓存 Key 减少非凡的前缀,如__yt_。缓存大多数没有表的概念,看起来就是一个微小的 Map 存储一样,所以除了加固定前缀并没有太好的方法。不过为了缩小压测数据的存储老本,通常须要:1) 在缓存 client 包中做一些解决来缩小压测数据的缓存过期工夫;2)缓存控制台提供专门清理压测数据的性能。
- 音讯:在发送、生产时透传压测标。尽量做到不须要业务团队的开发同学感知,在音讯的内部结构中减少是否是压测数据的标记,不须要业务团队申请新的压测专用的 Topic 之类。
- RPC:透传压测标。当然,HTTP、DUBBO 等具体的 RPC 接口透传的计划会是不同的。
- 缓存、数据库 client 包:依据压测标做申请的路由。这须要配合后面提到的具体缓存、DB 的具体实现计划。
- 异步线程池:透传压测标。为了缩小反对压测的革新代价,通常都会应用 ThreadLocal 来存储压测标,所以当应用到异步线程池的时候,须要记得带上它。
- 利用内缓存:做好压测数据与正式数据的隔离。如果压测数据的主键或者其它的惟一标识符能够让咱们显著的让它与正式数据辨别开来,兴许不必做太多,否则咱们兴许须要思考要么再 new 一套缓存、要么为压测数据加上一个什么特地的前缀。
- 自建工作:透传压测标。须要咱们自行做一些与前述提到的音讯组件相似的事件,毕竟工作和音讯从技术上来说是很像很像的。
- 二方、三方接口:具体分析与解决。须要看二方、三方接口是否反对压测,如果反对那么很好,咱们依照对方冀望的形式进行参数的传递即可,如果不反对,那么咱们须要想一些别的奇技婬巧(比方开设一个压测专用的商户、账户之类)了。
为了升高性能测试对用户的影响,通常都会抉择流量低峰时进行,个别都是中午。当然,如果咱们能有一套绝对独立的折中计划,比方应用小得物环境、在反对单元化的零碎中应用局部单元等等,就能够做到在任何时候进行性能测试,实现性能测试的常态化。
2.3 JProfiler 的应用
JProfiler 是一款十分成熟的产品,很贵很好用,它是专门为 Java 利用的性能剖析所筹备的,而且是跨平台的产品,是我常常应用的工具。它的大体的架构如下图所示,Linux agent 加上 Windows UI 是最举荐的应用形式,它岂但同时反对 Instrumentation & Sampling,CPU Time & Wall Time 的选项,而且还领有十分易用的图形界面。
剖析时,咱们只须要将其 agent 包上传到利用中的某个目录中(如:/opt/jprofiler11.1.2),而后增加 JVM 的启动选项来加载它,我通常都这样配置:
-agentpath:/opt/jprofiler11.1.2/bin/linux-x64/libjprofilerti.so=port=8849
接下来咱们重启利用,这里的批改就会失效了。应用这个配置,Java 过程在开始启动时须要期待 JProfiler UI 的连贯才会持续启动,这样咱们能够进行利用启动时性能的剖析了。
JProfiler 的性能很多,就不一一介绍了,大家能够浏览其官网文档。采集的性能数据还能够保留为 *.jps 文件,不便后续的剖析与交换。其典型的剖析界面如下图所示:
JProfiler 的一些毛病:
1)须要在 Java 利用启动加载 agent(当然它也有启动后 attach 的形式,然而有不少的限度),不太便于短时间的剖析一些紧急的性能问题;
2)对 Java 利用的性能影响偏大。应用采样的形式来采集性能数据开销必定会低很多,但还是没有接下来要介绍的 perf 做的更好。
2.4 perf 的应用
perf 是 Linux 下面当之无愧的性能剖析工具的一哥,这一点须要特别强调一下。岂但能够用来剖析 Linux 用户态利用的性能,甚至还罕用来剖析内核的性能。它的模块构造如下图所示:
想像一下这样的场景,如果咱们换了一家云厂商,或者云厂商的服务器硬件(次要就是 CPU 了)有了更新迭代,咱们想晓得具体性能变动的起因,有什么方法吗?perf 就能很好的胜任这个工作。
CPU 的设计者为了帮忙咱们剖析利用执行时的性能,专门设计了相干的硬件电路,PMU(性能监控单元,Performance Monitor Unit)就是这其中最重要的局部。简略来说外面蕴含了很多性能计数器(图中的 PMCs,Performance Monitor Counters),perf 能够读取这些数据。不仅如此,内核层面还提供了很多软件级别的计数器,perf 同样能够读取它们。一些和 CPU 架构相干的要害指标,能够理解一下:
- IPC(每周期执行指令条数,Instruction per cycle):基于功耗 / 性能的思考,大多数服务器处理器的频率都在 2.5~2.8GHz 的范畴,这代表同一时间片内的周期数是差别不大的,所以单个周期可能执行的指令条数越多阐明咱们的利用优化的越好。过多的跳转指令(即 if else 这类代码)、浮点计算、内存随机拜访等操作显然是十分影响 IPC 的。有的人比拟喜爱说 CPI(Cycle per instruction),它是 IPC 的倒数。
- LLC Cache Miss(最初一级缓存失落):偏内存型的利用须要关注这个指标,过大的话代表咱们没有利用好处理器或操作系统的缓存预加载机制。
- Branch Misses(预测谬误的分支指令数):这个值过高代表了咱们的分支类代码设计的不够敌对,应该做一些调整尽量满足处理器的分支预测算法的冀望。如果咱们的分支逻辑依赖于数据的话,做一些数据的调整一样能够进步性能(比方这个经典案例:数据有 100 万个元素,值在 0 -255 之间,须要统计值小于 128 的元素个数。提前对数组排序再进行 for 循环判断会运行的更快)。
因为 perf 是为 Linux 上的原生利用筹备的,所以间接应用它剖析 Java 应用程序的话,它只会把 Java 程序当成一个一般的 C ++ 程序来对待,不能显示出 Java 的调用栈和符号信息。好消息是 perf-map-agent 插件我的项目解决了这个问题,这个插件能够导出 Java 的符号信息并帮忙 perf 进行 Java 线程的栈回溯,这样咱们就能够应用 perf 来剖析 Java 应用程序的性能了。执行 perf top -p <Java 过程 Id> 后,就能够看到 perf 显示的实时性能统计信息了,如下图:
perf 仅反对采样 + CPU Time 的工作模式,不过它的性能十分好,进行一般的 Java 性能剖析工作时通常只会引入 5% 以内的额定开销。应用环境变量 PERF_RECORD_FREQ 来设置采样频率,推荐值是 999。不过如你所见,它是规范的 Linux 命令行式的交互行为,不是那么不便。同时尽管他是能够把性能数据录制为文件供后续持续剖析的,但要记得同时保留 Java 过程的符号文件,不然你就无奈查看 Java 的调用栈信息了。尽管限度不少,然而 perf 却是最适宜用来即时分析线上性能问题的工具,不须要任何后期的筹备,随时可用,同时对线上性能的影响也很小,能够很快的找到性能瓶颈点。在装置好 perf(须要 sudo 权限)以及 perf-map-agent 插件后,通常应用如下的命令来关上它:
export PERF_RECORD_SECONDS=5 && export PERF_RECORD_FREQ=999
./perf-java-report-stack <Java 过程 pid>
重点须要介绍的信息就是这么多,实际过程中须要用好 perf 的话须要再查阅相干的一些文档。
2.5 内核态与用户态
对操作系统有理解的同学会常常听到这两个词,也都晓得常常在内核态与用户态之间交互是十分影响性能的。从执行层面来说,它是处理器的设计者设计进去构建现在稳固的操作系统的根底。有了它,用户态(x86 下面是 ring3)过程无奈执行特权指令与拜访内核内存。大多数时候为了平安,内核也不能把某局部内核内存间接映射到用户态上,所以在进行内核调用时,须要先将那局部参数写入到特定的传参地位,而后内核再从这里把它想要的内容复制走,所以多会一次内存的复制开销。你看到了,内核为了平安,总是小心翼翼的面对每一次的申请。
在 Linux 上,TCP 协定反对是在内核态实现的,已经这有很多充沛的理由,但内核上的更新迭代速度必定是慢于现在互联网行业的要求的,所以 QUIC(Quick UDP Internet Connection,谷歌制订的一种基于 UDP 的低时延的互联网传输层协定)诞生了。现在支流的倒退思路是能不必内核就不必内核,尽量都在用户态实现所有。
有一个例外,就是抢占式的线程调度在用户态做不到,因为实现它须要的定时时钟中断只能在内核态设置和解决。协程技术始终是重 IO 型 Java 利用缩小内核调度开销的极好的技术,然而很遗憾它须要执行线程被动让出剩余时间片,不然与内核线程关联的多个用户态线程就可能会饿死。阿里巴巴的 Dragonwell 版 JVM 还尝试了动静调整策略(即用户态线程不与固定的内核态线程关联,在须要时能够切换),不过因为前述的时钟中断的限度,也不能工作的很好。
包含现在的虚拟化技术,尤其是 SR-IOV 技术,只须要内核参加接口调配 / 回收的工作,两头的通信局部齐全是在用户态实现的,不须要内核参加。所以,如果你发现你的应用程序在内核态上消耗了太多的工夫,须要想一想是否能够让它们在用户态实现。
2.6 JVM 要害指标
JVM 的指标很多,但有几个要害的指标须要大家常常关注。
- GC 次数与工夫:包含 Young GC、Full GC、Concurrent GC 等等,Young GC 频率过高往往代表过多长期对象的产生。
- Java 堆大小:包含整个 Java 堆的大小(由 Xmx、Xms 两个参数管制),年老代、老年代别离的大小。不同时指定 Xms 和 Xmx 很可能会让你的 Java 过程始终应用很小的堆空间,过大的老年代空间大多数时候也意味着内存的节约(多调配一些给年老代将显著升高 Young GC 频率)。
- 线程数:通常咱们采纳的都是 4C8G(4Core vCPU,8GB 内存)、8C16G 的机型,调配出上千个线程大多数时候都是谬误的。
- Metaspace 大小和使用率:不要让 JVM 动静的扩大元空间的大小,尽量通过设置 MetaspaceSize、MaxMetaspaceSize 让它固定住。咱们须要晓得咱们的利用到底须要多少元空间,过多的元空间占用以及过快的增长都意味着咱们可能谬误的应用了动静代理或脚本语言。
- CodeCache 大小和使用率:同样的,不要让 JVM 动静的扩大代码缓存的大小,尽量通过设置 InitialCodeCacheSize、ReservedCodeCacheSize 让它固定住。咱们能够通过它的变动来发现最近是不是又引入了新的类库。
- 堆外内存大小:限度最大堆外内存的大小,计算好 JVM 各块内存的大小,不要给操作系统触发 OOM Killer 的机会。
2.7 理解 JIT
字节码的解释执行必定是相当慢的,Java 之所以这么风行和他领有高性能的 JIT(即时,Just in time)编译器也有很大的关系。但编译过程自身也是相当耗费性能的,且因为 Java 的动静个性,也很难做到像 C /C++ 这样的编程语言提前编译为 native code 再执行,这导致 Java 利用的启动是相当慢的(大多数都须要 3 分钟以上,Windows 操作系统的启动都不须要这么久),而且同一个利用的多台机器之间并不能共享 JIT 的教训(这显然是极大的节约)。
咱们应用的 JVM 都采纳分层编译的策略,依据优化的水平不同从低到高别离是 C1、C2、C3、C4,C4 是最快的。JIT 编译器会收集不少运行时的数据,来领导它的编译策略,外围假如是能够逐渐收集信息、仅编译热点办法和门路。然而这个假如并不总是对的,比方对于双 11 大促的场景来说,咱们的流量是到点忽然垂直减少的,以及局部代码分支在某个工夫点之前并不会运行(比方某种优惠要零点过后才会失效可用)。
2.7.1 编译
极热的函数通常 JIT 编译器会函数进行内联(inlining)优化,就相当于间接把代码缮写到调用它的中央来缩小一次函数调用的开销,然而如果函数体过大的话(具体要看 JVM 的实现,通常是几百字节)将不能内联,这也是为什么编程标准外面通常都会说不要将一个函数写的过大的起因。
JVM 并不会对所有执行过的办法都进行 JIT 优化,通常须要 5000~10000 次的执行后才进行,而且它还仅仅编译那些已经执行过的分支(以缩小编译所须要的工夫和 Code Cache 的占用,优化 CPU 的执行性能)。所以在写代码的时候,if 代码前面紧跟的代码块最好是较大概率会执行到的,同时尽量让代码执行流比拟固定。
阿里巴巴的 Dragonwell 版 JVM 新增了一些性能,能够让 JVM 在运行时记录编译了哪些办法,再把它们写入文件中(还能够分发给利用集群中别的机器),下次 JVM 启动时能够利用这部分信息,在第一次运行这些办法时就触发 JIT 编译,而不是在执行上千次当前,这会极大的晋升利用启动的速度以及启动时 CPU 的耗费。不过动静 AOP(Aspect Oriented Programming)代码以及 lambda 代码将不能享受这个红利,因为它们运行时理论生成的函数名都是形如 MethodAccessor$1586 这类以数字结尾的不稳固的名称,这次是 1586,下一次就不晓得什么了。
2.7.2 退优化
JIT 编译器的激进优化并不总是对的,如果它发现目前须要的执行流在以前的编译中被省略了的话,它就会进行退优化,即从新提交该办法的编译申请。在新的编译申请实现之前,该办法很大可能是进行解释执行(如果存在还未抛弃的低阶编译代码,比方 C1,那么就会执行 C1 的代码),加上编译线程的开销,这会导致短时间内利用性能的降落。在双 11 大促这种场景下,也就是零点的顶峰时刻,因为退优化的产生,导致利用的性能比压测时有相当显著的升高。
阿里巴巴的 Dragonwell 版 JVM 在这一块也提供了一些选项,能够在 JIT 编译时去除一些激进优化,以避免退优化的产生。当然,这会导致利用的性能有强劲的降落。
2.8 实在案例
在性能优化的实际过程中,有一句话须要重复粗浅的了解:通过数据反映所有,而不是据说或者教训。
上面列举一个在重构我的项目中进行优化的利用的性能比拟数据,以展现如何利用咱们后面说到的常识。这个利用是偏末端的利用,上游根本不再依赖其它利用。特地阐明,【此案例非得物案例】,也不对应任何一个实在的案例。
利用容器:8C32G,Intel 8269CY 处理器,8 个处理器绑定到 4 个物理核的 8 个 HT 上。
老利用:12.177.126.52,12.177.126.141
新利用:12.177.128.150, 12.177.128.28
测试接口与流量 |
---|
确认订单优惠渲染 单机 @1012QPS |
下单确认优惠 单机 @330QPS |
下单核销优惠 单机 @416QPS |
工夫:2021-05-03
根底数据
我的项目 \ 数据 | 老利用(收集工夫 21:00) | 新利用(收集工夫 21:15) |
---|---|---|
CPU | 43.44%(user:36.78%) | 45.04%(user:40.52%) |
RT: 确认订单优惠渲染 | 5.2ms | 6.1ms |
RT: 下单确认优惠 | 7.5ms | 4.7ms |
RT: 下单核销优惠 | 1.0ms | 1.3ms |
GC 频次 | 18.5 | 25 |
Net | In:11.4M Out:13.7M | In:7.3M Out:12.4M |
Thread | 669(Daemon:554) | 789(Daemon:664) |
每分钟 Java Exception | 5132 | 7324 |
缓存拜访
操作 \QPS | 老利用 | 新利用 | 用处 |
---|---|---|---|
GET:9 | 3550 | 3825 | 商品缓存 |
GET:860 | 4848 | — | 券规定缓存 |
GET:758 | 1165 | 1365 | 卖家缓存 |
GET:8 | 1158 | 1280 | 卖家全局规定缓存 |
PREFIX_GETS:688 | 1425 | — | 流动索引(新利用废除) |
GET:688 | 2282 | — | 流动索引(新利用废除) |
GET:100 | 21 | 21 | 店铺免息优惠缓存 |
PREFIX_GETS:100 | — | 73 | 店铺免息优惠缓存 |
GET:4 | — | 1008 | 店铺某类优惠缓存 |
GET:10086 | 2394 | 2580 | 卡券支付关系缓存 |
GET:770 | 100 | — | SKU 优惠缓存 |
PREFIX_GETS:770 | 92 | — | SKU 优惠缓存 |
GET:88 | 21 | 21 | 商品限购缓存 |
DB 拜访
库表 \QPS | 老利用 | 新利用 | 用处 |
---|---|---|---|
QUERY promotion_detail | 865 | — | 优惠活动 |
KV_GET promotion_detail | — | 898 | |
QUERY promotion_detail_sku | 21 | SKU 优惠活动 | |
QUERY buyer_coupon | 190 | 160 | 优惠券 |
KV_GET buyer_coupon | — | 56 | |
UPDATE buyer_coupon | 55 | 60 | 优惠券 |
2.8.1 初步发现
- 通过内部依赖的差别能够发现,新老利用的代码逻辑会有不同,须要持续深刻评估差别是什么。通常在做收集性能数据的同时,咱们须要有一个简略的剖析和判断,首先确保业务逻辑的正确性,不然性能数据就没有多少意义。
- Java Exception 是很耗费性能的,次要耗费在收集异样栈信息,新老利用较大的异样数区别须要找到起因并解决。
- 新利用的接口“下单确认优惠”RT 有过于显著的降落,其它接口都是晋升的,阐明很可能存在执行门路上较大的差异,也须要深刻的剖析。
3. 开始做性能优化
和获取性能数据须要从底层逐渐理解到下层业务不同,做性能优化却是从下层业务开始,逐步推进到底层,即从高到低进行分层优化。越高层的优化往往难度更低,而且收益还越大,只是须要与业务的深度联合。越低层的优化往往难度比拟大,很难取得较大的收益(毕竟一堆技术精英始终在做着呢),然而通用性比拟好,往往能够实用于多类业务场景。接下来别离聊一聊每一层能够思考的一些方向和理论的例子。
3.1 优化的目标与准则
在聊具体的优化措施之前,咱们先聊一聊为什么要做性能优化。少数状况下,性能优化的目标都是为了老本、效率与稳固。达到同样的业务成果,应用更少的资源,或者带来更好的用户体验(通常是指页面的响应更快)。不怎么思考老本的技术计划往往没有太多的挑战,对于电商平台来说,咱们经常用单订单老本来掂量机器老本,比方淘宝这个值可能在 0.17 元左右。业务倒退的晚期往往并不是那么在意老本,反而更加看重效率,等到逐渐成熟起来过后,会缓缓的开始器重老本,艰深的讲就是开始比的是有没有,而后比的是好不好。所以在不同的期间,咱们进行性能优化的目标和方向会有所偏重。
互联网行业是一个疾速倒退的行业,研发效率对业务的衰弱倒退是至关重要的,在进行优化的过程中,咱们在技术计划的抉择上须要兼顾研发效率的晋升(至多不能侵害过多),给人一种“它原本应该就是这样”的感觉,而不是做一些显著无奈长期继续、前期保护老本过高的设计。好的优化计划就像艺术品一样,每一个看到的人都会为之赞叹。
3.2 业务
大家为什么会在双 11 的零点开始上各大电商网站买货色?春节前大家为什么都在上午 10 点抢火车票?等等,其实都是业务上的设计。筹备一大波机器资源应用 2 个月就为了双 11 峰值的那几分钟,实际上是极大的老本节约,所以为了不那么节约,淘宝的双 11 预售付尾款的工夫通常都放在凌晨 1 点。12306 早几年是每临进春节必挂,因为想要回家的游子切实是太多,所以前面缓缓依照车次将售卖工夫打散,参考其布告:
自往年 1 月 8 日起,为防止大量旅客在互联网排队购票,把原来的 8 点、10 点、12 点、15 点四个工夫节点放票改为 15 个节点放票,即:8 点 -18 点,其间每小时或每半小时均有局部新票起售。
这些策略都能够极大的升高零碎的峰值流量,同时对于用户应用体验来说根本是无感的,诸如此类的许多优化是咱们最开始就要去思考的(不过请记住永远要把业务成果放在第一位,和业务讲业务,而不是和业务讲技术)。
3.3 零碎架构
诸如商品详情页动静拆散(动态页面与动静页面离开不同零碎拜访),用户接口层(即 HTTP/ S 层)与后端(Java 层)合并部署等等,都是架构优化的胜利榜样。业务架构师往往会将零碎设计为很多层,然而在运行时,他们往往能够部署在一块儿,以缩小跨过程、跨机器、跨地区通信。
淘宝的单元化架构在性能上来看也是一个很好的设计,一个交易申请简直所有的解决都能够关闭在单元内实现,缩小了很多跨地区的网络长传带宽需要。
富客户端计划对于像商品信息、用户信息等根底数据来说也是很好的计划,毕竟大多数状况下它们都是拜访 Redis 等缓存,多一次到服务端的 RPC 申请总是显得很多余,当然,后续须要降级数据结构的时候则须要做更多的工作。
对于架构的探讨是永恒的话题,同时不同的公司有不同的背景,理论进行优化时也须要依据理论状况来取舍。
3.4 调用链路
在分布式系统里不可避免须要依赖很多上游服务能力实现业务动作,怎么依赖、依赖什么接口、依赖多少次则是须要深刻思考的问题。借助调用链查看工具(在得物,这个工具应该是 dependency),咱们能够仔细分析每一个业务申请,而后去思考它是不是最优的形式。举一个我据说过的例子(特地阐明,【此案例非得物案例】):
背景:营销团队接到了一个拉新的需要,它会在公司周年庆的 10:00 造成爆点,预计会产生最高 30 万的 UV,而后只须要点击流动页的参加按钮(预估转化率是 75%),就会弹出一个组团页,让用户邀请他的好友参加组团,每多邀请一个敌人,在团内的用户都能够享受更多的优惠折扣。
营销团队为组团页提供了一个新的后盾接口,最开始这个接口须要实现这些事:
在“为用户创立一个新团”这一步,会同时将新团的信息长久化到数据库中,依照业务的转化率预估,这会有 30W*75%=22.5W QPS 的峰值流量,基本上咱们须要 10 个左右的数据库实例能力撑持这么高的并发写入。但这是必要的吗?显然不是,咱们都晓得这类须要用户转发的流动的转化率是有如许的低(大多数不到 8%),同时对于那些没人参加的团,将它们的信息保留在数据库中也是意义不大的。最初的优化计划 是:
1)在“为用户创立一个新团”时,仅将团信息写入 Redis 缓存中;
2)在用户邀请的敌人批准参团时,再将团信息长久化到数据库中。新的设计,仅仅须要 1.8W QPS 的数据库并发写入量,应用原有的单个数据库实例就能够撑持,在业务成果上也没有任何区别。
除了上述的例子,链路优化还有十分多的办法,比方屡次调用的合并、仅在必要时才调用(相似 COW [Copy on write]思维)等等,须要大家联合具体的场景去剖析设计。
3.5 利用代码
利用代码的优化往往是咱们最热衷和善于的,毕竟业务与零碎架构的优化往往须要架构师出马。JProfiler 或者 perf 的分析(Profiling)数据是十分有用的参考,任何不基于理论运行数据的猜想往往会让咱们误入歧途,接下来咱们须要做的大多数时候都是“找热点 -> 优化”,而后“找热点 -> 优化”,而后始终循环。找热点不是那么难,难在精确的剖析代码的逻辑而后判断到底它应该耗费多少资源(通常都是 CPU),而后制订优化计划来达到目标,这须要相当多的优化教训。
从我做过的性能优化来总结,大略次要的问题都产生在这些中央:
- 和字符串过不去:十分多的代码喜爱将多个 Java 变量应用 StringBuilder 拼接起来(那些连 StringBuilder 都不会用,只会应用 + 的家伙就更让人头疼了),而后再找准机会 spilt 成多个 String,而后再转换成别的类型(Long 等)。好吧,下次应用 StringBuilder 时记得指定初始的容量大小。
- 日志满天飞:管它有用没有,反正打了是不会错的,毕竟谁都经验过没有日志时排查问题的苦楚。怎么说呢,打印有用的、无效的日志是程序员的必修课。
- 青睐 Exception:不晓得是不是某些 Java 的追随者吹过头了,说什么 Java 的 Exception 和 C /C++ 的错误码一样高效。然而事实并不是这样的,Exception 进行调用栈的回溯是相当耗费性能的,尤其是还须要将它们打印在日志中的时候,会更加蹩脚。
- 容器的深拷贝:List、HashMap 等是大家十分喜爱的 Java 容器,但 Java 语言并没有好的机制阻止他人批改它,所以大家经常深拷贝一个新的进去,反正也就是一句代码的事儿。
- 对 JSON 情有独钟:将对象序列化为 JSON string,将 JSON string 反序列化为对象。前者次要用来打日志,后者次要用来读配置。JSON 是挺好,只是请别用的到处都是(还把每个属性的名字都取的老长)。
- 反复反复再反复:一个申请里查问同样的商品 3 次,查问同样的用户 2 次,查问同样的缓存 5 次,都是常有的事,也许多查问几次一致性更好吧 :(。还有一些根本不会变的配置,也会放到缓存中,每次应用的时候都会从缓存中读出来,反序列化,而后再应用,嗯,挺重的。
- 多线程的乐趣:不会写多线程程序的开发不是好开发,所以大家都喜爱 new 线程池,而后异步套异步。在流量很低的时候,看起来多线程确实解决了问题(比方 RT 确实变小了),然而流量上来过后,问题反而好转了(毕竟咱们支流的机器都是 8 核的)。
再重申一遍,在这一步,找到问题并不是太艰难,找到好的优化计划却是很艰难和充斥考验的。
3.6 缓存
大多数的缓存都是 Key、Value 的构造,抉择紧凑的 Key、Value 以及高效的序列化、反序列化算法是重中之重(二进制序列化协定比文本序列化协定快的太多了)。还有的缓存是 Prefix、Key、Value 的构造,次要的区别是谁来决定理论的数据路由到哪台服务器进行解决。单条缓存不能太大,基本上大于 64KB 就须要小心了,因为它总是由某一台理论的服务器在解决,很容易将进口宽带或计算性能打满。
3.7 DB
数据库比起缓存来说他能抗的流量就低太多了,基本上是差一个数量级。SQL 通信协议尽管很易用,但实际上是十分低效的通信协议。对于 DB 的优化,通常都是从缩小写入量、缩小读取量、缩小交互次数、进行批处理等等方面着手。DB 的优化是一门简单的学识,很难用一篇文章说分明,这里仅举一些我认为比拟有代表性的例子:
- 应用 MultiQuery 缩小网络交互:MySQL 等数据库都反对将多条 SQL 语言写到一起,一起发送给 DB 服务器,这会将屡次网络交互缩小为一次。
- 应用 BatchInsert 代替屡次 insert:这个很常见。
- 应用 KV 协定取代 SQL:阿里云数据库团队在数据库服务器下面外挂了一个 KV 引擎,能够间接读取 InnoDB 引擎中的数据,bypass 掉了数据库的数据层,使得基于惟一键的查问能够比应用 SQL 快 10 倍。
- 与业务联合:淘宝下单时能够同时应用多达 10 个红包,这意味着一次下单须要发送至多 10 次 update SQL。假如一次下单应用了 N 个红包,基于对业务行为的剖析,会发现前 N - 1 个都是全额应用的,最初一个可能会局部应用。对于应用完的红包,咱们能够应用一条 SQL 就实现更新。
update red_envelop set balance = 0 where id in (...);
- 热点优化:库存的热点问题是每个电商平台都面临的问题,应用数据库来扣减库存必定是可靠性最高的计划,然而基本上都很难冲破 500tps 的瓶颈。阿里云数据库团队设计了新的 SQL hint,配合上第 1 条说的 MultiQuery 技术,与数据库进行一次交互就能够实现库存的扣减。同时加上数据库内核的针对性优化,曾经能够实现 8W tps 的热点扣减能力。下表中的 commit_on_success 用来表明,如果 update 执行胜利就立刻提交,这样能够让库存热点行的锁占用工夫降到最低。target_affect_row(1)以及 rollback_on_fail 用来限度当库存售罄时(即 inv_count – 1 >= 0 不成立)update 执行失败并回滚整个事务(即后面插入的库存流水作废)。
insert 库存扣减流水;
update /* commit_on_success rollback_on_fail target_affect_row(1) */ inventory
set inv_count = inv_count - 1
where inv_id = 11222 and inv_count - 1 >= 0;
3.8 运行环境
咱们的代码是运行在某个环境中的,这个环境有很多常识是咱们须要理解的,如果下面所有的优化实现后还不能满足要求,那么咱们也不得不向下深刻。这可能是一个艰难的过程,但也会是一个乏味的过程,因为你终于有了和各畛域的大佬们交换探讨的机会。
3.8.1 中间件
目前大多数中间件的代码是和咱们的业务代码运行在一起的,比方监控采集、音讯 client、RPC client、配置推送 client、DB 连贯组件等等。如果你发现这些组件的性能问题,那么能够大胆的提出来,不要胆怯挫伤到谁 :)。
我遇到过这样的一些场景:
-
利用偶然会大量的产生 ygc:排查到的起因是,在咱们依赖的服务产生地址列表变动(比方产生了重启、掉线、扩容等场景)时,RPC client 会接管到大量的推送,而后解析这些推送的信息,而后再更新一大堆内存构造。提出的优化倡议是:
1)地址推送从全量推送扭转为增量推送;
2)地址列表从挂接到服务接口维度更改为挂接到利用维度。 - DB 连贯组件过多的字符串拼接:DB 连贯组件须要进行 SQL 的解析来计算分库分表等信息,但实现下面不够优雅,拼接的字符串过多了,导致执行 SQL 时内存耗费过多。
3.8.2 容器
对于容器技术自身大多数时候咱们做不了什么,往往就是尽量采纳最新的技术(比方应用阿里云的神龙服务器什么的),不过在梆核(即容器调度)方面往往能够做不少事。咱们的利用和谁运行在一起、相互之间有资源争抢吗、有没有跨 NUMA 调度、有没有资源超卖等等问题须要咱们关注(当然,这须要容器团队提供相应的查看工具)。这里次要有两个须要思考的点:
- 是不是反对离在线混部:在线工作要求实时响应,而离线工作的运行又须要消耗十分多的机器。在双 11 大促这样的场景,把离线机器借过去用几个小时就能够缩小相应的在线机器洽购,能省下很多钱。
- 基于业务的调度:把高耗费的利用和低消耗的利用部署在一起,同时如果单方的峰值时刻还不完全相同,那就太美好啦。
3.8.3 JVM
为了解决重 IO 型利用线程过多的问题开发了协程。
为了解决 Java 容器过多小对象的问题(如 HashMap 的 K, V 都只能是包装类型)开发了值容器。
为了解决 Java 堆过大时 GC 工夫过长的问题(当然还有感觉 Java 的内存治理不够灵便的起因)开发了 GCIH(GC Invisible Heap,淘宝双 11 期间局部热点优惠活动的数据都是存在 GCIH 当中的)。
为了解决 Java 启动时的性能问题(即代码要跑好几千次才进行 JIT,而且每次启动都还要反复这个过程)开发了启动 Hint 性能。
为了解决业务峰值时刻 JIT 退优化的问题(即平时不应用的代码执行门路在业务峰值时候须要应用,比方 0 点才失效的优惠)开发了 JIT 编译激进优化去除选项。
尽管目前 JVM 的实现就是你晓得的那样,然而并不代表这样做就始终是正当的。
3.8.4 操作系统
基本上咱们都是应用 Linux 操作系统,新版本的内核通常会带来一些新性能和性能的晋升,同时操作系统还须要为撑持容器(即 Docker 等)做不少事件。对 Host 操作系统来说,开启通明大页、配置好网卡中断 CPU 打散、配置好 NUMA、配置好 NTP(Network Time Protocol)服务、配置好时钟源(不然 clock_gettime 可能会很慢)等等都是必要的。还有就是须要做好各种资源的隔离,比方 CPU 隔离(高优先级工作优先调度、LLC 隔离、超线程技术隔离等)、内存隔离(内存宽带、内存回收隔离防止全局内存回收)、网络隔离(网络宽带、数据包金银铜等级划分)、文件 IO 隔离(文件 IO 宽带的下限与上限、特定文件操作限度)等等。
大多数内核级别的优化都不是咱们能做的,但咱们须要晓得要害的一些影响性能的内核参数,并可能了解大多数内核机制的工作原理。
3.9 硬件
通常咱们都是应用 Intel 的 x86 架构的 CPU,比方咱们正在应用的 Intel 8269CY,不过它的单颗售价得卖到 4 万多块人民币,却只有区区 26C52T(26 核 52 线程)。相比之下,AMD 的 EPYC 7763 的规格就比拟牛逼了(64C128T,256MB 三级缓存,8 通道 DDR4 3200MHz 内存,领有 204GB/ s 的超高内存宽带),但却只有 3 万多一颗。当然,用 AMD 2021 年的产品和 Intel 2019 的产品比照并不是太偏心,次要 Intel 2021 的新品 Intel Xeon Platinum 8368Q 处理器并不争气,仅仅只是晋升到了 38C76T 而已(尽管和自家的上一代产品相比曾经大幅晋升了近 50%)。
除了 x86 处理器,ARM 64 位处理器也在向服务端产品发力,而且这个产业链还能够实现全国产化。华为 2019 年初公布的鲲鹏 920-6426 处理器,采纳 7nm 工艺,具备 64 个 CPU 核,主频 2.6GHz。尽管单核性能上其只有 Intel 8269CY 的近 2 /3,然而其 CPU 核数却要多上一倍还多,加上其售价亲民,同样计算能力的状况下 CPU 局部的老本会降落近一半(当然计算整个物理机老本的话其实降落无限)。
2020 年双 11 开始,淘宝在江苏南通部署了撑持 1 万笔 / s 交易的国产化机房,正是采纳了鲲鹏 920-6426 处理器,同时在 2021 年双 11,更是用上了阿里云自主研发的倚天 710 处理器(也是采纳 ARM 64 位架构)。在将来,更是有可能基于 RISC- V 架构设计本人的处理器。这些事实都在阐明,在处理器的抉择上,咱们还是有不少空间的。
除了采纳通用处理器,在一些非凡的计算畛域,咱们还能够采纳专用的芯片,比方:应用 GPU 减速深度学习计算,在 AI 推理时应用神经网络减速芯片 - 含光 NPU,以及应用 FPGA 芯片进行高性能的网络数据处理(阿里云神龙服务器上应用的神龙 MOC 卡)等等。
已经还有人想过设计能够间接运行 Java 字节码的处理器,尽管最终因为复杂度太高而放弃。
这所有都阐明,硬件也是始终在依据应用场景在一直的进化之中的,永远要充斥想像。