redis详解

 

参考 b 站狂神说的视频

https://www.bilibili.com/video/BV1S54y1R7SB?p=36&share_source=copy_web

非关系型数据库:nosql 数据库

非关系型数据库的优点

1。方便扩展(数据之间没有关系,很好扩展)

2。大数据高性能(nosql 的缓存记录数)

3。数据类型是多样型的 (不需要事先设计数据库 随去随用)

 

高性能 高可用  高可扩

 

NOSQL的四大分类

K V键值对.      ————->缓存 日志

美团:redis tait

阿里 百度:redis memcache

 

文档型数据库    —————> 分布式文件系统

MongoDB(必须掌握 ) 

基于分布式文件存储的数据库 C++ 编写,主要用来处理大量的文档

mongoDB 是一个介于关系型数据库和非关系型数据库的中间的产品。MongoDB 是非关系型数据库中功能最丰富 最像非关系型数据库的

ConthDB

 

列存储数据库—-》web 应用    HBase

图关系数据库——-〉社交网络  Neo4j

 

2.Redis 是什么

1.redis 远程字典服务c 语言编写,支持网络的,基于内存的可持久化 日志型,key value 数据库。支持多种语言的API

免费和开源。 结构化数据库。

 

作用:

内存存储 持久化 (内存中是断电即失)(持久化 rdb aof)

效率高 可以用于高速缓存

发布订阅系统(简单消息队列)

地图信息分析

计时器 计数器

 

特性

1. 多样的数据类型

2. 持久化

3.集群

4,事务

 

测试性能

Redis-benchmark 压力测试工具

测试 100个并发 100000请求

Refis-benchmark -h localhost -p 6379 -c 100 -n 100000

 

Redis 推荐在 linux上搭建

 

3.Redis基础知识

Redis默认有 16个数据库  默认使用的第0 个

可以使用select 进行切换数据库

1
2
3
4
5
6
7
eg:select 3
 
 keys * 查看所有的key
 
flushall 清空所有的数据库
 
flushdb 清空当前数据库

   

Redis 是单线程的

Redis 是基于内存操作,CPU 并不是redis 的瓶颈,根据机器的内存和网络带宽

为什么单线程还这么快?

CPU>内存 >硬盘

Redis 是将所有的数据都存在内存中,对于内存系统来说,没有上下文切换效率就是最高的。

 

它可以用作数据库 缓存 消息中间件。

支持多种类型的数据结构 字符串 散列 列表 集合 有序集合与范围查询

redis 的五大数据类型

1
Redis-Key
1
2
3
4
5
判断是否存在   exists key值
移除指定的key值 Move key值 数据库
设置过期时间,单位是秒  expire name 10
查看key的剩余时间 ttl key值
查看key的类型 type key值

string

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
set key值 value 赋值
get key值 获取值
APPEND key值 “hello” 拼接字符串
如果key不存在就新建
STRLEN key值 字符串长度
incr key值  对应的值加1
decr key值  对应的值减1
INCRBY key值 步长 可以设置指定增量
eg:INCRBY views 10 对应的值加10
获取范围
eg:set key1 “hello,zhangsan”
GETRANGE key1 0 3
“hell”
GETRANGE key1 0 -1 //查看全部字符串
“hello,zhangsan”

  替换

1
2
3
4
5
6
7
8
set key2 abcdefg
SETRANGE key2 1 xx //替换指定位置开始的字符串
get key2 ->axxdefg
 
setex(set with expire) 设置过期时间
eg:setex key3 30 “hello”
setnx(set if not exists) 不存在设置
eg:setnx mikes  “redis”

 批量获取和批量设置

1
2
3
4
5
6
7
8
9
10
11
12
13
批量设置 mset
eg: mset key1 value1 key2 value2 key3 value3
批量获取 mget
eg: mget key1 key2 key3
msetnx key1 value1 key4 value4
//msetnx是个原子性操作,一起成功 一起失败
 
对象 设置一个user对象
user:{id}:{filed}
mset user:1:name zhangsan user:1:age 18
mget user:1:name user:1:age
1)”zhangsan”
2)”18

 先 get 后 set

1
2
3
4
5
getset  先get后set
getset db redis ->(nil)
get db —>redis
Getset db mongdb —->redis
get db—>mongdb

string 类型的应用:

计数器

统计数量

统计多单位的数量

对象缓存

 

List

列表  

所有的list命令都是l开头的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
添加 LPUSH list one  //每次将数据插入头部
 
LPUSH list two
 
获取 LRANGE list 0 -1
-》“two”
-》“one”
 
Rpush list right //每次将数据放到尾部
 
LRANGE list 0 -1
-》“two”
-》“one”
-》“Right

LPOP 移除尾部的值

RPOP 移除头部的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
eg: Lpop list
 
通过下标获取值
 
LRAANGE list 0 -1
-》two
-》one
 
Lindex list 0 —》two
  
 
返回列表的长度
Llen list —>2
 
移除指定值。list可以重复
Lpush list three
Lpush list three
 
Lrem list 1 three //从list中移除一个three
 
  
List 截断
 
Rpush mylist “hello”
Rpush mylist “hello1”
Rpush mylist “hello2”
Rpush mylist “hello3”
 
Ltrim mylist 1 2 //通过下标截取指定的长度
 
Lrange mylist 0 -1
->“hello1”
->“Hello2”
 
  
 
rpoplpush 移动list中最后一个值 并将其移动到新的列表中
 
rRush mylist “hello”
rRush mylist “hello1”
rRush mylist “hello2”
 
rpoplpush mylist myotherlist
 
Lrange mylist 0 -1
->“hello”
->“hello1”
 
Lrange myotherlist 0 -1
->“Hello2”
 
 
lset  将列表中指定下标的值替换为另外一个值 更新操作
如果列表不存在 更新会报错 如果下标的值存在更新,不存在 报错
Linset 将某个具体的value 插入到list中某个元素的前面和后面
 
Rpush mylist “hello”
Rpush mylist “world”
 
Insert mylist before world other
->“hello”
->“other”
->“World

小结

实际上是一个链表。

如果key 不存在 创建新的链表

在两边插入或者改动值 效率最高,中间元素,相对来说效率低一些 

应用:消息队列

 

Set (集合)

不能重复

所有的set命令都是s开头的 

添加

 

1
2
3
4
5
6
7
sadd myset “hello”
sadd myset “kuangshen”
 
获取所有元素 SMembers
SMembers myset
->“hello”
->“kuangshen

 

1
2
3
4
5
6
7
8
9
10
判断是否包含某个元素  Sismeber myset hello
 
获取set集合的元素个数  scard myset
 
移除某一个元素 Srem myset hello
 
<br>随机抽出一个元素 Srandmember myset
 
 
Srandmember myset 2 随机抽取指定个数的元素

 

随机移除set集合中的元素

1
2
3
4
5
6
7
eg:SMembers myset
->Kuangshen2
->Kuangshen
->Hello
 
Spop myset ->kuanghen2
Spop myset —>kuangshen

  

Smove 将一个指定的key 移动到另一个set 

1
2
3
4
5
6
7
Sadd myset “hello”
 
Sadd myset “world”
 
Sadd myset2 “set2”
 
Smove myset myset2 “hello

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Sadd key1 a
Sadd key1 b
Sadd key1 c
 
Sadd key2 c
Sadd key2 d
Sadd key2 e
 
 
Sdiff key1 key2 //两个set集合的差集
-> “b”
-> “a”
key1中key2没有的
 
Sinter key1 key2//两个set集合的交集
->“c”
 
SUnion key1 key2//并集

Hash(哈希)

Map 集合 key -map

本质和string 没有什么太大区别

所有的hash命令都是h开头的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
set myhash field1 kuangshen
Hget myhash field1
->“Kuangshen”
//值会被覆盖
Hmset myhash field1 hello field2 world
Hmget myhash field1 field2
->“hello”
->”world”
Hgetall myhash
->“hello”
->”world”
hdel myhash field1 //删除hash指定key值
Hlen myhash //获取hash的长度
Exists myhash field1//判定hash中指定的key是否存在
Hkeys myhash//获取所有的key值
Hvals myhash//获取所有的value值
Hset myhash field3 5
 
Hincrby myhash field3 1//给指定key值增加指定步长
 
Hsetnx //如果存在不能设置 不存在就能设置
 
Hash 变更的数据 user name age 更适合对象的存储
Hset user:1 name “zhangsan”
Hset user:1 age “18

应用:集合之间的操作。交集。并集  差集

 

Zset 有序集合

set的基础上,增加了一个值  

set k1 v1 

Set k1 score1 v1

 

1
2
3
4
5
6
eg:Zadd myset 1 one
Zadd myset 2 two 3 three
Zrange myset 0 -1
->”one”
 “two”
 “three"

 

 

1
2
3
4
5
6
7
8
9
10
11
12
排序
 
 
Zadd salary 2500 xiaohong
Zadd salary 5000 zhangsan
Zadd salary 500 kuangshen
 
 
ZRANGEBYSCORE salary -inf +inf
->“kuangshen”
-> “xiaohong”
-> “zhangsan"

 

 

1
2
3
4
5
6
7
ZRANGEBYSCORE salary -inf +inf with scores
->”kuangshen”
->“500
->“xiaohong”
->“2500
->“zhangsan”
->“5000

排序(上限)

ZRANGEBYSCORE salary -inf 2500
->“kuangshen”
->“xiaohong

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Zrevrange salary 0 -1 (从大到小排序)
-》“zhangsan”
-》“Kuangshen”
 
移除元素 zrem
Zrem salary xiaohong
 
查看元素个数
Zcard salary
 
Zconut 获取指定区间的成员数量
Zadd myset 1 hello 2 world 3 kuangshen
Zconut myset 1 3
-> 3
 
 
Zcont myset 1 2 获取指定区间的成员数量
-〉2  

 

小结:set 排序 存储班级成绩表 工资表排序

普通消息 1,重要消息 2.带权重进行判断

 应用:排行榜应用实现

三种特殊数据类型

 1.Geospatial 地理位置

Redis  geo  可以推算地理位置的信息 两地之间的距离 方圆几里的人

Geoadd key 值(纬度 经度 名称)

规则:两极无法直接添加 一般会下载城市数据 直接java程序导入

Geoadd china:city 116.40 39.90 beijing

Geoadd china:city 121.47 31.23 Shanghai

Geoadd china:city 106.50 29.53 Chongqing 114.05 22.52 Shenzhen

有效的经度 -180 180

有效的纬度 -85 85

 

获取指定城市的经度和纬度

 

Geopos china:city beijing
->”116.3999..”
->“39.90000

 

Geodist 获取指定两个位置的距离(直线距离) 

Geodist china:city beijing Shanghai km

 

我附近的人?(获得所有附近的人的地址 定位)

Georadius 以给定的经纬度为中心 找出某一半径内的元素

1
2
Georadius China:city 110 30 500 km
->”chongqin”

 

Georadius China:city 110 30 500 km withdist 直线距离

Georadius China:city 110 30 500 km withcoord 经纬度

 

获得指定数量的人

Georadius China:city 110 30 500 km count 1

 

北京周围的城市

Georadiusbymember china:city beijing 100 km

 

Geohash 返回一个或者多个位置元素的geohash表示

将返回11个字符串的geohash字符串(将二维的经纬度转换为一维的字符串)

 

Geo 底层的实现原理就是zset ,可以使用zset 命令来操作

 

 2.Hyperloglog PF开头的命令

什么是基数?不重复的元素

A135787

B13578

基数(不重复的元素)     网页的浏览量(一个人访问一个网站多次,但是还是算作一个人)

占用的内存是固定的,2^64 不同的元素,只需要占用128kB内存

0.81%错误率

eg:PFADD mykey a b c d e f g h I j
PFCOUNT mykey
->10

PFADD mykey2 i j z x c v b n m
PFCOUNT Mykey2
->9

PFMEGRE mykey3 mykey mykey2
PFCOUNT mykey3
->15

如果允许有一定错误率,就可以用hyperloglog

 3.Bitmaps

位存储 操作二进制来记录 

统计用户信息 两个状态的 都可以使用Bitmaps

周一到周六的打卡

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Setbit sign 0 1
Setbit sign 1 0
Setbit sign 2 0
Setbit sign 3 1
Setbit sign 4 1
Setbit sign 5 0
Setbit sign 6 0
 
Getbit sign 6
->0
 
 
Getbit sign 3
->1
 
bitcount sign //统计打卡的天数
->3 

 

Redis基本的事务操作

事务

一组命令的集合 一个事务中的所有命令都会被序列化 会按照顺序执行,

一次性 顺序性 排他性 执行一些列的命令

Redis事务没有隔离级别的概念 单条命令出错不会影响前面的任务 也不会影响后面的命令

Redis 单条命令

开启事务(multi

命令入队()

执行事务(exec)

Multi //开启事务

Set k1 v1

Set k2 v2

Get k2

Set k3 v3

exec //执行事务

->ok

->ok

->v2

->ok

每个事务执行完就没有了

放弃事务 discard 在事务执行之前可以放弃事务

 

编译型异常(代码有问题,命令有错)事务中所有的命令都不会被执行

运行型异常(如果事务队列中存在语法性错误,)执行事务的时候,其他命令会正常执行,错误命令抛出异常。

 

乐观锁

悲观锁:无论做什么都会加锁

乐观锁:更新数据的时候去判断一下,在此期间,是否有人修改过这个数据,

Mysql 获取version,更新的时候比较version

 

Redis监视测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Set money 100
 
Set out 0
 
Watch money 监视money对象
 
Muiti
 
Decrby money 20
 
Incrby out 20
 
Exec  执行命令
 
->80
 
->20

  

测试多线程修改值,使用watch可以当作redis的乐观锁操作

1
2
3
4
5
6
7
Watch money
Multi
Decrby money 10
Incrby out 10
 
Exec 执行之前,另外一个线程,修改了我们的值,这个时候,就会导致我们事务执行失败
->(nil)<br><br>

如何解决?释放锁,重新写事务

Unwatch

Watch money

  

Jedis

使用java操作Redis

Redis官方推荐的java连接开发工具,使用java操作redis中间件。

导入包

2.编码测试:

连接数据库

操作命令

断开连接

 

Jedis jedis = new Jedis("127.0.0.1",6379);

System.out.println(jedis.ping());

方法和之前的api完全相同

Jedis.close();//关闭连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
事务:
public static void main(String[] args) {
    //
    Jedis jedis = new Jedis("127.0.0.1",6379);
    System.out.println(jedis.ping());
    jedis.flushDB();
    JSONObject jsonObject = new JSONObject();
    jsonObject.put("hello","world");
    jsonObject.put("name","zhangsan");
    Transaction multi = jedis.multi();//开启事务
    String result = jsonObject.toJSONString();
    try {
        multi.set("user1",result);
        multi.set("user2",result);
        multi.exec();//执行事务
    }catch (Exception e){
        multi.discard();//销毁事务
        e.printStackTrace();
    }finally {
        System.out.println(jedis.get("user1"));
        System.out.println(jedis.get("user2"));
        jedis.close();
    }
}

   

springboot整合

Springboot2.x之后。原来使用的jedis 被替换成了letture

Jedis:采用的直连。 多线程操作的话,不安全,避免不安全就使用jedis pool连接池 

Lettuce:采用netty,异步,实例可以在多个线程中进行共享,不存在线程不安全的情况,可以减少线程数量。

 

导入包

配置连接

测试 

传输对象需要序列化

自定义的redisTemplate

 

Redis.conf详解

1.大小写不敏感

2.可以包含其他配置文件

 

Network

Bind 127.0.0.1 绑定ip

通用 general

Demonize yes #后台运行 默认是no

Loglevel notice #生产环境

Logfile 日志文件名

Database 16 #数据库的数量

Always-show-logo yes #是否显示logo

 

 

快照

持久化,在规定的时间内,执行了多少次操作,则会持久化到文件.rdb .aof

如果900秒至少有一个key 进行了修改,我们

Save 900 1

Save 300 10

Save 60 10000

Stop-writes-on-bgsave-error yes 持久化如果出错,是否继续工作

Rdbcompression yes 是否压缩rdb文件,会消耗一些cpu资源

Rdbchecksum yes 保存rdb文件的时候,进行错误的检查校验

Dir ./ rdb文件保存的目录

 

Requirepass 123456 #设置密码

Config set require pass 123456

Config get require pass

->noauth authentication required

Auth 123456

->ok

 

Append only 模式

Appendonly no 默认是不开启,默认是使用rdb方式持久化,大部分所有的情况下,rdb完全够用

Appendfsync everysec 每秒执行一次

 

Redis持久化

Redis是内存数据库,如果不将内存中的数据库保存到磁盘中,一旦服务器进程退出,数据库状态也会消失,所以redis提供了持久化功能

默认是rdb 缺点就是最后一次持久化的数据可能会丢失

保存的文件是dump.rdb

触发规则

1.save的规则满足的情况下,会自动触发rdb规则

2.Flushall命令,也会触发我们的rdb规则

3.退出redis,也会产生rdb文件

备份就会自动生成一个dump.rdb

 

如果恢复rdb文件的数据

只需要将rdb文件

放到我们的redis启动目录下,redis启动的时候会自动检查dump.rdb 恢复其中的数据

查看需要的位置

Config  get dir

->dir

->usr/local/bin如果在该目录下存在dump.rdb文件,启动就会自动回复其中的数据

优点:

适合大规模的数据恢复

如果你对数据的完整性要求不高

缺点:

需要一定时间间隔进程操作,redis意外宕机了,最后一次修改数据就没了

Fork进程的时候,会占用一定的内存空间

  

AOF

以日志的形式来记录每个写操作,将redis执行过的所有指令记录下来(读操作不记录),只许追加文件不许修改文件,redis启动之初就会读取该文件重新构建,

AOF 保存的是appendonlyaof

默认不开启 

重启redis就可以生效了

如果aof文件有错误,redis无法启动

redis修复aof工具 redis-check-aof

Redis-check-aof -fix appendonly.aof

文件正常 重启就可以恢复

 

优点:

1.每次修改都同步,文件的完整性会更好

2。每秒同步一次,可能会丢失一秒的数据

3.从不同步,效率最高的

缺点:

1.相对于数据文件来说,aof远远大于rdb,修复的速度也比rdb

2.aof运行效率也比rdb慢,所以redis默认配置是rdb

 

Rewite 重写

 

发布订阅

消息通信模式 发送者发送消息 订阅者接收消息

消息发送者

频道

消息订阅者

Subscribe 频道名 //订阅频道

Publish 频道名 消息//向某频道发布消息

Subscribe 频道名 //订阅多个频道

 

Eg:

订阅端:subscribe bilibili

->subscribe

->kuangshenshuo

->message

->bilibili

->hello,bilibili

发送端:publish bilibili hello,bilibili

 

Subscribe就是把订阅的人加入到该频道的发布链表中

使用场景:

实时消息系统

实时聊天(聊天室)

订阅 关注系统都是可以的

 

 

主从复制 读写分离 

Master -slave1

            -slave2

            -slave3

主机进行读,从机进行写。减缓服务器的压力。

数据的复制是单向的,只能由主节点到从节点。

主从复制的作用:

1。数据冗余

2。故障恢复

3。负载均衡  通过多个从节点分担负载

4,高可用 

 

 

单台redis的最大使用内存不应该超过20G

默认情况下,每台redis都是主节点

info replication 查看当前库的信息

->role:master 角色

connected_slaves:0  没有从机

 

复制3个配置文件,修改对应的信息

1。端口

2pid名字

3log文件名字

4dump.rdb名字

 

启动服务 redis-server kconfig/redis79.conf

连接        redis-cli -p 6379

一主二从

 

主从复制之复制原理

默认情况下,每台redis都是主节点

info replication //查看当前库的信息

->role:master //角色

 

在从机中配置

slaveof 127.0.0.1 6379//绑定对应的主机

 

配置了之后,从机的角色变了 主机中可以看到从机的配置信息

 

真实的主从配置  修改配置文件

replicaof 主机IP 端口号

 

主机可以写,从机不能写只能读 

主机中的所有信息和数据,都会自动被从机保存

 

测试:主机断开连接 从机依旧连接到主机的,这个时候,主机如果回来了,从机依旧可以直接获取到主机写的信息

如果使用命令行 配置的主从,重启之后,又会变成主机,只要变成从机,立马就会从主机中获取值

 

从机启动成功连接到主机后,会发送一条sync同步命令

master接到命令后,启动后台的存盘进同时收集所有接收到的用于修改数据集命令,

在后台命令执行完毕之后,master将传送整个数据文件到slave.完成一次完全同步

 

 全量复制  slave服务在接收到数据库文件数据后,将其存盘加载到内存中

 增量复制  master继续将新的所有收集到的修改命令依次传给slave,完成同步

但是只要重新连接master ,一次全量复制将会被自动执行。

 

层层链路

Master -slave1 master  -slave2

     79       80                     81

80为从节点,不能写 ,只能读

slaveof no one

如果主机断开了连接 使用slaveof no one 让自己成为主机

   

 

哨兵模式(自动选举老大的模式)sentinel

通过后台监控主机是否故障,如果故障了根据投票数自动将从库转为主库

原理:哨兵通过发送命令 等待redis服务器响应 从而监控运行的多个redis实例

多哨兵模式

主服务器不能用 并且达到一定数量,让各个哨兵爸自己监控的从服务器实现切换主机,为客观下线

 

配置哨兵配置文件 sentinel.conf

Sentinel montior 被监控的名称  host port 1

Sentinel montior myredis 127.0.0.1 6379 1

后面的数字 代表主机挂了 slave投票看让谁接替成为主机 投票最多的,就会成为主机

启动哨兵

Reidis-sentinel kconfig/sentinel.conf

如果主机节点断开了,就会从从机中随机选择一个服务器

哨兵日志

Failover 发现故障

Sdown 主机转移

如果主机此时回来。只能归并到新的主机下,当作从机

 

 

优点

1.哨兵集群,基于主从复值模式,所有的主从配置优点,它都有

2.主从可以切换,故障可以转移,系统的可用性好

3.哨兵模式就是主从模式的升级,手动到自动,更加健壮

缺点

Redis不好在线扩容,集群容量一旦到达上限,在线扩容十分麻烦

实现哨兵模式的配置其实是很麻烦的,里面有很多选择。

 

Redis缓存穿透与雪崩

缓存穿透

用户查询一个数据 redis内存数据库中没有,缓存没有命中,于是在持久层数据库查询

发现也没有,于是本次查询失败,当用户很多,缓存都没有命中(秒杀),于是都去请求了持久层数据库,这就会给持久层数据库造成很大压力,出现了缓存穿透。

 

布隆过滤器

对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,避免了对底层存储系统的压力

客户端 -bloomfilter  ->缓存层   -〉存储层

 

缓存空对象

没命中的空对象缓存起来,同时设置一个过期时间,之后再访问这个数据从缓存中获取

 

问题:

如果空值能够被缓存起来,这就意味着缓存需要更多空间,因为这当中可能有许多空值的键

即使对空值设置了一个过期时间,还是会存在缓存层和存储层的数据会有一段时间的不一致,这对于需要保持一致性的业务会有影响。

 

缓存击穿

是指一个key非常热门,在不停的扛着高并发,这个key在失效的瞬间,持续的大并发就会穿透缓存,直接请求数据库。

 

设置热点数据永不过期

加分布式锁

使用分布式锁,保证每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限

 

缓存雪崩

在某一个时间段,缓存集中过期失效,redis宕机

停掉一些服务

 

redis高可用

多增几台redis

限流降级

这个解决方案的思想是,缓存失效时通过加锁或者队列控制数据库写缓存的线程数量

数据预热

数据预热的含义就正式部署之前,先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中,在即将发生大并发访问前手动触发并加载缓存不同的key,设置不同的缓存过期时间,让缓存失效的时间尽可能均匀