Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >一篇文章告诉你真实场景下服务端接口性能问题是如何解决的

一篇文章告诉你真实场景下服务端接口性能问题是如何解决的

作者头像
@派大星
发布于 2024-04-15 05:53:59
发布于 2024-04-15 05:53:59
16600
代码可运行
举报
文章被收录于专栏:码上遇见你码上遇见你
运行总次数:0
代码可运行

作为Java后端开发者,我们创作的许多代码直接影响着用户的使用体验。如果后端代码性能不佳,用户在访问网站时就必须花费更多时间等待服务器响应。这可能引发用户投诉甚至用户流失问题。

性能优化是一个广泛而重要的话题。《Java程序性能优化》提到性能优化可分为五个层次:设计优化、代码优化、JVM优化、数据库优化、操作系统优化等。每个层次都涵盖许多方法论和最佳实践。本文无意进行全面详尽的概述,只是列举几个常用的Java代码优化方案,希望读者阅读后能实际应用到自己的代码中。

单例层面

在处理IO操作、数据库连接、配置文件解析加载等耗费大量系统资源的任务时,我们必须限制这些实例的创建,或者始终使用一个共享的实例,以节约系统资源。这种情况下就需要使用单例模式。

批量操作

若有100个请求,逐个执行显然效率较低。将这100个请求合并为一个请求进行批量操作,则能大幅提升效率。

特别是在数据库操作中,批量处理不仅比逐条执行效率更高,还能有效降低数据库连接数,提升应用的QPS上限。

Future模式处理

假设某项任务需花费一定时间执行,为避免无谓的等待,可先获取一个“提货单”——即Future,随后继续处理其他任务,直至“货物”抵达,即任务执行完成并获得结果。这时便可凭借“提货单”提取物品,即通过Future对象获取返回值。

伪代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class RealData implements Callable<String> {
    protected String data;

    public RealData(String data) {
        this.data = data;
    }

    @Override
    public String call() throws Exception {
        // 通过sleep方法演示业务是缓慢的
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return data;
    }
}

public class Application {
    public static void main(String[] args) throws Exception {
        FutureTask<String> futureTask =
                new FutureTask<>(new RealData("name"));
        ExecutorService executor =
                Executors.newFixedThreadPool(1); // 使用线程池
        // 执行FutureTask,相当于上例中的client.request("name")发送请求
        executor.submit(futureTask);
        // 这里可以用一个sleep代替对其他业务逻辑的处理
        // 在处理这些业务逻辑的同时,RealData也在创建,充分利用等待时间
        Thread.sleep(2000);
        // 使用真实数据
        // 如果call()没有执行完成,仍会等待
        System.out.println("数据=" + futureTask.get());
    }
}

线程池思路

合理运用线程池带来三大益处。首先,降低资源消耗:通过重复利用已创建的线程,降低线程的创建与销毁成本。其次,提高响应速度:任务到达时,无需等待线程创建即可立即执行。第三,提升线程可管理性:线程是珍贵资源,无节制地创建会消耗系统资源,降低系统稳定性;线程池能实现统一分配、优化和监控。

自 Java 5 开始,引入了并发编程新API,如Executor框架,内部采用线程池机制,位于java.util.concurrent包中。通过该框架控制线程的启动、执行和关闭,可简化并发编程操作。

伪代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制

public class MultiThreadTest {
    public static void main(String[] args) {
        ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("thread-%d").build();
        ExecutorService executor = new ThreadPoolExecutor(2, 5, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory);
        executor.execute(new Runnable() {
            @Override
            public void run() {
               System.out.println("Hello, world!");
            }
        });
        System.out.println(" ===> Main Thread! ");
    }
}

NIO处理

JDK自1.4版本起引入了新的I/O编程类库,即NIO。NIO不仅带来了高效的Buffer和Channel,还引入了基于Selector的非阻塞I/O机制,可以将多个异步I/O操作集中到一个或少数几个线程中进行处理。使用NIO替代阻塞I/O能够提高程序的并发吞吐能力,降低系统开销。

针对每个请求,如果为其单独开启一个线程来处理逻辑,当客户端数据传输是间歇性的而非连续的时,相应线程会处于I/O等待状态,并频繁进行上下文切换。利用NIO引入的Selector机制,可以提升程序的并发效率,改善这种状况。

伪代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class NioTest {  
    static public void main( String args[] ) throws Exception {  
        FileInputStream fin = new FileInputStream("D:\\test.txt");  
        // 获取通道  
        FileChannel fc = fin.getChannel();  
        // 创建缓冲区  
        ByteBuffer buffer = ByteBuffer.allocate(1024);  
        // 读取数据到缓冲区  
        fc.read(buffer);  
        buffer.flip();  
        while (buffer.remaining()>0) {  
            byte b = buffer.get();  
            System.out.print(((char)b));  
        }  
        fin.close();  
    }  
}

优化锁层面

在并发场景中,频繁使用锁是很常见的情况。然而,锁引发竞争,而竞争又会耗费大量资源。那么,在Java代码中,如何优化锁呢?我们可以考虑以下几个方面:

  • 缩短锁持有时间
    • 尝试使用同步代码块替代同步方法,从而减少锁的占用时间。
  • 降低锁粒度
    • 在并发环境中使用Map时,最好选用ConcurrentHashMap替代HashTable和HashMap(ConcurrentHashMap采用分段锁,锁的粒度更细)。
  • 分离锁
    • 普通锁(例如synchronized)可能导致读写互相阻塞,可以尝试将读操作和写操作分开。
  • 锁粗化
    • 有时我们希望将多次锁的请求合并成一个,以减少频繁加锁、同步和解锁所带来的性能损失。
  • 锁消除
    • 锁消除是指Java虚拟机在JIT编译时,经过运行上下文的扫描,去除那些不会产生共享资源竞争的锁。通过锁消除,可以减少无谓的锁请求时间。

数据传输压缩

在数据传输之前,压缩数据是一种优化方式,可以减少网络传输的数据量,提升传输速度。接收端可解压数据,还原传输内容。压缩后的数据能节省存储介质(如磁盘或内存)空间和网络带宽,从而降低成本。然而,压缩并非无成本之举。数据压缩需要大量CPU计算,并且根据压缩算法的不同,计算复杂度和压缩比都有显著差异。通常需要根据业务情景选择合适的压缩算法。

缓存计算结果

对于相同的用户请求,若每次都重复查询数据库、重复计算,将浪费大量时间和资源。将计算结果缓存至本地内存或使用分布式缓存,可节约宝贵的CPU计算资源,减少数据库重复查询或磁盘I/O。将原本需要磁头物理转动的操作转化为内存中的电子运动,提高响应速度。同时,快速释放线程也提升了应用的吞吐能力。

SQL优化

具体可参考文章:

日活3kw下,如何应对实际业务场景中SQL过慢的优化挑战?

好了,本章节到此告一段落。希望对你有所帮助,祝学习顺利。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-04-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码上遇见你 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Java性能优化系列集锦
Java性能问题一直困扰着广大程序员,由于平台复杂性,要定位问题,找出其根源确实很难。随着10多年Java平台的改进以及新出现的多核多处理器,Java软件的性能和扩展性已经今非昔比了。现代JVM持续演进,内建了更为成熟的优化技术、运行时技术和垃圾收集器。与此同时,底层的硬件平台和操作系统也在演化。
技术zhai
2019/02/15
7290
深入了解多线程的解决方案
在Java开发中,多线程编程是提高系统并发性能和资源利用率的重要手段。然而,多线程编程也带来了诸如线程安全、死锁、接口阻塞等一系列复杂问题。本文将深度剖析多线程优化技巧、Future与FutureTask的源码、接口阻塞问题及解决方案,并通过具体业务场景和Java代码示例进行实战演示。
小马哥学JAVA
2024/11/05
2160
想进大厂?50个多线程面试题,你会多少?(一)
最近看到网上流传着,各种面试经验及面试题,往往都是一大堆技术题目贴上去,而没有答案。
程序员鹏磊
2018/03/18
3K5
想进大厂?50个多线程面试题,你会多少?(一)
14.Java中的Future模式
jdk1.7.0_79   本文实际上是对上文《13.ThreadPoolExecutor线程池之submit方法》的一个延续或者一个补充。在上文中提到的submit方法里出现了FutureTask,这不得不停止脚步将方向转向Java的Future模式。   Future是并发编程中的一种设计模式,对于多线程来说,线程A需要等待线程B的结果,它没必要一直等待B,可以先拿到一个未来的Future,等B有了结果后再取真实的结果。 ExecutorService executor = Executors.new
用户1148394
2018/01/12
1.8K0
14.Java中的Future模式
多线程不得不聊的Future类
在高性能编程中,并发编程已经成为了极为重要的一部分。在单核CPU性能已经趋于极限时,我们只能通过多核来进一步提升系统的性能,因此就催生了并发编程。
敖丙
2021/04/16
9780
多线程不得不聊的Future类
Java网络编程和NIO详解5:Java 非阻塞 IO 和异步 IO
本系列文章将整理到我在GitHub上的《Java面试指南》仓库,更多精彩内容请到我的仓库里查看
Java技术江湖
2019/11/21
5380
并发模式(一)Future模式 顶
常用的并发设计模式有Future模式、Master-Worker模式、Guarded Suspension模式、不变模式、生产者-消费者模式,在多线程环境中,合理使用模式,可以提高程序性能,优化程序设计。
用户2146693
2019/08/08
3680
并发模式(一)Future模式 
                                                                            顶
Java 非阻塞 IO 和异步 IO
上一篇文章介绍了 Java NIO 中 Buffer、Channel 和 Selector 的基本操作,主要是一些接口操作,比较简单。 本文将介绍非阻塞 IO 和异步 IO,也就是大家耳熟能详的 NIO 和 AIO。很多初学者可能分不清楚异步和非阻塞的区别,只是在各种场合能听到异步非阻塞这个词。 本文会先介绍并演示阻塞模式,然后引入非阻塞模式来对阻塞模式进行优化,最后再介绍 JDK7 引入的异步 IO,由于网上关于异步 IO 的介绍相对较少,所以这部分内容我会介绍得具体一些。 希望看完本文,读者可以对非阻塞
用户1257393
2018/03/21
1.3K0
【原创】Java并发编程系列33 | 深入理解线程池(上)
并发编程必不可少的线程池,接下来分两篇文章介绍线程池,本文是第一篇。线程池将介绍如下内容:
java进阶架构师
2020/08/28
4340
【原创】Java并发编程系列33 | 深入理解线程池(上)
浅谈 Java 并发编程中的若干核心技术
作者:一字马胡 原文:http://www.jianshu.com/p/5f499f8212e7 索引 Java线程 线程模型 Java线程池 Future(各种Future) Fork/Join框架 volatile CAS(原子操作) AQS(并发同步框架) synchronized(同步锁) 并发队列(阻塞队列) 本文仅分析java并发编程中的若干核心问题,对于上面没有提到但是又和java并发编程有密切关系的技术将会不断添加进来完善文章,本文将长期更新,不断迭代。本文试图从一个更高的视觉来总结Jav
前端教程
2018/03/05
9680
浅谈 Java 并发编程中的若干核心技术
Juc并发编程12——2万字深入源码:线程池这篇真的讲解的透透的了
本文将介绍常见的线程池的使用方法,介绍线程池的参数、拒绝策略、返回参数获取以及定时调度。
半旧518
2022/10/26
1640
Juc并发编程12——2万字深入源码:线程池这篇真的讲解的透透的了
面试题-关于Java线程池一篇文章就够了
线程池是一种多线程处理形式,处理过程中将任务提交到线程池,任务的执行交由线程池来管理。
程序新视界
2019/12/20
1.9K0
面试题-关于Java线程池一篇文章就够了
一篇文章,读懂Netty的高性能架构之道
Netty是一个高性能、异步事件驱动的NIO框架,它提供了对TCP、UDP和文件传输的支持,作为一个异步NIO框架,Netty的所有IO操作都是异步非阻塞的,通过Future-Listener机制,用户可以方便的主动获取或者通过通知机制获得IO操作结果。
Spark学习技巧
2021/03/05
8720
一篇文章,读懂Netty的高性能架构之道
【第十四篇】商城系统-异步处理利器-CompletableFuture
  上面的三种获取线程的方法是直接获取,没有对线程做相关的管理,这时可以通过线程池来更加高效的管理线程对象。
用户4919348
2022/10/04
3780
【第十四篇】商城系统-异步处理利器-CompletableFuture
线程&线程池&死锁问题
思考:Thread类的构造只能接受Runnable接口,并不能接口Callable接口,怎么办? 解决:找中间人。如果有一个中间人同时实现了Runnable和Callable,那不就行了嘛。这就是适配器模式。这个中间人就是FutureTask实现类。
贪挽懒月
2019/05/21
1.2K0
线程&线程池&死锁问题
java高并发系列 - 第19天:JUC中的Executor框架详解1
Executors框架是Doug Lea的神作,通过这个框架,可以很容易的使用线程池高效地处理并行任务。
路人甲Java
2019/12/10
8470
Future:异步任务结果获取
我们之前说过如何正确创建线程池,我们详细介绍了怎么合理使用线程池,我们也只是介绍了 ThreadPoolExecutor的void execute(Runnable command)方法,利用这个我们可以提交任务,让线程去消费处理,但是没有办法获取任务的执行结果。因为该方法没有返回值。而有一些场景我们需要获取任务的执行结果再判断逻辑。
码哥字节
2020/03/24
2.3K0
Future:异步任务结果获取
新手也能看懂的线程池学习总结
线程池提供了一种限制和管理资源(包括执行一个任务)。每个线程池还维护一些基本统计信息,例如已完成任务的数量。
范蠡
2019/11/21
4140
相关推荐
Java性能优化系列集锦
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验