首页 > 数据库 >分布式锁--redis 缓存实现

分布式锁--redis 缓存实现

时间:2022-12-14 18:36:32浏览次数:57  
标签:currentValue 缓存 -- lock redis value key 分布式


分布式锁

目前几乎所有的大型网站及应用都是采用分布式部署的方式,分布式系统开发带来的优点很多,高可用,高并发,水平扩展,分开部署等。但分布式的开发也带来了一些新问题,有的时候,我们需要保证一个方法在同一时间内只能被同一个线程执行。在单机环境中,Java中其实提供了很多并发处理相关的API  ,也就是我们常说的“锁”(如synchronized,lock),但是这些API在分布式场景中就无能为力了,也就是说Java没有提供分布式锁的功能。

基于分布式锁的实现有多种方案,常见的有基于数据库本身的锁来实现,或者基于zookeeper的API实现,或者是基于缓存来实现分布式锁等等,这些方案都各有可取之处,今天我们介绍的是基于redis的缓存实现分布式锁的方案。

在使用redis 实现,又两种方案。

第一种方案自己封装实现Lock和unLock函数。

第二种方案 Redisson 实现,这是一个牛逼的框架,属于二次开发,在redis的基础上开发得到的Redisson。

reids 实现分布式锁的原理

基于Redis的分布式锁,是常见的分布式锁实现方式之一。它通过以下一些机制来解决分布式锁的相关问题。

1. 通过setnx命令的排他性来达成锁的互斥。setnx命令的特性是,在插入一个key、value对时,当key不存在时,可以设置成功,返回1,当key已经存在时,会设置失败,返回0。

2. 通过锁超时来达成避免死锁的问题。当在Redis中插入key成功后,即认为获取锁成功,当锁使用完成后,需要删除相应key,即释放锁。当删除失败时,就会出现死锁问题。这时候,可以通过在set时,设置超时时间来解决。如设置超时时间为10s等。

3. 通过独立线程来完成锁续租。由于加入了锁超时机制,所以,有可能会发生任务还未完成,锁就被释放的问题。一些分布式锁的开源框架,会通过一个单独线程,来不断通过exprie命令重刷key的过期时间,来达成一个锁续租的目的。

4. 通过value来配合解决锁重入问题。在分布式锁下,value可以是当前进程或线程的一个唯一id,当获取锁时,如果发现锁已经存在,可以根据value值来判断,是否可以获取锁,来达成一个锁重入的目的。

第一种方案实现:

先在pom.xml中引入依赖

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

java 实现 redis如下:

public class RedisLock {

@Autowired
private StringRedisTemplate redisTemplate;

/**
* 加锁
*
* @param key
* @param value 当前时间+超时时间
* @return
*/
public boolean lock(String key, String value) {
//相当于setnx命令
if (redisTemplate.opsForValue().setIfAbsent(key, value)) {
return true;
}
//下面的这段代码是判断之前加的锁是否超时,是的话就更新,一定要加这段代码
//不然就有可能出现死锁。
String currentValue = redisTemplate.opsForValue().get(key);
//如果锁过期
if (!StringUtils.isEmpty(currentValue)
&& Long.parseLong(currentValue) < System.currentTimeMillis()) {
//获取上一个锁的时间,这段代码的判断是防止多线程进入这里,只会有一个线程拿到锁
String oldValue = redisTemplate.opsForValue().getAndSet(key, value);
if (!StringUtils.isEmpty(oldValue)
&& oldValue.equals(currentValue)) {
return true;
}
}
return false;
}

/**
* 解锁
*
* @param key
* @param value
*/
public void unLock(String key, String value) {
try {
String currentValue = redisTemplate.opsForValue().get(key);
if (!StringUtils.isEmpty(currentValue)
&& currentValue.equals(value)) {
redisTemplate.opsForValue().getOperations().delete(key);
}
} catch (Exception e) {
log.error("【redis分布式锁】 解锁异常,{}", e);
}
}
}

第二种实现方案

pom.xml 的引入

<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.11.1</version>
</dependency>

java 代码实现:

import org.junit.jupiter.api.Test;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.config.Config;

public class RedisTickTest {

private int count = 100;

private RLock lock =getRedissonLock();

/**
* 获取锁
* @return
*/
private RLock getRedissonLock(){

Config config = new Config();
config.useSingleServer().setAddress("redis://10.0.0.1:6379").setPassword("123456");
Redisson redisson = (Redisson) Redisson.create(config);
RLock lock = redisson.getLock("xxx");
return lock;



}


@Test
public void tickeTest() throws Exception {

RedisTickTest.TickRunnable tr = new RedisTickTest.TickRunnable();

new Thread(tr,"窗口A").start();
new Thread(tr,"窗口B").start();
new Thread(tr,"窗口c").start();

Thread.sleep(10000);
}


public class TickRunnable implements Runnable {

@Override
public void run() {

while (count>0) {
lock.lock();
try {
if(count>0){
System.out.println(Thread.currentThread().getName()+"售出:"+ count -- + " 张票");
}
}finally {
lock.unlock();
}

}
}
}
}

上面的例子相对比较简单,因为精力能力有限,楼主没法给大家展示真正的分布式锁的实现效果,但从原理上其实是一样的,都是用redis的setnx命令来加上锁,保证分布式环境下锁住的对象只能被一个线程访问,而且从实现方式上来说也比较简单  。

标签:currentValue,缓存,--,lock,redis,value,key,分布式
From: https://blog.51cto.com/u_15461374/5938160

相关文章

  • spring boot + Redis实现消息队列-生产消费者
    实现思路:Redis本身提供了一个发布/订阅模式,但生产消费者模式需要我们自己去实现。利用Redis中的队列,将新消息放入名称为xx的队列末尾,完成消息生产者。启动一个线程,使用​​b......
  • 1.3Dmax界面及试图操作
    一.初始界面1.菜单栏(软件的核心)2.工具栏3.石墨工具4.命令板块5.场景大纲tools-->newSceneExplorer创建的物体信息就会从出现在这里6.视图窗口ctrl+......
  • 服务器接口安全设计之--防止重复提交
    这里介绍是通过redis+token来实现防止重复提交问题。1.pom文件依赖<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boo......
  • redis5-cluster 集群搭建
    1、安装环境信息centos7redis52、整体集群信息#以直接在一台机器上实现上述的伪集群,因为端口号特意设置为不同的。#重点:不论机器多少,对于部署过程都是一样的,只不过是在不......
  • 8 多路召回的融合排序
    融合排序:将多种召回排序的列表进行融合为一个列表......
  • 监控报警体系:Prometheus和Grafana
    总体prometheus全链路监控报警,在当今云原生时代可观测领域,Prometheus + Grafana 成为可观测性事实标准。采集数据:运维团队可以使用 Prometheus 监控云原生 Kub......
  • Java基础之变量
    变量变量为可以变化的量。java是一种强类型语言,每个变量都必须声明其类型。Java变量是程序中最基本的存储单位,其要素包括:变量名,变量类型和作用域。 数据类型变量名=......
  • nginx 反向代理多示例----实现Session共享
    关于session共享的方式有多种:(1)通过nginx的ip_hash,根据ip将请求分配到对应的服务器(2)基于关系型数据库存储(3)基于cookie存储(4)服务器内置的session复制域。(5)基于nosq......
  • C# 获取指定固定范围内的比例固定的实际图片大小
    Rectangle?getImageRegion(PictureBoxpic){if(pic.Image==null)returnnull;varpicHeight=pic.Width*p......
  • gameboy GB 第二次机器人大战G 金手指 VBA
    ------------------------------------------------------------------------------------------------------------------------------------------------------------......