分布式锁(2) ----- 基于redis的分布式锁
分布式锁系列文章
分布式锁(1) ----- 介绍和基于数据库的分布式锁
分布式锁(2) ----- 基于redis的分布式锁
分布式锁(3) ----- 基于zookeeper的分布式锁
代码:https://github.com/shuo123/distributeLock
Redis单机版实现
set和lua实现
获取锁
SET resource_name my_random_value NX PX 30000
NX key不存在时才set
PX 设置过期时间
my_random_value 要保证每台客户端的每个锁请求唯一,可以使用UUID+ThreadID
该命令在Redis 2.6.12才有,网上有基于setnx、epire的实现和基于setnx、get、getset的实现,这些多多少少都有点瑕疵,大概率是旧版本的redis实现,建议高版本的redis还是使用这个实现。
释放锁
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
通过上述lua脚本实现,先判断锁是否该请求拥有,防止误解锁。
java代码
public boolean tryLock(long waitTime, long leaseTime) {
long end = Calendar.getInstance().getTimeInMillis() + waitTime;
Jedis jedis = jedisPool.getResource();
try {
do {
String result = jedis.set(lockName, getClientId(), "NX", "EX", leaseTime);
if ("OK".equals(result)) {
return true;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
return false;
}
} while (Calendar.getInstance().getTimeInMillis() < end);
}finally {
if(jedis != null) {
jedis.close();
}
}
return false;
}
public boolean unlock() {
String lua = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +
" return redis.call(\"del\",KEYS[1])\n" +
"else\n" +
" return 0\n" +
"end";
Jedis jedis = jedisPool.getResource();
try {
Object obj = jedis.eval(lua, Collections.singletonList(lockName), Collections.singletonList(getClientId()));
if (obj.equals(1)) {
return true;
}
}finally {
if(jedis != null){
jedis.close();
}
}
return false;
}
测试代码
public void lockTest() {
//用线程模拟进程
ExecutorService threadPool = Executors.newFixedThreadPool(20);
CyclicBarrier barrier = new CyclicBarrier(20);
for (int i = 0; i < 20; i++) {
threadPool.execute(new RedisLockTest(barrier));
}
threadPool.shutdown();
while (!threadPool.isTerminated()) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private static class RedisLockTest implements Runnable{
private CyclicBarrier barrier;
RedisLockTest(CyclicBarrier barrier){
this.barrier = barrier;
}
@Override
public void run() {
//模拟一台客户端一个jedisPool
JedisPool jedisPool = new JedisPool("192.168.9.150", 6379);
try {
DistributeLock lock = new SingletonRedisLock(jedisPool, "lock");
barrier.await();
boolean flag = lock.tryLock(Integer.MAX_VALUE, 300);
try {
System.out.println(Thread.currentThread().getId() + "get lock:flag=" + flag);
Thread.sleep(100);
System.out.println(Thread.currentThread().getId() + "get unlock");
} finally {
lock.unlock();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
jedisPool.close();
}
}
}
存在的问题
该实现简单,但存在以下问题:
1.没有实现可重入
2.没有锁续租,如果代码在锁的租期内没有执行完成,那么锁过期会导致另一个客户端获取锁
Redisson实现
Redisson是redis推荐的分布式锁实现开源项目。Redisson的分布式锁实现可重入,同时有LockWatchDogTimeout来实现锁续约
github:https://github.com/redisson/redisson
private static class RedissonLockTest implements Runnable{
private CyclicBarrier barrier;
RedissonLockTest(CyclicBarrier barrier){
this.barrier = barrier;
}
@Override
public void run() {
Config config = new Config();
config.useSingleServer().setAddress("redis://192.168.9.150:6379");
RedissonClient client = Redisson.create(config);
try {
RLock lock = client.getLock("lock");
barrier.await();
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "get lock");
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + "get unlock");
} finally {
lock.unlock();
}
}catch (Exception e){
e.printStackTrace();
}finally {
client.shutdown();
}
}
}
Redis多实例版实现
单机实现在多实例下问题
如果Redis实现了集群,由于主从之间时通过异步复制的,假设客户端A在主机上获得锁,这时在未将锁数据复制到从机时,主机挂了,从机切换为主机,那么从机没有这条数据,客户端B同样可以获得锁。
RedLock算法
使用多个独立(非集群)的实例来实现分布式锁,由于实例独立不需复制同步,所以没有上述问题;而保证可用性的是靠数据冗余,将数据多存放几份在不同的实例上。算法如下:
- 使用相同的key和value从N个实例上获取锁;
- 当从大于(N/2+1)个实例获取锁的时间<锁的过期时间,才获取锁成功
- 如果获取失败,解锁所有实例
Redisson实现
public void redLockTest() {
Config config = new Config();
config.useSingleServer().setAddress("redis://192.168.9.150:7000");
RedissonClient client = Redisson.create(config);
Config config1 = new Config();
config1.useSingleServer().setAddress("redis://192.168.9.150:7001");
RedissonClient client1 = Redisson.create(config1);
Config config2 = new Config();
config2.useSingleServer().setAddress("redis://192.168.9.150:7002");
RedissonClient client2 = Redisson.create(config2);
try{
RLock lock = client.getLock("lock");
RLock lock1 = client1.getLock("lock");
RLock lock2 = client2.getLock("lock");
RedissonRedLock redLock = new RedissonRedLock(lock, lock1, lock2);
redLock.lock();
try{
System.out.println(Thread.currentThread().getName() + "get lock");
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + "get unlock");
} finally {
redLock.unlock();
}
}catch (Exception e){
e.printStackTrace();
}finally {
client.shutdown();
client1.shutdown();
client2.shutdown();
}
}
参考资料
https://redis.io/topics/distlock
https://github.com/redisson/redisson/wiki
分布式锁(2) ----- 基于redis的分布式锁的更多相关文章
- 基于redis 实现分布式锁的方案
在电商项目中,经常有秒杀这样的活动促销,在并发访问下,很容易出现上述问题.如果在库存操作上,加锁就可以避免库存卖超的问题.分布式锁使分布式系统之间同步访问共享资源的一种方式 基于redis实现分布式锁 ...
- 基于redis的分布式锁
<?php /** * 基于redis的分布式锁 * * 参考开源代码: * http://nleach.com/post/31299575840/redis-mutex-in-php * * ...
- 基于Redis的分布式锁真的安全吗?
说明: 我前段时间写了一篇用consul实现分布式锁,感觉理解的也不是很好,直到我看到了这2篇写分布式锁的讨论,真的是很佩服作者严谨的态度, 把这种分布式锁研究的这么透彻,作者这种技术态度真的值得我好 ...
- 基于 Redis 的分布式锁
前言 分布式锁在分布式应用中应用广泛,想要搞懂一个新事物首先得了解它的由来,这样才能更加的理解甚至可以举一反三. 首先谈到分布式锁自然也就联想到分布式应用. 在我们将应用拆分为分布式应用之前的单机系统 ...
- 基于redis的分布式锁(转)
基于redis的分布式锁 1 介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分 ...
- 基于redis的分布式锁实现
1.分布式锁介绍 在计算机系统中,锁作为一种控制并发的机制无处不在. 单机环境下,操作系统能够在进程或线程之间通过本地的锁来控制并发程序的行为.而在如今的大型复杂系统中,通常采用的是分布式架构提供服务 ...
- 基于redis的分布式锁(不适合用于生产环境)
基于redis的分布式锁 1 介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分 ...
- 基于 redis 的分布式锁实现 Distributed locks with Redis debug 排查错误
小结: 1. 锁的实现方式,按照应用的实现架构,可能会有以下几种类型: 如果处理程序是单进程多线程的,在 python下,就可以使用 threading 模块的 Lock 对象来限制对共享变量的同步访 ...
- 基于 Redis 做分布式锁
基于 REDIS 的 SETNX().EXPIRE() 方法做分布式锁 setnx() setnx 的含义就是 SET if Not Exists,其主要有两个参数 setnx(key, value) ...
- 转载:基于Redis实现分布式锁
转载:基于Redis实现分布式锁 ,出处: http://blog.csdn.net/ugg/article/details/41894947 背景在很多互联网产品应用中,有些场景需要加锁处理,比如 ...
随机推荐
- Python3-socket模块-低级网络接口
Python3中的socket模块提供了对访问套接字(socket)的接口 socket可以理解为是一个管道,通过这个管道可以使两个不同的程序通过网络进行通信,在Python中的scoket()函数可 ...
- 解Bug之路-中间件"SQL重复执行"
前言 我们的分库分表中间件在线上运行了两年多,到目前为止还算稳定.在笔者将精力放在处理各种灾难性事件(例如中间件物理机宕机/数据库宕机/网络隔离等突发事件)时.竟然发现还有一些奇怪的corner ca ...
- Spring Security(三) —— 核心配置解读
摘要: 原创出处 https://www.cnkirito.moe/spring-security-3/ 「老徐」欢迎转载,保留摘要,谢谢! 3 核心配置解读 上一篇文章<Spring Secu ...
- Tomcat的介绍
Tomcat简介 Tomcat 服务器是一个免费的开放源代码的Web 应用服务器,属于轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试JSP 程序的首选. 支持Se ...
- JS数据类型判断的几种方法
JS数据类型判断 JavaScript 中常见数据类型有Number.String.Boolean.Object.Array.Json.Function.Date.RegExp.Error.undef ...
- web页面的重构和回流【转载】
在了解什么是重构和回流之前,我们应该先看看浏览器是怎么渲染的? 浏览器的渲染过程: 1.处理HTML脚本,生成DOM树(DOM树里包含所有的HTML标签,包括display:none和js动态添加的元 ...
- 使用centos8搭建僵尸毁灭工程(PZ)服务器
自从领到了阿里云的ECS服务器后,本着既能熟悉linux操作,又能为喜欢的游戏搭建一个可以和朋友一起联机的服务器(游戏提供自建本地服务器极渣)的想法.作为linux小白的我翻遍了网上的资料,用了五天终 ...
- axure8.0实现tab切换
这两天帮忙做产品原型图,tab切换做一次忘一次,这次索性记录一下,免得下次再忘了. 1.在元件库中拉出来一个动态面板,双击动态面板,添加状态,添加完成之后,点击红色标注的地方,开始编辑每个面板状态: ...
- Javascript安全编程规范
命名 1.原则——编码格式UTF-8,根据功能为变量与方法命名(尽量不缩写) 2.类——构造函数/类 使用驼峰命名法,且首字母大写 3.方法——动词.动宾结构,使用驼峰命名法,首字母小写 例如:get ...
- MySQL 对window函数执行sum函数疑似Bug
MySQL 对window函数执行sum函数疑似Bug 使用MySql的窗口函数统计数据时,发现一个小的问题,与大家一起探讨下. 环境配置: mysql-installer-community-8.0 ...