当前位置: 首页 > article >正文

使用原生Redis完成分布式锁

使用原生Redis完成分布式锁

假设我们需要对redis中对商品库存进行减少,但是redis中可能会不存在此商品信息,此时我们就需要从数据库中取出库存将其放入redis。我们要对这个操作进行添加分布式锁。

首先,先理清业务的流程:

  1. 检查redis中是否存有商品数据,如果没有就开始获取锁。
  2. 自旋来获取锁,获取到锁之后判断是否已经有人完成添加此商品到redis的操作如果已经完成就退出,否则就进行获取数据并添加到redis中。
  3. 释放锁。

整个业务最重要的就是如何获取锁和释放锁,要保证整个过程不会出现任何的并发问题(两个线程拿到同一个商品的锁之类的)。

redis实现分布式锁和synchronized的操作相似

问题
如果有线程获取到锁,但是在执行业务的时候报错了这个锁就不会被释放怎么办?

解决方法:在向redis中添加数据的时候,给数据添加一个时间,时间一到立马删除,但是这两个操作必须是一起执行的,所以需要使用lua脚本来保证原子性。

在释放锁的过程中释放了别的其他线程的锁?

线程1获取到锁之后,在执行业务的时候时间太长,导致redis自动消除了数据完成了锁的释放,然后线程2获取到锁开始执行业务,这时线程1执行完成开始释放锁,这个时候会导致线程1释放了线程2的锁,然后线程2释放线程3······。

解决方法:在添加数据的时候给这个数据添上自己的标记,在释放的时候检查标记是否当前线程的标记,因为在删除数据的时候需要先检查数据有没有被打上标记,所以需要使用lua脚本来保证操作的原子性。

如何保证一定会执行释放锁的操作?

使用try-finally,把释放的操作放在finally代码块中。

获取锁

先查看redis中是否存有对应的商品ID,如果没有对应的id,就可以使用redis的set命令对redis中添加数据,添加数据的键为对应商品的ID,值为获取的雪花ID。生成成功就放回雪花ID。

删除锁

先获取redis中商品id对应的值,如果值和拥有的值一样就可以进行删除操作(使用lua保证原子性)。

分布锁类
package com.example.demo.utils;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;

import java.util.Collections;

public class RedisLock {
    private RedisTemplate<String,Object> redisTemplate;
    private final String CHECK_LOCK="	local lock=redis.call('get',KEYS[1]) " +
            "if lock~=false " +
            "then " +
            "return false " +
            "else " +
            "redis.call('set',KEYS[1],ARGV[1],'EX',ARGV[2]) " +
            "end " +
            "return true ";
    private final String DEL_LOCK="	local lock=redis.call('get',KEYS[1]) " +
            "if lock~=ARGV[1] " +
            "then " +
            "return false " +
            "else " +
            "redis.call('del',KEYS[1]) " +
            "end " +
            "return true";

    public  RedisLock(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    public String getLock(String name, Long time, Long timeout){
        Long startTime=System.currentTimeMillis();
        String token = null;
        do {
            if((System.currentTimeMillis()-startTime)>timeout){
                break;
            }else{
                try {
                    Thread.sleep(30);
                } catch (InterruptedException e) {
                    e.printStackTrace(System.out);
                    return null;
                }
            }
            token=tryGetToken(name,time);
        }while (token==null);
        return token;
    }
    private String tryGetToken(String name,Long time){
        String id= String.valueOf(SnowFlake.ToGetAll());
        RedisScript<Boolean> redisScript=new DefaultRedisScript<>(CHECK_LOCK,Boolean.class);
        if(Boolean.TRUE.equals(redisTemplate.execute(redisScript, Collections.singletonList(name), id, time))){
            return id;
        }else{
            return null;
        }
    }

    public void delLock(String name,String token){
        RedisScript<Boolean> redisScript=new DefaultRedisScript<>(DEL_LOCK,Boolean.class);
        redisTemplate.execute(redisScript, Collections.singletonList(name),token);
    }
}
Integer stock= (Integer) redisTemplate.opsForHash().get("dishes",String.valueOf(shopping.getDishesId()));
                if(stock==null) {
                    //开始获取redis锁
                    RedisLock redisLock = new RedisLock(redisTemplate);
                    String token = null;
                    try {
                        do {
                            token = redisLock.getLock(String.valueOf(shopping.getDishesId()), (long) (20000), (long) (2100));
                            //检查redis中是否存有数据(是否有线程完成了此操作)
                            stock= (Integer) redisTemplate.opsForHash().get("dishes",String.valueOf(shopping.getDishesId()));
                            if(stock!=null){
                                break;
                            }
                            if(token!=null){
                                //再次判断
                                stock= (Integer) redisTemplate.opsForHash().get("dishes",String.valueOf(shopping.getDishesId()));
                                if(stock!=null){
                                    break;
                                }
                                System.out.println("获取到锁并且开始运行业务");
                                Integer dishesStock = dishesMapper.selectStocksById(shopping.getDishesId());
                                redisTemplate.opsForHash().put("dishes",String.valueOf(shopping.getDishesId()),dishesStock);
                            }
                        } while (token == null);
                    } finally {
                        //释放锁
                        redisLock.delLock(String.valueOf(shopping.getDishesId()), token);
                    }
                }


http://www.kler.cn/a/388818.html

相关文章:

  • 【软件工程】一篇入门UML建模图(类图)
  • 【STM32F1】——无线收发模块RF200与串口通信
  • LeetCode【0031】下一个排列
  • C++20 中最优雅的那个小特性 - Ranges
  • Systemd: disable和mask的区别
  • vue2.x elementui 固定顶部、左侧菜单与面包屑,自适应 iframe 页面布局
  • Rust安全性与最佳实践————安全编程技巧
  • 网络安全---安全见闻
  • 安卓/华为手机恢复出厂设置后如何恢复照片
  • 树莓派AI视觉小车--5.机器人小车超声波避障
  • Typora导出pdf手动分页和设置字体样式
  • 图像信号处理器(ISP,Image Signal Processor)详解
  • 如何让其他人连接到我们的数据库、进行项目前后端分离
  • Elasticsearch+kibana+filebeat的安装及使用
  • 刘卫国MATLAB程序设计与应用课后答案PDF第三版
  • SQL--查询连续三天登录数据详解
  • Windows命令行常用快捷指令
  • react 组件应用
  • 电子电气架构 --- 基于以太网的车载网络协议的描述
  • PHP字符串变量
  • 【ARM】MDK-E203 Undefined identifier
  • 青少年编程与数学 02-003 Go语言网络编程 14课题、Go语言Udp编程
  • MFC中Excel的导入以及使用步骤
  • 模型 用户画像
  • 原子操作 std::atomic
  • M芯片Mac构建Dockerfile - 注意事项