文章目录
- ①.Redis的概述
- ②. Redis6的安装
- ②.数据结构介绍
- ③. 持久化
- ④. Jedis客户端
- ⑤. Jedis连接池
- ⑥. 案列
- ⑦. Spring整合Redis
- ⑧. Spring Boot整合-redis
- ⑨. SpringDataRedis
- ⑩. 缓存穿透、缓存击穿、缓存雪崩
- ⑩①.缓存预热具体代码实现
①.Redis的概述
-
①. redis是一款高性能的开源NOSQL系列的非关系型数据库,Redis是用C语言开发的一个开源的高键值对(key value)数据库,官方提供测试数据,50个并发执行100000个请求,读的速度是110000次/s,写的速度是81000次/s,且Redis通过提供多种键值数据类型来适应不同场景下的存储需求,目前Redis支持的键值数据类型如下:(字符串类型String、哈希类型hash、列表类型list、集合类型set、有序集合类型sortedset)
-
②. 缓存思想:从缓存中获取数据
有数据,直接返回
没有数据,从数据库查询,将数据放入缓存,返回数据 -
③. 使用场景
- 高频次,热门访问的数据,降低数据库IO
- 分布式架构,做session共享
- 多样的数据结构存储持久化数据
- ④. Redis默认端口6379的由来(Alessia Merz)
- ⑤. 默认16个数据库,类似数组下标从0开始,初始默认使用0号库
使用命令 select dbid来切换数据库。如: select 8
统一密码管理,所有库同样密码。
dbsize查看当前数据库的key的数量
flushdb清空当前库
flushall通杀全部库
②. Redis6的安装
-
①. Redis6.0.9安装
-
③. linux下Redis的目录信息
- redis-benchmark:性能测试工具,可以在自己本子运行,看看自己本子性能如何
- redis-check-aof:修复有问题的AOF文件,rdb和aof后面讲
- redis-check-dump:修复有问题的dump.rdb文件
- redis-sentinel:Redis集群使用
- redis-server:Redis服务器启动命令
- redis-cli:客户端,操作入口
- ④. 由于我们的服务器在青牛云上,对端口进行了映射处理,这里的redis的端口也在配置文件中修改成了9736,我们在进入redis客户端的时候要使用redis-cli -p 9736
②.数据结构介绍
2>.
数据结构介绍
1.
redis的数据结构
redis存储的是:key,value格式的数据,其中key都是字符串,value有5种不同的数据结构如下:
-
①. 字符串类型 string
-
②. 列表类型 list :linkedlist格式。支持重复元素
-
③. 集合类型 set :不允许重复元
-
④. 哈希类型 hash :map格式
-
⑤. 有序集合类型 sortedset:不允许重复元素,且元素有顺序
①. 字符串类型:String
2.
字符串类型:String
-
①. 存储: set key value
-
②. 获取: get key
-
③. 删除:del key
②. hash类型
3.
hash类型
-
①. 存储: hset key field value
-
②. 获取:hget key field: 获取指定的field对应的值
-
③. 获取:hgetall key:获取所有的field和value
-
④. 删除: hdel key field
③. 列表类型 list
4.
列表类型 list
- 可以添加一个元素到列表的头部(左边)或者尾部(右边)
- ①. 添加:
- ②. 获取:
- ③. 删除:
④. 集合类型set
5.
集合类型set
不允许重复元素
-
①. 存储:sadd key value
-
②. smembers key:获取set集合中所有元素
-
③. 删除:srem key value:删除set集合中的某个元素
⑤. 有序集合sortedset
6.
有序集合类型 sortedset
-
不允许重复元素,且元素有顺序.每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序
-
①. 存储:zadd key score value
-
②. 获取:zrange key start end [withscores]
-
③.删除:zrem key value
⑥. 通用命令
7.
通用命令
-
①. keys * : 查询所有的键
-
②. type key : 获取键对应的value的类型
-
③. del key:删除指定的key value
③. 持久化
3>.
持久化(了解)
redis是一个内存数据库,当redis服务器重启,获取电脑重启,数据会丢失,我们可以将redis内存中的数据持久化保存到硬盘的文件中
1.
RDB(持久化机制默认)
-
默认方式,不需要进行配置,默认就使用这种机制;
-
在一定的间隔时间中,检测key的变化情况,然后持久化数据
2.
AOF
-
日志记录的方式,可以记录每一条命令的操作。可以每一次命令操作后,持久化数据
④. Jedis客户端
4>.
Java客户端 Jedis(了解)
- 一款java操作redis数据库的工具(类似于JDBC)
1.Jedis快速入门
public class JedisDemo1 {
public static void main(String[] args) {
Jedis jedis=new Jedis("localhost",6379);
jedis.set("username","xiaozhi");
System.out.println(jedis.get("username"));
jedis.close();
}
}
2.
Jedis操作String
-
字符串类型 string:set 、get
-
注意:
当我们使用jedis.setex(“activeCode”,20,“hehe”):这里的意思是,将键为activeCode值为hehe存入redis中,20s后自动删除该键值对
3.
哈希类型 hash : map格式
-
①. hset() hget() hgetAll()
-
②. 获取hash的所有map中的数据:
Map<String, String> user = jedis.hgetAll("user");
4.
列表操作(list)
-
①. 列表类型 list : linkedlist格式。支持重复元素
-
②. lpush / rpush || lpop / rpop || lrange start end : 范围获取
5.
集合类型 set : 不允许重复元素
- sadd( ) ||smembers( ):获取所有元素
6.
有序集合类型 sortedset
-
①. 不允许重复元素,且元素有顺序
-
②. 方法:zadd()zrange()
⑤. Jedis连接池
5>.
jedis连接池: JedisPool
- ①.Jedis详细配置文件
连接池是一种与无限数量的并发用户共享有限数量的数据库连接的技术。最大总连接数( maxTotal )
包括活动连接和空闲连接,即正在使用的连接和当前未使用的连接(它是所有连接的总和)。最大空闲
连接( maxIdle )是准备使用的连接(但当前未使用)
#最大活动对象数
redis.pool.maxTotal=1000
#最大能够保持idle状态的对象数
redis.pool.maxIdle=100
#最小能够保持idel状态的对象数
redis.pool.minIdle=50
#当池内没有返回对象时,最大等待时间
redis.pool.maxWaitMillis=10000
#当调用borrow Object方法时,是否进行有效性检查
redis.pool.testOnBorrow=true
#当调用return Object方法时,是否进行有效性检查
redis.pool.testOnReturn=true
#“空闲链接”检测线程,检测的周期,毫秒数。如果为负值,表示不运行“检测线程”。默认为-1.
redis.pool.timeBetweenEvictionRunsMillis=30000
#向调用者输出“链接”对象时,是否检测它的空闲超时;
redis.pool.testWhileIdle=true
# 对于“空闲链接”检测线程而言,每次检测的链接资源的个数。默认为3.
redis.pool.numTestsPerEvictionRun=50
#redis服务器的IP
redis.ip=xxxxxx
#redis服务器的Port
redis1.port=6379
- ②. jedis.properties
host=127.0.0.1
port=6379
maxTotal=50
maxIdle=10
- ③. 连接池工具类
[掌握]
public class JedisPoolUtils {
private static JedisPool jedisPool;
static{
//读取配置文件
InputStream is = JedisPoolUtils.class.getClassLoader().getResourceAsStream("jedis.properties");
//创建Properties对象
Properties pro = new Properties();
//关联文件
try {
pro.load(is);
} catch (IOException e) {
e.printStackTrace();
}
//获取数据,设置到JedisPoolConfig中
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(Integer.parseInt(pro.getProperty("maxTotal")));
config.setMaxIdle(Integer.parseInt(pro.getProperty("maxIdle")));
//初始化JedisPool
jedisPool = new JedisPool(config,
pro.getProperty("host"),
Integer.parseInt(pro.getProperty("port")));
}
/**
* 获取连接方法
*/
public static Jedis getJedis(){
return jedisPool.getResource();
}
}
⑥. 案列
6>.
案例(掌握)
需求:
(1). 提供index.html页面,页面中有一个省份 下拉列表
(2). 当页面加载完成后 发送ajax请求,加载所有省份
后台代码:
(1).servlet
(2).service
(3)dao
前台代码:
⑦. Spring整合Redis
7>.
Spring整合Redis
- ①.在SpringMvc中要在spring-mvc.xml中加上import resource=""
注意:
因为在SptringMvc中有<mvc:annotation/> 或
<dubbo:annotation package="com.itheima.controller" />
所以在@Autowired private JedisPool jedisPool中,
不用使用<context:annotation-config/> @Autowired可以注入成功!
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--Jedis连接池的相关配置-->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="200">
<!-- <value>200</value>-->
</property>
<property name="maxIdle" value="50">
<!-- <value>50</value>-->
</property>
<property name="testOnBorrow" value="true"/>
<property name="testOnReturn" value="true"/>
</bean>
<bean id="jedisPool" class="redis.clients.jedis.JedisPool">
<constructor-arg name="poolConfig" ref="jedisPoolConfig" />
<constructor-arg name="host" value="127.0.0.1" />
<constructor-arg name="port" value="6379" type="int" />
<constructor-arg name="timeout" value="30000" type="int" />
</bean>
</beans>
- ②.在Spring中整合Redis,会在web.xml加载Spring-redis.xml
注意:
因为Spring中有<context:component-scan> 或
<dubbo:annotation package="com.itheima.service" />
所以在@Autowired private JedisPool jedisPool中,
不用使用<context:annotation-config/> @Autowired可以注入成功!
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--Jedis连接池的相关配置-->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="200">
<!-- <value>200</value>-->
</property>
<property name="maxIdle" value="50">
<!-- <value>50</value>-->
</property>
<property name="testOnBorrow" value="true"/>
<property name="testOnReturn" value="true"/>
</bean>
<bean id="jedisPool" class="redis.clients.jedis.JedisPool">
<constructor-arg name="poolConfig" ref="jedisPoolConfig" />
<constructor-arg name="host" value="127.0.0.1" />
<constructor-arg name="port" value="6379" type="int" />
<constructor-arg name="timeout" value="30000" type="int" />
</bean>
</beans>
- ③. 如果不是在Spring或SpringMvc环境下,要加上 context:annotation-config开启注解,才能使用@Autowired
⑧. Spring Boot整合-redis
8>.
RedisTemplate
- ①. pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
</parent>
<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-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
- ②. yml文件
server:
port: 80
spring:
redis:
localhost: 127.0.0.1
port: 6379
- ③. 测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisTest {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void fun1(){
//1.字符串
redisTemplate.boundValueOps("myString").set("xiaozhi");
String myString = (String) redisTemplate.boundValueOps("myString").get();
System.out.println("字符串String类型"+myString);
System.out.println("----------");
//2.hash 散列
redisTemplate.boundHashOps("myHash").put("name","xiaoxing");
redisTemplate.boundHashOps("myHash").put("age",23);
Set keys = redisTemplate.boundHashOps("myHash").keys();
List values = redisTemplate.boundHashOps("myHash").values();
System.out.println("keys:"+keys);
System.out.println("values"+values);
System.out.println("----------");
//3.列表类型list
redisTemplate.boundListOps("myList").leftPush("a","b");
redisTemplate.boundListOps("myList").rightPush("c");
List myList = redisTemplate.boundListOps("myList").range(0, -1);
System.out.println("有序集合类型myList"+myList);
System.out.println("----------");
//4.集合类型
redisTemplate.boundSetOps("mySet").add("a");
Set mySet = redisTemplate.boundSetOps("mySet").members();
System.out.println("集合类型mySet = " + mySet);
System.out.println("----------");
//5.有序集合类型
redisTemplate.boundZSetOps("mySort").add("b",70);
redisTemplate.boundZSetOps("mySort").add("c",50);
Set mySort = redisTemplate.boundZSetOps("mySort").range(0, -1);
System.out.println("有序集合类型mySort = " + mySort);
}
}
⑨. SpringDataRedis
9>.
SpringDataRedis
①. SpringDataRedis简介
1.SpringDataRedis简介
-
SpringDataRedis 属于Spring Data 家族一员,用于对redis的操作进行封装的框架
-
Spring Data ----- Spring 的一个子项目。Spring 官方提供一套数据层综合解决方案,用 于简化数据库访问,支持NoSQL和关系数据库存储。包括Spring Data JPA 、Spring Data Redis 、SpringDataSolr 、SpringDataElasticsearch 、Spring DataMongodb 等 框架
②. SpringDataRedis快速入门
2.
SpringDataRedis快速入门
- ①. 准备工作
构建Maven工程 SpringDataRedisDemo 引入Spring相关依赖、JUnit依赖、Jedis 和SpringDataRedis依赖
<dependencies>
<!--缓存-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>2.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
</dependencies>
- ②. 在src/main/resources下创建properties文件夹,建立redis-config.properties
redis.host=127.0.0.1
redis.port=6379
redis.pass=
redis.database=0
redis.maxIdle=300
redis.maxWait=3000
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="classpath:redis-config.properties" />
<!-- redis 相关配置 -->
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxIdle" value="${redis.maxIdle}" />
<property name="maxWaitMillis" value="${redis.maxWait}" />
</bean>
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
p:host-name="${redis.host}" p:port="${redis.port}" p:password="${redis.pass}" p:pool-config-ref="poolConfig"/>
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory" />
</bean>
</beans>
- ③.value类型的案列
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(value = {"classpath:applicationContext-redis.xml"})
public class TestValue {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void setValue(){
//向name属性中去存xiaozhi
redisTemplate.boundValueOps("name").set("xiaozhi");
}
@Test
public void getValue(){
Object name = redisTemplate.boundValueOps("name").get();
System.out.println("name = " + name);//name=xiaozhi
}
@Test
public void deleteValue(){
Boolean flag = redisTemplate.delete("name");
System.out.println("flag = " + flag);
}
}
③. 五种基本类型操作redis
3.
五种基本类型操作redis
- ①.值类型操作
1.redisTemplate.boundValueOps("name").set("xiaozhi");
2.Object redisTemplate.boundValueOps("name").get();
3.boolean delete(Object Key)
[set username xiaozhi
get username
del username]
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(value = {"classpath:applicationContext-redis.xml"})
public class TestValue {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void setValue(){
//向name属性中去存xiaozhi
redisTemplate.boundValueOps("name").set("xiaozhi");
}
@Test
public void getValue(){
Object name = redisTemplate.boundValueOps("name").get();
System.out.println("name = " + name);//name=xiaozhi
}
@Test
public void deleteValue(){
Boolean flag = redisTemplate.delete("name");
System.out.println("flag = " + flag);
}
}
- ②. Set类型操作
1.boundSetOps("mySet").add("小智");
2.Set boundSetOps("mySet").members();
3.Long boundSetOps("mySet").remove("小智");
sadd myset a
smembers myset
srem myset a
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(value = {"classpath:applicationContext-redis.xml"})
public class TestSet {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void setValue(){
redisTemplate.boundSetOps("mySet").add("小智");
redisTemplate.boundSetOps("mySet").add("小幸");
redisTemplate.boundSetOps("mySet").add("洋洋");
}
@Test
public void getValue(){
Set mySet = redisTemplate.boundSetOps("mySet").members();
System.out.println("mySet = " + mySet);
}
@Test
public void deleteValue(){
Long remove = redisTemplate.boundSetOps("mySet").remove("小智");
System.out.println(remove);
}
- ③. List类型操作
1.redisTemplate.boundListOps("myList").leftPush("a");
redisTemplate.boundListOps("myList").rightPush("b");
2.boundListOps("myList").range(0, -1);
3.boundListOps("myList").remove(1, "a");
[range(开始索引,查询个数)]
存储:
lpush myList a
rpush myList b
获取:
lrange myList 0 -1
删除:
lpop myList
rpop myList
@Test
public void SetValue(){
redisTemplate.boundListOps("myList").leftPush("a");
redisTemplate.boundListOps("myList").leftPush("b");
redisTemplate.boundListOps("myList").rightPush("c");
//bac
}
@Test
public void getValue(){
//获取myList中的数据
List myList = redisTemplate.boundListOps("myList").range(0, -1);
System.out.println("myList = " + myList);//myList = [b, a, c]
//删除myList中的数据
Long result= redisTemplate.boundListOps("myList").remove(1, "a");
System.out.println("result = " + result);//result = 1
//再次遍历,只有bc
List myList2= redisTemplate.boundListOps("myList").range(0, -1);
System.out.println("myList2 = " + myList2);//myList2 = [b, c]
}
- ④. Hash类型
1.boundHashOps("myhash").put("name","xiaozhi");
2.获取:
获取所有的key:Set boundHashOps("myhash").keys();
获取所有的value:List boundHashOps("myhash").values();
通过某个key得到value:Object boundHashOps("myhash").get("name");
3.删除:
boundHashOps("myhash").delete("name");
hset myhash name xiaozhi
hget myhash name
hdel myhash name
@Autowired
private RedisTemplate redisTemplate;
@Test
public void setValue(){
redisTemplate.boundHashOps("myhash").put("name","xiaozhi");
redisTemplate.boundHashOps("myhash").put("age","13");
}
@Test
public void getValue(){
//1.得到所有的key
Set myhash = redisTemplate.boundHashOps("myhash").keys();
System.out.println("所有的key是: "+myhash);
//2.得到所有的value
List values = redisTemplate.boundHashOps("myhash").values();
System.out.println("得到的values是: "+values);
//3.通过某个key得到value
Object name = redisTemplate.boundHashOps("myhash").get("name");
System.out.println("name = " + name);
}
@Test
public void deleteValue(){
//删除name=xiaozhi
Long result = redisTemplate.boundHashOps("myhash").delete("name");
System.out.println("result = " + result);
}
- ⑤. 有序集合类型
1.存值:
boundZSetOps("myZset").add("xiaozhi",10);
2.获取值
从小到大获取值:Set boundZSetOps("myZset").range(0, -1);
倒序:从大到小 boundZSetOps("myZset").reverseRange(0, 3);
获取每个元素的key value: boundZSetOps("myZset").reverseRangeWithScores(0, -1);
typedTuple.getScore()
typedTuple.getValue()
3.给xiaozhi增加
score 100:boundZSetOps("myZset").incrementScore("xiaozhi",100);
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(value={"classpath:applicationContext-redis.xml"})
public class TestZSet {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void setValue(){
//向ZSet集合中添加数据
redisTemplate.boundZSetOps("myZset").add("xiaozhi",10);
redisTemplate.boundZSetOps("myZset").add("xiaoxing",20);
redisTemplate.boundZSetOps("myZset").add("yangyang",30);
}
@Test
public void getValue(){
//从小到大获取值
Set myZset1= redisTemplate.boundZSetOps("myZset").range(0, -1);
System.out.println("从小到大获取值"+myZset1);
//倒序:从大到小
//Set myZset2 = redisTemplate.boundZSetOps("myZset").reverseRange(0, -1);
Set myZset2 = redisTemplate.boundZSetOps("myZset").reverseRange(0, 3);
System.out.println("从大到小获取值"+myZset2);
//获取每个元素的key value
Set<ZSetOperations.TypedTuple> myZset4 = redisTemplate.boundZSetOps("myZset").reverseRangeWithScores(0, -1);
for(ZSetOperations.TypedTuple typedTuple:myZset4){
System.out.println("获取的key为:"+typedTuple.getScore());
System.out.println("获取的value为:"+typedTuple.getValue());
}
System.out.println("-------");
//给xiaozhi增加 score 100
redisTemplate.boundZSetOps("myZset").incrementScore("xiaozhi",100);
Set<ZSetOperations.TypedTuple> myZset5 = redisTemplate.boundZSetOps("myZset").reverseRangeWithScores(0, -1);
for(ZSetOperations.TypedTuple typedTuple:myZset5){
System.out.println("获取的key为:"+typedTuple.getScore());
System.out.println("获取的value为:"+typedTuple.getValue());
}
}
/*
从小到大获取值[xiaoxing, yangyang, xiaozhi]
从大到小获取值[xiaozhi, yangyang, xiaoxing]
获取的key为:110.0
获取的value为:xiaozhi
获取的key为:30.0
获取的value为:yangyang
获取的key为:20.0
获取的value为:xiaoxing
-------
获取的key为:210.0
获取的value为:xiaozhi
获取的key为:30.0
获取的value为:yangyang
获取的key为:20.0
获取的value为:xiaoxing
* */
}
- ⑥. 设置过期时间
Boolean expire(long timeout, TimeUnit unit)
;
redisTemplate.boundValueOps("name").set("itcast");
redisTemplate.boundValueOps("name").expire(10,TimeUnit.SECONDS);
⑩. 缓存穿透、缓存击穿、缓存雪崩
10>.
缓存穿透、缓存击穿、缓存雪崩
①. 缓存穿透
1.
缓存穿透
-
①. 问题的产生:缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id 为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据 库压力过大。如下面这段代码就存在缓存穿透的问题
-
②. 如下代码会产生缓存穿透
- ③. 解决方案:
三点解决方案:
(1).接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
(2).从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为 key-0。这样可以防止攻击用户反复用同一个id暴力攻击。代码举例:
(3).使用缓存预热
缓存预热就是将数据提前加入到缓存中,当数据发生变更,再将最新的数据更新到缓 存
②. 缓存击穿
2.
缓存击穿
-
①. 问题的产生:缓存击穿是指缓存中没有但数据库中有的数据。这时由于并发用户特别多,同时读 缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压 力
-
②. 以下代码可能会产生缓存击穿:
-
③. 解决方案:
解决方案:
(1).设置热点数据永远不过期
(2).缓存预热
③. 缓存雪崩
3.
缓存雪崩
-
①. 问题的产生:缓存雪崩是指缓存数据大批量到过期时间,而查询数据量巨大,引起数据库压力过 大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同 数据都过期了,很多数据都查不到从而查数据库
-
②. 解决方案:
1.缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生
2.设置热点数据永远不过期。
3.使用缓存预热
⑩①.缓存预热具体代码实现
11>.
缓存预热具体代码实现
按照如下三步进行:
1.将mysql数据库中的数据加载到缓存
2.从缓存中查询缓存数据[查询操作]
3.更新缓存[增删改操作]
①. 分类导航(String数据类型)
1.
分类导航缓冲预热实现
- ①. 通用mapper中分类导航查询方法
[掌握]
首页分类导航的渲染: 拿到数据后,在模板页面中进行三次循环即可
[注意:这里前端要一个树状的结构,我们需要一个parentId的字段]
思路有两种:
1.通过parentId=0 查询得到一级菜单,遍历一级菜单,得到二级菜单,遍历二级菜单,
得到三级菜单,不推荐使用,因为和数据库需要频繁的交互,交互次数=1+一级菜单数量+二级菜单数量
2.首先把符合条件的记录(每一级菜单列表)[推荐使用]
public List<Map> findByParentId(List<Category>categories,Integer parentId){
List<Map>listMap=new ArrayList<Map>();
for(Category category:categories){
if(category.getParentId().equals(parentId)){
Map map=new HashMap();
map.put("name",category.getName());
map.put("menus",findByParentId(categories,category.getId()));
listMap.add(map);
}
}
return listMap;
}
- ②. 创建一个Init 当服务开启的时候,这个类就进行加载
@Component
public class Init implements InitializingBean {
public Init(){
/* System.out.println("1111111");*/
}
@Autowired
private CategoryService categoryService;
public void afterPropertiesSet() throws Exception {
System.out.println("缓存预热");
//加载商品分类导航缓存
categoryService.saveCategoryTreeToRedis();
}
}
- ③. 核心分类导航代码
public void saveCategoryTreeToRedis() {
Example example=new Example(Category.class);
Example.Criteria criteria = example.createCriteria();
criteria.andEqualTo("isShow","1");//显示
example.setOrderByClause("seq");//排序
List<Category> categories = categoryMapper.selectByExample(example);
List<Map> categoryTree = findByParentId(categories, 0);
//存入redis
redisTemplate.boundValueOps(CacheKey.CATEGROY_TREE).set(categoryTree);
}
- ④. 从缓存中查询缓存数据[查询操作]
//首页分类导航渲染
public List<Map> findCategoryTree() {
/* Example example=new Example(Category.class);
Example.Criteria criteria = example.createCriteria();
criteria.andEqualTo("isShow","1");//显示
example.setOrderByClause("seq");//排序
List<Category> categories = categoryMapper.selectByExample(example);
return findByParentId(categories,0);*/
//从缓存中提取分类
System.out.println("redis category ");
return (List<Map>)redisTemplate.boundValueOps(CacheKey.CATEGROY_TREE).get();
}
- ⑤.更新缓存[增删改操作]
②. 广告轮播图缓存(Hash类型)
2.
广告轮播图缓存
- ①. 创建一个Init 当服务开启的时候,这个类就进行加载
@Component
public class Init implements InitializingBean {
@Autowired
private AdService adService;
public void afterPropertiesSet() throws Exception {
adService.saveAllAdToRedis();
}
}
- ②. 将mysql中的数据存储到redis中
//通过position在数据库中查询所有符合的条件并保存到redis
public void saveAdToRedisByPosition(String position) {
Example example=new Example(Ad.class);
Example.Criteria criteria = example.createCriteria();
criteria.andEqualTo("position",position);
criteria.andLessThanOrEqualTo("startTime",new Date());
criteria.andGreaterThanOrEqualTo("endTime",new Date());
criteria.andEqualTo("status","1");
List<Ad> adList = adMapper.selectByExample(example);
//将数据库查询的所有符合条件的adList存储到Redis
redisTemplate.boundHashOps(CacheKey.AD).put(position,adList);
}
private List<String> getPosition(){
List<String> list=new ArrayList<String>();
list.add("index_lb");
//如果还有别的广告在这里添加
return list;
}
//从mysql中查询所有的数据
public void saveAllAdToRedis() {
for(String position:getPosition()){
saveAdToRedisByPosition(position);
}
}
- ③.从缓存中查询缓存数据[查询操作]
//根据广告位置查询广告列表
public List<Ad> findByPosition(String position) {
/* //创建查询条件
Example example=new Example(Ad.class);
Example.Criteria criteria = example.createCriteria();
criteria.andEqualTo("position",position);
criteria.andLessThanOrEqualTo("startTime",new Date());
criteria.andGreaterThanOrEqualTo("endTime",new Date());
criteria.andEqualTo("status","1");
return adMapper.selectByExample(example);*/
System.out.println("redis Ad");
return (List<Ad>) redisTemplate.boundHashOps(CacheKey.AD).get(position);
}
- ④. 更新缓存[增删改操作]
注意:这里的修改,要考虑如果广告的位置从A -1 变成B +1
/**
* 修改
* @param ad
*/
public void update(Ad ad) {
//A -1 - - B +1
//position A
String position = adMapper.selectByPrimaryKey(ad.getId()).getPosition();
adMapper.updateByPrimaryKeySelective(ad);
/*修改后将redis数据将进行同步*/
saveAdToRedisByPosition(position);
if(!position.equals(ad.getPosition())){
saveAdToRedisByPosition(ad.getPosition());
}
}
/**
* 新增
* @param ad
*/
public void add(Ad ad) {
adMapper.insert(ad);
/*新增后将redis数据将进行同步*/
saveAdToRedisByPosition(ad.getPosition());
}
/**
* 删除
* @param id
*/
public void delete(Integer id) {
String position = adMapper.selectByPrimaryKey(id).getPosition();
adMapper.deleteByPrimaryKey(id);
/*删除后将redis数据将进行同步*/
saveAdToRedisByPosition(position);
}