Redis高级
一、数据类型
1. String
1.1 简介
String是Redis最基本的类型,一个key对应一个value。String是二进制安全的,意味着String可以包含任何数据,比如序列化对象或者一张图片。String最多可以放512M的数据。
1.2 使用场景
-
计数器
-
统计多单位的数量
-
粉丝数
-
对象缓存存储
-
2. List
2.1 简介
List是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。
底层是一个双向链表, 对两端操作性能极高,通过索引操作中间的节点性能较差。
一个List最多可以包含 2^{32}-1个元素 ( 每个列表超过40亿个元素)。
2.2 使用场景
-
-
排行榜
-
3. Set
3.1 简介
与List类似是一个列表功能,但Set是自动排重的,当需要存储一个列表数据,又不希望出现重复数据时,Set是一个很好的选择。
Set是String类型的无序集合,它底层其实是一个value为null的hash表,所以添加、删除、查找的时间复杂度都是O(1)。
3.2 使用场景
-
-
随机展示
-
好友
-
关注人
-
粉丝
-
感兴趣的人集合
4. Hash
4.1 简介
Hash存储结构优化
-
如果field数量较少,存储结构优化为类数组结构
-
4.2 使用场景
5. Zset
5.1 简介
Zset与Set非常相似,是一个没有重复元素的String集合。
不同之处是Zset的每个元素都关联了一个分数(score),这个分数被用来按照从低分到高分的方式排序集合中的元素。集合的元素是唯一的, 但分数可以重复。
因为元素是有序的,所以可以根据分数(score)或者次序(position)来获取一个范围内的元素。
5.2 使用场景
-
-
排行榜
-
6. Bitmaps
6.1 简介
在Redis中,Bitmaps是一种特殊的数据类型,它实际上是一系列位(0或1)的集合。这个数据结构允许你对位进行高效的操作,并且可以用于解决一些有趣的问题,如位图统计、用户在线状态等。
6.2 使用场景
-
-
打卡天数
-
登录天数
-
用户签到
-
统计活跃用户
-
统计用户是否在线
-
7. Geospatial
7.1 简介
GEO,Geographic,地理信息的缩写。该类型就是元素的二维坐标,在地图上就是经纬度。Redis基于该类型,提供了经纬度设置、查询、范围查询、距离查询、经纬度Hash等常见操作。
7.2 使用场景
-
-
附近的好友
-
8. HyperLogLog
8.1 简介
在我们做站点流量统计的时候一般会统计页面UV(独立访客:unique visitor)和PV(即页面浏览量:page view)。redis HyperLogLog是用来做基数统计的算法,HyperLogLog的优点是:在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且使很小的。
8.2 使用场景
-
网站PV统计
-
网站UV统计
-
统计访问量(IP数)
-
统计在线用户数
-
统计每天搜索不同词条的个数
-
订阅命令:
subscribe 主题名字
发布命令:
publish 主题名 发布的内容
三、慢查询
1. 介绍
慢查询就是查询速度较慢的查询。
说明:
-
慢查询发生在第3阶段
-
客户端超时不一定慢查询,但慢查询是客户端超时的一个可能因素
-
慢查询日志是存放在Redis内存列表中
2. 慢查询日志
慢查询日志是Redis服务端在命令执行前后计算每条命令的执行时长,当超过某个阈值是记录下来的日志。
日志中记录了慢查询发生的时间,还有执行时长、具体什么命令等信息,它可以用来帮助开发和运维人员定位系统中存在的慢查询。
可以使用 slowlog get 命令获取慢查询日志,在 slowlog get 后面还可以加一 个数字,用于指定获取慢查询日志的条数。
SLOWLOG get 3
slowlog len 命令获取慢查询日志的长度
> slowlog len
(integer) 121
3. 配置慢查询参数
-
命令执行时长的指定阈值 slowlog-log-slower-than:
slowlog-log-slower-than的作用是指定命令执行时长的阈值,执行命令的时长超过这个阈值时就会被记录下来。
-
存放慢查询日志的条数 slowlog-max-len。
配置方式:
【1】查看慢日志配置
查看redis慢日志配置,登陆redis服务器,使用redis-cli客户端连接redis server
127.0.0.1:6379> config get slow* 1) "slowlog-max-len" 2) "128" 3) "slowlog-log-slower-than" 4) "10000"
参数说明:10000阈值,单位微秒,此处为10毫秒,128慢日志记录保存数量的阈值,此处保存128条。
slowlog-log-slower-than 1000
slowlog-max-len 1200
【3】使用 config set 命令动态修改。
比如,还是把slowlog-log-slower-than设置为1000,slowlog-max-len设置为1200
> config set slowlog-log-slower-than 1000 OK > config set slowlog-max-len 1200 OK > config rewrite OK
4. 建议
slowlog-max-len配置建议
-
线上建议调大慢查询列表,记录慢查询时Redis会对长命令做截断操作,并不会占用大量内存。
-
增大慢查询列表可以减缓慢查询被剔除的可能,例如线上可设置为1000以上。
slowlog-log-slower-than配置建议
-
默认值超过10毫秒判定为慢查询,需要根据Redis并发量调整该值。
-
由于Redis采用单线程响应命令,对于高流量的场景,如果命令执行时间在1毫秒以上,那么Redis最多可支撑OPS(Operations Per Second每秒操作数)不到1000。因此对于高OPS场景的Redis建议设置为1毫秒。
四、Redis 流水线 pipeline
批量网络命令通信
经历了 n次时间 = n次网络时间 + n次命令时间
流水线:
经历了 1次pipeline(n条命令) = 1次网络时间 + n次命令时间,这大大减少了网络时间的开销,这就是流水线。
pipeline-Jedis实现
引入依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
使用pipeline
@Test void test3() { redisTemplate.executePipelined((RedisConnection connection)->{ long start = System.currentTimeMillis(); Object k1 = connection.get("k1".getBytes()); Object k2 = connection.get("k2".getBytes()); Object k3 = connection.get("k3".getBytes()); long end = System.currentTimeMillis(); System.out.println("花费时间:"+(end-start)); return null; }); }
五、Redis持久化机制
-
RDB(Redis DataBase)
-
1. RDB
在指定的时间间隔内将内存的数据集快照写入磁盘,也就是行话讲的快照,它恢复时是将快照文件直接读到内存里。
注意: 这种格式是经过压缩的二进制文件。
2. AOF
以日志的形式来记录每个写操作,将Redis执行过的所有写指令记录下来。
3. 如何选择
RDB数据快照文件,都是每隔5分钟,或者更长时间生成一次,这个时候就得接受一旦redis进程宕机,那么会丢失最近5分钟的数据。
也不要仅仅使用AOF
-
你通过AOF做冷备,没有RDB做冷备,来的恢复速度更快。
-
RDB每次简单粗暴生成数据快照,更加健壮,可以避免AOF这种复杂的备份和恢复机制的bug。
综合使用AOF和RDB两种持久化机制
Redis事务三大特性
-
单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断;
-
没有隔离级别的概念:队列中的命令没有提交之前都不会实际的被执行,因为事务提交前任何指令都不会被实际执行,也就不存在”事务内的查询要看到事务里的更新,在事务外查询不能看到”。
-
不保证原子性:redis同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚;
Redis事务执行的三个阶段
-
-
入队:将多个命令入队到事务中,接到这些命令并不会立即执行,而是放到等待执行的事务队列里面;
-
2. 基本操作
Multi:开始事务
Exec:执行
discard:放弃事务
事务从输入Multi命令开始,输入的命令都会依次压入命令缓冲队列中,并不会执行,直到输入Exec后,Redis会将之前的命令缓冲队列中的命令依次执行。组队过程中,可以通过discard来放弃组队。
注意:
- 命令集合中含有错误的指令(注意是语法错误),均连坐,全部失败。
- 运行时错误,即非语法错误,正确命令都会执行,错误命令返回错误。
七、主从复制
1. 简介
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave),数据的复制是单向的,只能由主节点到从节点。
主从复制的作用
-
数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
-
故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
-
负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务 (即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
-
高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
2. 原理
主从复制可以分为3个阶段
-
连接建立阶段(即准备阶段)
-
数据同步阶段
-
命令传播阶段
复制过程大致分为6个过程
-
-
从节点(slave)内部通过每秒运行的定时任务维护复制相关逻辑,当定时任务发现存在新的主节点后,会尝试与该节点建立网络连接
-
从节点与主节点建立网络连接
从节点会建立一个 socket 套接字,从节点建立了一个端口为51234的套接字,专门用于接受主节点发送的复制命令
-
发送ping命令
连接建立成功后从节点发送 ping 请求进行首次通信。
作用:
检测主从之间网络套接字是否可用。
检测主节点当前是否可以接受命令 。
-
权限验证。
如果主节点设置了 requirepass 参数,则需要密码验证,从节点必须配置 masterauth 参数保证与主节点相同的密码才能通过验证;如果验证失败复制将终止,从节点重新发起复制流程。
-
同步数据集。
主从复制连接正常通信后,对于首次建立复制的场景,主节点会把持有的数据全部发送给从节点,这部分操作是耗时最长的步骤。
-
主从同步策略
主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。当然,如果有需要,slave 在任何时候都可以发起全量同步。 redis 策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步。
八、Redis哨兵监控
1. 简介
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
-
集群监控:负责监控redis master和slave进程是否正常工作
-
消息通知:如果某个redis实例有故障,那么哨兵负责发送消息作为报警通知给管理员
-
故障转移:如果master node挂掉了,会自动转移到slave node上
-
2. 原理
2.1 监控阶段
-
-
sentinel(哨兵2)----->向master(主)发起info,就知道已经存在的sentinel(哨兵1)的信息,并且连接slave(从)。
-
2.2 通知阶段
sentinel不断的向master和slave发起通知,收集信息。
2.3 故障转移阶段
通知阶段sentinel发送的通知没得到master的回应,就会把master标记为SRI_S_DOWN,并且把master的状态发给各个sentinel,其他sentinel听到master挂了,说我不信,我也去看看,并把结果共享给各个sentinel,当有一半的sentinel都认为master挂了的时候,就会把master标记为SRI_0_DOWN。
2.4 投票阶段
剔除一些情况:
-
不在线的
-
响应慢的
-
与原来master断开时间久的
-
总结
-
监控: 哨兵周期性地检查与其关联的Redis节点的健康状态。它发送PING命令,检查节点是否响应,还可以通过执行自定义脚本来进一步检查节点的状态。
-
故障检测: 当一个Redis节点被认为不可用时,哨兵将其标记为主观下线(
SDOWN
)。主观下线是哨兵自己的观点,表示它认为节点已经故障。 -
共识: 多个哨兵之间进行通信,通过共识算法判断是否有足够的哨兵都认为某个节点是主观下线,形成共识。这时,哨兵将该节点标记为客观下线(
ODOWN
)。 -
故障切换: 当一个主节点被标记为客观下线时,哨兵会选择一个可用的从节点升级为新的主节点,以确保系统的持续可用性。这个过程叫做故障切换。
-
通知: 哨兵会通知其他哨兵和客户端关于发生的故障切换,以便它们可以调整配置并连接到新的主节点。
-
自愈和监控: 一旦故障切换完成,哨兵会继续监控集群中的节点,以确保新的主节点正常工作。如果其他节点恢复,哨兵还可以将它们重新加入集群。
-
配置管理: 哨兵还负责管理配置信息,包括监视的节点、故障切换策略、通知方式等。它可以根据需要动态调整配置。
哨兵基于Redis的发布订阅机制实现
在Redis Sentinel系统中,哨兵节点需要相互了解彼此的存在以及每个哨兵的状态。它们通过Redis的发布-订阅功能来实现这一点:
-
发布(Publish)消息: 每个哨兵节点会定期发布消息,通知其他哨兵其存在和状态。这些消息包含哨兵的信息和它所监控的Redis服务器的状态。
-
订阅(Subscribe)频道: 同时,每个哨兵节点也会订阅一个特定的频道,以便接收其他哨兵发布的消息。
-
共识和决策: 通过这种方式,哨兵节点可以相互交换信息,并在需要进行故障转移时共同达成决策。例如,当多数哨兵认为主节点不可达时,它们会协同选择一个从节点来提升为新的主节点。
-
配置传播: 此外,当发生故障转移或其他重要事件时,哨兵之间通过发布-订阅机制传播新的配置信息,以确保整个Sentinel系统的一致性。
九、Redis Cluster
-
主从模式
-
Sentinel模式
-
哨兵模式缺点:
当master挂掉的时候,sentinel 会选举出来一个 master,选举的时候是没有办法去访问Redis的,会存在访问瞬断的情况;
哨兵模式,对外只有master节点可以写,slave节点只能用于读。尽管Redis单节点最多支持10W的QPS,但是在电商大促的时候,写数据的压力全部在master上。
-
Redis集群有多个master,可以减小访问瞬断问题的影响
-
Redis集群有多个master,可以提供更高的并发量
-
2. 原理
Redis Cluster将所有数据划分为16384个slots(槽位),每个节点负责其中一部分槽位。槽位的信息存储于每个节点中。只有master节点会被分配槽位,slave节点不会分配槽位。
3. SpringDataRedis 连接集群
引入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
配置文件
spring:
redis:
cluster:
nodes: 192.168.184.137:8001,192.168.184.137:8002,192.168.184.138:8001,192.168.184.138:8002,192.168.184.139:8001,192.168.184.139:800
十、Redis常见问题
Redis集群中的“脑裂”(Brain Split)是指集群中的不同节点之间因为网络问题而失去联系,导致集群被分割成两个或多个相互独立的子集。在这种情况下,集群的不同部分可能无法相互通信,每个部分都可能认为自己是有效的集群,并试图独立进行操作。
Redis的集群脑裂是指因为网络问题,导致Redis Master节点跟Redis slave节点和Sentinel集群处于不同的网络分区,此时因为sentinel集群无法感知到master的存在,所以将slave节点提升为master节点。
解决方案注意:
此时存在两个不同的master节点,就像一个大脑分裂成了两个。集群脑裂问题中,如果客户端还在基于原来的master节点 继续写入数据,那么新的Master节点将无法同步这些数据,当网络问题解决之后,sentinel集群将原先的Master节点降为slave节点,此时再从新的master中同步数据,将会造成大量的数据丢失。
redis.conf配置参数:
min-replicas-to-write 1 min-replicas-max-lag 5
参数:
第一个参数表示最少的slave节点为1个
第二个参数表示数据复制和同步的延迟不能超过5秒
2. Redis 缓存预热
出现在大型项目中。
2.1 缓存冷启动
缓存中没有数据,由于缓存冷启动一点数据都没有,如果直接就对外提供服务了,那么并发量上来Mysql就裸奔挂掉了。
2.2 冷启动应用场景
新启动的系统没有任何缓存数据,在缓存重建数据的过程中,系统性能和数据库负载都不太好,所以最好是在系统上线之前就把要缓存的热点数据加载到缓存中,这种缓存预加载手段就是缓存预热。
2.3 解决思路
-
提前给redis中灌入部分数据,再提供服务
-
如果数据量非常大,就不可能将所有数据都写入redis,因为数据量太大了,第一是因为耗费的时间太长了,第二根本redis容纳不下所有的数据
-
需要根据当天的具体访问情况,实时统计出访问频率较高的热数据
-
然后将访问频率较高的热数据写入redis中,肯定是热数据也比较多,我们也得多个服务并行读取数据去写,并行的分布式的缓存预热
3. Redis缓存穿透
6.3.1 概念
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
解释:
缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空(相当于进行了两次无用的查询)。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题。
6.3.2 解决方案
-
对空值缓存:如果一个查询返回的数据为空(不管数据是否存在),我们仍然把这个空结果缓存,设置空结果的过期时间会很短,最长不超过5分钟。
-
布隆过滤器:如果想判断一个元素是不是在一个集合里,一般想到的是将集合中所有元素保存起来,然后通过比较确定。
6.3.3 布隆过滤器
布隆过滤器是一种数据结构,比较巧妙的概率型数据结构(probabilistic data structure),特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”。
注意:
布隆说不存在一定不存在,布隆说存在你要小心了,它有可能不存在
4. Redis 缓存击穿问题
4.1 概念
某一个热点 key,在缓存过期的一瞬间,同时有大量的请求打进来,由于此时缓存过期了,所以请求最终都会走到数据库,造成瞬时数据库请求量大、压力骤增,甚至可能打垮数据库。
4.2 解决方案
-
互斥锁:在并发的多个请求中,只有第一个请求线程能拿到锁并执行数据库查询操作,其他的线程拿不到锁就阻塞等着,等到第一个线程将数据写入缓存后,其他线程直接查询缓存。
-
热点数据不过期:直接将缓存设置为不过期,然后由定时任务去异步加载数据,更新缓存(不咋靠谱)
5. Redis缓存雪崩问题
5.1 概念
缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。
5.2 解决方案
-
过期时间打散:既然是大量缓存集中失效,那最容易想到就是让他们不集中生效。可以给缓存的过期时间时加上一个随机值时间,使得每个 key 的过期时间分布开来,不会集中在同一时刻失效。
-
热点数据不过期:该方式和缓存击穿一样,也是要着重考虑刷新的时间间隔和数据异常如何处理的情况。
-
加互斥锁: 该方式和缓存击穿一样,按 key 维度加锁,对于同一个 key,只允许一个线程去计算,其他线程原地阻塞等待第一个线程的计算结果,然后直接走缓存即可
用二级缓存解决:先查询本地nginx缓存查询有没有数据,有数据,直接返回; nginx缓存没有数据再去redis分布式缓存查询,如果有将redis缓存同步nginx缓存在返回;如果redis没有,取数据库查询,数据库存在将数据同步到redis并返回。只要nginx和redis的过期时间不一样,就解决了redis缓存雪崩问题,并合理使用了分布式缓存。
6. Redis 开发规范
6.1 key设计技巧
-
把表名转换为key前缀,如 tag:
-
把第二段放置用于区分key的字段,对应msyql中主键的列名,如 user_id
-
第三段放置主键值,如 2,3,4
-
第四段写存储的列名
示例:
# 表名 主键 主键值 存储列名字 set user:user_id:1:name 张三 set user:user_id:1:age 20
6.2 value设计
拒绝bigkey
防止网卡流量、慢查询,string类型控制在10KB以内,hash、 list、set、zset元素个数不要超过5000。
6.3 命令使用
-
禁用命令 :禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。
-
合理使用select :redis的多数据库较弱,使用数字进行区分,很多客户端支持较差, 同时多业务用多数据库实际还是单线程处理,会有干扰。
-
使用批量操作提高效率
-
原生命令:例如mget、mset。
-
非原生命令:可以使用pipeline提高效率
-
注意:但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。
-
不建议过多使用Redis事务功能
Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上。
7. Redis 数据一致性问题
缓存说明:
从理论上来说,给缓存设置过期时间,是保证最终一致性的解决方案。
三种更新策略
-
先更新数据库,再更新缓存
-
先删除缓存,再更新数据库
-
先更新数据库,再删除缓存
先更新数据库,再更新缓存
这套方案,大家是普遍反对的。为什么呢?
线程安全角度 ,同时有请求A和请求B进行更新操作,那么会出现
(1)线程A更新了数据库
(2)线程B更新了数据库
(3)线程B更新了缓存
(4)线程A更新了缓存
这就出现请求A更新缓存应该比请求B更新缓存早才对,但是因为网络等原因,B却比A更早更新了缓存。这就导致了脏数据,因此不考虑。
先删缓存,再更新数据库
该方案会导致不一致的原因是。同时有一个请求A进行更新操作,另一个请求B进行查询操作。那么会出现如下情形:
(1)请求A进行写操作,删除缓存
(2)请求B查询发现缓存不存在
(3)请求B去数据库查询得到旧值
(4)请求B将旧值写入缓存
(5)请求A将新值写入数据库
注意: 该数据永远都是脏数据。
先更新数据库,再延时删缓存√
这种情况存在并发问题吗?
(1)缓存刚好失效
(2)请求A查询数据库,得一个旧值
(3)请求B将新值写入数据库
(4)请求B删除缓存
(5)请求A将查到的旧值写入缓存
发生这种情况的概率又有多少?
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)