1.添加依赖及配置(application.yml)

<!-- 引入redis依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency> spring:
redis:
host: 127.0.0.1
port: 6379

2.Redis分布式锁

Redis加锁和解锁的工具类

@Component
@Slf4j
public class RedisLock { @Autowired
private StringRedisTemplate stringRedisTemplate; /**
* 加锁
*
* @param key productId - 商品的唯一标志
* @param value 当前时间+超时时间 也就是时间戳
* @return
*/
public boolean lock(String key, String value) { //锁不存在,未被占用,可以成功设置锁
if (stringRedisTemplate.opsForValue().setIfAbsent(key, value)) {
return true;
} //锁存在,判断锁超时 - 防止原来的操作异常,没有运行解锁操作 防止死锁
String currentValue = stringRedisTemplate.opsForValue().get(key);
//如果锁过期
if (!StringUtils.isEmpty(currentValue) && Long.parseLong(currentValue) < System.currentTimeMillis()) {
//获取旧锁,设置新锁
String oldValue = stringRedisTemplate.opsForValue().getAndSet(key, value);
//假如多个线程同时进入上面的if,但只有第一个线程获取的oldValue是上一个锁的currentValue,得以通过下一个if,获取锁
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 = stringRedisTemplate.opsForValue().get(key);
if (!StringUtils.isEmpty(currentValue) && currentValue.equals(value)) {
stringRedisTemplate.opsForValue().getOperations().delete(key);//删除key
}
} catch (Exception e) {
log.error("[Redis分布式锁] 解锁出现异常了,{}", e);
}
}
} 以上代码问题:
1. 由于是客户端自己生成过期时间,所以需要强制要求分布式下每个客户端的时间必须同步。
2. 当锁过期的时候,如果多个客户端同时执行 jedis.getSet() 方法,
那么虽然最终只有一个客户端可以加锁,但是这个客户端的锁的过期时间可能被其他客户端覆盖。
3. 锁不具备拥有者标识,即任何客户端都可以解锁。 正确代码: /**
* 尝试获取分布式锁
* @param jedis Redis客户端
* @param lockKey 锁
* @param requestId 请求标识
* @param expireTime 超期时间
* @return 是否获取成功
*/
public static boolean lock(Jedis jedis, String lockKey, String requestId, int expireTime) { String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
if (LOCK_SUCCESS.equals(result)) {
return true;
}
return false;
} SET IF NOT EXIST,即当key不存在时,我们进行set操作;若key已经存在,则不做任何操作。
SET_WITH_EXPIRE_TIME:给这个key加一个过期时间的设置,具体时间由第五个参数决定 /**
* 释放分布式锁
* @param jedis Redis客户端
* @param lockKey 锁
* @param requestId 请求标识
* @return 是否释放成功
*/
public static boolean unLock(Jedis jedis, String lockKey, String requestId) { //获取锁对应的value值,检查是否与requestId相等,如果相等则删除锁(解锁)
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
if (RELEASE_SUCCESS.equals(result)) {
return true;
}
return false;
}

参考:https://segmentfault.com/a/1190000015058486

分布式锁示例

@Service
public class SeckillServiceImpl implements SeckillService{ @Autowired
private RedisLock redisLock; private static final int TIMEOUT = 10*1000;//超时时间 10s /**
* 活动,特价,限量100000份
*/
static Map<String,Integer> products;//模拟商品信息表
static Map<String,Integer> stock;//模拟库存表
static Map<String,String> orders;//模拟下单成功用户表
static {
/**
* 模拟多个表,商品信息表,库存表,秒杀成功订单表
*/
products = new HashMap<>();
stock = new HashMap<>();
orders = new HashMap<>();
products.put("123456",100000);
stock.put("123456",100000);
} private String queryMap(String productId){//模拟查询数据库
return "国庆活动,皮蛋特教,限量"
+products.get(productId)
+"份,还剩:"+stock.get(productId)
+"份,该商品成功下单用户数:"
+orders.size()+"人";
} @Override
public String querySecKillProductInfo(String productId) {
return this.queryMap(productId);
} /**
* synchronized锁方法是可以解决的,但是请求会变慢,
* 主要是没做到细粒度控制。
* 比如有很多商品的秒杀,但是这个把所有商品的秒杀都锁住了。而且这个只适合单机的情况,不适合集群
* @param productId
*/ @Override
public void orderProductMocckDiffUser(String productId) { //加锁(不同的productId有不同的锁,达到细粒度的要求)
long time = System.currentTimeMillis() + TIMEOUT;
if(!redisLock.lock(productId,String.valueOf(time))){
//未获取锁,说明有人正在进行操作
throw new SellException(101,"很抱歉,人太多了,换个姿势再试试~~");
} //获取锁后
//1.查询该商品库存,为0则活动结束
int stockNum = stock.get(productId);
if(stockNum==0){
throw new SellException(100,"活动结束");
}else {
//2.下单
orders.put(KeyUtil.getUniqueKey(),productId);
//3.减库存
stockNum =stockNum-1;
try{
//不做处理的话,高并发下会出现超卖的情况,下单数,大于减库存的情况。
// 虽然这里减了,但由于并发,减的库存还没存到map中去。新的并发拿到的是原来的库存
Thread.sleep(100);//模拟减库存的处理时间
}catch (InterruptedException e){
e.printStackTrace();
}
stock.put(productId,stockNum);
} //解锁
redisLock.unlock(productId,String.valueOf(time));
}
}

Spring Cache + Redis 简介

Spring Cache是一种缓存实现的通用技术,基于Spring提供的Cache框架。

Spring Cache提供了本身的简单实现NoOpCacheManager、ConcurrentMapCacheManager等。
开发者也可以通过Spring Cache,快速嵌入自己的Cache实现,如Redis等。 SpringCache是代码级的缓存,一般是使用一个ConcurrentMap。
也就是说实际上还是是使用JVM的内存来缓存对象的,那么肯定会造成大量的内存消耗,但是使用方便。 Redis 作为一个缓存服务器,是内存级的缓存。它是使用单纯的内存来进行缓存。
spring-boot-starter-data-redis 集成了 Spring Cache Spring Cache + Redis 好处: 既可以很方便的缓存对象,同时用来缓存的内存的是使用redis的内存,不会消耗JVM的内存,提升了性能。 当然这里Redis不是必须的,换成其他的缓存服务器一样可以,只要实现Spring的Cache类,并配置到XML里面就行了。

Spring Cache + Redis 缓存

1. 方法返回的对象加了缓存注解的,一定要实现序列化!

2. springboot启动类上加上注解:@EnableCaching

@Cacheable

第一次访问会查询内容,方法会返回一个对象,返回对象的时候,会把这个对象存储。
下一次访问的时候,不会进去这个方法,直接从redis缓存中拿 value (也可使用 cacheNames):
可看做命名空间,表示存到哪个缓存里了。 key:
表示命名空间下缓存唯一key,使用Spring Expression Language生成。 condition:
表示在哪种情况下才缓存结果(对应的还有unless,哪种情况不缓存),同样使用SpEL @Cacheable(value = "models", key = "#model.name", condition = "#model.name != ''")
public Model getForm(Model model) {
...
return model;
} 注:key如果不填,默认是空,对应的值应该就是方法的参数的值了。由于参数可能不同,使key不同,而无法达到更新的目的。所有key需要设置。

@CacheEvict

value (也可使用 cacheNames):
同Cacheable注解,可看做命名空间。表示删除哪个命名空间中的缓存 allEntries:
标记是否删除命名空间下所有缓存,默认为false key:
同Cacheable注解,代表需要删除的命名空间下唯一的缓存key。 //访问这个方法之后删除对应的缓存,对应之前的Redis缓存注解的配置 。
@CacheEvict(cacheNames = "product",key = "123",allEntries = true)
public ModelAndView save(@Valid ProductForm productForm,BindingResult bindingResult,Map<String,Object> map){
...
}

@CachePut

@CachePut(cacheNames = "product",key = "123")

    不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。
一般使用在保存,更新方法中。 如果返回的是ModelAndView,由于ModelAndView无法序列化,返回ModelAndView不适合此注解。 可以到Service层注解或者Dao层注解@CachePut

Service使用@CachePut

在整个类上注解:
@CacheConfig(cacheNames = "product") //配置整个类的缓存cacheNames Service方法注解: @CachePut(key = "123")
public ProductInfo save(ProductInfo productInfo) {
return productInfoDao.save(productInfo);
}

SpringBoot集成Redis 一 分布式锁 与 缓存的更多相关文章

  1. springboot利用redis实现分布式锁(redis为单机模式)

    1.pom文件添加redis支持 <dependency> <groupId>org.springframework.boot</groupId> <arti ...

  2. springboot集成redis(mybatis、分布式session)

    安装Redis请参考:<CentOS快速安装Redis> 一.springboot集成redis并实现DB与缓存同步 1.添加redis及数据库相关依赖(pom.xml) <depe ...

  3. SpringBoot集成Redis分布式锁以及Redis缓存

    https://blog.csdn.net/qq_26525215/article/details/79182687 集成Redis 首先在pom.xml中加入需要的redis依赖和缓存依赖 < ...

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

    在之前的一篇文章(<Java分布式锁,搞懂分布式锁实现看这篇文章就对了>),已经介绍过几种java分布式锁,今天来个Redis分布式锁的demo.redis 现在已经成为系统缓存的必备组件 ...

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

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

  6. 【分布式缓存系列】Redis实现分布式锁的正确姿势

    一.前言 在我们日常工作中,除了Spring和Mybatis外,用到最多无外乎分布式缓存框架——Redis.但是很多工作很多年的朋友对Redis还处于一个最基础的使用和认识.所以我就像把自己对分布式缓 ...

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

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

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

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

  9. Spring Boot Redis 实现分布式锁,真香!!

    之前看很多人手写分布式锁,其实 Spring Boot 现在已经做的足够好了,开箱即用,支持主流的 Redis.Zookeeper 中间件,另外还支持 JDBC. 本篇栈长以 Redis 为例(这也是 ...

随机推荐

  1. 骑马修栅栏 Riding the Fences

    题目背景 Farmer John每年有很多栅栏要修理.他总是骑着马穿过每一个栅栏并修复它破损的地方. 题目描述 John是一个与其他农民一样懒的人.他讨厌骑马,因此从来不两次经过一个栅栏.你必须编一个 ...

  2. Spring-boot整合Redis,遇到的问题

    1.通过set进redis中的数据,get不到 String cityKey ="city_"+id; ValueOperations<String,City> ope ...

  3. JS中的垃圾回收(GC)

    垃圾回收(GC): 1. 就像人生活的时间长了会产生垃圾一样,程序运行过程中也会产生垃圾,这些垃圾积攒过多以后,会导致程序运行的速度过慢, 所以我们需要一个垃圾回收的机制,来处理程序运行中产生的垃圾. ...

  4. 从零开始搭建系统2.1——Nexus安装及配置

    在安装配置Nexus时,请先确定您已经配置好jdk 1.创建目录 2.下载安装包 [root@localhost usr]# cd nexus 下载地址:https://www.sonatype.co ...

  5. Vue-cli开发笔记二----------接口调用、配置全局变量

    我做的一个项目,本身是没用任何框架,纯手写的前端及数据交互,项目已经完结.最近学Vue,于是借用这个项目,改装成vue项目. (一)接口问题:使用axios的调用方法,proxyTable解决开发环境 ...

  6. JUC 一 线程池

    线程 线程,是程序执行的最小单元.线程是进程中的其中一个实体,是被系统独立调度和分派的基本单位 它可与同属一个进程的其它线程共享进程所拥有的全部资源. 一个线程可以创建和撤消另一个线程,同一进程中的多 ...

  7. 【重磅来袭】阿里小程序IDE上线8大功能

    时隔两个月,10月10日阿里小程序IDE上线了uni-app 跨平台研发支持.预览和真机调试交互优化.预检测新增代码扫描等8项功能,进一步完善了阿里小程序IDE的功能池,给大家更好的开发体验和环境. ...

  8. thinkphp 静态缓存

    要使用静态缓存功能,需要开启HTML_CACHE_ON参数,并且使用HTML_CACHE_RULES配置参数设置静态缓存规则文件 . 大理石构件厂家 虽然也可以在应用配置文件中定义静态缓存规则,但是建 ...

  9. 查看hadoop压缩方式

    bin/hadoop checknative  来查看我们编译之后的hadoop支持的各种压缩,如果出现openssl为false,那么就在线安装一下依赖包 bin/hadoop checknativ ...

  10. NX二次开发-UFUN获取点在面上的向量方向UF_MODL_ask_face_props【转载】

    1 NX11+VS2013 2 3 4 #include <uf.h> 5 #include <uf_ui.h> 6 #include <uf_modl.h> 7 ...