偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

我被 Parallel 函數(shù)雷了

開發(fā) 前端
系統(tǒng)使用的是 in 語(yǔ)句對(duì)數(shù)據(jù)進(jìn)行查詢,示例:select * from order_info where user_id in (?),當(dāng)入?yún)?shù)據(jù)量非常大時(shí),sql 執(zhí)行耗時(shí)變高。這可能是一個(gè)原因,但MySQL 慢請(qǐng)求中未記錄任何信息,說(shuō)明 sql 的執(zhí)行時(shí)間沒(méi)有超過(guò) 1 秒,所以,這個(gè)只是一個(gè)表因。

一、問(wèn)題&分析

性能優(yōu)化是技術(shù)人的永恒話題,當(dāng)我們遇到性能問(wèn)題時(shí),你的第一反應(yīng)是什么?

數(shù)據(jù)庫(kù)索引優(yōu)化,緩存優(yōu)化,算法優(yōu)化?

但,有時(shí)性能殺手往往就是性能優(yōu)化引入的。

1.1. 案例

今天一大早,小艾剛到公司便收到一組系統(tǒng)報(bào)警,原來(lái)有一個(gè)接口報(bào)了一堆的慢情況。仔細(xì)排查,發(fā)現(xiàn)是前兩天為服務(wù)域提供的一個(gè)訂單的查詢接口,該接口剛上線不久,正處于放量階段,小艾立即驚出一身冷汗,不會(huì)是數(shù)據(jù)庫(kù)出現(xiàn)了 慢SQL?記得上線前通過(guò) explain 指令對(duì) sql 進(jìn)行過(guò)分析,明確已經(jīng)使用了數(shù)據(jù)庫(kù)索引。他趕緊打開阿里云控制臺(tái),快速進(jìn)入 慢查詢功能進(jìn)行查看,但奇怪的是監(jiān)控顯示沒(méi)有一條 慢查詢,真是太詭異了。

還好不是數(shù)據(jù)庫(kù)慢查詢,不然可能存在將整個(gè) MySQL 數(shù)據(jù)庫(kù)拖垮的可能,小艾的懸著的心也終于放了下來(lái)。

可問(wèn)題出在哪里呢?

這個(gè)查詢接口非常簡(jiǎn)單,示例代碼如下:

@GetMapping("getOrdersByUsers")
public RestResult<List<OrderVO>> allOrderByUsers(@RequestParam List<Long> users){
    Stopwatch  stopwatch = Stopwatch.createStarted();
    List<Order> orders = getByUserId(users);
    List<OrderVO> orderVOS = orders.stream()
            .map(order -> OrderVO.applyByParallel(order))
            .collect(Collectors.toList());
    log.info("get order by user cost {} ms", stopwatch.stop().elapsed(TimeUnit.MILLISECONDS));
    return RestResult.success(orderVOS);
}

邏輯簡(jiǎn)單到令人發(fā)指,只有兩步:

  1. 根據(jù)傳入的 user id 從數(shù)據(jù)庫(kù)中查詢訂單
  2. 將查詢的 Order 轉(zhuǎn)換為 OrderVO 返回用戶

小艾,仔細(xì)觀察這個(gè)接口,發(fā)現(xiàn)一個(gè)現(xiàn)象:當(dāng)入?yún)⑤^多時(shí),接口的性能變的非常差。

這個(gè)也比較好理解,系統(tǒng)使用的是 in 語(yǔ)句對(duì)數(shù)據(jù)進(jìn)行查詢,示例:select * from order_info where user_id in (?),當(dāng)入?yún)?shù)據(jù)量非常大時(shí),sql 執(zhí)行耗時(shí)變高。這可能是一個(gè)原因,但MySQL 慢請(qǐng)求中未記錄任何信息,說(shuō)明 sql 的執(zhí)行時(shí)間沒(méi)有超過(guò) 1 秒,所以,這個(gè)只是一個(gè)表因。

為了更好的驗(yàn)證猜想,小艾對(duì)日志進(jìn)行完善,整體如下:

@GetMapping("getOrdersByUsers")
public RestResult<List<OrderVO>> allOrderByUsers(@RequestParam List<Long> users){
    Stopwatch  stopwatch = Stopwatch.createStarted();
    List<Order> orders = getByUserId(users);
    log.info("get data from DB cost {} ms", stopwatch.stop().elapsed(TimeUnit.MILLISECONDS));

    stopwatch = Stopwatch.createStarted();
    List<OrderVO> orderVOS = orders.stream()
            .map(order -> OrderVO.applyByParallel(order))
            .collect(Collectors.toList());
    log.info("convert to OrderVO cost {} ms", stopwatch.stop().elapsed(TimeUnit.MILLISECONDS));
    return RestResult.success(orderVOS);
}

選了幾個(gè)訂單較多的用戶進(jìn)行測(cè)試,打印日志如下:

圖片圖片

好奇怪,數(shù)據(jù)庫(kù)操作耗時(shí)有限,但 Order 向 OrderVO 的轉(zhuǎn)換居然耗時(shí)這么多,真是太不可思議!

1.2. 問(wèn)題分析

很明顯是轉(zhuǎn)化這步出了問(wèn)題,其核心代碼如下所示:

// 使用 Stream 流進(jìn)行類型轉(zhuǎn)化
List<OrderVO> orderVOS = orders.stream()
        .map(order -> OrderVO.applyByParallel(order))
        .collect(Collectors.toList());

// Order 到 OrderVO 的轉(zhuǎn)化邏輯
public static OrderVO applyByParallel(Order order){
    OrderVO orderVO = new OrderVO();
    orderVO.setId(order.getId());
    orderVO.setUserId(order.getUserId());

    orderVO.setStatus(OrderStatus.parallelParseByCode(order.getOrderStatus()));
    orderVO.setOrderType(OrderType.parallelParseByCode(order.getOrderType()));
    orderVO.setProductType(ProductType.parallelParseByCode(order.getProductType()));
    orderVO.setPromotionType(PromotionType.parallelParseByCode(order.getPromotionType()));
    return orderVO;
}

// 將 Code 轉(zhuǎn)換為對(duì)應(yīng)的枚舉
public static OrderStatus parallelParseByCode(int code) {
    return Stream.of(values())
            .parallel()
            .filter(status -> status.getCode() == code)
            .findFirst()
            .orElse(null);
}

看完核心代碼,請(qǐng)思考幾分鐘,問(wèn)題可能出現(xiàn)在哪里?

  1. Stream 操作?Stream 比 for 循環(huán)性能超差些,但還不至于有這么大差異
  2. 反射、BeanCopy?核心代碼沒(méi)有使用這些 API,乖乖的進(jìn)行 Coding

那問(wèn)題究竟在哪?答案是  Stream 的 parallel() 函數(shù)。使用 parallel 函數(shù)最初的目標(biāo)便是提升性能,為什么在這里卻成了性能殺手?在解答前,先快速了解下這個(gè)函數(shù):

`Stream.parallel()` 函數(shù)是 Java 8 中引入的新特性,底層采用了 Fork/Join 框架來(lái)實(shí)現(xiàn)并行處理。當(dāng)你調(diào)用 `parallel()` 函數(shù)時(shí),實(shí)際上是將流的并行性設(shè)計(jì)為 true。這意味著所進(jìn)行的任何操作,如 `map` 或 `filter`,都是在并行流(parallel stream)上執(zhí)行的。Fork/Join 框架首先會(huì)將一個(gè)大任務(wù)拆分成若干個(gè)小任務(wù)(Fork),然后分別對(duì)這些小任務(wù)進(jìn)行處理,最后將得到的結(jié)果合并(Join)來(lái)得到最終結(jié)果。

這種方式能有效地將任務(wù)進(jìn)行了分解,使得每個(gè)線程都可以獨(dú)立地處理一部分任務(wù),從而發(fā)揮了多核 CPU 的優(yōu)勢(shì),提高了整體的處理效率。

從上述解釋中可以看出,parallel 底層使用 Fork/Join 框架,對(duì)任務(wù)進(jìn)行拆解,可以發(fā)揮多核的優(yōu)勢(shì),那怎么就成了性能殺手呢?

先看下 Fork/Join 的整體執(zhí)行流程:

圖片圖片

其執(zhí)行主要分為以下幾個(gè)階段:

  1. 分割階段(Fork Phase):將大任務(wù)拆分成若干個(gè)小任務(wù),直到任務(wù)的規(guī)模足夠小,可以直接執(zhí)行。這通常是通過(guò)遞歸方式實(shí)現(xiàn)的。
  2. 執(zhí)行階段(Computation Phase):執(zhí)行每個(gè)小任務(wù),并生成結(jié)果。
  3. 結(jié)果合并階段(Join Phase):合并小任務(wù)的結(jié)果,生成大任務(wù)的結(jié)果。這也通常通過(guò)遞歸的方式實(shí)現(xiàn),與拆分階段對(duì)應(yīng)。
  4. 善后階段(Finalize Phase):所有任務(wù)的結(jié)果都已合并完畢,大任務(wù)的結(jié)果也已經(jīng)生成,可以進(jìn)行善后工作,比如釋放資源等。

每個(gè)階段都有一定開銷,從整個(gè)執(zhí)行流程上看,執(zhí)行階段占的時(shí)間越長(zhǎng),性能提升就越高。在數(shù)據(jù)量較少,或者執(zhí)行操作開銷較大時(shí),并行處理不但不能提高性能,還會(huì)由于線程管理和任務(wù)分配的開銷而導(dǎo)致性能下降。

再次回到上面這個(gè)案例:

// 將 Code 轉(zhuǎn)換為對(duì)應(yīng)的枚舉
public static OrderStatus parallelParseByCode(int code) {
    return Stream.of(values())
            .parallel()
            .filter(status -> status.getCode() == code)
            .findFirst()
            .orElse(null);
}

首先,枚舉的數(shù)量非常小,其次,執(zhí)行邏輯非常簡(jiǎn)單,僅進(jìn)行一個(gè)等值比較。在這種情況下使用 parallel 函數(shù),將致使線程管理和任務(wù)分配開銷巨大,從而成為系統(tǒng)瓶頸。

二、解決方案

既然問(wèn)題是通過(guò) parallel 函數(shù)引入的,那解決方案便是:刪除 parallel 函數(shù)調(diào)用,直接串行執(zhí)行即可。

修改后的代碼如下:

public static OrderStatus parseByCode(int code) {
    return Stream.of(values())
            // .parallel() 直接使用串行執(zhí)行
            .filter(status -> status.getCode() == code)
            .findFirst()
            .orElse(null);
}

使用相同的數(shù)據(jù)重新測(cè)試,耗時(shí)如下圖所示:

圖片圖片

可見(jiàn),性能直接提升 10 倍不止。

三、示例&源碼

代碼倉(cāng)庫(kù):https://gitee.com/litao851025/learnFromBug

代碼地址:https://gitee.com/litao851025/learnFromBug/tree/master/src/main/java/com/geekhalo/demo/thread/parallelfun

責(zé)任編輯:武曉燕 來(lái)源: geekhalo
相關(guān)推薦

2013-06-20 11:11:00

程序員經(jīng)理

2020-12-18 08:28:13

Redis數(shù)據(jù)數(shù)據(jù)庫(kù)

2021-06-29 10:02:04

亞馬遜機(jī)器解雇

2013-04-25 13:44:53

挨踢人物傳

2011-08-31 11:00:27

MIUI小米手機(jī)雷軍

2021-05-10 07:30:33

Google技術(shù)谷歌

2020-04-07 08:00:02

Redis緩存數(shù)據(jù)

2021-01-18 11:27:03

Istio架構(gòu)云環(huán)境

2022-05-11 09:18:04

微軟PowerShell降級(jí)

2021-09-13 08:41:52

職場(chǎng)互聯(lián)網(wǎng)自閉

2023-10-31 08:01:48

Mybatis參數(shù)jdbcurl?

2010-11-18 16:29:54

2020-03-12 07:55:50

訪問(wèn)量飆升DDoS

2010-08-30 11:12:42

2014-11-05 09:27:56

陳彤雷軍

2024-06-18 08:31:33

2013-12-18 16:20:20

雷軍小米

2023-09-28 13:21:32

2019-12-09 09:12:59

程序員年薪裁員

2020-02-12 08:09:32

日志規(guī)范推廣
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)