作者:冰峰编者说:比较实用的Redis加锁的方式,代码段可以收藏。
在最近的一次业务升级中,遇到这样一个问题,我们设计了新的账户体系,需要在用户将应用升级之后将原来账户的数据手动的同步过来,就是需要用户自己去触发同步按钮进行同步,因为有些数据是用户存在自己本地的。那么在这个过程中就存在一个问题,要是因为网络的问题,用户重复点击了这个按钮怎么办?就算我们在客户端做了一些处理,在同步的过程中,不能再次点击,但是经过我最近的爬虫实践,要是别人抓到了我们的接口那么还是不安全的。
基于这样的业务场景,我就使用Redis加锁的方式,限制了用户在请求的时候,不能发起二次请求。
我们在进入请求之后首选尝试获取锁对象,那么这个锁对象的键其实就是用户的id,如果获取成功,我们判断用户时候已经同步数据,如果已同步,那么可以直接返回,提示用户已经同步,如果没有那么直接执行同步数据的业务逻辑,最后将锁释放,如果在进入方法之后获取锁失败,那么有可能就是在第一次请求还没有结束的时候,接着又发起了请求,那么这个时候是获取不到锁的,也就不会发生数据同步出现同步好几次的情况。
华丽的分割线
那么有了这个需求之后,我们就来用Redis实现以下这个代码。首先我们要知道我们要介绍一下Redis的一个方法。
那么我们想要用Redis做用户唯一的锁对象,那么它在Redis中应该是唯一的,而且还不应该被覆盖,这个方法就是存储成功之后会返回true,如果该元素已经存在于Redis实例中,那么直接返回false
setIfAbsent(key,value)
但是这中间又存在一个问题,如果在获取了锁对象之后,我们的服务挂了,那么这个时候其他请求肯定是拿不到锁的,基于这种情况的考虑我们还应该给这个元素添加一个过期时间,防止我们的服务挂掉之后,出现死锁的问题。
/** * 添加元素 * * @param key * @param value */public void set(Object key, Object value) { if (key == null || value == null) { return; } redisTemplate.opsForValue().set(key, value.toString());}/** * 如果已经存在返回false,否则返回true * * @param key * @param value * @return */public Boolean setNx(Object key, Object value, Long expireTime, TimeUnit mimeUnit) { if (key == null || value == null) { return false; } return redisTemplate.opsForValue().setIfAbsent(key, value, expireTime, mimeUnit);}/** * 获取数据 * * @param key * @return */public Object get(Object key) { if (key == null) { return null; } return redisTemplate.opsForValue().get(key);}/** * 删除 * * @param key * @return */public Boolean remove(Object key) { if (key == null) { return false; } return redisTemplate.delete(key);}/** * 加锁 * * @param key * @param waitTime 等待时间 * @param expireTime 过期时间 */public Boolean lock(String key, Long waitTime, Long expireTime) { String value = UUID.randomUUID().toString().replaceAll("-