目录
数据类型
key过期相关
maxmemory相关
rehash相关
持久化
RDB
AOF
主从复制
哨兵
锁
事务
分片集群
缓存和数据库一致性
缓冲区
性能问题排查和调优
其它
一个数据类型都对应了很多种底层数据结构。以List为例,什么情况下是双向链表,反之又在什么情况下是压缩列表呢?还是说是并存状态?
1、Hash 和 ZSet 是数据量少采用压缩列表存储,数据量变大转为哈希表或跳表存储
2、但 List 不是这样,是并存的状态,List 是双向链表 + 压缩列表
请问Redis里面的惰性删除指的是什么,我看网上说是在访问到这个key的时候才执行删除才是惰性删除,但是这个专栏里面又表达的像惰性删除其实就是异步删除?
删除过期key是定时清理+懒惰删除。
懒惰删除默认是在主线程删除,并释放key内存的。
但在4.0+版本又做了优化,释放key内存可以放到了异步线程中去做了(lazy-free),目的是可以减少主线程释放内存的耗时,提升主线程处理性能。
也就是说4.0之前,懒惰删除就是同步删除。4.0优化后,懒惰删除就是同步删除全局hash表的键值对+子线程程执行释放内存,但需要开启lazy-free配置才生效。
有百万的key在一段时间过期,比如2个小时,如果应用订阅了key失效事件,在两个小时内,这些通知事件都会发出来吗?
不一定,如果没有请求到过期的key,那么这些过期key会是定时任务做清理,有可能key已经到了过期时间了,但还没有被扫描到,自然也就不会立即被清理掉,可能存在一定延迟。
Redis的过期策略有定期删除和惰性删除,定期删除每隔一段时间会取一些设置了过期时间的key检查是否过期,过期就删除,但会存在有的设置了过期时间的key定期删除策略没有检查到,那就会一直存在。所以有了惰性删除,在获取某个key的时候,redis会检查下,如果这个key设置了过期时间那么是否已经过期,如果过期了,就删除。master节点是这种情况,如果此时读取的是slave呢,slave针对这个过期key会怎么处理呢?
slave 也会记录 key 的过期时间,如果 key 已过期,在 slave 上查询到这个 key 时,slave 给客户端返回 null,但不删除这个 key。
slave 什么时候删除呢?
master 删除过期 key 时,会发 DEL 给 slave,此时 slave 才会删除这个 key。
如果有一批 key 已经过期,但还未被Redis清理掉,问:
- 此时生成的RDB会包含这些过期key吗?
- Redis加载这个RDB,会包含这些过期key吗?还是直接过滤?
Redis 5.0 以下版本,规则如下:
- Redis生成RDB,默认会过滤已过期的key,这么做的目的是,缩小RDB的体积。
- Redis加载RDB,主库和从库逻辑不一样:
- 主库加载 RDB,会自动忽略加载已过期的key。
- 从库加载RDB,不忽略,RDB有啥就加载啥。
Redis 5.0 以上版本,做了改动,规则如下:
加载RDB的逻辑不变,但生成RDB的逻辑变了。
5.0 以上版本,Redis 生成 RDB 时不忽略过期key了。(具体可参见 rdb.c -> rdbSaveKeyValuePair方法)。
为什么改成了这样?
这是因为,Redis 4.0对主从同步进一步做了优化。
主从切换后,旧主库可以和新主库直接增量同步,而不用走全量同步,避免了全量同步的成本。
但是,存在这样一种场景,会导致这个过程有问题:
主库大量key已过期,但还未清理,这时添加一个从库上来,此时主库RDB会过滤过期key,从库走全量同步拿到的key就不是最全的。
此时,发起了主从切换,因为4.0对主从同步做了优化,允许增量同步数据,所以,旧主库此时作为从库开始增量同步数据。
但是,此时从库有大量key未被清理,而主库加载RDB时也没有拿到这些过期key,所以没办法发DEL给从库,这时从库的这些key就遗留在内存中了,造成了内存泄露。
所以Redis针对这个问题,在生成RDB时,只能把全量数据写进去,不能做任何过滤逻辑。
主从库数据不一致的原因是否还有exipre命令同步延迟?expire 命令在主库、从库上执行时,会存在过期时间不一致的情况吗?
会的。
expire执行时,是以当前机器的时钟为基准,开始计算过期时间的。
可能存在这样的场景:slave因为某种原因卡住了一会,那master发给slave的expire命令,在slave上执行时计算的过期时间就会晚一些。
不过这种情况其实还好。
如果在master上查这个key,那master就会马上清理它,然后给slave发DEL也让slave清理掉。
最怕的是,slave时钟走得快,slave很多key本来没过期,却被认为过期了,这就比较麻烦了。如果此时主从切换,那新master会开始清理过期key,严重会引发缓存雪崩。
我的Redis的maxmemory是设置4G,但我的服务器是8G,那么在Redis启动时,是直接分配给4G内存空间给Redis还是按需分配呢?如果我maxmemory是4G,但我的Redis数据是5G,那么Redis启动时是加载4G数据,还是加载4G数据+1G SWAP?
1、没数据的Redis,启动时按需申请内存,不会一下申请maxmemory内存的。
2、设置了maxnemory,Redis必定不会让你超过maxmemory(5.0版本以下,主从都不会超过maxmemory)
3、5.0以上版本,Redis新增了一个配置项 replica-ignore-maxmemory yes:从库超过maxmemory也不淘汰数据,以此保证主从库的一致性,否则从库提前超过maxmemory就开始淘汰数据,会导致与主库不一致。
如果不超过maxmemory,但是系统内存快满了,这种会刷到swap吧?
开了swap,会把内存数据换到swap上,如果不开swap,Redis进程会被OOM。
Redis里的hash容量到一定程度的时候,会做渐进式的rehash,Redis有没有提供一些可以配置,让我可以指定hash的大小,这样可以防止hash在较大的情况下,发生多次rehash的情况?
没有配置,hash扩容是定好的规则。到达什么阈值,开始扩容,没办法自己控制。
Redis如何保证哈希表在扩容时的原子操作呢?如果把数据复制到新哈希表失败的话,原有的哈希表数据岂不是还存在,这个时候恢复后数据会向哪张哈希表写数据?
如果第一次拷贝后,哈希表2的哈希桶1再次超过了装载因子,但哈希表1中哈希桶2还有数据。这种情况也会去扩容哈希表1么?那原本没有渐进处理的数据是重新rehash放进来还是不会变?当然这种情况可能比较极端了
1、都是内存操作,内存没问题一般不会失败。
2、下一次rehash想要开始,必须等上一次完成
渐进式rehash是将拷贝分摊到客户端的多次请求上,是不是可以理解为请求命中了哈希表1的key,就把表1的数据rehash再分配到表2。那万一客户端迟迟不命中表1的某个key,表1的数据岂不是一直都不会迁移到表2?
每处理客户端请求,如果需要rehash,每次请求都会触发迁移数据,也就是说只要有请求进来,即使没有命中哈希表1的key,也会触发迁移一部分数据到哈希表2。
扩容的时候,假如哈希表1大小5g,那么哈希表2假如为原来的1.5倍,就是7.5g,如果Redis内存限制在15g,是不是会有一些内存因为扩容机制,没有得到利用呢?
如果限制Redis内存15g情况下,哈希表1已经到了10g,发现需要扩容,扩容为两倍,发现内存不够的情况下,应该怎么处理呢?
扩容是为了搞一个大一点的哈希桶,所以申请的内存只是新哈希桶所占内存,并不会申请新数据内存。
那即使每次申请的内存容量小,但总有个时间点,会发现内存不够了,但是还没到达限制的内存点,所以就干脆不申请了,继续在旧的哈希表里插入数据,最后通过lru再释放内存。
不是。如果实例设置了maxmemory最大10g内存,已经用了9.9g,扩容申请新的哈希桶,需要200m,Redis会照常申请,开始rehash扩容。
然后Redis发现,实例现在占用的内存,超过了maxmemory,那Redis会触发数据淘汰,开始淘汰删除key,直到把内存降到10g以下,也就是说,在这期间需要淘汰100m的key。
淘汰100m的key,有什么后果?后果就是,会阻塞住整个Redis(淘汰删除key是在主线程中执行的),这里是一个坑,需要格外注意rehash申请内存,撞上了正好超过maxmemory上限,就会引发此问题。
这个问题在3.x-6.0版本一直存在,好像在6.0+以上版本,才修复了这个问题,具体可以查阅6.0版本更新记录确认一下。
如果内存超过了maxmemory,但没有设置淘汰策略,会发生什么?
新写入的数据,会给客户端返回写入失败。
那什么情况下,Redis才会OOM?
只有机器内存无法申请到的时候:
- 整个机器内存不够了
- NUMA架构下,某一个内存节点内存不够,又配置的不允许去其他内存节点申请内存
Redis在RDB或AOF重写时是不允许进行rehash的,如果现在进行rehash,又触发了RDB或AOF重写条件会怎么处理呢?
这个问题非常好。
1、如果正在RDB和AOF rewrite,Redis默认会关闭rehash,等RDB和AOF rewrite完成后,Redis再打开rehash开关。但是有一个例外,如果全局哈希表冲突概率已经很大(有一个阈值),超过这个阈值,也会强制触发rehash(不管是否在RDB和AOF rewrite)
2、如果正在rehash,是允许RDB和AOF rewrite的。
3、为什么RDB和AOF rewrite期间,默认不允许rehash?因为RDB和AOF rewrite时,父进程rehash需要申请新的哈希表,这会造成父进程大量COW,影响父进程的性能
4、为什么rehash期间,允许RDB和AOF rewrite?因为rehash已经开始,说明新的哈希表内存已经申请完成了,之后的rehash过程只是迁移哈希桶下的数据指针,不涉及到内存申请了,所以RDB和AOF rewrite没影响。
RDB
为啥RDB 要 fork 子进程而不是线程?
1、先想一下RDB的目的是什么?就是把内存数据持久化到磁盘上,而且只持久化截止某一时刻的数据即可,不关心之后的数据怎么改(内存快照)
2、性能:如果用子线程做的话,主线程写,其他线程读,然后子线程数据写磁盘,有资源竞争,需要加锁,加锁会降低Redis性能,而且在实现上很复杂,成本高。
3、基于以上考虑,fork一个子进程来搞,最经济,成本也最低。因为fork子进程,操作系统把这些事都做好了,有内存快照数据,没有锁竞争,子进程怎么写磁盘也不会影响父进程,还有COW不影响主进程写数据,一举多得。
如果上一次生成RDB快照还没执行完,又触发了持久化策略,这个时候是顺序执行等上一次持久化完成?还是并行处理?
等上一次RDB执行完,才能触发执行下一次RDB。
关于Copy On write问题:数据持久化fork子进程时,子进程不会一次copy所有数据,而是在修改时触发Copy On write。假设主线程中有1000条数据,fork创建子进程后,主线程有请求新增了100条,修改了200条,这些内存是如何在主进程和子进程分配的?
1、首先要理解 Copy On Write 含义:即写时复制,谁写谁复制
2、fork子进程,此时的子进程和父进程会指向相同的地址空间,当父进程有新的写请求进来,它想要修改数据,那么它就把需要修改的key的内存,拷贝一份出来,再修改这块新内存的数据,此时父进程内存地址就会指向这个新申请的内存空间
3、在这期间,子进程不会修改任何数据,所以不会分配任何新的内存,它依旧指向父进程那些数据的内存地址空间,这个过程是操作系统层面做好的。
那fork期间会阻塞父进程吗?为什么会阻塞?
1、fork完成之前,会阻塞父进程,主要是父进程需要拷贝进程中的内存页表给子进程,每个进程都要有自己的内存页表,所以这个父子进程无法共享,必须要拷贝一份
2、拷贝内存页表也需要花费时间,进程占用的内存越大,拷贝时间越久
RDB写入的时候,通过主线程fork出bgsave子进程时,进行写入RDB文件,此时主线程也可以接受的写操作,那么主线程接收新的写操作,bgsave子进程还会再把这个数据,写入到RDB文件吗?
不会。RDB的目的是,只要一份内存快照,即只要fork那一瞬间,父进程所拥有的数据,fork完成后子进程指向父进程的所有内存数据地址空间,所以就与父进程共享数据了,此时子进程把这些数据scan出来,持久化到磁盘就可以了,不需要关心父进程有没有写入新数据。
子进程做RDB期间,父进程写入新数据,父进程做Copy On Write申请新的内存,那子进程完成RDB后,进程退出,内存回收是怎样的?
子进程退出时,如果它指向的内存数据,没有被父进程修改过(对于这块数据,父进程没有做COW),那么这块内存数据,还是归父进程所有,子进程不会回收。
如果在子进程RDB期间,父进程有新数据写入或修改,对一部分key的内存做了COW,那这些key的内存,父子进程各自独立,子进程退出时,就会回收它指向的这些内存空间。
AOF
AOF 中开启 always 刷盘策略也会存在数据丢失吗?
可能会。Redis 是先操作内存,后写AOF磁盘日志。比如 Redis 内存执行完了,去刷盘的时候宕机了就会导致数据丢失。
Redis在bgsave或rewriteAOF期间引起CPU飚高,有应对方案吗?
这是正常情况,在这期间会消耗比较多的CPU,如果实例比较大,持续时间越久。因为在这期间,子进程需要把进程中的所有数据都扫出来,然后写到磁盘上,这个扫描过程是需要耗费CPU资源的。
AOF假如写在内存A上,然后fork子进程进行AOF重写,因为copy on write机制,AOF重写子进程也是指向内存A。如果此时有新的key写入,父进程将其写入在新拷贝的内存B上,然后父子进程的内存逐渐分离。AOF重写子进程写完后将其替换AOF日志文件,然后释放内存A。父进程随后就一直使用内存B,这样理解对吗?
正确。
AOF重写的时候,如果重写缓冲区满了,怎么处理?是不是直接放弃本次的重写了?
AOF重写缓冲区不会满,是个链表,只要内存不超过设置的maxmemory。
如果超过maxmemory,执行配置的淘汰策略。
AOF配置为每秒刷盘,有可能阻塞Redis,影响性能吗?
有可能的。
AOF 配置为每秒刷盘,具体逻辑是这样的:
1、Redis 主线程把命令写到 AOF page cache(调用 write 系统调用)
2、Redis 后台线程每间隔 1 秒,把 AOF page cache 持久化到磁盘(调用 fsync 系统调用)
如果 2 执行时,迟迟没有成功,那么 1 执行时就会阻塞住,原因是在操作同一个 fd 时,fsync 和 write互斥的,一方必须等待另一方完成。
步骤 2 执行不成功的原因在于:机器的磁盘 IO 负载非常高(可能有别的程序在疯狂写磁盘,把磁盘带宽占满了),此时 1 在执行时,就会阻塞等待,从而影响到了主线程,进而影响整个 Redis 性能。
具体可参见 Redis 源码 aof.c,搜索:Asynchronous AOF fsync is taking too long (disk is busy?). Writing the AOF buffer without waiting for fsync to complete, this may slow down Redis.
开启混合持久化,在恢复时是先加载 RDB 文件,然后再加载 AOF 吗?
准确来说,混合持久化只是对 AOF rewrite 做的优化。
因为 AOF 写入很长时间后,文件体积会变得非常大,为了优化文件大小,Redis提出了 AOF rewrite,即文件增长一定阈值(可配置),则自动重写这个 AOF 文件,以达到缩小文件体积的目的。
后来到 Redis 4.0,又进一步做了优化,就是混合持久化。
就是在 AOF rewrite 时,先写一个 RDB 进去,再把在这期间产生的写命令,追加到 AOF 文件中。
在 AOF 文件中,前面是一个 RDB 格式的数据,后面是 AOF 格式的数据。
这样一来,这个 AOF 文件体积就更小了。
Redis【混合持久化】,具体持久化的数据格式是怎样的?
一个 Redis 要想开启混合持久化,必须这样配置:1.开启AOF 2.配置AOF rewrite 3.配置AOF rewrite 开启混合持久化,缺一不可。
重点:混合持久化是对 AOF rewrite 的进一步优化。
然后,从最简单的说起,一个空Redis,开始写入数据,数据做持久化,会是这样:
- 假设写入了 100 条命令,那 AOF 中记录的就是这 100 条命令
- 假设现在触发了 AOF rewrite 的阈值,需要对 AOF 进行瘦身,因为开启了【混合持久化】,那 Redis 会扫描整个实例,把整个实例中的数据,生成一个 RDB,写到 AOF 中 (RDB 是二进制数据)
- 在写 RDB 到 AOF 文件期间,Redis 依旧会收到写操作,假设这段时间收到了 10 条写命令,那这 10 条命令会等 RDB 写完成后,再依次追加到 AOF 中,此时 AOF 中的数据就是 RDB + 10 条写命令
- 之后 Redis 继续收到写操作,假设收到 5 条写命令,那也依次追加到 AOF 中,此时 AOF 中就是一个 RDB + 10 + 5 条写命令
- 继续写操作,继续追加 AOF,如果此时 AOF 又触发到了需要 rewrite 的阈值,那就继续走 2-3-4 步骤。
问:
- 如何区分rdb和aof的分界?
- 再次触发aof重写时候,如何处理的?会不会生成新的文件?rdb+10+5已经存在的数据如何处理
答:
- RDB 文件是有结束标志位的,读到标志位,就知道读完RDB了。
- 只要触发 AOF rewrite,就会生成新的 AOF 文件,替换旧的
问:为什么要有混合持久化?
答:
- AOF rewrite的目的是压缩AOF体积,防止AOF持续膨胀。
- 普通的AOF rewrite,就是重新扫描整个实例数据,然后转换成写命令,重写一遍到AOF中。但这样文件可能还是很大,所以4.0就提出了混合持久化,进一步做了优化,就是上面说的,其目的可以继续压缩AOF体积,加快恢复速度。
主从节点全量复制,会同步RDB文件。然后主节点继续接受写请求,这些写命令会存到复制缓冲区。RDB传输完成后,再把复制缓冲区的命令,发到各个从节点执行,那在发复制缓冲区里的命令时如果主节点又接受了写请求,这些新的写请求是怎么发给从节点的?
全量同步过程:
1、slave向master发起同步请求,slave会告知master自己的offset,如果是第一次,offset=-1表示需要全量同步
2、slave连上了master,master会给slave分配一个client buffer(这个就是复制缓冲区replication buffer)
3、master fork子进程dump RDB文件,dump完成后告诉父进程,父进程把RDB发给slave(通过slave的socket发过去)
4、在master dump RDB期间,master所有写命令,都写到这个client buffer(replication buffer),先积压在这
5、RDB发送完成,master把client buffer(replication buffer)积压的命令,都发给slave(写slave socket),这样主从数据就追平了
6、之后master写请求,还是都写到client buffer(replication buffer),然后实时传播给slave,主从保持一致
那backlog buffer干嘛的?
1、只要master下面有slave存在,master就会分配一块内存,这块内存就是backlog buffer
2、只要master有写命令进来,也都会写一份到backlog buffer里,但这个buffer是固定大小的环形缓冲区,写满就会覆盖旧数据
3、它的作用在于,主从复制意外中断了,slave再次向master发起同步请求,slave会告知master要从哪开始复制(offset),master在backlog buffer里找,数据能否接得上,接得上就把差异的增量数据发给slave,发的过程也是先写到上面所说的client buffer(replication buffer),然后写slave socket达到主从同步
4、如果接不上,走全量同步
重点:
1、replication buffer:别纠结这个名字,它就是master给client分配的一个buffer,因为是用于主从同步,所以大家才这么叫它,它的意义在于master和slave保持数据一致的传播通道
2、backlog buffer:主要作用在于,主从断开后,是否能增量同步
如果RDB很大,传输给从库时间很长,那replication buffer会不会积压很多数据,那这个buffer内存会不会无限大?需不需要清理?
不会清理。这个buffer大小是可配置的,如果超过了配置的大小,主库会强制断开这个从库的连接,这就会导致主从同步中断。
配置项:client-output-buffer-limit replica 256mb 64mb 60 这个是默认的buffer大小(低版本叫 client-output-buffer-limit slave)。
所以如果RDB很大,这个buffer要配置大一些,防止写请求太大,主从同步失败。
如果主从同步失败,从库又会发起全量复制请求,很有可能又会因为buffer超限导致失败,引发复制风暴。
repl-diskless-sync配置项要不要开启?开启后,主从全量数据复制,不会生成RDB文件,而是master直接通过网络socket,把全量数据发给slave。主从节点是内网部署的,是不是使用这种配置项更好?
关于这个配置,可以看一下配置文件关于这一项配置注释说明,写得非常清晰了。简单讲如下:
1、repl-diskless-sync = no,表示主从全量同步,master会先在磁盘上生成RDB文件,然后master读这个文件,把这个RDB文件数据发给slave,达到主从全量同步
2、repl-diskless-sync = yes,表示master不会在磁盘生成RDB文件,而是直接把全量数据,通过socket发给slave,这种也叫无盘复制
各有什么优劣?
1、用磁盘RDB文件的方式做全量同步,好处是在RDB文件写磁盘没有完成之前,在这期间,如果还有其他slave请求全量同步,那么这些slave都可以直接复用这个RDB数据,master只做一次RDB即可
2、如果采用无盘复制,master不生成RDB文件在磁盘,缺点是,master给一个slave开始传输全量数据了,其他slave又连上来需要全量复制,master还需要扫一遍整个实例,然后给这些slave发数据,没办法复用
3、所以,使用无盘复制时,Redis还提供了一个配置repl-diskless-sync-delay,表示开始给slave传输数据之前,等待一会再发数据给slave,如果此时有其他slave连上来请求全量同步,那么在这等待的期间,就可以兼顾其他slave,减少扫描整个实例的次数,降低同步成本
什么情况下会导致主从数据不一致?
5.0以下版本,slave如果提前master超过maxmemory,那么slave自己会淘汰数据,此时主从数据不一致。
5.0增加了一个配置replica-ignore-maxmemory,可以控制是否让slave淘汰数据,默认不淘汰,可以为了保证主从完全一致。
那什么情况下slave会提前master超过maxmemory?
比如slave快要达到maxmemory时,在slave上执行monitor命令,此时这个执行monitor的client输出缓冲区会占用很多内存(实例QPS比较大的情况下很明显),有可能导致slave提前超过maxmemory,需要格外注意。
询问一个Redis数据丢失的问题,如下图:
第一种是主从同步的数据丢失,第二种是哨兵选举导致的脑裂的数据丢失。这是网上找到的解决方案,想问下实战是这样解决的吗?这样会导致主库不能写操作,我感觉是个很危险的行为。
看你具体业务场景,如果主从不一致,对业务影响很大,宁可业务不可用也要保证一致性,就可以用这种方案,缺点是业务有损失。
如果需要优先保证业务可用性,只能降低一致性的要求。然后从运维层面保证主从同步的可靠性,降低出问题的概率。
场景1出问题的原因是,从库有问题,或者主从网络存在问题。那最好解决从库问题和主从之间的网络问题。
从另一个角度说,主从即使不一致,如果Redis只是当做缓存来用,后面还有数据库兜底,所以对业务基本没影响。如果没有数据库兜底,那你就需要把握好业务了,评估丢数据的损失,从运维层面规避降低出问题的概率。
场景2脑裂的问题,细节很多。Redis主从集群本身不是分布式强一致的,所以遇到这种问题也没办法,还是得运维层面做好,例如哨兵怎么部署,要不要和master在一台机器之类的。
能应对脑裂的集群,内部都是有共识协议保证的。比如master脑裂被孤立了,自己退下来不让写入类似这种规则,但是Redis没有。
AOF和RDB同时开启,那么数据恢复是按照什么来恢复的?是4.0之前使用AOF来恢复,4.0之后是按照RDB来恢复吗?
如果同时开启,恢复时优先使用AOF恢复。因为AOF数据比RDB全。
主从同步时,只要有从库存在,主库就会创建一个 backlog_buffer 环形缓冲区,那从库是否也有 backlog_buffer 呢?
Redis 4.0以下版本,slave确实不会有backlog_buffer(环形缓冲区)。
主从切换后,slave只能全量同步数据,代价比较大。
但Redis 4.0版本做了优化,允许slave也有backlog_buffer。
这样主从在同步数据时,主库会写自己的 backlog_buffer,从库也会写自己的 backlog_buffer。
这样一来,主从切换后,旧主变为slave,它就可以继续和新master增量同步数据了,无需走全量同步。
现在Redis哨兵集群中有有三个哨兵,Redis一主两从。现在主从中有一个主挂了,然后哨兵会选一个主哨兵去切换。问题是,下一次Redis主节点挂了后,还会再次选一个主哨兵去切换吗?还是说由上一次的主哨兵去切换呢?
每次都会选一次哨兵领导者去执行切换。
那每次选会不会有时间损耗,从而影响性能?
哨兵选举很快的。哨兵判定主挂了,这期间也需要时间。而哨兵选举的时间,跟这个时间比,几乎可以忽略不计了。
问题场景:1、发生脑裂(主库假故障,主从切换期间,旧主库恢复和客户端通信,客户端把数据写到旧主库,主从切换完成,旧主库降为从库,清数据,全量同步)。
2、主库真故障,主从切换期间丢失数据
这两种情况不是都会丢失主从切换期间的数据吗?两者区别是不是主从切换完成到旧主库变成从库这段期间,是新主库服务还是新主库和旧主库同时服务?
1、场景1发生时,业务应用不报错,以为写成功了,过一会却查不到数据了,结果不符合预期。
2、场景2主库挂了,写请求直接失败,用户可以感知到,自己可以重试,数据是符合预期的。
宁可让2发生,也不要1,1排查起来很困难的。
锁被误释放的问题:锁未过期之前,在什么情况下锁会被误释放?
一个客户端,自己加锁后,执行业务逻辑,但自己却阻塞了很久,然后锁过期了,这个时候别的客户端就可以获取到锁,然后阻塞的客户端执行业务结束了,再去删除锁的时候,会释放别人的锁,那别人的锁就相当于失效了。
redis 主从部署,主库执行 setnx 加锁,然后主库挂了,这个命令还未同步到从库,会导致什么结果?
由于命令还未同步到从库,所以这时会导致锁失效(从库顶上来,对于客户端来说却没有成功加锁)。
所以Redis作者才提出了Redlock来避免这种情况,可查一下资料了解下Redlock原理,并不复杂。
lua脚本可以会保证原子性吗?实际测试中,我给了错误的参数,部分命令运行成功,部分命令运行失败,最终还是不符合原子?
lua只保证了隔离性,并不保证原子性。Redis和MySQL的原子性,不是一回事。
最近面试被问到Redis执行 lua脚本如何保证原子性的?我说执行lua脚本,实际就是开启了一个事物,就保证了原子性。面试官觉得我答的不完全对。这个要怎么答?
因为Redis处理请求是单线程的,单线程可以保证执行lua脚本时不会被别的请求打断(隔离性)。
Redis中的 Lua 执行到一半,Redis实例宕机了,那 Lua 执行的命令能保证原子性吗?
不能保证。
lua只保证2个线程并发操作Redis,2个lua脚本只会串行执行,不会互相影响,说白了是保证了隔离性。
但原子性是无法保证的,Redis没有MySQL的事务机制的。只要一个命令在Redis执行了,那数据在Redis中就被修改了,后面命令没执行成功,前面命令也不会回滚。
关于Redis宕机是如何处理的,这取决于Redis配置的持久化策略了,也就是RDB和AOF。RDB是定时执行,数据肯定不全。AOF如果配置的是每秒刷盘,宕机时也存在丢数据的可能。
看到网上很多文章提到,Redis 的 Lua 脚本可以保证原子性,这到底是为什么呢?
这些文章说的原子性其实并不严谨。
可能很多人理解的原子性是这样的:lua脚本中,如果有2条命令:1是查询命令,2是基于查询的结果执行修改命令。
在这种情况下,1查出来数据是可以直接用的,不用担心被别的线程修改了,很多人为了简单描述,说是原子操作,其实不是的,其本质上是因为 lua 的隔离性保证的,因为 Redis 在处理请求时是单线程的,所以执行 lua 脚本时,也不会有其它请求进来扰乱 lua 脚本的执行。
Redis cluster在扩容/缩容时,具体的迁移流程是怎样的?
简单说一下迁移流程。
假设Redis cluster有2个实例,A和B。
key1/key2在A上,并且属于同一个slot,这2个key要迁移到B。
此时,客户端有流量打到这个集群上,就会有如下情况:
1、key1/key2想要发起迁移,先对这个slot标记为【迁移中】
2、假设key1已迁移到B,但key2没迁移走
3、客户端要读key1,那依旧读A实例,因为这个slot是在A上是【迁移中】的状态,可能这个key确实不在A上,但也有可能刚被迁移走,那此时A只能说我这没有,你去B上问问,回复ASK并告知客户端B的地址,让客户端去B上读
4、客户端给B发送ASKING,B回复OK,客户端再发读命令给B,读到这个key。
5、如果客户端要查key2,由于key2还没从A迁移走,而且因为是读命令,A直接给客户端返回结果即可
6、但是,如果客户端要改key2,那A不会让客户端改,而是先把key2搞到B上,然后回复客户端,让其去B上修改,跳转的流程和上面一样,此时key1/key2所在slot都迁移完成,那redis会标记这个slot【迁移完成】,之后再有请求打到A上,则回复MOVE,告知客户端数据都给B了,你也把路由表改过来,以后都去B上找。
7、如果客户端访问key3,经过crc计算,要访问实例A,但A发现这个key没在我这,那就看key3对应的slot是否是【迁移中】的状态,如果不是,直接返回NULL。如果是,那会怎么办?
8、和上面类似,A不知道key3是本身就不存在呢,还是刚迁移走,所以只能老老实实给客户端说,你去B上找找看,客户端再去B查询,发现确实没有,B返回NULL。
大概流程就是这样,细节大家可以再结合资料看下。
为啥去target上找,要让客户端发ASKING+读命令?
正常命令发给target时,target有可能会认为key不属于自己所管辖的slot,又会发送MOVE让其去source请求,这会造成无限循环,所以需要发送ASKING+命令强制让target处理请求。
为什么说Redis cluster在做数据迁移时,会对实例性能有影响?
迁移一个key,调用的就是Redis的migrate命令,这个命令执行时,会同时阻塞住source和target,直到这个key迁移完成,所以迁移对集群有性能影响。如果要是迁移的是bigkey,那阻塞时间更久。
但是这个命令的好处是同步迁移,因此一个key的迁移是原子的,要么迁移成功,要么迁移失败,不存在迁移一半的状态。
migrate执行的细节是怎样的?
从source dump key,通过网络发给target,target再load回来到实例中。
在这期间经历3步,dump key、网络传输、load key。
redis cluster迁移一个key经历的这3步,都是同步的,必须全部完成。
但codis针对这问题,进行了优化,大概方案就是,source dump key后,发给target就不管了,target啥时load完事,啥时再回调source告诉已完成,把这个步骤拆成异步了,这样就降低了source和target的阻塞时间。
Redis cluster 添加新节点,是不是要手动迁移数据到新节点中, 集群能自动转移数据过去么?
需要手动触发迁移数据。
现在实际生产中,是Redis cluster用的多,还是codis用的多?
前期codis多,因为Redis cluster不稳定。现在cluster是主流了,用的越来越多了,而且codis已经不维护了。
建议直接上 Redis cluster,没有历史包袱。
Redis Cluster的数据迁移是在主线程中进行的吗,也就是说在迁移某个key的是时候,这个key所对应slot的源节点和目标节点都无法响应任何操作?还是说仅仅是这个slot里面的数据被阻塞,其他slot能被正常访问?
一个key迁移过程中,整个source和target实例都会阻塞,如果一个key很小,迁移时几乎不影响性能,如果是bigkey,会增加阻塞时间,影响性能。
先修改了数据库中的值后,为什么删除缓存中数据比更新缓存中数据好呢?
如果去更新缓存,更新过程中数据源又被其他请求再次修改的话,缓存又要面临处理多次赋值的复杂时序问题。所以直接失效缓存,等下次用到该数据时自动回填,期间无论数据源中的值被改了多少次都不会造成任何影响。
客户端缓冲区有个问题,服务器端处理请求的速度过慢,例如,Redis 主线程出现了间歇性阻塞,无法及时处理正常发送的请求,导致客户端发送的请求在缓冲区越积越多。这个我有点没理解,抛开阻塞不说,如果客户端传过来一个大key,大于32k,这个时候客户端缓冲区流溢出了吗?还有如果不溢出,那么报文不完整,Redis如何处理这个请求呢?
Redis 的客户端输入缓冲区大小的上限阈值,在代码中就设定为了 1GB。也就是说,Redis 服务器端允许为每个客户端最多暂存 1GB 的命令和数据。1GB 的大小,对于一般的生产环境已经是比较合适的了。一方面,这个大小对于处理绝大部分客户端的请求已经够用了;另一方面,如果再大的话,Redis 就有可能因为客户端占用了过多的内存资源而崩溃。
client的output buffer特别高,导致占用内存非常大,可能的原因?
例如client执行了某个命令后,server返回大量数据,但client没有及时去读取和处理这些数据,也没有断开这个连接,导致server发给client的数据积压在输出缓冲区中。
或者client执行pipeline,一次发太多数据给server了,server返回给client的数据也非常多,client来不及处理就积压在缓冲区了,这是典型的业务使用问题,client需要控制pipeline发送命令的数量。
如何查看每一个client的output buffer有多大?
执行client list命令,可以看到每个client的omem,即是client output buffer。
之前公司遇到过一个问题,当业务请求中的写流量突然增高,或网络延迟增高时,由于主从复制缓存区空间用尽(尤其是跨地域同步场景更容易触发),主从同步断开,重新同步则触发全量同步,但该期间积累数据超过buffer限制,又会导致同步断开,一段时间内主从一直没法完成同步,这种情况如何解决?
好问题。
其实问题在于,master上的从库缓冲区 slave client-output-buffer-limit 被写满了,写满之后,master会强制断开主从复制,复制就会失败。
之后 slave 重新向 master 发起数据同步请求,由于 master 写入量太大,backlog buffer 可能又被覆写了,这就导致需要全量同步数据。全量同步数据时,slave client buffer 又写满,恶性循环,导致复制持续失败。
解决此问题,需要先把 backlog_buffer 和 slave client-output-buffer-limit 都调大点,避免缓冲区溢出。
master 如果写入量很大,最好拆分实例部署,分散写压力。
slave 机器的负载和硬件也需要关注下,避免slave同步变慢,导致数据积压。
如何排查Redis变慢问题,如何性能调优?
直接看我写的这篇文章,全网最全性能分析教程:https://mp.weixin..com/s/Qc4t_-_pL4w8VlSoJhRDcg(Redis为什么变慢了?一文讲透如何排查Redis性能问题 | 万字长文)
想问下线上运行的redis实例,如果主线程被卡了,第一时间是怎么知道的呢?(比如自己执行了一个keys命令,卡住了主线程),运维说我执行的命令有问题,它是如何及时发现的?
运维应该有监控,会定时采集 Redis INFO 信息,如果长时间拿不到结果,再拿执行 client list 命令,就能发现有人执行keys了,然后监控报警出来。
一个 Redis 实例存入的 key 有上限吗,会出现达到某一阈值量个key,性能就会大幅度降低或者没法继续新增吗?
Redis要存储的key的数量上限受限于哈希表的size字段,类型是unsigned long,4个字节32位,那key的上限就是 2^32 大约 42 亿个key。
但实际情况一个实例存不了这么多,就会有性能问题。最明显的就是rehash扩容。
因为每次扩容,申请新的哈希表,所需内存需要翻倍申请,如果key越写越多,达到一亿个以上,每次申请内存就有可能达到上G,这个成本会越来越高,那势必会导致redis卡顿,性能严重下降。
另外,从持久化方面考虑,fork子进程的时间也会非常长,也会导致redis阻塞。
当然,以上只是假设每个key都很小的情况下,尽可能写入更多的key,如果一个key本身就很大,实例内存使用也会上涨更快,写入的key还没达到上亿个,实例内存已经占用很大了。
所以,从这个角度来看,生产环境下尽可能不要让一个实例太大,尽可能做拆分,避免性能问题。
如何理解Redis数据持久化、主从复制、哨兵、分片集群它们之间的联系?
看我写的这篇文章,把这几个知识点串联起来,通俗易懂:https://mp.weixin..com/s/q79ji-cgfUMo7H0p254QRg
为什么master和slave执行scan命令,返回的结果和数量不一样?
master和slave的全局哈希表,哈希桶的分布可能是不同的,而且scan扫描的结果也是无序的。
不能只看结果,要重点看cursor返回是不是0,不是0需要继续执行scan,直到返回0才拿到了全部数据。而且一次最多返回count元素,有可能少于count,关键看cursor的值。
Redis集群的节点的大小2-4g最合适是吗?
这是个经验值,意思是越小越不容易发生阻塞风险,比如实例很小,执行RDB就很快。
但是实例很小,那部署的节点数量会比较多,维护成本高一些。我个人觉得10G以下问题也不大,这样节点数也不是很多,维护起来方便些。
重点注意的是,实例越大,实例阻塞风险越大,而且维护会变得越来越困难,出问题的概率也大。
我理解的pipeline 好像只会减少网络传输的时间,并不能保证发送的一批命令不会被其他命令穿插执行?如果pipeline提交的命令较多呢?
还是不一样的,pipline关注的是打包把多个命令发到服务端。事务关注的如何保证ACID这些。
Pipeline一次发多个命令,服务端解析一个命令,执行一次。而事务是必须收到exec才会执行。
在一定程度上,pipline能实现和事务一样的效果,但是同样这么操作,如果遇到执行一半命令,Redis崩溃了,pipeline执行了多少命令就是多少,但事务因为有multi标记,在AOF恢复的时候可以把执行一半的命令撤销掉,这是不一样的地方。另外事务可以配合watch使用,pipeline不行。
使用pipeline批量发数据给Redis,是不是功能上跟multi/exec一样了?
multi/exec可以一个个发命令到服务端,也可以打包一次全发过去,一次全发就是配合了pipeline使用的。
关于CPU绑核:一般业务场景下,应该用不到绑核吧?而且swap线上一般都是关闭的吧?
除非追求更好的性能,一般不绑核,绑核对于DBA要求高,了解原理才可以操作,否则会达到反效果。另外,Swap线上不一定是关的,很多服务器默认都是开的。
Redis6.0多线程处理,是先把socket连接放入全局队列,然后阻塞,让io多线程解析处理,然后主线程处理读写命令,写到缓冲区,再阻塞,交给io多线程放入socket缓冲区,然后,主线程清空全局队列,返回。我的疑问是,这个全局队列在命令处理期间,始终只有一个socket吗,如果不是的话,那么最后清空全局队列,会不会把其他没完成的socket也清理了呢。
所有请求处理完成后,才会清理的。
为什么推荐只使用 db0,从而减少 SELECT 命令的消耗,select命令为什么会坑?select命令是客户端发送过来执行的,想知道如果一个客户端固定用一个db,并且用连接池,不会每次都执行select吧?
除非客户端连接池,1个db建一个连接操作Redis,如果是一个连接会操作多个db的话,每次执行时,肯定需要先执行一次SELECT命令的。如果QPS很高的话,执行SELECT命令也是消耗。
另一方面,既然数据要存在不同db下,目的就是为了做隔离,其实更好的方式是拆分实例,不同实例保存不同业务线的数据,这样每个实例承担的QPS也变高了,后期也方便DBA运维。
最后,Redis cluster只支持使用db0,如果你后期想往cluster上迁移,使用了多个db就会很麻烦,还得拆分数据,所以只建议使用db0,减少SELECT消耗,也便于迁移到Redis cluster。
操作系统的net.core.somaxconn如果设置为512,而Redis客户端最大允许10000连接,这个客户端连接是不是受限于somaxconn,最多不会超过512呢?
客户端向服务端建立一个TCP连接时,服务端先把连接放到半连接队列,然后再放到全连接队列(TCP三次握手的细节很多,这里简化了,你可以去查细节),somaxconn是用于控制这两个队列最大长度的。
这些队列有什么用?有队列的好处是,当多个连接同时打到服务端时,服务端只能一个个处理连接,还没处理到的连接不能丢弃吧?所以服务端有这样一个队列来缓冲,把连接都放到这里来,应用层accept时就从全连接队列拿一个出来进行交互。
至于Redis配置文件设置的最大连接数,Redis服务端每拿出来连接和客户端交互,都可以在应用层记录现在服务的连接数,如果服务的连接数已经超过了配置的,那么就可以直接拒绝掉。你看到的配置文件连接数限制,是在这控制的。
一个是TCP层的,一个是应用层的。
布隆过滤器第一次还是会直接访问缓存,缓存没有再访问DB上,如果未获取到数据,就在布隆过滤器上进行添加,下次有相同的请求的时候,直接屏蔽该请求。是这个做法吗?
不是,请求进来先查布隆过滤器,布隆没有,直接返回。布隆存在,查缓存,查DB,同时在布隆里设置标记数据存在。查缓存和查DB,看你缓存是否有数据,有的话只查缓存就可以,不需要查DB。
示例代码:
if not bloom_filter.exists(user_id): return null user = query_cache(user_id) if not user: user = query_db(user_id) set_cache(user_id, user) // user = null 可以不设置布隆 减少一次ops // 也可以不加这个判断 对结果也没影响 if user: bloom_filter.set(user_id) return user
重点:
1、如果是新业务,直接按照上面伪代码写就可以
2、如果是老业务,想要上一层布隆过滤无效请求,需要扫数据库把已存在的数据刷到布隆里
3、当然,每次新增数据也同步设置标记到布隆里
到底什么是 IO 多路复用?
简单描述一下:
IO多路复用,重点是,应用层可以把多个socket连接注册给操作系统,让操作系统帮忙盯着这些socket有没有数据过来(可读/可写)。
注册完成之后,应用层就可以去干别的事了。当socket有数据过来时,操作系统会通知应用层,应用层再去处理。这样的优势在于应用层1个线程,就可以服务多个网络请求,即 IO 多路复用。
这个技术是Redis单线程可以处理大量请求的原因。IO多路复用的具体实现模型有 select/poll/epoll,目前epoll是性能最好的。
下面简单描述客户端和服务端是如何通过 epoll 读写数据的。
一个网络程序,客户端A给服务端B发数据,A编写socket程序,调用write API向这个socket fd写数据。
写完之后,数据怎么发给B呢?
是需要经过操作系统、网卡、网线发过去的。
A的操作系统把数据按照网络协议包装好,通过网卡发出去,经过网络线路,最终都到B了。
B谁先来处理?肯定是操作系统先拿到这些数据,它会先放在内核态,然后通知上层的应用,你的数据来了,你来读取吧。
epoll是操作系统提供的API,应用层把socket提前注册给操作系统,先调epoll的注册方法,这就托管给操作系统了。
然后应用层再调用epoll的 wait 方法,有fd有数据过来/可写,就返回,返回指的就是wait方法return了,应用层就拿到这个fd可以操作了。不返回说明,注册的这些fd都不能读写,那应用层就阻塞在wait上等着,除非wait方法传了个timeout参数,那就到了timeout也给应用层返回。
这是读的情况。除了读,还有写。写对应的是发数据,fd可以写了,就通知应用层,应用就写这个fd。
还是拿刚才A->B传输数据来说。刚才说的是读,是B要读A的数据。现在看A这一侧,A要写这个fd给B发数据。
一开始写,fd对应的是操作系统的缓冲区,A写fd,会先写到操作系统的缓冲区里,然后由操作系统把缓冲区的数据发给B。
但如果A一直写一直写,B那边读的慢,那A这边的缓冲区就会满了,满了之后应用层还在调write,肯定就不可以写了,那啥时再可以写?A操作系统能知道B那边有没有把缓冲区的数据读走,如果读走了,缓冲区空出来了,那A的操作系统就通知应用层说,缓冲区空出来了,你继续可以写了。
fd是操作系统封装的数据结构,但凡一个应用层要做网络交互,必须经过操作系统,所以操作系统就提供了很多高效的办法,比如缓冲区和这些通知机制。
说白了就是,操作系统是一个大管家,其目的就是为了更好地服务上层应用,所以它做了很多事,这些IO多路复用,都是操作系统在帮应用层干活。
到此这篇redis连接哨兵命令(redis哨兵连接池不释放)的文章就介绍到这了,更多相关内容请继续浏览下面的相关推荐文章,希望大家都能在编程的领域有一番成就!版权声明:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权、违法违规、事实不符,请将相关资料发送至xkadmin@xkablog.com进行投诉反馈,一经查实,立即处理!
转载请注明出处,原文链接:https://www.xkablog.com/rfx/12714.html