分布式锁的作用或者说意义在于
什么是锁
对于单进程应用,有时需要保证对共享资源读取和修改的线程安全性,Java提供了从volatile到synchronized到Lock接口的各种粒度的锁,并在jvm层面做了各种优化。而对于分布式系统,要保证的是多个进程、机器节点对于共享资源的使用的安全性。这时候不仅要考虑进程可见,还要考虑进程或者物理机(云节点)与锁之间的网络问题.
分布式锁有两个常见的场景:
- 效率 使用分布式锁可以避免不同节点重复相同的工作,这些工作会浪费资源。比如用户付了钱之后有可能不同节点会发出多封短信。
- 正确性 加分布式锁同样可以避免破坏正确性的发生,如果两个节点在同一条数据上面操作,比如多个节点机器对同一个订单操作不同的流程有可能会导致该笔订单最后状态出现错误,造成损失。
分布式锁需要实现的功能
当我们确定了在不同节点上需要分布式锁,那么我们需要了解分布式锁到底应该有哪些特点:
- 互斥性:和我们本地锁一样互斥性是最基本,但是分布式锁需要保证在不同节点的不同线程的互斥。
- 可重入性:同一个节点上的同一个线程如果获取了锁之后那么也可以再次获取这个锁。
- 锁超时:和本地锁一样支持锁超时,防止死锁。
- 高效,高可用:加锁和解锁需要高效,同时也需要保证高可用防止分布式锁失效,可以增加降级。
- 支持阻塞和非阻塞:和ReentrantLock一样支持lock和trylock以及tryLock(long timeOut)。
- 支持公平锁和非公平锁(可选):公平锁的意思是按照请求加锁的顺序获得锁,非公平锁就相反是无序的。这个一般来说实现的比较少。
redis的分布式锁的代码实现
public class RedisLockImpl implements RedisLock {
private Logger logger = LoggerFactory.getLogger(RedisLockImpl.class);
public static final String LOCK_PREFIX = "lock_";
public static final String LOCK_SUCCESS = "OK";
@Autowired
StatefulRedisConnection<String, String> connection;
@Override
public String acquireLockWithTimeout(String lockName, int lockTime) {
long start = System.currentTimeMillis();
String identifier = null;
RedisCommands<String, String> commands = connection.sync();
String key = LOCK_PREFIX + lockName;
identifier = UUID.randomUUID().toString();
String success = commands.set(key, identifier, SetArgs.Builder.nx().ex(lockTime));
if (LOCK_SUCCESS.equals(success)) {
return identifier;
} else {
return null;
}
}
@Override
public boolean releaseLock(String lockName, String identifier) {
RedisCommands<String, String> commands = connection.sync();
String key = LOCK_PREFIX + lockName;
while (true) {
String value = commands.get(key);
if (value == null) {
break;
} else if (value.equals(identifier)) {
commands.del(key);
return true;
}
}
return true;
}
}
这里用到的是redis2.6.12之后的set命令可以添加可选选项,lettuce客户端做了oop的封装.
实际上是set key value NX EX timeout
这个命令.实现加锁和设置超时时间的原子操作,防止死锁.
附 pom依赖和连接设置
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.pctf</groupId>
<artifactId>redisInAction</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.0.BUILD-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<version>2.1.3.RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
连接设置
@Configuration
public class ConnectionConfig {
public static final String HOST = "127.0.0.1";
public static final String PASSWORD = "123456";
public static final int PORT = 6379;
@Bean
public StatefulRedisConnection<String, String> getRedisConnection() {
RedisURI uri = RedisURI.builder().withHost(HOST).withPassword(PASSWORD).withPort(PORT).build();
RedisClient clusterClient = RedisClient.create(uri);
GenericObjectPool<StatefulRedisConnection<String, String>> pool = ConnectionPoolSupport.createGenericObjectPool(
() -> clusterClient.connect(), new GenericObjectPoolConfig());
try {
return pool.borrowObject();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
网友评论