Spring (boot) + Redis 实现接口分布式并发锁, 同一个用户同一时间只能访问一次

源代码已上传至github: https://github.com/qiaomengnan16/spring-boot-rest-redis-lock,检出记得修改redis连接地址。

  1. 应用场景:例如出现用户领券、抢红包这种高并发的情况下,用户只能抢一次,这时候简单的代码if判断在毫秒级别内无法完全控制住,数据库可能又无法做唯一锁、乐观锁等,这时候可以通过redis来控制。

  2. 说下思路

1. 通过使用redis的setNx命令来做同一时间内唯一并发基础。
2. 在接口层面加上锁,这时候考虑采用AOP,进入接口前加锁,结束后释放锁
3. 如果用户没有获取到锁,则直接退出
4. redis key可以考虑利用用户ID、证件号等等唯一标识 

上代码

上锁解锁AOP


 /**
     * @Fields  : redisBiz
     * @author qiaomengnan
     */
    @Autowired
    private RedisBiz redisBiz;

    @Around("execution(* com.demo..*.*(..)) && @annotation(concurrentLock)")
    public Object around(ProceedingJoinPoint joinPoint, ConcurrentLock concurrentLock) throws Throwable {
        String key = buildKey(concurrentLock);
        try {
            // 在执行前上锁,以用户名或者ID为单位作为锁
            if(redisBiz.lockBySecondsTime(key, concurrentLock.time())) {
                return joinPoint.proceed();
            } else {
                // 没有获取到锁,则把key赋值为null,以免finally里解锁
                key = null;
                throw new ServiceException(concurrentLock.message());
            }
        } finally {
            // 执行完毕后解锁
            if(key != null) {
                redisBiz.delete(key);
            }
        }
    }

    /**
     * @Title:
     * @Description:  构建redis锁 key
     * @return
     * @throws
     * @author qiaomengnan
     * @date 2020/7/2
     */
    private String buildKey(ConcurrentLock concurrentLock) {
        // 自行根据系统业务构建出唯一的key,例如 lock_用户id  , return lock_用户id
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
        return concurrentLock.key() + request.getParameter("userId");
    }

接口层代码

@RestController
@RequestMapping("/")
public class HelloRest {

    @ConcurrentLock
    @GetMapping("/lock")
    public String participate(String userId) {
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "hello" + userId;
    }

}

接口故意休眠了10秒,这样10秒内只有这个线程拿到了锁,在做处理,其他接口请求时就会告知用户请求频繁,需要重试,直到上次操作结束。

userId为1的用户第一次访问成功了。
Spring (boot) + Redis 实现接口分布式并发锁, 同一个用户同一时间只能访问一次_第1张图片
再访问失败了,因为这个锁被上一个线程拿去了还没有处理完。
Spring (boot) + Redis 实现接口分布式并发锁, 同一个用户同一时间只能访问一次_第2张图片

总结:通过采用Redis的setNx实现唯一性、分布式锁,可以使用Jmeter、loadruner等并发测试工具尝试极端情况秒级、毫秒级、微妙级下的验证,AOP是辅助功能,让我们不在业务代码里加上任何锁相关的代码,只需要在接口上添加一个注解即可,这里锁默认时间是120秒,也就是说这个接口要在120秒内处理完成,如果120秒后还在继续处理,这时候锁自动失效了,则会出现问题,使用者要根据接口情况适当调整锁时间,加上过期时间是为了避免发生死锁,笔者推荐了解Redisson这个框架。

你可能感兴趣的:(Spring,分布式锁)