概述

目前几乎很多大型网站及应用都是分布式部署的,分布式场景中的数据一致性问题一直是一个比较重要的话题。分布式的CAP理论告诉我们“任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项。”所以,很多系统在设计之初就要对这三者做出取舍。在互联网领域的绝大多数的场景中,都需要牺牲强一致性来换取系统的高可用性,系统往往只需要保证“最终一致性”,只要这个最终时间是在用户可以接受的范围内即可。

在很多场景中,我们为了保证数据的最终一致性,需要很多的技术方案来支持,比如分布式事务、分布式锁等。

选用Redis实现分布式锁原因

  • Redis有很高的性能
  • Redis命令对此支持较好,实现起来比较方便

在此就不介绍Redis的安装了,具体在Linux和Windows中的安装可以查看我前面的博客。
https://www.cnblogs.com/hongmoshui/p/10599050.html

使用命令介绍

SETNX

SETNX key val
当且仅当key不存在时,set一个key为val的字符串,返回1;若key存在,则什么都不做,返回0。

expire

expire key timeout
为key设置一个超时时间,单位为second,超过这个时间锁会自动释放,避免死锁。

delete

delete key
删除key

在使用Redis实现分布式锁的时候,主要就会使用到这三个命令。

实现

使用的是jedis来连接Redis。

实现思想

  • 获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断。
  • 获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
  • 释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。

分布式锁的核心代码如下:

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Transaction;
import redis.clients.jedis.exceptions.JedisException; import java.util.List;
import java.util.UUID; /**
* Created by liuyang on 2017/4/20.
*/
public class DistributedLock {
private final JedisPool jedisPool; public DistributedLock(JedisPool jedisPool) {
this.jedisPool = jedisPool;
} /**
* 加锁
* @param locaName 锁的key
* @param acquireTimeout 获取超时时间
* @param timeout 锁的超时时间
* @return 锁标识
*/
public String lockWithTimeout(String locaName,
long acquireTimeout, long timeout) {
Jedis conn = null;
String retIdentifier = null;
try {
// 获取连接
conn = jedisPool.getResource();
// 随机生成一个value
String identifier = UUID.randomUUID().toString();
// 锁名,即key值
String lockKey = "lock:" + locaName;
// 超时时间,上锁后超过此时间则自动释放锁
int lockExpire = (int)(timeout / 1000); // 获取锁的超时时间,超过这个时间则放弃获取锁
long end = System.currentTimeMillis() + acquireTimeout;
while (System.currentTimeMillis() < end) {
if (conn.setnx(lockKey, identifier) == 1) {
conn.expire(lockKey, lockExpire);
// 返回value值,用于释放锁时间确认
retIdentifier = identifier;
return retIdentifier;
}
// 返回-1代表key没有设置超时时间,为key设置一个超时时间
if (conn.ttl(lockKey) == -1) {
conn.expire(lockKey, lockExpire);
} try {
Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
} catch (JedisException e) {
e.printStackTrace();
} finally {
if (conn != null) {
conn.close();
}
}
return retIdentifier;
} /**
* 释放锁
* @param lockName 锁的key
* @param identifier 释放锁的标识
* @return
*/
public boolean releaseLock(String lockName, String identifier) {
Jedis conn = null;
String lockKey = "lock:" + lockName;
boolean retFlag = false;
try {
conn = jedisPool.getResource();
while (true) {
// 监视lock,准备开始事务
conn.watch(lockKey);
// 通过前面返回的value值判断是不是该锁,若是该锁,则删除,释放锁
if (identifier.equals(conn.get(lockKey))) {
Transaction transaction = conn.multi();
transaction.del(lockKey);
List<Object> results = transaction.exec();
if (results == null) {
continue;
}
retFlag = true;
}
conn.unwatch();
break;
}
} catch (JedisException e) {
e.printStackTrace();
} finally {
if (conn != null) {
conn.close();
}
}
return retFlag;
}
}

测试

下面就用一个简单的例子测试刚才实现的分布式锁。
例子中使用50个线程模拟秒杀一个商品,使用--运算符来实现商品减少,从结果有序性就可以看出是否为加锁状态。

模拟秒杀服务,在其中配置了jedis线程池,在初始化的时候传给分布式锁,供其使用。

import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig; /**
* Created by liuyang on 2017/4/20.
*/
public class Service {
private static JedisPool pool = null; 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);
} DistributedLock lock = new DistributedLock(pool); int n = 500; public void seckill() {
// 返回锁的value值,供释放锁时候进行判断
String indentifier = lock.lockWithTimeout("resource", 5000, 1000);
System.out.println(Thread.currentThread().getName() + "获得了锁");
System.out.println(--n);
lock.releaseLock("resource", indentifier);
}
}
// 模拟线程进行秒杀服务 public class ThreadA extends Thread {
private Service service; public ThreadA(Service service) {
this.service = service;
} @Override
public void run() {
service.seckill();
}
} public class Test {
public static void main(String[] args) {
Service service = new Service();
for (int i = 0; i < 50; i++) {
ThreadA threadA = new ThreadA(service);
threadA.start();
}
}
}

结果如下,结果为有序的。

若注释掉使用锁的部分

public void seckill() {
// 返回锁的value值,供释放锁时候进行判断
//String indentifier = lock.lockWithTimeout("resource", 5000, 1000);
System.out.println(Thread.currentThread().getName() + "获得了锁");
System.out.println(--n);
//lock.releaseLock("resource", indentifier);
}

从结果可以看出,有一些是异步进行的。

在分布式环境中,对资源进行上锁有时候是很重要的,比如抢购某一资源,这时候使用分布式锁就可以很好地控制资源。
当然,在具体使用中,还需要考虑很多因素,比如超时时间的选取,获取锁时间的选取对并发量都有很大的影响,上述实现的分布式锁也只是一种简单的实现,主要是一种思想。

下一次我会使用zookeeper实现分布式锁,使用zookeeper的可靠性是要大于使用redis实现的分布式锁的,但是相比而言,redis的性能更好。

上面的代码可以在我的GitHub中进行查看,地址如下:
https://github.com/hongmoshui/DistributedLock

原文链接:https://www.cnblogs.com/liuyang0/p/6744076.html

Redis分布式锁【实战】的更多相关文章

  1. Redis分布式锁实战

    什么是分布式锁 在单机部署的情况下,要想保证特定业务在顺序执行,通过JDK提供的synchronized关键字.Semaphore.ReentrantLock,或者我们也可以基于AQS定制化锁.单机部 ...

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

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

  3. 基于SpringBoot AOP面向切面编程实现Redis分布式锁

    基于SpringBoot AOP面向切面编程实现Redis分布式锁 基于SpringBoot AOP面向切面编程实现Redis分布式锁 基于SpringBoot AOP面向切面编程实现Redis分布式 ...

  4. 手撕redis分布式锁,隔壁张小帅都看懂了!

    前言 上一篇老猫和小伙伴们分享了为什么要使用分布式锁以及分布式锁的实现思路原理,目前我们主要采用第三方的组件作为分布式锁的工具.上一篇运用了Mysql中的select ...for update实现了 ...

  5. Redisson 分布式锁实战与 watch dog 机制解读

    Redisson 分布式锁实战与 watch dog 机制解读 目录 Redisson 分布式锁实战与 watch dog 机制解读 背景 普通的 Redis 分布式锁的缺陷 Redisson 提供的 ...

  6. Redis 分布式锁|从青铜到钻石的五种演进方案

    缓存系列文章: 缓存实战(一):20 图 |6 千字|缓存实战(上篇) 缓存实战(二):Redis 分布式锁|从青铜到钻石的五种演进方案 缓存实战(三):分布式锁中的王者方案 - Redisson 上 ...

  7. Redis分布式锁 (图解-秒懂-史上最全)

    文章很长,而且持续更新,建议收藏起来,慢慢读! 高并发 发烧友社群:疯狂创客圈(总入口) 奉上以下珍贵的学习资源: 疯狂创客圈 经典图书 : 极致经典 + 社群大片好评 < Java 高并发 三 ...

  8. 从Redis分布式缓存实战入手到底层原理分析、面面俱到覆盖大厂面试考点

    概述 官方说明 Redis官网 https://redis.io/ 最新版本6.2.6 Redis中文官网 http://www.redis.cn/ 不过中文官网的同步更新维护相对要滞后不少时间,但对 ...

  9. 利用redis分布式锁的功能来实现定时器的分布式

    文章来源于我的 iteye blog http://ak478288.iteye.com/blog/1898190 以前为部门内部开发过一个定时器程序,这个定时器很简单,就是配置quartz,来实现定 ...

  10. Redis分布式锁

    Redis分布式锁 分布式锁是许多环境中非常有用的原语,其中不同的进程必须以相互排斥的方式与共享资源一起运行. 有许多图书馆和博客文章描述了如何使用Redis实现DLM(分布式锁管理器),但是每个库都 ...

随机推荐

  1. postgresql获取表最后更新时间(通过发布订阅机制将消息发送给应用程序)

    一.创建测试表 CREATE TABLE weather( city ), temp_lo int, --最低温度 temp_hi int, --最高温度 prcp real, --湿度 date d ...

  2. Flashtext:大规模数据清洗的利器

    Flashtext:大规模数据清洗的利器 在这篇文章中,我们将介绍一种新的关键字搜索和替换的算法:Flashtext 算法.Flashtext 算法是一个高效的字符搜索和替换算法.该算法的时间复杂度不 ...

  3. Flask中的实例化配置

    也就是在app=Flask(__name__)括号中的参数 1.static_folder = 'static', # 静态文件目录的路径 默认当前项目中的static目录 2.static_url_ ...

  4. Sails.js中文文档,Sails中文文档

    Sails.js中文文档   http://sailsdoc.swift.ren/ Sails.js是一个Web框架,可以于轻松构建自定义,企业级Node.js Apps.它在设计上类似于像Ruby ...

  5. dataframe指定位置插入行

    1 loc( ) 函数可以定位行后,并直接赋值插入. 如下可见loc函数对直接改变原来行的值 df = pd.DataFrame({ '动物' : ['狗','猫','兔'], '数量' : [ 3, ...

  6. Fiddler抓百度请求

    fiddler是一个很好的抓包工具,默认是抓http请求的,对于pc上的https请求,会提示网页不安全,这时候需要在浏览器上安装证书. 一.网页不安全 1.用fiddler抓包时候,打开百度网页:h ...

  7. 浅谈Java反射机制 之 获取类的字节码文件 Class.forName("全路径名") 、getClass()、class

    另一个篇:获取 类 的 方法 和 属性(包括构造函数) 先贴上Java反射机制的概念: AVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法: 对于任意一个对象,都能够调用它 ...

  8. DFS序1

    给一棵有根树,这棵树由编号为1..N的N个结点组成.根结点的编号为R.每个结点都有一个权值,结点i的权值为vi .接下来有M组操作,操作分为两类:1 a x,表示将结点a的权值增加x:2 a,表示求结 ...

  9. Win10.更新

    1.资料: win10怎么关闭自动更新?win10关闭自动更新步骤-太平洋IT百科.html(https://product.pconline.com.cn/itbk/software/win10/1 ...

  10. java webservice - cxf使用总结 一

    1.创建maven项目 加入pom依赖 <dependency> <groupId>org.apache.cxf</groupId> <artifactId& ...