`

分布式锁实现(redis/zookeeper)

 
阅读更多
先理了解本地锁Lcok:
http://572327713.iteye.com/blog/2407789

1.基于数据库实现分布式锁
性能较差,容易出现单点故障
锁没有失效时间,容易死锁
非阻塞式的
不可重入

2.基于缓存实现分布式锁
性能好
锁失效时间难设置,容易死锁
非阻塞式的(使用线程等待解决)
不可重入

3.基于zookeeper实现分布式锁
实现相对简单
可靠性高
性能较好
可重入

数据库:
性能较差,容易出现单点故障:
mysql并发性能瓶颈300-700,一个线程访问时插入一条数据,处理后删除此数据
很容易出现单点故障,连接有瓶劲性能差
锁没有失效时间,容易死锁:
一个线程突然宕机,因为数据没有删除,其他线程一直等待--死锁
非阻塞式的:
拿锁失败后立刻返回结果,线程做其他事情
不可重入:
线程加锁,其他线程不能加锁了

redis:
性能好:
轻轻松松响应并发10万
锁失效时间难设置,容易死锁:
非阻塞式的(使用线程等待解决):
不可重入:
线程加锁,其他线程不能加锁了
加解锁正确的姿势:(来自redis作者antirez的总结归纳)
1.加锁:
1.1生成唯一的随机值(uuid,时间搓),向1.2 redis写入随机值完成加锁1.3设置失效时间
必须使用sentnx? SET if Not exists(如果不存在,则SET)
SET resource_name my_random_value NX PX 30000
2.解锁:
2.1根据生成随机值2.2进行比对(线程与redis里如果一致),2.3删除redis上的数据
执行如下lua脚本
if redis.call("get".KEYS[1]) == ARGV[1] then
  return redis.call("del",KEYS[1]);
else
return 0;
end
注意!:
1.分布式锁必须要设置一个过期时间(好比unlock一定放在finally一个道理)
2.设置一个随机字符串my_random_value是很有必要的
3.加锁和设置失效时间必须是原子操作
4.释放锁保证三步原子性(get,比较del值,del)可用基于lua脚本实现
原因:a线程解锁比较redis值相等时恰好到了过期时间,此时redis写入了b线程的值,a线程会继续删除redis,导致b线程失效,所以必须保证释放锁三步原子性,使用lua脚本,不会受到b线程任何影响


图中2:a线程特意暂停3秒,但是已经超了超时时间。
图中3:a线程过了超时时间,redis清空,b线程加入了锁。
图中4:当a线程解锁时,发现此redis是b线程的时间戳,默默的离去,这就是设置随机时间搓的原因。

图中第二步: 比较redis值与本地值是否一致,删除redis进行解锁
此时本地值用的是threadlocal修饰变量。

redis的lock具体实现:
pom.xml
    <!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
     <dependency>
         <groupId>redis.clients</groupId>
         <artifactId>jedis</artifactId>
         <version>2.9.0</version>
     </dependency>


RedisLock.java
package com.hailong.yu.dongnaoxuexi.lock;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

import redis.clients.jedis.Jedis;

public class RedisLock implements Lock{

	private static final String LOCK_KEY = "lock";
	
	// 线程上下文,在这个线程执行过程中,保存的变量放这里,变量传递
	private ThreadLocal<String> local = new ThreadLocal<String>();


    /**
     * 阻塞锁(synchonied是阻塞的)
     */
    public void lock() {
    	if(tryLock()) {
    		
    	} else {
    		//reids做阻塞不太灵活,用现成阻塞
    		try {
				Thread.sleep(200);
				
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
    		lock();
    	}
	}

    /**
	 * 非阻塞式锁
     */
    public boolean tryLock() {
    	
    	String uuid = UUID.randomUUID().toString();

    	Jedis redis = new Jedis("localhost");
    	// key
    	// value
    	// @param nxxx NX|XX, NX -- Only set the key if it does not already exist. XX -- Only set the key
    	// 		if it already exist. 一定要没有值才设置成功/一定要有值才能设置成功
    	// expx EX|PX, expire time units: EX = seconds; PX = milliseconds
    	// 100ms有效期
    	String ret = redis.set(LOCK_KEY, uuid, "NX", "PX", 100);
    	if (ret !=null && ret.equals("OK")) {
        	local.set(uuid);
			return true;
		}
		return false;
	}

    /**
     * 解锁
     */
    public void unlock() {
    	// FileUtils.readFileByLines("E:/workspaces/.../unlock.lua");
    	String script = "";
    	// 执行脚本命令
    	Jedis redis = new Jedis("localhost");
    	List<String> keys = new ArrayList<String>();
    	keys.add(LOCK_KEY);
    	List<String> locals = new ArrayList<String>();
    	locals.add(local.get());
    	redis.eval(script, keys, locals);
	}

	public void lockInterruptibly() throws InterruptedException {
		// TODO Auto-generated method stub
		
	}

	public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
		// TODO Auto-generated method stub
		return false;
	}

	public Condition newCondition() {
		// TODO Auto-generated method stub
		return null;
	}
}


unlock.lua
if redis.call("get".KEYS[1]) == ARGV[1] then 
  return redis.call("del",KEYS[1]); 
else 
	return 0; 
end 


基于redis分布式锁方案问题:
1.锁失效时间难把握,一般为单线程处理时长两到三倍
超时时间不能长也不能短,为什么设置了100ms,一般为单线程处理线程的两到三倍。
2.可能出现锁失效情况
a线程如果超时,a线程还一直以为拿着锁,出现了共享资源竞争的问题。
3.此分布式锁不能在redis集群中使用,集群环境中可用redLock。
用于单节点redis,如果再集群下不太合适,可以使用redLock复杂很多。
所以建议使用zookeeper的分布式锁。
思考:有人会说redis单点可以有主从啊,但是主从会有问题:
比如服务1获取主机锁,主机宕机,这是还没来得及同步到从机,此时丛机变主机,这时服务2获取新主机锁,这样会有两个服务获取到锁。
https://blog.csdn.net/hh1sdfsf56456/article/details/79474434


zookeeper:
1.基于内存
2.实现简单

liux
持久节点create /temp(路径) temp(值)
临时节点 create -e /temp(路径) temp(值)
顺序节点 create -s
临时顺序 create -s -e

zk应用场景:
数据发布订阅(配置中心)
命名服务
Master选举
集群管理
分布式队列
分布式锁

羊群效应在分布式集群规模比较大的环境中危害是严重的:
1.巨大的服务器性能损耗:我去发事件,我要序列化的事件啊
客户端无端接受了很多与自己无关的通知事件。
2.网络冲击,每个节点网络发事件,网络带宽消耗很大
3.当羊群效应频繁的发生,整个节点都挂了,节点可能造成宕机
如果以后集群环境2个以上节点时。

集群节点有10个,只有1个会抢占锁
存在死锁的可能性。
生产的订单号服务宕机

临时顺序节点:


package com.baozun.util.locks;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;
import org.I0Itec.zkclient.serialize.SerializableSerializer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class ZookeeperLock implements Lock{

	private static String LOCK_PATH = "/LOCK";
	
//	@Value("${dubbo.registry.address}")
//	private static String ZK_IP_PORT;
	private static String ZK_IP_PORT = "123.206.*.*:2181";
	
	private static final Log logger = LogFactory.getLog(ZookeeperLock.class);
	
//	private ZkClient client = new ZkClient(ZK_IP_PORT, 1000, 10, new SerializableSerializer());
	private ZkClient client = new ZkClient(ZK_IP_PORT);

	private CountDownLatch cdl;

	// 之前节点
	private String beforePath;

	// 当前请求的节点
	private String currentPath;

	public ZookeeperLock() {
		if(!client.exists(LOCK_PATH)){
			client.createPersistent(LOCK_PATH);
		}
	}

    /**
     * 非阻塞式锁
     */
	@Override
	public boolean tryLock() {
		
		// 如果currentPath为空则为第一次尝试加锁,第一次加锁赋值currentPath
		if(currentPath == null || currentPath.length() <=0) {
			// 创建临时顺序节点
			currentPath = client.createEphemeralSequential(LOCK_PATH + '/', "lock");
		}
		// 获取所有临时节点并排序,临时节点名称为自增长的字符串:0000000400
		List<String> childrens = client.getChildren(LOCK_PATH);
		Collections.sort(childrens);
		// 如果当前节点在所有节点中排名第一则获取锁成功
		if(currentPath.equals(LOCK_PATH + '/' + childrens.get(0))) {
			return true;
		// 如果当前节点在所有节点中排名中不是第一,则获取前面的节点名称,并赋值给beforePath
		} else {
			int wz = Collections.binarySearch(childrens, currentPath.substring(6));
			beforePath = LOCK_PATH + '/' + childrens.get(wz-1);
		}
		return false;
	}

	@Override
	public void unlock() {
		// TODO Auto-generated method stub
		client.delete(currentPath);
	}

    /**
     * 阻塞式锁
     */
	@Override
	public void lock() {

		if(tryLock()) {
			waitForLock();
			lock();
		} else {
			logger.info(Thread.currentThread().getName()+" 获得分布式锁!");
		}
	}

	/**
	 * 等待锁
	 * @return
	 */
	public void waitForLock() {
		
		// 监听器
		IZkDataListener iZkDataListener = new IZkDataListener() {
			
			@Override
			public void handleDataDeleted(String arg0) throws Exception {

				// 节点数据被删除
				if(cdl!=null) {
					cdl.countDown();
				}
			}
			
			@Override
			public void handleDataChange(String arg0, Object arg1) throws Exception {
				// TODO Auto-generated method stub
				
			}
		};
		// 给排在前面的节点增加数据删除的wetcher
		client.subscribeDataChanges(beforePath, iZkDataListener);
		if(client.exists(beforePath)) {
			cdl = new CountDownLatch(1);
			try {
				cdl.await();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		client.unsubscribeDataChanges(beforePath, iZkDataListener);
	}

	// =========================暂不实现===========================
	@Override
	public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
		// TODO Auto-generated method stub
		return false;
	}

    /** 
     * 中断机制(可中断锁) 
     */ 
	@Override
	public void lockInterruptibly() throws InterruptedException {
		// TODO Auto-generated method stub
		
	}
	
    /** 
    * 设置条件加锁或解锁
    * 多个条件变量
    */ 
	@Override
	public Condition newCondition() {
		// TODO Auto-generated method stub
		return null;
	}
}


import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.ReentrantLock;

import com.baozun.util.locks.ZookeeperLock;


/**
 * @author hailong.yu1
 * @date 2018年1月25日 上午10:36:57
 */
public class SecurityProcessorTest implements Runnable {
	
	public static final int NUM = 10;
	public static CountDownLatch countDownLatch = new CountDownLatch(NUM);
	public static OrderCodeGenerator orderCodeGenerator = new OrderCodeGenerator();
	public ZookeeperLock lock = new ZookeeperLock();
	
	@Override
	public void run() {
		try {
			countDownLatch.await();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		createOrder();
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
		for(int i=0; i<NUM; i++) {
			
			new Thread(new SecurityProcessorTest()).start();
			countDownLatch.countDown();
		}
	}
	
	public void createOrder() {
		String orderNum = null;
		
		try {
			lock.lock();
			orderNum = orderCodeGenerator.getOrderCode();
		} catch (Exception e) {
			// TODO: handle exception
		} finally {
			lock.unlock();
		}

		System.out.println(Thread.currentThread().getName()+"===="+orderNum);
	}
}


ps aux|grep java
zkServer.sh start

linux服务器中访问zk服务:


在zk服务查看znode节点:



如果线程抢占一个资源,也可以使用队列抢占解决。

  • 大小: 69.1 KB
  • 大小: 116 KB
  • 大小: 23.7 KB
  • 大小: 16.5 KB
  • 大小: 8.5 KB
分享到:
评论

相关推荐

    redis和zookeeper实现分布式锁的区别

    `SETNX`命令在键不存在时设置键值,但如果存在则返回失败,这可以用来简单地实现锁。然而,单实例的Redis分布式锁存在一定的风险,如主节点故障可能导致锁无法释放。 RedLock算法是Redis创始人Antirez提出的一种...

    分布式锁用 Redis 还是 Zookeeper?.zip

    计算机技术、IT咨询、人工智能AI理论介绍,学习参考资料计算机技术、IT咨询、人工智能AI理论介绍,学习参考资料计算机技术、IT咨询、人工智能AI理论介绍,学习参考资料计算机技术、IT咨询、人工智能AI理论介绍,学习...

    分布式锁/信号量的实现方式:基于Redis、Zookeeper 、etcd 的分布式锁 确保在分布式环境中安全地访问共享资源

    下面将详细介绍基于Redis、Zookeeper以及etcd的分布式锁实现方式。 #### 二、基于Redis的分布式锁实现 Redis作为一款高性能的键值存储系统,非常适合用来实现分布式锁。以下是一个简单的示例代码: - **获取锁**...

    netty-redis-zookeeper高并发实战学习-netty-redis-zookeeper.zip

    5. 如何结合Netty、Redis和ZooKeeper来设计和实现一个高并发的微服务架构。 6. 性能调优,包括Netty的线程模型调整、Redis的内存管理和ZooKeeper的配置优化。 7. 实战项目中可能遇到的问题及解决策略,例如网络抖动...

    分布式锁实现(基于redis-mysql)1

    在单机环境中,我们可以通过Java等编程语言内置的并发控制手段,如synchronized关键字或ReentrantLock等实现锁。然而,在分布式环境中,由于多台服务器可能同时访问同一资源,这时就需要借助于分布式锁来确保数据的...

    浅谈分布式锁的几种使用方式(redis、zookeeper、数据库)

    Redis是一个内存数据结构存储系统,其速度非常快,适合实现锁。使用Redis实现分布式锁通常有两种方法:`SETNX`命令和`lua`脚本。`SETNX`命令在键不存在时设置键值,实现互斥锁。为了防止锁不能被释放(例如,客户端...

    springboot redis zookeeperlock rabbit实现的分布式锁

    在分布式锁的实现中,Zookeeper可以借助其强一致性、顺序一致性和事件通知机制,通过创建临时节点实现锁的获取与释放。当一个客户端创建了一个临时节点,其他客户端则会监视这个节点,当节点消失(即锁被释放),...

    基于Redis方式实现分布式锁

    一旦键过期,Redis会自动删除该键值对,从而实现锁的自动释放,避免死锁。 3. **`DEL` 命令**:用于删除Redis中的键,释放锁。 ##### 实现步骤 1. **获取锁**: - 使用`SETNX`命令尝试获取锁,并使用`EXPIRE`命令...

    windows下分布式部署Mysql、Redis、Zookeeper、Nginx、FastDFS集合

    ### Windows 下分布式部署 Mysql、Redis、Zookeeper、Nginx、FastDFS 集合 #### MySQL 安装与配置 在 Windows 环境下安装 MySQL 的过程较为直观,但需要注意细节。 1. **下载 MySQL 安装程序:** - 从 MySQL ...

    分布式锁-分析产生的原因,推导解决方案的原理及注意事项,适用于redis/hbase/zookpeer/etcd/mysql等

    2. 基于Redis实现:Redis提供了丰富的数据结构和操作命令,如`SETNX`(设置并返回值为新)用于原子性地设置键,配合`EXPIRE`设置过期时间,实现锁的自动释放。Redisson是基于Redis的Java客户端,提供了一套完整的...

    如何操作Redis和zookeeper实现分布式锁

    如何操作Redis和zookeeper实现分布式锁 在分布式场景下,有很多种情况都需要实现最终一致性。在设计远程上下文的领域事件的时候,为了保证最终一致性,在通过领域事件进行通讯的方式中,可以共享存储(领域模型和...

    springboot redis zookeeperlock rabbit实现的分布式锁.zip

    本项目“springboot redis zookeeperlock rabbit实现的分布式锁”结合了Spring Boot、Redis、Zookeeper以及RabbitMQ这四款强大的工具,旨在构建一个健壮的分布式锁系统。以下是关于这些技术及其在分布式锁中的应用的...

    zk:redis分布式锁.zip

    分布式锁是一种在分布式系统中实现锁机制的技术,用于在多节点之间协调访问共享资源,确保在高并发环境下数据的一致性和完整性。本压缩包“zk:redis分布式锁.zip”提供了基于Zookeeper(zk)和Redis两种分布式锁实现...

    浅析redis与zookeeper构建分布式锁的异同.docx

    分布式锁的实现机制和 Redis、ZooKeeper 的差异 分布式锁是指在分布式系统中,多个进程或线程之间为了访问共享资源而需要获取的锁。实现分布式锁需要考虑三个阶段:1. 进程请求获取锁;2. 获取锁的进程持有锁并执行...

    zk分布式锁1

    * 需要客户端实现:ZooKeeper分布式锁需要客户端实现锁机制,增加了客户端的复杂性。 其他分布式锁实现 除了ZooKeeper分布式锁外,还有其他分布式锁实现方案,如Redis分布式锁、MySQL分布式锁等。这些分布式锁实现...

    Java基于SOA架构的分布式电商购物商城源码.zip

    Java基于SOA架构的分布式电商购物商城 前后端分离 前台商城:Vue全家桶 后台管理系统:Dubbo/SSM/Elasticsearch/Redis/MySQL/ActiveMQ/Shiro/Zookeeper等。 Java基于SOA架构的分布式电商购物商城 前后端分离 前台商城...

    Redis与Zookeeper高并发分布式锁实战.ppt

    redis和zk两种不同方式实现分布式锁,互联网开发小伙伴必备技能!

    java分布式锁实现代码

    3. 使用Curator的InterProcessMutex实现锁: ```java InterProcessMutex lock = new InterProcessMutex(client, "/path/to/lock"); try (LockHandle handle = lock.acquire()) { // 执行关键操作 } catch ...

    基于 Redis 的分布式锁

    分布式锁的实现通常需要依赖一个可靠的外部协调系统,这样的系统可以是数据库、ZooKeeper、Redis等。 Redis作为一个开源的高性能键值存储系统,被广泛应用于实现分布式锁。Redis提供的命令操作简单且执行速度快,...

    Java Redis分布式锁的正确实现方式详解

    分布式锁有三种实现方式:数据库乐观锁、基于Redis的分布式锁和基于ZooKeeper的分布式锁。本篇博客将详细介绍第二种方式,基于Redis实现分布式锁。 可靠性是分布式锁的重要特性。为了确保分布式锁的可靠性,至少...

Global site tag (gtag.js) - Google Analytics