接口幂等性

什么是幂等性

  • 幂等性:f(f(x))=f(x)
  • 幂等元素运行多次,还等于它原来的运行结果
  • 在系统中,一个接口运行多次,与运行一次的效果是一致的

什么情况下需要幂等性

  • 重复提交
  • 接口重试
  • 前端操作抖动

业务场景:

  • 多次点击提交订单,后台应只生成一个订单
  • 支付时,由于网络问题重发,应该只扣一次前

幂等性的核心思想:

  • 通过唯一的业务单号保证幂等

    非并发情况下,查询业务单号有没有操作过,没有则执行操作
    并发情况下,整个操作过程加锁
    

增删改查幂等性

  • select操作:不会对业务数据有影响,天然幂等

  • delete操作:第一次已经删除,第二次也不会有影响

  • update操作:更新操作传入数据版本号,通过乐观所实现幂等性

    update table set age=10    幂等
    update table set age=age+1 非幂等
    
  • insert操作:此时没有唯一业务单号,使用token保证幂等性

  • 混合操作:找到操作的唯一业务单号,有则使用分布式锁,没有可以通过Token保证幂等

update操作的幂等性

  • 根据唯一业务号去更新数据的情况

    使用乐观锁与update行锁,保证幂等
    
    用户查询出要修改的数据,系统将数据返回页面,将数据版本号放入隐藏域
    用户修改数据,点击提交,将版本号一起提交给后台
    后台使用版本号作为更新条件
    update set version=version+1,xxx=${xxx}  where id=xxx and
    version=${version}
    
  • 更新操作没有唯一业务号

    可使用token机制
    

insert操作的幂等性

  • 有唯一业务号的insert操作,例如:秒杀,商品id+用户id

    通过分布式锁,保证接口幂等。业务完成之后,不进行锁释放,
    让其过期自动释放
    
  • 没有唯一业务号的insert操作,比如,用户注册,点击多次

     使用token机制,保证幂等,进入注册页时,后台统一生成token,
     返回前台隐藏域中。用户点击提交时,将token一同传入后台,
     使用token获取分布式锁,完成insert操作,业务完成之后,不进行
     锁释放,让其过期自动释放
    

update幂等性代码实现
首先查询数据,然后将版本号查询放置在页面的隐藏域中,更新的时候,将版本号一起提交后台,只有页面的版本号和数据库中的版本号一致,才可以修改成功。同时,版本号+1。前端多次点击,到数据库更新的时候会有行锁,变成串行执行,第一次更新的时候版本号一致,更新成功,其他的更新,由于数据库中的版本号已经发生变化,更新失败。

修改页面

<form style="margin: 0 auto 0 300px" method="post" action="/user/updateUser">
        <input type="hidden" name="id" th:attr="value=${user.id}" >
        <input type="hidden" name="version" th:attr="value=${user.version}">
        用户名:<input type="text" name="username" th:attr="value=${user.username}"> <br>
        性别:
        <select name="sex">
            <option value="0" th:attr="selected=${user.sex == 0}">未填option>
            <option value="1" th:attr="selected=${user.sex == 1}">option>
            <option value="2" th:attr="selected=${user.sex == 2}">option>
        select>
        <br>
        年龄:<input type="number" name="age" th:attr="value=${user.age}"><br>

        更新次数:<span th:text="${user.updateCount}">span> <br>

        <button type="submit">更新button>
    form>

更新控制层

@RequestMapping("updateUser")
    public String updateUser(User user,String token) throws Exception {
        Thread.sleep(5000);
        System.out.println("更新用户");
        userService.updateUser(user);
        return "redirect:/user/userList";
    }

数据库语句

    <update id="updateUser">
      update t_user
      <set>
          username = #{username,jdbcType=VARCHAR},
          sex = #{sex,jdbcType=INTEGER},
          age = #{age,jdbcType=INTEGER},
          update_count = update_count + 1,
          version = version+1
      set>
      where id = #{id,jdbcType=INTEGER} and version=#{version,jdbcType=INTEGER}
    update>

前台点击多次更新,页面更新次数为1
接口幂等性_第1张图片
insert幂等性实现
用户名为唯一业务号
控制层

    @RequestMapping("insertUser")
    public String insertUser(User user) throws Exception {

        Thread.sleep(5000);
        System.out.println("添加用户");
        userService.insertUser(user);
        return "redirect:/user/userList";
    }

service层

    public int insertUser(User user) throws Exception {
        InterProcessMutex lock = new InterProcessMutex(zkClient, "/"+user.getUsername());
        // 尝试获取分布式锁
        boolean isLock = lock.acquire(30, TimeUnit.SECONDS);
        // 获取锁成功,则插入数据,获取失败,则直接返回0
        if (isLock){
            log.info(user.getUsername()+" 获取锁成功");
            return userMapper.insertSelective(user);
        }else{
            log.info(" 获取锁失败");
        }
        return 0;
    }
<insert>
	insert into t_user id,username,sex,age
	<trim prefix="values (" suffix=")" suffixOverrides=",">
	 #{id,jdbcType=INTEGER},#{username,jdbcType=VARCHAR},
	   #{sex,jdbcType=INTEGER},#{age,jdbcType=INTEGER}
	trim>
insert>

前台点击4次提交按钮,只有最先到达的请求获取锁成功,然后成功插入数据,其他三次获取锁失败。
接口幂等性_第2张图片
由于接口幂等性,只会插入一条数据
接口幂等性_第3张图片

当没有唯一业务号的时候,

    public int insertUser(User user,String token) throws Exception {
        InterProcessMutex lock = new InterProcessMutex(zkClient, "/"+token);
        boolean isLock = lock.acquire(30, TimeUnit.SECONDS);
        if (isLock){
            log.info(user.getUsername()+" 获取锁成功");
            return userMapper.insertSelective(user);
        }else{
            log.info(" 获取锁失败");
        }
        return 0;
    }

你可能感兴趣的:(一步一步成为架构师)