分布式锁: 一个集群中多台jvm 只有一台能够获取这个锁, 其他jvm则等待。
redis 内存数据库, 性能好。
分布式锁的实现方式有很多种,推荐使用如下两种:
1: redis 实现
2: zookeeper 实现
redis 通过 setnx + expire 命令实现分布式锁, 乐观锁方式获取。 key对应的value值为 不重复的随机数。
setnx 与 set命令的区别, setnx 存在则返回失败,不能覆盖, set直接覆盖。
setnx 写入一个key, key不存在则保存,返回1 成功。. key存在则不保存, 返回0失败。 key不能重复
expire 是key的存活时间。设置key的失效时间, 防止死锁。
key失效或者操作完毕删除key释放锁, 记住, 要将创建的key对应的value值返回, 在删除时判断value是否相等, value相等 才能删除
zk 通过临时节点 + 事件通知实现分布式锁。乐观锁方式获取。节点不能重复。
zk 中有 临时节点与持久节点。 实现分布式锁用的是 临时节点,
哪台jvm 能够在zk创建成功 临时节点, 则获取锁, 其他则等待。 节点的创建与删除都有事件通知。
断开会话则删除节点, 释放锁。设置 session的时间防止死锁。
性能对比:
redis 比 zk 快, 毕竟 redis是内存数据库, zk在硬盘上。
稳定性:
zk 比 redis好。 zk是事件通知,稳定性比redis好。
代码实现:
pom 引入依赖:
<dependencies>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
</dependencies>
代码:
/**
* Project Name:servive-redislock
* File Name:Lock.java
* Package Name:com.tang.service
* Date:2019年11月7日 下午9:21:13
* Copyright (c) 2019,
*
*/
package com.tang.service;
import java.time.LocalDateTime;
import java.util.UUID;
import org.apache.commons.lang3.StringUtils;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/**
* ClassName: Lock
* Function: 获取redis锁与释放redis锁
* date: 2019年11月7日 下午9:21:13
*
* @author tangjiandong
*/
public class Lock {
//redis 线程池
private JedisPool jedisPool;
//唯一key, 获取锁的条件就是看是否可以创建这个key
private static final String redisKey = "redis_lock";
public Lock(JedisPool jedisPool)
{
this.jedisPool = jedisPool;
}
/**
* getRedisLock
* (获取锁)
*
* @param beginTime 获取锁之前的等待时间 (单位毫秒)
* @param endTime redis Key的失效时间 (单位毫秒)
* @return
*/
public String getRedisLock(Long beginTime, Long endTime)
{
Jedis conn = null;
//key对应的值, 在释放锁时判断是否是传入key的value的判断条件。
String value = UUID.randomUUID().toString();
int expireTime = (int)(endTime / 1000); //redis时间单位为秒, 这是redis Key的失效时间
//循环方式获取锁, 乐观锁方式, 系统时间加等待时间为获取锁的等待时间,
Long waitTimeOut = System.currentTimeMillis() + beginTime;
try {
conn = jedisPool.getResource();
//系统时间小于等待时间,继续循环获取锁,超时退出返回
while(System.currentTimeMillis() < waitTimeOut)
{
//setnx 方法key值唯一,成功保存返回1, 失败返回0, 成功代表key不存在进行 保存,失败代表key存在
if(1 == conn.setnx(redisKey, value))
{
//设置key的失效时间, 防止死锁.
conn.expire(redisKey, expireTime);
//返回value, 在释放锁时进行判断,防止释放不是当前锁的错误。
return value;
}
}
} catch (Exception e) {
e.printStackTrace();
//异常可以自定义。
throw new RuntimeException("获取redis锁时异常!");
}finally {
if (null != conn)
{
conn.close();
}
}
//获取锁超时。
return null;
}
/**
* unRedisLock
* 释放redis锁
*
* @return
*/
public void unRedisLock(String value)
{
Jedis conn = null;
try {
// 1.建立redis连接
conn = jedisPool.getResource();
String str = conn.get(redisKey);
//判断是否为当前值的锁,是当前锁才能释放
if(StringUtils.isNotBlank(str) && str.equals(value))
{
//释放锁
conn.del(redisKey);
System.out.println("2:释放锁...value:" + value);
}else {
System.out.println("2:释放锁失败... key: " +redisKey+ " : value: " + value);
}
} catch (Exception e) {
e.printStackTrace();
//异常可以自定义。
throw new RuntimeException("释放redis锁时异常!");
}finally {
if (null != conn)
{
conn.close();
}
}
}
}
/**
*
* ClassName: LockService
* Function: 配置连接redis线程池
* date: 2019年11月9日 下午4:27:20
*
* @author tangjiandong
*/
class LockService {
//如果使用的jedispool来得到的jedis,那cloese操作不是关闭,而是归还给线程池
private static JedisPool jedisPool = null;
static {
//redis线程池配置
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
//最大连接数
jedisPoolConfig.setMaxTotal(200);
//最大空闲数
jedisPoolConfig.setMaxIdle(8);
//最大等待时间
jedisPoolConfig.setMaxWaitMillis(1000 * 100);
//在borrow一个jedis实例时,是否提前进行alidate操作;如果为true,则得到的jedis实例均是可用的
//向资源池借用连接时是否做连接有效性检查(ping),无效连接会被移除,建议false,默认值false
//jedisPoolConfig.setTestOnBorrow(true);
//配置,地址, 端口, 超时时间, 客户端密码(没有密码则不填)
jedisPool = new JedisPool(jedisPoolConfig, "redis服务端IP", 6379, 3000, "123456");
}
private Lock lock = new Lock(jedisPool);
public void test()
{
String value = lock.getRedisLock(5000L, 5000L);
if(null == value)
{
System.out.println(Thread.currentThread().getName() + " 锁获取失败");
return;
}
System.out.println("1:获取锁...value:" + value);
//释放锁
lock.unRedisLock(value);
}
}
测试:
/**
* Project Name:servive-redislock
* File Name:Tests.java
* Package Name:com.tang.service
* Date:2019年11月8日 下午10:40:36
* Copyright (c) 2019,
*
*/
package com.tang.service;
/**
* ClassName: Tests
* Function: TODO ADD FUNCTION.
* date: 2019年11月8日 下午10:40:36
*
* @author tangjiandong
*/
public class Tests {
public static void main(String[] args) {
LockService LockService = new LockService();
for (int i = 0; i < 10; i++)
{
new Thread(new Runnable()
{
@Override
public void run()
{
LockService.test();
}
}).start();
}
}
}
结果: