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. Aggregate report 聚合报告

  2. 枚举对象中的属性 for . . in

    枚举对象中的属性:把对象中所有的属性和值都取出来 使用 for . . . in 语句 语法: for(var  变量  in  对象){ } for  . . . in 语句  对象中有几个属性,循 ...

  3. Linux grep return code

    The exit code is 1 because nothing was matched by grep. EXIT STATUS The exit status is 0 if selected ...

  4. Mariadb 10.2.8版本GTID主从环境搭建以及切换

    1.首先搭建主从 主环境:192.168.1.117 从环境:192.168.1.123 a.首先以二进制包的形式安装好MariaDB (忽略不计) b.配置环境的变量 通配 [mysqld] bin ...

  5. 【颓废篇】Py:从零开始的poj自动提交

    之前学习了一些python的爬虫技术... 已经可以通过python来水blog的阅读量了 你知道的太多了, 然而你看我这个blog惨不忍睹的访问量, 有人吗? 有人吗? 今天突然又双叒叕心血来潮想写 ...

  6. 在jquery中应该使用prop方法来获取和设置checked属性,不应该使用attr。

    在jquery中应该使用prop方法来获取和设置checked属性,不应该使用attr. $("#checkAll").prop("checked", true ...

  7. CF 848E(动态规划+分治NTT)

    传送门: http://codeforces.com/problemset/problem/848/E 题解: 假设0-n一定有一条边,我们得到了一个方案,那么显然是可以旋转得到其他方案的. 记最大的 ...

  8. 排列+函数映射——hdu6038好题

    /* 引理:[0,n-1]的排列,i向a[i]连边,那么每个数必定在一个环中 所以数组a可以分割成一些环,数组b也可以分割成一些环 先讨论a的一个环 a[a1]=a2 a[a2]=a3 a[a3]=a ...

  9. 46 python学习笔记

    0 引言 之前用python跑过深度学习的代码,用过一段时间的jupiter和tensorflow:最近在Ubuntu下搭建起了VSCode + Anaconda的python开发环境,感觉很好用,尤 ...

  10. LUOGU P1501 [国家集训队]Tree II (lct)

    传送门 解题思路 \(lct\),比较模板的一道题,路径加和乘的维护标记与线段树\(2\)差不多,然后剩下就没啥了.但调了我将近一下午.. 代码 #include<iostream> #i ...