1.Redis 三种集群模式?
- 主从模式 可以实现读写分离,数据备份。但是并不是「高可用」的
- 哨兵模式 可以看做是主从模式的「高可用」版本,其引入了 Sentinel 对整个 Redis服务集群进行监控。但是由于只有一个主节点,因此仍然有写入瓶颈。
- Cluster 模式 不仅提供了高可用的手段,同时数据是分片保存在各个节点中的,可以支持高并发的写入与读取。
2.sds的结构?
- sds是Redis里面的一种结构,通过它对字符串的操作进行了很多的优化操作。
struct stdhdr {
int len //记录buff数组中已使用字节的数量
int free //记录buff数组中未使用空间字节的数量
char buff[] //字节数组,用于保存字符串
}
- 空间预分配用于优化SDS的字符串增长操作,当对SDS字符做修改时,不仅会分配所需要的空间,还会为SDS分配额外的未使用空间。
- 惰性空间释放用于优于SDS的字符串缩短操作,当缩短SDS字符串时,程序不立即使用内存重分配回收多出来的字节,而是用free属性记录,下交要用时,查看 free的大小,避免重复分配了。
3.SDS与C字符串的区别?
- C语言使用长度N+1的字符数组来表示长度为N的字符串,并且最后一个字符串总是空字符串’\0‘,而且它并不记录自身的长度信息,如果要获取,必须全部遍历;但SDS不一样,他有len属性记录了长度,所以获取长度的复杂度很高。
- C字符串容易缓冲区溢出,而SDS的空间分配策略则不会,当需要操作字符串时,会先检测空间大小,如果不满足,则需要对空间做扩展。
- C在对字符串做修改时,因为没有记录长度信息,所以需要频繁对内存做分配,而SDS通过free属性来记录未使用的空间,实现空间预分配和惰性空间释放。
4.hash 怎么实现的?怎么解决 hash 冲突?除了 hashTable 还有别的吗?
- Redis 中的 Hash是数组+链表的结构。
- 发生 hash 碰撞时把元素追加到链表上.值得注意的是在 Redis 的 Hash 中 value 只能是字符串。
- ziplist,内部更加紧凑。当键的个数小于hash-max-ziplist-entries(默认512)的配置时;同时所有值小于hash-max-ziplist-value(默认64)的配置时才会使用ziplist。
5.zset 怎么实现的?跳表是怎么插入的?为什么不使用List\红黑树或平衡二叉树呢?除了跳表还有别的吗?
-
通过一种名为 SkipList(跳跃表) 的数据结构来实现的。
-
采取的策略是通过抛硬币来决定新插入结点跨越的层数:每次我们要插入一个结点的时候,就来抛硬币,如果抛出来的是正面,则继续抛,直到出现负面为止,统计这个过程中出现正面的次数,这个次数作为结点跨越的层数
-
List是顺序存储,访问速度很快,但是添加和删除操作是一个(On)的操作,对于Redis这样要求高写入和高读取的数据库来说,List显然不能满足其要求。至于红黑树和平衡二叉树,每次更新redis的值,都要调整树结构,这样会造成额外的开销,而跳跃表只需要调整表局部的链表结构就行,显然跳跃表更适合。跳跃表实现起来更简单。
-
ziplist(压缩链表),保存的元素少于128个,保存的所有元素大小都小于64字节。
6.rehash 过程?
-
为ht[1]分配空间,让字典同时拥有两个哈希表,在字典中维持一个rehashidx变量设为0,表示rehash正式开始。
-
在rehash期间,每次对字典进行添加,删除,查找或者更新操作时,程序除了执行指定的操作外,还会顺带将将ht[0]哈希表在rehashidx索引上的所有键值对rehash到ht[1],当rehash工作完成后,程序将rehashidx属性+1
-
随着字典操作不断执行,最终在某个时间点上,ht[0]的所有键值对都会rehash至ht[1],这时程序会将rehash属性的值设为-1,表示rehash操作已完成。
7.redis 实现队列?怎么实现一个延时队列?
-
基于List的 LPUSH+BRPOP 的实现;PUB/SUB,订阅/发布模式。
-
使用 zset(sortedset)这个命令,用设置好的时间戳作为score进行排序,再利用 zrangebysocre
查询符合条件的所有待处理的任务,通过循环执行队列任务即可。
8.rdb 和 aof 过程?rdb 为什么用创建子进程的方式进行?
- RDB持久化:把当前进程数据生成快照保存到硬盘的过程;AOF(append only file)持久化:以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中命令达到恢复数据的目的。
- 通过 fork 创建子进程时不会立刻触发大量内存的拷贝,内存在被修改时会以页为单位进行拷贝,这也就避免了大量拷贝内存而带来的性能问题;
9.redis 为什么快?Redis为什么使用单线程?
- 纯内存KV操作;内部是单程实现的(不需要创建/销毁线程,避免上下文切换,无并发资源竞争的问题);异步非阻塞的I/O(多路复用)。
- Redis的使用瓶颈并不是CPU,它主要受到内存和网络的限制,单线程切换开销小,容易实现。
10.一致性哈希是什么?节点较少时数据分布不均匀怎么办?
- 首先求出memcached服务器(节点)的哈希值,并将其配置到0~232的圆(continuum)上。
然后采用同样的方法求出存储数据的键的哈希值,并映射到相同的圆上。
然后从数据映射到的位置开始顺时针查找,将数据保存到找到的第一个服务器上。如果超过232仍然找不到服务器,就会保存到第一台memcached服务器上。 - 为了解决数据倾斜问题,一致性Hash算法引入了虚拟节点机制,即对每一个服务节点计算多个哈希,每个计算结果位置都放置一个此服务节点,称为虚拟节点。具体做法可以在服务器IP或主机名的后面增加编号来实现。
11.几种 key 的淘汰策略?
- noeviction:当内存使用超过配置的时候会返回错误,不会驱逐任何键
allkeys-lru:加入键的时候,如果过限,首先通过LRU算法驱逐最久没有使用的键
volatile-lru:加入键的时候如果过限,首先从设置了过期时间的键集合中驱逐最久没有使用的键
allkeys-random:加入键的时候如果过限,从所有key随机删除
volatile-random:加入键的时候如果过限,从过期键的集合中随机驱逐
volatile-ttl:从配置了过期时间的键中驱逐马上就要过期的键
volatile-lfu:从所有配置了过期时间的键中驱逐使用频率最少的键 allkeys-lfu:从所有键中驱逐使用频率最少的键
12.lua 脚本的作用是什么?
-
高效性:减少网络开销及时延,多次redis服务器网络请求的操作,使用LUA脚本可以用一个请求完成
-
数据可靠性:Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。
-
复用性:LUA脚本执行后会永久存储在Redis服务器端,其他客户端可以直接复用
13.缓存击穿 / 穿透 / 雪崩的处理策略?
-
缓存和数据库中都不存在的数据,可以将此key对应的value设置为一个默认的值,比如“NULL”,并设置一个缓存的失效时间,这时在缓存失效之前,所有通过此key的访问都被缓存挡住了。
-
对热点key,加互斥锁,设置热点数据永不过期。
-
设置热点数据永不过期,过期时间设置随机,防止同一时间大量数据过期现象发生。