1.导入依赖(并配置底层调用客户端lettuce或jedis)
<!-- 这里使用jedis作为客户端 -->
<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>
<!-- 以后使用Redisson作为所有分布式锁 -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.12.0</version>
</dependency>
2.配置redission
/**
* 所有对Redisson的使用都是通过RedissonClient
*/
@Configuration
public class MyRedissonConfig {
@Bean(destroyMethod = "shutdown")
public RedissonClient redisson() throws IOException {
//1、创建配置
Config config = new Config();
//Redis url should start with redis:// or rediss://
config.useSingleServer().setAddress("redis://192.168.89.100:6379");
//2、根据Config创建出RedissonClient实例
RedissonClient redissonClient = Redisson.create(config);
return redissonClient;
}
}
3.简单使用(redisTemplate 普通查询与存储)
public Map<String, List<Catelog2Vo>> getCatalogJson() {
//缓存查询
String catalogJson = redisTemplate.opsForValue().get("catalogJson");
//缓存为空则调用数据库查询并将结果存入redis并返回
if (StringUtils.isEmpty(catalogJson)) {
//将结果转化为Json字符串
Map<String, List<Catelog2Vo>> catalogJsonFromDb = getCatalogJsonFromDb();
//重新存入缓存中,并设置过期时间为1天
redisTemplate.opsForValue().set("catalogJson", JSON.toJSONString(catalogJsonFromDb), 1, TimeUnit.DAYS);
return catalogJsonFromDb;
}
2.redis同时作为分布式锁
/**
* 从数据库查询并封装数据::分布式锁
* @return
*/
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() {
//1、占分布式锁。
//设置过期时间必须和加锁是同步的,保证原子性(避免死锁) setIfAbsent--> setnxex key value 时长 单位
String uuid = UUID.randomUUID().toString();
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);
if (lock) {
System.out.println("获取分布式锁成功...");
Map<String, List<Catelog2Vo>> dataFromDb = null;
try {
//加锁成功...执行业务查询数据库
dataFromDb = getDataFromDb();
} finally {
//使用lua脚本保证原子性(判断+删除锁)
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
stringRedisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);
}
return dataFromDb;
} else {
System.out.println("获取分布式锁失败...等待重试...");
//加锁失败...重试机制
//休眠一百毫秒
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
return getCatalogJsonFromDbWithRedisLock(); //自旋的方式
}
}
3.Redisson 实现分布式锁
1.Redisson底层实现了看门狗(watch dog)的机制:加锁时给了过期时间(默认30s),但是如果业务执行时间大于过期时间就会自动续期(默认续期30s) 注意:如果在 hello.lock(10, TimeUnit.SECONDS) 时指定了过期时间,看门狗机制会失效
2.上锁的key是getLock("hello") 时指定的,value则是使用UUID+线程序号
(1).RedissonClient简单使用
@Controller
public class IndexController {
@Autowired
RedissonClient redisson;
@GetMapping("/hello")
public void hello() {
//获取 hello 锁对象
RLock hello = redisson.getLock("hello");
//加锁并设置过期时间,如果不设置默认30s 拿不到锁时会进行阻塞等待
hello.lock(10, TimeUnit.SECONDS);
try {
System.out.println("hello world!");
} finally {
//释放锁
hello.unlock();
}
}
}
(2).RedissonClient读写锁(RReadWriteLock)
@Controller
public class IndexController {
@Autowired
RedissonClient redisson;
@Autowired
StringRedisTemplate stringRedisTemplate;
/**
* 获取写锁
* @param
* @return java.lang.String
*/
private String getWriteString() {
String s = "";
RReadWriteLock readWriteLock = redisson.getReadWriteLock("rw-lock");
RLock rLock = readWriteLock.writeLock();
try {
//1、改数据加写锁,读数据加读锁
rLock.lock();
s = UUID.randomUUID().toString();
ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
ops.set("writeValue", s);
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
rLock.unlock();
}
return s;
}
/**
* 获取读锁
* @param
* @return java.lang.String
*/
private String getReadString() {
String s = "";
RReadWriteLock readWriteLock = redisson.getReadWriteLock("rw-lock");
//加读锁
RLock rLock = readWriteLock.readLock();
try {
rLock.lock();
ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
s = ops.get("writeValue");
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
rLock.unlock();
}
return s;
}
/**
* 测试 读写锁
* 读读 -> 无互斥
* 读写、写写、写都 ->互斥
* @param type
* @return java.lang.String
*/
@GetMapping(value = "/readwrite/{type}")
@ResponseBody
public String readValue(@PathVariable("type") String type) {
String s="";
switch (type) {
case "read":
s= getReadString();
case "write":
s= getWriteString();
}
return s;
}
}
(3).RedissonClient 递减锁 (countDownLatch)
@Controller
public class IndexController {
@Autowired
RedissonClient redisson;
/**
* 放假、锁门
* 1班没人了
* 5个班,全部走完,我们才可以锁大门
* 分布式闭锁
*/
@GetMapping(value = "/lockDoor")
@ResponseBody
public String lockDoor() throws InterruptedException {
RCountDownLatch door = redisson.getCountDownLatch("door");
door.trySetCount(5);
door.await(); //等待闭锁完成
return "放假了...";
}
@GetMapping(value = "/gogogo/{id}")
@ResponseBody
public String gogogo(@PathVariable("id") Long id) {
RCountDownLatch door = redisson.getCountDownLatch("door");
door.countDown(); //计数-1
return id + "班的人都走了...";
}
}
(4).RedissonClient 信号量
@Controller
public class IndexController {
@Autowired
RedissonClient redisson;
/**
* 车库停车
* 3车位
* 信号量也可以做分布式限流
*/
@GetMapping(value = "/park")
@ResponseBody
public String park() throws InterruptedException {
RSemaphore park = redisson.getSemaphore("park");
park.acquire(); //获取一个信号、获取一个值,占一个车位
boolean flag = park.tryAcquire();
if (flag) {
//执行业务
} else {
return "error";
}
return "ok=>" + flag;
}
@GetMapping(value = "/go")
@ResponseBody
public String go() {
RSemaphore park = redisson.getSemaphore("park");
park.release(); //释放一个车位
return "ok";
}
}
/**
* 1.为了防止缓存穿透问题,当查询数据库没有值的时候缓存一个空对象
* 2.为了解决缓存雪崩问题,在设置超时时间是加上随机值
* 3.为了解决缓存击穿问题,当redis没有值的时候加上分布式锁 进行数据库访问
*/