破解消息重复迷局:确保交易唯一性的高级策略

前言

针对消息重复消费和重复下单等问题,可以从多个层面进行解决,包括消息队列的设计、消费者端的处理逻辑、数据库层面的约束以及系统架构的优化等。以下是一些具体的解决方案:

解决消息重复消费

  1. 实现消费幂等性
    • 定义幂等性:无论消息被消费多少次,系统的状态都应该保持一致,即不会产生副作用或影响系统的正确性。
    • 实现方式
      • 对于消息的消费结果,可以通过唯一标识符(如消息ID)进行判断,确保在重复消费时不会产生副作用。
      • 对于需要记录状态的操作,可以使用分布式锁来保证同一时刻只有一个节点可以执行此操作。
      • 对于数据库操作,可以使用唯一索引来防止重复插入重复数据。
  2. 消息去重
    • 在消费者端去重:使用缓存(如Redis)或数据库来记录已经处理过的消息ID,避免重复消费。
    • 在消息队列端去重:通过在消息队列中记录已发送的消息ID(如使用map或set结构),避免重复推送相同消息。
  3. 确保消息确认机制
    • 消费者在成功处理消息后,应及时向消息队列发送确认回执(ack),告知消息已被消费,消息队列可以删除或标记已消费的消息。
  4. 设置消息有效期
    • 在消息中设置有效期,确保消息在一定时间内被消费,超过有效期的消息将被丢弃,从而避免过期消息被重复消费。

解决重复下单

  1. 前端控制
    • 按钮置灰:在用户点击下单按钮后,立即将按钮置灰或禁用,防止用户重复点击。
    • 提示信息:在用户尝试重复下单时,显示提示信息告知用户订单已提交,避免重复操作。
  2. 后端控制
    • 请求唯一ID+数据库唯一索引约束
      • 当用户进入订单提交界面时,调用后端获取请求唯一ID,并将ID值存储在页面中。
      • 用户点击提交按钮时,后端检查这个唯一ID是否已使用,如果已使用则提示重复提交。
      • 将唯一ID存入业务表中,并设置该字段为唯一索引,从数据库层面防止重复提交。
    • 分布式锁+token
      • 使用Redis等分布式锁服务,对请求ID在限定时间内尝试加锁。
      • 如果加锁成功,则继续后续流程;如果加锁失败,则提示用户重复提交。
      • 锁的时间范围应根据实际业务需求设置,避免过长或过短导致的用户体验问题。
  3. 接口幂等性设计
    • 设计接口时考虑幂等性,确保无论请求被发送多少次,对系统状态的影响都与执行一次相同。
    • 可以通过状态机、业务流水表等方式进行重复操作的判断。
  4. 系统架构优化
    • 在高并发场景下,优化系统架构以提高处理能力和响应速度,减少因超时重试等原因导致的重复下单问题。

解决方案

token验证策略:
import redis.clients.jedis.Jedis;  
import java.util.UUID;  
  
public class RedisToken {  
    public static void main(String[] args) {  
        // 连接到Redis服务器  
        Jedis jedis = new Jedis("localhost");  
  
        try {  
            // 生成一个UUID作为token  
            String token = UUID.randomUUID().toString();  
  
            // 使用setex命令设置token,60*60表示过期时间为1小时(秒为单位)  
            // 注意:setex的参数需要是字符串类型,并且是用英文的逗号或空格分隔  
            jedis.setex(token, 60 * 60, "1"); // 假设"1"是用来标记token的值或状态  
  
            // 输出token  
            System.out.println("Token: " + token);  
        } finally {  
            // 关闭Jedis连接  
            jedis.close();  
        }  
    }  
}

在生成了token之后,后续请求就可以校验这个token是否有效,并且确保只能用一次:

import redis.clients.jedis.Jedis;  
  
public class RedisTokenVerify {  
    public void verify(String token) {  
        // 使用try-with-resources语句自动关闭Jedis连接  
        try (Jedis jedis = new Jedis("localhost")) {  
            String value = jedis.get(token);  
            if (value != null) {  
                // 删除token  
                jedis.del(token);  
                // 校验成功,可以添加日志或执行其他操作  
                System.out.println("Token verified and deleted successfully.");  
                // 也可以考虑返回一个布尔值或抛出一个自定义的成功异常  
            } else {  
                // 校验失败,可以添加日志或执行其他操作  
                System.out.println("Token verification failed. Token not found.");  
                // 同样,也可以返回一个布尔值或抛出一个自定义的异常  
            }  
        } catch (Exception e) {  
            // 处理与Redis交互时可能发生的任何异常  
            e.printStackTrace();  
            // 可以选择抛出或记录这个异常,或者根据应用需求进行其他处理  
        }  
    }  
}

这是一个token校验的简单实现,但是这个逻辑存在一个问题,那就是如果高并发场景,可能会导致多个线程同时,导致token同时校验通过。要解决这个问题,有两个办法,第一个是把get和del放到一个事务中,或者用lua脚本,或者用分布式锁也可以。

  • 23
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值