ToB企服应用市场:ToB评测及商务社交产业平台

标题: 大数据量、高并发业务怎么优化?(一) [打印本页]

作者: 立聪堂德州十三局店    时间: 2022-12-15 07:22
标题: 大数据量、高并发业务怎么优化?(一)
博主这里的大数据量、高并发业务处理优化基于博主线上项目实践以及全网资料整理而来,在这里分享给大家
一. 大数据量上传写入优化

线上业务后台项目有一个消息推送的功能,通过上传包含用户id的文件,给指定用户推送系统消息
1.1 如上功能描述很简单,但是对于技术侧想要做好这个功能,保证大用户量(比如达到百万级别)下,系统正常运行,功能正常其实是需要仔细思考的,博主这里给出思路:

通常情况下大部分用户都会使用excel文件,但是相比excel文件还有一种更加推荐的文件格式,那就是csv文件,相比excel文件它可以直接在记事本编辑,excel也可以打开cvs文件,且占用内存更少(画重点),对于上传的csv文件过于庞大,也可以采用流式读取,读一部分写一部分
由于大批量数据插入是一个耗时操作(可能几秒也可能几分钟),所以需要保存批量插入是否成功的状态,在后台中可以显现出这条消息推送记录是成功还是失败,方便运营回溯消息推送状态
博主这里给出两种方案利弊:
综上:在大数据量下,我们要是追求极致性能可以不启用事务,具体选择也需各位结合自身业务情况
建议功能设计上,可以屏蔽对失败消息再进行操作,这样不需要再处理之前推送失败写入的脏数据,直接新增消息推送即可
1.2 批量写入代码优化

  1. spring.datasource.master.jdbc-url=jdbc:mysql://localhost:3306/test_db?allowMultiQueries=true&characterEncoding=utf8&autoReconnect=true&useSSL=false&rewriteBatchedStatements=true
复制代码
  1. insert into im_notice_app_ref(notice_id, app_id, create_time)
  2. values
  3. <foreach collection="list" separator="," item="item">
  4.     (#{item.noticeId}, #{item.appId}, #{item.createTime})
  5. </foreach>
复制代码
一般情况下大家都知道第二条优化,但是可能会忽略jdbc参数携带 rewriteBatchedStatements=true,这个参数能在第二条的基础上启用批量执行SQL,进一步提升写入性能
二. 大事务优化,减小影响范围,提升系统处理能力

@Transactional 大于 Spring 提供得事务注解,许多人都知道,但是在高并发下,不建议使用,推荐通过编程式事务来手动控制事务提交或者回滚,减少事务影响范围
如下是一段订单超时未支付回滚业务数据得代码,采用 @Transactional 事务注解
  1. @Transactional(rollbackFor = Exception.class)
  2. public void doUnPaidTask(Long orderId) {
  3.     // 1. 查询订单是否存在
  4.     Order order = orderService.getById(orderId);
  5.     if (order == null) {
  6.         throw new BusinessException(String.format("订单不存在,orderId:%s", orderId));
  7.     }
  8.     if (order.getOrderStatus() != OrderStatusEnum.ORDER_PRE_PAY.getOrderStatus()) {
  9.         throw new BusinessException(String.format("订单状态错误,order:%s", order));
  10.     }
  11.     // 2. 设置订单为已取消状态
  12.     order.setOrderStatus((byte) OrderStatusEnum.ORDER_CLOSED_BY_EXPIRED.getOrderStatus());
  13.     order.setUpdateTime(new Date());
  14.     if (!orderService.updateById(order)) {
  15.         throw new BusinessException("更新数据已失效");
  16.     }
  17.     // 3.商品货品数量增加
  18.     LambdaQueryWrapper<OrderItem> queryWrapper = Wrappers.lambdaQuery();
  19.     queryWrapper.eq(OrderItem::getOrderId, orderId);
  20.     List<OrderItem> orderItems = orderItemService.list(queryWrapper);
  21.     for (OrderItem orderItem : orderItems) {
  22.         if (orderItem.getSeckillId() != null) { // 秒杀单商品项处理
  23.             Long seckillId = orderItem.getSeckillId();
  24.             SeckillService seckillService = SpringContextUtil.getBean(SeckillService.class);
  25.             if (!seckillService.addStock(seckillId)) {
  26.                 throw new BusinessException("秒杀商品货品库存增加失败");
  27.             }
  28.         } else { // 普通单商品项处理
  29.             Long goodsId = orderItem.getGoodsId();
  30.             Integer goodsCount = orderItem.getGoodsCount();
  31.             if (!goodsDao.addStock(goodsId, goodsCount)) {
  32.                 throw new BusinessException("秒杀商品货品库存增加失败");
  33.             }
  34.         }
  35.     }
  36.     // 4. 返还优惠券
  37.     couponService.releaseCoupon(orderId);
  38.     log.info("---------------订单orderId:{},未支付超时取消成功", orderId);
  39. }
复制代码
采用编程式事务对其优化,代码如下:
  1. @Resource
  2. private PlatformTransactionManager platformTransactionManager;
  3. @Resource
  4. private TransactionDefinition transactionDefinition;
  5. public void doUnPaidTask(Long orderId) {
  6.     // 启用编程式事务
  7.     // 1. 在开启事务钱查询订单是否存在
  8.     Order order = orderService.getById(orderId);
  9.     if (order == null) {
  10.         throw new BusinessException(String.format("订单不存在,orderId:%s", orderId));
  11.     }
  12.     if (order.getOrderStatus() != OrderStatusEnum.ORDER_PRE_PAY.getOrderStatus()) {
  13.         throw new BusinessException(String.format("订单状态错误,order:%s", order));
  14.     }
  15.     // 2. 开启事务
  16.     TransactionStatus transaction = platformTransactionManager.getTransaction(transactionDefinition);
  17.     try {
  18.         // 3. 设置订单为已取消状态
  19.         order.setOrderStatus((byte) OrderStatusEnum.ORDER_CLOSED_BY_EXPIRED.getOrderStatus());
  20.         order.setUpdateTime(new Date());
  21.         if (!orderService.updateById(order)) {
  22.             throw new BusinessException("更新数据已失效");
  23.         }
  24.         // 4. 商品货品数量增加
  25.         LambdaQueryWrapper<OrderItem> queryWrapper = Wrappers.lambdaQuery();
  26.         queryWrapper.eq(OrderItem::getOrderId, orderId);
  27.         List<OrderItem> orderItems = orderItemService.list(queryWrapper);
  28.         for (OrderItem orderItem : orderItems) {
  29.             if (orderItem.getSeckillId() != null) { // 秒杀单商品项处理
  30.                 Long seckillId = orderItem.getSeckillId();
  31.                 SeckillService seckillService = SpringContextUtil.getBean(SeckillService.class);
  32.                 RedisCache redisCache = SpringContextUtil.getBean(RedisCache.class);
  33.                 if (!seckillService.addStock(seckillId)) {
  34.                     throw new BusinessException("秒杀商品货品库存增加失败");
  35.                 }
  36.                 redisCache.increment(Constants.SECKILL_GOODS_STOCK_KEY + seckillId);
  37.                 redisCache.deleteCacheSet(Constants.SECKILL_SUCCESS_USER_ID + seckillId, order.getUserId());
  38.             } else { // 普通单商品项处理
  39.                 Long goodsId = orderItem.getGoodsId();
  40.                 Integer goodsCount = orderItem.getGoodsCount();
  41.                 if (!goodsDao.addStock(goodsId, goodsCount)) {
  42.                     throw new BusinessException("秒杀商品货品库存增加失败");
  43.                 }
  44.             }
  45.         }
  46.         // 5. 返还优惠券
  47.         couponService.releaseCoupon(orderId);
  48.         // 6. 所有更新操作完成后,提交事务
  49.         platformTransactionManager.commit(transaction);
  50.         log.info("---------------订单orderId:{},未支付超时取消成功", orderId);
  51.     } catch (Exception e) {
  52.         log.info("---------------订单orderId:{},未支付超时取消失败", orderId, e);
  53.         // 7. 发生异常,回滚事务
  54.         platformTransactionManager.rollback(transaction);
  55.     }
  56. }
复制代码
可以看到采用编程式事务后,我们将查询逻辑排除在事务之外,减小了其影响范围,也就提升了性能,在高并发场景下,性能优先的场景,我们甚至可以考虑不适用事务
三. 客户端海量日志上报优化

线上项目客户端,采用tcp协议与日志采集服务建立连接,上报日志数据。业务高峰期下,会有同时成千个客户端建立连接实时上报日志数据
如上场景,高峰期下,对日志采集服务会造成不小的压力,处理服务处理不当,会造成高峰期下,服务卡顿、CPU占用过高、内存溢出等。
这里给出海量日志高并发下优化点:

如上三种方案:大家可以结合自身项目实际体量选择
对上报后的日志如果要再发送给其他服务,推荐是对其进行压缩处理,避免消耗过多网络带宽以及最终数据落库选型:
最后,附博主 github 地址:https://github.com/wayn111
欢迎大家点赞、收藏、转发,你的支持将是博主更文的动力

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4