博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
SpringBoot进阶教程(五十五)整合Redis之分布式锁
阅读量:5039 次
发布时间:2019-06-12

本文共 5602 字,大约阅读时间需要 18 分钟。

在之前的一篇文章(),已经介绍过几种java分布式锁,今天来个Redis分布式锁的demo。redis 现在已经成为系统缓存的必备组件,针对缓存读取更新操作,通常我们希望当缓存过期之后能够只有一个请求去更新缓存,其它请求依然使用旧的数据。这就需要用到锁,因为应用服务多数以集群方式部署,因此这里的锁就必需要是分布式锁才能符合需求。

学习本章节之前,建议依次阅读以下文章,更好的串联全文内容,如已掌握以下列出知识点,请跳过:

SpringBoot进阶教程(二十七)整合Redis之分布式锁

v简单实现

锁是针对某个资源的状态,保证其访问的互斥性,在实际使用当中,这个状态一般是一个字符串。使用 Redis 实现锁,主要是将状态放到 Redis 当中,利用其原子性,当其他线程访问时,如果 Redis 中已经存在这个状态,就不允许之后的一些操作。spring boot使用Redis的操作主要是通过RedisTemplate(或StringRedisTemplate )来实现。

1.1 将锁状态放入 Redis:

redisTemplate.opsForValue().setIfAbsent("lockkey", "value"); // setIfAbsent如果键不存在则新增,存在则不改变已经有的值。

1.2 设置锁的过期时间

redisTemplate.expire("lockkey", 30000, TimeUnit.MILLISECONDS);

1.3 删除/解锁

redisTemplate.delete("lockkey");

这么就是简单实现,但是1.1和1.2这么做,这两步违背了原子性,也就是一旦锁被创建,而没有设置过期时间,则锁会一直存在。

1.4 获取锁

redisTemplate.opsForValue().get("lockkey");

1.5 解决方案

spring data的 RedisTemplate 当中并没有这样的方法。但是在jedis当中是有这种原子操作的方法的,需要通过 RedisTemplate 的 execute 方法获取到jedis里操作命令的对象.

String result = template.execute(new RedisCallback
() { @Override public String doInRedis(RedisConnection connection) throws DataAccessException { JedisCommands commands = (JedisCommands) connection.getNativeConnection(); return commands.set(key, "锁定的资源", "NX", "PX", 3000); } });

注意: Redis 从2.6.12版本开始 set 命令支持 NX 、 PX 这些参数来达到 setnx 、 setex 、 psetex 命令的效果,文档参见:

NX: 表示只有当锁定资源不存在的时候才能 SET 成功。利用 Redis 的原子性,保证了只有第一个请求的线程才能获得锁,而之后的所有线程在锁定资源被释放之前都不能获得锁。

v锁的进阶

模拟一个比较常见的秒杀场景,这时候就需要用到锁。

2.1 创建RedisLockHelper

package com.demo.common;import com.google.common.base.Strings;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.StringRedisTemplate;import org.springframework.stereotype.Component;/** * Created by toutou on 2019/1/27. */@Component@Slf4jpublic class RedisLockHelper {    @Autowired    private StringRedisTemplate stringRedisTemplate;    /**     * 加锁     * @param targetId   targetId - 商品的唯一标志     * @param timeStamp  当前时间+超时时间 也就是时间戳     * @return     */    public boolean lock(String targetId,String timeStamp){        if(stringRedisTemplate.opsForValue().setIfAbsent(targetId,timeStamp)){            // 对应setnx命令,可以成功设置,也就是key不存在            return true;        }        // 判断锁超时 - 防止原来的操作异常,没有运行解锁操作  防止死锁        String currentLock = stringRedisTemplate.opsForValue().get(targetId);        // 如果锁过期 currentLock不为空且小于当前时间        if(!Strings.isNullOrEmpty(currentLock) && Long.parseLong(currentLock) < System.currentTimeMillis()){            // 获取上一个锁的时间value 对应getset,如果lock存在            String preLock =stringRedisTemplate.opsForValue().getAndSet(targetId,timeStamp);            // 假设两个线程同时进来这里,因为key被占用了,而且锁过期了。获取的值currentLock=A(get取的旧的值肯定是一样的),两个线程的timeStamp都是B,key都是K.锁时间已经过期了。            // 而这里面的getAndSet一次只会一个执行,也就是一个执行之后,上一个的timeStamp已经变成了B。只有一个线程获取的上一个值会是A,另一个线程拿到的值是B。            if(!Strings.isNullOrEmpty(preLock) && preLock.equals(currentLock) ){                // preLock不为空且preLock等于currentLock,也就是校验是不是上个对应的商品时间戳,也是防止并发                return true;            }        }        return false;    }    /**     * 解锁     * @param target     * @param timeStamp     */    public void unlock(String target,String timeStamp){        try {            String currentValue = stringRedisTemplate.opsForValue().get(target);            if(!Strings.isNullOrEmpty(currentValue) && currentValue.equals(timeStamp) ){                // 删除锁状态                stringRedisTemplate.opsForValue().getOperations().delete(target);            }        } catch (Exception e) {            log.error("警报!警报!警报!解锁异常{}",e);        }    }}

这个是Redis加锁和解锁的工具类,里面使用的主要是两个命令,SETNX和GETSET。

SETNX命令 将key设置值为value,如果key不存在,这种情况下等同SET命令。 当key存在时,什么也不做

GETSET命令 先查询出原来的值,值不存在就返回nil。然后再设置值 对应的Java方法在代码中提示了。 注意一点的是,Redis是单线程的!所以在执行GETSET和SETNX不会存在并发的情况。

2.2 创建Controller模拟秒杀场景

package com.demo.controller;import com.demo.common.RedisLockHelper;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;/** * Created by toutou on 2019/1/27. */@RestController@Slf4jpublic class RedisController {    @Autowired    RedisLockHelper redisLockHelper;    /**     * 超时时间 5s     */    private static final int TIMEOUT = 5*1000;    @RequestMapping(value = "/seckilling")    public String Seckilling(String targetId){        //加锁        long time = System.currentTimeMillis() + TIMEOUT;        if(!redisLockHelper.lock(targetId,String.valueOf(time))){            return "排队人数太多,请稍后再试.";        }        int surplusCount = 0;        // 查询该商品库存,为0则活动结束 e.g. getStockByTargetId        if(surplusCount==0){            return "活动结束.";        }else {            // 下单 e.g. buyStockByTargetId            //减库存 不做处理的话,高并发下会出现超卖的情况,下单数,大于减库存的情况。虽然这里减了,但由于并发,减的库存还没存到map中去。新的并发拿到的是原来的库存            surplusCount =surplusCount-1;            try{                Thread.sleep(100);//模拟减库存的处理时间            }catch (InterruptedException e){                e.printStackTrace();            }            // 减库存操作数据库 e.g. updateStockByTargetId            // buyStockByTargetId 和 updateStockByTargetId 可以同步完成(或者事物),保证原子性。        }        //解锁        redisLockHelper.unlock(targetId,String.valueOf(time));        return "恭喜您,秒杀成功。";    }}

 

其他参考资料:

注:本文中很多内容来自以上链接的学习心得,感谢以上人员分享,也请转载本文的各站保持以上链接。

v源码地址

作  者:
出  处:
关于作者:专注于基础平台的项目开发。如有问题或建议,请多多赐教!
版权声明:本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。
特此声明:所有评论和私信都会在第一时间回复。也欢迎园子的大大们指正错误,共同进步。或者我
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【】一下。您的鼓励是作者坚持原创和持续写作的最大动力!

转载于:https://www.cnblogs.com/toutou/p/redis_lock.html

你可能感兴趣的文章
摸索出来的文字居中 定位后怎么都不居中,,
查看>>
数据库索引
查看>>
VS 自带Git使用教程
查看>>
iOS ReactiveCocoa简单使用笔记
查看>>
[TCP/IP]TCP的三次握手和四次挥手
查看>>
python中交换两个值的方法
查看>>
软件开发中对架构、构架、结构、框架的理解
查看>>
JAVA通信系列一:Java Socket技术总结
查看>>
VS 2010打开设计器出现错误
查看>>
SQLServer 镜像功能完全实现
查看>>
Vue-详解设置路由导航的两种方法
查看>>
一个mysql主从复制的配置案例
查看>>
大数据学习系列(8)-- WordCount+Block+Split+Shuffle+Map+Reduce技术详解
查看>>
dvwa网络渗透测试环境的搭建
查看>>
Win8 安装VS2012 和 Sql Server失败问题
查看>>
过点(2,4)作一直线在第一象限与两轴围成三角形,问三角形面积的最小值?...
查看>>
java aes CBC的填充方式发现
查看>>
使用ionic cordova build android --release --prod命令打包报有如下错误及解决方法
查看>>
BZOJ 2338 HNOI2011 数矩形 计算几何
查看>>
关于页面<!DOCTYPE>声明
查看>>