物联网流量卡骗局_纯流量卡骗局

物联网流量卡骗局_纯流量卡骗局冰河用多线程优化了亿级流量电商业务下的海量数据校对系统,性能直接提升了200%,这次将整个优化过程分享给大家,全程干货,建议收藏!!

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

大家好,我是冰河~~

最近不少运营同事找到我说:咱们的数据校对系统越来越慢了,要过很久才会显示出校对结果,你能不能快速优化一下呢?我:好的,我先了解下业务啊。

在这里插入图片描述
注:全程干货,文章对你有点帮助的话,小伙伴们点赞,收藏,评论,分享,走起~~

优化背景

由于这个数据校对系统最初不是我开发的,我了解了下数据校对系统的业务,整体来说,数据校对系统的业务还是比较简单的。用户通过商城提交订单后,会在订单微服务中生成订单信息,保存在订单数据库中。

订单微服务会调用库存微服务的接口,扣减商品的库存数量,并且会将每笔订单扣减库存的记录保存在库存数据库中。为了防止用户提交订单后没有扣减库存,或者重复扣减库存,数据校对系统每天会校验订单中提交的商品数量与扣减的库存数量是否一致,并且会将校对的结果信息保存到数据校对信息表中。

数据校对系统的总体流程为:先查询订单记录,然后在查询库存的扣减记录,然后对比订单和库存扣减记录,然后将校对的结果信息保存到数据校对信息表中,整体流程如下所示。

在这里插入图片描述

为了能够让大家更好的了解数据校对系统对于订单和库存的校对业务,我将代码精简了下,核心业务逻辑代码如下所示。

//检测是否存在未对账订单
checkOrders = checkOrders();
while(checkOrders != null){ 
   
    //查询未校对的订单信息
    hasNoOrders = getHasNoOrders();
    //查询未校对的库存记录
    hasNoStock = getHasNoStock();
    //校对数据并返回结果
    checkResult = checkData(hasNoOrders, hasNoStock);
    //将结果信息保存到数据校对信息表中
    saveCheckResult(checkResult);
    //检测是否存在未对账订单
    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,继续往下执行。这样,就能够完美解决我们遇到的问题了。优化后的代码如下所示。

//检测是否存在未对账订单
checkOrders = checkOrders();
//创建线程池
Executor executor =  Executors.newFixedThreadPool(2);
while(checkOrders != null){ 
   
    CountDownLatch latch = new CountDownLatch(2);
    executor.execute(()->{ 
   
        //查询未校对的订单信息
        hasNoOrders = getHasNoOrders();
        latch.countDown();
    });
    executor.execute(()->{ 
   
       //查询未校对的库存记录
       hasNoStock = getHasNoStock();
       latch.countDown();
    });
    
    //等待子线程的逻辑执行完毕
    latch.await();
 
    //校对数据并返回结果
    checkResult = checkData(hasNoOrders, hasNoStock);
    //将结果信息保存到数据校对信息表中
    saveCheckResult(checkResult);
    //检测是否存在未对账订单
    checkOrders = checkOrders();
}

至此,我们就完成了系统的优化工作。

总结与思考

这次系统性能的优化,主要是将单线程执行的数据校对业务,优化成使用多线程执行。在平时的工作过程中,我们需要认真思考,找到系统性能瓶颈所在,找出在逻辑上不相干,并且没有先后顺序的业务逻辑,将其放到不同的线程中执行,能够大大提供系统的性能。

这次,对于系统的优化,我们最终使用线程池来执行比较耗时的查询订单与查询库存记录的操作,并且在主线程中等待线程池中的线程逻辑执行完毕后再执行主线程的后续业务逻辑。这种场景,使用Java中提供的CountDownLatch类再合适不过了。这里,再强调一下:CountDownLatch主要的使用场景就是一个线程等待多个线程执行完毕后再执行。如下图所示。

在这里插入图片描述

这里,也进一步提醒了我们:如果想学好并发编程,熟练的掌握Java中提供的并发类库是我们必须要做到的。

最后,给大家一个思考题:其实,上面的代码不是最优的,你有更好的优化方法吗?欢迎各位小伙伴在文末留言讨论呀!

写在最后

如果你想进大厂,想升职加薪,或者对自己现有的工作比较迷茫,都可以私信我交流,希望我的一些经历能够帮助到大家~~

推荐阅读:

好了,今天就到这儿吧,小伙伴们点赞、收藏、评论,一键三连走起呀,我是冰河,我们下期见~~

物联网流量卡骗局_纯流量卡骗局

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/172030.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 汇编语言中各种移位指令的区别与联系

    汇编语言中各种移位指令的区别与联系本文转载自:https://blog.csdn.net/richerg85/article/details/27558005SHL、SHR、SAL、SAR:移位指令;SHL(ShiftLeft):   逻辑左移;SHR(ShiftRight):   逻辑右移;SAL(ShiftArithmeticLeft):算术左移;SAR(Shift…

    2022年6月9日
    22
  • js替换html中的字符串,js怎么替换字符串?

    js替换html中的字符串,js怎么替换字符串?在js中,可以使用str.replace()方法来替换字符串。replace()方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串;然后返回一个新的字符串。replace()方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。语法:stringObject.replace(regexp/substr,replacement)返回值一个新的字符串…

    2022年5月18日
    40
  • listnode用法(java list set)

    javaListNodeclassListNode{Eval;//结点值,泛型ListNode<E>next;//下一结点ListNode(Ex){val=this.x;}}创建及遍历链表classListNode{intval;ListNodenext;ListNode(intx){val=this.x;}}ListNodenodestr=newListN

    2022年4月17日
    148
  • 嵌入式学习书籍大推荐

    嵌入式学习书籍大推荐ARM+Linux嵌入式底层内核驱动方向学习总体路线图 附上ARM学习路径图,希望对学习者能有所启发。基础学习Ⅰ—Linux入门      目前嵌入式主要开发环境有Linux、Wince等;Linux因其开源、开发操作便利而被广泛采用。而Linux操作系统也只是一个简单的操作系统,简单的使用对于嵌入式开发人员来说价值并不很高,真正有价值的是掌握Linux的基本服务和

    2022年6月13日
    30
  • uart 时序_8080接口时序

    uart 时序_8080接口时序数据传送速率用波特率来表示,指单位时间内载波参数变化的次数,或每秒钟传送的二进制位数  如每秒钟传送240个字符,而每个字符包含10位(1个起始位,1个停止位,8个数据位),这时的波特率为2400Bd  传输时序如下图    在UART中,信号线上共有两种状态,分别用逻辑1(高电平)和逻辑0(低电平)来区分  在空闲时,数据线应该保持在逻辑高电平状态  其中…

    2022年9月14日
    0
  • html页面导出为pdf(jsPDF、iText、wkhtmltopdf)「建议收藏」

    html页面导出为pdf(jsPDF、iText、wkhtmltopdf)「建议收藏」html页面导出pdf,本来是一件很简单的事情,在浏览器直接打印(Mac快捷键为⌘+p;Windows快捷键为ctrl+p),就可以把页面另存为pdf文件,但对于要经常把页面导出为pdf的用户来说并不友好。调研了几种html导出pdf的实现方式,这里把要点记录下来分享下。调研对象优点缺点分页图片表格链接中文特殊字符、样式导出…

    2022年6月8日
    37

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号