倍-20-21-JDK-优化运行效率-性能大幅优化高达
JDK21颁布曾经过去1个月期间了,除了每次发版肯定更新的GC机制,和一些增强性能外,还引入了一个全新的概念——虚构线程。
什么是虚构线程
先来看一下官方对虚构线程(VisualThreads)的形容(已翻译):
如今,JDK中.lang.Thread的每个实例都是一个平台线程。平台线程在底层操作系统线程上运转Java代码,并在代码的整个生命周期中捕捉操作系统线程。平台线程的数量限度为操作系统线程的数量。
虚构线程是java.lang.thread的一个实例,它在底层操作系统线程上运转java代码,但在代码的整个生命周期内不会捕捉该操作系统线程。这象征着许多虚构线程可以在同一个操作系统线程上运转Java代码,从而有效地共享代码。
虽然平台线程独占了贵重的操作系统线程,但虚构线程却没有。虚构线程的数量可以比操作系统线程的数量大得多。
虚构线程是JDK而不是OS提供的线程的轻量级成功。它们是用户形式线程的一种方式,在其余多线程言语中也很成功(例如Go中的goroutines和Erlang中的processes)。用户形式线程甚至在早期版本的Java中被称为绿色线程,过后操作系统线程还不成熟和宽泛经常使用。但是,Java的绿色线程都共享一个操作系统线程(M:1调度),并且最终被平台线程所逾越,后者被成功为操作系统线程的包装器(1:1调度)。虚构线程驳回M:N调度,其中少量(M)虚构线程被调度在大批(N)OS线程上运转。
简而言之,如今咱们可以轻松地创立一个轻量级的虚构线程,成功繁难性,同时充散施展配件性能。
可以概括为:轻量、多路复用、开支极小、异步编程、可移植性
测试
开局一个繁难的测试:1万并发阻塞1秒模拟web衔接池,经常使用Thread.sleep(1)模拟IO操作须要的期间。
publicstaticvoidmn(String[]args)throwsInterruptedException{longl=System.currentTimeMillis();//normal();virtual();System.out.println(System.currentTimeMillis()-l);}
先经常使用惯例线程池方式运转:
publicstaticvoidnormal()throwsInterruptedException{ExecutorServiceexecutor=Executors.newFixedThreadPool(200);for(inti=0;i<10000;i++){executor.execute(()->{try{System.out.println("normal");Thread.sleep(1000);}catch(InterruptedExceptione){e.printStackTrace();}});}executor.shutdown();executor.awaitTermination(100,java.util.concurrent.TimeUnit.SECONDS);}
失掉如下结果:
图片
经常使用JDK21虚构线程池运转:
publicstaticvoidvirtual()throwsInterruptedException{ExecutorServiceexecutor=Executors.newVirtualThreadPerTaskExecutor();for(inti=0;i<10000;i++){executor.execute(()->{try{System.out.println("jdk21");Thread.sleep(1000);}catch(InterruptedExceptione){e.printStackTrace();}});}executor.shutdown();executor.awaitTermination(100,java.util.concurrent.TimeUnit.SECONDS);}
失掉如下结果:
图片
20多倍的提速!!!
我不了解,但我大受震撼!!!
假设有好友想自己测试一下虚构线程,或许JDK21的其余新个性,可以尝试一下哦~
JDK21官方下载链接:
目前虚构线程还属于预览版本,没有正式启用。假设是IDEA开发工具,在启动的时刻,须要性能VM参数--enable-preview
图片
小结
经过测试,在该例子中,Executors.newFixedThreadPool(size);size的值越大,普通的线程池用时越短,在线程池的大小开明到10000后,用时简直一样。
应该是本例的IO操作是Thread.sleep的缘故,所以不会占用CPU资源。
传统的线程池,普通咱们会指定外围线程数量为CPU外围数*4,最大线程数为CPU外围数*8,而在虚构线程池中,不须要指定线程数,JDK会智能依据资源占用状况,创立适合数量的线程,谋求更高的吞吐量。
据说springboot3.2.0版本开局,springboot要把webflux底层也改成虚构线程了,不知道以后会不会把一切的关于异步线程的中央都改成虚构线程。
了不起是感觉这个虚构线程真是个好物品,繁难易用,再也不纠结怎样性能线程池,依据配件自顺应,效率极高。
如今Spring6是基于JDK17的,不知道下个大版本更新会不会间接更新到有虚构线程稳固颁布的JDK版本,等候!
以下哪些属于华为jdk优化
以下属于华为jdk优化:1、JIT编译优化,GC内存回收管理优化提升内存管理性能。 2、JVM循环、向量化、序列化技术,提升程序执行性能。 毕升JDK是华为内部OpenJDK定制版HuaweiJDK的开源版本,是一个高性能、可用于生产环境的OpenJDK发行版。
服务化架构的分布式事务问题用什么方法解决?
1. 性能和时延问题在服务化之前,业务通常都是本地API调用,本地方法调用性能损耗较小。 服务化之后,服务提供者和消费者之间采用远程网络通信,增加了额外的性能损耗:1) 客户端需要对消息进行序列化,主要占用CPU计算资源。 2) 序列化时需要创建二进制数组,耗费JVM堆内存或者堆外内存。 3) 客户端需要将序列化之后的二进制数组发送给服务端,占用网络带宽资源。 4) 服务端读取到码流之后,需要将请求数据报反序列化成请求对象,占用CPU计算资源。 5) 服务端通过反射的方式调用服务提供者实现类,反射本身对性能影响就比较大。 6) 服务端将响应结果序列化,占用CPU计算资源。 7) 服务端将应答码流发送给客户端,占用网络带宽资源。 8) 客户端读取应答码流,反序列化成响应消息,占用CPU资源。 通过分析我们发现,一个简单的本地方法调用,切换成远程服务调用之后,额外增加了很多处理流程,不仅占用大量的系统资源,同时增加了时延。 一些复杂的应用会拆分成多个服务,形成服务调用链,如果服务化框架的性能比较差、服务调用时延也比较大,业务服务化之后的性能和时延将无法满足业务的性能需求。 1.1 RPC框架高性能设计影响RPC框架性能的主要因素有三个。 1) I/O调度模型:同步阻塞I/O(BIO)还是非阻塞I/O(NIO)。 2) 序列化框架的选择:文本协议、二进制协议或压缩二进制协议。 3) 线程调度模型:串行调度还是并行调度,锁竞争还是无锁化算法。 1. I/O调度模型在I/O编程过程中,当需要同时处理多个客户端接入请求时,可以利用多线程或者I/O多路复用技术进行处理。 I/O多路复用技术通过把多个I/O的阻塞复用到同一个select的阻塞上,从而使得系统在单线程的情况下可以同时处理多个客户端请求。 与传统的多线程/多进程模型比,I/O多路复用的最大优势是系统开销小,系统不需要创建新的额外进程或者线程,也不需要维护这些进程和线程的运行,降低了系统的维护工作量,节省了系统资源。 JDK1.5_update10版本使用epoll替代了传统的select/poll,极大地提升了NIO通信的性能,它的工作原理如图1-1所示。 图1-1 非阻塞I/O工作原理Netty是一个开源的高性能NIO通信框架:它的I/O线程NioEventLoop由于聚合了多路复用器Selector,可以同时并发处理成百上千个客户端Channel。 由于读写操作都是非阻塞的,这就可以充分提升I/O线程的运行效率,避免由于频繁I/O阻塞导致的线程挂起。 另外,由于Netty采用了异步通信模式,一个I/O线程可以并发处理N个客户端连接和读写操作,这从根本上解决了传统同步阻塞I/O一连接一线程模型,架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。 Netty被精心设计,提供了很多独特的性能提升特性,使它做到了在各种NIO框架中性能排名第一,它的性能优化措施总结如下。 1) 零拷贝:(1)Netty的接收和发送ByteBuffer采用DIRECT BUFFERS,使用堆外直接内存进行Socket读写,不需要进行字节缓冲区的二次拷贝。 如果使用传统的堆内存(HEAP BUFFERS)进行Socket读写,JVM会将堆内存Buffer拷贝一份到直接内存中,然后才写入Socket中。 相比于堆外直接内存,消息在发送过程中多了一次缓冲区的内存拷贝。 (2)Netty提供了组合Buffer对象,可以聚合多个ByteBuffer对象,用户可以像操作一个Buffer那样方便地对组合Buffer进行操作,避免了传统通过内存拷贝的方式将几个小Buffer合并成一个大的Buffer。 (3)Netty的文件传输采用了transferTo方法,它可以直接将文件缓冲区的数据发送到目标Channel,避免了传统通过循环write方式导致的内存拷贝问题。 2) 内存池:随着JVM虚拟机和JIT即时编译技术的发展,对象的分配和回收是个非常轻量级的工作。 但是对于缓冲区Buffer,情况却稍有不同,特别是对于堆外直接内存的分配和回收,是一件耗时的操作。 为了尽量重用缓冲区,Netty提供了基于内存池的缓冲区重用机制。 性能测试表明,采用内存池的ByteBuf相比于朝生夕灭的ByteBuf,性能高23倍左右(性能数据与使用场景强相关)。 3) 无锁化的串行设计:在大多数场景下,并行多线程处理可以提升系统的并发性能。 但是,如果对于共享资源的并发访问处理不当,会带来严重的锁竞争,这最终会导致性能的下降。 为了尽可能地避免锁竞争带来的性能损耗,可以通过串行化设计,即消息的处理尽可能在同一个线程内完成,期间不进行线程切换,这样就避免了多线程竞争和同步锁。 为了尽可能提升性能,Netty采用了串行无锁化设计,在I/O线程内部进行串行操作,避免多线程竞争导致的性能下降。 表面上看,串行化设计似乎CPU利用率不高,并发程度不够。 但是,通过调整NIO线程池的线程参数,可以同时启动多个串行化的线程并行运行,这种局部无锁化的串行线程设计相比一个队列-多个工作线程模型性能更优。 4) 高效的并发编程:volatile的大量、正确使用;CAS和原子类的广泛使用;线程安全容器的使用;通过读写锁提升并发性能。 2. 高性能序列化框架影响序列化性能的关键因素总结如下。 1) 序列化后的码流大小(网络带宽的占用)。 2) 序列化&反序列化的性能(CPU资源占用)。 3) 是否支持跨语言(异构系统的对接和开发语言切换)。 4) 并发调用的性能表现:稳定性、线性增长、偶现的时延毛刺等。 相比于JSON等文本协议,二进制序列化框架性能更优异,以Java原生序列化和Protobuf二进制序列化为例进行性能测试对比,结果如图1-2所示。 图1-2 序列化性能测试对比数据在序列化框架的技术选型中,如无特殊要求,尽量选择性能更优的二进制序列化框架,码流是否压缩,则需要根据通信内容做灵活选择,对于图片、音频、有大量重复内容的文本文件(例如小说)可以采用码流压缩,常用的压缩算法包括GZip、Zig-Zag等。 3. 高性能的Reactor线程模型该模型的特点总结如下。 1) 有专门一个NIO线程:Acceptor线程用于监听服务端,接收客户端的TCP连接请求。 2) 网络I/O操作:读、写等由一个NIO线程池负责,线程池可以采用标准的JDK线程池实现,它包含一个任务队列和N个可用的线程,由这些NIO线程负责消息的读取、解码、编码和发送。 3) 1个NIO线程可以同时处理N条链路,但是1个链路只对应1个NIO线程,防止产生并发操作。 由于Reactor模式使用的是异步非阻塞I/O,所有的I/O操作都不会导致阻塞,理论上一个线程可以独立处理所有I/O相关的操作,因此在绝大多数场景下,Reactor多线程模型都可以完全满足业务性能需求。 Reactor线程调度模型的工作原理示意如图1-3所示。 图1-3 高性能的Reactor线程调度模型1.2 业务最佳实践要保证高性能,单依靠分布式服务框架是不够的,还需要应用的配合,应用服务化高性能实践总结如下:1) 能异步的尽可能使用异步或者并行服务调用,提升服务的吞吐量,有效降低服务调用时延。 2) 无论是NIO通信框架的线程池还是后端业务线程池,线程参数的配置必须合理。 如果采用JDK默认的线程池,最大线程数建议不超过20个。 因为JDK的线程池默认采用N个线程争用1个同步阻塞队列方式,当线程数过大时,会导致激烈的锁竞争,此时性能不仅不会提升,反而会下降。 3) 尽量减小要传输的码流大小,提升性能。 本地调用时,由于在同一块堆内存中访问,参数大小对性能没有任何影响。 跨进程通信时,往往传递的是个复杂对象,如果明确对方只使用其中的某几个字段或者某个对象引用,则不要把整个复杂对象都传递过去。 举例,对象A持有8个基本类型的字段,2个复杂对象B和C。 如果明确服务提供者只需要用到A聚合的C对象,则请求参数应该是C,而不是整个对象A。 4) 设置合适的客户端超时时间,防止业务高峰期因为服务端响应慢导致业务线程等应答时被阻塞,进而引起后续其他服务的消息在队列中排队,造成故障扩散。 5) 对于重要的服务,可以单独部署到独立的服务线程池中,与其他非核心服务做隔离,保障核心服务的高效运行。 6) 利用Docker等轻量级OS容器部署服务,对服务做物理资源层隔离,避免虚拟化之后导致的超过20%的性能损耗。 7) 设置合理的服务调度优先级,并根据线上性能监控数据做实时调整。 2. 事务一致性问题服务化之前,业务采用本地事务,多个本地SQL调用可以用一个大的事务块封装起来,如果某一个数据库操作发生异常,就可以将之前的SQL操作进行回滚,只有所有SQL操作全部成功,才最终提交,这就保证了事务强一致性,如图2-1所示。 服务化之后,三个数据库操作可能被拆分到独立的三个数据库访问服务中,此时原来的本地SQL调用演变成了远程服务调用,事务一致性无法得到保证,如图2-2所示。 图2-2 服务化之后引入分布式事务问题假如服务A和服务B调用成功,则A和B的SQL将会被提交,最后执行服务C,它的SQL操作失败,对于应用1消费者而言,服务A和服务B的相关SQL操作已经提交,服务C发生了回滚,这就导致事务不一致。 从图2-2可以得知,服务化之后事务不一致主要是由服务分布式部署导致的,因此也被称为分布式事务问题。 2.1 分布式事务设计方案通常,分布式事务基于两阶段提交实现,它的工作原理示意图如图2-3所示。 图2-3 两阶段提交原理图阶段1:全局事务管理器向所有事务参与者发送准备请求;事务参与者向全局事务管理器回复自己是否准备就绪。 阶段2:全局事务管理器接收到所有事务参与者的回复之后做判断,如果所有事务参与者都可以提交,则向所有事务提交者发送提交申请,否则进行回滚。 事务参与者根据全局事务管理器的指令进行提交或者回滚操作。 分布式事务回滚原理图如图2-4所示。 图2-4 分布式事务回滚原理图两阶段提交采用的是悲观锁策略,由于各个事务参与者需要等待响应最慢的参与者,因此性能比较差。 第一个问题是协议本身的成本:整个协议过程是需要加锁的,比如锁住数据库的某条记录,且需要持久化大量事务状态相关的操作日志。 更为麻烦的是,两阶段锁在出现故障时表现出来的脆弱性,比如两阶段锁的致命缺陷:当协调者出现故障,整个事务需要等到协调者恢复后才能继续执行,如果协调者出现类似磁盘故障等错误,该事务将被永久遗弃。 对于分布式服务框架而言,从功能特性上需要支持分布式事务。 在实际业务使用过程中,如果能够通过最终一致性解决问题,则不需要做强一致性;如果能够避免分布式事务,则尽量在业务层避免使用分布式事务。 2.2 分布式事务优化既然分布式事务有诸多缺点,那么为什么我们还在使用呢?有没有更好的解决方案来改进或者替换呢?如果我们只是针对分布式事务去优化的话,发现其实能改进的空间很小,毕竟瓶颈在分布式事务模型本身。 那我们回到问题的根源:为什么我们需要分布式事务?因为我们需要各个资源数据保持一致性,但是对于分布式事务提供的强一致性,所有业务场景真的都需要吗?大多数业务场景都能容忍短暂的不一致,不同的业务对不一致的容忍时间不同。 像银行转账业务,中间有几分钟的不一致时间,用户通常都是可以理解和容忍的。 在大多数的业务场景中,我们可以使用最终一致性替代传统的强一致性,尽量避免使用分布式事务。 在实践中常用的最终一致性方案就是使用带有事务功能的MQ做中间人角色,它的工作原理如下:在做本地事务之前,先向MQ发送一个prepare消息,然后执行本地事务,本地事务提交成功的话,向MQ发送一个commit消息,否则发送一个rollback消息,取消之前的消息。 MQ只会在收到commit确认才会将消息投递出去,所以这样的形式可以保证在一切正常的情况下,本地事务和MQ可以达到一致性。 但是分布式调用存在很多异常场景,诸如网络超时、VM宕机等。 假如系统执行了local_tx()成功之后,还没来得及将commit消息发送给MQ,或者说发送出去由于网络超时等原因,MQ没有收到commit,发生了commit消息丢失,那么MQ就不会把prepare消息投递出去。 MQ会根据策略去尝试询问(回调)发消息的系统(checkCommit)进行检查该消息是否应该投递出去或者丢弃,得到系统的确认之后,MQ会做投递还是丢弃,这样就完全保证了MQ和发消息的系统的一致性,从而保证了接收消息系统的一致性。 3. 研发团队协作问题服务化之后,特别是采用微服务架构以后。 研发团队会被拆分成多个服务化小组,例如AWS的Two Pizza Team,每个团队由2~3名研发负责服务的开发、测试、部署上线、运维和运营等。 随着服务数的膨胀,研发团队的增多,跨团队的协同配合将会成为一个制约研发效率提升的因素。 3.1 共用服务注册中心为了方便开发测试,经常会在线下共用一个所有服务共享的服务注册中心,这时,一个正在开发中的服务发布到服务注册中心,可能会导致一些消费者不可用。 解决方案:可以让服务提供者开发方,只订阅服务(开发的服务可能依赖其他服务),而不注册正在开发的服务,通过直连测试正在开发的服务。 它的工作原理如图3-1所示。 图3-1 只订阅,不发布3.2 直连提供者在开发和测试环境下,如果公共的服务注册中心没有搭建,消费者将无法获取服务提供者的地址列表,只能做本地单元测试或使用模拟桩测试。 还有一种场景就是在实际测试中,服务提供者往往多实例部署,如果服务提供者存在Bug,就需要做远程断点调试,这会带来两个问题:1) 服务提供者多实例部署,远程调试地址无法确定,调试效率低下。 2) 多个消费者可能共用一套测试联调环境,断点调试过程中可能被其他消费者意外打断。 解决策略:绕过注册中心,只测试指定服务提供者,这时候可能需要点对点直连,点对点直联方式将以服务接口为单位,忽略注册中心的提供者列表。 3.3 多团队进度协同假如前端Web门户依赖后台A、B、C和D 4个服务,分别由4个不同的研发团队负责,门户要求新特性2周内上线。 A和B内部需求优先级排序将门户的优先级排的比较高,可以满足交付时间点。 但是C和D服务所在团队由于同时需要开发其他优先级更高的服务,因此把优先级排的相对较低,无法满足2周交付。 在C和D提供版本之前,门户只能先通过打测试桩的方式完成Mock测试,但是由于并没有真实的测试过C和D服务,因此需求无法按期交付。 应用依赖的服务越多,特性交付效率就越低下,交付的速度取决于依赖的最迟交付的那个服务。 假如Web门户依赖后台的100个服务,只要1个核心服务没有按期交付,则整个进度就会延迟。 解决方案:调用链可以将应用、服务和中间件之间的依赖关系串接并展示出来,基于调用链首入口的交付日期作为输入,利用依赖管理工具,可以自动计算出调用链上各个服务的最迟交付时间点。 通过调用链分析和标准化的依赖计算工具,可以避免人为需求排序失误导致的需求延期。 3.4 服务降级和Mock测试在实际项目开发中,由于小组之间、个人开发者之间开发节奏不一致,经常会出现消费者等待依赖的服务提供者提供联调版本的情况,相互等待会降低项目的研发进度。 解决方案:服务提供者首先将接口定下来并提供给消费者,消费者可以将服务降级同Mock测试结合起来,在Mock测试代码中实现容错降级的业务逻辑(业务放通),这样既完成了Mock测试,又实现了服务降级的业务逻辑开发,一举两得。 3.5 协同调试问题在实际项目开发过程中,各研发团队进度不一致很正常。 如果消费者坐等服务提供者按时提供版本,往往会造成人力资源浪费,影响项目进度。 解决方案:分布式服务框架提供Mock桩管理框架,当周边服务提供者尚未完成开发时,将路由切换到模拟测试模式,自动调用Mock桩;业务集成测试和上线时,则要能够自动切换到真实的服务提供者上,可以结合服务降级功能实现。 3.6 接口前向兼容性由于线上的Bug修复、内部重构和需求变更,服务提供者会经常修改内部实现,包括但不限于:接口参数变化、参数字段变化、业务逻辑变化和数据表结构变化。 在实际项目中经常会发生服务提供者修改了接口或者数据结构,但是并没有及时知会到所有消费者,导致服务调用失败。 解决方案:1) 制定并严格执行《服务前向兼容性规范》,避免发生不兼容修改或者私自修改不通知周边的情况。 2) 接口兼容性技术保障:例如Thrift的IDL,支持新增、修改和删除字段,字段定义位置无关性,码流支持乱序等。 4. 总结服务化之后,无论是服务化框架,还是业务服务,都面临诸多挑战,本章摘取了其中一些比较重要的问题,并给出解决方案和最佳实践。 对于本章节没有列出的问题,则需要服务框架开发者和使用者在实践中探索,找出一条适合自己产品的服务化最佳实践。
免责声明:本文转载或采集自网络,版权归原作者所有。本网站刊发此文旨在传递更多信息,并不代表本网赞同其观点和对其真实性负责。如涉及版权、内容等问题,请联系本网,我们将在第一时间删除。同时,本网站不对所刊发内容的准确性、真实性、完整性、及时性、原创性等进行保证,请读者仅作参考,并请自行核实相关内容。对于因使用或依赖本文内容所产生的任何直接或间接损失,本网站不承担任何责任。