目录
- 分布式锁的核心思想:
- 分布式锁的实现步骤:
- 一、Redis实现分布式锁
- 1.配置pom.xml文件
- 2.配置application.properties文件
- 3.配置Redis Bean
- 4.RedisTemplate分布式锁工具类
- 二、Redisson分布式框架实现分布式锁
- 1.pom.xml配置
- 2.application.properties配置
- 3.配置Redisson
- 4.Redis配置
- 5.测试
分布式锁的核心思想:
用一个状态值表示锁,对锁的占用和释放通过状态值来表示。例如在商品秒杀活动中通过锁定goodsId来解决商品超卖等问题。
分布式锁的实现步骤:
1.判断googsId在当前线程是否处于锁定状态,如果是则新线程需要等待或根据业务需求做其他处理
2.锁定goodsId,当拿到资源后先对其加锁,然后做核心业务处理
3.设置超时时间,当线程出现阻塞或死锁时需要用到超时时间来确保服务的可用性。
4.解锁
一、Redis实现分布式锁
Spring封装了RedisTemplate对象来进行对Redis的各种操作,它支持所有的Redis原生的api。
1.配置pom.xml文件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.配置application.properties文件
spring.redis.host=123.207.123.159
spring.redis.port=6379
spring.redis.password=root
spring.redis.database=1
3.配置Redis Bean
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
//使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(mapper);
template.setValueSerializer(serializer);
template.setKeySerializer(new StringRedisSerializer()); //使用StringRedisSerializer来序列化和反序列化redis的key值
template.setHashKeySerializer(serializer);
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
}
4.RedisTemplate分布式锁工具类
import com.zzx.provider.dao.UserDao;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
public class TestRedisTemplate {
private static final long TIMEOUT = 30 * 1000;
@Autowired
RedisTemplate<String, Object> redisTemplate;
@Autowired
UserDao userDao;
@GetMapping("/redis/template")
public void order() {
log.info("starting lock!");
for (int i = 0; i < 50; i++) {
String goodsId = "1";
long time = System.currentTimeMillis() + TIMEOUT;
//加锁
if (!lock(goodsId, String.valueOf(time))) {
System.out.println("The current number or people is too large!");
} else {
//一大坨逻辑代码这里用到了往数据库修改数据,处理一次age减一
int age = userDao.findAge(1);
age=age - 1;
System.out.println("age:" + age);
userDao.update(1,age );
//解锁
unLock(goodsId, String.valueOf(time));
}
}
}
/**
* 加锁
*/
public boolean lock(String key, String value) {
String currentValue=String.valueOf(redisTemplate.opsForValue().get(key));
//setIfAbsent 如果key存在false, 如果不存在返回true
if (!redisTemplate.opsForValue().setIfAbsent(key, value)) {
if (currentValue.equals("null") || ((Long.parseLong(currentValue) > System.currentTimeMillis()))) {
return false;
}
System.out.println("time:" + currentValue);
//setIfAbsent 如果key存在false, 如果不存在返回true
System.out.println("add lock true");
redisTemplate.opsForValue().set(key, value);
return true;
} else {
System.out.println("add lock true");
redisTemplate.opsForValue().set(key, value);
return true;
}
}
/**
* 解锁
*/
public void unLock(String key, String value) {
try {
System.out.println("unlock success");
redisTemplate.delete(key);
} catch (Throwable e) {
log.error("解锁异常, {}", e.getMessage());
}
}
}
测试结果:
二、Redisson分布式框架实现分布式锁
Redisson基于Redis实现分布式锁的加锁与释放锁。此外Redisson还支持redis单实例、redis哨兵、redis cluster、redis master-slave等各种部署架构。
1.pom.xml配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--start redis distributed lock-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.6.5</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.25.Final</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
<!--end redis distributed lock-->
2.application.properties配置
spring.redis.host=123.207.123.159
spring.redis.port=6379
spring.redis.password=123456
spring.redis.database=1
3.配置Redisson
@SpringBootApplication
@ConfigurationProperties(prefix = "spring.redis")
@Data
public class InsertApplication {
private String host;
private String port;
private String password;
@Bean
public RedissonClient getRedisson(){
Config config = new Config();
config.useSingleServer().setAddress("redis://" + host + ":" + port).setPassword(password);
//添加主从配置
//config.useMasterSlaveServers().setMasterAddress("").setPassword("").addSlaveAddress(new String[]{"",""});
return Redisson.create(config);
}
public static void main(String[] args) {
SpringApplication.run(InsertApplication.class, args);
}
}
4.Redis配置
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
//使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(mapper);
template.setValueSerializer(serializer);
template.setKeySerializer(new StringRedisSerializer()); //使用StringRedisSerializer来序列化和反序列化redis的key值
template.setHashKeySerializer(serializer);
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
}
5.测试
这里我就没有使用jemeter压力测试工具类,我将测试类拷贝到了我另一个模块中:
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
@RestController
public class SellController {
@Autowired
RedisTemplate<String, Object> redisTemplate;
@Autowired
private RedissonClient redisson;
@GetMapping("/save")
public void add(){
redisTemplate.delete("zzx");
redisTemplate.opsForValue().set("zzx","100");
}
private final String LOCKKEY = "locks";
@GetMapping("/sell")
public void sell(){
for(int i=0; i < 55; i++){
RLock lock = redisson.getLock(LOCKKEY);
lock.lock(60, TimeUnit.SECONDS); //加锁,60秒后自动释放锁 (默认30秒)
int stock = Integer.parseInt(String.valueOf(redisTemplate.opsForValue().get("zzx")));
if(stock > 0){
redisTemplate.opsForValue().set("zzx",(stock-1)+"");
System.out.println(LOCKKEY+":"+(stock-1)+"");
}
lock.unlock(); //释放锁
}
}
}
测试结果如下:
可以看到在并发情况下,各个线程得到了自己的锁,互不干扰,处理完毕之后释了放锁。