栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

幂等性概念及实现方案

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

幂等性概念及实现方案

概念

幂等性:就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。

以支付为例,用户购买商品后支付,支付扣款成功,但是返回结果的时候网络中断或者异常,用户没有看到支付成功的提示,但此时钱已经扣了,用户再次点击按钮,此时进行第二次扣款,返回结果成功,用户查询余额发现多扣钱了,流水记录也变成了两条。

在增删改查4个操作中:查询对于结果是不会有改变的;删除只会进行一次,用户多次点击产生的结果一样;修改在大多场景下结果一样;增加在重复提交的场景下会出现。

支付中的幂等也可以称之为冲正,保证客户端与服务端的交易一致性,避免多次扣款。

常见的解决幂等性的方式有以下:
  1. 唯一索引:保证插入的数据只有一条;
  2. token机制:每次接口请求前先获取一个token,然后在下次请求的时候在请求的header体中加上这个token,后台进行验证,如果验证通过删除token,下次请求再次判断token
  3. 悲观锁或者乐观锁,悲观锁可以保证每次for update的时候其他sql无法update数据(在数据库引擎是innodb的时候,select的条件必须是唯一索引,防止锁全表)
  4. 先查询后判断,首先通过查询数据库是否存在数据,如果存在证明已经请求过了,直接拒绝该请求,如果没有存在,就证明是第一次进来,直接放行。

比如可以采用以下方式保证支付系统支付保证幂等:

  1. 查询订单支付状态
  2. 如果已经支付,直接返回结果
  3. 如果未支付,则支付扣款并且保存流水
  4. 返回支付结果
  5. 如果步骤4通信失败,用户再次发起请求,那么最终结果还是一样的
redis实现幂等性

  • 接口
public interface TokenService {
    
    String createToken();

    
    boolean checkToken(HttpServletRequest httpServletRequest) throws Exception;
}
  • 实现类
@Service
public class TokenServiceImpl implements TokenService {
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    private static final String TOKEN_PREFIX = "token_prefix";
    private static final String TOKEN_NAME = "check_token";

    @Override
    public String createToken() {
        String str = UUID.randomUUID().toString();
        StringBuilder token = new StringBuilder();
        try {
            token.append(TOKEN_PREFIX).append(str);
            stringRedisTemplate.opsForValue().set(token.toString(), token.toString(), 10000L);
            boolean notEmpty = StringUtils.isNotBlank(token.toString());
            if (notEmpty) {
                return token.toString();
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

    @Override
    public boolean checkToken(HttpServletRequest request) throws Exception {
        String token = request.getHeader(TOKEN_NAME);
        if (StringUtils.isBlank(token)) {
            token = request.getParameter(TOKEN_NAME);
            if (StringUtils.isBlank(token)) {
                throw new OperationException("token不存在");
            }
        }
        String isExist = stringRedisTemplate.opsForValue().get(token);
        if (StringUtils.isBlank(isExist)){
            throw new OperationException("token不存在");
        }
        Boolean remove = stringRedisTemplate.delete(token);
        if (!remove) {
            throw new OperationException("token不存在");
        }
        return true;
    }
}

如果是服务之间的调用。比如dubbo,fegin接口之间的调用。可以将Token作为参数传给服务端进行校验。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/684363.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号