Redis概述
Redis(Remote Dictionary Server),远程字典服务,做持久化的缓存(c语言编写,提供多种语言的开发api),也被人称为结构化数据库,官方测试数据,读110000 ,写80000 在redis中无论什么数据类型,在数据库中都是以key-value形式保存,通过进行对Redis-key的操作,来完成对数据库中数据的操作
Redis特点
Redis 是单线程的,Redis是基于内存操作,cpu不是Redis的性能瓶颈,Redis的瓶颈是根据机器的内存和网络带宽,将所有的数据全部放在内存中,所以说单线程的操作效率是最高的,多线程会进行上下文操作,对于内存系统来说,如果没有上下文切换就是最快的。redis默认有16个数据库,默认使用第一个,也就是0号数据库
- 内存存储,持久化(内存中断电即失,所以数据的持久化非常重要(rdb,aof))
- 效率高,可以用于缓存
- 发布订阅系统
- 计时器,计数器(浏览量)
五大数据类型(String List Set Hash Zset)三种特殊数据类型(geospatial(地理位置,例如附近的人等) hyperloglog(基数,不重复的元素 可以接受误差 ,可以计算网站访问量等) bitmaps(位存储 ,只有0和1两种状态,记录活动和不活动等,打卡不打卡等))这里不做特殊介绍
常用命令
命令 | 描述 |
select n | 切换数据库,默认0号数据库 |
flushdb | 清空当前数据库中的键值对 |
flushall | 清空所有数据库的键值对 |
keys * | 查看当前数据库中所有的key |
dbsize | 键总数 |
exists key | 是否存在key |
del key [key …] | 删除key |
expire key seconds | 设置键过期 |
persist key | 移除键过期时间,永久有效 |
ttl key | 获取键有效时长 |
randomkey | 随机返回数据库中一个键 |
rename key1 key2 | 重命名 |
renamex key1 key2 | 当key2不存在时,key1重命名 |
String类型
字符串类型是Redis最基础的数据结构,其它的几种数据结构都是在字符串类型基础上构建的,字符串的值可以是:字符串、数字、二进制,其值最大不能超过512M。
适用场景:缓存、计数器、流水号,对象存储缓存(共享session)
String 常用命令
String命令 | 描述 |
set key value | 设置一个key的value值 |
setex key seconds value | 设置key值的同时设置过期时间 |
setnx key value | 仅当key不存在时候设置key(分布式锁的基础命令) |
mset key value [key value …] | 设置多个key value |
msetnx key1 value1 [key2 value2…] | 批量设置键值对仅参数中所有的key都不存在时执行,原子性操作,一起成功,一起失败 |
get key | 返回key的value |
mget key [key …] | 批量获取多个key保存的值 |
decr/incr key | 将指定key的value数值进行-1/+1(仅对于数字) 取号 |
incrby/decrby key n | 对指定的key以指定的步长n增加减少 |
incrbyfloat key n | 为数值加上浮点型数值 |
append key value | 向指定的key的value后追加字符串 |
strlen key | 返回key的string类型value的长度 |
getset key value | 设置一个key的value,并获取设置前的值,如果不存在则返回null |
setrange key offset value | 设置指定位置的字符 |
getrange key start end | 获取存储在key上的值的一个子字符串 |
String命令演示
127.0.0.1:6379> set name shilei
127.0.0.1:6379> append name "1" #追加name里存储的值 如果key不存在的话就是直接set
(integer) 7
127.0.0.1:6379> get name
"shilei1"
127.0.0.1:6379> strlen name #字符串的长度
(integer) 7
127.0.0.1:6379> set num 0
OK
127.0.0.1:6379> incr num #自动递增(可以做浏览量)
(integer) 1
127.0.0.1:6379> decr num #自动递减
(integer) 0
127.0.0.1:6379> incrby num 2 #增加设置步长 decrby 一样
(integer) 2
###############字符串截取替换
127.0.0.1:6379> getrange name 0 3 #截取字段串[0,3]
"shil"
127.0.0.1:6379> getrange name 0 -1 #获取全部的字符串
"shilei1"
127.0.0.1:6379> setrange name 1 XX #替换指定位置字符串
(integer) 7
127.0.0.1:6379> get name
"aXXdefg"
127.0.0.1:6379>
############### 如果存在和批量设置
127.0.0.1:6379> setex aaa 30 hello #设置过期时间 存在的话
OK
127.0.0.1:6379> setnx aaa db #如果不存在在设置
(integer) 0
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 #批量设置值,空格分开
OK
127.0.0.1:6379> mget k1 k2 k3 #批量获取值
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> msetnx k1 v1 k4 v4 #msetnx是一个原子性操作,要么一起成功,要么一起失败
(integer) 0
###############对象设置
127.0.0.1:6379> set user {name:zhangsan,age:20}
OK
127.0.0.1:6379> get user
"{name:zhangsan,age:20}"
###############getset如果不存在,在设置,如果存在的话会设置新的值
127.0.0.1:6379> getset db redis #先获取在设置
(nil)
127.0.0.1:6379> get db
"redis"
List类型
基本的数据类型,列表(栈和队列),在redis里边可以变成栈和队列,Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边),也可以获取指定范围指定下标的元素等。一个列表最多可以包含 232 - 1 个元素 (4294967295, 每个列表超过40亿个元素)。
- 列表中的元素是有序的,可以通过索引下标获取某个元素霍某个某个范围内的元素列表
- 列表中的元素可以是重复的
List常用命令
list命令 | 描述 |
lpush/rpush key value1[value2…] | 从左边/右边向列表中PUSH值(一个或者多个) |
lpushx/rpushx key value | 向已存在的列名中push值(一个或者多个) |
linsert key before|after pivot value | 在指定列表元素的前/后 插入value |
lindex key index | 通过索引获取列表元素 |
lrange key start end | 获取list 起止元素 (索引从左往右 递增) |
llen key | 查看列表长度 |
lpop/rpop key | 从最左边/最右边移除值 并返回 |
lrem key count value | 从头部开始搜索 然后删除指定的value 至多删除count个 count < 0:从尾部开始搜索… count = 0:删除列表中所有的指定value。 |
ltrim key start end | 通过下标截取指定范围内的列表 |
rpoplpush source destination | 将列表的尾部(右)最后一个值弹出,并返回,然后加到另一个列表的头部 |
lset key index value | 通过索引为元素设值 |
blpop/brpop key1[key2] timout | 移出并获取列表的第一个/最后一个元素,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
brpoplpush source destination timeout | 和rpoplpush功能相同,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
List命令演示
127.0.0.1:6379> lpush list one #将一个值或者多个值插入到列表的头部(从左边放进去)
127.0.0.1:6379> rpush list three #从右边放进去
(integer) 1
127.0.0.1:6379> lpush list two
(integer) 2
127.0.0.1:6379> lrange list 0 -1 #遍历值
1) "two"
2) "one"
127.0.0.1:6379>
#############移除元素
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
3) "three"
127.0.0.1:6379> lpop list #从列表的左边移除
"two"
127.0.0.1:6379> rpop list #从列表的右边移除
"three"
127.0.0.1:6379> lrem list 1 one #移除一个叫one的值
(integer) 1
##############通过下标获取值
127.0.0.1:6379> lindex list 0
"one"
##############长度
127.0.0.1:6379> llen list #返回列表的长度
(integer) 3
##############修剪ltrim
127.0.0.1:6379> ltrim list 1 2 #截断列表 通过下标截取长度
OK
##############rpoplpush移除列表最后一个元素并添加到新的列表上
127.0.0.1:6379> rpoplpush list otherlist
"hello"
127.0.0.1:6379> lset list 0 item #如果列表存在我们会更新值,不存在报错
OK
127.0.0.1:6379> lrange list 0 -1
1) "item"
2) "hello1"
#############插入指定的值
127.0.0.1:6379> linsert list before item other #将other插入列表中某个值的前面或者后面 after
(integer) 3
Set类型
Redis的Set是string类型的无序集合,我们不能通过索引获取元素。集合成员是唯一的,这就意味着集合中不能出现重复的数据。Redis中集合是通过哈希表实现的
应用场景:共同关注,共同爱好,二度好友,推荐好友(六度分割理论)
Set常用命令
set命令 | 描述 |
sadd key member1[member2…] | 向集合中无序增加一个/多个成员 |
srem key member1[member2…] | 移除集合中一个/多个成员 |
scard key | 获取集合的成员数 |
smembers key | 返回集合中所有的成员 |
sismember key membe | 查询member元素是否是集合的成员,存在返回1,不存在返回0 |
srandmember key [count] | 随机返回集合中count个成员,count缺省值为1 |
spop key [count] | 随机移除并返回集合中count个成员,count缺省值为1 |
sinter key1 [key2…] | 返回所有集合的交集(共同好友等) |
sinterstore destination key1[key2…] | 在SINTER的基础上,存储结果到集合中。覆盖 |
sunion key1 [key2…] | 返回所有集合的并集 |
sunionstore destination key1 [key2…] | 在SUNION的基础上,存储结果到集合中。覆盖 |
sdiff key1[key2…] | 返回所有集合的差集 key1- key2 - … |
sdiffstore destination key1[key2…] | 在SDIFF的基础上,将结果保存到集合中。覆盖 |
smove source destination member | 将source集合的成员member移动到destination集合 |
sscan key [MATCH pattern] [COUNT count] | 在大量数据环境下,使用此命令遍历集合中元素,每次遍历部分 |
Set命令演示
127.0.0.1:6379> sadd myset hello #添加一个元素
(integer) 1
127.0.0.1:6379> sadd myset shilei
(integer) 1
127.0.0.1:6379> smembers myset #查看元素
1) "shilei"
2) "hello"
127.0.0.1:6379> scard myset #获取set集合中内容元素的个数
(integer) 2
127.0.0.1:6379> srem myset hello #移除某一个元素
(integer) 1
127.0.0.1:6379> spop myset #随机移除一个元素
"hello"
127.0.0.1:6379> srandmember myset #随机抽出一个元素
"shilei"
127.0.0.1:6379> srandmember myset 2 #随机抽出指定的个数
1) "shilei"
127.0.0.1:6379> smove myset myset1 shilei #将集合的元素shilei移动到myset1
(integer) 1
###############集合操作
127.0.0.1:6379> sadd set a
(integer) 1
127.0.0.1:6379> sadd set b
(integer) 1
127.0.0.1:6379> sadd set c
(integer) 1
127.0.0.1:6379> sadd set1 c
(integer) 1
127.0.0.1:6379> sadd set1 d
(integer) 1
127.0.0.1:6379> sadd set1 e
(integer) 1
127.0.0.1:6379> sdiff set set1 #两个集合的差集(去掉共同的)
1) "b"
2) "a"
127.0.0.1:6379> sinter set set1 #两个集合的交集(共同好友)
1) "c"
127.0.0.1:6379> sunion set set1 #两个集合的并集
1) "b"
2) "c"
3) "a"
4) "d"
5) "e"
Hash类型
几乎所有的编程语言都提供了哈希(hash)结构,Redis中 hash 是一个string类型的field和value的映射表value={{field1,value1},{field2,value2}…},可以将一个Hash表作为一个对象进行存储,表中存放对象的信息。
应用场景: 用户信息缓存,hash变更的数据 尤其是用户的信息之类的,经常变动的信息,hash更适合对象的存储,string偏向于字符创
Hash类型命令
hash命令 | 描述 |
hset key field value | 将哈希表 key 中的字段 field 的值设为 value。重复设置同一个field会覆盖,返回0 |
hmset key field1 value1 [field2 value2…] | 同时将多个 field-value (域-值)对设置到哈希表 key 中 |
hsetnx key field value | 只有在字段 field不存在时,设置哈希表字段的值 |
hget key field value | 获取存储在哈希表中指定字段的值 |
hmget key field1 [field2…] | 获取所有给定字段的值 |
hexists key field | 查看哈希表 key 中,指定的字段是否存在 |
hdel key field1 [field2…] | 删除哈希表key中一个/多个field字段 |
hlen key | 获取哈希表中字段的数量 |
hkeys key | 获取所有字段field |
hvals key | 获取哈希表中所有值value |
hgetall key | 获取在哈希表key 的所有字段和值 |
hincrby key field n | 为哈希表 key 中的指定字段的整数值加上增量n |
hincrbyfloat key field n | 为哈希表 key 中的指定字段的浮点数值加上增量 n |
hscan key cursor [MATCH pattern] [COUNT count] | 迭代哈希表中的键值对 |
Hash命令演示
127.0.0.1:6379> hset myhash file shilei #存值
(integer) 1
127.0.0.1:6379> hget myhash file #取值
"shilei"
127.0.0.1:6379>
127.0.0.1:6379> hmset myhash filed shilei filed1 shilei1 #设置多个值
OK
127.0.0.1:6379> hmget myhash filed filed1 #取出多个值
1) "shilei"
2) "shilei1"
127.0.0.1:6379> hgetall myhash #获取所有值
1) "file"
2) "shilei"
3) "filed"
4) "shilei"
5) "actor"
6) "shilei1"
127.0.0.1:6379> hdel myhash file #删除指定的值
(integer) 1
127.0.0.1:6379> hlen myhash #获取字段的长度
(integer) 2
127.0.0.1:6379> hexists myhash shilei #判断hash的指定字段是否存在
(integer) 0
127.0.0.1:6379> hkeys myhash #获取所有的key
1) "filed"
2) "actor"
127.0.0.1:6379> hvals myhash #获取所有的值
1) "shilei"
2) "shilei1"
127.0.0.1:6379> hincrby myhash ab 1 #自动增加
(integer) 1
hsetnx myhash name hello #如果不存在创建
Zset类型
在有序集合中保留了不能有重复成员的特性,但其中的成员是可以排序的,每一个元素都会关联一个double类型的分数(score)作为排序依据,score相同时按字典顺序排序。redis正是通过分数来为集合中的成员进行从小到大的排序
应用场景: 排行榜系统,成绩单,工资表
Zset类型命令
zset命令 | 描述 |
zadd key score member1 [score2 member2] | 向有序集合添加一个或多个成员,或者更新已存在成员的分数 |
zcard key | 获取有序集合的成员数 |
zscore key member | 返回有序集中,成员的分数值 |
zcount key min max | 计算在有序集合中指定区间score的成员数 |
zlexcount key min max | 在有序集合中计算指定字典区间内成员数量 |
zincrby key n member | 有序集合中对指定成员的分数加上增量 n |
zscan key cursor [MATCH pattern] [COUNT count] | 迭代有序集合中的元素(包括元素成员和元素分值) |
zrank key member | 返回有序集合中指定成员的索引 |
zrevrank key member | 返回有序集合中指定成员的索引,从大到小排序 |
zrange key start end | 通过索引区间返回有序集合成指定区间内的成员 |
zrevrange key start end | 通过索引区间返回有序集合成指定区间内的成员,分数从高到底 |
zrangebylex key min max | 通过字典区间返回有序集合的成员 |
zrevrangebylex key max min | 按字典顺序倒序返回有序集合的成员 |
zrangebyscore key min max | 返回有序集中指定分数区间内的成员 -inf 和 +inf分别表示最小最大值,只支持开区间 |
zrevrangebyscore key max min | 返回有序集中指定分数区间内的成员,分数从高到低排序 |
zrem key member1 [member2…] | 移除有序集合中一个/多个成员 |
zremrangebylex key min max | 移除有序集合中给定的字典区间的所有成员 |
zremrangebyrank key start stop | 移除有序集合中给定的排名区间的所有成员 |
zremrangebyscore key min max | 移除有序集合中给定的分数区间的所有成员 |
zinterstore destination numkeyskey1 [key2 …] | 计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 key 中,numkeys:表示参与运算的集合数,将score相加作为结果的score |
zunionstore destination numkeys key1 [key2…] | 计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 key 中 |
Zset命令演示
127.0.0.1:6379> zadd myset 1 one #添加值,指定一个标识,方便排序
(integer) 1
127.0.0.1:6379> zadd myset 2 two
(integer) 1
127.0.0.1:6379> zadd myset 3 three
(integer) 1
127.0.0.1:6379> zrange myset 0 -1 #遍历
1) "one"
2) "two"
3) "three"
#############实现排序
127.0.0.1:6379> zadd salary 2500 xiaohong
(integer) 1
127.0.0.1:6379> zadd salary 5000 zhangsan
(integer) 1
127.0.0.1:6379> zadd salary 500 shilei
(integer) 1
127.0.0.1:6379> zrangebyscore salary -inf +inf #从小到大排序,从负无穷到正无穷
1) "shilei"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> zrangebyscore salary -inf +inf withscores #排序时带上钱
1) "shilei"
2) "500"
3) "xiaohong"
4) "2500"
5) "zhangsan"
6) "5000"
127.0.0.1:6379> zrangebyscore salary -inf 2500 withscores #工资小于等于2500的员工排序
1) "shilei"
2) "500"
3) "xiaohong"
4) "2500"
127.0.0.1:6379> zrevrange salary 0 -1 #从大到小排序
1) "zhangsan"
2) "shilei"
127.0.0.1:6379> zrem salary xiaohong #移除有序集合中指定的元素
(integer) 1
127.0.0.1:6379> zcount salary salary 1 2' #获取区间的成员数量
Invalid argument(s)
Redis事务
redis的事务中,一次执行多条命令,本质是一组命令的集合,一个事务中所有的命令将被序列化,即按顺序执行而不会被其他命令插入
- MULTI:使用该命令,标记一个事务块的开始,通常在执行之后会回复OK,(但不一定真的OK),这个时候用户可以输入多个操作来代替逐条操作,redis会将这些操作放入队列中。
- EXEC:执行这个事务内的所有命令
- DISCARD:放弃事务,即该事务内的所有命令都将取消
- WATCH:监控一个或者多个key,如果这些key在提交事务(EXEC)之前被其他用户修改过,那么事务将执行失败,需要重新获取最新数据重头操作(类似于乐观锁)。
- UNWATCH:取消WATCH命令对多有key的监控,所有监控锁将会被取消。
redis事务是一个队列,放到队列中不会被执行,只有发起执行的时候才会被执行,如果执行之前,redis检测到语法错误,则不会执行命令,如果,在运行之前没有检测出错误,则只会失败一条,其他条成功
#开启事务
127.0.0.1:6379> multi
OK
#命令入队
127.0.0.1:6379> set name shilei
QUEUED
127.0.0.1:6379> set age 20
QUEUED
127.0.0.1:6379> get name
QUEUED
127.0.0.1:6379> get age
QUEUED
#执行事务
127.0.0.1:6379> exec
1) OK
2) OK
3) "shilei"
4) "20"
#放弃事务discard
127.0.0.1:6379> discard
OK
###命令有错,事务中所有命令都不会执行,如果事务中存在与发行,可以执行。
1. 单独的隔离操作:事务中的所有命令会被序列化、按顺序执行,在执行的过程中不会被其他客户端发送来的命令打断
2. 没有隔离级别的概念:队列中的命令在事务没有被提交之前不会被实际执行
3. 不保证原子性:redis中的一个事务中如果存在命令执行失败,那么其他命令依然会被执行,没有回滚机制
Redis持久化
Redis是内存数据库,内存断电即失,所以要将数据持久化保存到磁盘。 redis持久化的意义,在于数据备份和故障恢复。比如你部署了一个redis,作为cache缓存,当然也可以保存一些较为重要的数据。如果没有持久化的话,redis遇到灾难性故障的时候,就会丢失所有的数据。如果通过持久化将数据持久化一份儿在磁盘上去,然后定期比如说同步和备份到一些云存储服务上去,那么就可以保证数据不丢失全部,还是可以恢复一部分数据回来的。redis中的持久化分为 RDB持久化和AOF持久化,
在生产中,一般采用两种方式结合的方式混合模式redis4.0版本以上做持久化方案
RDB持久化机制
RDB全称Redis Database Backup file (Redis数据备份文件),也被叫做Redis数据快照。简单来说就是把内存中的所有数据都记录到磁盘中。是一个压缩二进制文件,当Redis实例故障重启后,默认从磁盘读取快照文件,恢复数据
RDB执行机制
RDB在停机时执行一次RDB
或者手动执行RDB
- save 主线程执行,阻塞其他命令
- bgsave 开启子线程执行,避免主线程受到影响
RDB持久化配置
redis.conf文件,也就是/etc/redis/6379.conf,去配置持久化
- save 60 1000:每隔60s,如果有超过1000个key发生了变更,那么就生成一个新的,save可以设置多个
- dbfilename dump.rdb :配置文件名
- dump.rdb文件,就是当前redis内存中完整的数据快照文件名,重启之后redis自动恢复数据
- dir ./ 文件保存的路径目录
- rdbcompression yes 是否压缩,建议不开启,压缩也会消耗cpu,磁盘的话不值钱
RDB持久化机制的工作流程
bgsave开始时会fork主进程得到子进程,子进程共享主进程的内存数据。完成fork后读取内存数据并写入 RDB文件fork采用的是copy-on-write技术
- 当主进程执行读操作时,访问共享内存
- 当主进程执行写操作时,则会拷贝一份数据,执行写操作
dump.rdb始终只会有一个。因为每次生成一个新的快照,都会覆盖之前的老快照
AOF持久化
就是追加文件,将我们的所有命令都记录下来,history,恢复的时候就把这个文件全部在执行一遍,恢复速度慢
以日志的形式来记录每个写操作,将Redis执行过的所有指令记录下来(读操作不记录),只许追加文件 但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件 的内容将写指令从前到后执行一次以完成数据的恢复工作
- AOF默认关闭 appendonly yes 开启
- 配置AOF文件名 appendfilename"appendonlv.aof"
- 执行频率:appendfsync everysec 写命令执行完先放入AOF缓冲区,由操作系统决定何时将缓存
AOF持久化步骤以及文件同步策略
AOF 持久化的实现主要是以上三步:命令追加、文件写入、文件同步
命令追加: 将 redis 写操作命令追加到 aof_buf 缓冲区
文件写入: 周期性地将 aof_buf 缓冲区的命令写入 AOF 文件的内核缓冲区。
文件同步:根据配置同步策略,将 AOF 文件缓冲区的内容同步到磁盘文件同步策略是将文件刷新进磁盘,redis有以下配置
always:每次有命令写入时都立即同步。这提供了最高的数据安全性,但效率最低。
everysec:每秒同步一次。这是一个权衡安全性和效率的策略。最多只丢失 1 秒 的数据
no:让操作系统决定最佳的同步时间。这可能导致数据丢失,但提供了最高的效率
AOF重写
随着追加命令的不断增加,这个 AOF 文件可能会变得很大和冗长。面对这样的问题,Redis 提供了一个非常有效的优化手段,那就是 AOF 重写
AOF 重写,可以看作是对 AOF文件 进行的一次“精简”操作。它的目的是减少AOF文件的大小,并去除那些冗余的、不再必要的命令,使得该文件只包含恢复当前数据集所需的最小命令集
RDB与AOF的区别
RDB文件是间隔保存,可能会造成数据丢失,但丢失量不大,AOF是无限追加文件,恢复数据比较慢,会从头到尾执行一遍所有写操作,AOF运行效率也要比RDB要慢很多
RDB+AOF混合模式
当RDB,AOF都存在时,redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文供保存的数据集要完整。RDB的数据不实时,同时使用两者时服务器重启也只会找AOF文件
开启混合模式:在redis.conf中redis默认开启
缺点:AOF 文件中添加了 RDB 格式的内容,使得 AOF 文件可读性变的更差,支持4.0以上版本
详细了解:redis持久化详解
Redis 缓存穿透缓存击穿缓存雪崩
缓存穿透
在某一时刻,用户查询了一个根本查不到的id,这个id缓存中没有,于是查询数据库,但是数据库中也没有,当用户很多的时候,缓存都没有命中(秒 杀),于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力 ,这就是缓存穿透
解决方案:
1、布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则 丢弃,从而避免了对底层存储系统的查询压力
2、缓存空对象,当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数 据将会从缓存中获取,保护了后端数据源;
缓存击穿
缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中 对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一 个屏障上凿开了一个洞。 当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访 问数据库来查询新数据,并且回写缓存,会导使数据库瞬间压力过大。
解决方案:
1、设置热点数据永不过期 ,从缓存层面来看,没有设置过期时间,所以不会出现热点 key 过期后产生的问题。
2、加互斥锁 ,分布式锁:使用分布式锁,保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布 式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考 验很大。setnx命令
缓存雪崩
缓存雪崩,是指在某一个时间段,缓存集中过期失效。Redis 宕机!比较致命的缓存雪崩,是缓存服务器某个节点宕机或断网。因为自然 形成的缓存雪崩,一定是在某个时间段集中创建缓存,这个时候,数据库也是可以顶住压力的。无非就 是对数据库产生周期性的压力而已。而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知 的,很有可能瞬间就把数据库压垮。
解决方案:
1、部署集群,异地多活
2、限流降级,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对 某个key只允许一个线程查询数据和写缓存,其他线程等待。
3、数据预热 ,数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数 据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让 缓存失效的时间点尽量均匀
SpringBoot 整合Redis
1、导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2、添加配置
spring.redis.host = 127.0.0.1
spring.redis.port = 6379
spring.redis.password = 123456
3、整合template 存储数据
package com.Bivin;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
@SpringBootTest
class Springboot09RedisApplicationTests {
/**
* 第三步:自动装配RedisTemplate对象
*/
@Autowired
private RedisTemplate redisTemplate;
/**
* 首先我们知道Redis是一款key,value存储结构的数据库
*
* 因此我们假定这个set()测试方法以key,value的形式往Redis数据库中存储数据
*
* key,value形式的存储命令:set
* key,value形式的取数据命令:get
*
*/
@Test
void set() {
ValueOperations ops = redisTemplate.opsForValue(); // 首先redisTemplate.opsForValue的目的就是表明是以key,value形式储存到Redis数据库中数据的
ops.set("address1","zhengzhou");// 到这里就表明Redis数据库中存储了key为address1,value为zhengzhou的数据了(取的时候通过key取数据)
}
/**
* 取数据
*/
@Test
void get() {
ValueOperations ops = redisTemplate.opsForValue(); // 表明取的是key,value型的数据
Object o = ops.get("address1"); // 获取Redis数据库中key为address1对应的value数据
System.out.println(o);
}
}
上述存储,可以在redis客户端 取出address1的值为 zhengzhou,但是数据没有真正存储到redis数据库中,因此我们需要把第三步中的自动装配的RedisTemplate对象换成StringRedisTemplate对象即可储存成功了。(因为Redis数据库都是字符串形式的数据,所以用StringRedisTemplate对象)
4、整合工具Jedis
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.3.0</version>
</dependency>
需要改写配置 spring.redis.client-name = jedis //默认是lettuce
在springboot 2.x之后,原来使用过的jedis被改为了lettuce
jedis:底层直连,如果多个线程操作的话,是不安全的,如果想要安全使用jredis连接池,更像BIO模式
lettuce:底层采用netty,实例可以再多个线程中共享,不存在线程不安全的情况,可以减少线程数据,更像NIO模式
5、redis存储对象的方式
1、.将对象序列化后保存到Redis 序列化字节数组存储,取出数据反序列化
2、将对象用FastJSON转为JSON字符串后存储
3、将对象用Hash数据类型存储
详情见:redis存储对象三种方式