Java中提升接口性能的一些方法

1.使用线程池并行执行

假如有一个接口的逻辑如下:

接口的整体耗时大约在1s左右,那么如果我们使用并行处理,类似木桶效应,接口的响应时间就不再是所有模块的耗时相加,而是取决于耗时最长的模块(600ms)了。

在这里插入图片描述

2.数据库优化

我们可以通过JVM参数调整、应用节点数扩容来增加系统的吞吐量,但是用户的请求最终都会落到数据库上,如果数据库的性能不高的话,就会成为整个链路的性能瓶颈。主要有以下几种数据库的优化方案:

2.1 小表关联大表

生产环境的数据库中的数据量一般都会非常的大,联表查询是一件非常耗时、吃内存的操作,操作不当的话可能会导致服务被拖垮。

所以我们在联表查询时,一般先查询小表,然后再用小表的查询结果作为条件去筛选大表的数据。

2.2 反三大范式操作

一般不会改变的字段,可以在表当中冗余一下,这样可以减少我们的关联查询次数,提升接口响应速度。

比如常见的:用户姓名字段。

2.3 增加索引

比如我们建表时所使用的主键是默认添加索引的,对于经常关联查询的字段也需要添加索引。

当然有一些特殊的查询方式会导致索引失效,我们需要注意一下:

在这里插入图片描述

2.4 减小事务粒度

我们在数据库操作的时候,为了保证数据的一致性,经常会需要使用到数据库的事务。

其实,我们在程序中使用数据库事务的时候,稍稍注意一下也可以提升我们接口的性能。

比如这个方法上面就加了一个 @Transactional 注解来开启事务。

@Transactional
public Boolean bigTransaction() {
    Object a = queryDataFromA();
    Object b = queryDataFromB();
    handleData(a, b);
    insertDataA(a);
    updateDataB(b);
}

那么,其实这里需要保证事务的地方也就只有最后两行 insert 和 update,没有必要将整个方法都放到事务当中。

在这里插入图片描述

最直接的,我们可能会想把这两个事务抽出来,单独用一个带有 @Transactional 注解修饰的方法来执行。

@Transactional
public void handleABTransaction(Object a, Object b)
	insertDataA(a);
    updateDataB(b);
}

但是由于 @Transactional 注解底层是使用 AOP 来实现的,直接在类内部进行方法的调用,事务是不生效的。这里我们可以采用编程式事务来代替声明式事务:

import org.springframework.transaction.support.TransactionTemplate;

@Autowired
private TransactionTemplate transactionTemplate;

public Boolean bigTransaction() {
    Object a = queryDataFromA();
    Object b = queryDataFromB();
    handleData(a, b);
    
    // 编程式事务
    transactionTemplate.execute(() -> {
    	insertDataA(a);
    	updateDataB(b);
    });
}

2.5 读写分离、分库分表

随着业务的发展,数据库中的数据量会越来越多,这个时候就需要进行读写分离、分库分表的技术。尤其是现在微服务高可用的大环境下,不同业务使用不同的数据库已经成为了一种主流的设计。

具体分库分表的逻辑比较复杂,这里可以使用 ShardingJDBC 来实现。

3.拥抱缓存

3.1 Redis

Redis 相信大家都再熟悉不过了,它可以用来做缓存、分布式锁,甚至可以直接用来做数据库。

我们可以把变动不是很频繁,但是访问却非常频繁的数据放到 redis 里面,比如配置数据、热点数据等等。

3.2 内存缓存

我们常用的内存缓存有 Guava Cache,还有现在非常火的,性能非常高的 Caffeine Cache

当我们在使用一些内存缓存框架的时候,我们一定要了解的一点就是内存数据是跟随GVM进程同时存在的。所以当我们重启应用,缓存就会有一段时间的真空期,也就是我们常说的缓存击穿。所以我们需要考虑一下数据预热,或者是选择低谷的时候重启应用。

Caffeine 框架有一个非常好的功能就是它可以自动去刷新缓存,这样就可以保证我们缓存里面一直有数据,而且大概率是最新的数据。

private final LoadingCache<CountryCacheKey, String> appSettingCache = CaffeineCacheUtils
    .createLoadingCache(1000, Duration.ofHours(2), Duration.ofDays(1),
                       "app-setting-thread", // threadName
                       new CacheLoader<CountryCacheKey, String>() {
                           @Nullable
                           @Override
                           public String load(CountryCacheKey key) throws Exception {
                               String k = key.getKey(String.class);
                               String setting = settingApi.getAppSettingByName(k);
                               return setting;
                           }
                       });

4.锁和异步

4.1 减小锁的粒度

这里其实和上面说到的数据库事务是一个道理,我们可以使用 Lock 类来控制锁的范围。它比 synchronized 关键字更为灵活。

在这里插入图片描述

现在我们的系统都是向微服务的演进,一个业务可能涉及多个服务,所以在锁定资源或者做互斥操作的时候,我们需要考虑用到分布式锁。

4.2 分布式锁

常用的分布式锁的解决方案会用到 Redis,上层是用 Redisson 的框架来提供 java 锁的 API。当我们在使用这些框架的时候,需要注意:

  • 获取锁要加一个等待时间,不能让程序一直自旋,一直在获取锁。
  • 对于获取到的锁要添加一个失效时间,如果不添加失效时间的话。拿 Redisson 举例,里面有一个看门狗机制,会不断进行锁的续期,这样就会增加锁的持有时间。
  • 代码中,一定要在 final 代码块中释放锁,否则因为程序 Bug 导致锁没有释放,导致请求就会卡住,当请求的线程占满以后,整个服务就不可用了。

除了锁的粒度意外呢,我们还可以采用异步的方式去提升服务的性能。比如配合使用 @EnableAsync@Async 来异步地处理日志记录等操作。这样就算日志记录异常也不会影响主流程的流转,接口的响应时间也会下降,性能也会得到明显的提升。

另外,系统间交互我们会用到 MQ。MQ 是一个异步处理的方式。消息的生产者制造消息,然后把消息放到消息队列,比如说 RabbitMQ。接下来消费者只需要去监听这个消息队列,有新的消息会自动去触发和处理,如果消费者处理失败了,消息队列还会进行重发。比之前 A 系统同步调用 B 系统,等 B 系统处理之后才能做接下来的事情相比,性能提升了不少。

在这里插入图片描述

整理完毕,完结撒花~ 🌻





参考地址:

1.用4个方法,提升接口性能 | 多线程 | 数据库优化 | 缓存 | 异步与MQ,https://www.bilibili.com/video/BV1QG4y1g7QJ

  • 0
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
使用本地方法可以提高系统性能,但也会带来一些风险和不便。因此,是否使用本地方法需要根据具体情况来决定。 本地方法是指使用本地语言(如C/C++)编写的方法,可以通过Java的本地接口(JNI)在Java应用程序进行调用。使用本地方法可以提高系统性能,因为本地语言通常比Java语言更接近底层硬件,可以更好地处理一些底层操作,例如文件操作、网络编程和图像处理等。 但是,使用本地方法也会带来一些风险和不便。首先,使用本地方法可能会导致代码的不可移植性,因为不同的操作系统和硬件平台可能需要使用不同的本地语言编写本地方法。其次,使用本地方法也可能会带来安全风险,因为本地方法可以直接访问系统资源,例如文件系统和网络,如果本地方法存在漏洞,可能会导致系统被攻击和破坏。最后,使用本地方法也可能会带来编码和调试的不便,因为本地语言通常需要使用一些特殊的编译器和调试工具来进行开发和调试。 因此,是否使用本地方法需要根据具体情况来决定。在需要处理大量数据、进行底层操作或需要高性能的场景下,可以考虑使用本地方法来提高系统性能。但是,在使用本地方法时需要注意其安全性和移植性,并且需要进行充分的测试和调试,以确保其正确性和稳定性。 总之,推荐使用本地方法来提高系统性能,但需要根据具体情况来决定是否使用,并注意其安全性和移植性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不愿放下技术的小赵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值