幂等性

幂等性要求:记录无重复, 并且多次请求的返回状态值相同

出现场景

  • 在POSTform表单时, 保存按钮不小心快速点了两次, 表中产生了两条重复的数据, 只是id不一样。
  • 为了解决接口超时问题, 通常会引入了超时重试机制。第一次请求接口超时了, 请求方没能及时获取返回结果(此时有可能已经成功了), 为了避免返回错误的结果(这种情况不能直接返回失败), 于是会对该请求重试几次, 这样也会产生重复的数据。
  • mq消费者在读取消息时, 有时候会读取到重复消息, 如果处理不好, 也会产生重复的数据。
    • 生产者重复发送
      • 生产者发送消息的时候, 必须等待Broker的响应。
      • 如果Broker的响应由于网络波动一时没有收到, 那么当超出等待时间之后, 通常生产者会因为没有收到响应而认为这条消息发送失败, 又会重复发送一次。
      • 最终导致两条消息都发送成功了, 消息队列有两条重复的消息, 这样就导致了消息的重复。
    • 消费者重复消费
      • 消费者消费消息的时候, 如果业务逻辑已经走完了, 那么就需要提交offset
      • 如果恰好此时消费者挂了, 那么被消费但是没有被提交的消息将被认为是消费失败
      • 此消息会被分发到其他消费者上, 导致一条消息被重复消费。

分类

  • GET请求不会改变记录状态, 因此天然是幂等的
  • POST请求可能产生重复记录
  • PUT请求
    • 如果只是对不同字段赋值字面量, 天然是幂等的, 如UPDATE t_user SET age=18 WHERE id=1
    • 否则, 可能产生非法结果, 如UPDATE t_user SET age = age + 1 WHERE id=1UPDATE t_user SET age=18 WHERE age=17
  • DELETE请求
    • 对于服务端, 多次删除请求只会删除固定记录, 结果是幂等的
    • 对于客户端, 由于首次删除已经成功, 后续多次删除表中已无记录, 会返回内部错误

对策

  • 悲观锁:
    • 首先加行级锁, 然后利用GET的幂等性进行判断, 如SELECT * FROM t_user WHERE id=1 FOR UPDATE
      • 一定要注意, id必须是主键或唯一索引, 否则会升级为表级锁, 数据量大, 非死即伤!!
    • 如果不存在记录, 则POST
    • 否则, PUT
    • 最后释放锁
  • 乐观锁:
    • 利用版本号机制, 多次PUT请求只有一次能真正执行, 只需返回正常即可
  • 分布式锁
    • 全局唯一索引
      • 针对POST请求, 对某字段添加唯一索引, 之后如果有重复插入, 只需捕获DuplicateKeyExceptionMySQLIntegrityConstraintViolationException(spring框架)异常并finally返回正常即可
    • 建全局防重表
      • 针对DELETE, 可以新建防重表, 每次DELETE都将记录的id插入防重表, 之后捕获异常并返回正常即可
    • 基于redis、zookeeper
posted @ 2022-08-20 19:32  Blazer96  阅读(20)  评论(0)    收藏  举报