[优化]上下游交互策略

news2024/10/2 10:29:12

书接上文:https://blog.csdn.net/weixin_43303530/article/details/127227147?spm=1001.2014.3001.5502,为满足产品提的在24小时内能重试尽量去重试,不计较重试的次数的要求,在第三方电子卡系统无法提升接口并发数量的情况下,优化用户领取红包策略;

1、业务与技术背景

1、为了促进公司商城项目的推广与引流,项目组引入了第三方的电子卡系统,该电子卡系统可以通过生成相应的红包类型,运营人员可以通过该系统将红包发放给需要引流的客户来促进消费与推广。
2、由于目前该电子卡发红包行为只能在小程序端触发,无法从后台进行派发和控制,希望礼品卡可以实现运营端应用,业务可以像发优惠券那样派发红包,实现多样化的用户运营。
3、由于目前该电子卡每个红包只能有一百个用户领取,而业务希望开发可以在后台系统中增加用户列表excel文件的解析,对列表中的用户去领取红包,这就不可避免的需要发送大量的领取红包请求到该电子卡系统。
4、为了防止由于不稳定的因素(如网络波动、流量峰值过高等)在excel文件解析后直接调用该电子卡系统导致用户领取红包的请求失败,导致数据丢失,将请求的消息放入mq队列。
5、由于该电子卡的系统性能很差,请求高频率的失败,所以需要使用mq的消息重试,但由于上文中采用Spring RetryTemplate做RabbitMQ消息重试机制发现可能会造成队列堵塞,且同样避免不了第三方接口性能差的问题,大批量的请求可能导致第三方接口堵塞。

2、解决思路

  • 1、降低接口请求的频率,防止大量请求执行至第三方导致接口堵塞;
  • 2、短时间内大批量失败不再请求第三方接口;
  • 3、对于短时间内已经执行失败的请求不做处理,采取补偿;
  • 4、当接口请求成功后尝试补偿。

3、解决方案

  • 1、降低请求第三方接口频率:

    • mq消费者每次消费后进行一定时间的sleep

    • 降低最大消费者数量
      在这里插入图片描述
  • 2、采用feign调用,增加hystrix熔断(熔断配置已省略):
    在这里插入图片描述
    在这里插入图片描述

  • 3、失败的请求保存记录mongo:
    在这里插入图片描述

  • 3.1、mongo表结构:

/**
 * 批量领红包重试
 * @author liurui
 * @date 2022/8/16
 */
@Document(collection = "ReceiveRedPacketRetryTask")
public class ReceiveRedPacketRetryTask {

    @Id
    private String id;

    /**
     * 调用卡系统入参
     */
    private BatchReceiveRedPacketInput batchReceiveRedPacketInput;

    /**
     * 创建任务id
     */
    private String prepareRedPacketTaskId;

    /**
     * 上传发放红包任务id
     */
    private String uploadUserTaskId;

    /**
     * 状态:0:待重试,1:重试成功,2:重试失败(可以增加一个进行中的状态,但此处已经做了分布式锁,一个时间只存在一个重试任务,所以没有增加)
     */
    private Integer status;

    /**
     * 重试次数
     */
    private Integer retryCount;

    /**
     * 创建人ID
     */
    private Long createUserid;

    /**
     * 创建时间
     */
    private Long createTime;

    /**
     * 最后修改人ID
     */
    private Long updateUserid;

    /**
     * 最后修改时间
     */
    private Long updateTime;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public BatchReceiveRedPacketInput getBatchReceiveRedPacketInput() {
        return batchReceiveRedPacketInput;
    }

    public void setBatchReceiveRedPacketInput(BatchReceiveRedPacketInput batchReceiveRedPacketInput) {
        this.batchReceiveRedPacketInput = batchReceiveRedPacketInput;
    }

    public String getPrepareRedPacketTaskId() {
        return prepareRedPacketTaskId;
    }

    public void setPrepareRedPacketTaskId(String prepareRedPacketTaskId) {
        this.prepareRedPacketTaskId = prepareRedPacketTaskId;
    }

    public String getUploadUserTaskId() {
        return uploadUserTaskId;
    }

    public void setUploadUserTaskId(String uploadUserTaskId) {
        this.uploadUserTaskId = uploadUserTaskId;
    }

    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }

    public Integer getRetryCount() {
        return retryCount;
    }

    public void setRetryCount(Integer retryCount) {
        this.retryCount = retryCount;
    }

    public Long getCreateUserid() {
        return createUserid;
    }

    public void setCreateUserid(Long createUserid) {
        this.createUserid = createUserid;
    }

    public Long getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Long createTime) {
        this.createTime = createTime;
    }

    public Long getUpdateUserid() {
        return updateUserid;
    }

    public void setUpdateUserid(Long updateUserid) {
        this.updateUserid = updateUserid;
    }

    public Long getUpdateTime() {
        return updateTime;
    }

    public void setUpdateTime(Long updateTime) {
        this.updateTime = updateTime;
    }
}

  • 4、补偿机制:

原意是考虑当有请求成功发送时就进行补偿,但是会存在一个问题,就是如果最后一大批数据都请求失败了,就不会有请求去唤醒补偿了或者说要等待下一次发红包请求才有可能进行补偿,对此,采取了请求成功时进行补偿和job补偿相结合的机制,又考虑到并发场景的问题,虽然第三方的电子卡系统做了幂等,但是为了减免压力,进行补偿的时候做了一次redis分布式锁,没有获取到锁,说明有补偿任务在进行,那么就继续之前的补偿任务不做处理,没有就进行补偿

  • 4.1、请求成功采取异步线程进行补偿(可考虑采用线程池,优化cpu资源利用):在这里插入图片描述
  • 4.2、xxljob每隔半个小时执行一次补偿任务:
/**
 * 礼品卡-批量领红包重试
 * @author liurui
 * @date 2022/8/1
 */
@Component
public class RetryReceiveRedPacketTaskJob extends IJobHandler {
    private final Logger logger = LogUtils.getLogger(this.getClass());

    @Resource
    private UserReceiveDetailWriteService userReceiveDetailWriteService;

    @Override
    @XxlJob("retryReceiveRedPacketTaskJob")
    public ReturnT<String> execute(String param) throws Exception {
        ReturnT<String> result = new ReturnT<>();
        long beginTime = System.currentTimeMillis();
        try {
            logger.info("retryReceiveRedPacketTaskJob开始执行");
            XxlJobLogger.log("retryReceiveRedPacketTaskJob开始:"+ beginTime);
            result.setCode(ReturnT.SUCCESS_CODE);
            userReceiveDetailWriteService.executeRetryTask();
            result.setMsg("执行成功");
        } catch (Exception e) {
            logger.error("retryReceiveRedPacketTaskJob执行失败",e);
            result.setCode(ReturnT.FAIL_CODE);
            result.setMsg("retryReceiveRedPacketTaskJob执行失败" + e);
        } finally {
            logger.info("retryReceiveRedPacketTaskJob结束执行");
            XxlJobLogger.log("retryReceiveRedPacketTaskJob结束:"+ (System.currentTimeMillis() - beginTime));
        }
        return result;
    }
}

xxljob相关信息:
在这里插入图片描述
在这里插入图片描述

  • 4.3、补偿逻辑,此处采用redisson做redis分布式锁:
public void executeRetryTask() {
    RLock rLock = redisson.getLock(GiftCardCenterConstant.GIFTCARD_RETRYRECEIVE_REDPACKET_LOCK);
    try {
        boolean lockSuccess = rLock.tryLock(0, TimeUnit.SECONDS);
        if (lockSuccess) {
            int pageNum = 1;
            // 一次查询20条
            int pageSize = 20;
            List<ReceiveRedPacketRetryTask> list;
            // 分页获取数据
            do {
                list = receiveRedPacketRetryTaskReadService.getReceiveRedPacketRetryTaskByPageParams(pageNum, pageSize);
                // 重新请求
                list.forEach(this::retryBatchReceiveRedPacket);
                pageNum++;
            } while (CollectionUtils.isNotEmpty(list));
        }
    } catch (Exception e) {
        logger.error("批量领红包重试发生异常", e);
    } finally {
        if (rLock.isLocked()) {
            try {
                rLock.unlock();
            } catch (Exception e) {
            }
        }
    }
}

mongo待补偿记录查询

public List<ReceiveRedPacketRetryTask> getReceiveRedPacketRetryTaskByPageParams(int pageNum, int pageSize) {
    Query query = new Query(Criteria.where("status").ne(1));
    query.with(PageRequest.of(pageNum - 1, pageSize));
    query.with(Sort.by(Sort.Direction.ASC, "status","retryCount"));
    query.with(Sort.by(Sort.Direction.DESC, "createTime"));
    return mongoTemplate.find(query, ReceiveRedPacketRetryTask.class);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/374373.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

电脑C盘空间不足?这样做就行了!

一般来说&#xff0c;电脑C盘都是系统盘&#xff0c;它的稳定关系到系统能否正常运行&#xff0c;但是很多朋友的C盘却总是红色的&#xff0c;这也就意味着C盘储存空间不足了&#xff0c;那么我们就需要进行以下操作&#xff0c;来让C盘重新拥有储存空间。方案一&#xff1a;运…

BLE Mesh蓝牙协议学习记录

BLE Mesh蓝牙协议学习 文章目录BLE Mesh蓝牙协议学习前言概述一、蓝牙技术整体框架二、经典蓝牙和低功耗蓝牙mesh协议架构图承载层&#xff08;Bearer Layer&#xff09;网络层&#xff08;Network Layer&#xff09;底层传输层&#xff08;Lower Transport Layer&#xff09;上…

JAVA连接数据库——JDBC的简单使用

JDBC即Java数据库连接.用来实现Java程序对数据库增删查改。 为了对接Java程序和数据库&#xff0c;java.sql提供了很多api包含在java.sql和javax.sql里面 结构: DriverManager接口: 每一个数据库的驱动程序都必须去到DriverManager注册&#xff0c;生成一个Connection Conn…

电商平台的促销活动如何抵御大流量的ddos攻击

每一次活动大促带来的迅猛流量&#xff0c;对技术人而言都是一次严峻考验。如果在活动期间遭受黑产恶意 DDoS 攻击&#xff0c;无疑是雪上加霜。电商的特性是业务常态下通常不会遭受大流量 DDoS 攻击&#xff0c;且对延迟敏感&#xff0c;因此只需要在活动期间按需使用 DDoS 防…

【第五章 AOP概述,底层原理,AOP术语,切入点表达式,AOP操作(基于注解方式,基于xml配置文件)】

第五章 AOP概述&#xff0c;底层原理&#xff0c;AOP术语&#xff0c;切入点表达式&#xff0c;AOP操作&#xff08;基于注解方式&#xff0c;基于xml配置文件&#xff09; 1.AOP概述&#xff1a; &#xff08;1&#xff09;什么是AOP&#xff1a; ①面向切面编程&#xff08;…

11-KMP算法

KMP算法是一个字符串匹配算法&#xff0c;总的意义是在给定的字符串A中利用优化的方法快速地找出字符串B的位置&#xff0c;相比于传统匹配算法&#xff0c;它能有效减少匹配时间&#xff0c;提高效率。 前缀和后缀 在我们看KMP算法前我们先考虑一个问题&#xff1a;假如我们…

基于框架的平台总线式开发

一、总线、设备、驱动 硬编码式的驱动开发带来的问题&#xff1a; 1. 垃圾代码太多 2. 结构不清晰 3. 一些统一设备功能难以支持 4. 开发效率低下 1.1 初期解决思路&#xff1a;设备和驱动分离 struct device来表示一个具体设备&#xff0c;主要提供具体设备相关的资源&am…

Telnet 基础实验2: SSH 实验

Telnet 基础实验2&#xff1a; SSH 实验 本实验只能使用 eNSP中 AR 系统的路由器做 拓扑图 SSH &#xff1a; Secure Shell 是一个网络安全协议&#xff0c;基本于 TCP 协议 22 端口传输数据&#xff0c;通过对网络数据的加密&#xff0c;使其能够在一个不安全的网络环境中&a…

网易新财报:游戏养家,教育维稳、音乐快走

配图来自Canva可画 随着互联网流量红利逐渐消退&#xff0c;互联网大厂们也告别高增长时代&#xff0c;逐渐进入稳定增长阶段。近两年&#xff0c;流量焦虑、业务失速等问题更是成为了一团浓雾&#xff0c;笼罩在互联网大厂周围。不过&#xff0c;面对所遭遇的难题&#xff0c…

力扣-换座位

大家好&#xff0c;我是空空star&#xff0c;本篇带大家了解一道简单的力扣sql练习题。 文章目录前言一、题目&#xff1a;626. 换座位二、解题1.正确示范①提交SQL运行结果2.正确示范②提交SQL运行结果3.正确示范③提交SQL运行结果4.正确示范④提交SQL运行结果5.其他总结前言 …

读书笔记——《富爸爸穷爸爸》

《富爸爸穷爸爸》&#xff0c;以前不屑读这种书。这种书就是那种走进书店放在门口展销位的成功学著作&#xff0c;一眼看上去没什么实在的内容&#xff0c;看上去很不靠谱&#xff0c;感觉就是骗一些社会底层又做着暴富梦的人来买的&#xff0c;但是由于自身原因或环境局限根本…

Spring Boot + Vue3 前后端分离 实战 wiki 知识库系统<二>---后端架构完善与接口开发

数据库准备&#xff1a; 在上一次Spring Boot Vue3 前后端分离 实战 wiki 知识库系统<一>---Spring Boot项目搭建已经将SpringBoot相关的配置环境给搭建好了&#xff0c;接下来则需要为咱们的项目创建一个数据库。 1、mysql的安装&#xff1a; 关于mysql的安装这里就…

【C语言每日一题】杨氏矩阵(源码以及改进源码)

【C语言每日一题】—— 杨氏矩阵&#x1f60e;&#x1f60e;&#x1f60e; 目录 &#x1f4a1;前言&#x1f31e;&#xff1a; &#x1f49b;杨氏矩阵题目&#x1f49b; &#x1f4aa; 解题思路的分享&#x1f4aa; &#x1f60a;题目源码的分享&#x1f60a; &#x1f4…

夜天之书 #73 Apache Pulsar 的社群指标

去年十一月&#xff0c;我成为了 Apache Pulsar[1] 社群 Committers 的一员。成为 Committer 之前和之后&#xff0c;我都积极参与了代码仓库上 Issue 和 Pull Request (PR) 的处理回应和评审。去年十二月期间&#xff0c;我把未解决的 Issue 和 PR 数量分别从接近 2000 个和 4…

STM32学习笔记-I2C通信协议

文章目录介绍&#xff1a;两种实现方式&#xff1a;I2C设备的常用连接方式&#xff1a;I2C协议时序&#xff1a;STM32硬件I2C框架图I2C外设通讯过程**I2C读写EEPROM**&#xff08;硬件I2C&#xff09;介绍&#xff1a; 两根通信线SCL&#xff08;时钟线&#xff09;、SDA&#…

C语言中的强制类型转换

强制类型转换是把变量从一种类型转换为另一种数据类型。例如&#xff0c;如果您想存储一个 long 类型的值到一个简单的整型中&#xff0c;您需要把 long 类型强制转换为 int 类型。您可以使用强制类型转换运算符来把值显式地从一种类型转换为另一种类型&#xff0c;如下所示&am…

“ChatGPT之父”Sam Altman:如何成功?

背靠微软&#xff0c;OpenAI能拳打谷歌&#xff0c;脚踢Meta&#xff0c;它背后的男人&#xff0c;必然不简单。 让我们来看一看&#xff0c;Sam Altman是如何一步步成长为今天这个搅动全世界的男人。 山姆奥特曼&#xff08;Sam Altman&#xff09; 成长和创业经历 在YC创始…

代码随想录【Day27】| 39. 组合总和、40. 组合总和 II、131. 分割回文串

39. 组合总和 题目链接 题目描述&#xff1a; 给定一个无重复元素的数组 candidates 和一个目标数 target &#xff0c;找出 candidates 中所有可以使数字和为 target 的组合。 candidates 中的数字可以无限制重复被选取。 说明&#xff1a; 所有数字&#xff08;包括 tar…

JavaScript 高级2 :构造函数和原型 d331702016e84f54b3594ae05e0eeac

JavaScript 高级2 &#xff1a;构造函数和原型 Date: January 16, 2023 Text: 构造函数和原型、继承、ES5中的新增方法 目标 能够使用构造函数创建对象 能够说出原型的作用 能够说出访问对象成员的规则 能够使用 ES5新增的一些方法 构造函数和原型 概述 在典型的 OOP 的…

MySQL之EXPLAIN

使用方法 查询结果分析 id&#xff1a;识别符 select_type&#xff1a;表示查询的类型 table&#xff1a;输出结果集的表 partitions&#xff1a;匹配的分区 type&#xff1a;表示表的连接类型 possible_keys&#xff1a;表示查询时&#xff0c;可能使用的索引 key&#xff1a…