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