Redis学习
引言
本博客是根据黑马程序员Redis入门到精通教学视频学习时,所做的笔记
1. Redis入门
1.1 Redis简介
高性能键值对(key-value)数据库
1.2 Redis下载与安装
1.2.1 windows
下载链接:Tags · microsoftarchive/redis (github.com)
解压即可
1.2.2 linux
- 安装
1 |
|
- 服务端启动
1 |
|
- 客户端连接
1 |
|
1.3 Redis常用指令
1 |
|
示例
2. Redis数据类型
- Redis 自身是一个 Map,其中所有的数据都是采用 key : value 的形式存储
- 数据类型指的是存储的数据的类型,也就是 value 部分的类型,key 部分永远都是字符串
2.1 String
- 存储的数据:单个数据,最简单的数据存储类型,也是最常用的数据存储类型
- 存储数据的格式:一个存储空间保存一个数据
- 存储内容:通常使用字符串,如果字符串以整数的形式展示,可以作为数字操作使用
2.1.1 基本操作
添加修改
1
set key value
获取数据
1
get key
删除数据
1
del key
添加/修改多个数据
1
mset key1 value1 key2 value2 key3 value3....
获取多个数据
1
mget key1 key2 key3...
获取数据字符个数
1
2strlen key
//返回的是字符个数追加信息到原始信息后部(如果存在就追加,否则新建)
1
2append key value
//返回的是追加后的字符个数
2.1.2 扩展操作
1 |
|
- string在redis内部存储默认就是一个字符串,当遇到增减类操作incr,decr时会转成数值型进行计算。
- redis所有的操作都是原子性的,采用单线程处理所有业务,命令是一个一个执行的,因此无需考虑并发带来的数据影响。
- 注意:按数值进行操作的数据,如果原始数据不能转成数值,或超越了redis 数值上限范围,将报错。 9223372036854775807(java中long型数据最大值,Long.MAX_VALUE)
tips:
- redis用于控制数据库表主键id,为数据库表主键提供生成策略,保障数据库表的主键唯一性
- 此方案适用于所有数据库,且支持数据库集群
设置数据具有指定的生命周期
1 |
|
tips
- redis 控制数据的生命周期,通过数据是否失效控制业务行为,适用于所有具有时效性限定控制的操作
2.1.3 命名规范
例如
1 |
|
2.2 Hash
- 新的存储需求:对一系列存储的数据进行编组,方便管理,典型应用存储对象信息
- 需要的存储结构:一个存储空间保存多个键值对数据
- hash类型:底层使用哈希表结构实现数据存储
hash存储结构优化
- 如果field数量较少,存储结构优化为类数组结构
- 如果field数量较多,存储结构使用HashMap结构
2.2.1 基本操作
添加/修改数据
1
2hset key field value //插入(如果已存在同名的field,会被覆盖)
hsetnx key field value //插入(如果已存在同名的field,不会被覆盖)获取数据
1
2hget key field //获取指定field的数据
hgetall key //获取指定key下的所有数据删除数据
1
hdel key field1 [field2]
添加/修改多个数据
1
hmset key field1 value1 field2 value2 ...
获取多个数据
1
hmget key field1 field2 ...
获取哈希表中字段的数量
1
hlen key
获取哈希表中是否存在指定的字段
1
hexists key field
2.2.2 操作注意事项
- hash类型下的value只能存储字符串,不允许存储其他数据类型,不存在嵌套现象。如果数据未获取到, 对应的值为(nil)
- 每个 hash 可以存储 2^32 - 1 个键值
- hash类型十分贴近对象的数据存储形式,并且可以灵活添加删除对象属性。但hash设计初衷不是为了存储大量对象而设计的,切记不可滥用,更不可以将hash作为对象列表使用
- hgetall 操作可以获取全部属性,如果内部field过多,遍历整体数据效率就很会低,有可能成为数据访问瓶颈
2.2.3 购物车场景的设计
- 每个用户的购物车中的商品记录保存成两条field
- field专用于保存购买数量
- 命名格式:商品id:nums
- 保存数据:数值
- field2专用于保存购物车中商品的信息hash的id
2.3 List
- 数据存储需求:存储多个数据,并对数据进入存储空间的顺序进行区分
- 需要的存储结构:一个存储空间保存多个数据,且通过数据可以体现进入顺序
- list类型:保存多个数据,底层使用双向链表存储结构实现
- 元素有序,且可重
2.3.1 基本操作
添加/修改数据
1
2
3//lpush为从左边添加,rpush为从右边添加
lpush key value1 value2 value3...
rpush key value1 value2 value3...获取并移除数据
1
2lpop key //从左边移除
rpop key //从右边移除获取数据
1
2
3
4
5
6
7
8//获取 从 start 到 stop 之间的数据,如果不知道list有多少个元素,stop的值可以为-1,代表倒数第一个元素
lrange key start stop
//获取 索引为 index 的数据 即类似a[0]
lindex key index
//获取list长度
llen key
2.3.2 扩展操作
1 |
|
1 |
|
2.3.2 操作注意事项
- list中保存的数据都是string类型的,数据总容量是有限的,最多2^32 - 1 个元素 (4294967295)。
- list具有索引的概念,但是操作数据时通常以队列的形式进行入队出队(rpush, rpop)操作,或以栈的形式进行入栈出栈(lpush, lpop)操作
- 获取全部数据操作结束索引设置为-1 (倒数第一个元素)
- list可以对数据进行分页操作,通常第一页的信息来自于list,第2页及更多的信息通过数据库的形式加载
2.4 Set
- 新的存储需求:存储大量的数据,在查询方面提供更高的效率
- 需要的存储结构:能够保存大量的数据,高效的内部存储机制,便于查询
- set类型:与hash存储结构完全相同,仅存储键,不存储值(nil),并且值是不允许重复的
- 不重复且无序
2.4.1 基本操作
添加元素
1
sadd key member1 member2...
查看元素
1
smembers key
移除元素
1
srem key member
查看元素个数
1
scard key
查看某个元素是否存在
1
sismember key member
2.4.2 扩展操作
求两个集合的交、并、差集
1
2
3
4
5
6
7
8//交
sinter key1 [key2]
//并
sunion key1 [key2]
//差 key1 - key2 有顺序的
sdiff key1 [key2]求两个集合的交、并、差集并存储到指定集合中
1
2
3
4
5
6
7
8
9
10//交
sinterstore destination key1 key2...
//并
sunionstore destination key1 key2...
//差
sdiffstore destination key1 key2...
/**同上就不演示了**/将指定数据从原始集合中移动到目标集合中
1
2//从 source 中 移到 destination里 移动的值为member
smove source destination member随机获取集合中指定数量的数据
1
2//获取个数为 count
srandmember key [count]随机获取集合中的某个数据并将该数据移出集合
1
spop key [count]
2.4.3 注意事项
- set 类型不允许数据重复,如果添加的数据在 set 中已经存在,将只保留一份
- set 虽然与hash的存储结构相同,但是无法启用hash中存储值的空间
2.4.4 简单权限设置实现
- 权限1:查询所有、根据id查询
- 权限2:查询所有、根据id删除
- 用户001:具有权限1、2
- 用并集实现
2.5 sorted_set
- 不重但有序(score)
- 新的存储需求:数据排序有利于数据的有效展示,需要提供一种可以根据自身特征进行排序的方式
- 需要的存储结构:新的存储模型,可以保存可排序的数据
- sorted_set类型:在set的存储结构基础上添加可排序字段
2.5.1 基本操作
插入元素,需要指定 score 用于排序
1
zadd key score1 member1 [score2 member2]
获取全部数据
1
2
3
4//正序 从 start 到 stop 当末尾添加withscore时,会将元素的score一起打印出来
zrange key start stop [WITHSCORES]
//反序
zrevrange key start stop [WITHSCORES]删除数据
1
zrem key member [member ...]
按条件获取数据
1
2
3
4
5//正序 查询 key 中 score在[min , max]之间的数据 其中 limit 是从offset为索引开始位置,count为获取的数目
zrangebyscore key min max [WITHSCORES] [LIMIT offset count]
//反序
zrevrangebyscore key max min [WITHSCORES] [LIMIT offset count]按条件删除数据
1
2zremrangebyrank key start stop
zremrangebyscore key min max获取集合数据总量
1
2
3
4
5//集合总个数
zcard key
//score 在min 到 max 范围的个数
zcount key min max集合交、并操作
1
2
3
4
5//交
zinterstore destination numkeys key [key ...]
//并
zunionstore destination numkeys key [key ...]
注意
- min与max用于限定搜索查询的条件
- start与stop用于限定查询范围,作用于索引,表示开始和结束索引
- offset与count用于限定查询范围,作用于查询结果,表示开始位置和数据总量
2.5.2 扩展操作
获取数据对应的索引(排名)
1
2
3
4// 注意是排名 不是具体的 socre
//比如说张三的score是100,李四是60
zrank key member //正序 张三返回的是 0
zrevrank key member//反序 张三返回的是1score值获取与修改
1
2
3
4//获取score
zscore key member
//修改
zincrby key increment member
2.5.3 注意事项
- score保存的数据存储空间是64位,如果是整数范围是-9007199254740992~9007199254740992
- score保存的数据也可以是一个双精度的double值,基于双精度浮点数的特征,可能会丢失精度,使用时候要慎重
- sorted_set 底层存储还是基于set结构的,因此数据不能重复,如果重复添加相同的数据,score值将被反复覆盖,保留最后一次修改的结果 (修改的返回值是0 )
2.6 数据类型实践案例
- redis用于控制数据库表主键id,为数据库表主键提供生成策略,保障数据库表的主键唯一性
- string类型,自增时默认转换成数值型计算,单线程,保证唯一性
- redis 控制数据的生命周期,通过数据是否失效控制业务行为,适用于所有具有时效性限定控制的操作
- redis的时效性
- redis应用于各种结构型和非结构型高热度数据访问加速
- user:id:3506728370:fans
- redis 应用于购物车数据存储设计
- hash
- redis 应用于抢购,限购类、限量发放优惠卷、激活码等业务的数据存储设计
- hash
- redis 应用于具有操作先后顺序的数据控制
- list
- redis 应用于最新消息展示
- list
- redis 应用于随机推荐类信息检索,例如热点歌单推荐,热点新闻推荐,热卖旅游线路,应用APP推荐,大V推荐等
- set的随机获取集合指定数量的数据指令
- redis 应用于同类信息的关联搜索,二度关联搜索,深度关联搜索
- set的交并差等指令
- redis 应用于同类型不重复数据的合并、取交集操作
- set的交并差等指令
- redis 应用于同类型数据的快速去重
- set是无重复的集合
- redis 应用于基于黑名单与白名单设定的服务控制
- set
- redis 应用于计数器组合排序功能对应的排名
- sorted_set
- redis 应用于定时任务执行顺序管理或任务过期管理
- sorted_set
- redis 应用于及时任务/消息队列执行管理
- sorted_set 可设置用户优先级
- redis 应用于按次结算的服务控制
- string 利用incr操作超过最大值抛出异常的形式替代每次判断是否大于最大值
- redis 应用于基于时间顺序的数据操作,而不关注具体时间
- list的双端进出特征
3. 通用指令
3.1 key通用指令
3.1.1 基本操作
查看key是否存在
1
exists key
删除key
1
del key
查看key‘的类型
1
type key
3.1.2 扩展操作(时效性操作)
设置生命周期
1
2
3
4//秒
expire key seconds
//毫秒
pexpire key milliseconds查看有效时间, 如果有有效时间则返回剩余有效时间, 如果为永久有效,则返回-1, 如果Key不存在则返回-2
1
2
3
4//秒
ttl key
//毫秒
pttl key将有效的数据设置为永久有效
1
persist key
3.1.3 扩展操作(查询操作)
1 |
|
3.1.4 其他操作
为key改名
1
2
3
4//重命名key,为了避免覆盖已有数据,尽量少去修改已有key的名字,如果要使用最好使用renamenx
rename key newKey //如果newkey已存在 则会覆盖
renamenx key newKey//如果newkey已存在 则不会覆盖其他key通用操作
1
help @generic
3.2 数据库通用指令
- redis为每个服务提供有16个数据库,编号从0到15
- 每个数据库之间的数据相互独立
3.2.1 db基本操作
切换数据库
1
select index
其他操作
1
2
3quit //退出
ping //测试连通性
echo message //输出一个message数据移动
1
2//把当前数据库的 key 移到 db号 数据库中
move key db数据清除
1
2
3dbsize //查询当前数据库有多少个key
flushdb //清除当前数据库的数据
flushall //清除所有数据库的所有数据
4. Jedis
- Java用来连接Redis服务的工具
- 需要导入对应jar包或者Maven依赖
1 |
|
4.1 操作步骤
连接Redis
1
2//参数为Redis所在的ip地址和端口号
Jedis jedis = new Jedis(String host, int port)操作Redis(操作redis的指令和redis本身的指令一致)
1
2jedis.set(String key, String value);
jedis.hset(String key, String field, String value);断开连接
1
jedis.close();
1 |
|
4.2 Jedis工具类制作
配置文件jedis.properties
1
2
3
4jedis.host=127.0.0.1
jedis.port=6379
jedis.maxTotal=30
jedis.maxIdle=10静态代码块初始化资源,放在静态代码块的话,初始化就只会执行一次,不会创建多个Jedis连接
1
2
3
4
5
6
7
8
9
10
11
12static{
//读取配置文件 获得参数值
ResourceBundle rb = ResourceBundle.getBundle("jedis");
host = rb.getString("jedis.host");
port = Integer.parseInt(rb.getString("jedis.port"));
maxTotal = Integer.parseInt(rb.getString("jedis.maxTotal"));
maxIdle = Integer.parseInt(rb.getString("jedis.maxIdle"));
poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(maxTotal);
poolConfig.setMaxIdle(maxIdle);
jedisPool = new JedisPool(poolConfig,host,port);
}获取连接;对外访问接口,提供jedis连接对象,连接从连接池获取
1
2
3
4
5//调用getJedis即可获得连接对象
public static Jedis getJedis(){
Jedis jedis = jedisPool.getResource();
return jedis;
}
ps:这里是很好的单例模式,可以参考本站另外一篇博客:jvm学习 - GuTaicheng’s Blog
1 |
|
1 |
|
5. 持久化
5.1 持久化简介
5.1.1 什么是持久化?
利用永久性存储介质(如硬盘)将数据进行保存,在特定的时间将保存的数据进行恢复的工作机制称为持久化。
5.1.2 为什么要持久化
防止数据的意外丢失,确保数据安全性
5.1.3 持久化过程保存什么
- 将当前数据状态进行保存,快照形式,存储数据结果,存储格式简单,关注点在数据
- 将数据的操作过程进行保存,日志形式,存储操作过程,存储格式复杂,关注点在数据的操作过程
5.2 RDB
5.2.1 启动方式
5.2.1.1 save指令
命令
1
save
作用:手动执行一次保存操作
save指令相关配置
- dbfilename dump.rdb
- 说明:设置本地数据库文件名,默认值为 dump.rdb
- 经验:通常设置为dump-端口号.rdb
- dir
- 说明:设置存储.rdb文件的路径
- 经验:通常设置成存储空间较大的目录中,目录名称data
- rdbcompression yes
- 说明:设置存储至本地数据库时是否压缩数据,默认为 yes,采用 LZF 压缩
- 经验:通常默认为开启状态,如果设置为no,可以节省 CPU 运行时间,但会使存储的文件变大(巨大)
- rdbchecksum yes
- 说明:设置是否进行RDB文件格式校验,该校验过程在写文件和读文件过程均进行
- 经验:通常默认为开启状态,如果设置为no,可以节约读写性过程约10%时间消耗,但是存储一定的数据损坏风险
1 |
|
save指令工作原理
save指令的执行会阻塞当前Redis服务器,直到当前RDB过程完成为止,有可能会造成长时间阻塞,线上环境不建议使用
5.2.1.2 bgsave指令
命令
1
bgsave
作用
手动启动后台保存操作,但不是立即执行
bgsave指令相关配置
- 其余和save一样
- stop-writes-on-bgsave-error yes
- 说明:后台存储过程中如果出现错误现象,是否停止保存操作
- 经验:通常默认为开启状态
bgsave指令工作原理
5.2.1.3 save配置(自动保存)
配置
1
save second changes
作用
满足限定时间范围内key的变化数量达到指定数量即进行持久化
参数
- second:监控时间范围
- changes:监控key的变化量
配置位置
在conf文件中进行配置
1 |
|
save配置原理
注意:
- save配置要根据实际业务情况进行设置,频度过高或过低都会出现性能问题,结果可能是灾难性的
- save配置中对于second与changes设置通常具有互补对应关系(一个大一个小),尽量不要设置成包含性关系
- save配置启动后执行的是bgsave操作
5.2.2 三种启动方式对比
RBF优缺点
- 优点
- RDB是一个紧凑压缩的二进制文件,存储效率较高
- RDB内部存储的是redis在某个时间点的数据快照,非常适合用于数据备份,全量复制等场景
- RDB恢复数据的速度要比AOF快很多
- 应用:服务器中每X小时执行bgsave备份,并将RDB文件拷贝到远程机器中,用于灾难恢复
- 缺点
- RDB方式无论是执行指令还是利用配置,无法做到实时持久化,具有较大的可能性丢失数据
- bgsave指令每次运行要执行fork操作创建子进程,要牺牲掉一些性能
- Redis的众多版本中未进行RDB文件格式的版本统一,有可能出现各版本服务之间数据格式无法兼容现象
5.3 AOF
5.3.1 AOF简介
- AOF(append only file)持久化:以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中命令,以达到恢复数据的目的。与RDB相比可以简单描述为改记录数据为记录数据产生的过程
- AOF的主要作用是解决了数据持久化的实时性,目前已经是Redis持久化的主流方式
5.3.2 AOF写数据过程
5.3.3 AOF写数据三种策略(appendfsync)
always
- 每次写入操作均同步到AOF文件中,数据零误差,性能较低,不建议使用
everysec
- 每秒将缓冲区中的指令同步到AOF文件中,数据准确性较高,性能较高 ,建议使用,也是默认配置
- 在系统突然宕机的情况下丢失1秒内的数据
no
- 由操作系统控制每次同步到AOF文件的周期,整体过程不可控
配置
1
appendonly yes|no
- 作用
- 是否开启AOF持久化功能,默认为不开启状态
配置
1
appendfsync always|everysec|no
- 作用
- AOF写数据策略
- 作用
配置
1
appendfilename filename
- 作用
- AOF持久化文件名,默认文件名未appendonly.aof,建议配置为appendonly-端口号.aof
- 作用
例:
1 |
|
可以简单阅读懂aof
5.3.4 AOF重写
随着命令不断写入AOF,文件会越来越大,为了解决这个问题,Redis引入了AOF重写机制压缩文件体积。AOF文件重
写是将Redis进程内的数据转化为写命令同步到新AOF文件的过程。简单说就是将对同一个数据的若干个条命令执行结
果转化成最终结果数据对应的指令进行记录。
比如上例的:实际上的name是 ”CCC“ 但是在AOF中保存的三次 set 在恢复时也会执行三次,大大的降低了效率
5.3.4.1 AOF重写作用
- 降低磁盘占用量,提高磁盘利用率
- 提高持久化效率,降低持久化写时间,提高IO性能
- 降低数据恢复用时,提高数据恢复效率
5.3.4.2 AOF重写规则
进程内已超时的数据不再写入文件
忽略无效指令重写时使用进程内数据直接生成,这样新的AOF文件
只保留最终数据的写入命令
- 如del key1、 hdel key2、srem key3、set key4 111、set key4 222等
对同一数据的多条写命令合并为一条命令
- 如lpush list1 a、lpush list1 b、 lpush list1 c 可以转化为:lpush list1 a b c
- 为防止数据量过大造成客户端缓冲区溢出,对list、set、hash、zset等类型,每条指令最多写入64个元素
5.3.4.3 AOF重写方式
手动重写
1 |
|
简单演示
初始设置
重写前
重写后
bgrewriteaof指令工作原理
自动重写
1 |
|
自动重写触发比对参数( 运行指令info Persistence获取具体信息 )
1
2
3
4//当前.aof的文件大小
aof_current_size
//基础文件大小
aof_base_size自动重写触发条件
5.3.4.4 AOF重写工作原理
重写之前的AOF流程
基于everysec开启重写后,会有一个重写缓冲区,提示信息是控制台上提示的重写在后台已开始……
- 是由aof重写缓冲区来提供数据进行重写
5.4 RDB VS AOF
5.4.1 RDB与AOF的选择之惑
对数据非常敏感建议使用默认的AOF持久化方案
- AOF持久化策略使用everysecond,每秒钟fsync一次。该策略redis仍可以保持很好的处理性能,当出现问题时,最多丢失0-1秒内的数据。
注意:由于AOF文件存储体积较大,且恢复速度较慢
数据呈现阶段有效性建议使用RDB持久化方案
- 数据可以良好的做到阶段内无丢失(该阶段是开发者或运维人员手工维护的),且恢复速度较快,阶段 点数据恢复通常采用RDB方案
注意:利用RDB实现紧凑的数据持久化会使Redis降的很低
综合比对
- RDB与AOF的选择实际上是在做一种权衡,每种都有利有弊
- 如不能承受数分钟以内的数据丢失,对业务数据非常敏感,选用AOF
- 如能承受数分钟以内的数据丢失,且追求大数据集的恢复速度,选用RDB
- 灾难恢复选用RDB
- 双保险策略,同时开启 RDB 和 AOF,重启后,Redis优先使用 AOF 来恢复数据,降低丢失数据
6. 事务
6.1 事务简介
redis事务就是一个命令执行的队列,将一系列预定义命令包装成一个整体(一个队列)。当执行时,一次性
按照添加顺序依次执行,中间不会被打断或者干扰。
一个队列中,一次性、顺序性、排他性的执行一系列命令
比如说同时有两个线程,线程一设置了一个string=a,当线程一要读取这个key时,线程二修改了这个值,则导致线程一读取到和预想值不同的值(类似mysql的读脏数据),因此需要有事务;
6.2 事务的基本操作
开启事务
1
multi
- 作用
- 作设定事务的开启位置,此指令执行后,后续的所有指令均加入到事务中
- 作用
取消事务
1
discard
- 作用
- 终止当前事务的定义,发生在multi之后,exec之前
- 作用
执行事务
1
exec
- 作用
- 设定事务的结束位置,同时执行事务。与multi成对出现,成对使用
- 作用
注意:加入事务的命令暂时进入到任务队列中,并没有立即执行,只有执行exec命令才开始执行
6.3 事务的工作流程
6.4 事务的注意事项
定义事务的过程中,命令格式输入错误怎么办?
- 语法错误
- 指命令书写格式有误 例如执行了一条不存在的指令
- 处理结果
- 如果定义的事务中所包含的命令存在语法错误,整体事务中所有命令均不会执行。包括那些语法正确的命令
定义事务的过程中,命令执行出现错误怎么办?
- 运行错误
- 指命令格式正确,但是无法正确的执行。例如对list进行incr操作
- 处理结果
- 能够正确运行的命令会执行,运行错误的命令不会被执行
注意:已经执行完毕的命令对应的数据不会自动回滚,需要程序员自己在代码中实现回滚。
- Redis 命令只会因为错误的语法而失败,或是命令用在了错误类型的键上面,这些问题不能在入队时发现,这也就是说,从实用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中.
- 因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。
6.5 基于特定条件的事务执行
6.5.1 锁
对 key 添加监视锁,在执行exec前如果key发生了变化,终止事务执行
1
watch key1, key2....
取消对所有key的监视
1
unwatch
事务内部 不能执行watch和unwatch,这两个命令在事务外部才有效
6.5.2 分布式锁
分布式锁其实是一个设计概念,也可以说是一个约定,不是redis自带的,原理是利用 setnx的不重性来约定实现的,就是个string类型
使用 setnx 设置一个公共锁
1
2
3
4//上锁
setnx lock-key value
//释放锁
del lock-key- 利用setnx命令的返回值特征,有值(被上锁了)则返回设置失败,无值(没被上锁)则返回设置成功
- 操作完毕通过del操作释放锁
这个 lock-key 就是自定义的一个约定规范,一般是lock-(要锁的key值)
实际上就是判断 lock-(要锁的key值) 是否存在,存在返回为0,后台接收到后就不执行,不存在就返回为1,后台接收到继续执行
6.5.3 分布式锁加强
使用 expire 为锁key添加时间限定,到时如果不释放,放弃锁
1
2expire lock-key seconds
pexpire lock-key milliseconds由于操作通常都是微秒或毫秒级,因此该锁定时间不宜设置过大。具体时间需要业务测试后确认。
- 例如:持有锁的操作最长执行时间127ms,最短执行时间7ms。
- 测试百万次最长执行时间对应命令的最大耗时,测试百万次网络延迟平均耗时
- 锁时间设定推荐:最大耗时120%+平均网络延迟110%
- 如果业务最大耗时<<网络平均延迟,通常为2个数量级,取其中单个耗时较长即可
7. 删除策略
7.1 过期数据
- Redis是一种内存级数据库,所有数据均存放在内存中,内存中的数据可以通过TTL指令获取其状态
- XX :具有时效性的数据
- -1 :永久有效的数据
- -2 :已经过期的数据 或 被删除的数据 或 未定义的数据
7.2 数据删除策略
- 定时删除
- 惰性删除
- 定期删除
存储结构
redis中有两个字典,一个是键值对字典,一个是过期字典,保存键的过期时间
目标
在内存占用与CPU占用之间寻找一种平衡,顾此失彼都会造成整体redis性能的下降,甚至引发服务器宕机或
内存泄露
7.2.1 定时删除
- 在设置某个key 的过期时间同时,创建一个定时器,当key设置有过期时间,且过期时间到达时,由定时器任务立即执行对键的删除操作
- 优点:节约内存,到时就删除,快速释放掉不必要的内存占用
- 缺点:CPU压力很大,无论CPU此时负载量多高,均占用CPU,会影响redis服务器响应时间和指令吞吐量
- 总结:用处理器性能换取存储空间 (拿时间换空间)
7.2.2 惰性删除
- 数据到达过期时间,不做处理。等下次访问该数据时
- 如果未过期,返回数据
- 发现已过期,删除,返回不存在
- 优点:节约CPU性能,发现必须删除的时候才删除
- 缺点:内存压力很大,出现长期占用内存的数据
- 总结:用存储空间换取处理器性能 (拿空间换时间)
7.2.3 定期删除
周期性轮询redis库中的时效性数据,采用随机抽取的策略,利用过期数据占比的方式控制删除频度
特点1:CPU性能占用设置有峰值,检测频度可自定义设置
特点2:内存压力不是很大,长期占用内存的冷数据会被持续清理
优点:可以通过限制删除操作执行的时长和频率来减少删除操作对 CPU 的影响。另外定期删除,也能有效释放过期键占用的内存。
缺点:难以确定删除操作执行的时长和频率。如果执行的太频繁,定期删除策略变得和定时删除策略一样,对CPU不友好。如果执行的太少,那又和惰性删除一样了,过期键占用的内存不会及时得到释放。另外最重要的是,在获取某个键时,如果某个键的过期时间已经到了,但是还没执行定期删除,那么就会返回这个键的值,这是业务不能忍受的错误。
总结:周期性抽查存储空间 (随机抽查,重点抽查)
- 如果删除的key大于 W的四分之一,说明是重点区域,current_db不变,下次继续该区域
- 反之,current_db + 1
7.2.4 删除策略对比
7.3 逐出算法(淘汰策略)
如果现在所有的数据都是永久的,那新数据进来就内存不足了,这个适合就要有逐出算法(淘汰策略)
- Redis使用内存存储数据,在执行每一个命令前,会调用freeMemoryIfNeeded()检测内存是否充足。如果内存不满足新加入数据的最低存储要求,redis要临时删除一些数据为当前指令清理存储空间。清理数据的策略称为逐出算法
- 注意:逐出数据的过程不是100%能够清理出足够的可使用的内存空间,如果不成功则反复执行。当对所有数据尝试完毕后,如果不能达到内存清理的要求,将出现错误信息。
7.3.1 影响数据逐出的相关配置
最大可使用内存
1
maxmemory
占用物理内存的比例,默认值为0,表示不限制。生产环境中根据需求设定,通常设置在50%以上。
每次选取待删除数据的个数
1
maxmemory-samples
选取数据时并不会全库扫描,导致严重的性能消耗,降低读写性能。因此采用随机获取数据的方式作为待检测删除数据
删除策略
1
maxmemory-policy
达到最大内存后的,对被挑选出来的数据进行删除的策略
7.3.2 逐出算法的删除策略
- 检测易失数据(可能会过期的数据集server.db[i].expires)
- volatile-lru:挑选最近最少使用的数据淘汰,就是很久没用的
- volatile-lfu:挑选最近使用次数最少的数据淘汰,就是用的很少的
- volatile-ttl:挑选将要过期的数据淘汰
- volatile-random:任意选择数据淘汰
- 检测全库数据(所有数据集server.db[i].dict )
- allkeys-lru:挑选最近最少使用的数据淘汰,就是很久没用的
- allkeys-lfu:挑选最近使用次数最少的数据淘汰,就是用的很少的
- allkeys-random:任意选择数据淘汰
- 放弃数据驱逐
- no-enviction(驱逐):禁止驱逐数据(redis4.0中默认策略),会引发错误OOM(Out Of Memory)
LRU:最近使用 Least Recently Used
LFU(Least Frequently Used)算法,也就是最频繁被访问的数据将来最有可能被访问到
内存淘汰策略可以通过配置文件来修改,Redis.conf对应的配置项是maxmemory-policy 修改对应的值就行,默认是noeviction。
1 |
|
8. redis.conf配置
参杂在每个章节,需要哪个功能配置哪个
1 |
|
9. 高级数据类型
9.1 Bitmaps
对于一个只有是或者不是的状态,可以采用0/1表示;比如:男生用1表示,女士用0表示;是为党员用1表示,不是党员用0表示等等
因此一个bit大小的数据,就可以保存8个状态,极大的节约了空间。
实际上还是string,只不过操作的是二进制上某一位的状态,比如:有100个员工打卡,第99位来打卡时,把第99位的0改位1。
9.1.1 基础操作
获取指定key对应偏移量上的bit值
1
getbit key offset
设置指定key对应偏移量上的bit值,value只能是1或0
1
setbit key offset value
9.1.2 扩展操作
对指定key按位进行交、并、非、异或操作,并将结果保存到destKey中
1
2bitop op destKey key1 [key2...]
//op如下- and:交
- or:并
- not:非
- xor:异或
统计指定key中1的数量
1
bitcount key [start end]
可以实现统计8月19号到8月30号中间没有打卡的人数.
从没打卡过:用 or
5 - 4 = 1,一个人从没打过
只要有一次没打卡:用 and
5 - 2 = 3,有三个人有一次没打
9.2 HyperLogLog
9.2.1 基数
- 基数是数据集去重后元素个数
- HyperLogLog 是用来做基数统计的,运用了LogLog的算法
9.2.2 基础操作
添加数据
1
pfadd key element1, element2...
统计数据
1
pfcount key1 key2....
合并数据(就是合并多个HyperLogLog到 destkey中)
1
pfmerge destkey sourcekey [sourcekey...]
9.2.3 注意事项
- 用于进行基数统计,不是集合,不保存数据,只记录数量而不是具体数据
- 核心是基数估算算法,最终数值存在一定误差
- 误差范围:基数估计的结果是一个带有 0.81% 标准错误的近似值
- 耗空间极小,每个hyperloglog key占用了12K的内存用于标记基数
- pfadd命令不是一次性分配12K内存使用,会随着基数的增加内存逐渐增大
- Pfmerge命令合并后占用的存储空间为12K,无论合并之前数据量多少
9.3 GEO
和位置有关,坐标等等
可应用于附近的人之类的功能
9.3.1 基础操作
添加坐标点
1
geoadd key longitude latitude member [longitude latitude member ...]
获取坐标点
1
geopos key member [member ...]
计算坐标点距离
1
geodist key member1 member2 [unit]
根据坐标求范围内的数据
1
georadius key longitude latitude radius m|km|ft|mi [withcoord] [withdist] [withhash] [count count]
根据点求范围内的数据
1
georadiusbymember key member radius m|km|ft|mi [withcoord] [withdist] [withhash] [count count]
获取指定点对于坐标的hash值
1
geohash key member [member ...]
10. 主从复制
10.1 简介
- 提供数据方:master
- 主服务器,主节点,主库
- 主客户端
- 接收数据的方:slave
- 从服务器,从节点,从库
- 从客户端
- 需要解决的问题
- 数据同步
- 核心工作
- master的数据复制到slave中
10.1.1 什么是主从复制
主从复制即将master中的数据即时、有效的复制到slave中
特征:一个master可以拥有多个slave,一个slave只对应一个master
职责:
- master:
- 写数据
- 执行写操作时,将出现变化的数据自动同步到slave
- 读数据(可忽略)
- slave:
- 读数据
- 写数据(禁止)
10.1.2 主从复制的作用
- 读写分离:master写、slave读,提高服务器的读写负载能力
- 负载均衡:基于主从结构,配合读写分离,由slave分担master负载,并根据需求的变化,改变slave的数量,通过多个从节点分担数据读取负载,大大提高Redis服务器并发量与数据吞吐量
- 故障恢复:当master出现问题时,由slave提供服务,实现快速的故障恢复
- 数据冗余:实现数据热备份,是持久化之外的一种数据冗余方式
- 高可用基石:基于主从复制,构建哨兵模式与集群,实现Redis的高可用方案
10.2 工作流程
- 主从复制过程大体可以分为3个阶段
- 建立连接阶段(即准备阶段)
- 数据同步阶段
- 命令传播阶段
10.2.1 阶段一:建立连接阶段
建立slave到master的连接,使master能够识别slave,并保存slave端口号
10.2.1.1 连接方式(slave连接master)
方式一:客户端发送命令
1
2
3slaveof <masterip> <masterport>
//例
slaveof 127.0.0.1 6379方式二:启动服务器参数
1
2
3redis-server -slaveof <masterip> <masterport>
//例
redis-server -slaveof 127.0.0.1 6379方式三:服务器配置 (常用)写在slave的conf文件中,然后直接启动即可
1
slaveof <masterip> <masterport>
输入info可查看系统信息
slave
master
10.2.1.2 主从断开连接
slave客户端发送命令
1
slaveof no one
- 说明: slave断开连接后,不会删除已有数据,只是不再接受master发送的数据
10.2.1.3 授权访问
master客户端发送命令设置密码
1
requirepass <password>
master配置文件设置密码
1
2config set requirepass <password>
config get requirepassslave客户端发送命令设置密码
1
auth <password>
slave配置文件设置密码
1
masterauth <password>
slave启动服务器设置密码
1
redis-server –a <password>
10.2.2 阶段二:数据同步阶段
10.2.2.1 同步流程(简)
全量复制
- 将master执行bgsave之前,master中所有的数据同步到slave中
部分复制(增量复制)
RDB是一个时刻的快照,在生成RDB文件和发送接收RDB文件的这段时间里,master也会在接收指令存储数据,这些指令会存放到复制缓冲区里(这里传输其实也会有指令进来,但是很少,因此做不到百分百)。
将master执行bgsave操作中,新加入的数据(复制缓冲区中的数据)传给slave,slave通过bgrewriteaof指令来恢复数据
部分复制
部分复制的三个核心要素
- 服务器的运行 id(run id)
- 主服务器的复制积压缓冲区
- 主从服务器的复制偏移量(offset)
服务器的运行 id(run id)
- 概念:服务器运行ID是每一台服务器每次运行的身份识别码,一台服务器多次运行可以生成多个运行id
- 一台服务器重启之后运行id就不同了
- 组成:运行id由40位字符组成,是一个随机的十六进制字符 例如- -
- fdc9ff13b9bbaab28db42b3d50f852bb5e3fcdce
- 作用:运行id被用于在服务器间进行传输,识别身份
- 如果想两次操作均对同一台服务器进行,必须每次操作携带对应的运行id,用于对方识别
- 实现方式:运行id在每台服务器启动时自动生成的,master在首次连接slave时,会将自己的运行ID发送给slave,slave保存此ID,通过info Server命令,可以查看节点的runid
复制缓冲区(复制积压缓冲区)
- 概念:复制缓冲区,又名复制积压缓冲区,是一个先进先出(FIFO)的队列,用于存储服务器执行过的命 令,每次传播命令,master都会将传播的命令记录下来,并存储在复制缓冲区
- 由来:每台服务器启动时,如果开启有AOF或被连接成为master节点,即创建复制缓冲区
- 作用:用于保存master收到的所有指令(仅影响数据变更的指令,例如set,select)
- 数据来源:当master接收到主客户端的指令时,除了将指令执行,会将该指令存储到缓冲区中
工作原理
主从服务器复制偏移量(offset)
- 概念:一个数字,描述复制缓冲区中的指令字节位置
- 分类:
- master复制偏移量:记录发送给所有slave的指令字节对应的位置(多个)
- slave复制偏移量:记录slave接收master发送过来的指令字节对应的位置(一个)
- 数据来源: master端:发送一次记录一次 slave端:接收一次记录一次
- 作用:同步信息,比对master与slave的差异,当slave断线后,恢复数据使用
10.2.2.2 同步流程(精)
10.2.2.3 注意事项
数据同步阶段master说明
如果master数据量巨大,数据同步阶段应避开流量高峰期,避免造成master阻塞,影响业务正常执行
复制缓冲区大小设定不合理,会导致数据溢出。如进行全量复制周期太长,进行部分复制时发现数据已经存在丢失的情况,必须进行第二次全量复制,致使slave陷入死循环状态。(比如用户操作高峰期)
1
2//设置缓冲区大小
repl-backlog-size 1mbmaster单机内存占用主机内存的比例不应过大,建议使用50%-70%的内存,留下30%-50%的内存用于执 行bgsave命令和创建复制缓冲区
数据同步阶段slave说明
为避免slave进行全量复制、部分复制时服务器响应阻塞或数据不同步,建议关闭此期间的对外服务
1
slave-serve-stale-data yes|no
数据同步阶段,master发送给slave信息可以理解master是slave的一个客户端,主动向slave发送命令
多个slave同时对master请求数据同步,master发送的RDB文件增多,会对带宽造成巨大冲击,如果master带宽不足,因此数据同步需要根据业务需求,适量错峰
slave过多时,建议调整拓扑结构,由一主多从结构变为树状结构,中间的节点既是master,也是 slave。注意使用树状结构时,由于层级深度,导致深度越高的slave与最顶层master间数据同步延迟较大,数据一致性变差,应谨慎选择
10.2.3 阶段三:命令传播阶段
当master数据库状态被修改后,导致主从服务器数据库状态不一致,此时需要让主从数据同步到一致的
状态,同步的动作称为命令传播master将接收到的数据变更命令发送给slave,slave接收命令后执行命令
10.2.3.1 心跳机制
- 进入命令传播阶段候,master与slave间需要进行信息交换,使用心跳机制进行维护,实现双方连接保持在线
- master心跳:
- 指令:PING
- 周期:由repl-ping-slave-period决定,默认10秒
- 作用:判断slave是否在线
- 查询:INFO replication 获取slave最后一次连接时间间隔,lag项维持在0或1视为正常(过大视为断线)
- slave心跳任务
- 指令:REPLCONF ACK {offset}
- 周期:1秒
- 作用1:汇报slave自己的复制偏移量,获取最新的数据变更指令
- 作用2:判断master是否在线
心跳阶段注意事项
当slave多数掉线,或延迟过高时,master为保障数据稳定性,将拒绝所有信息同步操作
1
2min-slaves-to-write 2
min-slaves-max-lag 10- slave数量少于2个,或者所有slave的延迟都大于等于10秒时,强制关闭master写功能,停止数据同步
slave数量由slave发送REPLCONF ACK命令做确认
slave延迟由slave发送REPLCONF ACK命令做确认
10.2.3.2 主从复制完整流程
10.3 主从复制常见问题
10.3.1 频繁的全量复制
10.3.2 频繁的网络中断
10.3.3 数据不一致
11. 哨兵(sentinel)
11.1 简介
主从模式下,当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这种方式并不推荐,实际生产中,我们优先考虑哨兵模式。这种模式下,master 宕机,哨兵会自动选举 master 并将其他的 slave 指向新的 master。
哨兵(sentinel) 是一个分布式系统,用于对主从结构中的每台服务器进行监控,当出现故障时通过投票机制选择一个哨兵,让这个哨兵去选择一个新的master并将所有slave连接到新的master。
11.2 哨兵的作用
- 监控
- 不断的检查master和slave是否正常运行。 master存活检测、master与slave运行情况检测
- 通知(提醒)
- 当被监控的服务器出现问题时,向其他(哨兵间,客户端)发送通知。
- 自动故障转移
- 断开master与slave连接,选取一个slave作为master,将其他slave连接到新的master,并告知客户端新的服务器地址
注意:
哨兵也是一台redis服务器,只是不提供数据服务 通常哨兵配置数量为单数(单数是为了在投票时减少票数相同的情况)
11.3 配置哨兵
配置一拖二的主从结构
配置三个哨兵(配置相同,端口不同)
- 参看sentinel.conf
启动哨兵
1
redis-sentinel sentinel端口号 .conf
11.4 哨兵工作原理
11.4.1 阶段一:监控阶段
- 用于同步各个节点的状态信息
- 获取各个sentinel的状态(是否在线)
- 获取master的状态
- master属性
- runid
- role:master
- 各个slave的详细信息
- master属性
- 获取所有slave的状态(根据master中的slave信息)
- slave属性
- runid
- role:slave
- master_host、master_port
- offset
- …
- slave属性
这个阶段,第一个sentinel去获取完信息之后保存好,第二个sentinel来获取时在master处有一个sentinels的信息,就相当于发现了“战友”,于是哨兵2和哨兵1之间就会有有一个共享信息的通道
11.4.2 阶段二:通知阶段
- 各个哨兵将得到的信息相互同步(信息对称)
11.4.3 阶段三:故障转移阶段
① 当有一个哨兵去监控master时,发现master宕机了。这个时候master的info中有一个flags配置会变成
1 |
|
意为主观下线;同时这个哨兵会去通知其他哨兵这个master宕机了;
② 其他哨兵收到后都去检测master,如果总体下来,认同master确实宕机了的哨兵数量大于哨兵总数量的一半(配置文件可设置),那么flags会变成
1 |
|
意为客观下线
③ 开启哨兵之间的投票环节,选举一个负责人进行故障转移工作
④ 具体选举新的master的条件
- 由推选出来的哨兵对当前的slave进行筛选,筛选条件有:
- 服务器列表中挑选备选master
- 在线的
- 响应块的
- 与原master断开时间短的
- 优先原则
- 优先级 :按照slave优先级进行排序,slave priority越低,优先级就越高
- offset :如果slave priority相同,那么看replica offset,哪个slave复制了越多的数据,offset越靠后,优先级就越高
- runid : 如果上面两个条件都相同,那么选择一个run id比较小的那个slave
⑤ 发送指令( sentinel )
向新的master发送slaveof no one(断开与原master的连接)
向其他slave发送slaveof 新masterIP端口(让其他slave与新的master相连)
11.5 为什么Redis哨兵集群只有2个节点无法正常工作?
哨兵集群必须部署2个以上节点。
如果两个哨兵实例,即两个Redis实例,一主一从的模式。
则Redis的配置quorum=1,表示一个哨兵认为master宕机即可认为master已宕机。
但是如果是机器1宕机了,那哨兵1和master都宕机了,虽然哨兵2知道master宕机了,但是这个时候,需要majority,也就是大多数哨兵都是运行的,2个哨兵的majority就是2(2的majority=2,3的majority=2,5的majority=3,4的majority=2),2个哨兵都运行着,就可以允许执行故障转移。
但此时哨兵1没了就只有1个哨兵了了,此时就没有majority来允许执行故障转移,所以故障转移不会执行。
12. 集群(Redis cluster)
12.1 简介
12.1.1 集群架构
- 集群就是使用网络将若干台计算机联通起来,并提供统一的管理方式,使其对外呈现单机的服务效果
12.1.2 集群作用
- 分散单台服务器的访问压力,实现负载均衡
- 分散单台服务器的存储压力,实现可扩展性
- 降低单台服务器宕机带来的业务灾难
12.2 Redis集群结构设计
12.2.1 数据存储设计
- 通过算法设计,计算出key应该保存的位置
- 将所有的存储空间计划切割成16384份,每台主机保存一部分 每份代表的是一个存储空间,不是一个key的保存空间
- 将key按照计算出的结果放到对应的存储空间
- 取模16384,有点像hash
12.2.2 实现数据分布
Redis cluster有固定的16384个hash slot(哈希槽),对每个key计算CRC16值,然后对16384取模,可以获取key对应的hash slot。
Redis cluster中每个master都会持有部分slot(槽),比如有3个master,那么可能每个master持有5000多个hash slot。
hash slot让node的增加和移除很简单,增加一个master,就将其他master的hash slot移动部分过去,减少一个master,就将它的hash slot移动到其他master上去。每次增加或减少master节点都是对16384取模,而不是根据master数量,这样原本在老的master上的数据不会因master的新增或减少而找不到。并且增加或减少master时Redis cluster移动hash slot的成本是非常低的。
12.2.3 集群内部节点通讯设计
- 各个数据库互相连通,保存各个库中槽的编号数据
- 一次命中,直接返回
- 一次未命中,告知具体的位置,key再直接去找对应的库保存数据
如果key在110中在A之间命中,如果是1120中,告知在B中,再去B中找
Redis cluster节点间采取gossip协议进行通信,所有节点都持有一份元数据(就是上面的数据编号),不同的节点如果出现了元数据的变更之后U不断地i将元数据发送给其他节点让其他节点进行数据变更。
节点互相之间不断通信,保持整个集群所有节点的数据是完整的。 主要交换故障信息、节点的增加和移除、hash slot信息等。
这种机制的好处在于,元数据的更新比较分散,不是集中在一个地方,更新请求会陆陆续续,打到所有节点上去更新,有一定的延时,降低了压力;
缺点,元数据更新有延时,可能导致集群的一些操作会有一些滞后。
13. 企业级解决方案
13.1 缓存预热
13.1.1 问题排查
- 请求数量较高
- 主从之间数据吞吐量较大,数据同步操作频度较高
13.1.2 解决方案
- 前置准备工作:
- 日常例行统计数据访问记录,统计访问频度较高的热点数据
- 利用LRU数据删除策略,构建数据留存队列 例如:storm与kafka配合
- 准备工作:
- 将统计结果中的数据分类,根据级别,redis优先加载级别较高的热点数据
- 利用分布式多服务器同时进行数据读取,提速数据加载过程
- 热点数据主从同时预热
- 实施:
- 使用脚本程序固定触发数据预热过程
- 如果条件允许,使用了CDN(内容分发网络),效果会更好
13.1.3 总结
缓存预热就是系统启动前,提前将相关的缓存数据直接加载到缓存系统。避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!
13.2 缓存雪崩
13.2.1 数据库服务器崩溃
- 系统平稳运行过程中,忽然数据库连接量激增
- 应用服务器无法及时处理请求
- 大量408,500错误页面出现
- 客户反复刷新页面获取数据
- 数据库崩溃
- 应用服务器崩溃
- 重启应用服务器无效
- Redis服务器崩溃
- Redis集群崩溃
- 重启数据库后再次被瞬间流量放倒
13.2.2 问题排查
- 在一个较短的时间内,缓存中较多的key集中过期
- 此周期内请求访问过期的数据,redis未命中,redis向数据库获取数据
- 数据库同时接收到大量的请求无法及时处理
- Redis大量请求被积压,开始出现超时现象
- 数据库流量激增,数据库崩溃
- 重启后仍然面对缓存中无数据可用
- Redis服务器资源被严重占用,Redis服务器崩溃
- Redis集群呈现崩塌,集群瓦解
- 应用服务器无法及时得到数据响应请求,来自客户端的请求数量越来越多,应用服务器崩溃
- 应用服务器,redis,数据库全部重启,效果不理想
13.2.3 问题分析
- 短时间范围内
- 大量key集中过期
13.2.4 解决方案(道)
- 更多的页面静态化处理
- 构建多级缓存架构 Nginx缓存+redis缓存+ehcache缓存
- 检测Mysql严重耗时业务进行优化 对数据库的瓶颈排查:例如超时查询、耗时较高事务等
- 灾难预警机制 监控redis服务器性能指标
- CPU占用、CPU使用率
- 内存容量
- 查询平均响应时间
- 线程数
- 限流、降级 短时间范围内牺牲一些客户体验,限制一部分请求访问,降低应用服务器压力,待业务低速运转后再逐步放开访问
13.2.5 解决方案(术)
- LRU与LFU切换
- 数据有效期策略调整
- 根据业务数据有效期进行分类错峰,A类90分钟,B类80分钟,C类70分钟
- 过期时间使用固定时间+随机值的形式,稀释集中到期的key的数量
- 超热数据使用永久key
- 定期维护(自动+人工) 对即将过期数据做访问量分析,确认是否延时,配合访问量统计,做热点数据的延时
- 加锁 慎用!
13.2.6总结
缓存雪崩就是瞬间过期数据量太大,导致对数据库服务器造成压力。如能够有效避免过期时间集中,可以有效解决雪崩现象的出现 (约40%),配合其他策略一起使用,并监控服务器的运行数据,根据运行记录做快速调整。
13.3 缓存击穿
13.3.1 数据库服务器崩溃
- 系统平稳运行过程中
- 数据库连接量瞬间激增
- Redis服务器无大量key过期
- Redis内存平稳,无波动
- Redis服务器CPU正常
- 数据库崩溃
13.3.2 问题排查
- Redis中某个key过期,该key访问量巨大
- 多个数据请求从服务器直接压到Redis后,均未命中
- Redis在短时间内发起了大量对数据库中同一数据的访问
13.3.3 问题分析
- 单个key高热数据
- key过期
13.3.4 解决方案(术)
预先设定
以电商为例,每个商家根据店铺等级,指定若干款主打商品,在购物节期间,加大此类信息key的过期时长
注意:购物节不仅仅指当天,以及后续若干天,访问峰值呈现逐渐降低的趋势
现场调整
- 监控访问量,对自然流量激增的数据延长过期时间或设置为永久性key
后台刷新数据
- 启动定时任务,高峰期来临之前,刷新数据有效期,确保不丢失
二级缓存
- 设置不同的失效时间,保障不会被同时淘汰就行
加锁 分布式锁,防止被击穿,但是要注意也是性能瓶颈,慎重!
13.3.5 总结
缓存击穿就是单个高热数据过期的瞬间,数据访问量较大,未命中redis后,发起了大量对同一数据的数据库问,导致对数据库服务器造成压力。应对策略应该在业务数据分析与预防方面进行,配合运行监控测试与即时调整策略,毕竟单个key的过期监控难度较高,配合雪崩处理策略即可
13.4 缓存穿透
缓存穿透是指用户请求的数据在缓存中不存在即没有命中,同时在数据库中也不存在,导致用户每次请求该数据都要去数据库中查询一遍。如果有恶意攻击者不断请求系统中不存在的数据,会导致短时间大量请求落在数据库上,造成数据库压力过大,甚至导致数据库承受不住而宕机崩溃。
缓存穿透的关键在于在Redis中查不到key值,它和缓存击穿的根本区别在于传进来的key在Redis中是不存在的。假如有黑客传进大量的不存在的key,那么大量的请求打在数据库上是很致命的问题,所以在日常开发中要对参数做好校验,一些非法的参数,不可能存在的key就直接返回错误提示。
解决方法:
- 将无效的key存放进Redis中:
当出现Redis查不到数据,数据库也查不到数据的情况,我们就把这个key保存到Redis中,设置value=”null”,并设置其过期时间极短,后面再出现查询这个key的请求的时候,直接返回null,就不需要再查询数据库了。但这种处理方式是有问题的,假如传进来的这个不存在的Key值每次都是随机的,那存进Redis也没有意义。
- 使用布隆过滤器:
如果布隆过滤器判定某个 key 不存在布隆过滤器中,那么就一定不存在,如果判定某个 key 存在,那么很大可能是存在(存在一定的误判率)。于是我们可以在缓存之前再加一个布隆过滤器,将数据库中的所有key都存储在布隆过滤器中,在查询Redis前先去布隆过滤器查询 key 是否存在,如果不存在就直接返回,不让其访问数据库,从而避免了对底层存储系统的查询压力。
如何选择:针对一些恶意攻击,攻击带过来的大量key是随机,那么我们采用第一种方案就会缓存大量不存在key的数据。那么这种方案就不合适了,我们可以先对使用布隆过滤器方案进行过滤掉这些key。所以,针对这种key异常多、请求重复率比较低的数据,优先使用第二种方案直接过滤掉。而对于空数据的key有限的,重复率比较高的,则可优先采用第一种方式进行缓存。