2-1. 键值保存在哈希表中,哈希表中保存的值具体是键和值的指针。
2-2. Redis 采用了渐进式 rehash。在第二步拷贝数据时,Redis 仍然正常处理客户端请求,每处理一个请求时,从哈希表 1 中的第一个索引位置开始,顺带着将这个索引位置上的所有 entries 拷贝到哈希表 2 中;等处理下一个请求时,再顺带拷贝哈希表 1 中的下一个索引位置的 entries

3-1. 单线程 Redis 为什么那么快,一方面,Redis 的大部分操作在内存上完成,再加上它采用了高效的数据结构,例如哈希表和跳表,这是它实现高性能的一个重要原因。另一方面,就是 Redis 采用了多路复用机制,使其在网络 IO 操作中能并发处理大量的客户端请求,实现高吞吐率
3-2. Linux 中的 IO 多路复用机制是指一个线程处理多个 IO 流,就是我们经常听到的 select/epoll 机制。简单来说,在 Redis 只运行单线程的情况下,该机制允许内核中,同时存在多个监听套接字和已连接套接字。内核会一直监听这些套接字上的连接请求或数据请求。一旦有请求到达,就会交给 Redis 线程处理,
3-3. Redis 单线程是指它对网络 IO 和数据读写的操作采用了一个线程,而采用单线程的一个核心原因是避免多线程开发的并发控制问题。单线程的 Redis 也能获得高性能,跟多路复用的 IO 模型密切相关,因为这避免了 accept() 和 send()/recv() 潜在的网络 IO 操作阻塞点。

4-1. AOF 是写后日志,“写后”的意思是 Redis 是先执行命令,把数据写入内存
4-2. 为了避免额外的检查开销,Redis 在向 AOF 里面记录日志的时候,并不会先去对这些命令进行语法检查。所以,如果先记日志再执行命令的话,日志中就有可能记录了错误的命令,Redis 在使用日志恢复数据时,就可能会出错
4-3. AOF 还有一个好处:它是在命令执行后才记录日志,所以不会阻塞当前的写操作。AOF 虽然避免了对当前命令的阻塞,但可能会给下一个操作带来阻塞风险。这是因为,AOF 日志也是在主线程中执行的,如果在把日志文件写入磁盘时,磁盘写压力大,就会导致写盘很慢,进而导致后续的操作也无法执行了
4-4. AOF 重写机制就是在重写时,Redis 根据数据库的现状创建一个新的 AOF 文件,也就是说,读取数据库中的所有键值对,然后对每一个键值对用一条SET命令记录它的写入
4-5. 重写AOF 时,吧内存拷贝一份,子线程用拷贝的内存写AOF,期间主线程上的请求记录会被记录到AOF重写缓冲中,最后这部分缓存也会写入新的AOF日志。

5-1. Redis 提供了两个命令来生成 RDB 文件,分别是 save 和 bgsave。save:在主线程中执行,会导致阻塞;bgsave:创建一个子进程,专门用于写入 RDB 文件,避免了主线程的阻塞,这也是 Redis RDB 文件生成的默认配置。
5-2. 生成RDB时的写时复制:bgsave 子线程在读取内存生成RDB时,如果主线程上来了写请求,会把需要变更的内存复制一份,在副本上修改。
5-3. Redis 4.0 中提出了一个混合使用 AOF 日志和内存快照的方法。快照不用很频繁地执行,AOF 日志也只用记录两次快照间的操作

6-1. 主从库间如何进行第一次同步:启动多个实例,使用replicaof建立主从关系,然后数据复制要经过三个阶段,阶段一:各个节点确认主从关系。阶段二,主库bgsave生成rdb文件发给从库,同时把这期间新的写请求写入 replication buffer ,从库接到rdb文件后先删除本地数然后将rdb文件写入内存。阶段三:主库把 replication buffer 中的数据发给从库同步。
6-2. 级联结构,即 主-从-从 模式, 主库的从库可以接续有自己的从库,这样可以分担一部分主库同步数据的压力。
6-3. 主从正常同步过程中维护一个 repl_backlog_buffer 的环形内存空间,主节点记录自己写入未知的偏移量,从节点记录了自己读数据的偏移量。主从断连并恢复连接后,从库发送自己的读数据偏移量,就知道了从哪里开始继续同步。如果断连时间太长,主库已经把缓存写了一圈覆盖掉从库读取的位置了,就要进行全量同步。

7-1. 哨兵其实就是一个运行在特殊模式下的 Redis 进程,主从库实例运行的同时,它也在运行。哨兵主要负责的就是三个任务:监控、选主(选择主库)和通知。
7-2. 通常会采用多实例组成的集群模式进行部署,这也被称为哨兵集群。引入多个哨兵实例一起来判断,就可以避免单个哨兵因为自身网络状况不好,而误判主库下线的情况
7-3. “客观下线”的标准就是,当有 N 个哨兵实例时,最好要有 N/2 + 1 个实例判断主库为“主观下线”,才能最终判定主库为“客观下线
7-4. 选主,先把过去一段时间内常常断连的从库过滤掉,剩下的从空打分,有3轮,按配置的优先级,按同步进度,按id从小到大排。当某一轮选出分数最高唯一实例时,选主结束。

8-1. 哨兵实例之间可以相互发现,要归功于 Redis 提供的 pub/sub 机制,也就是发布 / 订阅机制,哨兵只要和主库建立起了连接,就可以在主库上发布消息了,比如说发布它自己的连接信息(IP 和端口)。同时,它也可以从主库上订阅消息,获得其他哨兵发布的连接信息。然后哨兵之间互相建立连接。
8-2. 除了哨兵实例,我们自己编写的应用程序也可以通过 Redis 进行消息的发布和订阅
8-3. 哨兵给主库发送 INFO 命令,主库接受到这个命令后,就会把从库列表返回给哨兵。接着,哨兵就可以根据从库列表中的连接信息,和每个从库建立连接,并在这个连接上持续地对从库进行监控
8-4. 客户段通过订阅哨兵的频道来得知主从切换到了哪一步以及最终选出的主节点的地址信息。
8-5. 怎么选出由哪个哨兵执行通知主从切换信息的任务? 当一个哨兵判定主节点主管下线后,给其他哨兵发送 is-master-down-by-addr 命令,询问其他哨兵对主节点状态的判断情况,其他哨兵若也判断主管下线则回复Y,否则回复N,如果过回复Y的数量加上自己的一票 的值大于配置文件中配置的quorum 值,则这个哨兵就可以标记主库为“客观下线”了,此时,这个哨兵就可以再给其他哨兵发送命令,表明希望由自己来执行主从切换,并让所有其他哨兵进行投票。在投票过程中,任何一个想成为 Leader 的哨兵,要满足两个条件:第一,拿到半数以上的赞成票;第二,拿到的票数同时还需要大于等于哨兵配置文件中的 quorum 值,就可以了。如果投票选举失败则过一段时间后再来一次。

9-1. 在切片集群中,数据需要分布在不同实例上,那么,数据和实例之间如何对应呢?这就和接下来我要讲的 Redis Cluster 方案有关了。
9-2. 在 Redis Cluster 方案中,一个切片集群共有 16384 个哈希槽,可以使用 cluster create 命令创建集群,此时,Redis 会自动把这些槽平均分布在集群实例上。也可以使用 cluster meet 命令手动建立实例间的连接,形成集群,再使用 cluster addslots 命令,指定每个实例上的哈希槽个数。在手动分配哈希槽时,需要把 16384 个槽都分配完,否则 Redis 集群无法正常工作。
9-3. Redis 实例会把自己的哈希槽信息发给和它相连接的其它实例,来完成哈希槽分配信息的扩散。当实例之间相互连接后,每个实例就有所有哈希槽的映射关系了。客户端和集群实例建立连接后,实例就会把哈希槽的分配信息发给客户端,客户端从实例收到哈希槽信息后,会把哈希槽信息缓存在本地。当客户端请求键值对时,会先计算键所对应的哈希槽,然后就可以给相应的实例发送请求了
9-4. 如果某个实例下线了,slot 会被重新分配,有一些slot 就会被迁移。这时客户端是不知到迁移后的slot在哪个节点。所以去请求原节点,原节点返回MOVE 命令和迁移的目标节点地址,客户端根据MOVE信息请求新节点,并更新本地缓存。 若请求原节点时,这个slot正在迁移但还没来的及迁移完成,就返回ASK 命令加 目标节点,客户端收到ASK命令,向目标节点发送ASKING命令表示让这个实例允许执行客户端接下来发送的命令,然后,客户端再向这个实例发送 GET 命令,以读取数据。

10-1. 在主从切换过程中,客户端能否正常地进行请求操作呢?主从集群一般是采用读写分离模式,当主库故障后,客户端仍然可以把读请求发送给从库,让从库服务。但是,对于写请求操作,客户端就无法执行了
10-2. 果一个哨兵想要执行主从切换,就要获到半数以上的哨兵投票赞成,这里的半数所对应的总数是一开始所有的哨兵数,即使当前有一部分哨兵已经下线了,总数还是不变的,半数当然就也没变。
10-3. 采用渐进式 hash 时,如果实例暂时没有收到新请求,是不是就不做 rehash 了?其实不是的。Redis 会执行定时任务,定时任务中就包含了 rehash 操作。所谓的定时任务,就是按照一定频率(例如每 100ms/ 次)执行的任务。

11-1. String 类型占用内存的原因,redis 的哈希表的每一项是一个 dictEntry 的结构体,用来指向一个键值对。dictEntry 结构中有三个 8 字节的指针,分别指向 key、value 以及下一个 dictEntry,三个指针共 24 字节。 分配内存只能是2的整次幂,所以要分32字节。另外,存储建和值的对象叫 RedisObject ,RedisObject 有元数据和 地址指针构成,地址指针指向数据内存地址,元数据中保存了最后一次访问的时间、被引用的次数等。占8字节,可以看到,保存一个键值对要用到很多额外的空间,如果需要保存的键值很小很多,直接用String类型存储就会浪费很多空间了。
11-2. 一种解决办法是把这些键值对分组保存在如 hash 的集合类数据结构里,就可以省不少空间。

12-1. 集合类型的计算,如对set 类型做交集、差集计算,复杂度很高,可以在从库上进行计算。或者读到本地计算,防止redis实例阻塞。
12-2. list 类型只能根据元素在 List 中的位置来排序,而新元素的插入会导致元素位置后移。Sorted Set 可以解决这个问题,因为可以按权重排序抽查固定范围的数据,而新元素插入不会影响其它元素的权重。
12-3. Bitmap 本身是用 String 类型作为底层数据结构实现的一种统计二值状态的数据类型。String 类型是会保存为二进制的字节数组,所以,Redis 就把字节数组的每个 bit 位利用起来,用来表示一个元素的二值状态。你可以把 Bitmap 看作是一个 bit 数组
12-4. 使用 HyperLogLog 做基数统计,唯一值存储,类似set的作用。但他是概率统计,标准误算率是 0.81%,节省空间。

13-1.Redis 还提供了 3 种扩展数据类型,分别是 Bitmap、HyperLogLog 和 GEO。
13-2 GeoHash 编码能将多维数据转为一维数据。当然这个也不是很准确。
13-3. GEO数据类型基于sorted set,利用了GeoHash ,可以保存经纬库信息,实现查询某一地点周围指定范围内其它数据的功能。
13-4. 可以使用c代码实现自己的数据类型。

14-1. 如果一种数据结构不能很好的满足查询业务,可以用多种数据节后存储多分。
14-2. RedisTimeSeries 是 Redis 的一个扩展模块。它专门面向时间序列数据提供了数据类型和访问接口,并且支持在 Redis 实例上直接对数据进行按时间范围的聚合计算。因为 RedisTimeSeries 不属于 Redis 的内建功能模块,在使用时,我们需要先把它的源码单独编译成动态链接库 redistimeseries.so,再使用 loadmodule 命令进行加载

15-1. List 本身就是按先进先出的顺序对数据进行存取的。
15-2. BRPOP 命令也称为阻塞式读取,客户端在没有读到队列数据时,自动阻塞,直到有新的数据写入队列,再开始读取新数据。
15-3. 为了留存消息,List 类型提供了 BRPOPLPUSH 命令,这个命令的作用是让消费者程序从一个 List 中读取消息,同时,Redis 会把这个消息再插入到另一个 List(可以叫作备份 List)留存。
15-4. Redis 从 5.0 版本开始提供的 Streams,Streams 是 Redis 专门为消息队列设计的数据类型。
15-5. XADD:插入消息,保证有序,可以自动生成全局唯一 ID;XREAD:用于读取消息,可以按 ID 读取数据;XREADGROUP:按消费组形式读取消息;XPENDING 和 XACK:XPENDING 命令可以用来查询每个消费组内所有消费者已读取但尚未确认的消息,而 XACK 命令用于向消息队列确认消息处理已完成。

16-1. Redis 性能的 5 大方面的潜在因素,分别是:Redis 内部的阻塞式操作;CPU 核和 NUMA 架构的影响;Redis 关键系统配置;Redis 内存碎片;Redis 缓冲区。
16-2. 会导致 Redis 性能受损的 5 大阻塞点,包括集合全量查询和聚合操作、bigkey 删除、清空数据库、AOF 日志同步写,以及从库加载 RDB 文件
16-3. bigkey 删除、清空数据库、AOF 日志同步写不属于关键路径操作,可以使用异步子线程机制来完成
16-4. 异步删除操作是 Redis 4.0 以后才有的功能,如果你使用的是 4.0 之前的版本,当你遇到 bigkey 删除时,我给你个小建议:先使用集合类型提供的 SCAN 命令读取数据,然后再进行删除,对于 Hash 类型的 bigkey 删除,你可以使用 HSCAN 命令,每次从 Hash 集合中获取一部分键值对(例如 200 个),再使用 HDEL 删除这些键值对
16-5. 集合全量查询和聚合操作:可以使用 SCAN 命令,分批读取数据,再在客户端进行聚合计算
16-6. 对于从库来说,它在接收了 RDB 文件后,需要使用 FLUSHDB 命令清空当前数据库,从库在清空当前数据库后,RDB 文件越大,加载过程越慢,把主库的数据量大小控制在 2~4GB 左右,以保证 RDB 文件能以较快的速度加载。

17-1。 在 CPU 多核的场景下,用 taskset 命令把 Redis 实例和一个核绑定,可以减少 Redis 实例在不同核上被来回调度执行的开销,避免较高的尾延迟;在多 CPU 的 NUMA 架构下,如果你对网络中断程序做了绑核操作,建议你同时把 Redis 实例和网络中断程序绑在同一个 CPU Socket 的不同核上,这样可以避免 Redis 跨 Socket 访问内存中的网络数据的时间开销

18-1. 基线性能和当前的操作系统、硬件配置相关,如果你观察到的 Redis 运行时延迟是其基线性能的 2 倍及以上,就可以认定 Redis 变慢了,从 2.8.7 版本开始,redis-cli 命令提供了–intrinsic-latency 选项,可以用来监测和统计测试期间内的最大延迟,./redis-cli --intrinsic-latency 120 ,为了避免网络对基线性能的影响,刚刚说的这个命令需要在服务器端直接运行
18-2. 如果你想了解网络对 Redis 性能的影响,一个简单的方法是用 iPerf 这样的工具
18-3. Redis 提供的命令操作很多,并不是所有命令都慢,这和命令操作的复杂度有关,比如说,Value 类型为 String 时,GET/SET 操作主要就是操作 Redis 的哈希表索引。这个操作复杂度基本是固定的,即 O(1)。但是,当 Value 类型为 Set 时,SORT、SUNION/SMEMBERS 操作复杂度分别为 O(N+M*log(M)) 和 O(N)
18-4. Redis 键值对的 key 可以设置过期时间。默认情况下,Redis 每 100 毫秒会删除一些过期 key,具体的算法如下:采样 ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 个数的 key,并将其中过期的 key 全部删除;如果超过 25% 的 key 过期了,则重复删除的过程,直到过期 key 的比例降至 25% 以下。
18-5. 如果有大量的过期key需要删除,删除操作是阻塞的(Redis 4.0 后可以用异步线程机制来减少阻塞影响)。要检查业务代码在使用 EXPIREAT 命令设置 key 过期时间时,是否使用了相同的 UNIX 时间戳。

19-1. AOF 日志提供了三种日志写回策略:no、everysec、always。这三种写回策略依赖文件系统的两个系统调用完成,也就是 write 和 fsync。 no 策略是调用write,由操作系统决定什么时候执行fsync。
19-2. 当写回策略配置为 everysec 时,Redis 会使用后台的子线程异步完成 fsync 的操作。always 策略并不使用后台子线程来执行。
19-3. AOF 重写会对磁盘进行大量 IO 操作,同时,fsync 又需要等到数据写到磁盘后才能返回,所以,当 AOF 重写的压力比较大时,就会导致 fsync 被阻塞。虽然 fsync 是由后台子线程负责执行的,但是,主线程会监控 fsync 的执行进度。当主线程使用后台子线程执行了一次 fsync,需要再次把新接收的操作记录写回磁盘时,如果主线程发现上一次的 fsync 还没有执行完,那么它就会阻塞。所以,如果后台子线程执行的 fsync 频繁阻塞的话(比如 AOF 重写占用了大量的磁盘 IO 带宽),主线程也会阻塞,导致 Redis 性能变慢
19-4. redis 内存不够用就会触发内存swap, swap 会影响redis性能。解决方式是使用更大内存或者redis集群。
19-5. 怎么查看swap 情况? 可以先通过下面的命令查看 Redis 的进程号, redis-cli info | grep process_id ,进入/proc 下对应进程目录, cd /proc/5332 。 执行 cat smaps | egrep '^(Swap|Size)' 查看swap情况
19-6. 操作系统:内存大页。Linux 内核从 2.6.38 开始支持内存大页机制,该机制支持 2MB 大小的内存页分配,而常规的内存页分配是按 4KB 的粒度来执行的。redis 数据持久化时使用了写时复制机制,内存太大需要复制的时间就更长了,影响性能。
19-7. cat /sys/kernel/mm/transparent_hugepage/enabled ,如果执行结果是 always,就表明内存大页机制被启动了;如果是 never,就表示,内存大页机制被禁止。使用 echo never /sys/kernel/mm/transparent_hugepage/enabled 关闭内存大页。
 
20-1. 内存碎片是如何形成的,一个是操作系统分配内存都只会分配 2的整次幂大小内存。二是redis键值的删改。
20-2. 如何判断是否有内存碎片,使用INFO memory 命令。结果中:used_memory_rss 是操作系统实际分配给 Redis 的物理内存空间,里面就包含了碎片;而 used_memory 是 Redis 为了保存数据实际申请使用的空间。mem_fragmentation_ratio 是used_memory_rss 和 used_memory 相除的结果。
20-3. mem_fragmentation_ratio 大于 1 但小于 1.5。这种情况是合理的。mem_fragmentation_ratio 大于 1.5 。这表明内存碎片率已经超过了 50%。一般情况下,这个时候,我们就需要采取一些措施来降低内存碎片率了。
20-4. 处理内存碎片: 重启是可以的。或者可以把 activedefrag 配置项设置为 yes,命令如下:config set activedefrag yes 。再结合一些条件参数控制什么时候清理碎片。active-defrag-ignore-bytes     碎片大小限制,active-defrag-threshold-lower 碎片比例限制,active-defrag-cycle-min 最低使用cpu时长限制,active-defrag-cycle-max 最高使用cpu时长限制。

21-1. 客户端写入bigkey 或者服务端处理速度过慢,就会导致服务端上的客户端输入缓存溢出。
21-2. 要查看和服务器端相连的每个客户端对输入缓冲区的使用情况,我们可以使用 CLIENT LIST 命令。结果中:cmd,表示客户端最新执行的命令。qbuf,表示输入缓冲区已经使用的大小。。qbuf-free,表示输入缓冲区尚未使用的大小。
21-3. 避免输入缓冲区溢出。我们可以从两个角度去考虑如何避免,一是把缓冲区调大,二是从数据命令的发送和处理速度入手。Redis 并没有提供参数让我们调节客户端输入缓冲区的大小。只能避免客户端写入 bigkey,以及避免 Redis 主线程阻塞。
21-4. Redis 为每个客户端设置的输出缓冲区也包括两部分:一部分,是一个大小为 16KB 的固定缓冲空间,用来暂存 OK 响应和出错信息;另一部分,是一个可以动态增加的缓冲空间,用来暂存大小可变的响应结果。
21-5. 输出缓冲区溢出呢? 我为你总结了三种:服务器端返回 bigkey 的大量结果;执行了 MONITOR 命令;缓冲区大小设置得不合理。
21-6. MONITOR 命令是用来监测 Redis 执行的。MONITOR 的输出结果会持续占用输出缓冲区,并越占越多,最后的结果就是发生溢出,MONITOR 命令主要用在调试环境中,不要在线上生产环境中持续使用 MONITOR。
21-7. 可以使用 client-output-buffer-limit 设置合理的缓冲区大小上限,或是缓冲区连续写入时间和写入量上限
21-8. 复制缓冲区: 在全量复制过程中,主节点在向从节点传输 RDB 文件的同时,会继续接收客户端发送的写命令请求。这些写命令就会先保存在复制缓冲区中,等 RDB 文件传输完成后,再发送给从节点去执行。主节点上会为每个从节点都维护一个复制缓冲区
21-9. 如果在全量复制时,从节点接收和加载 RDB 较慢,同时主节点接收到了大量的写命令,写命令在复制缓冲区中就会越积越多,最终导致溢出。
21-10. 为避免复制缓存区溢出,可以控制主节点数据量再2~4G,时全量复制时间不会太长。也可以用  client-output-buffer-limit  设置缓冲区大小。
21-11. repl_backlog_buffer ,主节点在把接收到的写命令同步给从节点时,同时会把这些写命令写入复制积压缓冲区。一旦从节点发生网络闪断,再次和主节点恢复连接后,从节点就会从复制积压缓冲区中,读取断连期间主节点接收到的写命令,进而进行增量同步。 可以通过repl_backlog_size 参数调整这个缓存区的大小。

22-1. Streams 可以定义多个消费者组,实现消息被多个地方消费。另外,Redis 基于字典和链表数据结构,实现了发布和订阅功能,这个功能可以实现一个消息被多个消费者消费使用
22-2. 如何看慢查询日志:1,通过slowlog-log-slower-than、slowlog-max-len 参数和 SLOWLOG GET 命令查看。 2,使用latency monitor 监控工具 监控工具。
22-3. 如何排查 Redis 的 bigkey?Redis 可以在执行 redis-cli 命令时带上–bigkeys 选项,进而对整个数据库中的键值对大小情况进行统计分析,在使用–bigkeys 选项时,这个工具是通过扫描数据库来查找 bigkey 的,所以,在执行的过程中,会对 Redis 实例的性能产生影响。建议你在从节点上执行该命令。

24-1. 可以使用下面这个命令来设定缓存的大小了。CONFIG SET maxmemory 4gb
24-2. 8种淘汰策略:noeviction 不淘汰。 在设置了过期时间的数据中进行淘汰,包括 volatile-random、volatile-ttl、volatile-lru、volatile-lfu(Redis 4.0 后新增)四种。在所有数据范围内进行淘汰,包括 allkeys-lru、allkeys-random、allkeys-lfu(Redis 4.0 后新增)三种。
24-3. volatile-ttl 在筛选时,会针对设置了过期时间的键值对,根据过期时间的先后进行删除,越早过期的越先被删除。volatile-random 就像它的名称一样,在设置了过期时间的键值对中,进行随机删除。volatile-lru 会使用 LRU 算法筛选设置了过期时间的键值对。volatile-lfu 会使用 LFU 算法选择设置了过期时间的键值对。

25-1. 缓存一定是要有更新的,无论是通过过期时间的更新,还是定时查库更新最新修改该的数据。

26-1. 处理缓存雪崩: 避免设置大量相同的过期时间点、服务降级,熔断
26-2. 处理缓存击穿: 指热点缓存过期的情况。解决:线程加锁更新缓存。或者不设置过期时间,主动触发更新或者定时更新。
26-3. 梳理缓存穿透: 缓存空值、使用布隆过滤器。

27-1. LFU 策略中会从两个维度来筛选并淘汰数据:一是,数据访问的时效性(访问时间离当前时间的远近);二是,数据的被访问次数。
27-2. 当使用 LFU 策略筛选淘汰数据时,首先会根据数据的访问次数进行筛选,把访问次数最低的数据淘汰出缓存。如果两个数据的访问次数相同,LFU 策略再比较这两个数据的访问时效性,把距离上一次访问时间更久的数据淘汰出缓存。

29-1. 客户端可以执行 LOCK() 和 UNLOCK() 命令加锁,只让自己操作。
29-2. Redis 的两种原子操作方法,1, 把多个操作在 Redis 中实现成一个操作,也就是单命令操作;2,把多个操作写到一个 Lua 脚本中,以原子性方式执行单个 Lua 脚本。

30-1。SETNX 命令,它用于设置键值对的值。具体来说,就是这个命令在执行时会判断键值对是否存在,如果不存在,就设置键值对的值,如果存在,就不做任何设置。
30-2. SET 命令在执行时还可以带上 EX 或 PX 选项,用来设置键值对的过期时间
30-3. redis 做分布式锁没什么罕见的,还可以用多个redis做高可用分布式锁,基本思路是 第一步是,客户端获取当前时间。第二步是,客户端【按顺序】依次向 N 个 Redis 实例执行加锁操作。第三步是,一旦客户端完成了和所有 Redis 实例的加锁操作,客户端就要计算整个加锁过程的总耗时,和获得到的锁数。如果得到了 二分之一以上数量的锁而且时间消耗合理的话(这个时间规则就根据实际业务来定),就算拿到了锁。否则再把锁依次释放掉。

31-1. redis 事物原子性:命令入队时就报错,会放弃事务执行,保证原子性;命令入队时没报错,实际执行时报错,不保证原子性;EXEC 命令执行时实例故障,如果开启了 AOF 日志,可以保证原子性。
31-2. ACID 的解释:http://blog.itpub.net/26736162/viewspace-2141490/
31-3. 一直性:对于redis 到底什么才算一直性呢?这个有待商榷,与其讨论一直性这个名词,不如直接讨论一个实际的场景下redis的行为方式。
31-4. 隔离性:并发操作在 EXEC 命令前执行,此时,隔离性的保证要使用 WATCH 机制来实现,否则隔离性无法保证;并发操作在 EXEC 命令后执行,此时,隔离性可以保证。
31-5. WATCH 机制的作用是,在事务执行前,监控一个或多个键的值变化情况,当事务调用 EXEC 命令执行时,WATCH 机制会先检查监控的键是否被其它客户端修改了。如果修改了,就放弃事务执行,避免事务的隔离性被破坏。然后,客户端可以再次执行事务,此时,如果没有并发修改事务数据的操作了,事务就能正常执行,隔离性也得到了保证。
31-6. 持久性:如果 Redis 使用了 RDB 模式,那么,在一个事务执行后,而下一次的 RDB 快照还未执行前,如果发生了实例宕机,这种情况下,事务修改的数据也是不能保证持久化的。如果 Redis 采用了 AOF 模式,因为 AOF 模式的三种配置选项 no、everysec 和 always 都会存在数据丢失的情况,所以,事务的持久性属性也还是得不到保证。所以,不管 Redis 采用什么持久化模式,事务的持久性属性是得不到保证的。
31-7. always 也会有数据丢失吗:其实我们每次执行客户端命令的时候操作并没有写到aof文件中,只是写到了aof_buf内存当中,当进行下一个事件循环的时候执行beforeSleep之时,才会去fsync到disk中。在fsync之前已经把执行结果返回给客户端了。

32-1. 主从数据不一致情况一:主库更新的数据从库上还没来得及更新,解决:升级硬件,提高网速,将空复制延迟时间,在业务上做止损处理。
32-2. Redis 同时使用了两种策略来删除过期的数据,分别是惰性删除策略和定期删除策略。先说惰性删除策略。当一个数据的过期时间到了以后,并不会立即删除数据,而是等到再有请求来读写这个数据时,对数据进行检查,如果发现数据已经过期了,再删除这个数据。定期删除策略是指,Redis 每隔一段时间(默认 100ms),就会随机选出一定数量的数据,检查它们是否过期,并把其中过期的数据删除
32-3. 定期删除是随机选出一些删除的,所以还会存在一些过期数据。
32-3. 惰性删除下,从库不会自己删除数据。所以如果主库上的过期数据没有被访问过,从库上的过期数据就一直存在。
32-4. 那么,从库会给客户端返回过期数据吗?这就和你使用的Redis版本有关了。如果你使用的是Redis3.2之前的版本,那么,从库在服务读请求时,并不会判断数据是否过期,而是会返回过期数据。在3.2版本后,Redis 做了改进,如果读取的数据已经过期了,从库虽然不会删除,但是会返回空值,这就避免了客户端读到过期数据。
32-5. EXPIRE/PEXPIRE 计算的过期时间是从执行的时间开始的,所以从库上的过期时间就有可能延后。EXPIREAT/PEXPIREAT 命令 就没关系了。

33-1. 脑裂:在主从切换的过程中,如果原主库只是“假故障”,它会触发哨兵启动主从切换,一旦等它从假故障中恢复后,又开始处理请求,这样一来,就会和新主库同时存在,形成脑裂。等到哨兵让原主库和新主库做全量同步后,原主库在切换期间保存的数据就丢失了。
33-2. 可以把 min-slaves-to-write 和 min-slaves-max-lag 这两个配置项搭配起来使用,分别给它们设置一定的阈值,假设为 N 和 T。这两个配置项组合后的要求是,主库连接的从库中至少有 N 个从库,和主库进行数据复制时的 ACK 消息延迟不能超过 T 秒,否则,主库就不会再接收客户端的请求了。即使原主库是假故障,它在假故障期间也无法响应哨兵心跳,也不能和从库进行同步,自然也就无法和从库进行 ACK 确认了。这样一来,min-slaves-to-write 和 min-slaves-max-lag 的组合要求就无法得到满足,原主库就会被限制接收客户端请求,客户端也就不能在原主库中写入新数据了。

34-1. 从库永远不要自己改动或删除任何数据。否则就一定有主从不一致的情况。

35-1. Codis 的整体架构和基本流程:codis server:这是进行了二次开发的 Redis 实例,其中增加了额外的数据结构,支持数据迁移操作,主要负责处理具体的数据读写请求。codis proxy:接收客户端请求,并把请求转发给 codis server。Zookeeper 集群:保存集群元数据,例如数据位置信息和 codis proxy 信息。codis dashboard 和 codis fe:共同组成了集群管理工具。其中,codis dashboard 负责执行集群管理工作,包括增删 codis server、codis proxy 和进行数据迁移。而 codis fe 负责提供 dashboard 的 Web 操作界面,便于我们直接在 Web 界面上进行集群管理。
35-2. 在 Codis 集群中,一个数据应该保存在哪个 codis server 上,这是通过逻辑槽(Slot)映射来完成的,具体来说,总共分成两步。第一步,Codis 集群一共有 1024 个 Slot,编号依次是 0 到 1023。我们可以把这些 Slot 手动分配给 codis server,每个 server 上包含一部分 Slot。当然,我们也可以让 codis dashboard 进行自动分配,例如,dashboard 把 1024 个 Slot 在所有 server 上均分。第二步,当客户端要读写数据时,会使用 CRC32 算法计算数据 key 的哈希值,并把这个哈希值对 1024 取模。而取模后的值,则对应 Slot 的编号。此时,根据第一步分配的 Slot 和 server 对应关系,我们就可以知道数据保存在哪个 server 上了。
35-3.Codis 中的路由表是我们通过 codis dashboard 分配和修改的,并被保存在 Zookeeper 集群中。在 Redis Cluster 中,数据路由表是通过每个实例相互间的通信传递的,最后会在每个实例上保存一份。
35-4. 在 Codis 集群中,客户端是和 codis proxy 直接连接的,所以,当客户端增加时,一个 proxy 无法支撑大量的请求操作,此时,我们就需要增加 proxy。

37-1. 数据倾斜值数据量或者访问量再切片上分布非常不均匀。
37-2. 数据量倾斜的原因:bigkey,slot分布不均,Hash Tag 导致倾斜,Hash Tag 是指加在键值对 key 中的一对花括号{}。这对括号会把 key 的一部分括起来,客户端在计算 key 的 CRC16 值时,只对 Hash Tag 花括号中的 key 内容进行计算。
37-3. 热点数据造成访问量不均的情况可以采用热点数据多副本的方法来应对。

38-1. 配置项 cluster-node-timeout 定义了集群实例被判断为故障的心跳超时时间,默认是 15 秒。如果 cluster-node-timeout 值比较小,那么,在大规模集群中,就会比较频繁地出现 PONG 消息接收超时的情况