本文旨在深入探讨性能优化的重要性,并提供一套全面的性能优化方案。我们将从硬件、软件和网络三个方面进行分析,以帮助您提高系统的整体性能。通过遵循这些建议,您将能够显著提高系统的响应速度、降低延迟,以及提升用户体验。
1、分而冶之(“分”字诀)
业务分层、系统分级、服务分布、数据库分库/表、动静分离、同步拆分成异步、单线程分解成多线程、原数据缓存分离、单表分多表、单库分多库、分流等等。。。。 在系统性能优化中,"分而治之"是一种常用的策略,通过将问题分解为更小、更可管理的子问题,然后分别解决每个子问题,最终得到整体的优化效果。以下是一些常见的"分而治之"技术和方法:
2、合而为一(“合”字诀)
微服务粒度不可太细该合则合、数据库大表减少联合查询该冗余则合并冗余数据。直观的表述就是:从前端用的CDN、动静分离,到后台服务拆分成微服务、分布式、负载均衡、缓存、池化、多线程、IO、分库表、搜索引擎等等。都是强调一个“分”字。 在系统性能优化中,"合而为一"是一种策略,强调整体性能的最大化,而不仅仅是局部问题的解决。以下是一些常见的"合而为一"技术和方法:
通过采用以上的"合而为一"技术,可以在整体上提升系统性能,通过综合考虑各个组件和模块的相互关系和相互影响,达到全局性能的最大化。这种方法强调整体性能的提升,使系统在各个层面和维度上获得最佳的性能和效率。 一、引言 随着科技的发展,高性能计算已经成为了许多行业的关键需求。无论是游戏、金融、医疗还是其他领域,高性能计算都扮演着举足轻重的角色。然而,实现高性能并非易事。为了满足用户对速度和效率的需求,我们需要不断地进行性能优化。在这篇文章中,我们将为您介绍一套全面的性能优化方案,帮助您提高系统的整体性能。 1、性能优化的定义 性能优化是指对计算机硬件、操作系统和应用程序有相当深入的了解,调节三者之间的关系,实现整个系统(包括硬件、操作系统、应用)的性能最大化,并能不断的满足现有的业务需求。通过对系统进行调整、改进和优化,以提高其执行速度、响应时间、吞吐量、资源利用率或其他性能指标的过程。性能优化旨在通过减少延迟、提高吞吐量、降低资源消耗等手段,使系统能够更高效地满足用户需求,提供更好的用户体验。 性能优化可以应用于各个领域,包括软件开发、数据库管理、网络通信、算法设计、系统架构等。 性能优化是一个持续的过程,需要综合考虑系统的需求、资源限制和用户体验,通过实时监测、分析和优化来不断改进系统的性能。它涉及到细致的性能分析、实验、调整和验证,以确保系统在实际运行中能够达到最佳的性能表现。 2、性能优化的目标 性能优化的目标是提高系统、应用程序或算法的性能,以满足用户需求,并提供更好的用户体验。以下是性能优化的主要目标:
性能优化的目标是使系统在给定的资源和约束条件下,尽可能地达到最佳的性能水平。综合考虑用户需求、系统要求和资源限制,以实现性能和可用性的最佳平衡。 3、性能优化的重要性 性能优化在现代应用开发和系统设计中具有重要性,以下是性能优化的几个关键方面和其重要性:
性能优化不仅是满足用户期望的基本要求,还是保持竞争力、提高效率和节约资源的关键因素。通过投入适当的时间和资源进行性能优化,可以实现系统的卓越性能和用户体验,从而为个人用户、企业和组织带来巨大的利益。 4、性能优化的原则 性能优化需要遵循一些基本的原则:
5、性能优化的方法 1、硬件层面的性能优化
2、软件层面的性能优化
3、网络层面的性能优化
二、内存管理优化 1、了解Java内存相关基础 1.1、Java内存模型 JMM(Java Memory Model)是Java虚拟机规范中定义的一种内存模型,它描述了Java程序如何在多线程环境下访问共享内存。JMM主要是为了屏蔽各种硬件和操作系统对内存访问的差异而定义出来的内存模型。JMM定义了一个抽象的计算机内存模型,包括主内存和工作内存两部分。
1.2、运行时数据区 JVM运行时数据区是Java虚拟机在执行Java程序时所使用的内存区域。这些区域包括了以下几个部分:
以上就是Java虚拟机运行时数据区的主要组成部分。不同的区域在内存大小和使用方式上有所不同,但它们都是支撑Java程序正常执行的重要组成部分。理解Java虚拟机的运行时数据区,对于编写高效、稳定的Java程序非常重要。
1.3、Java垃圾回收机制 Java的垃圾回收(Garbage Collection,GC)机制是Java虚拟机(JVM)负责自动回收不再使用的对象所占用的内存空间的一种机制。垃圾回收机制大大减轻了开发人员手动管理内存的负担,并帮助预防内存泄漏和提高应用程序的性能。 1、回收过程
1)先判断对象是否存活(是否是垃圾) 可以通过引用计数算法和可达性分析算法来判断,由于引用计数算法无法解决循环引用的问题,所以目前使用的都是可达性分析算法 2)再遍历并回收对象(回收垃圾) 可以通过垃圾收集器(Serial/Parallel/CMS/G1)来回收垃圾,垃圾收集器使用的算法标记清除算法、标记整理算法、复制回收算法和分代回收算法。 2、GC种类
3、GC原理
2、选择合适的垃圾收集器
收集器 | CMS | G1 |
回收算法 | 标记清除 | 标记整理 |
回收区域 | 老年代 | 新生代+老年代 |
内存布局 | 传统 | 将新生代、老年代切一起分成一个个Region |
内存碎片 | 产生碎片空间 | 碎片空间小 |
并发 | 并发 | 并发 |
JDK使用 | JDK8默认(Parallel) | JDK9默认 |
停顿时间 | 最短停顿时间 | 可预测停顿时间 |
Java虚拟机还提供了许多参数用于调整垃圾收集器的行为和性能。可以使用这些参数来指定特定的垃圾收集器,调整堆大小、停顿时间、吞吐量等。根据应用程序的需求和特性,可以通过这些参数来优化垃圾收集器的性能。 3、内存分析工具 1)jstat:用于监控JVM内存使用情况和垃圾回收信息。 2)jmap:用于生成JVM堆转储文件,以便分析内存使用情况。 3)jconsole:用于监控JVM性能指标、线程数量等信息。
4)VisualVM:一个功能强大的性能分析工具,可以统计CPU、内存、GC等各种指标,并提供图形化界面。
5)阿里Arthas:应用程序的性能分析、内存泄漏检测、线程问题排查、方法调用追踪等操作。
6)Apache JMeter:用于进行压力测试和性能测试。可测试出系统的性能拐点。
7)Eclipse MAT:Mat是Eclipse的一个插件, 也可以独立运行, 所以即使你使用IDEA也可以独立使用Mat。MAT主要的功能就是分析dump文件。
8)XRebel:一款轻量级的Java性能分析工具,提供实时的代码级性能分析和优化建议。 页面操作后,会在xrebel控制台看到每个http请求的耗时
把每个被调用或执行的类的方法耗时都显示出来,同时不同颜色标明耗时情况。
三、多线程并发优化 1、线程池(池化技术)
Oracle 官方并没有给出线程池 corePoolSize 的具体参考值,因为这个值的大小应该根据实际业务场景和系统资源情况来进行优化调整。不同的业务场景和系统资源状况可能需要不同的 corePoolSize 设置。 在《Java并发编程实战》一书中,作者 Brian Goetz 等人指出,线程池的规模应该根据任务类型和计算密集度来确定,对于 CPU 密集型任务,应该将核心线程数设置为处理器核心数加 1 或者 2;对于 I/O 密集型任务,可以适当增加核心线程数以利用空闲的 CPU 时间。 这个建议是基于以下考虑:对于 CPU 密集型任务,线程需要大量计算,因此需要足够多的 CPU 资源,而处理器核心数加 1 或者 2 的数量可以充分利用 CPU 资源,避免线程之间的竞争和阻塞;而对于 I/O 密集型任务,由于线程大部分时间都处于等待 I/O 操作的状态,因此可以适当增加核心线程数以利用空闲的 CPU 时间,从而提高系统效率。 虽然这个建议并非官方标准,但在实际应用中已经得到广泛的认可和应用,并取得了不错的效果。 2、锁竞争和粒度(锁优化技术) 性能优化中减少锁竞争和使用细粒度锁是常用的策略,可以有效提高并发程序的性能。以下是一些减少锁竞争和使用细粒度锁的策略: 2.1、减少锁粒度 通过分解大锁为多个小锁,减小锁的粒度,从而降低锁竞争的程度。例如,将一个大的同步代码块拆分成多个小的同步代码块,可以使并发执行的线程之间的竞争减少。 2.2、使用读写锁 对于读多写少的场景,可以使用读写锁(ReentrantReadWriteLock)来提高并发性能。读写锁允许多个线程同时获取读锁,但只有一个线程可以获取写锁,从而提供更高的并发性。 2.3、使用并发集合 Java提供了一些并发集合类,如ConcurrentHashMap、ConcurrentLinkedQueue等。这些并发集合类使用了内部的细粒度锁机制,可以在多线程环境下提供高效的并发操作。 2.4、使用CAS操作 CAS(Compare and Swap)是一种乐观锁机制,通过比较并交换的方式进行原子操作,避免了使用传统的互斥锁带来的性能开销和线程阻塞。Java中的Atomic类和AtomicReference类提供了CAS操作的支持。
具体来说,多线程CAS操作包括以下几个步骤:
在并发环境下,多线程CAS操作可以保证共享变量的原子性操作,同时也避免了传统锁机制所带来的线程阻塞和上下文切换的开销。因此,多线程CAS操作被广泛应用于各种高并发场景中,如数据库事务、分布式系统等。 2.5、读写分离锁 将数据结构中的只读操作和写操作分离,分别使用读锁和写锁。这样可以允许多个线程同时读取数据,而只有在写操作时才需要加锁,减少了读操作之间的竞争。 2.6、使用无锁算法 无锁算法(Lock-Free)是一种不使用互斥锁的并发算法。它通常基于CAS操作和其他原子操作,通过无阻塞的方式实现并发访问,避免了锁竞争带来的性能损失。 2.7、避免过度同步 合理评估同步代码块的范围,避免不必要的同步,减少锁竞争的范围,提高并发性能。 需要根据具体的应用场景和需求选择适合的策略。同时,性能优化是一个综合考虑的过程,还需综合其他因素,如资源利用、算法优化等,以获得最佳的性能提升效果。 四、代码优化 代码优化是性能优化的基础,它可以通过减少代码中的冗余和重复操作来提高程序的执行效率。以下是一些常见的代码优化技巧: 1、数据结构 选择合适的数据结构可以显著提高代码的性能。通过分析问题的特点,选择更高效的算法来解决问题,如使用快速排序替代冒泡排序等。 2、避免重复计算 将重复计算的结果存储起来,避免在程序中多次计算相同的结果。例如,可以将一个数字的平方存储在一个变量中,然后在需要的时候直接使用这个变量。 3、内存管理 合理管理内存资源可以减少内存分配和释放的开销,如避免频繁的对象创建和销毁,使用对象池、缓存等技术进行优化。 4、使用位运算 位运算比算术运算更快,因为它们可以直接操作二进制位。例如,可以使用按位与操作符(&)来检查一个整数是否为偶数。 5、减少函数调用 函数调用会产生额外的开销,因此应该尽量减少函数调用的次数。例如,可以将一些常用的计算结果存储在全局变量中,然后在需要的时候直接使用这些变量。 关于代码优化可以选择一份编码规范或形成一定的代码库。官方下载地址:《Java开发手册(黄山版).pdf》
6、流技术 性能优化之stream技术是指使用Java 8中的Stream API来对集合进行高效的遍历和操作。Stream API可以提供一种声明式的编程风格,利用Lambda表达式对集合进行各种聚合操作和批量数据操作,例如排序、过滤、映射等。Stream API还可以支持并行处理,利用多核CPU的优势,提高数据的处理速度。 Stream API的实现原理主要涉及以下几个方面:
7、Reactive技术 性能优化之reactive技术是指一种面向数据流和事件的异步编程范式,可以提高程序的响应速度和资源利用率。reactive技术可以分为reactive编程和reactive架构两个方面。 reactive编程是指使用一些库或框架,如RxJava、Reactor、Redux等,来实现数据流和事件的响应式处理,可以简化异步编程的复杂度,提高代码的可读性和可维护性。 reactive架构是指使用一些设计原则和模式,如响应式宣言、微服务、消息驱动等,来构建高可用、高伸缩、高弹性的分布式系统,可以应对不断变化的需求和负载。 五、数据库访问优化 1、MySQL 1.1、MySQL调优维度
1)SQL及索引优化:SQL查询语句的优化是提高MySQL性能的关键。优化查询语句可以采用各种方法,如使用合适的索引、避免在WHERE子句中使用函数操作符、减少子查询等。 2)表结构优化:表的设计和结构也会影响MySQL的性能。适当的表设计可以提高查询性能和数据处理速度。例如,使用分区表可以加速查询,而垂直拆分表可以降低数据库的负载。 3)系统配置优化:MySQL及服务器的参数设置对于性能也非常重要。调整配置可以让MySQL更好地利用硬件资源。例如,增加缓存区大小、调整连接超时时间或者优化排序缓存等都可以提高系统性能。 4)硬件优化:除了软件方面的优化,还可以通过硬件来优化MySQL性能。例如,使用更快的磁盘、增加内存以及升级CPU等都可以提高MySQL的负载能力。 1.2、MySQL调优分解
2、缓存技术 缓存技术是性能优化中常用的一种策略,它通过存储计算结果、数据或资源的副本,以减少对原始数据源的访问次数,从而提高数据访问的速度和性能。 2.1、常见的缓存技术:
需要根据具体的应用场景和需求选择合适的缓存技术,并综合考虑缓存的一致性、容量、更新机制等因素。同时,缓存的设计和管理也需要谨慎,避免缓存膨胀、数据一致性问题和过期缓存的影响。 2.2、缓存分类
1)本地缓存: 将缓存数据存储在单个应用程序进程内部的内存中,通常是使用Java集合类如HashMap、ConcurrentHashMap等进行实现。本地缓存的优点是速度快、易于实现,并且不需要网络传输,但无法跨越多个应用程序进程共享数据。 2)分布式缓存: 将缓存数据存储在多台服务器上,通过网络传输数据实现缓存共享。常见的分布式缓存框架有Redis、Memcached、Ehcache等。分布式缓存的优点是可以扩展性好、支持高并发、容量大,并且能够提高应用程序的可靠性和可用性。 3)多级缓存(本地+分布式): 将缓存数据同时存储在本地缓存和分布式缓存中,以加快访问速度并提高可靠性。常见的多级缓存方案包括EHCache+Redis、Guava Cache+Redis等。多级缓存的优点是兼顾了本地缓存和分布式缓存的优点,使得缓存系统更灵活、性能更强。 六、通信及IO优化 1、非阻塞IO和异步IO模型
2、NIO和多路复用技术
选择器(Selector) 选择器是Java NIO中的一个重要组件,它可以用于同时监控多个通道的读写事件,并在有事件发生时立即做出响应。选择器可以实现单线程监听多个通道的效果,从而提高系统吞吐量和运行效率。 通道(Channel) 通道是一个用于读写数据的对象,类似于Java IO中的流(Stream)。与流不同的是,通道可以进行非阻塞式的读写操作,并且可以同时进行读写操作。通道分为两种类型:FileChannel和SocketChannel,分别用于文件和网络 通信。 缓冲区(Buffer) 在Java NIO中,所有数据都是通过缓冲区对象进行传输的。缓冲区是一段连续的内存块,可以保存需要读写的数据。缓冲区对象包含了一些状态变量,例如容量(capacity)、限制(limit)、位置(position)等,用于控制数据的读写。 3、协议和数据格式
方面 | http协议 | rpc协议 |
传输层 | 基于TCP,有特定的传输格式,包含大量的头部信息,数据传输效率低 | 基于TCP或UDP,自定义数据格式,数据传输效率高 |
通用性 | 不关心实现细节,跨语言、跨平台,适合部门间或外部服务的调用 | 需要在API层面进行封装,限制了开发的语言环境,适合内部服务的调用 |
开发难度 | 相对简单,只需遵循REST规范,请求、响应等细节需要自己实现 | 相对复杂,需要考虑server选择、序列化、通信、容错等功能 |
速度 | 较慢,受到HTTP头部信息和TCP握手的影响 | 较快,数据格式简洁,通信方式可靠 |
4、Http连接池和连接复用 Http连接池是一种用于管理和复用HTTP连接的技术,可以提高HTTP请求的性能和效率。以下是几种常见的Http连接池实现技术:
以下是一些开源http连接池实现技术:
5、同步变异步 性能优化同步变异步是一种常见的编程模式,可以提高系统的响应速度和吞吐量。同步操作会导致请求一直阻塞,直到失败或者成功的返回结果。异步操作可以支持横向扩容,可以缓解瞬间的请求压力,使得请求变得平滑。 如果一个接口中需要进行多步,而这些业务操作又是各自独立的,传统的依据代码顺序同步执行又比较耗时,传统的优化的空间又比较少,这时就可以考虑使用多线程的方式优化接口,让同步变异步,接口业务操作并行处理,极大提升接口的性能。 七、性能监测 在微服务架构中,系统性能监控通常使用以下工具和技术:
这些工具和技术可以提供实时的系统性能监控、故障排查和性能优化的能力,帮助开发人员和运维团队监控和管理微服务架构中的性能和可用性。选择合适的工具和技术需要考虑具体的需求、技术栈和可扩展性要求。 八、架构优化 系统性能优化的架构优化是关键的一部分,它包括以下几个方面的技术和策略:
这些架构优化技术和策略可以根据具体的应用需求和系统瓶颈进行选择和应用。通过合理的架构设计和优化,可以提升系统的性能、可伸缩性和可用性,确保系统能够承受高负载和高并发的请求。 九、系统优化 系统优化是对整个系统的优化,它可以通过调整系统的配置和参数来提高系统的性能。以下是一些常见的系统优化技巧:
作为公司的架构师或者程序员,你是否曾经为公司的系统在面对高并发和性能瓶颈时感到手足无措或者焦头烂额呢?笔者在出道那会为此是吃尽了苦头的,不过也得感谢这段苦,让笔者从头到尾去探索,找寻解决之法。
今天就结合自己的经验,带你踏上一段神奇之旅,探索高并发与性能优化的秘密。我们将一起穿越技术的迷雾,揭示那些隐藏在代码背后的魔法,助你构建稳定可靠的系统应用!
高并发环境下,用户不耐烦的等待时间就像一道坚固的墙。为了突破这道障碍,我们可以施展异步处理的魔法。将耗时的操作转化为异步任务,让系统能同时处理更多请求,提高并发能力。还有神奇的缓存技术,通过减少对后端资源的频繁访问,加速系统的响应速度,像是给应用注入了快进的魔力。
在高并发的战场上,资源的争夺可是一场惨烈的战斗。为了保护宝贵的资源不被耗尽,我们可以借助锁机制、线程池和消息队列等技巧,有效地控制并发访问,防止资源的过度竞争和系统的崩溃。就像是聪明的指挥官,合理调度战力,稳定前线。
在信息的海洋中,数据库常常是一个令人头疼的瓶颈。但是别灰心!我们可以用缓存技术打破这个限制。将常用的数据存储在缓存中,避免频繁访问数据库,就像是给系统搭建了一个高速通道,让数据瞬间传送到用户面前。同时,设置合理的缓存策略,让缓存变得更加智能,提升系统的性能和吞吐量。
当用户涌入你的应用,你是否感到无法承受之重?别害怕!负载均衡技术就是你的救命稻草。通过将请求分发到多个服务器上,平衡系统的负载,提升系统的并发处理能力。就像是魔法师的魔法阵,将能量分散,使系统保持平衡与稳定。
在高并发的舞台上,数据的一致性常常被忽视。但是小心!一不小心,数据的错乱就会引发巨大的灾难。这时,事务机制和锁机制就是你的魔法武器。通过合理使用事务和锁,保证并发操作的数据一致性,让数据变得安全可靠。
在这段神奇的旅程中,我们一起揭开了高并发与性能优化的神秘面纱。从异步处理的加速法术到资源的守护者并发控制技巧,再到缓存奇迹和负载均衡的魔法仪式,最后以数据的魔法魅力作为压轴大结局。希望这些技巧和魔法能够帮助你构建稳定可靠的系统应用,并成为技术的英雄!记住,只要勇敢地迈出第一步,就能够超越困难,成就非凡!
成功的前端工程师很会善用工具,这些年低代码概念开始流行,像国外的 Mendix,国内的 JNPF,这种新型的开发方式,图形化的拖拉拽配置界面,并兼容了自定义的组件、代码扩展,确实在 B 端后台管理类网站建设中很大程度上的提升了效率。
开源地址:JNPF体验中心
代码量少,系统的稳定性和易调整性都会得到一定的保障。基于代码生成器,可一站式开发多端使用 Web、Android、IOS、微信小程序。代码自动生成后可以下载本地,进行二次开发,有效提高整体开发效率。同时,支持多种云环境部署、本地部署给予最大的安全保障,可以快速搭建适合自身应用场景的产品。
系统性能优化,一直是程序员从初级通向高阶的必修课,也是在面试中被重点考查的点。
下图是我基于对性能优化的理解,梳理出来的五大方向和对应的16种策略:
下面我们就来一一进行讲解。
预计算是一种“笨鸟先飞”的思想,与之相对应的是实时计算。
实时计算:当用户请求到来的时候,才进行数据的结果计算。
预计算:在用户请求到来之前,先将结果计算好,并保存在缓存或数据库中。当用户触发请求的时候,直接返回准备好的结果。
预计算的优缺点非常分明,其优点是性能足够高。缺点是,可能数据结果为非实时数据,存在时延性,也可能是技术方案比较复杂。
预计算的另外需要考虑的点是:结果命中率和计算范围,也就是说,我们希望结果命中率越高越好,而计算范围越小越好。因为计算范围越大,计算的周期就越长,对硬件存储的消耗也就越大。
预计算又可以分为全量预计算和存量预计算 + 增量即席计算两种。
全量预计算
假设如下场景,我们计算一张销售报表:
报表中的内容为这家企业季度销售情况,如果用全量预计算的方式,我们按照前图的方式,通过每十分钟跑一次定时任务,将页面上的几十个业务汇总指标全部计算一遍,然后存储到MySQL或Redis中,等待前端请求调用。
这种方式叫全量预计算,代码实现简单,可能一条复杂大SQL就能搞定,但如果数据量特别庞大,可能会出现执行时间很长,甚至跑不出来结果的情况。
存量预计算 + 增量即席计算
还是以上图的销售报表为例,假设今天为12月15日,那么前三个季度的销售额已经是不可变的存量数据了,我们只需要预计算一次,并将它存储到MySQL或Redis中即可,不用每次跑任务都进行计算。
接下来我们再说说第四个季度,其实12月14日及以前的销售额也是不可变的存量数据,我们一样可以将它存储下来以作备用。
这时,当前端的请求发送过来,我们只需要即席计算12月15日的销售额数据,然后再跟12月14日及以前的销售额数据进行累加,即可得出实时的第四季度销售额数据。
另外一个问题是,在什么时间点进行存量数据累加,比较常见的做法是,每天的0点跑定时任务,把前一天的全天销售数据累加进之前存量数据中。
这种方式叫存量预计算 + 增量即席计算,同时兼备高性能和实时性的优点,唯一的缺点就是技术方案相对复杂。
总之,预计算场景非常适合于对数据时延性要求不高的统计分析场景。
与并行计算相对应的是串行计算。
并行计算的所体现的思想是“人多力量大,众人拾柴火焰高”,旨在通过将任务拆解后,以多路并行的方式,将任务执行的总时长进行缩短,以达到提升性能的目的。
其中,并行计算又可以分为:单机多线程并行计算、集群并行计算和集群并行 + 单机多线程并行计算。
单机多线程并行计算
目前Java的JUC包中有很多非常好用的工具类,如:ThreadPoolExecutor、ForkJoinPool、newFixedThreadPool、newCachedThreadPool、CountDownLatch、CyclicBarrier、Phaser、CompletableFuture等。
我们假设给大量用户进行短信发送的场景,newFixedThreadPool示例如下:
package com.example.demo;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MessageSender{
public static void main(String[]args){
List<String> userList=List.of("张一", "张二", "张三", "张四", "张五", "张六", "张七", "张八", "张九", "张十",
"王一", "王二", "王三", "王四", "王五", "王六", "王七", "王八", "王九", "王十");
ExecutorService executorService=Executors.newFixedThreadPool(5);
for (String user : userList){
executorService.execute(new Task(user));
}
executorService.shutdown();
}
}
class Task implements Runnable{
private String user;
public Task(String user){
this.user=user;
}
public void run(){
System.out.println("调用发送短信SDK,给用户" + user + "发短信。");
}
}
用CompletableFuture电商下单的前置校验场景:
package com.example.demo;
import java.util.concurrent.*;
public class Order{
public static void main(String[]args) throws ExecutionException, InterruptedException{
CompletableFuture<String> basicCheck=CompletableFuture.supplyAsync(() ->{
try{
Thread.sleep(1000);
}catch (InterruptedException e){
throw new RuntimeException(e);
}
return "基本信息校验";
});
CompletableFuture<String> riskControlCheck=CompletableFuture.supplyAsync(() ->{
try{
Thread.sleep(2000);
}catch (InterruptedException e){
throw new RuntimeException(e);
}
return "风控信息校验";
});
CompletableFuture<String> result=CompletableFuture.allOf(basicCheck, riskControlCheck).thenApply(res ->{
return basicCheck.join() + "和" + riskControlCheck.join();
});
System.out.println("订单完成" + result.join() + ",可以正式下单了。");
}
}
除此之外,Java 1.8的stream中的并行流模式——parallelStream(),也是一种不错的选择。
我们还是假设给大量用户进行短信发送的场景:
import java.util.List;
public class MessageSender{
public static void main(String[]args){
List<String> userList=List.of("张一", "张二", "张三", "张四", "张五", "张六", "张七",
"张八", "张九", "张十", "王一", "王二", "王三", "王四", "王五", "王六", "王七", "王八", "王九", "王十");
userList.parallelStream().forEach((entry) ->{
System.out.println("调用发送短信SDK,给用户" + entry + "发短信。");
});
}
}
集群并行计算
大家可能不太清楚,单机多线程并行计算和集群并行计算各自的适用场景,我这里先来解释一下。
单机多线程并行计算场景,适用于用户触发请求的短时间执行场景,而集群并行计算场景,则适用于后台定时任务的长时间处理场景。
原因在于,在集群模式下,如果其中的一台服务器,以单机多线程模式长时间处理任务,容易出现硬件资源消耗倾斜的情况,再加上还要处理业务请求,容易使其成为集群中的性能瓶颈点。
集群并行计算模式,可以使用XXL-JOB分布式任务调度平台的分片广播模式进行实现。
其实现核心原理为,通过XXL-JOB的调度器,往执行器集群中的每个节点都发送一个请求,然后各节点通过自己不同的shardIndex来执行不同的任务。
代码如下:
@XxlJob("broadcastJob")
public void broadcastJob(){
int shardCount=10; // 分片总数
int shardIndex=XxlJobHelper.getShardIndex(); // 当前分片项
// 执行任务逻辑
for (int i=1; i <=全部任务ID; i++){
if (i % shardCount==shardIndex){
// 当前分片项需要执行的任务逻辑
System.out.println("分片 " + shardIndex + "负责执行任务" + i);
}
}
}
集群并行 + 单机多线程并行计算
这个不用过多解释,相当于前两种模式的集合,适用于将该集群作为特定的任务服务器,并将计算能力拉满的情况。
常见的大数据框架:Flink、Spark、Storm都是使用的这种方式。
总之,并行计算场景非常适合于,可将一个大任务拆解成若干个小任务,且各任务间没有先后依赖关系的场景。
本期我们介绍了常见的两种性能优化方向,预计算和并行计算,以及对应的五种策略,后面几篇再跟大家介绍异步计算、存储系统优化和其他算法优化。
最后,再给知友们来一波福利。
本人在之前看机会的时候,也从网上找遍了各式各类的八股文资料,但总觉得答案还不够准确,深度还有所欠缺,或是内容组织的逻辑性还不够清晰。
于是,我便自己动手,丰衣足食地自己总结了一套博采众家之长的八股文,那可真是字字斟酌,题题验证。
btw:该八股文除了场景题之外,还包括Java基础、Spring生态、MyBatis、MySQL、JVM、Redis、Kafka、RocketMQ、Dubbo、操作系统和网络、Netty、Doris、ClickHouse,以及一些非常高频的场景题,非常全面。
现在,我“大公无私”地把它分享出来,希望更多的同学可以由此受益。
Java技术栈的经典八股文最后,祝大家工作顺利,纵情向前,人人都能收获自己满意的offer。
我们的团队人数
我们服务过多少企业
我们服务过多少家庭
我们设计了多少方案