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

开发 前端
对于相同的用户请求,若每次都重复查询数据库、重复计算,将浪费大量时间和资源。将计算结果缓存至本地内存或使用分布式缓存,可节约宝贵的CPU计算资源,减少数据库重复查询或磁盘I/O。

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

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

单例层面

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

批量操作

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

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

Future模式处理

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

伪代码

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包中。通过该框架控制线程的启动、执行和关闭,可简化并发编程操作。

伪代码

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机制,可以提升程序的并发效率,改善这种状况。

伪代码

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。将原本需要磁头物理转动的操作转化为内存中的电子运动,提高响应速度。同时,快速释放线程也提升了应用的吞吐能力。

责任编辑:武晓燕 来源: 码上遇见你
相关推荐

2021-11-04 10:34:02

JavaScript继承编程

2022-02-18 00:13:53

JavaScript编程语言数组

2019-07-28 20:15:07

2017-05-23 09:20:32

大数据数据分析多层模型

2021-01-20 05:35:08

PythonMock微服务

2021-09-27 09:18:30

ListIterato接口方法

2019-10-17 19:15:22

jQueryJavaScript前端

2023-11-01 15:52:35

2022-08-04 09:39:39

Kubernetes声明式系统

2019-07-01 15:01:44

NVMe接口存储

2024-07-08 12:40:18

MySQL索引失效

2020-12-23 13:28:18

云计算云计算技术后端技术

2021-06-29 10:00:02

JavaScriptStorage接口

2021-08-02 10:01:09

Iterator接口Java项目开发

2024-05-23 15:32:33

StorageJavaScript

2021-08-06 07:51:48

JavaScriptStorage接口

2024-10-08 15:51:09

JavaScriptStorage 接口

2024-08-06 14:44:48

JavaScriptStorage 接口

2021-05-29 10:20:54

GoModules语言

2021-09-15 10:00:33

Go语言Modules
点赞
收藏

51CTO技术栈公众号