1.pom文件添加redis支持

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

2.application.properties或者(application.yml)添加redis配置

spring.redis.database=1
spring.redis.host=172.xx.xx.xx
spring.redis.password=123456
spring.redis.port=6379

上面的spring.redis.host替换成自己的redis服务地址,如果没有用到密码则删除spring.redis.password配置即可

3.redis工具类

package com.example.demo;

import com.example.demo.extend.FastJsonRedisSerializer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component; import javax.annotation.PostConstruct;
import java.util.Collections;
import java.util.List; @Component
public class RedisUtil { @Autowired
private RedisTemplate<String,String> template; @PostConstruct
public void init(){
template.setKeySerializer(template.getStringSerializer());
template.setValueSerializer(template.getStringSerializer());
template.setHashKeySerializer(template.getStringSerializer());
template.setHashValueSerializer(template.getStringSerializer());
} /**
* 通过lua脚本 加锁并设置过期时间
* @param key 锁key值
* @param value 锁value值
* @param expire 过期时间,单位秒
* @return true:加锁成功,false:加锁失败
*/
public boolean getLock(String key,String value,String expire){
DefaultRedisScript<String> redisScript = new DefaultRedisScript<String>();
redisScript.setResultType(String.class);
String strScript = "";
strScript +=" if redis.call('setNx',KEYS[1],ARGV[1])==1 then ";
strScript +=" return redis.call('expire',KEYS[1],ARGV[2]) ";
strScript +=" else";
strScript +=" return 0 ";
strScript +=" end ";
redisScript.setScriptText(strScript);
try{
Object result = this.template.execute(redisScript,template.getStringSerializer(),template.getStringSerializer(), Collections.singletonList(key),value,expire);
System.out.println("redis返回:"+result);
return "1".equals(""+result);
}catch (Exception e){
//可以自己做异常处理
return false;
} } /**
* 通过lua脚本释放锁
* @param key 锁key值
* @param value 锁value值(仅当redis里面的value值和传入的相同时才释放,避免释放其他线程的锁)
* @return true:释放锁成功,false:释放锁失败(可能已过期或者已被释放)
*/
public boolean releaseLock(String key,String value){
DefaultRedisScript<String> redisScript = new DefaultRedisScript<>();
redisScript.setResultType(String.class);
String strScript = "";
strScript +="if redis.call('get',KEYS[1]) == ARGV[1] then ";
strScript +=" return redis.call('del',KEYS[1]) ";
strScript +="else ";
strScript +=" return 0 ";
strScript +="end ";
redisScript.setScriptText(strScript);
try{
Object result = this.template.execute(redisScript,template.getStringSerializer(),template.getStringSerializer(), Collections.singletonList(key),value);
return "1".equals(""+result);
}catch (Exception e){
//可以自己做异常处理
return false;
}
}
}

redis锁用到的是setNx命令,这个命令的意思是如果redis里面存在这个key则不再添加,如果key不存在则添加成功,当setNx设置成功之后再给这个值设置一个超期时间来防止出现极端情况(断网,服务终止)导致锁没有被及时释放的情况。

上面的加锁和解锁都是通过lua脚本进行,redis里面lua脚本执行时是原子操作,可以保证加锁和设置超时同时成功或者失败,不会出现设置值成功 添加超时时间失败的情况

4.使用

在需要加锁的地方注入RedisUtil对象即可。有问题的可以留言一起探讨细节问题

@Autowired
private RedisLockUtil redisUtil;
boolean lock = this.redisUtil.getLock("FORM_SUBMIT"+formId,formId,"2");
if(!lock){
//未获得锁
throw new ServiceException("当前已经有任务在执行!");
} //---------执行业务逻辑 if(lock){
//释放锁不关心成功与否
this.redisUtil.releaseLock("FORM_SUBMIT"+formId,formId);
}

上面的业务逻辑最好放在try catch中执行,释放锁的代码放到finally里面执行。

5.考虑各种情况下会不会导致bug的出现

5.1:加锁失败

  拿不到锁业务逻辑不执行,没问题

5.2:加锁成功,释放锁失败

  网络原因或者其他原因没有释放掉,没关系 ,超时时间过了就会自己释放,没问题

5.3:加锁成功,业务执行时间过长,锁已经被redis自己释放

  此种情况需要根据业务的实际情况设置合理的超时时间,可能会出问题,原因在于 基于分布式的系统是无法避免类似的问题,具体可以参考如下博客的文章,

此文章引入了 关于Redis分布式锁的安全性问题,在分布式系统专家Martin Kleppmann和Redis的作者antirez之间发生过的一场争论,内容很精彩。https://blog.csdn.net/paincupid/article/details/75094550

springboot利用redis实现分布式锁(redis为单机模式)的更多相关文章

  1. Redis除了做缓存--Redis做消息队列/Redis做分布式锁/Redis做接口限流

    1.用Redis实现消息队列 用命令lpush入队,rpop出队 Long size = jedis.lpush("QueueName", message);//返回存放的数据条数 ...

  2. Redis 中的原子操作(3)-使用Redis实现分布式锁

    Redis 中的分布式锁如何使用 分布式锁的使用场景 使用 Redis 来实现分布式锁 使用 set key value px milliseconds nx 实现 SETNX+Lua 实现 使用 R ...

  3. Java基于redis实现分布式锁(SpringBoot)

    前言 分布式锁,其实原理是就是多台机器,去争抢一个资源,谁争抢成功,那么谁就持有了这把锁,然后去执行后续的业务逻辑,执行完毕后,把锁释放掉. 可以通过多种途径实现分布式锁,例如利用数据库(mysql等 ...

  4. 利用多写Redis实现分布式锁原理与实现分析(转)

    利用多写Redis实现分布式锁原理与实现分析   一.关于分布式锁 关于分布式锁,可能绝大部分人都会或多或少涉及到. 我举二个例子:场景一:从前端界面发起一笔支付请求,如果前端没有做防重处理,那么可能 ...

  5. 利用redis实现分布式锁知识点总结及相关改进

    利用redis实现分布式锁知识点总结及相关改进 先上原文,本文只为总结及对相关内容的质疑并提出若干意见,原文内容更详细https://www.cnblogs.com/linjiqin/p/800383 ...

  6. 利用redis实现分布式锁

    分布式锁一般有三种实现方式: 1. 数据库乐观锁: 2. 基于ZooKeeper的分布式锁: 3. 基于Redis的分布式锁: 这里大概说一下三种方式的优缺点,数据库乐观锁优点是实现简单,只需要for ...

  7. spring boot 利用redisson实现redis的分布式锁

    原文:http://liaoke0123.iteye.com/blog/2375469 利用redis实现分布式锁,网上搜索的大部分是使用java jedis实现的. redis官方推荐的分布式锁实现 ...

  8. SpringBoot电商项目实战 — Redis实现分布式锁

    最近有小伙伴发消息说,在Springboot系列文第二篇,zookeeper是不是漏掉了?关于这个问题,其实我在写第二篇的时候已经考虑过,但基于本次系列文章是实战练习,在项目里你能看到Zookeepe ...

  9. 通过Redis 实现分布式锁_利用Jedis 客户端

    前言 分布式锁一般有三种实现方式: 数据库乐观锁:2. 基于Redis的分布式锁:3. 基于ZooKeeper的分布式锁. 本篇博客将介绍第二种方式,基于Redis实现分布式锁. 虽然网上已经有各种介 ...

  10. SpringBoot集成Redis 一 分布式锁 与 缓存

    1.添加依赖及配置(application.yml) <!-- 引入redis依赖 --> <dependency> <groupId>org.springfram ...

随机推荐

  1. Python设计模式(1)-简单工厂模式

    为操作数据库设计增删改查操作 # coding=utf-8class DbManager: def __init__(self): pass def operate_db(self): pass cl ...

  2. Arthas-Java的线上问题定位工具

    Arthas(阿尔萨斯) 能为你做什么? Arthas 是Alibaba开源的Java诊断工具,深受开发者喜爱. 当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决: 这个类从哪个 jar ...

  3. Spring Boot 集成 Spring Security 入门案例教程

    前言 本文作为入门级的DEMO,完全按照官网实例演示: 项目目录结构 Maven 依赖 <parent> <groupId>org.springframework.boot&l ...

  4. Daily Scrum 12/24/2015

    Process: Zhaoyang: Some UI change and compile the Caffe in the IOS. Yandong: Do some code integratio ...

  5. reactnavigation 5.x简单例子

    随着RN和reactnavigation的版本更新,网上很多老版的例子都不能用了. 自己摸索着跑通了一些简单的功能. 使用的是最新的    "react-native": &quo ...

  6. 美化你的终端利器Iterm2

    Iterm2是特别好用的一款终端,支持自定义字体和高亮,让日常开发,充满愉悦. 安装iterm2(mac版) brew tap caskroom/cask brew cask install iter ...

  7. 重启mysql服务

    重启mysql 启动mysql: 方式一:sudo /etc/init.d/mysql start 方式二:sudo service mysql start 停止mysql: 方式一:sudo /et ...

  8. .NET中 kafka消息队列、环境搭建与使用

    前面几篇文章中讲了一些关于消息队列的知识,就每中消息队列中间件,我们并没有做详细的讲解,那么,今天我们就来详细的讲解一下消息队列之一kafka的一些基本的使用与操作. 一.kafka介绍 kafka: ...

  9. [V&N2020 公开赛] Web misc部分题解

    0x00 前言 写了一天题目,学到了好多东西, 简单记录一下 0x01 Web HappyCTFd 直接使用网上公开的cve打: 解题思路:先注册一个admin空格账号,注意这里的靶机无法访问外网,邮 ...

  10. C++养成好的代码习惯

    [C++小技巧] -------------------------------------------------------------#ifdef _DEBUG    imwrite(" ...