redis 缓存击穿
客户端访问redis,redis的下级还有有mysql,击穿发生在redis做缓存的时候
,redis中的key设置了过期时间或者过期策略(LRU和LFU),如果key过期了
那么很多请求会直达数据库。【前置肯定是发生了高并发,且key过期了】
解决方案
并发有了:要组织并发到达DB,redis没有请求的key【redis单进程单实例的】
所有客户端增加一个逻辑,使用setnx()-》约等于是一把锁,所有的人去抢这把锁,
只有获得所得去访问DB。
整个流程是:
先去getkey如果失败了以后,大家都去setnx,拿到锁的人去DB,其他的人则去随机等待。
产生问题:
如果第一个人挂了,会产生让其他人一直等待【死锁】,所以可以设置锁的过期时间,
但是这样会产生,如果第一个人没挂,但是锁超时了,其他的人来也超时了,【此时使用多线程一个线程取DB另一个线程去监控是否取回来,更新锁时间,但是会提高客户端代码逻辑复杂度】
缓存穿透
从业务接收查询的是你系统根本不存在的数据,(缓存和数据库里面没有),这样会造成数据库压力变大
。
解决方案:
使用布隆过滤器:
1、客户端包含布隆过滤器的算法,对客户端内存要求大
2、客户端只包含算法,然后使用redis的bitmap来存,这样客户端是无状态的
3、redis里面集成一个bloom的模块指令(算法数据都有,客户端比较简单)
但是会有如下问题:
1、不能删除(换成布谷鸟等布隆过滤器,设置一个空key【有key无value】)
缓存雪崩
客户端访问redis,redis做缓存用,出现情况,大量的key同时失效,间接造成大量的访问到达DB。
解决方案:
随机过期时间:
分为时点性无关【随机过期时间】
时点性有关的【必须要零点去刷新DB的数据到内存】,则需要强依赖击穿的方案,在业务层里面
加一个判断,零点延时(业务层只要到了十二点了就做一个延时,等待更新完毕)
分布式锁
1、setnx
2、过期时间
3、多线程(守护线程)延长过期时间使用
使用redisson 和zookeeper做分布式锁
常用API
jedis,多线程不安全,需要做池化技术
,如客户端1开启的事务未提交,客户端2的请求又来了,那么是拿不到结果的
基本API演示
本地连接时配置
redis常用操作:
首先引入如下依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-json</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
启动类通过如下方式去调用:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ConfigurableApplicationContext ctx = SpringApplication.run(DemoApplication.class, args);
TestRedis redis = ctx.getBean(TestRedis.class);
redis.testRedis();
}
}
@Autowired
private RedisTemplate redisTemplate;
//这种方式redis的key是序列化了的
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private ObjectMapper objectMapper;
public void testRedis(){
redisTemplate.opsForValue().set("hello01","china");
System.out.println(redisTemplate.opsForValue().get("hello01"));
RedisConnection conn = redisTemplate.getConnectionFactory().getConnection();
conn.set("hello02".getBytes(),"test".getBytes());
System.out.println(new String(conn.get("hello02".getBytes())));
HashOperations<String, Object, Object> hash = stringRedisTemplate.opsForHash();
hash.put("sean","name","lsd");
hash.put("sean","age","22");
System.out.println(hash.entries("sean"));
Person p = new Person();
p.setName("zhangsan");
p.setAge(16);
//value是个Integer,此处通过json的方式去转换
stringRedisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
//第二个参数是是否展开,就是说是否包含内部类
Jackson2HashMapper jm = new Jackson2HashMapper(objectMapper, false);
stringRedisTemplate.opsForHash().putAll("sean01",jm.toHash(p));
Map map = stringRedisTemplate.opsForHash().entries("sean01");
//转换取出的map
Person per = objectMapper.convertValue(map, Person.class);
System.out.println(per.getName());
}
也能自定义template:
/**
* 自定义template
*/
@Configuration
public class MyTemplate {
@Bean
public StringRedisTemplate ooxx(RedisConnectionFactory fc){
StringRedisTemplate tp = new StringRedisTemplate(fc);
tp.setHashValueSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class));
return tp ;
}
}
@Component
public class TestRedis {
@Autowired
private RedisTemplate redisTemplate;
//这种方式redis的key是序列化了的
@Autowired
@Qualifier("ooxx")
private StringRedisTemplate stringRedisTemplate;
@Autowired
private ObjectMapper objectMapper;
public void testRedis(){
redisTemplate.opsForValue().set("hello01","china");
System.out.println(redisTemplate.opsForValue().get("hello01"));
RedisConnection conn = redisTemplate.getConnectionFactory().getConnection();
conn.set("hello02".getBytes(),"test".getBytes());
System.out.println(new String(conn.get("hello02".getBytes())));
HashOperations<String, Object, Object> hash = stringRedisTemplate.opsForHash();
hash.put("sean","name","lsd");
hash.put("sean","age","22");
System.out.println(hash.entries("sean"));
Person p = new Person();
p.setName("zhangsan");
p.setAge(16);
//value是个Integer,此处通过json的方式去转换
stringRedisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
//第二个参数是是否展开,就是说是否包含内部类
Jackson2HashMapper jm = new Jackson2HashMapper(objectMapper, false);
stringRedisTemplate.opsForHash().putAll("sean01",jm.toHash(p));
Map map = stringRedisTemplate.opsForHash().entries("sean01");
//转换取出的map
Person per = objectMapper.convertValue(map, Person.class);
System.out.println(per.getName());
stringRedisTemplate.convertAndSend("ooxx","hello");
RedisConnection cc = stringRedisTemplate.getConnectionFactory().getConnection();
cc.subscribe(new MessageListener() {
@Override
public void onMessage(Message message, byte[] pattern) {
byte[] body = message.getBody();
System.out.println(new String(body));
}
}, "ooxx".getBytes());
while(true){
stringRedisTemplate.convertAndSend("ooxx","hello from wo zi ji ");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}