使用
Redis
boot整合Redis
- docker安装Redis,参考Docker安装mysql和redis详细介绍
- 引入依赖,排除lettuce,使用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>
- application.yml
spring:
redis:
host: 118.31.188.33
port: 6379
- 使用SpringBoot自动配置好的StringRedisTemplate
@RunWith(SpringRunner.class)
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Test
public void testStringRedisTemplate() {
ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
//保存
ops.set("hello","world_" + UUID.randomUUID().toString());
//查询
String hello = ops.get("hello");
System.out.println("之前保存的数据:"+hello);
}
tips
- 一般往缓存中存json,json之间的转换采用alibaba的fastjson
// 转换为json
String s = JSON.toJSONString();
// json转化为指定对象
JSON.parseObject()
高并发下的缓存失效问题
-
缓存穿透:查询一个缓存和数据库中都不存在的数据
解决:空结果缓存,就是从数据库查询结果为空时,往缓存存放0或者其他非null的值,查询到数据时,就把数据放入缓存
-
缓存雪崩:缓存在某一时刻集体失效
解决:给每个缓存数据设置不同的过期时间
xxx.opsForValue.set()可以设置过期时间
-
缓存击穿:某一热点数据过期失效,失效时大量请求进来,刚好落到数据库上
解决:加锁
本地锁只能锁住当前进程(一个进程相当于一个服务)
本地锁使用注意事项
1.线程拿到锁后第一件事应该是判断缓存中有没有想要的数据,而不是直接查询数据库。
2.锁的时序问题
解决方法:把查询数据库操作和放入缓存操作放在同一把锁内执行
分布式锁推演
一、
来到阶段二
二、
来到阶段三
三、
解决:
四、
五、
复制一个服务 --server.port=10001
分布式锁
redisson
@Configuration
public class MyRedissonConfig {
/**
* 所有对Redisson的使用都是通过RedissonClient
*/
@Bean(destroyMethod="shutdown")
public RedissonClient redisson() throws IOException {
//1、创建配置
Config config = new Config();
config.useSingleServer().setAddress("redis://192.168.56.10:6379");
//2、根据Config创建出RedissonClient实例
//Redis url should start with redis:// or rediss://
RedissonClient redissonClient = Redisson.create(config);
return redissonClient;
}
}
- 测试
@Autowired
RedissonClient redissonClient;
@Test
public void testRedisson() {
System.out.println(redissonClient);
}
可重入锁
假设有方法A和方法B,方法A调用方法B,方A和方法B同一把锁,方法A调用方法B时,B看A已经加了同一把锁了,就把锁直接拿来用,这样B就可以正常执行,执行完A释放锁
不可重入锁
A和B都想加同一把锁,A调用B,一进到A,A持有锁,调用到B时,B需要等A释放锁才能抢到锁,B在等A,A想调B,造成死锁
看门狗
@ResponseBody
@GetMapping(value = "/hello")
public String hello() {
//1、获取一把锁,只要锁的名字一样,就是同一把锁
RLock myLock = redisson.getLock("my-lock");
//2、加锁
myLock.lock(); //阻塞式等待。默认加的锁都是30s
//1)、锁的自动续期,如果业务超长,运行期间自动锁上新的30s。不用担心业务时间长,锁自动过期被删掉
//2)、加锁的业务只要运行完成,就不会给当前锁续期,即使不手动解锁,锁默认会在30s内自动过期,不会产生死锁问题
// myLock.lock(10,TimeUnit.SECONDS); //10秒钟自动解锁,自动解锁时间一定要大于业务执行时间
//问题:在锁时间到了以后,不会自动续期
//1、如果我们传递了锁的超时时间,就发送给redis执行脚本,进行占锁,默认超时就是 我们制定的时间
//2、如果我们指定锁的超时时间,就使用 lockWatchdogTimeout = 30 * 1000 【看门狗默认时间】
//只要占锁成功,就会启动一个定时任务【重新给锁设置过期时间,新的过期时间就是看门狗的默认时间】,每隔10秒都会自动的再次续期,续成30秒
// internalLockLeaseTime 【看门狗时间】 / 3, 10s
try {
System.out.println("加锁成功,执行业务..." + Thread.currentThread().getId());
try { TimeUnit.SECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }
} catch (Exception ex) {
ex.printStackTrace();
} finally {
//3、解锁 假设解锁代码没有运行,Redisson会不会出现死锁
System.out.println("释放锁..." + Thread.currentThread().getId());
myLock.unlock();
}
return "hello";
}
因此,所有所都应该设计成可重入锁,避免死锁问题
只加synchronized,==》线程排队等锁,有多少个请求就会查询几次数据库
所以在锁的开始,需要判断缓存中是否已经有想要的数据了 这样最终只会查询一次数据库
上述只能用于单体应用,分布式应用有很多服务,每个服务对应一个实例,一个进程 有几个实例就查询几次数据库