分布式锁的实现
锁是用来解决什么问题的;
- 一个进程中的多个线程,多个线程并发访问同一个资源的时候,如何解决线程安全问题。
- 一个分布式架构系统中的两个模块同时去访问一个文件对文件进行读写操作
- 多个应用对同一条数据做修改的时候,如何保证数据的安全性
在但进程中,我们可以用到synchronized、lock之类的同步操作去解决,但是对于分布式架构下多进程的情况下,如何做到跨进程的锁。就需要借助一些第三方手段来完成
zookeeper实现分布式锁
利用zookeeper的唯一节点特性或者有序临时节点特性获得最小节点作为锁. zookeeper 的实现相对简单,通过curator客户端,已经对锁的操作进行了封装,原理如下
zookeeper的优势
1. 可靠性高、实现简单
2. zookeeper因为临时节点的特性,如果因为其他客户端因为异常和zookeeper连接中断了,那么节点会被删除,意味着锁会被自动释放
3. zookeeper本身提供了一套很好的集群方案,比较稳定
4. 释放锁操作,会有watch通知机制,也就是服务器端会主动发送消息给客户端这个锁已经被释放了
基于redis的分布式锁实现
redis中有一个setNx命令,这个命令只有在key不存在的情况下为key设置值。所以可以利用这个特性来实现分布式锁的操作
具体实现代码:
添加jar包
redis连接代码:
public class RedisManager {
private static JedisPool jedisPool;
static {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
//最大连接数
jedisPoolConfig.setMaxTotal(200);
//最大空闲数
jedisPoolConfig.setMaxIdle(10);
jedisPool = new JedisPool(jedisPoolConfig,"127.0.0.1",6379);
}
public static Jedis getJedis() throws Exception {
if (jedisPool != null){
return jedisPool.getResource();
}
throw new Exception("Jedispool was not init");
}
}
分布式锁的具体实现
public class RedisRock {
/**
* 获取锁
* @param key
* @param timeOut
* @return
* @throws Exception
*/
public String getLock(String key,int timeOut,int acquireTimeOut) throws Exception {
Jedis jedis = RedisManager.getJedis();
String value = UUID.randomUUID().toString();
//设置获取锁的超时时间,为了方便与锁的超时时间设置的一样
long end = System.currentTimeMillis()+acquireTimeOut;
//阻塞
while (System.currentTimeMillis() < end){
//如果设置成功
if(jedis.setnx(key,value) == 1){
//设置超时时间
jedis.expire(key,timeOut);
return value;
}
//如果超时时间为永久 这里是为了应对setnx成功后,
// 还未设置超时时间此时redis挂掉了
if (jedis.ttl(key) == -1){
jedis.expire(key,timeOut);
}
Thread.sleep(50);
}
return null;
}
/**
* 释放锁
* @param key
* @param value
* @return
*/
public boolean releaseLock(String key,String value) throws Exception {
Jedis jedis = RedisManager.getJedis();
while (true){
//监听key 一旦key被修改后面的事务代码就不会被执行 list的size会为0
jedis.watch(key);
//判断获得锁的线程和当前redis中存的锁是同一个锁,这里只能通过value来判断,因为抢占的key是相同的
if (value.equals(jedis.get(key))){
//创建事务
Transaction transaction = jedis.multi();
//删除key
transaction.del(key);
//事务是一个队列,所有的命令放入队列,所以返回结果是list
List<Object> list = transaction.exec();
if (list.size() == 0){
continue;
}
System.out.println(Thread.currentThread().getName()+" 释放锁");
//删除成功
return true;
}
jedis.unwatch();
break;
}
return false;
}
public static void main(String[] args) throws Exception {
RedisRock redisRock = new RedisRock();
String lockValue = redisRock.getLock("bbb", 1000,500);
if (null != lockValue){
System.out.println("获得锁成功 "+lockValue);
}else{
System.out.println("获取锁失败");
}
boolean flag = redisRock.releaseLock("bbb",lockValue);
if (flag){
System.out.println("释放锁成功");
}else {
System.out.println("释放锁失败");
}
}
}
下面测试一下:
来个线程类
public class ThreadA extends Thread{
private Service service;
public ThreadA(Service service) {
this.service = service;
}
@Override
public void run() {
service.seckill();
}
}
测试类:
public class Service {
private RedisRock redisRock = new RedisRock();
private int n = 100;
public void seckill() {
try {
//返回锁的value值
String value = redisRock.getLock("iii", 5000,1000);
System.out.println(Thread.currentThread().getName()+"获得了锁,锁的值为:"+value);
System.out.println(--n);
redisRock.releaseLock("iii",value);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
final Service service = new Service();
for (int i=0;i<50;i++){
ThreadA threadA = new ThreadA(service);
threadA.start();
}
}
}
运行结果:
有序的获得释放锁
end。。。。