logo资料库

基于Redis方式实现分布式锁.pdf

第1页 / 共9页
第2页 / 共9页
第3页 / 共9页
第4页 / 共9页
第5页 / 共9页
第6页 / 共9页
第7页 / 共9页
第8页 / 共9页
资料共9页,剩余部分请下载后查看
基于ZooKeeper的分布式锁
基于Redis的分布式锁
SETNX
Expire
Delete
实现思路
核心代码
Maven依赖信息
RedisLock.java
RedisLockDemo.java
三种分布式对比
Redis实现分布式锁与Zookeeper实现分布式锁区别
基于 Redis 的分布式锁 分布式锁一般有三种实现方式: 1. 基于关系数据库乐观锁 2. 基于 Redis 的分布式锁 3. 基于 ZooKeeper 的分布式锁 基于关系数据库乐观锁 基于关系数据库实现分布式锁主要利用数据库的锁机制,通常使用乐观锁。但是由于高 并发场景下 RDBMS 的 IO 开销太大,所以一般不适用与大型、高并发项目。 基于 ZooKeeper 的分布式锁 基于 zookeeper 临时有序节点实现的分布式锁,其大致思想为:每个客户端获取锁时, 在 zookeeper 上的指定节点的目录下,生成一个唯一的临时有序节点。判断是否成功获取锁 的方式很简单,只需要判断有序节点中序号最小的一个。当释放锁的时候,只需将这个瞬时 节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。基于 z k 实现分布式锁可靠性高,但是实现相对复杂,详细介绍参见:https://blog.csdn.net/hellozpc /article/details/80383883 基于 Redis 的分布式锁 本篇主要介绍使用 redis 构建分布式锁服务 使用 redis 实现分布式锁主要基于 Redis 的 setnx 命令 SETNX SETNX key val 当且仅当在 redis 中 key 不存在时,set 一个 key 为 val 的字符串,返回 1; 若 key 存在,则什么都不做,返回 0。 Expire expire key timeout 为 key 设置一个超时时间,单位为 second,超过这个时间 redis 会自动删除该键值对,即锁 会自动释放,避免死锁。
Delete 删除 redis 中的 key,即释放锁。 在使用 Redis 实现分布式锁的时候,主要就会使用到这三个命令。 实现思路 1、获取锁的时候,使用 setnx 加锁,并使用 expire 命令为锁添加一个超时时间,超过该时 间则自动释放锁,锁的 value 值需要指定,可以根据具有业务含义,本文简化为 UUID,在 释放锁的时候进行判断,避免误释放。 2、获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。 3、释放锁的时候,通过 UUID 判断是不是该锁,若是该锁,则执行 delete 进行锁释放。 核心代码 Maven 依赖信息 redis.clients jedis 2.9.0 RedisLock.java package com.zpc.redis.lock; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import java.util.UUID; /** * 使用 redis 实现分布式锁 * 实际应用还需进一步封装,本 demo 只是演示原理 */ public class RedisLock {
private JedisPool jedisPool; public RedisLock(JedisPool jedisPool) { this.jedisPool = jedisPool; } /** * 加锁 * * @param lockKey * @param acquireTimeout * @param timeOut */ public String lock(String lockKey, Long acquireTimeout, Long timeOut) { Jedis conn = null; String returnedLockId = null; try { // 1.建立 redis 连接 conn = jedisPool.getResource(); // 2.定义锁的名称 String lockName = "redis_lock_" + lockKey; // 3.定义上锁成功之后,锁的超时时间 int expireTime = (int) (timeOut / 1000); //4.随机生成一个 value 标识这把锁 String lockId = UUID.randomUUID().toString().replaceAll("-", ""); // 5.定义在没有获取锁之前,锁的超时时间 Long endTime = System.currentTimeMillis() + acquireTimeout; while (System.currentTimeMillis() < endTime) { // 6.使用 setnx 方法设置锁值 if (conn.setnx(lockName, lockId) == 1) { // 7.判断返回结果如果为 1,则可以成功获取锁,并且设置锁的超时时间 conn.expire(lockName, expireTime); returnedLockId = lockId; return returnedLockId; } // 8.否则情况下继续循环等待 } } catch (Exception e) { e.printStackTrace(); } finally { if (conn != null) { conn.close();
} } return returnedLockId; } /** * 释放锁 */ public boolean releaseLock(String lockKey, String lockId) { Jedis conn = null; boolean flag = false; try { // 1.建立 redis 连接 conn = jedisPool.getResource(); // 2.定义锁的名称 String lockName = "redis_lock_" + lockKey; // 3.如果 value 与 redis 中一直直接删除,否则等待超时 if (lockId.equals(conn.get(lockName))) { conn.del(lockName); flag = true; System.out.println("release lock:" + lockId); } } catch (Exception e) { e.printStackTrace(); } finally { if (conn != null) { conn.close(); } } return flag; } } RedisLockDemo.java package com.zpc.redis.demo; import com.zpc.redis.lock.RedisLock; import org.springframework.util.StringUtils; import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig; public class RedisLockDemo { private static JedisPool pool; static { JedisPoolConfig config = new JedisPoolConfig(); // 设置最大连接数 config.setMaxTotal(200); // 设置最大空闲数 config.setMaxIdle(8); // 设置最大等待时间 config.setMaxWaitMillis(1000 * 100); // 在 borrow 一个 jedis 实例时,是否需要验证,若为 true,则所有 jedis 实例均是可用的 config.setTestOnBorrow(true); pool = new JedisPool(config, "127.0.0.1", 6379, 3000); } private static RedisLock redisLock = new RedisLock(pool); //开启多线程测试 public static void main(String[] args) { for (int i = 0; i < 50; i++) { ThreadRedis threadRedis = new ThreadRedis(redisLock); threadRedis.start(); } } } class ThreadRedis extends Thread { private RedisLock redisLock; public ThreadRedis(RedisLock redisLock) { this.redisLock = redisLock; } @Override public void run() { String identifier = null; try { //20 分钟自动删除(防止死锁),3 秒钟没有获取到则返回失败 identifier = redisLock.lock("foodLock", 3000L, 1000 * 60 * 20L);
if (StringUtils.isEmpty(identifier)) { // 获取锁失败 System.out.println(Thread.currentThread().getName() + ",获取锁失败!!!"); return; } System.out.println(Thread.currentThread().getName() + "获取锁成功,锁 id identifier:" + identifier); System.out.println("执行业务逻辑..."); Thread.sleep(300);//模拟业务处理耗时 } catch (Exception e) { //异常处理 e.printStackTrace(); } finally { // 释放锁 if (!StringUtils.isEmpty(identifier)) { boolean releaseLock = redisLock.releaseLock("foodLock", identifier); if (releaseLock) { System.out.println(Thread.currentThread().getName() + "释放锁成功,锁 id identifier:" + identifier + "\r\n"); } } } } } 启动 redis 测试,控制台输出如下,发现超过 3s 钟获取锁失败: Thread-46 获取锁成功,锁 id identifier:4df77d9f42114757aa3088367d45fabc 执行业务逻辑... release lock:4df77d9f42114757aa3088367d45fabc Thread-46 释放锁成功,锁 id identifier:4df77d9f42114757aa3088367d45fabc Thread-40 获取锁成功,锁 id identifier:224f6f8246f1494f8dc014229c17252e 执行业务逻辑... release lock:224f6f8246f1494f8dc014229c17252e Thread-40 释放锁成功,锁 id identifier:224f6f8246f1494f8dc014229c17252e Thread-19 获取锁成功,锁 id identifier:4a60b879e959432e9314296286e53b0f 执行业务逻辑... release lock:4a60b879e959432e9314296286e53b0f Thread-19 释放锁成功,锁 id identifier:4a60b879e959432e9314296286e53b0f Thread-49 获取锁成功,锁 id identifier:fc25b10808e6419a9a555fa5f6f9c52b
执行业务逻辑... release lock:fc25b10808e6419a9a555fa5f6f9c52b Thread-49 释放锁成功,锁 id identifier:fc25b10808e6419a9a555fa5f6f9c52b Thread-13 获取锁成功,锁 id identifier:6e4f47d09d1145d59c42eb17dfe5fe1d 执行业务逻辑... release lock:6e4f47d09d1145d59c42eb17dfe5fe1d Thread-13 释放锁成功,锁 id identifier:6e4f47d09d1145d59c42eb17dfe5fe1d Thread-20 获取锁成功,锁 id identifier:f43bb0581138443280abd8d503c08f4a 执行业务逻辑... release lock:f43bb0581138443280abd8d503c08f4a Thread-20 释放锁成功,锁 id identifier:f43bb0581138443280abd8d503c08f4a Thread-1 获取锁成功,锁 id identifier:3d797a11690e4f01895539b7faea3157 执行业务逻辑... release lock:3d797a11690e4f01895539b7faea3157 Thread-1 释放锁成功,锁 id identifier:3d797a11690e4f01895539b7faea3157 Thread-22 获取锁成功,锁 id identifier:7de04eaf3e5045b08b4311042c1ed312 执行业务逻辑... release lock:7de04eaf3e5045b08b4311042c1ed312 Thread-22 释放锁成功,锁 id identifier:7de04eaf3e5045b08b4311042c1ed312 Thread-33 获取锁成功,锁 id identifier:cb5b6c0a008f4522b2d6876e5bff8329 执行业务逻辑... release lock:cb5b6c0a008f4522b2d6876e5bff8329 Thread-33 释放锁成功,锁 id identifier:cb5b6c0a008f4522b2d6876e5bff8329 Thread-23 获取锁成功,锁 id identifier:baf7ff7de8aa42e2b4d213bf74f032e9 执行业务逻辑... Thread-4,获取锁失败!!! Thread-14,获取锁失败!!! Thread-11,获取锁失败!!! Thread-43,获取锁失败!!! Thread-37,获取锁失败!!! Thread-50,获取锁失败!!! Thread-28,获取锁失败!!! Thread-26,获取锁失败!!! Thread-30,获取锁失败!!! Thread-44,获取锁失败!!! Thread-39,获取锁失败!!! Thread-48,获取锁失败!!! Thread-32,获取锁失败!!!
Thread-29,获取锁失败!!! Thread-42,获取锁失败!!! Thread-36,获取锁失败!!! Thread-31,获取锁失败!!! Thread-35,获取锁失败!!! Thread-27,获取锁失败!!! Thread-18,获取锁失败!!! Thread-34,获取锁失败!!! Thread-24,获取锁失败!!! Thread-7,获取锁失败!!! Thread-12,获取锁失败!!! Thread-47,获取锁失败!!! Thread-25,获取锁失败!!! Thread-17,获取锁失败!!! Thread-41,获取锁失败!!! Thread-45,获取锁失败!!! Thread-16,获取锁失败!!! Thread-21,获取锁失败!!! Thread-38,获取锁失败!!! Thread-15,获取锁失败!!! Thread-10,获取锁失败!!! Thread-8,获取锁失败!!! Thread-5,获取锁失败!!! Thread-6,获取锁失败!!! Thread-2,获取锁失败!!! Thread-3,获取锁失败!!! Thread-9,获取锁失败!!! release lock:baf7ff7de8aa42e2b4d213bf74f032e9 Thread-23 释放锁成功,锁 id identifier:baf7ff7de8aa42e2b4d213bf74f032e9 在分布式环境中,对竞争资源加锁很重要,使用分布式锁就可以很好地控制资源。在具 体使用中,还需要考虑很多因素,比如超时时间的选定,循环获取锁的时间的选取对并发量 都有很大的影响,本文实现的分布式锁只是一种简单的实现,主要学习思想。 三种分布式对比 上面几种方式,哪种方式都无法做到完美。正如 CAP 定理一样,在复杂性、可用性、 分区容错性等方面无法同时满足,所以要根据不同的应用场景选择最适合的。 从理解的难易程度角度(从低到高)
分享到:
收藏