当前位置:首页 > 科技  > 软件

我被 parallel 函数雷了

来源: 责编: 时间:2024-01-15 09:18:20 319观看
导读 1. 问题&分析性能优化是技术人的永恒话题,当我们遇到性能问题时,你的第一反应是什么?数据库索引优化,缓存优化,算法优化?但,有时性能杀手往往就是性能优化引入的。1.1. 案例今天一大早,小艾刚到公司便收到一组系统报警,原来有

 1. 问题&分析

性能优化是技术人的永恒话题,当我们遇到性能问题时,你的第一反应是什么?LgA28资讯网——每日最新资讯28at.com

数据库索引优化,缓存优化,算法优化?LgA28资讯网——每日最新资讯28at.com

但,有时性能杀手往往就是性能优化引入的。LgA28资讯网——每日最新资讯28at.com

1.1. 案例

今天一大早,小艾刚到公司便收到一组系统报警,原来有一个接口报了一堆的慢情况。仔细排查,发现是前两天为服务域提供的一个订单的查询接口,该接口刚上线不久,正处于放量阶段,小艾立即惊出一身冷汗,不会是数据库出现了 慢SQL?记得上线前通过 explain 指令对 sql 进行过分析,明确已经使用了数据库索引。他赶紧打开阿里云控制台,快速进入 慢查询功能进行查看,但奇怪的是监控显示没有一条 慢查询,真是太诡异了。LgA28资讯网——每日最新资讯28at.com

还好不是数据库慢查询,不然可能存在将整个 MySQL 数据库拖垮的可能,小艾的悬着的心也终于放了下来。LgA28资讯网——每日最新资讯28at.com

可问题出在哪里呢?LgA28资讯网——每日最新资讯28at.com

这个查询接口非常简单,示例代码如下:LgA28资讯网——每日最新资讯28at.com

@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);}

逻辑简单到令人发指,只有两步:LgA28资讯网——每日最新资讯28at.com

  1. 根据传入的 user id 从数据库中查询订单
  2. 将查询的 Order 转换为 OrderVO 返回用户

小艾,仔细观察这个接口,发现一个现象:当入参较多时,接口的性能变的非常差。LgA28资讯网——每日最新资讯28at.com

这个也比较好理解,系统使用的是 in 语句对数据进行查询,示例:select * from order_info where user_id in (?),当入参数据量非常大时,sql 执行耗时变高。这可能是一个原因,但MySQL 慢请求中未记录任何信息,说明 sql 的执行时间没有超过 1 秒,所以,这个只是一个表因。LgA28资讯网——每日最新资讯28at.com

为了更好的验证猜想,小艾对日志进行完善,整体如下:LgA28资讯网——每日最新资讯28at.com

@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);}

选了几个订单较多的用户进行测试,打印日志如下:LgA28资讯网——每日最新资讯28at.com

图片图片LgA28资讯网——每日最新资讯28at.com

好奇怪,数据库操作耗时有限,但 Order 向 OrderVO 的转换居然耗时这么多,真是太不可思议!LgA28资讯网——每日最新资讯28at.com

1.2. 问题分析

很明显是转化这步出了问题,其核心代码如下所示:LgA28资讯网——每日最新资讯28at.com

// 使用 Stream 流进行类型转化List<OrderVO> orderVOS = orders.stream()        .map(order -> OrderVO.applyByParallel(order))        .collect(Collectors.toList());// Order 到 OrderVO 的转化逻辑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 转换为对应的枚举public static OrderStatus parallelParseByCode(int code) {    return Stream.of(values())            .parallel()            .filter(status -> status.getCode() == code)            .findFirst()            .orElse(null);}

看完核心代码,请思考几分钟,问题可能出现在哪里?LgA28资讯网——每日最新资讯28at.com

  1. Stream 操作?Stream 比 for 循环性能超差些,但还不至于有这么大差异
  2. 反射、BeanCopy?核心代码没有使用这些 API,乖乖的进行 Coding

那问题究竟在哪?答案是  Stream 的 parallel() 函数。使用 parallel 函数最初的目标便是提升性能,为什么在这里却成了性能杀手?在解答前,先快速了解下这个函数:LgA28资讯网——每日最新资讯28at.com

`Stream.parallel()` 函数是 Java 8 中引入的新特性,底层采用了 Fork/Join 框架来实现并行处理。当你调用 `parallel()` 函数时,实际上是将流的并行性设计为 true。这意味着所进行的任何操作,如 `map` 或 `filter`,都是在并行流(parallel stream)上执行的。Fork/Join 框架首先会将一个大任务拆分成若干个小任务(Fork),然后分别对这些小任务进行处理,最后将得到的结果合并(Join)来得到最终结果。LgA28资讯网——每日最新资讯28at.com

这种方式能有效地将任务进行了分解,使得每个线程都可以独立地处理一部分任务,从而发挥了多核 CPU 的优势,提高了整体的处理效率。LgA28资讯网——每日最新资讯28at.com

从上述解释中可以看出,parallel 底层使用 Fork/Join 框架,对任务进行拆解,可以发挥多核的优势,那怎么就成了性能杀手呢?LgA28资讯网——每日最新资讯28at.com

先看下 Fork/Join 的整体执行流程:LgA28资讯网——每日最新资讯28at.com

图片图片LgA28资讯网——每日最新资讯28at.com

其执行主要分为以下几个阶段:LgA28资讯网——每日最新资讯28at.com

  1. 分割阶段(Fork Phase):将大任务拆分成若干个小任务,直到任务的规模足够小,可以直接执行。这通常是通过递归方式实现的。
  2. 执行阶段(Computation Phase):执行每个小任务,并生成结果。
  3. 结果合并阶段(Join Phase):合并小任务的结果,生成大任务的结果。这也通常通过递归的方式实现,与拆分阶段对应。
  4. 善后阶段(Finalize Phase):所有任务的结果都已合并完毕,大任务的结果也已经生成,可以进行善后工作,比如释放资源等。

每个阶段都有一定开销,从整个执行流程上看,执行阶段占的时间越长,性能提升就越高。在数据量较少,或者执行操作开销较大时,并行处理不但不能提高性能,还会由于线程管理和任务分配的开销而导致性能下降。LgA28资讯网——每日最新资讯28at.com

再次回到上面这个案例:LgA28资讯网——每日最新资讯28at.com

// 将 Code 转换为对应的枚举public static OrderStatus parallelParseByCode(int code) {    return Stream.of(values())            .parallel()            .filter(status -> status.getCode() == code)            .findFirst()            .orElse(null);}

首先,枚举的数量非常小,其次,执行逻辑非常简单,仅进行一个等值比较。在这种情况下使用 parallel 函数,将致使线程管理和任务分配开销巨大,从而成为系统瓶颈。LgA28资讯网——每日最新资讯28at.com

2. 解决方案

既然问题是通过 parallel 函数引入的,那解决方案便是:删除 parallel 函数调用,直接串行执行即可。LgA28资讯网——每日最新资讯28at.com

修改后的代码如下:LgA28资讯网——每日最新资讯28at.com

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

使用相同的数据重新测试,耗时如下图所示:LgA28资讯网——每日最新资讯28at.com

图片图片LgA28资讯网——每日最新资讯28at.com

可见,性能直接提升 10 倍不止。LgA28资讯网——每日最新资讯28at.com

3. 示例&源码

代码仓库:https://gitee.com/litao851025/learnFromBugLgA28资讯网——每日最新资讯28at.com

代码地址:https://gitee.com/litao851025/learnFromBug/tree/master/src/main/java/com/geekhalo/demo/thread/parallelfunLgA28资讯网——每日最新资讯28at.com


LgA28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-60900-0.html我被 parallel 函数雷了

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 内蒙古通辽破获特大网络组织淫秽表演案:涉十余万名注册用户、4000 名女主播

下一篇: Requestium - 将Requests和Selenium合并在一起的自动化测试工具

标签:
  • 热门焦点
  • K60 Pro官方停产 第三方瞬间涨价

    虽然没有官方宣布,但Redmi的一些高管也已经透露了,Redmi K60 Pro已经停产且不会补货,这一切都是为了即将到来的K60 Ultra铺路,属于厂家的正常操作。但有意思的是该机在停产之后
  • 小米官宣:2023年上半年出货量中国第一!

    今日早间,小米电视官方微博带来消息,称2023年小米电视上半年出货量达到了中国第一,同时还表示小米电视的巨屏风暴即将开始。“公布一个好消息2023年#小米电视上半年出货量中国
  • Redmi Pad评测:红米充满野心的一次尝试

    从Note系列到K系列,从蓝牙耳机到笔记本电脑,红米不知不觉之间也已经形成了自己颇有竞争力的产品体系,在中端和次旗舰市场上甚至要比小米新机的表现来得更好,正所谓“大丈夫生居
  • K8S | Service服务发现

    一、背景在微服务架构中,这里以开发环境「Dev」为基础来描述,在K8S集群中通常会开放:路由网关、注册中心、配置中心等相关服务,可以被集群外部访问;图片对于测试「Tes」环境或者
  • 如何使用JavaScript创建一只图像放大镜?

    译者 | 布加迪审校 | 重楼如果您曾经浏览过购物网站,可能遇到过图像放大功能。它可以让您放大图像的特定区域,以便浏览。结合这个小小的重要功能可以大大改善您网站的用户体验
  • 从零到英雄:高并发与性能优化的神奇之旅

    作者 | 波哥审校 | 重楼作为公司的架构师或者程序员,你是否曾经为公司的系统在面对高并发和性能瓶颈时感到手足无措或者焦头烂额呢?笔者在出道那会为此是吃尽了苦头的,不过也得
  • WebRTC.Net库开发进阶,教你实现屏幕共享和多路复用!

    WebRTC.Net库:让你的应用更亲民友好,实现视频通话无痛接入! 除了基本用法外,还有一些进阶用法可以更好地利用该库。自定义 STUN/TURN 服务器配置WebRTC.Net 默认使用 Google 的
  • 7月4日见!iQOO 11S官宣:“鸡血版”骁龙8 Gen2+200W快充加持

    上半年已接近尾声,截至目前各大品牌旗下的顶级旗舰都已悉数亮相,而下半年即将推出的顶级旗舰已经成为了数码圈爆料的主流,其中就包括全新的iQOO 11S系
  • 荣耀Magic4 至臻版 首创智慧隐私通话 强劲影音系统

    2022年第一季度临近尾声,在该季度内,许多品牌陆续发布自己的最新产品,让大家从全新的角度来了解当今的手机技术。手机是电子设备中,更新迭代十分迅速的一款产品,基
Top