SpringBoot集成Redis 一 分布式锁 与 缓存
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 一 分布式锁 与 缓存的更多相关文章
- springboot利用redis实现分布式锁(redis为单机模式)
1.pom文件添加redis支持 <dependency> <groupId>org.springframework.boot</groupId> <arti ...
- springboot集成redis(mybatis、分布式session)
安装Redis请参考:<CentOS快速安装Redis> 一.springboot集成redis并实现DB与缓存同步 1.添加redis及数据库相关依赖(pom.xml) <depe ...
- SpringBoot集成Redis分布式锁以及Redis缓存
https://blog.csdn.net/qq_26525215/article/details/79182687 集成Redis 首先在pom.xml中加入需要的redis依赖和缓存依赖 < ...
- SpringBoot进阶教程(二十七)整合Redis之分布式锁
在之前的一篇文章(<Java分布式锁,搞懂分布式锁实现看这篇文章就对了>),已经介绍过几种java分布式锁,今天来个Redis分布式锁的demo.redis 现在已经成为系统缓存的必备组件 ...
- SpringBoot电商项目实战 — Redis实现分布式锁
最近有小伙伴发消息说,在Springboot系列文第二篇,zookeeper是不是漏掉了?关于这个问题,其实我在写第二篇的时候已经考虑过,但基于本次系列文章是实战练习,在项目里你能看到Zookeepe ...
- 【分布式缓存系列】Redis实现分布式锁的正确姿势
一.前言 在我们日常工作中,除了Spring和Mybatis外,用到最多无外乎分布式缓存框架——Redis.但是很多工作很多年的朋友对Redis还处于一个最基础的使用和认识.所以我就像把自己对分布式缓 ...
- Java基于redis实现分布式锁(SpringBoot)
前言 分布式锁,其实原理是就是多台机器,去争抢一个资源,谁争抢成功,那么谁就持有了这把锁,然后去执行后续的业务逻辑,执行完毕后,把锁释放掉. 可以通过多种途径实现分布式锁,例如利用数据库(mysql等 ...
- Redis除了做缓存--Redis做消息队列/Redis做分布式锁/Redis做接口限流
1.用Redis实现消息队列 用命令lpush入队,rpop出队 Long size = jedis.lpush("QueueName", message);//返回存放的数据条数 ...
- Spring Boot Redis 实现分布式锁,真香!!
之前看很多人手写分布式锁,其实 Spring Boot 现在已经做的足够好了,开箱即用,支持主流的 Redis.Zookeeper 中间件,另外还支持 JDBC. 本篇栈长以 Redis 为例(这也是 ...
随机推荐
- @value传值到static字段
解决办法: 1.将这两个私有属性的set方法从私有变成公开(private -->>> public); 2.生成这两个静态属性的set方法: 3.将原来在静态属性上的@Value( ...
- 从零开始搭建系统1.1——CentOs安装
本篇主要是记录安装CentOs的过程,为什么会选择CentOs,没有过多的原因,主要是出于CentOs相对来说安装的人比较多, 以后有问题了方便查资料.本次安装是安装在一台笔记本上,WIN7+Cent ...
- AWS的lambda和S3之间如何连携
今天正好遇到了这个问题,就在官方文档里查询,然后根据他的说明整理了一下大致的流程,详细的请参考AWS国际版的官方文档,这里只是作者的一个简单的流程展示. Lambda和S3连接 1.在S3的同一区域当 ...
- 图(graph)
一.非线性结构:图 图由顶点集V,集合规模为n,在n个顶点之间可能存在对应关系,我们用连边来描述这种,即边E,规模为e. 邻接关系:顶点与顶点之间的关系:关联关系:顶点与它相连的边的关系.序列结构(v ...
- python_django_urls基础配置
url配置:请求地址与views函数的匹配 首先,指定根级url配置文件,默认为setting.py中的ROOT_URLCONF='项目名.urls'(俺们也不用去修改啥) 我们urls有两个,一个是 ...
- CSIC_716_20191205【TCP-解决粘包问题、UDP模板】
---------------------------------------------------------------------------------------------------- ...
- JavaScript: 变量提升和函数提升
第一篇文章中提到了变量的提升,所以今天就来介绍一下变量提升和函数提升.这个知识点可谓是老生常谈了,不过其中有些细节方面博主很想借此机会,好好总结一下. 今天主要介绍以下几点: 1. 变量提升 2. 函 ...
- python编程语言学习day02
格式化输出 (1)info 格式 (2)%字符串占位 %s 表示字符串占位 %d 表示整数占位 %f 表示浮点数占位 中间的% 之后是所需要输入的值 多个占位, % 之后用()括号括起 ...
- HDU3342:判断有向图中是否存在3元环-Tarjan或拓扑排序
题目大意: 给你一个关系图,判断是否合法.每个人都有师父和徒弟,可以有很多个: 若A是B的师父,B是C的师父,则A也算C的师父. 不合法: 1) . 互为师徒:(有回路) 2) .你的师父是你徒弟 ...
- 编译器报错: error LNK2001: unresolved external symbol "struct _ServiceDescriptorTable * KeServiceDescript
转自VC错误:http://www.vcerror.com/?p=10 问题描述: 编译器报错: error LNK2001: unresolved external symbol "str ...