使用redis incr处理并发,存在死锁问题
目录
1.项目场景:
2.问题代码 :
3.修改后的代码:
4.实战示例:
5.更新:setIfAbsent
6.再次更新:redisson
1.项目场景:
锁主要是用来实现资源共享同步,只有获取到了锁才能访问该同步代码,否则等待其他线程使用结束释放锁。
2.问题代码 :
@Autowired
private RedisTemplate redisTemplate;
/**
* 加锁
*/
public boolean getLock(String key) {
try {
long count = redisTemplate.opsForValue().increment(key, 1);
//此段代码出现异常则会出现死锁问题,key一直都存在
if(count == 1){
//设置有效期2秒
redisTemplate.expire(key, 2, TimeUnit.SECONDS);
return true;
}
//如果存在表示重复
return false;
} catch (Exception e) {
logger.error("redis加锁异常", e);
redisTemplate.delete(key); //出现异常删除锁
return true;
}
}
当正常情况没有问题,但是当计数器设置成功后,服务出现异常,那么这个key会永远存在,这样肯定不行。
Redis Incr 命令将 key 中储存的数字值增一,如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作,且将key的有效时间设置为长期有效。
3.修改后的代码:
@Autowired
private RedisTemplate redisTemplate;
/**
* 加锁
*/
public boolean getLock(String key) {
try {
long count = redisTemplate.opsForValue().increment(key, 1);
if(count == 1){
//设置有效期2秒
redisTemplate.expire(key, 2, TimeUnit.SECONDS);
return true;
}else{
long time = redisTemplate.getExpire(key,TimeUnit.SECONDS);
if(time == -1){
//设置失败重新设置过期时间
redisTemplate.expire(key, 2, TimeUnit.SECONDS);
return true;
}
}
//如果存在表示重复
return false;
} catch (Exception e) {
logger.error("redis加锁异常", e);
redisTemplate.delete(key); //出现异常删除锁
return true;
}
}
4.实战示例:
场景:同一个接口,必须等待上个线程处理完后,业务才能处理。
Service层:RedisLock.java
package com.test.utils;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import lombok.extern.slf4j.Slf4j;
/**
* Redis锁
*/
@Service
@Slf4j
public class RedisLock {
@Autowired
private RedisTemplate<String, ?> redisTemplate;
/**
* 加锁
* @param key key
* @param timeExpire 过期时间单位秒
* @return true表示key存在
*/
public boolean getLock(String key, long timeExpire) {
try {
long count = redisTemplate.opsForValue().increment(key, 1);
if(count == 1){
//设置有效期timeExpire
redisTemplate.expire(key, timeExpire, TimeUnit.SECONDS);
return false;
}else{
long time = redisTemplate.getExpire(key,TimeUnit.SECONDS);
if(time == -1){
//设置失败重新设置过期时间
redisTemplate.expire(key, timeExpire, TimeUnit.SECONDS);
return false;
}
}
//如果存在表示重复
return true;
} catch (Exception e) {
log.error("redis加锁异常", e);
redisTemplate.delete(key); //出现异常删除锁
return false;
}
}
/**
* 解锁
* @param key key
* @return true表示成功
*/
public boolean unLock(String key) {
try {
redisTemplate.delete(key); //删除key
return true;
} catch (Exception e) {
log.error("redis解锁异常", e);
redisTemplate.delete(key); //出现异常删除锁
return false;
}
}
}
Controller层:TestController.java
package com.test.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.test.utils.RedisLock;
import lombok.extern.slf4j.Slf4j;
@RestController
@Slf4j
public class TestController{
@Autowired
private RedisLock redisLock;
/**
* 测试并发
*/
@RequestMapping(value = "/test", method = RequestMethod.GET)
public String test(String key) {
try {
long expireTime = 30000; //有效时长(毫秒)
long beginTime = System.currentTimeMillis(); //开始时间
//判断同一时间是否有多个用户操作
boolean flagLock = redisLock.getLock(key, 6);
if(flagLock){
//如果锁存在,尝试重新获取锁
while (redisLock.getLock(key, 6)) {
//执行时长(毫秒)
long time = System.currentTimeMillis() - beginTime;
//防止死循环
if(time > expireTime){
log.error("超时,请稍后重试");
return "超时,请稍后重试!";
}
Thread.sleep(100);
}
}
//TODO 业务操作...
return "success";
} catch (Exception e) {
log.error("出错:", e);
return "出错,请联系管理员!";
}finally {
//解锁
redisLock.unLock(key);
}
}
}
5.更新:setIfAbsent
将redis版本升级到2.1以上,可以直接使用setIfAbsent设置过期时间,使用 4 个参数的重载方法是原子性的。
redisTemplate.opsForValue().setIfAbsent(key, value, timeout, TimeUnit.SECONDS);
说明:当key不存在,将key的值设为value,并设置过期时间,返回true;若给定的key已经存在,则不做任何动作,并返回false。此方法是原子性的。
使用setIfAbsent
@Autowired
private RedisTemplate redisTemplate;
/**
* 加锁
* @param key key
* @param value value
* @param timeout 过期时间单位秒
* @param true表示key不存在
*/
public boolean getLock(String key, String value, long timeout) {
try {
//原子性操作
boolean flag = redisTemplate.opsForValue().setIfAbsent(key, value, timeout, TimeUnit.SECONDS);
//如果存在false表示重复
return flag;
} catch (Exception e) {
logger.error("redis加锁异常", e);
redisTemplate.delete(key); //出现异常删除锁
return true;
}
}
6.再次更新:redisson
推荐使用Redisson来实现分布式锁,当然前面两种方式也是可行的,条条大路通罗马。
什么是redisson:
Redisson - 是一个高级的分布式协调Redis客服端,Redisson API 侧重于分布式开发,充分的利用了Redis键值数据库提供的一系列优势,在Java实用工具包中常用接口的基础上,为使用者提供了一系列具有分布式特性的常用工具类。
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.7.5</version>
</dependency>
application.properties
# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接密码(默认为空)
spring.redis.password=
# Redis服务器连接端口
spring.redis.port=6379
Redisson的配置类
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;
@Configuration
public class RedissonConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private String port;
@Value("${spring.redis.password}")
private String password;
/**
* RedissonClient,单机模式
* @return
* @throws IOException
*/
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer().setAddress("redis://" + host + ":" + port).setPassword(password);
return Redisson.create(config);
}
}
Redisson分布式锁业务类
import java.util.concurrent.TimeUnit;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
public class TestService {
@Autowired
RedissonClient redissonClient;
private final static String LOCK_KEY = "TEST_KEY";
int count = 50;
public void seckill() {
//定义锁
RLock rlock = redissonClient.getLock(LOCK_KEY);
try {
//尝试加锁,最大等待时间30秒,上锁10秒自动解锁
boolean flag = rlock.tryLock(30, 10, TimeUnit.SECONDS);
if (flag) {
log.info("线程:" + Thread.currentThread().getName() + "获得了锁");
log.info("剩余数量:{}", --count);
}
} catch (Exception e) {
log.error("程序执行异常:", e);
//释放锁
rlock.unlock();
} finally {
log.info("线程:" + Thread.currentThread().getName() + "准备释放锁");
//释放锁
rlock.unlock();
}
}
}
源码地址包含三种方式:
https://download.csdn.net/download/u011974797/86772996
还没有评论,来说两句吧...