我用多线程优化了亿级流量电商业务下的海量数据校对系统,性能直接提升了200%

//检测是否存在未对账订单

checkOrders = checkOrders();

}

好了,上述就是系统优化的背景,想必看到这里,很多小伙伴应该知道问题出在哪里了。我们继续往下看。

问题分析


虽然很多小伙伴应该已经知道系统性能低下的问题所在了,这里,我们就一起详细分析下校对系统性能低下的原因。

既然运营的同事说数据校对系统越来越慢了,我们首先要做的就是找到系统的性能瓶颈所在。据了解,目前的数据对账系统,由于订单记录和库存扣减记录数据量巨大,所以查询未校对的订单信息的方法getHasNoOrders()和查询为校对的库存记录的方法getHasNoStock()相对来说比较慢。并且在数据校对系统中,校对订单和库存记录的方法是单线程执行的,我们可以简单画一个时间抽线图,如下所示。

在这里插入图片描述

由图可以看出,以单线程的方式getHasNoOrders()方法和getHasNoStock()方法耗费了大量的时间,这两个方法本身在逻辑上就是两个独立的方法,并且这两个方法没有先后的执行的顺序依赖。那这两个方法能不能并行执行呢?很显然是可以的。那我们把getHasNoOrders()方法和getHasNoStock()方法分别放到两个不同的线程中,优化下系统的性能,整体流程如下所示。

在这里插入图片描述

优化后,我们将getHasNoOrders()方法放到线程1中执行,getHasNoStock()方法放到线程2中执行,checkData()方法和saveCheckResult()方法发放到线程3中执行,优化后的系统性能相比优化前的系统性能几乎提升了一倍,优化效果相对来说还是比较明显的。

说到这里,大家应该应该知道具体怎么优化了吧?好,我们继续往下看!

解决方案


解决问题的思路有了,接下来,我们看看如何使用代码实现我们上面分析的解决问题的思路。这里,我们可以分别开启两个线程执行getHasNoOrders()方法和getHasNoStock()方法,在主线程中执行checkData()方法和saveCheckResult()方法。这里需要注意的是:主线程需要等待两个子线程执行完毕之后再执行checkData()方法和saveCheckResult()方法。

为了实现这个功能,我们可以使用Thread类中join()方法,有关Thread类中join()方法的具体说明,小伙伴们可以参见《高并发之——朋友去面试竟然栽在了Thread类的源码上》一文。这里,具体的逻辑就是在主线程中调用两个子线程的join()方法实现阻塞等待,当两个子线程执行完毕退出时,调用两个子线程join()方法的主线程会被唤醒,从而执行主线程中的checkData()方法和saveCheckResult()方法。大体代码如下所示。

//检测是否存在未对账订单

checkOrders = checkOrders();

while(checkOrders != null){

Thread t1 = new Thread(()->{

//查询未校对的订单信息

hasNoOrders = getHasNoOrders();

});

t1.start();

Thread t2 = new Thread(()->{

//查询未校对的库存记录

hasNoStock = getHasNoStock();

});

t2.start();

//阻塞主线程,等待线程t1和线程t2执行完毕

t1.join();

t2.join();

//校对数据并返回结果

checkResult = checkData(hasNoOrders, hasNoStock);

//将结果信息保存到数据校对信息表中

saveCheckResult(checkResult);

//检测是否存在未对账订单

checkOrders = checkOrders();

}

至此,我们基本上能够解决问题了。但是,还有没有进一步优化的空间呢?我们进一步往下看。

进一步优化


通过上面对系统优化,基本能够达成我们的优化目标,但是上面的解决方案存在着不足的地方,那就是在while循环里每次都要新建两个线程分别执行getHasNoOrders()方法和getHasNoStock()方法,了解Java多线程的小伙伴们应该都知道,在Java中创建线程可是个非常耗时的操作。所以,最好是能够将创建出来的线程反复使用。这里,估计很多小伙伴都会想到使用线程池,没错,我们可以使用线程池进一步优化上面的代码。

遇到新的问题


不过在使用线程池进一步优化时,我们会遇到一个问题,就是主线程如何等待子线程中的结果数据呢?说直白点就是:主线程如何知道子线程中的getHasNoOrders()方法和getHasNoStock()方法执行完了? 由于在之前的代码中我们是在主线程中调用子线程的join()方法等待子线程执行完毕,获取到子线程执行的结果后,继续执行主线程的逻辑。但是如果使用了线程池的话,线程池中的线程根本不会退出,此时,我们无法使用线程的join()方法等待线程执行完毕。

所以,主线程如何知道子线程中的getHasNoOrders()方法和getHasNoStock()方法执行完了? 这个问题就成了关键的突破点。这里,我们使用线程池进一步优化的代码如下所示。

//检测是否存在未对账订单

checkOrders = checkOrders();

//创建线程池

Executor executor = Executors.newFixedThreadPool(2);

while(checkOrders != null){

executor.execute(()->{

//查询未校对的订单信息

hasNoOrders = getHasNoOrders();

});

executor.execute(()->{

//查询未校对的库存记录

hasNoStock = getHasNoStock();

});

/如何知道子线程中的getHasNoOrders()方法和getHasNoStock()方法执行完了成为关键/

//校对数据并返回结果

checkResult = checkData(hasNoOrders, hasNoStock);

//将结果信息保存到数据校对信息表中

saveCheckResult(checkResult);

//检测是否存在未对账订单

checkOrders = checkOrders();

}

那么,如何解决这个问题呢?我们继续往下看。

新的解决方案


相信细心的小伙伴们能够看出,整个业务的场景就是:一个线程需要等待其他两个线程的逻辑执行完毕后再执行。在Java的并发类库中,为我们提供了一个能够在这种场景下使用的类库,那就是CountDownLatch类。如果对CountDownLatch类不了解的小伙伴可以参考《浅谈AQS中的CountDownLatch、Semaphore与CyclicBarrier》一文。

使用CountDownLatch类优化我们程序的具体做法就是:在程序的while()循环中首先创建一个CountDownLatch对象,计数器的值初始化为2。分别在hasNoOrders = getHasNoOrders();代码和hasNoStock = getHasNoStock();代码的后面调用latch.countDown()方法使得计数器的值分别减1。在主线程中调用latch.await()方法,等待计数器的值变为0,继续往下执行。这样,就能够完美解决我们遇到的问题了。优化后的代码如下所示。

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
img

那么如何才能正确的掌握Redis呢?

为了让大家能够在Redis上能够加深,所以这次给大家准备了一些Redis的学习资料,还有一些大厂的面试题,包括以下这些面试题

  • 并发编程面试题汇总

  • JVM面试题汇总

  • Netty常被问到的那些面试题汇总

  • Tomcat面试题整理汇总

  • Mysql面试题汇总

  • Spring源码深度解析

  • Mybatis常见面试题汇总

  • Nginx那些面试题汇总

  • Zookeeper面试题汇总

  • RabbitMQ常见面试题汇总

JVM常频面试:

Redis高频面试笔记:基础+缓存雪崩+哨兵+集群+Reids场景设计

Mysql面试题汇总(一)

Redis高频面试笔记:基础+缓存雪崩+哨兵+集群+Reids场景设计

Mysql面试题汇总(二)

Redis高频面试笔记:基础+缓存雪崩+哨兵+集群+Reids场景设计

Redis常见面试题汇总(300+题)

Redis高频面试笔记:基础+缓存雪崩+哨兵+集群+Reids场景设计
r面试题汇总

  • RabbitMQ常见面试题汇总

JVM常频面试:

[外链图片转存中…(img-CArGapFr-1711861361904)]

Mysql面试题汇总(一)

[外链图片转存中…(img-Q1AJPQ4e-1711861361904)]

Mysql面试题汇总(二)

[外链图片转存中…(img-DdITQbSS-1711861361905)]

Redis常见面试题汇总(300+题)

[外链图片转存中…(img-YOOzOj0N-1711861361905)]

  • 24
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值