Redis
2024-07-05 18:02:25 58 举报
AI智能生成
Redis 2024年4月28日20:55:29:补充底层数据结构的实现(面试被问麻了) 2024年7月5日18:01:17:大版本更新,除了一些进阶的知识,基本上都全了,分布式锁这块没有详细记录,直接记了Redission
作者其他创作
大纲/内容
Redis是一款内存高速缓存数据库。Redis全称为:Remote Dictionary Server(远程数据服务),使用C语言编写,Redis是一个key-value存储系统(键值存储系统),支持丰富的数据类型,如:String、list、set、zset、hash
是什么
基于内存,读写优异
数据类型丰富,数据结构优化
持久化
为什么
工作中用的最多的就这个,而且主要是用String存token、权限信息、验证码这些
读取前,先去读Redis,如果没有数据,读取数据库,将数据拉入Redis
插入数据时,同时写入Redis
缓存的使用方式
缓存
要么是RedisUtil封一下setnx方法,要么直接用redisson
这个主要利用redis的setnx命令进行,setnx:\"set if not exists\"就是如果不存在则成功设置缓存同时返回1,否则返回0
分布式锁
没用过
计数器
存储地理信息
签到打卡
统计页面访问量
没用过,不要抢专业MQ的饭碗(RabbitMQ...)
消息队列
......
使用场景
开启daemonize yes
注释bing 127.0.0.1
protected-mode no
指定端口
指定当前工作目录,dir
pid文件名,pidfile
log文件名,logfile
dump.rdb名字
aof文件名,appendfilename
密码requirepass
安装需要注意的事项:
1、基础和概念
介绍:String类型是二进制安全的,意思是 redis 的 string 可以包含任何数据。如数字,字符串,jpg图片或者序列化的对象,最大512M
使用场景:缓存、计数器、Session,分布式锁
动态字符串SDS
底层数据结构
String
介绍:Redis中的List其实就是链表,底层是个双端链表
使用场景:排队功能、消息队列
快表
List
介绍:Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
使用场景:标签、点赞、踩、收藏
整数集
哈希表
Set
使用场景:排行榜
跳表
压缩列表
ZSet(SortSet)
介绍:Redis hash 是一个 string 类型的 field(字段) 和 value(值) 的映射表,hash 特别适合用于存储对象。
使用场景:存储对象
Hash
5种基础数据结构
可以非常省内存的去统计各种计数,比如注册 IP 数、每日访问 IP 数、页面实时UV、在线用户数,共同好友数等
基数统计
Hyperloglog
存储、操作地理位置信息
Geospatial(GEO)
两个状态的,都可以使用 Bitmaps
位图数据结构,都是操作二进制位来进行记录,只有0 和 1 两个状态
Bitmap
一次性操作多个二进制位
Bitfield
4种特殊数据结构
类似Redis版本的MQ,Redis想抢饭碗,或者是怕被卡脖子(还是前面的意见,专业的事儿交给专业的做,要用MQ直接上MQ中间件)
Stream
2、数据结构
帮助命令:help @具体的数据类型
keys * 查看当前库所有的key
exists key 判断某个key是否存在
type key 查看key的类型
del key 删除指定的key(同步),大key的情况下会阻塞
unlink key 非阻塞(异步)删除
ttl key 查看还有多少秒过期 -1永不过期 -2已过期
expire key 设置过期时间
move key dbindex ,将当前数据库的key移动到指定的db
key操作
select dbindex 切换数据库【0-15】,默认0
dbsize 当前db的key数量
flushdb 清空当前库
flushall 通杀全部库
db操作
NX(分布式锁)
XX
EX,秒单位的过期时间
keepttl,保留最初设置的过去时间,对key修改时,ttl不会重制
常用参数
set key value
get key
最常用
mset k1 v1 k2 v2
mget k1 k2
同时设置、获取多个键值
getrange key start end
setrange key index value
获取指定区间的键值(字符串截取)
incr key
incr key num
decr key
decr key num
数值增减
长度:strlen key
追加:append key value
获取字符串长度和内容追加
setnx key value
setex
getset,给定key的值设为value,并返回key的旧值
先get再set
lpush/rpush/lrange
lpop/rpop
按照索引下标获得元素
lindex
获取列表中元素的个数
llen
删除N个等于V1的元素
lrem key 数字N 给点值V1
截取指定范围,然后赋值给key
ltrim key startIndex endIndex
rpoplpush 源列表 目的列表
lset key index value
linsert key befor/after 已有的值 新插入的值
hset/hget/hmset/hmget/hgetall/hdel
hlen
hexists key
hkeys/hvals
hincrby/hincrbyfloat
hsetnx
SADD key member
遍历所有元素
SMEMBERS key
判断元素是否在集合中
SISMEMBER key member
删除元素
SREM key member
获取集合里元素个数
scard
随机弹出元素,不删除
srandmember key 数字
随机弹出元素,出一个删一个
spop key 数字
smove key1 key2
SDIFF key [key ...]
差集A-B(属于A但不属于B)
SUNION
并集
SINTER
交集
集合运算
zset k1 score1 v1
zrange key start stop
zscore key member 获取元素的分值
zcard key 获取集合中元素的数量
zrem key score
增加元素的分值
zincrby key num member
ZSet
GEO
setbit key offset val
getbit key offset
bitcount key start end
bitop operation destkey key
具体到数据类型的操作
3、常用命令
RDB持久化是把当前进程数据生成快照保存到磁盘上的过程,由于是某一时刻的快照,那么快照中的值要早于或者等于内存中的值
内存快照
阻塞当前Redis服务器,直到RDB过程完成为止,对于内存 比较大的实例会造成长时间阻塞,线上环境不建议使用
save
Redis进程执行fork操作创建子进程,RDB持久化过程由子 进程负责,完成后自动结束。阻塞只发生在fork阶段,一般时间很短
bgsave
手动触发
配置文件里Save m n,基于配置文件里设置的规则m时间内有n次修改 触发
主从复制会触发
flushdb/flushall会触发
shutdown命令,如果没有开启aof,会触发
自动触发
配置文件里改成save \" \"
禁用rdb
触发方式
redis-check-rdb命令
rdb文件检查修复
关闭redis,把RDB文件复制到Redis的安装目录里,启动redis
恢复
RDB文件是某个时间节点的快照,默认使用LZF算法进行压缩,压缩后的文件体积远远小于内存大小,适用于备份、全量复制等场景
Redis加载RDB文件恢复数据要远远快于AOF方式
优点
无法做到秒级持久化
fork子进程属于重量级操作,开销大
RDB文件没有可读性,是二进制文件
实时性不够
缺点
RDB(默认)
针对RDB不适合实时持久化的问题,Redis提供了AOF持久化方式来解决
配置文件里 appendonly yes
AOF开启(默认关闭)
AOF日志记录Redis的每个写命令
同步写回
always
每秒写回
everysec(默认)
躺平,操作系统自己控制时机
no
AOF写回策略
和rdb一样
redis-check-aof --fix
AOF文件修改
实时性持久化,秒级
日志不易损坏,损坏了也能修复
满足配置文件的配置,默认是大于上一次的一倍且文件到达64M
自动
bgrewriteaof命令
手动
AOF重写触发机制
Redis通过创建一个新的AOF文件来替换现有的AOF,新旧两个AOF文件保存的数据相同,但新AOF文件没有了冗余命令
①主线程fork出子进程重写aof日志
②子进程重写日志完成后,主线程追加aof日志缓冲
③替换日志文件
重写原理
AOF文件过大的情况下有AOF重写机制进行瘦身
日志内容可读
优势
AOF文件比相同数据集的RDB文件更大
恢复速度慢于RDB
劣势
AOF
同时开启数据恢复只会加载aof文件,不会加载rdb
aof-use-rdb-preamble yes
开启
aof做增量,rdb做全量
特点
混合模式
同时关闭rdb和aof
纯缓存模式
4、持久化机制
介绍:可用一次执行多个命令,本质是一组命令的集合。一个事务中的所以命令都会序列化,按照顺序串行化执行而不会被其他命令插入
原理:本质上就是一个队列,一次性、顺序、排他性的执行一系列redis命令
使用:Redis 可以通过 MULTI,EXEC,DISCARD 和 WATCH 等命令来实现事务(transaction)功能
1开始事务(MULTI)
2、命令入队(批量操作 Redis 的命令,先进先出(FIFO)的顺序执行)。
3执行事务(EXEC)
使用过程
你也可以通过 DISCARD (opens new window)命令取消一个事务,它会清空事务队列中保存的所有命令
WATCH是乐观锁,基于CAS
WATCH (opens new window)命令用于监听指定的键,当调用 EXEC 命令执行事务时,如果一个被 WATCH 命令监视的键被修改的话,整个事务都不会执行,直接返回失败。
UNWATCH:取消WATCH对所有key的监视。
Redis的事务是原子性的:所有的命令,要么全部执行,要么全部不执行。而不是完全成功,在MySQL的语义里它是不保证原子性的。
redis事务可以保证命令失败的情况下得以回滚,数据能恢复到没有执行之前的样子,是保证一致性的,除非redis进程意外终结。
redis事务是严格遵守隔离性的,原因是redis是单进程单线程模式(v6.0之前),可以保证命令执行过程中不会被其他客户端命令打断。但是,Redis不像其它结构化数据库有隔离级别这种设计。
redis事务是不保证持久性的,这是因为redis持久化策略中不管是RDB还是AOF都是异步执行的,不保证持久性是出于对性能的考虑。
Redis事务的ACID
5、事务
批处理命令的优化措施,类似Redis的原生批命令(多条命令打包执行)
①命令集写到txt文件里
②通过管道符 | ,连接redis-cli 通过添加--pipe命令执行
使用
6、管道
消息队列,实现进程间消息通信
干嘛的
不推荐使用,专业的事交个专业的做,市面上有更好的消息中间件
subscribe channel:1
订阅
publish channel:1 hi
发布
基于频道(Channel)的发布/订阅
psubscribe c? b* d?*
publish c m1
publish c11 m1
基于模式(pattern)的发布/订阅
7、发布订阅(了解即可)
介绍:master以写为主,slave读为主,master数据变化时,会异步同步到slave
读写分离
容灾恢复
数据备份
水平扩容支撑高并发
能干什么:
配从库不配主库
master需要配密码
slave需要配置masterauth来设置master的密码
权限细节(重要)
info replication
一般会写进配置文件里
replicaof 主库ip 主库端口
每次和master端口后,都需要重新连接,除非在配置文件里配置了
可以用这个命令取消和当前主库的绑定,换绑到其他主库
slaveof 主库ip 主库端口
取消和主库的绑定,转成主库
slaveof no one
基本操作命令
怎么玩
master启动
slave启动成功连接到master后,发送一个sync命令
slave首次全新连接master,一次完全同步(全量复制)将被执行,slave自身数据会被覆盖清除
slave启动,同步初请
master收到sync命令后,开始在后台保存快照,对于期间接受到的命令进行缓存。快照执行完后,master将rdb文件和缓存的命令一次性打包发给slave,完成全量同步
slave接收到文件后,进行加载,完成复制初始化
首次连接,全量复制
repl-ping-replica-period 10,master发送ping包,默认10秒
心跳持续,保持通信
master继续将新收集到的命令传给slave同步
进入平稳,增量复制
master检查backlog里面的offset,master和slave都会保存一个复制的offset和masterId,offset是保存在backlog里的。Master只会把offset后面的数据传给slave,类似断点续传
从机下线,重连续传
复制的原理和工作流程
系统繁忙、slave数量过多会导致复制延时不可接受
复制延时,信号衰减
默认情况下,不会在slave里进行重新选举master,,需要人工进行干预,基于此,自动选举的需求就出来了
master单点问题,挂了怎么办?
复制的缺点
主从复制replica(最少2台)
吹哨人巡查监控后台master是否故障,如果出现故障,根据投票数自动将某一个从库转换为新的主库,继续对外服务
介绍
监控主从redis运行是否正常
主从监控
master异常会进行选举切换
故障转移
将故障转移的结果发送给其他客户端
消息通知
客户端通过连接哨兵来获得当前redis服务的主服务地址
配置中心
能干什么
只做监控维护集群,不放数据
3个哨兵
用于数据读取和存储
1主2从
图示:
标准架构(说白了最少6台):
quorum表示最少有几个哨兵认可客观下线,同意故障迁移的法定票数
sentinel monitor master-name ip port quorum
sentinel auth-pass master-name password
配置sentinel.conf文件
当一个主从配置中的master失效后,sentinel可以选举出一个新的master用于自动接替原masterd 工作,主从配置中的其他slave自动指向新的master同步数据,一般建议sentinel采用奇数台
SDOWN(主观不可用)是单个sentinel自己主观上检测到的关于master的状态,从sentinel的角度来看,如果发送了PING心跳后,在一定时间内没有收到合法的回复,就达到了SDOWN的条件。
Sentinel配置文件中的down-after-milliseconds设置判断主观下线的时间长度
主观下线
需要一定数量的sentinel达成一致,才认为master挂了
客观下线
Raft算法(先到先得)
怎么选?
选出哨兵中的领导者
配置文件中的优先级
偏移位置offset最大的
RunId最小的
规则
选出新的master
Sentinel发送命令,新的master解绑成为master,其他节点切换master
其他的节点指向当前master
同上
旧的master回来,成为slave,也指向当前master
有哨兵的领导者开始推动故障切换流程并选出新的master
运行流程、故障切换
运行流程和选举原理
哨兵数量多个、奇数、配置一致
哨兵+主从不能保证数据完全不丢失,承上启下引出下面的集群模式
使用建议
哨兵sentinel(最少6台)
数据量过大,单个master难以负重
sentinel不保证数据完全不丢失
为什么要集群?
Redis集群是一个提供在多个节点之间共享数据的程序集,redis集群可以支持多个master
定义
支持多个master,每个master又可以挂载多个slave
内置故障转移,支持高可用,无需哨兵
客户端连接任意一台都可以用
槽位slot负责分配到各个物理节点,由对应的集群来负责维护节点、插槽和数据直接的关系
定义:hash(key) % N个机器台数,计算出哈希值,用来决定数据映射到哪一个节点上
简单粗暴,直接有效
扩容或者缩容会很麻烦
弹性扩容或故障停机的情况下,原来的取模公式就会发生变化
某个redis机器宕机了,由于台数数量变化,会导致hash取余全部数据重新洗牌
哈希取余分区
定义:上面算法的优化,目的是当服务器个数发生变动时,尽量减少影响客户端到服务器的映射关系
算法构建一致性哈希环
服务器IP节点映射
key 落到服务器的落键规则
步骤:
一致性哈希算法的容错性
一致性哈希算法的扩展性
一致性哈希算法的数据倾斜问题
一致性哈希算法分区
一个集群的槽位上限是16384,集群建议最大主节点数1000
Redis没有使用一致性hash,而是引入哈希槽的概念,每个key通过CRC16校验后对16384进行取模来决定防止在哪一个槽里,集群的每一个节点负责一部分Hash槽
槽位slot
定义:使用Redis集群时我们会将存储的数据分散到多台redis机器上,这称为分片。简言之,集群中的每个Redis实例都被认为是整个数据的一个分片。
为了找到给定key的分片,我们对key进行CRC16(key)算法处理并通过对总分片数量取模。然后,使用确定性哈希函数,这意味着给定的key将多次始终映射到同一个分片,我们可以推断将来读取特定key的位置。
如何找到key的分片?
分片
方便扩缩容和数据分派查找
Redis集群不保证 强一致性,这意味着在特定的条件下,Redis集群可能会丢掉一些被系统收到的写入请求命令
槽位太大,发心跳太耗带宽
槽位越小,节点少的情况下,压缩比高,容易传输
redis作者不建议redis cluster节点数量超过1000个。 那么,对于节点数在1000以内的redis cluster集群,16384个槽位够用了。没有必要拓展到65536个
为什么最大槽是16384?
补充:
哈希槽分区(√)
分布式存储
集群算法-分片-槽位slot
常用命令
CRC16算法分析
集群cluster
8、高可用
Jedis Client是Redis官网推荐的一个面向Java客户端,库文件实现了对各类API进行封装调用
jedis
Lettuce是一个Redis的Java驱动包
lettuce(√)默认
客户端
RedisTemplate使用的是JDK序列化方式(默认),替换成jackson
封装RedisTemplate
9、SpringBoot集成
基于内存读写快
数据结构优化的好,也快
单线程IO多路复用和非阻塞IO
为什么当年说它快
作者认为Reds的主要瓶颈是内存或者带宽,不是CPU,而且单线程开发、调试、维护简单
即使使用单线程模型也并发的处理多客户端的请求,主要使用的是IO多路复用和非阻塞IO
当年为什么是单线程
时代变了大人,硬件发展快
3.x时代大key阻塞删除,大问题
为什么又要加多线程
4以前是单线程
4开始有一点多线程
6开始完全支持多线程
版本
IO多路复用+epoll函数使用,才是redis为什么这么快的直接原因,而不是仅仅单线程命令+redis安装在内存中。
那它为什么这么快呢?
总结:Redis自身出道就是优秀,基于内存操作、数据结构简单、多路复用和非阻塞I/O、避免了不必要的线程上下文切换等特性,在单线程的环境下依然很快;但对于大数据的 key删除还是卡顿厉害,因此在Redis 4.0引入了多线程unlink key/flushall async等命令,主要用于Redis 数捷的异步删除;而在Redis6/7中引入了I/0多线程的读写,这样就可以更加高效的处理更多的任务了,Redis只是将I/O读写变成了多线程,而命令的执行依旧是由主线程串行执行的,因此在多线程下操作 Redis不会出现线程安全的问题。Redis无论是当初的单线程设计,还是如今与当初设计相背的多线程,目的只有一个:让 Redis变得越来越快。
Redis单线程VS多线程
String大于10kb
list、hash、set、zset元素个数达到5000
什么是大key?
内存不均匀,迁移困难
删除时造成阻塞
网络流量阻塞
危害
日积月累
如何产生?
redis-cli --bigkeys
memory usage key
如何发现?
一般del,太大unlink
ltrim渐进删除
sscan获取少量,srem删除
使用zscan每次获取部分元素,再使用ZREMRANGEBYRANK命令删除每个元素
ZSET
hscan获取少量,然后hdel
如何删除?
BigKey
对于恶意攻击导致的缓存穿透,使用布隆过滤器
对于业务逻辑本身就不能避免的缓存穿透,将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
缓存和数据库中都没有的数据,而用户不断发起请求。
缓存穿透
设置热点数据永不过时
接口限流与熔断,降级
互斥加锁
缓存中可能过期了,没有,请求到数据库
缓存击穿
缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生
如果缓存数据库是分布式部署,将热点数据均匀分布在不同的缓存数据库中。
主从
哨兵
集群
redis高可用
上云
服务降级、限流
数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
缓存雪崩
读数据时,先读缓存,缓存没有的话,再读数据源,然后将数据放入缓存,再响应请求
写数据时,先写数据源,然后失效(而不是更新)掉缓存
缓存污染多数是由开发者更新缓存不规范造成的
缓存污染是指缓存中的数据与真实数据源中的数据不一致的现象
缓存污染
缓存问题
只读缓存
写数据库后同步写redis
同步直写
容许数据库和redis之间的数据存在一定的延时
异步缓写
读写缓存
QPS<=1000可以用,高并发不行!
先读redis,没有再度数据库,读完回写redis
redis没有,加锁,再查一次redis,没有,从数据库查,查完回写redis,返回
双检加锁
高QPS怎么解决?
具体实现
先更数据库,再更缓存×
先更缓存,再更数据库×
但是也会引申出其他问题
进阶 延迟双删
先删缓存,在更数据库×
先更新数据库,在删除缓存(主流的,但是不100%权威)
四种更新策略
消息中间件实现最终一致性
引申
认清一个现实:缓存和数据库之间的数据是不可能永远维持在一致性状态的
缓存双写一致性
大数据统计
定义:由一个初值都为零的bit数组和多个哈希函数构成,用来快速判断集合中是否存在某个元素
高效地插入和查询,占用空间少,返回的结果是不确定性+不够完美
能干嘛
实质就是一个大型位数组和几个不同的无偏hash函数(无偏表示分布均匀)。由一个初值都为零的bit数组和多个哈希函数构成,用来快速判断某个数据是否存在。
原理
高效地插入和查询,内存中占用bit空间小
不能删除元素。因为删除元素会导致误判率增加,因为hash冲突同一个位置可能存的东西是多个共有的,你删除一个的同事可能也把其他的删除了存在误判,不能精准过滤;有,是很有可能有;无,是肯定无
布隆过滤器
更进一步set NX PX + Lua
setNX(小公司够用)
WatchDog机制,默认情况下有30秒的锁持有时间,会进行续期操作
lua
细节:当我们加锁成功拿到redis分布式锁后,同时也异步启动一个线程,这个就是看门狗线程,它会判断对应key是否到过期时间的1/3,如果到了,自动执行expire key命令,对key进行续期,知道任务完成。比如过期时间是30秒,到第20秒后,会自动给key续命加到30秒,以此类推。当然,实现看门狗的任务通过lua脚本来完成
WatchDog机制
①单点失效
②锁续期
③可重入
④原子性
Redission解决了我们自己实现分布式锁时遇到的什么问题?
上redisson
基于RedLock实现分布式锁
分布式锁实现
对cpu友好,对内存不友好
取出的时候才会去检查
惰性删除
对内存友好,对cpu不友好
字面意思,定期扫描
定期删除
缓存过期策略
最近最少使用
lru
最不经常使用
lfu
将要过期
ttl
随机
random
volatile(设置了过期时间)
allkeys(所有key)
不淘汰
noeviction
缓存淘汰机制
缓存过期淘汰策略
简单动态字符串SDS:Redis会根据不同的键值选择使用不同的编码格式
压缩链表ziplist(特殊编码的双向链表)
哈希表hashtable
7之前
listpack(紧凑列表,它的特点就是用一块连续的内存空间来紧凑地保存数据)
7之后
ziplist(特殊编码的双向链表)
intset整数集合
skiplist(redis跳跃表并没有在单独的类(比如skplist.c)中定义,空间换时间)
数据类型底层的数据结构
10、进阶
Redis
0 条评论
回复 删除
下一页