内存 Join 可以如此简单!!!

本文讨论了数据库Join在高并发场景下的性能问题,以及如何通过内存Join进行优化。作者介绍了线上订单接口性能问题的背景,以及从单条查询优化到批量内存Join的演变过程,包括并行处理的实现。文章还提出了简化开发的目标,并提供了快速入门的教程,包括添加starter、使用@JoinInMemory注解以及自定义注解的方式。最后,文章给出了性能比较,展示了内存Join的性能优势。
摘要由CSDN通过智能技术生成

1. 概览

数据库 Join 真的太香了,但由于各种原因,在实际项目中越来越受局限,只能由开发人员在应用层完成。这种繁琐、无意义的“体力劳动”让我们离“快乐生活”越来越远。

1.1. 背景

不知道什么时候,数据库 join 成为了公认的“性能杀手”,对此,很多公司严厉禁止其使用。上有政策下有对策,你的应对之道是什么?

数据库 Join 退出历史舞台,主要由以下几大推动力:

  1. 微服务。微服务要求“数据资产私有化”,也就是说每个服务的数据库是私有资产,不允许其他服务的直接访问;如果需要访问,只能通过服务所提供的接口完成;

  2. 分库分表的限制。当数据量超过 MySQL 单实例承载能力时,通常会通过“分库分表”这一技术手段来解决,分库分表后,数据被分散到多个分区中,导致 join 语句失效;

  3. 性能瓶颈。在高并发情况下,join 存在一定的性能问题,高并发、高性能端场景不适合使用;

不管原因几何,目前,很多大厂已经将 “禁止join” 列入编码规范,我们该如何面对?

只定规范,不给工具,是一种极度不负责任的表现。

1.1.1. 线上问题跟踪

线上 order/list 接口 tp99 超过 2s,严重影响用户体验,同时还有愈演愈烈之势。通过 Trace 系统,发现一个请求居然存在几百甚至上千次 DB 调用!

第一反应,肯定是在 for 循环中调用了 DB,翻看代码果然如此,代码示例如下:

@Override
public List<? extends OrderDetailVO> getByUserId(Long userId) {
    List<Order> orders = this.orderRepository.getByUserId(userId);
    return orders.stream()
            .map(order -> convertToOrderDetailVO(order))
            .collect(toList());
}

private OrderDetailVOV1 convertToOrderDetailVO(Order order) {
    OrderVO orderVO = OrderVO.apply(order);
    OrderDetailVOV1 orderDetailVO = new OrderDetailVOV1(orderVO);

    Address address = this.addressRepository.getById(order.getAddressId());
    AddressVO addressVO = AddressVO.apply(address);
    orderDetailVO.setAddress(addressVO);

    User user = this.userRepository.getById(order.getUserId());
    UserVO userVO = UserVO.apply(user);
    orderDetailVO.setUser(userVO);

    Product product = this.productRepository.getById(order.getProductId());
    ProductVO productVO = ProductVO.apply(product);
    orderDetailVO.setProduct(productVO);

    return orderDetailVO;
}

代码非常简单,只做了几件事:

  1. 获取用户的 order 信息;

  2. 遍历每一个 order,为其装配关联数据;

  3. 返回最终结果;

逻辑非常清晰,单请求数据库访问总次数 = 1(获取用户订单)+ N(订单数量) * 3(需要抓取的关联数据)

可见,N(订单数量) * 3(关联数据数量)  是性能的最大杀手,存在严重的读放大效应。不同的用户,订单数量相差巨大,导致该接口性能差距巨大。

1.1.2. 繁琐、无意义的代码

如何应对?第一反应就是 批量获取,然后在内存中完成 Join。这是一个好的方案,但引入了大量繁琐、无意义的代码。

该问题常规解决方案如下:

@Override
public List<? extends OrderDetailVO> getByUserId(Long userId) {
    List<Order> orders = this.orderRepository.getByUserId(userId);

    List<OrderDetailVOV2> orderDetailVOS = orders.stream()
            .map(order -> new OrderDetailVOV2(OrderVO.apply(order)))
            .collect(toList());

    List<Long> userIds = orders.stream()
            .map(Order::getUserId)
            .collect(toList());
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值