笔者看过网络上各种各样使用redis实现分布式锁的代码,要么错误,要么片段化,没有一个完整的例子,借这个周末给大家总结一下redis实现分布式锁的两种机制

自旋锁和排他锁

鉴于实现锁的方式不同,那么这里使用策略模式来组织代码

一、自旋锁

分布式锁抽象策略接口

package com.srr.lock;

/**
* @Description 分布式锁的接口
*/
abstract public interface DistributedLock {
/**
* 获取锁
*/
boolean lock();
/**
* 解锁
*/
void unlock();
}

自旋锁策略抽象类,使用模板方法模式构建

package com.srr.lock;

/**
* 自旋锁策略模板
*/
public abstract class SpinRedisLockStrategy implements DistributedLock { private static final Integer retry = 50; //默认重试5次
private static final Long sleeptime = 100L;
protected String lockKey;
protected String requestId;
protected int expireTime; private SpinRedisLockStrategy(){}
public SpinRedisLockStrategy(String lockKey, String requestId, int expireTime){
this.lockKey=lockKey;
this.requestId=requestId;
this.expireTime=expireTime;
}
/**
* 模板方法,搭建的获取锁的框架,具体逻辑交于子类实现
*/
@Override
public boolean lock() {
Boolean flag = false;
try {
for (int i=0;i<retry;i++){
flag = tryLock();
if(flag){
System.out.println(Thread.currentThread().getName()+"获取锁成功");
break;
}
Thread.sleep(sleeptime);
}
}catch (Exception e){
e.printStackTrace();
}
return flag;
}
/**
* 尝试获取锁,子类实现
*/
protected abstract boolean tryLock() ; /**
* 解锁:删除key
*/
@Override
public abstract void unlock();
}

自旋锁实现子类

package com.srr.lock;

import redis.clients.jedis.Jedis;

import java.util.Collections;

/**
* 自旋锁
*/
public class SpinRedisLock extends SpinRedisLockStrategy{ private static final Long RELEASE_SUCCESS = 1L;
private static final String LOCK_SUCCESS = "OK";
private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX"; public SpinRedisLock(String lockKey, String requestId, int expireTime) {
super(lockKey,requestId, expireTime);
} @Override
protected boolean tryLock() {
Jedis jedis = new Jedis("localhost", 6379); //创建客户端,1p和端口号
String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
if (LOCK_SUCCESS.equals(result)) {
return true;
}
return false;
} @Override
public void unlock() {
Jedis jedis = new Jedis("localhost", 6379); //创建客户端,1p和端口号
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
if (RELEASE_SUCCESS.equals(result)) {
System.out.println("lock is unlock");
}
}
}

至此,自旋锁方式实现分布式锁就完成了,下面来看排他锁阻塞的方式实现

二、排他锁

  在实现之前需要大家搞懂一个概念,也就是redis的事件通知:

/**
* 键空间通知,所有通知以 keyspace@ 为前缀
* 键事件通知,所有通知以 keyevent@ 为前缀
* 所有命令都只在键真的被改动了之后,才会产生通知,比如删除foo会产生
* 键空间通知
* “pmessage”,"__ key*__ : * “,”__ keyspace@0__:foo",“set”
* 和键事件通知
* “pmessage”,"__ key*__ : *","__ keyevent@0__:set",“foo”
*/

搞懂概念之后,需要在redis的配置文件redis.conf中将其 notify-keyspace-events "KEA",默认为notify-keyspace-events "",这样才能启动redis的事件监听机制。

排它锁策略抽象类

package com.srr.lock;

import redis.clients.jedis.Jedis;

/**
* @Description 阻塞获取锁,模板类
*/
public abstract class BlockingRedisLockStrategy implements DistributedLock { protected String lockKey;
protected String requestId;
protected int expireTime; private BlockingRedisLockStrategy(){}
public BlockingRedisLockStrategy(String lockKey, String requestId,int expireTime){
this.lockKey=lockKey;
this.requestId=requestId;
this.expireTime=expireTime;
}
/**
* 模板方法,搭建的获取锁的框架,具体逻辑交于子类实现
* @throws Exception
*/
@Override
public final boolean lock() {
//获取锁成功
if (tryLock()){
System.out.println(Thread.currentThread().getName()+"获取锁成功");
return true;
}else{ //获取锁失败
//阻塞一直等待
waitLock();
//递归,再次获取锁
return lock();
}
}
/**
* 尝试获取锁,子类实现
*/
protected abstract boolean tryLock() ;
/**
* 等待获取锁,子类实现
*/
protected abstract void waitLock();
/**
* 解锁:删除key
*/
@Override
public abstract void unlock();
}

排他锁实现子类

package com.srr.lock;

import redis.clients.jedis.Jedis;

import java.util.Collections;

/**
* 排他锁,阻塞
*/
public class BlockingRedisLock extends BlockingRedisLockStrategy { private static final Long RELEASE_SUCCESS = 1L;
private static final String LOCK_SUCCESS = "OK";
private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX"; public BlockingRedisLock(String lockKey, String requestId, int expireTime) {
super(lockKey,requestId, expireTime);
} /**
* 尝试获取分布式锁
* @return 是否获取成功
*/
@Override
public boolean tryLock() {
Jedis jedis = new Jedis("localhost", 6379); //创建客户端,1p和端口号
String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
if (LOCK_SUCCESS.equals(result)) {
return true;
}
return false;
} @Override
public void waitLock() {
//判断key是否存在
Jedis jedis = new Jedis("localhost", 6379); //创建客户端,1p和端口号
KeyExpiredListener keyExpiredListener = new KeyExpiredListener();
/**
* 键空间通知,所有通知以 keyspace@ 为前缀
* 键事件通知,所有通知以 keyevent@ 为前缀
* 所有命令都只在键真的被改动了之后,才会产生通知,比如删除foo会产生
* 键空间通知
* “pmessage”,"__ key*__ : * “,”__ keyspace@0__:foo",“set”
* 和键事件通知
* “pmessage”,"__ key*__ : *","__ keyevent@0__:set",“foo”
*/
//如果要监听某个key的执行了什么操作,就订阅__ keyspace@0__,监听某种操作动了哪些key,就订阅__ keyevent@0__
//这里我们需要监听分布式锁的键被删除了,所以要监听删除动作"__keyspace@0__:"+key
jedis.psubscribe(keyExpiredListener, "__keyspace@0__:"+lockKey);
System.out.println("over");
} /**
* 释放分布式锁
* @return 是否释放成功
*/
@Override
public void unlock() {
Jedis jedis = new Jedis("localhost", 6379); //创建客户端,1p和端口号
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
if (RELEASE_SUCCESS.equals(result)) {
System.out.println("lock is unlock");
}
}
}

redis事件监听类

package com.srr.lock;

import redis.clients.jedis.JedisPubSub;

/**
* redis 事件监听器
*/
public class KeyDelListener extends JedisPubSub {
public KeyDelListener(){ }
// 初始化订阅时候的处理
@Override
public void onPSubscribe(String pattern, int subscribedChannels) {
} // 取得订阅的消息后的处理
@Override
public void onPMessage(String pattern, String channel, String message) {
System.out.println("message == "+message);
this.punsubscribe();
System.out.println("unsubscribe == "+message);
}
}

到这里排他锁的完整代码就写完了,其实对比一下,两者的区别在于lock的实现方式不同,笔者为了确保代码完整性就全部贴上了。

代码写完了那么给一个场景测试一下我们的代码有没有问题,请看下面的测试代码:

这里我们构建一个Lock工具类:

package com.srr.lock;

/**
* 锁工具类
*/
public class Lock {
/**
* 获取锁
*/
boolean lock(DistributedLock lock) {
return lock.lock();
}; /**
* 释放锁
*/
void unlock(DistributedLock lock) {
lock.unlock();
};
}

测试类:

package com.srr.lock;

import redis.clients.jedis.Jedis;

/**
* 测试场景
* count从1加到101
* 使用redis分布式锁在分布式环境下保证结果正确
*/
public class T { volatile int count = 1; public void inc(){
for(int i = 0;i<100;i++){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
System.out.println("count == "+count);
}
} public int getCount(){
return count;
} public static void main(String[] args) {
final T t = new T();
final Lock lock = new Lock();
//final RedisLock redisLock = new BlockingRedisLock("","1",100000,jedis);
final DistributedLock distributedLock = new SpinRedisLock("test","1",100000);
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
if(lock.lock(distributedLock)){
t.inc();
System.out.println("t1 running");
System.out.println("t1 == count == "+ t.getCount());
lock.unlock(distributedLock);
} }
}); Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
if(lock.lock(distributedLock)) {
t.inc();
System.out.println("t2 running");
System.out.println("t2 == count == " + t.getCount());
lock.unlock(distributedLock);
} }
}); t1.start();
t2.start();
}
}

测试结果:

到这里,全部代码就完成了,如果想使用zookeeper实现分布式锁只需要抽象出一个策略类实现DistributedLock接口即可。是不是很方便呢。

原创不易,多多关注!

Redis实现分布式锁(设计模式应用实战)的更多相关文章

  1. SpringBoot电商项目实战 — Redis实现分布式锁

    最近有小伙伴发消息说,在Springboot系列文第二篇,zookeeper是不是漏掉了?关于这个问题,其实我在写第二篇的时候已经考虑过,但基于本次系列文章是实战练习,在项目里你能看到Zookeepe ...

  2. 基于Redis实现分布式锁实战

    背景在很多互联网产品应用中,有些场景需要加锁处理,比如:秒杀,全局递增ID,楼层生成等等.大部分的解决方案是基于DB实现的,Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端 ...

  3. Redis实战(十二)Redis实现分布式锁

    序言 SET my_key my_value NX PX milliseconds 资料 如何优雅地用Redis实现分布式锁?

  4. 不用找了,基于 Redis 的分布式锁实战来了!

    Java技术栈 www.javastack.cn 优秀的Java技术公众号 作者:菜蚜 my.oschina.net/wnjustdoit/blog/1606215 前言:在分布式环境中,我们经常使用 ...

  5. Spring Boot Redis 实现分布式锁,真香!!

    之前看很多人手写分布式锁,其实 Spring Boot 现在已经做的足够好了,开箱即用,支持主流的 Redis.Zookeeper 中间件,另外还支持 JDBC. 本篇栈长以 Redis 为例(这也是 ...

  6. Redis 中的原子操作(3)-使用Redis实现分布式锁

    Redis 中的分布式锁如何使用 分布式锁的使用场景 使用 Redis 来实现分布式锁 使用 set key value px milliseconds nx 实现 SETNX+Lua 实现 使用 R ...

  7. 基于redis 实现分布式锁的方案

    在电商项目中,经常有秒杀这样的活动促销,在并发访问下,很容易出现上述问题.如果在库存操作上,加锁就可以避免库存卖超的问题.分布式锁使分布式系统之间同步访问共享资源的一种方式 基于redis实现分布式锁 ...

  8. 用Redis构建分布式锁-RedLock(真分布)

    在不同进程需要互斥地访问共享资源时,分布式锁是一种非常有用的技术手段. 有很多三方库和文章描述如何用Redis实现一个分布式锁管理器,但是这些库实现的方式差别很大,而且很多简单的实现其实只需采用稍微增 ...

  9. 用Redis实现分布式锁 与 实现任务队列(转)

    这一次总结和分享用Redis实现分布式锁 与 实现任务队列 这两大强大的功能.先扯点个人观点,之前我看了一篇博文说博客园的文章大部分都是分享代码,博文里强调说分享思路比分享代码更重要(貌似大概是这个意 ...

随机推荐

  1. 使用SVG内置API计算图形或点经过transform之后的新坐标

    一个应用场景是,点击一条路径,显示该路径的控制点.因为有transform变形( 平移.缩放.倾斜.旋转等变换),所以获取变形后的新坐标需要计算. 纯数学的方法,就是用2D变换矩阵的一些公式去运算,过 ...

  2. 化繁为简,弱监督目标定位领域的新SOTA - 伪监督目标定位方法(PSOL) | CVPR 2020

    论文提出伪监督目标定位方法(PSOL)来解决目前弱监督目标定位方法的问题,该方法将定位与分类分开成两个独立的网络,然后在训练集上使用Deep descriptor transformation(DDT ...

  3. [译]谈谈SpringBoot 事件机制

    要"监听"事件,我们总是可以将"监听器"作为事件源中的另一个方法写入事件,但这将使事件源与监听器的逻辑紧密耦合. 对于实际事件,我们比直接方法调用更灵活.我们可 ...

  4. day7作业

    # day7作业 # 1. 使用while循环输出1 2 3 4 5 6 8 9 10 count = 1 while count < 11: if count == 7: count += 1 ...

  5. 13. 罗马数字转整数----LeetCode

    13. 罗马数字转整数 罗马数字包含以下七种字符: I, V, X, L,C,D 和 M. 字符 数值 I 1 V 5 X 10 L 50 C 100 D 500 M 1000 例如, 罗马数字 2 ...

  6. wechall前十题

    今天开始打一打wechall 累了打wechall,不累的时候开始打buu 第一题:Get Sourced 查看源代码即可,拉到底部 第二题:Stegano 属于misc的范畴,直接下载下来,然后no ...

  7. [GO] mac安装GO 初次尝试

    其实试了好多方法,我用的是下面这种方法,简单快捷! 安装homebrew 在终端输入命令 ruby -e "$(curl -fsSL https://raw.githubuserconten ...

  8. 【题解】P1972 [SDOI2009]HH的项链 - 树状数组

    P1972 [SDOI2009]HH的项链 声明:本博客所有题解都参照了网络资料或其他博客,仅为博主想加深理解而写,如有疑问欢迎与博主讨论✧。٩(ˊᗜˋ)و✧*。 题目描述 \(HH\) 有一串由各种 ...

  9. mysql 更改默认字符集

    mysql 默认字符集概述 首先,MySQL的字符集问题主要是两个概念: haracter Sets Collations 前者是字符内容及编码,后者是对前者进行比较操作的一些规则.这两个参数集可以在 ...

  10. vue3开发饿了么商城2020年新版本

    带手机验证码登陆, 带全套购物车系统 带数据库 前后端分离开发 带定位用户功能 数据库代码为本地制作好了 带支付宝支付系统 带django开发服务器接口教程 地址:   https://www.dua ...