Redis基础
redis是一个分布式的缓存技术,采用key-value对数据进行存储,它的数据类型主要分为5种:
- String
- Hash
- List
- Set
- ZSet
在日常开发中,redis是十分重要的,是一种面向NoSql的存储结构,也可用作快存快取的缓存场景。
Redis整合springBoot
配置
- 引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- 配置yml文件
spring:
redis:
host: 127.0.0.1
port: 6379
应用
在Java中操作redis时一般采用的是RedisTemplate,这个类主要有以下几种方法:
- redisTemplate.opsForValue();//操作字符串
- redisTemplate.opsForHash();//操作hash
- redisTemplate.opsForList();//操作list
- redisTemplate.opsForSet();//操作set
- redisTemplate.opsForZSet();//操作有序set
代码示例
private String selectByRedis() {
String s = redisTemplate.opsForValue().get("spuModel");
if (StringUtils.isEmpty(s)) {
//查询数据库
//查询完后存入redis
List<SpuEsModel> spuEsModels = selectByDb();
s = JSON.toJSONString(spuEsModels);
redisTemplate.opsForValue().set("spuModel", s);
return s;
}
return s;
}
缓存失效
缓存穿透
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
- 解决
缓存空对象、布隆过滤器、mvc拦截器
缓存雪崩
缓存雪崩是指在我们设置缓存时key采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。
- 解决
规避雪崩:缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
如果缓存数据库是分布式部署,将热点数据均匀分布在不同缓存数据库中。
设置热点数据永远不过期。
出现雪崩:降级 熔断
缓存击穿
缓存雪崩和缓存击穿不同的是:
- 缓存击穿 指 并发查同一条数据。缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
- 缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决
- 设置热点数据永远不过期。
- 加锁
分布式锁
如果redis中没有相应的key,就需要去查询DB, 但是在分布式场景下,多台机器发送请求同时去查询DB可能会造成负载飙高,所有就需要进行加入redis分布式锁进行处理。也就是不同的机器同时去竞争锁,先拿到锁的机器去查询数据库并设置缓存,这样就节省了很大的时间以及空间。
- redis分布式锁:Set NX
API:SET key value [EX seconds] [PX milliseconds] [NX|XX] - 业务场景图:
- 代码实例
//redis分布式锁
private String selectByRedisLock() throws InterruptedException {
String uuid = UUID.randomUUID().toString();
//給redis设置分布式锁并设置过期时间
boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);
if (lock) {
//已经拿到锁了
//利用LUA脚本删除锁
String s = null;
try {
//设置redis缓存
List<SpuEsModel> spuEsModels = selectByDb();
s = JSON.toJSONString(spuEsModels);
} finally {
//无论成功与否,都要把锁删除,利用LUA脚本
// get和delete原子操作
String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +
" return redis.call(\"del\",KEYS[1])\n" +
"else\n" +
" return 0\n" +
"end";
stringRedisTemplate.execute(
new DefaultRedisScript<Long>(script, Long.class), // 脚本和返回类型
Arrays.asList("lock"), // 参数
uuid); // 参数值,锁的值
}
return s;
} else {
//自旋锁。重试获取锁
//先等待200毫秒再进行自旋
Thread.sleep(200);
selectByRedisLock();
}
return null;
}