大数据量、高并发业务优化教程(一)

简介: 大数据量、高并发业务优化教程(一)

image.png

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情

博主这里的大数据量、高并发业务处理优化基于博主线上项目实践以及全网资料整理而来,在这里分享给大家

一. 大数据量上传写入优化

线上业务后台项目有一个消息推送的功能,通过上传包含用户id的文件,给指定用户推送系统消息


1.1 如上功能描述很简单,但是对于技术侧想要做好这个功能,保证大用户量(比如达到百万级别)下,系统正常运行,功能正常其实是需要仔细思考的,博主这里给出思路:

  1. 上传文件类型选择

通常情况下大部分用户都会使用excel文件,但是相比excel文件还有一种更加推荐的文件格式,那就是csv文件,相比excel文件它可以直接在记事本编辑,excel也可以打开cvs文件,且占用内存更少(画重点),对于上传的csv文件过于庞大,也可以采用流式读取,读一部分写一部分

  1. 消息推送成功与否状态保存

由于大批量数据插入是一个耗时操作(可能几秒也可能几分钟),所以需要保存批量插入是否成功的状态,在后台中可以显现出这条消息推送记录是成功还是失败,方便运营回溯消息推送状态

  1. 批量写入启不启用事务

博主这里给出两种方案利弊:

  • 启用事务:好处在于如批量插入过程中,异常情况可以保证原子性,但是性能比不开事务低,在特大数据量下会明显低一个档次
  • 不启用事务:好处就是写入性能高,特大数据量写入性能提升明显,但是无法保证原子性,但是对于已经批量插入的新增数据,只是会产生脏数据而已,在功能设计合理的情况下是不影响业务的,如下面第四点

综上:在大数据量下,我们要是追求极致性能可以不启用事务,具体选择也需各位结合自身业务情况

  1. 推送异常失败的消息处理

建议功能设计上,可以屏蔽对失败消息再进行操作,这样不需要再处理之前推送失败写入的脏数据,直接新增消息推送即可

1.2 批量写入代码优化

  1. jdbc参数携带 rewriteBatchedStatements=true 在jdbc驱动上启动批量写入功能,如下
spring.datasource.master.jdbc-url=jdbc:mysql://localhost:3306/test_db?allowMultiQueries=true&characterEncoding=utf8&autoReconnect=true&useSSL=false&rewriteBatchedStatements=true

启用 insert into table(id, name) values(1, 'tom'),(2, 'jack') 模式,建议一次写入个数不要太多,MySQL对于sql长度是有限制的,对于这种字段少的表,一次写入500 - 1000问题不大,字段多了需要降低这个写入量

insert into im_notice_app_ref(notice_id, app_id, create_time)
values
<foreach collection="list" separator="," item="item">
    (#{item.noticeId}, #{item.appId}, #{item.createTime})
</foreach>

一般情况下大家都知道第二条优化,但是可能会忽略jdbc参数携带 rewriteBatchedStatements=true,这个参数能在第二条的基础上启用批量执行SQL,进一步提升写入性能

二. 大事务优化,减小影响范围,提升系统处理能力

@Transactional 大于 Spring 提供得事务注解,许多人都知道,但是在高并发下,不建议使用,推荐通过编程式事务来手动控制事务提交或者回滚,减少事务影响范围

如下是一段订单超时未支付回滚业务数据得代码,采用 @Transactional 事务注解

@Transactional(rollbackFor = Exception.class)
public void doUnPaidTask(Long orderId) {
    // 1. 查询订单是否存在
    Order order = orderService.getById(orderId);
    if (order == null) {
        throw new BusinessException(String.format("订单不存在,orderId:%s", orderId));
    }
    if (order.getOrderStatus() != OrderStatusEnum.ORDER_PRE_PAY.getOrderStatus()) {
        throw new BusinessException(String.format("订单状态错误,order:%s", order));
    }
    // 2. 设置订单为已取消状态
    order.setOrderStatus((byte) OrderStatusEnum.ORDER_CLOSED_BY_EXPIRED.getOrderStatus());
    order.setUpdateTime(new Date());
    if (!orderService.updateById(order)) {
        throw new BusinessException("更新数据已失效");
    }
    // 3.商品货品数量增加
    LambdaQueryWrapper<OrderItem> queryWrapper = Wrappers.lambdaQuery();
    queryWrapper.eq(OrderItem::getOrderId, orderId);
    List<OrderItem> orderItems = orderItemService.list(queryWrapper);
    for (OrderItem orderItem : orderItems) {
        if (orderItem.getSeckillId() != null) { // 秒杀单商品项处理
            Long seckillId = orderItem.getSeckillId();
            SeckillService seckillService = SpringContextUtil.getBean(SeckillService.class);
            if (!seckillService.addStock(seckillId)) {
                throw new BusinessException("秒杀商品货品库存增加失败");
            }
        } else { // 普通单商品项处理
            Long goodsId = orderItem.getGoodsId();
            Integer goodsCount = orderItem.getGoodsCount();
            if (!goodsDao.addStock(goodsId, goodsCount)) {
                throw new BusinessException("秒杀商品货品库存增加失败");
            }
        }
    }
    // 4. 返还优惠券
    couponService.releaseCoupon(orderId);
    log.info("---------------订单orderId:{},未支付超时取消成功", orderId);
}

采用编程式事务对其优化,代码如下:

@Resource
private PlatformTransactionManager platformTransactionManager;
@Resource
private TransactionDefinition transactionDefinition;
public void doUnPaidTask(Long orderId) {
    // 启用编程式事务
    // 1. 在开启事务钱查询订单是否存在
    Order order = orderService.getById(orderId);
    if (order == null) {
        throw new BusinessException(String.format("订单不存在,orderId:%s", orderId));
    }
    if (order.getOrderStatus() != OrderStatusEnum.ORDER_PRE_PAY.getOrderStatus()) {
        throw new BusinessException(String.format("订单状态错误,order:%s", order));
    }
    // 2. 开启事务
    TransactionStatus transaction = platformTransactionManager.getTransaction(transactionDefinition);
    try {
        // 3. 设置订单为已取消状态
        order.setOrderStatus((byte) OrderStatusEnum.ORDER_CLOSED_BY_EXPIRED.getOrderStatus());
        order.setUpdateTime(new Date());
        if (!orderService.updateById(order)) {
            throw new BusinessException("更新数据已失效");
        }
        // 4. 商品货品数量增加
        LambdaQueryWrapper<OrderItem> queryWrapper = Wrappers.lambdaQuery();
        queryWrapper.eq(OrderItem::getOrderId, orderId);
        List<OrderItem> orderItems = orderItemService.list(queryWrapper);
        for (OrderItem orderItem : orderItems) {
            if (orderItem.getSeckillId() != null) { // 秒杀单商品项处理
                Long seckillId = orderItem.getSeckillId();
                SeckillService seckillService = SpringContextUtil.getBean(SeckillService.class);
                RedisCache redisCache = SpringContextUtil.getBean(RedisCache.class);
                if (!seckillService.addStock(seckillId)) {
                    throw new BusinessException("秒杀商品货品库存增加失败");
                }
                redisCache.increment(Constants.SECKILL_GOODS_STOCK_KEY + seckillId);
                redisCache.deleteCacheSet(Constants.SECKILL_SUCCESS_USER_ID + seckillId, order.getUserId());
            } else { // 普通单商品项处理
                Long goodsId = orderItem.getGoodsId();
                Integer goodsCount = orderItem.getGoodsCount();
                if (!goodsDao.addStock(goodsId, goodsCount)) {
                    throw new BusinessException("秒杀商品货品库存增加失败");
                }
            }
        }
        // 5. 返还优惠券
        couponService.releaseCoupon(orderId);
        // 6. 所有更新操作完成后,提交事务
        platformTransactionManager.commit(transaction);
        log.info("---------------订单orderId:{},未支付超时取消成功", orderId);
    } catch (Exception e) {
        log.info("---------------订单orderId:{},未支付超时取消失败", orderId, e);
        // 7. 发生异常,回滚事务
        platformTransactionManager.rollback(transaction);
    }
}

可以看到采用编程式事务后,我们将查询逻辑排除在事务之外,减小了其影响范围,也就提升了性能,在高并发场景下,性能优先的场景,我们甚至可以考虑不适用事务

三. 客户端海量日志上报优化

线上项目客户端,采用tcp协议与日志采集服务建立连接,上报日志数据。业务高峰期下,会有同时成千个客户端建立连接实时上报日志数据

如上场景,高峰期下,对日志采集服务会造成不小的压力,处理服务处理不当,会造成高峰期下,服务卡顿、CPU占用过高、内存溢出等。

这里给出海量日志高并发下优化点:

  1. 上报日志进行异步化处理,
  • 普通版:采用阻塞队列 ArrayBlockingQueue 得生产者消费者模式,对日志数据进行异步批量处理,在此场景下,通过生产者将数据缓存再内存中,然后再消费者中批量保存入库。
  • 进阶版:采用 Disruptor 队列,也是基于内存队列的生产者消费者模型,消费速度对比 ArrayBlockingQueue 有一个数量级得性能提升,附简介说明:www.jianshu.com/p/bad7b4b44…
  • 终极版:采用 kfaka 消息队列中间件,持久日志数据,慢慢消费。虽然引入第三方依赖会增加系统复杂度,但是相比 kfaka 在大数据场景下提供的优秀表现,这一点也是值得。

如上三种方案:大家可以结合自身项目实际体量选择

  1. 采集日志压缩

对上报后的日志如果要再发送给其他服务,推荐是对其进行压缩处理,避免消耗过多网络带宽以及最终数据落库选型:

  • 网络传输,在 Java 里通常是指序列化方式,Jdk 自带得序列化方式对比 Protobuf、fst、Hession 等在序列化速度和大小的表现上都没有优势,甚至可以用垃圾形容,博主这里直接给出 Java 得几种序列化方式对比链接:segmentfault.com/a/119000003…建议对传输大小要求较高可以使用 Avro 序列化, 对综合要求较高可采用 Protobuf
  • 落库选型,像日志这种大数据量落库,都是新增且无修改得场景建议使用 Clickhouse 进行存储,相同数据量下对比 MySql 占用存储更少,查询性能更高

最后,附博主 github 地址:github.com/wayn111

欢迎大家点赞、收藏、转发,你的支持将是博主更文的动力

相关实践学习
基于MaxCompute的热门话题分析
Apsara Clouder大数据专项技能认证配套课程:基于MaxCompute的热门话题分析
目录
相关文章
|
9月前
|
存储 分布式计算 大数据
MaxCompute聚簇优化推荐功能发布,单日节省2PB Shuffle、7000+CU!
MaxCompute全新推出了聚簇优化推荐功能。该功能基于 31 天历史运行数据,每日自动输出全局最优 Hash Cluster Key,对于10 GB以上的大型Shuffle场景,这一功能将直接带来显著的成本优化。
386 3
|
9月前
|
数据采集 搜索推荐 Java
Java 大视界 -- Java 大数据在智能教育虚拟学习环境构建与用户体验优化中的应用(221)
本文探讨 Java 大数据在智能教育虚拟学习环境中的应用,涵盖多源数据采集、个性化推荐、实时互动优化等核心技术,结合实际案例分析其在提升学习体验与教学质量中的成效,并展望未来发展方向与技术挑战。
|
8月前
|
存储 SQL 分布式计算
MaxCompute 聚簇优化推荐原理
基于历史查询智能推荐Clustered表,显著降低计算成本,提升数仓性能。
471 4
MaxCompute 聚簇优化推荐原理
|
8月前
|
存储 并行计算 算法
【动态多目标优化算法】基于自适应启动策略的混合交叉动态约束多目标优化算法(MC-DCMOEA)求解CEC2023研究(Matlab代码实现)
【动态多目标优化算法】基于自适应启动策略的混合交叉动态约束多目标优化算法(MC-DCMOEA)求解CEC2023研究(Matlab代码实现)
363 4
|
8月前
|
数据采集 存储 弹性计算
高并发Java爬虫的瓶颈分析与动态线程优化方案
高并发Java爬虫的瓶颈分析与动态线程优化方案
|
8月前
|
大数据 数据挖掘 定位技术
买房不是拍脑袋:大数据教你优化房地产投资策略
买房不是拍脑袋:大数据教你优化房地产投资策略
353 2
|
9月前
|
运维 监控 Kubernetes
高并发来了,运维别慌:如何优化运维流程,才能稳住阵脚?
高并发来了,运维别慌:如何优化运维流程,才能稳住阵脚?
271 4
|
8月前
|
数据采集 网络协议 API
协程+连接池:高并发Python爬虫的底层优化逻辑
协程+连接池:高并发Python爬虫的底层优化逻辑
|
9月前
|
存储 人工智能 算法
Java 大视界 -- Java 大数据在智能医疗影像数据压缩与传输优化中的技术应用(227)
本文探讨 Java 大数据在智能医疗影像压缩与传输中的关键技术应用,分析其如何解决医疗影像数据存储、传输与压缩三大难题,并结合实际案例展示技术落地效果。
|
9月前
|
机器学习/深度学习 算法 Java
Java 大视界 -- Java 大数据机器学习模型在生物信息学基因功能预测中的优化与应用(223)
本文探讨了Java大数据与机器学习模型在生物信息学中基因功能预测的优化与应用。通过高效的数据处理能力和智能算法,提升基因功能预测的准确性与效率,助力医学与农业发展。