高性能方法级幂等性

本文围绕高性能方法级幂等性展开,介绍幂等性概念,即多次请求结果一致无副作用,如支付场景。阐述方案设计思路,针对新增和更新业务,新增业务用唯一流水ID等作标识,结合分布式锁;修改业务以系统角色为例。还给出sys_role表结构及相关代码类。

高性能方法级幂等性

 

1.幂等性

就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。举个最简单的例子,那就是支付,用户购买商品使用约支付,支付扣款成功,但是返回结果的时候网络异常,此时钱已经扣了,用户再次点击按钮,此时会进行第二次扣款,返回结果成功,用户查询余额返发现多扣钱了,流水记录也变成了两条.

2.方案设计思路

在分布式系统条件下,需要针对新增和更新业务进行幂等性设计。对于新增业务来说,会出现一份数据多次保存的问题,因此针对这个问题,一般可以设计为除主键ID外,更复杂的操作以唯一流水ID或者业务Code作为一次请求的正确处理记录的唯一标识,例如:支付场景下,如果用户扣款请求成功后网络异常,再次发起同一流水号请求时,会返回记录存在(状态为已支付的订单)。因此需要每次新增之前需要判断此流水号是否存在,如存在即插入失败,此处:还需要控制分布式环境下多线程并发问题,因此需要结合分布式锁设计和幂等性设计。对于修改来说,具体例子以系统角色的添加和修改为例,代码在后文介绍。

3.解决方案代码

sys_role表结构:

 
	CREATE TABLE  sys_role   (
	   id  int(11) NOT NULL AUTO_INCREMENT,
	   code  varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色code',
	   name  varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色名',
	   create_time  datetime(0) DEFAULT NULL,
	   update_time  datetime(0) DEFAULT NULL,
	  PRIMARY KEY ( id ) USING BTREE,
	  INDEX  code ( code ) USING BTREE
	) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

幂等顶层接口ISuperService


	import com.baomidou.mybatisplus.core.conditions.Wrapper;
	import com.baomidou.mybatisplus.extension.service.IService;
	import com.central.common.lock.DistributedLock;
	
	/**
	 * service接口父类
	 *
	 * @author 
	 * @date 
	 */
	public interface ISuperService<T> extends IService<T> {
	    /**
	     * 幂等性新增记录
	     *
	     * @param entity       实体对象
	     * @param lock         锁实例
	     * @param lockKey      锁的key
	     * @param countWrapper 判断是否存在的条件
	     * @param msg          对象已存在提示信息
	     * @return
	     */
	    boolean saveIdempotency(T entity, DistributedLock lock, String lockKey, Wrapper<T> countWrapper, String msg);
	
	    /**
	     * 幂等性新增记录
	     *
	     * @param entity       实体对象
	     * @param lock         锁实例
	     * @param lockKey      锁的key
	     * @param countWrapper 判断是否存在的条件
	     * @return
	     */
	    boolean saveIdempotency(T entity, DistributedLock lock, String lockKey, Wrapper<T> countWrapper);
	
	    /**
	     * 幂等性新增或更新记录
	     *
	     * @param entity       实体对象
	     * @param lock         锁实例
	     * @param lockKey      锁的key
	     * @param countWrapper 判断是否存在的条件
	     * @param msg          对象已存在提示信息
	     * @return
	     */
	    boolean saveOrUpdateIdempotency(T entity, DistributedLock lock, String lockKey, Wrapper<T> countWrapper, String msg);
	
	    /**
	     * 幂等性新增或更新记录
	     *
	     * @param entity       实体对象
	     * @param lock         锁实例
	     * @param lockKey      锁的key
	     * @param countWrapper 判断是否存在的条件
	     * @return
	     */
	    boolean saveOrUpdateIdempotency(T entity, DistributedLock lock, String lockKey, Wrapper<T> countWrapper);
	}


注:DistributedLock为分布式锁实现,后续会贴

幂等实现类SuperServiceImpl


	import cn.hutool.core.util.StrUtil;
	import com.baomidou.mybatisplus.core.conditions.Wrapper;
	import com.baomidou.mybatisplus.core.mapper.BaseMapper;
	import com.baomidou.mybatisplus.core.metadata.TableInfo;
	import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils;
	import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
	import com.baomidou.mybatisplus.core.toolkit.StringUtils;
	import com.baomidou.mybatisplus.core.toolkit.TableInfoHelper;
	import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
	import com.central.common.exception.IdempotencyException;
	import com.central.common.exception.LockException;
	import com.central.common.lock.DistributedLock;
	import com.central.common.service.ISuperService;
	
	import java.io.Serializable;
	import java.util.Objects;
	
	/**
	 * service实现父类
	 *
	 * @author
	 * @date 
	 */
	public class SuperServiceImpl<M extends BaseMapper<T>, T> extends ServiceImpl<M, T> implements ISuperService<T> {
	    /**
	     * 幂等性新增记录
	     * 例子如下:
	     * String username = sysUser.getUsername();
	     * boolean result = super.saveIdempotency(sysUser, lock
	     *                 , LOCK_KEY_USERNAME+username
	     *                 , new QueryWrapper<SysUser>().eq("username", username));
	     *
	     * @param entity       实体对象
	     * @param lock         锁实例
	     * @param lockKey      锁的key
	     * @param countWrapper 判断是否存在的条件
	     * @param msg          对象已存在提示信息
	     * @return
	     */
	    @Override
	    public boolean saveIdempotency(T entity, DistributedLock lock, String lockKey, Wrapper<T> countWrapper, String msg) {
	        if (lock == null) {
	            throw new LockException("DistributedLock is null");
	        }
	        if (StrUtil.isEmpty(lockKey)) {
	            throw new LockException("lockKey is null");
	        }
	        try {
	            //加锁
	            boolean isLock = lock.lock(lockKey);
	            if (isLock) {
	                //判断记录是否已存在
	                int count = super.count(countWrapper);
	                if (count == 0) {
	                    return super.save(entity);
	                } else {
	                    if (StrUtil.isEmpty(msg)) {
	                        msg = "已存在";
	                    }
	                    throw new IdempotencyException(msg);
	                }
	            } else {
	                throw new LockException("锁等待超时");
	            }
	        } finally {
	            lock.releaseLock(lockKey);
	        }
	    }
	
	    /**
	     * 幂等性新增记录
	     *
	     * @param entity       实体对象
	     * @param lock         锁实例
	     * @param lockKey      锁的key
	     * @param countWrapper 判断是否存在的条件
	     * @return
	     */
	    @Override
	    public boolean saveIdempotency(T entity, DistributedLock lock, String lockKey, Wrapper<T> countWrapper) {
	        return saveIdempotency(entity, lock, lockKey, countWrapper, null);
	    }
	
	    /**
	     * 幂等性新增或更新记录
	     * 例子如下:
	     * String username = sysUser.getUsername();
	     * boolean result = super.saveOrUpdateIdempotency(sysUser, lock
	     *                 , LOCK_KEY_USERNAME+username
	     *                 , new QueryWrapper<SysUser>().eq("username", username));
	     *
	     * @param entity       实体对象
	     * @param lock         锁实例
	     * @param lockKey      锁的key
	     * @param countWrapper 判断是否存在的条件
	     * @param msg          对象已存在提示信息
	     * @return
	     */
	    @Override
	    public boolean saveOrUpdateIdempotency(T entity, DistributedLock lock, String lockKey, Wrapper<T> countWrapper, String msg) {
	        if (null != entity) {
	            Class<?> cls = entity.getClass();
	            TableInfo tableInfo = TableInfoHelper.getTableInfo(cls);
	            if (null != tableInfo && StringUtils.isNotEmpty(tableInfo.getKeyProperty())) {
	                Object idVal = ReflectionKit.getMethodValue(cls, entity, tableInfo.getKeyProperty());
	                if (StringUtils.checkValNull(idVal) || Objects.isNull(getById((Serializable) idVal))) {
	                    if (StrUtil.isEmpty(msg)) {
	                        msg = "已存在";
	                    }
	                    return this.saveIdempotency(entity, lock, lockKey, countWrapper, msg);
	                } else {
	                    return updateById(entity);
	                }
	            } else {
	                throw ExceptionUtils.mpe("Error:  Can not execute. Could not find @TableId.");
	            }
	        }
	        return false;
	    }
	
	    /**
	     * 幂等性新增或更新记录
	     * 例子如下:
	     * String username = sysUser.getUsername();
	     * boolean result = super.saveOrUpdateIdempotency(sysUser, lock
	     *                 , LOCK_KEY_USERNAME+username
	     *                 , new QueryWrapper<SysUser>().eq("username", username));
	     *
	     * @param entity       实体对象
	     * @param lock         锁实例
	     * @param lockKey      锁的key
	     * @param countWrapper 判断是否存在的条件
	     * @return
	     */
	    @Override
	    public boolean saveOrUpdateIdempotency(T entity, DistributedLock lock, String lockKey, Wrapper<T> countWrapper) {
	        return this.saveOrUpdateIdempotency(entity, lock, lockKey, countWrapper, null);
	    }
	}


具体业务SysRoleServiceImpl


	import java.util.List;
	import java.util.Map;
	
	import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
	import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
	import com.central.common.constant.CommonConstant;
	import com.central.common.lock.DistributedLock;
	import com.central.common.model.*;
	import com.central.common.service.impl.SuperServiceImpl;
	import org.apache.commons.collections4.MapUtils;
	import org.springframework.beans.factory.annotation.Autowired;
	import org.springframework.stereotype.Service;
	import org.springframework.transaction.annotation.Transactional;
	
	import com.central.user.mapper.SysRoleMapper;
	import com.central.user.mapper.SysRoleMenuMapper;
	import com.central.user.mapper.SysUserRoleMapper;
	import com.central.user.service.ISysRoleService;
	
	import lombok.extern.slf4j.Slf4j;
	
	import javax.annotation.Resource;
	
	/**
	 * @author
	 */
	@Slf4j
	@Service
	public class SysRoleServiceImpl extends SuperServiceImpl<SysRoleMapper, SysRole> implements ISysRoleService {
	    private final static String LOCK_KEY_ROLECODE = CommonConstant.LOCK_KEY_PREFIX+"rolecode:";
	
	    @Resource
	    private SysUserRoleMapper userRoleMapper;
	
	    @Resource
	    private SysRoleMenuMapper roleMenuMapper;
	
	    @Autowired
	    private DistributedLock lock;
	
	    @Transactional(rollbackFor = Exception.class)
	    @Override
	    public void saveRole(SysRole sysRole) {
	        String roleCode = sysRole.getCode();
	        super.saveIdempotency(sysRole, lock
	                , LOCK_KEY_ROLECODE+roleCode, new QueryWrapper<SysRole>().eq("code", roleCode), "角色code已存在");
	    }
	
	 
	    @Override
	    @Transactional
	    public Result saveOrUpdateRole(SysRole sysRole) {
	        if (sysRole.getId() == null) {
	            this.saveRole(sysRole);
	        } else {
	            baseMapper.updateById(sysRole);
	        }
	        return Result.succeed("操作成功");
	    }
	}

SysRole Model类

	
	import com.baomidou.mybatisplus.annotation.TableName;
	import lombok.Data;
	import lombok.EqualsAndHashCode;
	
	/**
	 * @author 
	 * 角色
	 */
	@Data
	@EqualsAndHashCode(callSuper = false)
	@TableName("sys_role")
	public class SysRole extends SuperEntity {
	    private static final long serialVersionUID = 4497149010220586111L;
	    private String code;
	    private String name;
	    private Long userId;
	}

SuperEntity

	
	import com.baomidou.mybatisplus.annotation.FieldFill;
	import com.baomidou.mybatisplus.annotation.TableField;
	import com.baomidou.mybatisplus.annotation.TableId;
	import com.baomidou.mybatisplus.extension.activerecord.Model;
	import lombok.Getter;
	import lombok.Setter;
	
	import java.io.Serializable;
	import java.util.Date;
	
	/**
	 * 实体父类
	 *
	 * @author 
	 */
	@Setter
	@Getter
	public class SuperEntity<T extends Model<?>> extends Model<T> {
	    /**
	     * 主键ID
	     */
	    @TableId
	    private Long id;
	    @TableField(fill = FieldFill.INSERT)
	    private Date createTime;
	    @TableField(fill = FieldFill.INSERT_UPDATE)
	    private Date updateTime;
	
	    @Override
	    protected Serializable pkVal() {
	        return this.id;
	    }
	}

<think> 我们正在讨论高并发场景下的接口幂等性问题。根据引用内容,幂等性是指无论操作执行一次还是多次,其结果都是相同的。在高并发场景下,保证接口幂等性至关重要,尤其是在支付、订单创建等业务中。 引用[1]提到,常见的幂等性保证方案包括防重令牌、随机字符串、幂等表、防重表、数据库唯一索引、乐观锁等。引用[2]则总结为“一锁二判三更新”的通用方案,并分为基于缓存和基于数据库两大类。引用[3]强调在分布式系统中,幂等性设计的关键是通过唯一ID(如UUID或Snowflake算法)保障操作的幂等性。 下面我将详细解释高并发场景下解决幂等性问题的几种核心方法: ### 1. **唯一标识符(Token/UUID)** - **原理**:客户端在请求前先向服务端申请一个唯一令牌(Token),服务端将该Token存入缓存(如Redis)并设置较短的有效期。客户端请求时携带此Token,服务端校验Token是否存在: - 若存在,则执行业务并删除Token; - 若不存在,则拒绝重复请求[^1][^2]。 - **适用场景**:用户提交订单、支付等前端交互频繁的操作。 - **优点**:实现简单,对数据库压力小。 - **缺点**:需额外维护Token生成和校验逻辑。 ### 2. **数据库唯一索引** - **原理**:利用数据库的唯一约束(如唯一索引或主键)防止重复数据插入。例如,为订单表添加`order_id`的唯一索引,插入重复订单时会抛出异常[^1]。 - **适用场景**:创建唯一性资源的场景(如生成订单号、用户注册)。 - **优点**:数据库层天然支持,可靠性高。 - **缺点**:不适合更新操作;高并发下可能因索引冲突导致性能下降。 ### 3. **乐观锁(基于版本号)** - **原理**:在数据表中增加版本号字段`version`。更新数据时,校验当前版本号与数据库中的版本号是否一致: ```sql UPDATE table SET amount=100, version=version+1 WHERE id=123 AND version=5; ``` 若影响行数为0,说明版本号已变更,拒绝重复更新[^1][^2]。 - **适用场景**:库存扣减、账户余额更新等并发写操作。 - **优点**:避免悲观锁的性能开销。 - **缺点**:需在业务逻辑中处理版本号校验。 ### 4. **幂等表/防重表** - **原理**:单独创建一张表记录已处理的请求唯一标识(如业务ID+操作类型)。处理请求前,先向该表插入记录(利用唯一索引防重),插入成功才执行业务逻辑[^1]。 - **适用场景**:分布式系统中跨服务调用的幂等性保证。 - **优点**:与业务解耦,通用性强。 - **缺点**:增加数据库写入压力。 ### 5. **分布式锁** - **原理**:使用Redis或ZooKeeper实现分布式锁,在关键操作前加锁。例如: ```java if (redis.setnx("lock:order_123", "1", 10, TimeUnit.SECONDS)) { try { // 执行业务逻辑 } finally { redis.del("lock:order_123"); } } ``` - **适用场景**:需要强一致性的复杂业务链(如库存扣减+订单创建)。 - **优点**:保证跨进程/服务的互斥。 - **缺点**:锁失效时间设置不当可能导致死锁或锁失效。 ### 6. **状态机机制** - **原理**:定义业务状态流转规则(如“待支付→已支付→已完成”),每次更新时校验当前状态是否允许流转到目标状态[^2]。 - **适用场景**:订单状态变更等有明确状态转移路径的业务。 - **优点**:逻辑清晰,避免非法状态跃迁。 - **缺点**:需预先设计状态机模型。 ### 高并发下的优化建议 - **组合使用**:例如“Token+数据库唯一索引”或“乐观锁+幂等表”,提升可靠性。 - **降级策略**:在极端高并发下,可暂时降级为异步队列处理(如Kafka),通过消息顺序性保障幂等[^3]。 - **性能权衡**:优先使用Redis等缓存方案减少数据库压力,必要时引入本地缓存(如Guava Cache)[^2]。 --- §§相关问题§§ 1. 如何选择Token生成算法(如UUID vs Snowflake)? 2. 乐观锁在哪些场景下可能失效? 3. 分布式锁与数据库锁的性能对比如何? 4. 幂等性方案如何与微服务架构结合? </think> 在高并发场景下解决幂等性问题,需结合业务特性和系统架构选择合适方案。以下是核心方法及实践建议,综合引用内容分析: ### 一、幂等性核心解决方案 1. **唯一标识符(Token/UUID)** - **原理**:请求前生成唯一Token(如UUID或Snowflake ID),服务端校验Token是否已存在 - **实现**: ```java // 伪代码示例:基于Redis的Token校验 String token = generateToken(); // 生成唯一Token redis.setex(token, 60, "1"); // 存入Redis(60秒过期) // 接口处理逻辑 if (redis.exists(token)) { executeBusinessLogic(); redis.del(token); // 删除Token确保幂等 } ``` - **适用场景**:用户提交订单、支付请求等[^1][^2] 2. **数据库唯一索引** - 为业务唯一字段(如订单号)添加数据库唯一约束 - **优势**:数据库层天然支持,可靠性高 - **局限**:仅适用于插入操作,更新操作需配合其他方案[^1] 3. **乐观锁(版本号机制)** - **实现流程**: 1. 数据表增加`version`字段 2. 更新时校验版本号: ```sql UPDATE orders SET status = 'paid', version = version + 1 WHERE order_id = 10086 AND version = 3; ``` - **效果**:若版本号不匹配,SQL影响行数为0,拒绝重复操作[^1][^2] 4. **状态机控制** - 定义明确状态流转规则(如:待支付 → 已支付 → 已完成) - **回滚机制**:拒绝非法状态跃迁(如已支付订单再次支付)[^2] --- ### 二、高并发场景优化策略 1. **分层防护体系** - **前端层**:按钮防重+Token生成 - **网关层**:API限流+Token校验 - **服务层**:本地缓存+分布式锁 - **数据层**:唯一索引+乐观锁[^1][^2] 2. **分布式锁升级方案** | 方案 | 适用场景 | 性能影响 | |---------------------|----------------------------|---------| | Redis SETNX | 短时操作(<500ms) | 低 | | RedLock | 强一致性场景 | 中 | | ZooKeeper临时节点 | 长事务处理 | 高 | 3. **最终一致性设计** - 结合消息队列(如Kafka)实现异步处理: ```mermaid graph LR A[请求] --> B{Token校验} B -->|通过| C[发送MQ] C --> D[消费者幂等处理] D -->|成功| E[更新DB] ``` - **优势**:削峰填谷,降低数据库压力[^3] --- ### 三、方案选型建议 | 场景 | 推荐方案 | 理由 | |---------------------|----------------------------|--------------------------| | 高频写操作(如支付) | Token+乐观锁 | 兼顾性能与可靠性 | | 批量数据处理 | 幂等表+唯一索引 | 避免分布式锁开销 | | 跨服务调用 | Saga模式+补偿事务 | 保证最终一致性[^3] | | 金融级强一致性 | TCC模式+2PC | 数据零丢失 | > **关键原则**:根据**业务容忍度**选择方案——强一致性场景用锁,最终一致性场景用消息队列[^3]。 ---
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值