引入redis
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置文件
spring:
redis:
host: 192.168.56.10
port: 6379
测试
@Autowired
StringRedisTemplate redisTemplate;
@Test
public void testRedisTemplate(){
//存储hello world
ValueOperations<String, String> ops = redisTemplate.opsForValue();
//保存
ops.set("hello","world"+ UUID.randomUUID());
//查询
String hello = ops.get("hello");
System.out.println(hello);
}
bug
//todo 并发产生堆外内存溢出
//原因springboot2.0以后redia默认使用lettuce作为redis客户端,使用netty进行网络通信
//lettuce的bug导致netty内存溢出,netty如果没有配置,默认使用xmx的值作为堆外内存
//可以通过-Dio.netty.maxDirectMemory进行设置
//解决方案:不能使用-Dio.netty.maxDirectMemory只去调大堆外内存
//1、升级lettuce客户端 2、切换使用jedis
//lettuce、jedis直接操作redis底层,redistemplate为再次封装
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
存在的问题
加锁–synchronized (this)(分布式下不可用)
public Map<String, List<Catelog2Vo>> getCatelogJsonPlusFromDB() {
//加锁,只要是同一把锁,就能锁住需要这个锁的所有线程
//springboot所有的组件在容器中默认单例,使用this可以锁住
//synchronized (this)分布式下不可用
synchronized (this){
//得到锁之后,先去看redis中有无再操作
String catelogJSON = redisTemplate.opsForValue().get("catelogJSON");
if (!StringUtils.isEmpty(catelogJSON)){
Map<String, List<Catelog2Vo>> result=JSON.parseObject(catelogJSON,new TypeReference<Map<String, List<Catelog2Vo>>>(){});
return result;
}
//要返回的格式
Map<String, List<Catelog2Vo>> mapl1 = new HashMap<>(16);
//查出所有数据
List<CategoryEntity> catelogOrgin = baseMapper.selectList(new LambdaQueryWrapper<CategoryEntity>().orderByDesc(CategoryEntity::getCatLevel));
//获得最深层
Integer catLevel = catelogOrgin.get(0).getCatLevel();
if (!catelogOrgin.isEmpty()) {
Map<String, List<Catelog2Vo.Catelog3Vo>> mapl2 = new HashMap<>(16);
catelogOrgin.forEach(item -> {
if (item.getCatLevel().equals(catLevel)) {
//先构造二级数据,k,v==item.pid,item
//先判断k存在
Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo(item.getParentCid().toString(), item.getCatId().toString(), item.getName());
if (mapl2.containsKey(item.getParentCid().toString())) {
//存在就放数据
List<Catelog2Vo.Catelog3Vo> catelog3Vos = mapl2.get(item.getParentCid().toString());
catelog3Vos.add(catelog3Vo);
} else {
List<Catelog2Vo.Catelog3Vo> catelog3Vos = new ArrayList<>(11);
catelog3Vos.add(catelog3Vo);
mapl2.put(item.getParentCid().toString(), catelog3Vos);
}
} else if (item.getCatLevel().equals(catLevel - 1)) {
//判断一级map是否存在这个k
//获得此分类下的所有三级
List<Catelog2Vo.Catelog3Vo> catelog3Vos = mapl2.get(item.getCatId().toString());
Catelog2Vo catelog2Vo = new Catelog2Vo(item.getParentCid().toString(), catelog3Vos, item.getCatId().toString(), item.getName());
if (mapl1.containsKey(item.getParentCid().toString())) {
List<Catelog2Vo> catelog2Vos = mapl1.get(item.getParentCid().toString());
catelog2Vos.add(catelog2Vo);
} else {
List<Catelog2Vo> list=new ArrayList<>(11);
list.add(catelog2Vo);
mapl1.put(item.getParentCid().toString(),list);
}
} else {
//避免一级分类下无数据的不显示,这里也要判断一下
if (!mapl1.containsKey(item.getCatId().toString())) {
// List<Catelog2Vo> list=new ArrayList<>(11);
mapl1.put(item.getCatId().toString(), null);
}
}
});
}
return mapl1;
}
}
- 加锁时序问题
解决方法
@Override
public Map<String, List<Catelog2Vo>> getCatelogJsonPlus() {
/** 空结果缓存,解决缓存穿透
*设置过期时间,加随机值,解决缓存雪崩
* 加锁,解决缓存击穿
*/
String catelogJSON = redisTemplate.opsForValue().get("catelogJSON");
if (StringUtils.isEmpty(catelogJSON)) {
Map<String, List<Catelog2Vo>> catelogJsonFromDB = getCatelogJsonPlusFromDB();
//以JSON存储,跨语言跨平台
return catelogJsonFromDB;
}
//redis查到的反序列化成对象
Map<String, List<Catelog2Vo>> result = JSON.parseObject(catelogJSON, new TypeReference<Map<String, List<Catelog2Vo>>>() {
});
return result;
}
public Map<String, List<Catelog2Vo>> getCatelogJsonPlusFromDB() {
//加锁,只要是同一把锁,就能锁住需要这个锁的所有线程
//springboot所有的组件在容器中默认单例,使用this可以锁住
//synchronized (this)分布式下不可用
synchronized (this) {
//得到锁之后,先去看redis中有无再操作
String catelogJSON = redisTemplate.opsForValue().get("catelogJSON");
if (!StringUtils.isEmpty(catelogJSON)) {
Map<String, List<Catelog2Vo>> result = JSON.parseObject(catelogJSON, new TypeReference<Map<String, List<Catelog2Vo>>>() {
});
return result;
}
log.info("查询了数据库");
//要返回的格式
Map<String, List<Catelog2Vo>> mapl1 = new HashMap<>(16);
//查出所有数据
List<CategoryEntity> catelogOrgin = baseMapper.selectList(new LambdaQueryWrapper<CategoryEntity>().orderByDesc(CategoryEntity::getCatLevel));
//获得最深层
Integer catLevel = catelogOrgin.get(0).getCatLevel();
if (!catelogOrgin.isEmpty()) {
Map<String, List<Catelog2Vo.Catelog3Vo>> mapl2 = new HashMap<>(16);
catelogOrgin.forEach(item -> {
if (item.getCatLevel().equals(catLevel)) {
//先构造二级数据,k,v==item.pid,item
//先判断k存在
Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo(item.getParentCid().toString(), item.getCatId().toString(), item.getName());
if (mapl2.containsKey(item.getParentCid().toString())) {
//存在就放数据
List<Catelog2Vo.Catelog3Vo> catelog3Vos = mapl2.get(item.getParentCid().toString());
catelog3Vos.add(catelog3Vo);
} else {
List<Catelog2Vo.Catelog3Vo> catelog3Vos = new ArrayList<>(11);
catelog3Vos.add(catelog3Vo);
mapl2.put(item.getParentCid().toString(), catelog3Vos);
}
} else if (item.getCatLevel().equals(catLevel - 1)) {
//判断一级map是否存在这个k
//获得此分类下的所有三级
List<Catelog2Vo.Catelog3Vo> catelog3Vos = mapl2.get(item.getCatId().toString());
Catelog2Vo catelog2Vo = new Catelog2Vo(item.getParentCid().toString(), catelog3Vos, item.getCatId().toString(), item.getName());
if (mapl1.containsKey(item.getParentCid().toString())) {
List<Catelog2Vo> catelog2Vos = mapl1.get(item.getParentCid().toString());
catelog2Vos.add(catelog2Vo);
} else {
List<Catelog2Vo> list = new ArrayList<>(11);
list.add(catelog2Vo);
mapl1.put(item.getParentCid().toString(), list);
}
} else {
//避免一级分类下无数据的不显示,这里也要判断一下
if (!mapl1.containsKey(item.getCatId().toString())) {
// List<Catelog2Vo> list=new ArrayList<>(11);
mapl1.put(item.getCatId().toString(), null);
}
}
});
}
String jsonString = JSON.toJSONString(mapl1);
redisTemplate.opsForValue().set("catelogJSON", jsonString, 1, TimeUnit.DAYS);
return mapl1;
}
}
分布式锁
redisson
- 引入依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.12.0</version>
</dependency>
- 配置
package com.atguigu.gulimall.product.config;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;
@Configuration
public class MyRedissonConfig {
@Bean(destroyMethod="shutdown")
public RedissonClient redisson() throws IOException {
//1、创建配置
Config config = new Config();
config.useSingleServer().setAddress("redis://192.168.56.10:6379");
//2、根据Config创建出Redisson实例
RedissonClient redissonClient = Redisson.create(config);
return redissonClient;
}
}
- 测试
@Autowired
RedissonClient redissonClient;
@Test
public void redisson(){
System.out.println(redissonClient);
}
可重入锁(Reentrant Lock)
@Autowired
RedissonClient redissonClient;
@ResponseBody
@GetMapping("/hello")
public String hello() {
//1、获取一把锁,只要锁的名字一样,就是同一把锁
RLock lock = redissonClient.getLock("my_lock");
//2、加锁,阻塞式等待
lock.lock();//默认加锁30s
//看门狗锁的自动续期,可通过修改Config.lockWatchdogTimeout来指定续期时间,每1/3看门狗时间会重新将锁续满
//加锁的业务只要完成,就不会续期,即使不手动释放锁,也会自动30删除
try {
System.out.println("加锁成功" + Thread.currentThread().getId());
Thread.sleep(30000);
} catch (Exception ignored) {
} finally {
//3、解锁
System.out.println("释放锁成功" + Thread.currentThread().getId());
lock.unlock();
}
return "hello";
}
另外Redisson还通过加锁的方法提供了leaseTime
的参数来指定加锁的时间。超过这个时间后锁便自动解开了。不会续期
lock.lock(10, TimeUnit.SECONDS);
读写锁
/**
* 读写锁
* 保证一定能读到最新数据,修改时,写锁是排他锁。读锁是共享锁。
* 写+读 阻塞
* 读+写 阻塞
* 写+写 阻塞
* 读+读 相当于无锁,并发读,redis只会记录当前的读锁,会同时加锁成功
* @return
*/
@ResponseBody
@GetMapping("/write")
public String write() {
RReadWriteLock lock = redissonClient.getReadWriteLock("rw-lock");
String s = "";
RLock writeLock = lock.writeLock();
try {
//1、改数据加写锁,读数据加读锁
writeLock.lock();
s = UUID.randomUUID().toString();
Thread.sleep(30000);
stringRedisTemplate.opsForValue().set("write", s);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
writeLock.unlock();
}
return s;
}
@ResponseBody
@GetMapping("/read")
public String read() {
RReadWriteLock lock = redissonClient.getReadWriteLock("rw-lock");
String s = "";
RLock readLock = lock.readLock();
//1、加读锁
readLock.lock();
try {
s = stringRedisTemplate.opsForValue().get("write");
Thread.sleep(30000);
} catch (Exception e) {
e.printStackTrace();
} finally {
readLock.unlock();
}
return s;
}
- 写-读效果
信号量Semaphore
/**
* 信号量(Semaphore)
* 车库停车
* 3车位
* k可以做分布式限流
*/
@GetMapping("/park")
@ResponseBody
public String park() throws InterruptedException {
RSemaphore park = redissonClient.getSemaphore("park");
// park.acquire();//获取一个信号,获取一个值,阻塞拿车位,park的值自动减一,若为0则等待
boolean b = park.tryAcquire();//有车位就停,没有就算了
if (b){
//业务
}else {
return "error";
}
return "ok"+b;
}
@GetMapping("/go")
@ResponseBody
public String go() throws InterruptedException {
RSemaphore park = redissonClient.getSemaphore("park");
park.release();//释放一个车位park加1
return "ok";
}
闭锁(CountDownLatch)
/**
* 闭锁(CountDownLatch)
* 学校锁门
* 5个班全部走完,锁门
*/
@GetMapping("/lockDoor")
@ResponseBody
public String lockDoor() throws InterruptedException {
RCountDownLatch door = redissonClient.getCountDownLatch("door");//自动加锁
door.trySetCount(5);
door.await();//等待闭锁都完成
return "放假了";
}
@GetMapping("/gogogo/{id}")
@ResponseBody
public String gogogo(@PathVariable("id") Long id) {
RCountDownLatch door = redissonClient.getCountDownLatch("door");
door.countDown();//减一,减到0自动释放锁
return id+"班的人都走了";
}
一致性