Redis概述
Redis介绍以及应用场景
Redis是使用C语言开发的数据库,它与传统的数据库的不同之处在于它的数据都放在内存中,也叫内存数据库,所以它的存储速度非常的快。因此redis被广泛用于应用缓存的方向
Redis的应用场景
-
在客户端和数据库之间做缓存使用,减轻数据库的压力
-
做数据库使用,临时存储一些数据,如字典表,常用信息等
-
解决分布式场景下的Session分离问题
-
分布式锁
-
任务队列(秒杀,抢红包等)
-
应用排行榜
-
签到
Redis基本操作
这里只对redis的常用操作做记录,详细的操作见:https://www.redis.com.cn/commands.html
**
**Redis的key的一般设计原则
-
用:分割,也可用-
-
用表名做key的前缀,比如: user:
-
第二段key用主键
-
第三段key用列名
String
常用命令:set、get、strlen、del、exists、decr、incr、setex等
单值缓存:set、get
多值缓存:MGET、MSET
分布式锁:setnx、del
返回1代表锁成功,返回0代表锁失败
防止程序出现意外导致死锁,可以对分布式锁设置过期时间:
set key true ex seceond nx (设置10秒的过期时间)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BpC0MKIh-1639066498714)(https://i.loli.net/2021/11/24/e7GKmJlY95dWfnv.png)]
设置key的生存时间:setex key seconds value
将key设置seconds的存活时间,值为value
如果key存在,则被覆盖
设置将key自增或自减:incr、decr
如果key没有值,则都将key初始化为1
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gwuMyuY3-1639066498717)(https://i.loli.net/2021/11/24/UkQVDE8Ri4r7Hsw.png)]
判断key是否存在:exists
Set
类似java中的hashSet,是一个无序不重复集合。可以实现取交集、并集、差集的操作。
常用命令:sadd,spop,smembers,sismember,scard,sinterstore,sunion等
缓存与查看:sadd、smembers
弹出数据:spop
查看集合中数据个数:scard
判断集合中是否存在某元素:sismember
取交集:sinter、sinterstore
将取出的交集放入新集合
取并集:sunion、sunionstore
Hash
hash类似于java中的hashMap,主不过redis中的hash做了更多的优化。hash非常适合存储对象类型的数据,可以通过set、get只对对象中的某个字段做一个操作
常用命令:hset、hmset、hexists、hget、hmget、hgetall、hkeys、hvals
注意:hash的过期时间只能作用在key上,而不可以设置在field上
单值操作:hset、hget
多值操作:hmset、hmget
查询某个key下全部值:hgetall
查询全部key或value:hkeys、hvals
List
redis中的list是双向链表,但是带来了额外的内存开销
常用命令:rpush、rpop、lpush、lpop、lrange、llen
左右插入:lpush、rpush
左右删除并获取:lpop、rpop
获取数据:lrange
lrange key 开始index 结束index
Zset(sortedset)
有序集合,和set相比多了一个score权重,可以根据权重排序
常用命令:zadd、zcard、zscore、zrange、zrevrange、zrem等
添加与查看:zadd、zrange
查看单个和个数:zscore、zcard
BitMap
bitmap存储的连续的二进制数字,只需要一个bit位,即可表示对应的值或者状态,8个bit组成一个byte,所以bitmap特别省空间
常用命令:setbit、getbit、bitcount、bitop
设置和查看bit位的值:setbit、getbit
统计区间内bit位为1的数量:bitcount key、bitcount key start end
Geo(地理位置)
Redis深入了解
Redis的持久化原理
redis的数据存储在内存中,持久化是指redis将数据写成数据文件,存入硬盘中。在redis重启时,通过读取数据文件的方式将数据重新存入内存中,从而达到数据持久化存储的目的。redis有两种不同的持久化存储方式,一种是RDB,一种是AOF,在redis.conf中可以配置数据的存储方式为rdb或者aof
redis的持久化文件默认存储在配置文件的文件夹下
RDB
RDB是以快照的方式,通过某种触发规则,将redis的全量数据快照,并生成rdb文件(后缀是.rdb)。在redis重启时,会读取RDB快照文件恢复数据。
RDB的触发方式有手动触发和自动触发
指令手动触发
- sava:输入此命令会阻塞redis服务器,知道rdb文件创建完毕为止,一般线上不采用这种方式
- bgsave:输入此命令不会阻塞redis服务器,该方式会fork一个子进程,子进程负责持久化
自动触发
-
根据 sava m n(m秒内有n次key值的变化操作就触发持久化)配置规则自动触发
-
从节点全量复制时,主节点会发送rdb文件给从节点,主节点触发bgsave
-
执行debug reload时
-
执行shutdown时,如果没有开启aof,也会发生
redis.conf:
# 时间策略
save 900 1 # 表示900 秒内如果至少有 1 个 key 的值变化,则触发RDB
save 300 10 # 表示300 秒内如果至少有 10 个 key 的值变化,则触发RDB
save 60 10000 # 表示60 秒内如果至少有 10000 个 key 的值变化,则触发RDB
# 文件名称
dbfilename dump.rdb
# 文件保存路径
dir /home/work/app/redis/data/
# 如果持久化出错,主进程是否停止写入
stop-writes-on-bgsave-error yes
# 是否压缩
rdbcompression yes
# 导入时是否检查
rdbchecksum yes
RDB的优缺点分析
优点:
- 备份数据效率高,适用于大规模的数据备份,自动备份不会影响主线程工作
- 备份相比AOF方式来说占用空间很小。
缺点:
- 可能会造成数据丢失,如果配置了自动备份的条件,如果没有触发备份条件,但是redis又因为一些问题宕机了,那么 没有触发备份的数据就会丢失。
- 自动备份通过fork子进程的方式进行的,会占用两倍的内存,如果内存不足,可能会备份失败
AOF
AFO存储是指记录每一次对服务器的写操作(写命令),当服务器重启的时候,会重新执行这些命令以达到恢复数据的目的
特点:
-
以日志的方式来记录写操作,读操作不会记录
-
文件已追加的形式而不是修改
-
redis的aof其实就是把追加的文件从开始读到结尾,执行写操作
AOF的流程:
-
所有的写命令都会追加到redis 的缓冲区中
-
aof的缓冲区根据策略向硬盘进行同步的操作
-
随着aof文件越来越大,redis会定期对aof文件执行重写操作(将对同一个key的操作合成一个,无效的操作删除)
-
当redis重启时,加载aof文件,恢复数据
# 可以通过修改redis.conf配置文件中的appendonly参数开启
appendonly yes
# AOF文件的保存位置和RDB文件的位置相同,都是通过dir参数设置的。
dir ./
# 默认的文件名是appendonly.aof,可以通过appendfilename参数修改
appendfilename appendonly.aof
AOF文件写入和同步:
redis每结束一次事件,都会调用flushAppendOnlyFile函数,判断是否需要将AOF缓冲区中的数据写入到AOF文件中。
这个函数的判断逻辑是配置在redis.conf文件中的:
always:每次都存入
everysec:每秒存入
no:只写入不保存,只有当redis关闭时才执行写入aof的操作
AOF的重写:
当随着时间的积累,aof文件越来越大,那么这时候,就需要对aof的文件进行重写操作,即:
- 将对同一个key的操作合成为一条命令
- 将在后面就会被删除的key的命令,在文件中删除
触发:
- 手动调用bgrewriteaof命令
- 自动触发,需要配置
# 重写机制:避免文件越来越大,自动优化压缩指令,会fork一个新的进程去完成重写动作,新进程里的内存
数据会被重写,此时旧的aof文件不会被读取使用,类似rdb
# 当前AOF文件的大小是上次AOF大小的100% 并且文件体积达到64m,满足两者则触发重写
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
总结
如果rdb文件和aof文件同时存在,那么重启时会读取aof文件来恢复数据
线上实战经验
-
如果Redis中的数据不重要或者不敏感,可以不开启持久化,及时数据丢失对业务影响不大
-
可以根据情况指定redis的策略,可以手动出发备份
-
可以加入主从机器,一台机器单独进行备份处理,另一台机器正常响应客户端的命令
-
RDB持久化和AOF可以都开启,配合使用,但是会影响一部分的性能,影响不大,看情况使用
过期删除策略和内存淘汰策略
redis的key是可以设置过期时间的,那么请思考:
-
如何设置redis的过期时间
-
redis的key过期了,数据就从redis中删除了吗
-
如何设置redis 的内存大小,如果redis的内存满了,该如何调整?
Redis设置过期时间
redis有四种方式设置过期时间
EXPIRE :表示将键 key 的生存时间设置为 ttl 秒。
PEXPIRE :表示将键 key 的生存时间设置为 ttl 毫秒。
EXPIREAT :表示将键 key 的生存时间设置为 timestamp 所指定的秒数时间戳。
PEXPIREAT :表示将键 key 的生存时间设置为 timestamp 所指定的毫秒数时间戳。
在redis中,前三种的方式最后都会转换为最终一条命令去执行
Redis过期的判定
redis中一旦给一个key设置了过期时间,那么这条数据就会被放置于一个过期字典中。当我们查询一个键时,如果该键在过期字典中,那么就查询他的过期时间,将其和系统时间进行判定,如果比系统的时间小,那么就过期了。
Redis过期删除策略
redis中的数据并不是一旦到了过期时间就会被删除,而是分为惰性删除和定时删除
惰性删除:惰性删除就是,所有的读写命令在执行前都会调用一个expireIfNeeded函数对其进行判定,如果过期了,那么就将key删除,然后再执行原有操作。
定时删除:定时删除就是,redis 以一定的频率定时取出一部分的随机数据进行检查,然后删除其中过期的key
注意:并不是一次检查所有,而是随机检查一定数量的key
在redis2.6.0中,大概每秒执行10次,2.8后可以在redis.conf中调整hz的参数来调整频率
内存淘汰策略
在redis.conf文件中,可以通过配置maxmemory参数来指定redis的最大内存,当redis现有内存大于这个值时,就会出发内存淘汰策略。
内存淘汰有常见的三种策略:FIFO、LRU和LFU三种
-
FIFO:先进先出:移除最早存入redis的数据
-
LRU:移除最近最少使用的
-
LFU:移除最不经常使用的
redis可以通过配置,选择他内存淘汰的算法
Redis性能测试
Redis的性能测试可以使用官方提供的工具:benchmark
语法:
redis-benchmark -t set,get,incr -n 1000000 -q
Redis高可用
当redis停止服务后,重新启动,可通过读取rdb或aof文件的方式将数据恢复,这是正常情况。如果redis服务器损坏,导致数据丢失,该如何处理?
针对这个问题,redis推出了主从复制
主从复制
主从复制就是指将一台redis服务器的数据复制到其他的redis服务器节点。前者称为主节点(master),而后者称为从节点(slave)。从节点有着和主节点一样的数据。数据的复制是单向的,只能从主节点复制到从节点。引入主从复制后,主节点可以负责查询和修改等操作,而从节点只能负责查询的操作。当主节点的数据发生更改,会像从节点同步,确保他们的数据一致
主从复制的原理
主从复制有两种方式,一种是全量同步,一种是增量同步。
-
**全量同步:**是指主节点发送自己的rdb文件到从节点,从节点通过读取rdb文件的方式将数据写入自己的服务器。同步期间主节点进行的操作也会写入,再发送给从节点
-
**增量同步:**因为各种原因导致主从节点断开后,从节点在连上主节点后会尝试从断开后未同步的部分开始同步,也叫部分复制
-
- 增量同步的原理是:主节点会记录一个字符串标志同步的版本号,还会一直记录一个数据集的偏移量,从节点会根据版本号和偏移量确定自己要同步的数据有哪些。如果版本号和偏移量差异太大,redis会选择重新进行全量同步
如何配置主从复制
要配置主从复制,不要主节点做任何事,只需要对从节点进行操作即可。
有以下三种方式来配置主从复制:
-
通过配置文件,在配置文件中加入slaveof host port
-
在redis-server启动命令后加入 --slaveof host port
-
在Redis服务器启动后,通过客户端执行命令:slaveof host port,则该Redis实例称为从节点
通过 info replication命令可以查看节点的状态,如下图所示,就表示该节点为主节点
一个主节点可以配置多个从节点,这样数据的安全性和完整性可以得到保证,但是如果主节点挂了,那么从节点会变成主节点吗?不会的,所以要引入哨兵机制
sentinel哨兵模式
哨兵模式为了解决的问题就是:当一个主节点有多个从节点,但是主节点挂掉了,从节点又无法担任主节点,那么整个redis还是不可用。
哨兵模式是一种特殊的模式,哨兵是一个独立的进程,他会独立运行,他会主动向redis服务器发送命令,以此来监控各个redis实例的状态
哨兵的搭建步骤:
- 在redis的配置文件目录下新建sentinel.conf文件,然后配置相应的内容:
sentinel monitor 被监控机器的名字(自己起名字)ip地址 端口号 得票数
上面的得票数表示主机挂掉后salve投票让谁称为主机,得票数大于1即可变成主机
- 启动哨兵
redis-sentinel /路径/sentinel.conf
哨兵也支持创建集群
Redis集群 cluster
主从加哨兵模式问题缺陷
主从加哨兵模式,本质上还是只有一个master节点,数据安全性得到了保证,但是当写请求过于频繁时,对master的压力就会很大,并且每一个节点都需要保存全量的数据,数据冗余比较多
Cluster
custer的主要用途是实现数据分片,将数据分散在多台主机上,当查询key时会计算该key会存储在哪台服务器上,存储也是一样。
当主节点下线时,从节点会自动变为主节点!!!
集群的分片策略:集群的分片策略是为了决定key的存储位置的,常见的数据分布的方式有:顺分布、哈希分布、节点取余哈希、一致性哈希
Redis集群中主节点内置了故障检测和故障转移功能。当集群中的某个主节点下线时,集群中的其他在线主节点会注意到,然后进行故障转移
Redis的集群搭建
首先,创建如下6个文件夹,将redis.conf文件分别拷贝到这些文件夹中
对这6个文件夹中的配置文件进行修改:
#cluster-enabled yes 改为 cluster-enabled yes
#cluster-config-file nodes-6379.conf 改为 cluster-config-file "/usr/local/ruanjian/redis/clustor/nodes-9001.conf" 端口设置为多少,这里就叫多少
启动好后查看进程:
使用redis-cli连接即可:
#replicas表示副本数,如果指定1则表示1个从库做备用
redis-cli --cluster create 127.0.0.1:9001 127.0.0.1:9002 127.0.0.1:9003
127.0.0.1:9004 127.0.0.1:9005 127.0.0.1:9006 --cluster-replicas 1
Redis实战
Spring Data Redis的搭建
首先创建maven项目,以springboot作为父依赖
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.2.6.RELEASE</version>
</parent>
引入SpringDataRedis的依赖:
<!--springdata-redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置好依赖后,在application.yml或application.properties中加入如下配置:
spring:
redis:
host: 127.0.0.1
port: 6379
password: XXXX 没有不设置
database: 0 也可以选其他
在使用时直接注入StringRedisTemplate和RedisTemplate即可
StringRedisTemplate和RedisTemplate之间的数据时不互通的,是因为StringRedisTemplate的key存的是字符串,value也是正常的字符。而RedisTemplate存的是序列化后的key和value,所以他们之间的数据不互通。使用RedisTemplate有一个好处,就是他可以直接把对象存储进去,拿回来时不需要再转化,直接就能拿到完整的对象
Redis的企业应用
分布式锁
为什么要上锁?
因为在多线程大量并发时,如果有类似秒杀的场景,会导致出现商品超卖问题,因为代码中判断是否卖完和对商品总量-1这两步一定不是原子操作,所以在大量并发时,因为线程之间频繁切换,就会导致商品超卖出很多。
为什么要分布式锁?
因为现在大部分企业采用的都是多容器部署,都采用K8S的方式,如果只上了单机锁,在其他的容器是获取不到锁的,所以必须使用分布式锁。
分布式锁通常怎么做?
分布式锁通常使用redis或zookeeper来实现,但在实际开发中,redis是首选
分布式锁在使用时有哪些需要注意的
- 锁续约:很多业务在执行时,都会给redis上锁,为了避免死锁,会给锁一个过期时间。但是如果过期时间设置的太小,业务还没执行完,锁已经过期,其他请求就可以进来,这会导致业务出现问题
锁续约是给redis开启一个守护线程,这个守护线程会监控主线程的执行,若主线程还没执行结束,就给主线程的锁续约,已达到线程安全的目的
- 可重入性:redis是不支持可重入的,一个请求再调用自己的时候,会发生被锁住的情况,支持可重入锁是很麻烦复杂的,我们可以用其他的方式
以上两个问题-锁续约和可重入锁实现起来都很复杂,但是可以依赖第三方 Redisson,他已经对redis做了很多封装,简单易用
坐标:
<dependency>
<groupId>>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.8.2</version>
</dependency>
布隆过滤器
如果有以下场景:
- 需要在10亿的数字中,判断某个数字是否存在
- 在一亿的手机号码中判断谁某人是否已经注册过
这时候,如果要快速的判断数据,如果将数据全部放在redis或者内存中,那会非常大。那么布隆过滤器就是很好的选择
思想
如果想判断一个元素是不是在集合中,一般的做法是将数据保存起来,然后通过比较确定。但是随着集合越来越庞大,检索的速度就会越来越慢。那么,数据能不能不存储呢?
可以通过hash表的方式,调用hash函数,将原数据映射成一个点,这样,只要判断hash过后的那个点是不是1,就能知道数据是否存在。
原理
redis中的bitmap就是借鉴布隆过滤器的思想,布隆过滤器就是一个超大二进制数组,每一位上要么是1,要么是0。但是一次hash过后的取值可能有问题,因为hash过后的值时有可能重复的。而布隆过滤器采用了将一个key进行多次hash运算的方式,确保发生hash碰撞的概率很低。
简单来说就是:布隆过滤器中判断数据是否存在,如果为1,那么有可能存在,如果为0,那么一定不存在。
优缺点
布隆过滤器的优点:
- 占内存极小
- 检索非常快
布隆过滤器的缺点:
- 不准确,一定会有误判率
- 数据无法删除,只能添加
实现方式
如果在内存中实现布隆过滤器,那么guava工具类可以帮我们做到
如果在redis中实现,可以借助Redisson工具类
Redis的经典面试题
- 什么是redis的雪崩?如何应对?
redis雪崩是指多个热门的key在同一时间大量过期,大量的请求突然全部进入数据库,导致数据库的压力骤增。
对于这个问题,可以通过给redis的key设置统一的过期时间时,在后面加上一个随机值,这样数据就会陆续的过期,给数据库缓冲的时间
- 什么是缓存穿透?
缓存穿透是指,有人以大量的请求去请求系统内一个不存在的key,如-1等,redis无法拦截,导致数据大量的进入数据库。
解决思路:
-
对不合法的key进行判断,如-1等
-
使用布隆过滤器,可以拦截大量的请求,只有当数据在库中可能存在时,请求才会穿过redis
-
什么是缓存击穿?
缓存击穿是指一个热点访问的key,在大规模并发请求的情况下,这个key过期了,数据库的请求量骤增
解决思路:
- 关于特别热门的key,可以考虑设置key不过期
- 使用互斥锁,可以防止把数据库弄崩,但是会导致系统性能非常差