高性能方法级幂等性

高性能方法级幂等性

 

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;
	    }
	}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值