redis最强最细内容
2021-10-06 11:11:22 0 举报
AI智能生成
redis最强最细内容
作者其他创作
大纲/内容
数据类型
五大基本类型
string
字符串,最基本的类型,底层基于字节数组
list
元素可重复,从两端压入或弹出元素
粉丝列表,文章的评论列表
set
元素不可重复,无序集合,底层基于hash
通过并集、交集、差集等,计算共同好友
hash
键值对集合,键为不可重复的string,值为任意类型
任何需要用到键值对的场景
zset
类似于set,但元素可根据分数排名
游戏排名
三种扩展类型
bitmap
类似于一个以比特位为单位的数组,数组的每个单元只能存储0和1,用少量空间记录大量状态(状态值只能为0或者1)
二值统计,例如布隆过滤器
hyperloglog
使用固定的大小(不超过12k),用来统计海量数据中不重复的元素个数
基于伯努利算法,不会存储原始基数数据,只做基数统计
统计结果是近似结果,有一定的误差
适用于uv统计
geo
提供地理位置的功能
附近的人列表
发布订阅功能
pub/sub
简单的发布/订阅功能,基于channel,实现消息的简单发布和订阅
没有ack机制,消息发布到channel后如果没有被消费就会丢失
没有持久化功能
stream
相当于pub/sub的升级版
提供持久化功能
参考kafka,提供消费者组功能
适用于需要简单使用消息列队的场景
数据结构
六大数据结构
整数集合(intset)
非重复整数的集合,可以保存类型为 int16_t 、 int32_t 或者 int64_t 的整数值
sds
字符数组,redis没有使用过c语言原生的字符数组,而是自己实现了sds类型
优点
可以O(1)时间复杂度计算字符串长度
支持动态扩容
链表(linkedlist)
底层是双端链表
字典(dict)
底层是哈希表
跳表(zkiplist)
每个节点有多级索引的链表
压缩列表(ziplist)
为了节约内存设计的数据结构,本质是一块连续内存的数据结构
数据类型和对应编码
REDIS_STRING
REDIS_ENCODING_INT
使用整数值实现的字符串对象
REDIS_ENCODING_EMBSTR
使用 embstr 编码的简单动态字符串实现的字符串对象
REDIS_ENCODING_RAW
使用简单动态字符串实现的字符串对象
REDIS_LIST
REDIS_ENCODING_ZIPLIST
使用压缩列表实现的列表对象
REDIS_ENCODING_LINKEDLIST
使用双端链表实现的列表对象
REDIS_HASH
REDIS_ENCODING_ZIPLIST
使用压缩列表实现的哈希对象
REDIS_ENCODING_HT
使用字典实现的哈希对象
REDIS_SET
REDIS_ENCODING_INTSET
使用整数集合实现的集合对象
REDIS_ENCODING_HT
使用字典实现的集合对象
REDIS_ZSET
REDIS_ENCODING_ZIPLIST
使用压缩列表实现的有序集合对象
REDIS_ENCODING_SKIPLIST
使用跳跃表和字典实现的有序集合对象
线程模型
单线程
6.0以前是单线程模型,其实就在主线程的一个for循环里面,完成io多路复用和处理命令的所有工作:
- 在主线程里面调用一个for循环,这个for循环会一直循环直到redis服务停止。
- 每次循环开始,调用io多路复用阻塞获取一个就绪的事件列表(其实就是网上说的那个队列)。
- 拿到事件列表后,遍历列表,依次调用每个事件的注册回调函数。
- 处理完所有事件列表后,重复第2个步骤。
多线程
6.0之后redis支持多线程,把网络io使用多个子线程(网络线程)去完成,处理命令等其他工作仍然在主线程上执行:
- 主线程通过IO复用等待网络连接上事件就绪。
- IO复用返回时,主线程收集所有有就绪事件的文件,并将这些文件派发多个网络线程,在完成派发文件之前,其他的网络线程不会进行数据读取。
- 派发完成后,网络线程开始数据读取,通过read系统调用将数据读取到应用层的缓存之中。(主线程阻塞)
- 当所有的网络线程完成数据读取之后,主线程开始依次处理这些数据,解析并执行命令,将命令的结果写入应用层的输出缓存之中。
- 当主线程完成所有查询请求的处理之后,网络线程开始进行数据的输出,通过write系统调用将数据写入内核的输出缓冲区之中,等待系统将数据发送到网络上。(主线程阻塞)
异步线程
主从复制子线程
备份子进程
bgsave子进程
bgrewriteaof子进程
后台线程
aof日志写操作
键值对删除和清空数据库
文件关闭
数据库键空间
redis中有多个数据库,每个数据库实际上是一个dict结构的数据集合
持久化
aof
aof是什么
把redis执行的所有写命令记录到日志文件
aof的特点
数据安全,详细记录到每一条写指令,但是文件体积大,数据恢复速度慢
aof持久化策略
Always
同步写回,每个命令都落盘,数据安全性高,但性能较差
EverySec
每秒写回,每秒落盘一次数据,宕机时会丢失1s数据,性能适中
No
操作系统控制写回,性能最好,但宕机时容易丢失大量数据
aof重写
aof文件过大的时候,把针对同一个key的多条命令合并成一条命令,只保留该key的最新状态的命令,最终使aof文件变小
aof过程
普通aof过程
新命令 —> aof缓存区 —> aof文件
aof重写过程
1、fork出一个bgrewriteaof子进程,拷贝出一份数据副本,并建立一个aof重写缓冲区。
2、然后两部分工作同时进行:
整个过程只有第3步是阻塞主线程的,这将 AOF 重写对性能造成的影响降到了最低。 2、然后两部分工作同时进行:
- 主线程处理完命令之后,把写命令写到aof缓冲区(然后刷新到旧的aof文件中,保证重写失败的时候旧文件不受影响),然后再写到aof重写缓冲区。
- 子进程把数据副本的数据重写到新的aof文件。
rdb
rdb是什么
按照一定的时间频率,定时将内存数据以快照的形式保存到硬盘上
rdb的特点
rdb文件体积小,便于快速恢复,启动效率高,但隔一段时间才持久化一次,存在宕机导致数据丢失的问题,数据安全性低
rdb过程
rdb触发
rdb触发可以分为手动触发和配置文件触发:
手动触发可以使用SAVE 和 BGSAVE 两个命令,都会调用 rdbSave 函数,但它们调用的方式各有不同:
手动触发可以使用SAVE 和 BGSAVE 两个命令,都会调用 rdbSave 函数,但它们调用的方式各有不同:
- SAVE 直接调用 rdbSave ,阻塞 Redis 主进程,直到保存完成为止。在主进程阻塞期间,服务器不能处理客户端的任何请求。
- BGSAVE 则 fork 出一个子进程,子进程负责调用 rdbSave ,并在保存完成之后向主进程发送信号,通知保存已完成。Redis 服务器在 BGSAVE 执行期间仍然可以继续处理客户端的请求。
普通rdb过程
1、fork出一个bgsave子进程,对fork出子进程那一刻的内存数据进行备份。
2、使用cow技术,kernel把父进程中所有的内存页的权限都设为read-only,然后子进程的地址空间指向父进程。当父子进程都只读内存时,相安无事。当其中某个进程写内存时,CPU硬件检测到内存页是read-only的,于是触发页异常中断(page-fault),陷入kernel的一个中断例程。中断例程中,kernel就会把触发的异常的页复制一份,于是父子进程各自持有独立的一份。
2、使用cow技术,kernel把父进程中所有的内存页的权限都设为read-only,然后子进程的地址空间指向父进程。当父子进程都只读内存时,相安无事。当其中某个进程写内存时,CPU硬件检测到内存页是read-only的,于是触发页异常中断(page-fault),陷入kernel的一个中断例程。中断例程中,kernel就会把触发的异常的页复制一份,于是父子进程各自持有独立的一份。
混合模式
1、rdb以一定的频率执行。
2、在两次rdb期间,使用aof日志记录这期间的所有命令操作。
3、下一次rdb的时候,清空aof日志。
2、在两次rdb期间,使用aof日志记录这期间的所有命令操作。
3、下一次rdb的时候,清空aof日志。
选择合适的持久化方式
可以忍受数分钟以内的数据丢失,使用rdb
追求高数据完整性,使用aof
同时开启aof和rdb,在重启redis的时候优先使用aof文件恢复数据,推荐
过期/淘汰策略
数据过期和删除
过期判断
在Redis内部,每当我们设置一个键的过期时间时,Redis就会将该键带上过期时间存放到一个过期字典中。
当我们查询一个键时,Redis便首先检查该键是否存在过期字典中,如果存在,那就获取其过期时间。然后将过期时间和当前系统时间进行比对,比系统时间大,那就没有过期;反之判定该键过期。
当我们查询一个键时,Redis便首先检查该键是否存在过期字典中,如果存在,那就获取其过期时间。然后将过期时间和当前系统时间进行比对,比系统时间大,那就没有过期;反之判定该键过期。
过期删除策略
定时删除:设置一个定时器,让定时器在该key的过期之后,立即执行对其进行删除的操作。
节省内存,但对cpu不友好,频繁的删除可能会影响性能
惰性删除:key过期后不立刻进行删除,需要该key时,我们在检查其是否过期,如果过期,我们就删掉它,反之返回该key。
性能最好,但对内存不友好,如果key长时间没有被访问,过期key会大量堆积
定期删除:每隔一段时间,我们就对一些key进行检查,删除里面过期的key。
折中方案,同时兼顾了cpu和内存,但是可能会存在业务上的问题,如果某个key的过期时间到了,但还没有执行定期删除,就会返回该key的值
Redis的过期删除策略是惰性删除和定期删除两种策略配合使用
惰性删除:Redis的惰性删除策略由 db.c/expireIfNeeded 函数实现,所有键读写命令执行之前都会调用 expireIfNeeded 函数对其进行检查,如果过期,则删除该键,然后执行键不存在的操作;未过期则不作操作,继续执行原有的命令。
定期删除:由redis.c/activeExpireCycle 函数实现,函数以一定的频率运行,每次运行时,都从一定数量的数据库中取出一定数量的随机键进行检查,并删除其中的过期键。定期删除每100ms执行一次。
数据淘汰
为什么要淘汰数据
如果我们的数据还没到过期时间,又往内存插入大量数据,超过设置的最大内存,就需要数据淘汰策略来处理新的数据
淘汰策略
不进行数据淘汰(noeviction)
进行数据淘汰
在设置了过期时间的数据中淘汰
volatile-ttl
在设置了过期时间的键值对,根据过期时间的先后进行删除,越早过期的越先被删除。
volatile-random
在设置了过期时间的键值对中,进行随机删除。
volatile-lru
在设置了过期时间的键值对,使用LRU算法筛选,删除最近最长时间不使用的key。
volatile-lfu
在设置了过期时间的键值对,使用LFU算法选择,删除最近使用次数最少的key。
在所有数据中进行淘汰
allkeys-random
从所有键值对中随机选择数据进行删除。
allkeys-lru
在所有数据中进行筛选,使用LRU算法,删除最近最长时间不使用的key。
allkeys-lfu
在所有数据中进行筛选,使用LFU算法,删除最近使用次数最少的key。
选择合适的淘汰策略
优先使用allkeys-lru策略,可以充分利用LRU这一经典缓存算法的优势,把最近最常访问的数据留在缓存中,提升应用的访问性能。
如果业务应用中的数据访问频率相差不大,没有明显的冷热数据区分,建议使用allkeys-random策略, 随机选择淘汰的数据就行。
如果你的业务中有置顶的需求,比如置顶新闻、置顶视频,那么,可以使用volatile-lru策略,同时不给这些置顶数据设置过期时间。这样一来,这些需要置顶的数据一直不会被删除,而其他数据会在过期时根据 LRU规则进行筛选。
事务
redis事务是什么
redis把多个命令缓存到事务队列,在事务提交后按顺序串行执行队列中的命令
一个完整的事务如下所示
> multi
OK
> incr star
QUEUED
> incr star
QUEUED
> exec
(integer) 1
(integer) 2
> multi
OK
> incr star
QUEUED
> incr star
QUEUED
> exec
(integer) 1
(integer) 2
redis事务命令
MULTI
标记一个事务块的开始
EXEC
执行所有事务块内的命令
DISCARD
取消事务,放弃执行事务块内的所有命令
WATCH
监视一个(或多个)key,如果在事务执行之前这个(或多个)key被其他命令所改动,那么事务将被打断
UNWATCH
取消 WATCH 命令对所有 keys 的监视
redis事务特性
Redis 事务保证了其中的一致性(C)和隔离性(I),但并不保证原子性(A)和持久性(D)
一致性:redis保证了事务一致性,这是最基本的特性
隔离性:redis事务中的所有命令都会序列化、按顺序地执行,所以不存在隔离级别的概念
原子性:redis 同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚
持久性:redis事务的本质只是用队列包裹起一些命令,本身并没有提供持久化的功能,事务的持久性依赖redis本身设置的持久化功能
集群方案
服务端方案
redis3.0之前,使用sentinel + 主库 + 从库
主从模式
主从模式是什么
为了支持读多写少的场景,用来实现读写分离的功能,在一个redis主库下挂载多个从库,主库向外提供读写功能,从库只提供读功能。
主从模式没有自动切换主从的功能,如果主库宕机了需要手动切换主库。
主从架构
1、一个主库下可以挂载多个从库,主库向外提供读写功能,从库向外只提供读功能,主库通过主从复制向从库同步写操作。
2、一个从库下也可以挂多个从库,用来解决主库下挂载的从库过多导致主库压力过大的问题。
2、一个从库下也可以挂多个从库,用来解决主库下挂载的从库过多导致主库压力过大的问题。
主从复制
replication buffer
主从复制过程中会用到的一个环形缓冲区,主库会记录自己写到的位置,从库则会记录已经读到的位置。缓冲区写满后,主库会继续写入,会覆盖掉之前写入的操作。
主从复制过程
1、当从库和主库建立master-slave关系后,会向主数据库发送PSYNC命令。
2、主库接收到PSYNC命令后会开始在后台保存快照(RDB持久化过程),并将期间接收到的写命令缓存到replication buffer。
3、快照完成后,主库先向从库发送RDB文件,从库会清空现有数据,然后加载RDB文件。
4、然后主库向从库发送replication buffer缓存的写命令,从库加载写命令。
5、后面主从间的数据同步都通过replication buffer来完成。
2、主库接收到PSYNC命令后会开始在后台保存快照(RDB持久化过程),并将期间接收到的写命令缓存到replication buffer。
3、快照完成后,主库先向从库发送RDB文件,从库会清空现有数据,然后加载RDB文件。
4、然后主库向从库发送replication buffer缓存的写命令,从库加载写命令。
5、后面主从间的数据同步都通过replication buffer来完成。
哨兵模式
哨兵模式是什么
为了监控主库和从库的健康状况并实现自动主从切换,在主从架构的基础上发展出哨兵模式,哨兵模式一般有三个作用
监控:判断主从下线
选主:选出新的主库
通知:通知从库与新主库实现数据同步,通知客户端与新主库连接
哨兵本身也是一个redis进程,但是不用来存储数据,只用来监控
哨兵本身也需要高可用,所以一般会搭建哨兵集群,搭建3个以上哨兵节点
哨兵模式不能保证数据不丢失,只能保证高可用
哨兵架构
1、客户端直接与sentinel集群连接,从sentinel集群获取主库和从库的地址信息。
2、sentinel集群包含3个以上的sentinel节点(一般是奇数个),少于一半的节点宕机了集群仍可以正常工作。
3、一个sentinel集群一般只监控一个主从集群。
2、sentinel集群包含3个以上的sentinel节点(一般是奇数个),少于一半的节点宕机了集群仍可以正常工作。
3、一个sentinel集群一般只监控一个主从集群。
哨兵集群的通讯
哨兵间的互相发现
哨兵间没有直接连接,那哨兵是怎么发现其他哨兵的存在并组成哨兵集群的呢?
哨兵只连接主库,通过redis的pub/sub机制,通过订阅名为__sentinel__:hello的channel,来获取其他哨兵的信息。
哨兵只连接主库,通过redis的pub/sub机制,通过订阅名为__sentinel__:hello的channel,来获取其他哨兵的信息。
哨兵发现从库信息
哨兵向主库发送info命令来获取从库的信息
哨兵通知从库
从库也是通过pub/sub机制,通过订阅其他的channel来获取哨兵通知
哨兵执行主从切换
1、哨兵进程会使用PING命令检测它自己和主库的网络连接情况,如果PING命令的响应超时了,先把它标记为“主观下线”。
2、然后向其他哨兵发送is-master-down-by-addr命令,有N/2 + 1个实例判断主库为“主观下线”,即判断为“客观下线”。
2、然后向其他哨兵发送is-master-down-by-addr命令,有N/2 + 1个实例判断主库为“主观下线”,即判断为“客观下线”。
判断客观下线后,哨兵间会进行一次选举,选出一个哨兵执行主从切换
然后开始选举新的主库,选主的规则是依次进行三轮打分,这三个规则分别是从库优先级、从库复制进度以及从库ID号。只要在某一轮中,有从库得分最高,那么它就是主库了。
- 从库优先级:从库配置slave-priority,优先级高的当选主库。
- 从库复制进度:主从同步有一个repl_backlog_buffer缓冲区,主库会用master_repl_offset记录当前的最新写操作,从库会用slave_repl_offset这个值记录当前的复制进度。slave_repl_offset最接近master_repl_offset的当选主库。
- 从库ID号:从库ID小的当选主库。
redis3.0之后,使用redis cluster
redis cluster模式
cluster模式是什么
无论是主从还是哨兵模式,都是对单个节点进行写入,在高并发的应用场景里,单节点的写入容易成为性能瓶颈。
在redis3.0之前,要实现分布式写入,只能通过第三方的客户端sharding的技术。为了满足这个需求,redis3.0后推出了官方的cluster集群技术方案,实现多节点的分布式读写和存储。
cluster架构
1、多个节点组成cluster,所有节点彼此关联通过gossip协议进行通讯,每个节点都保存整个cluster的状态。
2、cluster定义了16384个hash slot,每个节点保存一部分hash slot,写入的数据会根据算法【CRC16(key) mod 16384】,分配到16384个hash slot的其中一个。
3、为了保持高可用性,每个节点可以挂一个到多个从节点,从节点负责从主节点同步数据,当主节点挂了之后,从节点会自动切换为主节点。
2、cluster定义了16384个hash slot,每个节点保存一部分hash slot,写入的数据会根据算法【CRC16(key) mod 16384】,分配到16384个hash slot的其中一个。
3、为了保持高可用性,每个节点可以挂一个到多个从节点,从节点负责从主节点同步数据,当主节点挂了之后,从节点会自动切换为主节点。
注意
1、不支持多个key的操作,keys命令也只会显示连接的当前节点的key数
2、只支持1个db:db0
3、客户端必须以 cluster 的协议与 redis 进行通讯
2、只支持1个db:db0
3、客户端必须以 cluster 的协议与 redis 进行通讯
客户端方案
客户端sharding(codis)
codis是什么
codis是一种客户端sharding的技术,是redis cluster技术出来前非官方的redis分布式部署方案
codis的部署过程较为繁琐,在redis cluster技术出来后,作者已经停止维护
codis架构
常见问题
缓存异常问题
缓存雪崩
描述
大量数据集中在某个时间段过期,或者由于redis服务器宕机,造成大量的访问直达数据库
解决方案
给缓存数据的过期时间加上小的随机数,避免瞬间大量过期
搭建高可用集群
降级限流
缓存击穿
描述
少量key扛着高并发访问,这些热门key在过期瞬间,大量针对该点的访问直达数据库
解决方案
不给热点数据设置过期时间
加互斥锁,保证单个key只有一个线程去数据库查询,其他线程负责等待
缓存穿透
描述
访问redis和数据库中都没有的数据,造成所有的的访问都到达数据库,一般出现在恶意攻击的场景
解决方案
接口增加参数校验,拦截无效请求
缓存无效key,如果缓存和数据库都查不到就在redis里面设置一个空值
使用布隆过滤器进行快速判断
脑裂问题
描述
redis主库被阻塞一段时间,超过了哨兵检测心跳的的最大时长,哨兵判断主库客观下线,开始新一轮选主,这时候主库恢复正常。在旧主库恢复正常后,到新主库被选举出来之前,这段时间内集群中同时存在两个主库,客户端不知道写入到哪个主库。
解决方案
redis 提供了两个配置项来限制主库的请求处理:
- min-slaves-to-write:主库能进行数据同步的最少从库数量
- min-slaves-max-lag:主从复制时,slave连接到master的最大延迟时间
redis和数据库不一致问题
redis缓存方案有只读缓存和读写缓存两种
读写缓存:每次更新数据,都同时更新数据库和redis
只读缓存:每次更新数据的时候,直接删除redis的数据,在读取数据的时候,再从数据库更新到redis。这里又有两种方案
先删除缓存,再更新数据库
先删除数据库,再删除缓存
优先使用先更新数据库,再删除缓存的策略
解决方案
延时双删策略+TTL
1. 先删除缓存
2. 再写数据库
3. 异步删除缓存,可以通过休眠500毫秒或者mq机制完成
缓存数据都加过期时间
2. 再写数据库
3. 异步删除缓存,可以通过休眠500毫秒或者mq机制完成
缓存数据都加过期时间
异步更新缓存(基于订阅binlog的同步机制)
MySQL binlog增量订阅消费+消息队列+增量数据更新到redis
1、读数据:读操作通过Redis
2、写数据:增删改都是操作MySQL
3、异步更新:MySQ更新的数据通过binlog更新到Redis
类似MySQL的主从备份机制,可以使用canal来解析binlog,解析后推送消息到kafka
1、读数据:读操作通过Redis
2、写数据:增删改都是操作MySQL
3、异步更新:MySQ更新的数据通过binlog更新到Redis
类似MySQL的主从备份机制,可以使用canal来解析binlog,解析后推送消息到kafka
redis为什么这么快
纯内存操作
数据结构专门优化过
单线程,使用IO多路复用技术
Copy-On-Write
1、在需要进行rdb或aof的时候,使用COW技术fork出子线程的时候,子线程和父线程共享物理内存空间,并把所有内存页设置成read-only。
2、当父进程写数据的时候,在写入数据的内存页上触发页中断异常,这时候主进程再把要修改的数据写入到新的物理页,子进程仍然指向
旧的物理页数据,把旧数据进行备份。
2、当父进程写数据的时候,在写入数据的内存页上触发页中断异常,这时候主进程再把要修改的数据写入到新的物理页,子进程仍然指向
旧的物理页数据,把旧数据进行备份。
解决慢查询
可能存在的阻塞点
大集合全量查询和聚合操作
bigkey删除
清空数据库
AOF日志同步写到磁盘
从库加载RDB文件
性能慢排查方案
1. 获取Redis实例在当前环境下的基线性能。
2. 是否用了慢查询命令?如果是的话,就使用其他命令替代慢查询命令,或者把聚合计算命令放在客戶端做。
3. 是否对过期key设置了相同的过期时间?对于批量删除的key,可以在每个key的过期时间上加一个随机数,避免同时删除。
4. 是否存在bigkey?对于bigkey的删除操作,如果你的Redis是4.0及以上的版本,可以直接利用异步线程 机制减少主线程阻塞;如果是Redis 4.0以前的版本,可以使用SCAN命令迭代删除;对于bigkey的集合查询和聚合操作,可以使用SCAN命令在客戶端完成。
5. RedisAOF配置级别是什么?业务层面是否的确需要这一可靠性级别?如果我们需要高性能,同时也允许数据丢失,可以将配置项no-appendfsync-on-rewrite设置为yes,避免AOF重写和fsync竞争磁盘IO资源,导致Redis延迟增加。当然,如果既需要高性能又需要高可靠性,最好使用高速固态盘作为AOF日志的写入盘。
6. Redis实例的内存使用是否过大?发生swap了吗?如果是的话,就增加机器内存,或者是使用Redis集 群,分摊单机Redis的键值对数量和内存压力。同时,要避免出现Redis和其他内存需求大的应用共享机 器的情况。
7. 在Redis实例的运行环境中,是否启用了透明大⻚机制?如果是的话,直接关闭内存大⻚机制就行了。
8. 是否运行了Redis主从集群?如果是的话,把主库实例的数据量大小控制在2~4GB,以免主从复制时,从库因加载大的RDB文件而阻塞。
9. 是否使用了多核CPU或NUMA架构的机器运行Redis实例?使用多核CPU时,可以给Redis实例绑定物理核;使用NUMA架构时,注意把Redis实例和网络中断处理程序运行在同一个CPU Socket上。
2. 是否用了慢查询命令?如果是的话,就使用其他命令替代慢查询命令,或者把聚合计算命令放在客戶端做。
3. 是否对过期key设置了相同的过期时间?对于批量删除的key,可以在每个key的过期时间上加一个随机数,避免同时删除。
4. 是否存在bigkey?对于bigkey的删除操作,如果你的Redis是4.0及以上的版本,可以直接利用异步线程 机制减少主线程阻塞;如果是Redis 4.0以前的版本,可以使用SCAN命令迭代删除;对于bigkey的集合查询和聚合操作,可以使用SCAN命令在客戶端完成。
5. RedisAOF配置级别是什么?业务层面是否的确需要这一可靠性级别?如果我们需要高性能,同时也允许数据丢失,可以将配置项no-appendfsync-on-rewrite设置为yes,避免AOF重写和fsync竞争磁盘IO资源,导致Redis延迟增加。当然,如果既需要高性能又需要高可靠性,最好使用高速固态盘作为AOF日志的写入盘。
6. Redis实例的内存使用是否过大?发生swap了吗?如果是的话,就增加机器内存,或者是使用Redis集 群,分摊单机Redis的键值对数量和内存压力。同时,要避免出现Redis和其他内存需求大的应用共享机 器的情况。
7. 在Redis实例的运行环境中,是否启用了透明大⻚机制?如果是的话,直接关闭内存大⻚机制就行了。
8. 是否运行了Redis主从集群?如果是的话,把主库实例的数据量大小控制在2~4GB,以免主从复制时,从库因加载大的RDB文件而阻塞。
9. 是否使用了多核CPU或NUMA架构的机器运行Redis实例?使用多核CPU时,可以给Redis实例绑定物理核;使用NUMA架构时,注意把Redis实例和网络中断处理程序运行在同一个CPU Socket上。
分布式问题
分布式锁实现方式
数据库
利用主键的唯一性约束实现
1、唯一索引
2、select … for update排他锁
1、唯一索引
2、select … for update排他锁
优点:实现简单
缺点:性能差,没有失效时间
zookeeper
基于临时有序节点
1、客户端获取锁,在zk指定目录下创建一个临时有序节点
2、判断自己创建的节点序号是不是最小的,如果是最小的就获取锁成功,执行业务。
3、如果不是最小的,就监听目录下节点删除事件,每次删除节点判断自己节点序号是否最小,如果是就获取锁成功,否则就继续监听。
4、获取锁成功的节点执行业务后,删除节点,表示释放锁。
客户端一般使用Curator
1、客户端获取锁,在zk指定目录下创建一个临时有序节点
2、判断自己创建的节点序号是不是最小的,如果是最小的就获取锁成功,执行业务。
3、如果不是最小的,就监听目录下节点删除事件,每次删除节点判断自己节点序号是否最小,如果是就获取锁成功,否则就继续监听。
4、获取锁成功的节点执行业务后,删除节点,表示释放锁。
客户端一般使用Curator
优点:由于临时节点在客户端断开后自动删除,可解决死锁问题。
缺点:性能不如redis锁,自身实现复杂,一般需要借助第三方客户端
redis
基于setnx命令,现在使用set命令加上nx参数
1、setnx设置一个key,表示获取锁成功,其他线程调用setnx会返回null,需要不断重试
2、处理完业务后,del key表示释放锁
为了解决锁误删问题,可以在把value设置为线程id,在删除前判断value是否等于当前线程id,但是没办法解决锁超时问题
1、setnx设置一个key,表示获取锁成功,其他线程调用setnx会返回null,需要不断重试
2、处理完业务后,del key表示释放锁
为了解决锁误删问题,可以在把value设置为线程id,在删除前判断value是否等于当前线程id,但是没办法解决锁超时问题
优点:性能高
缺点:存在锁超时和锁误删问题,主从切换导致的锁丢失问题
redisson
redisson解决了锁超时、锁误删和死锁问题,redisson使用lua脚本,保证获取锁是原子操作
1、判断当前锁是否存在,不存在就设置key,key为客户端id,value为锁次数,返回null
2、如果锁存在,判断key是否等于当前客户端id,相等就把value加1(支持重入锁),返回null
3、其他情况,返回一个数值,表示key的剩余生存时间
返回 null 说明加锁成功,返回一个数值则说明获取锁失败,该数值为锁的剩余存活时间。此时获取锁失败的线程通过pub/sub机制等待获取锁释放的消息,并不断重试;如果在过了超时时间都没有收到消息,就返回获取锁失败
1、判断当前锁是否存在,不存在就设置key,key为客户端id,value为锁次数,返回null
2、如果锁存在,判断key是否等于当前客户端id,相等就把value加1(支持重入锁),返回null
3、其他情况,返回一个数值,表示key的剩余生存时间
返回 null 说明加锁成功,返回一个数值则说明获取锁失败,该数值为锁的剩余存活时间。此时获取锁失败的线程通过pub/sub机制等待获取锁释放的消息,并不断重试;如果在过了超时时间都没有收到消息,就返回获取锁失败
优点:使用简单,使用watch dog解决锁超时和锁误删问题
缺点:存在主从切换导致的锁丢失问题
redlock
1、顺序向五个节点请求加锁,根据一定的超时时间来推断是不是跳过该节点
2、三个节点加锁成功并且花费时间小于锁的有效期,认定加锁成功
2、三个节点加锁成功并且花费时间小于锁的有效期,认定加锁成功
优点:解决了主从切换导致的锁丢失问题
缺点:实现复杂
存在问题
锁超时和锁误删问题
锁超时问题:线程A持有锁,但是执行时间超过了过期时间,结果线程A还没执行完就释放了锁,线程B获取到锁,这时候线程A和B同时获取到锁,造成锁的互斥性问题。
锁误删问题:过了一段时间线程A执行完毕后del key删除锁,而且线程B此时还在执行,造成线程B没有执行完毕锁就被提前释放的问题。
锁丢失问题
加锁的key只存在一个节点上,如果该节点宕机,存在锁丢失的问题。
收藏
收藏
0 条评论
下一页