为什么需要分布式锁
当并发去读写⼀个【共享资源】的时候,我们为了保证数据的正确,需要控制同⼀时刻只有⼀个线程访问。分布式锁就是⽤来控制同⼀时刻,只有⼀个 JVM 进程中的⼀个线程可以访问被保护的资源。
在同一个JVM虚拟机内,可以使用synchronized或者Lock接口,但是在分布式的环境下,有多个不同JVM虚拟机时单机的线程锁机制不再起作用,资源类在不同的服务器之间共享了,这个时候就需要分布式锁了。
分布式锁的特点
-
独占排他互斥
- 可以通过 setnx (redis命令:执行多次,只有一次能够成功)
- set key value ex 3 nx
-
防死锁发生
- 请求获取到锁之后,服务器挂掉了,导致锁无法释放:给lock锁添加过期时间
- 通过 set key value ex 3 nx
- 通过redis命令 expire
- 请求获取到锁之后,服务器挂掉了,导致锁无法释放:给lock锁添加过期时间
-
保证原子性
- 获取锁和设置过期时间之间
- 判断和删除之间:lua脚本
-
自动续期
-
防误删
- uuid给每个线程的锁添加唯一标识
-
可重入
- hash数据结构 + lua脚本
基于redis分布式锁的实现
使用场景:多个服务间保证同一时刻同一时间段内同一用户只能有一个请求(防止关键业务出现并发攻击)
创建redis项目
- 创建名称为redis_distributed_lock_1的maven项目
- 修改pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.distribute.redislock</groupId>
<artifactId>redis_distributed_lock_1</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.12</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<lombok.version>1.16.18</lombok.version>
</properties>
<dependencies>
<!--SpringBoot通用依赖模块-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--SpringBoot与Redis整合依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!--swagger2-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<!--redisson-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.13.4</version>
</dependency>
<!--通用基础配置boottest/lombok/hutool-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.8</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
-
创建yml文件
创建名称为application.properties的配置文件
server.port=8081 spring.application.name=redis_distributed_lock2 # ========================swagger2===================== # http://localhost:8081/swagger-ui.html swagger2.enabled=true spring.mvc.pathmatch.matching-strategy=ant_path_matcher # ========================redis\u5355\u673A===================== spring.redis.database=0 spring.redis.host=192.168.181.8 spring.redis.port=6379 spring.redis.password=123456 spring.redis.lettuce.pool.max-active=8 spring.redis.lettuce.pool.max-wait=-1ms spring.redis.lettuce.pool.max-idle=8 spring.redis.lettuce.pool.min-idle=0
-
修改主启动类
package com.distribute.redislock; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class RedisDistributedLockApp8081 { public static void main(String[] args) { SpringApplication.run(RedisDistributedLockApp8081.class,args); } }
-
编写业务类代码
创建RedisConfig类
package com.distribute.redislock.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration public class RedisConfig { @Bean public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) { RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(lettuceConnectionFactory); //设置key序列化方式string redisTemplate.setKeySerializer(new StringRedisSerializer()); //设置value的序列化方式json redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer()); redisTemplate.afterPropertiesSet(); return redisTemplate; } }
创建Swagger2Config类
package com.distribute.redislock.config; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.ApiInfo; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; import java.time.