zoukankan      html  css  js  c++  java
  • 如何做一个防重设计

    https://segmentfault.com/a/1190000038424677

    前言

    在业务设计中防重设计是一个关键点,以接口设计为例,防重就是防止接口被多次调用而产生脏数据,比如支付订单出现重复支付,所以说防重至关重要,在如何防重之前我们首先看一下是如何出现重复请求的。

    何时出现多次调用

    多次调用接口的出现有主观原因比如:人为的重复请求攻击,用户的误操作等;也有客观原因比如:为了健壮性进行超时重试;

    • 重复请求攻击

    对于这种恶意攻击,其实已经属于安全范畴了,我们可以通过黑名单+限流来处理,下一步再考虑防重处理;

    • 用户的误操作

    比如用户在界面点击提交按钮,因为手误出现多次点击提交,这种情况客户端可以做一些处理,减少用户的误操作,比如提交完按钮变灰等;当然提供方的防重处理也是必不可少的;

    • 超时重试

    接口调用方为了保证系统的健壮性,往往会做一些重试处理,比如各种RPC框架都已经帮我们内置了容错机制,提供方需要做好防重;

    如何做好防重处理,其实核心就是保证多个相同的请求只有一次被执行,或者说多次调用和一次调用产生的效果是一样的,也就是我们经常说的要保证幂等性;

    何时需要防重

    所有的操作说到底都是增删改查,其实我们真正需要做防重处理的,更重要的是增加和修改;查询和删除本身执行一次和多次,产生的效果是一样的,有天然幂等性,虽然说有天然幂等性,但是查询和删除本身也是要消耗资源的,如果能防止重复执行,也能节省资源;
    增加和修改是必须要做防重的,增加可以以下单为例,修改可以以更新库存为例,如果没有做好防重后果是非常严重的;下面具体看看都有哪些防重的措施;

    如何防重

    上面说到主要是针对增加和修改需要做好防重处理,当然针对增加和修改其实是有不同的防重措施,也有统一的方式,下面分别介绍;

    统一防重

    • token机制

    服务器需要提供获取token机制的服务,这样每次客户端请求的时候先获取token,服务器端会将token保存在redis中;客户端发送请求的时候会带上token,这样服务器端可以拿到token直接去redis中做删除处理,根据返回值判断是否成功:

    localhost:0>get token
    "111"
    localhost:0>del token
    "1"
    localhost:0>del token
    "0"

    通过redis存放token,这样在分布式环境下也能很好的工作;

    • 防重key

    根据请求参数生成md5密文,然后用此密文作为key存入redis中,可以通过使用setnx命令来保证只有一个能保存成功;

    key = MD5.md5("param1="+param1+"&param2="+param2...)
    
    localhost:0>setnx key 1
    "1"
    localhost:0>setnx key 1
    "0"
    • 防重表

    可以利用表的唯一索引约束,可以使用类似防重key作为唯一索引字段,多次请求过来只会有一个插入成功,为了防止防重表数据过多,可以启动一个定时器定时清理;

    以上几种方式其实和具体的业务关系不大,可以适用大部分场景;而且通过redis或者数据库,以及原子性操作来保证在分布式环境下也可以很好的运行;

    插入防重

    • select+insert

    插入数据最先想到就是先检查有没有,然后在插入,但是这样明显存在两个操作,不是原子性操作,单节点下还能通过锁来解决,分布式环境下就需要用到分布式锁来保证原子性了;当然也可以结合其他方式一起使用,比如下面的唯一主键机制;

    • 唯一主键

    这种方式其实就是不直接使用数据库的自增主键了,使用分布式id算法生成,这样在插入数据的时候就可以通过唯一主键来进行约束,保证只会有一条成功;

    • 回滚机制

    有些业务其实是有正向流程和逆向流程,比如支付订单,在接收到银行返回的支付成功通知时判断订单的状态,如果已经支付成功,可以直接走退款流程;

    更新防重

    • 乐观锁方式

    乐观锁常被用在更新场景中,如下面通过版本号的方式来实现:

    update table_name
        sale = sale + 1,
        version = version + 1,
    WHERE id = #{id}
    AND version = #{version}
    • 状态机乐观锁

    在相关订单的业务中,很多都会涉及到状态机,状态是流转的,有了上一个状态才会有下一个状态,比如常见的购物订单包括:提交未支付,支付成功,待发货,发货中,已签收等,每个状态都需要前置状态;

    update table_name
       set  status = 下一个状态
       where  id = #{id} 
       and status = #{status} 

    总结

    防重的方法很多,我们往往需要更加自己的业务做相关的选择,不同的业务,不同的业务量,不同的容忍程度都会影响我们如何去做防重;每种方式也不是都是独立存在的,有时候往往需要多种方式整合起来。

  • 相关阅读:
    使用C39HrP48DhTt字體生成條型碼
    GUI設計禁忌
    music
    AADL的四种经典设计模式
    Model to Text工具Acceleo使用教程(七)——模板服务
    Model to Text工具Acceleo使用教程(二)——体系结构
    Model to Text工具Acceleo使用教程——背景知识
    P/NP/NPC/NPhard概念的图形解释
    哥德巴赫猜想穷举验证算法及实现
    Model to Text工具Acceleo使用教程(五)——模板服务
  • 原文地址:https://www.cnblogs.com/linus-tan/p/14704851.html
Copyright © 2011-2022 走看看