1. 有哪些数据类型?底层分别使用什么数据结构?
|
|
- String:
- int: 底层使用long类型存储
- raw: 简单动态字符串SDS(含总长和剩余长度信息),字符长度大于39 byte
- embstr: 字符串长度<= 39 byte,redisObject和sdshdr结构在一块连续的内存中,浮点数也使用这种结构存储
- List
- ziplist: 当元素个数小于512且元素长度小于64字节时使用
- linkedlist
- Hash
- ziplist: 当元素个数小于512且元素长度小于64字节时使用
- hashtable
- Set
- intset: 当元素个数小于512且元素全是整数时使用
- hashtable
- SortedSet
- ziplist: 当元素个数小于128且元素长度小于64字节时使用
- skiplist: 底层实际使用了skiplist和dict两种结构,且他们会通过指针来共享成员和分数
2. 持久化方式有哪些?
AOF(Append Only File)
AOF是写后日志,已追加的方式记录的是每一条写命令
三种写回策略
- Always: 每个写命令执行完后立即将日志写回磁盘
- Everysec: 将日志写入文件缓存,每秒执行fsyncl落盘
- No: 操作系统决定格式将缓冲区的内存写会磁盘
AOF重写机制 fork一个子进程bgrewriteaof,逐一将每个键值对用一条命令记录并写入临时文件,完成后会通过信号通知主进程,主进程会将AOF重新缓冲区的内容写入临时文件中,这时临时文件保存的内容和数据库一致,对临时文件改名,原子的覆盖 现有的AOF文件
RDB(Redis DataBase)
fork一个子进程将全量的数据以二进制的方式写入RDB文件
Redis4.0推出了混合使用AOF和RDB的方式,在两个RBD快照之间使用AOF日志记录期间的所有命令,等下一次做全量快照时,可以清空AOF日志
3. 主从同步是怎么样的?
初始同步
- 从库发送`psync ? -1命令申请全量复制
- 主库执行
bgsave
生成RBD文件,发送给从库加载数据 - 主库将期间新的写入操作replication buffer中的数据发送给从库
增量同步
主库有一个repl_backlog_buffer环形缓冲区,主库依据自己的master_repl_offset和从库的slave_repl_offset之间的操作发给从库
4. 如何保证高可用?
通过哨兵机制保证高可用,哨兵负责监控节点、选主和通知
- 监控:哨兵会周期性的给主/从节点发ping命令,如果规定时间内没有响应,对于从库将其标记为下线状态,对于主库则进行切换主库流程
- 选主:
- 客观下线:当有
quronum
个哨兵判断主库下线,才判断为客观下线 - 筛选打分:
- 去除已下线和总是断连的从节点
- 选择复制进度最高的节点
- 如果多个选ID最小的
- 客观下线:当有
- 通知:
- 新主库选好后,会给所有从库发信主库连接信息,随之从库会设置主库,重新从新主库同步数据
- 另外也会在
switch-master
频道里面发送主库信息,客户端可以通过订阅收到消息
5. 哨兵集群是如何工作的?
- 相互发现:通过pub/sub主库的
__sentinel:hello__
频道来相互发现,随之建立网络连接 - 发现从库:通过
INFO
命令获取从库信息 - Leader选举:当哨兵拿到
quronum
个主观下线后,就将主库标记为客观下线,随后发起选举命令,如果获得>=N/2+1且>=quronum
张票,则选为Leader(注:如果此轮没有产生Leader,会过段时间重试;每个哨兵的定时器执行周期都会加上一个小小的随机时间偏移,目的是让每个哨兵执行上述操作的时间能稍微错开些,也是为了避免它们都同时判定主库下线,同时选举Leader)
6. Cluster分片集群是如何工作的?
-
集群分为16384(2^14 == 2KB)个hash slot哈希槽,key通过CRC算法得到一个16bit的值,再对16384取模,确定落在哪个槽中
-
创建cluster时会自动给每个实例分配16384/N个槽,也可以手动指定
-
实例之间通过相互传递消息获取最新哈希槽分布信息,
-
客户端和集群连接时,会获得哈希槽对应节点的分布信息,并缓存在本地,之后如果分布信息变更后,客户端把命名发送给了旧的节点,旧的节点会将新节点的地址信息告知客户端,其中有 两种状态:
- 哈希槽已经迁移完毕,旧节点会返回
MOVED
信息,客户端的缓存也会更新
1 2
GET hello:key (error) MOVED 13320 172.16.19.5:6379
- 哈希槽正在迁移,旧节点会返回
ASK
信息
1 2
GET hello:key (error) ASK 13320 172.16.19.5:6379
- 哈希槽已经迁移完毕,旧节点会返回
7. 事务的实现?
通过结合WATCH
、MULTI
和EXEC
命令实现事务,执行EXEC
时会检查WATCH
的键是否有变更,如有则取消事务
原子性:如果执行事务命令队列中的某个命令出错,仍会继续执行下去,无法回滚
8. 有哪些耗时操作?
- 全量查询和聚合等复杂操作
- bigkey删除:释放大量内存很耗时 (用专门负责删除的子线程来处理)
- AOF写回磁盘 (频率设置为everysec后用专门负责AOF日志落盘的子线程来处理)
9. 有哪些淘汰缓存策略?
不进行淘汰:noeviction
对所有数据进行淘汰
- allkeys-lru
- allkeys-random
- allkeys-lfu
对设置了过期时间(无论有没有到期)的数据进行淘汰
- volatile-random: 随机删除
- volatile-ttl: 越早过期的数据越先被删除
- volatile-lru: 每个RedisObject有一个lru字段,记录最新访问时间,淘汰数据时会随机选取N个数据作为一个候选集合,将lru字段最小的数据淘汰
- volatile-lfu: 在lru的基础上为每个数据统计访问次数,优先淘汰次数低的;范围次数相同的情况下淘汰lru更小的
10. 有哪些缓存使用模式?
- Cache Aside只读缓存:
- 读操作命中缓存则直接返回,否则从后端数据库加载到缓存中再返回
- 写操作直接更新数据库然后删除缓存
- Read/Write Throught 同步直写
- 写操作会更新缓存和数据库,然后给客户端返回
- Read/Write Write Back 异步写回
- 写操作先更新缓存,等到缓存要淘汰时将它们写回数据库
如果需要给写请求加速,选择读写缓存;如果写请求很少选择只读缓存
11. 有哪些缓存异常
缓存雪崩: 大量请求无法在Redis中进行处理,导致数据库层压力激增
通常因为大量数据同时过期,可以给数据过期时间加一个小的随机数
可通过服务降级来应对
缓存击穿:热点请求没有在缓存中处理
通常发生在热点数据过期时,可以对热点数据不设置过期时间
缓存穿透:访问的数据既不在redis中也不在数据库中
解决方案:a. redis缓存一个缺省值 b. 查询数据库时先通过查询布隆过滤器快速判断数据是否存在
12. 分布式锁实现?
单个redis
|
|
使用SETNX和DEL, 为了DEL可能异常,需要给key设置过期时间
多个redis节点 Redlock
分为三步:
- 客户端获取当前时间
- 按顺序依次想N个redis示例执行加锁操作,加锁操作设置超时时间,超时则认为该示例加锁失败
- 计算加锁总耗时
如果获取了N/2+1把锁且总耗时没有超过锁的有效时间则认为加锁成功
- 哈希槽正在迁移,旧节点会返回ASK
信息
```redis
GET hello:key
(error) ASK 13320 172.16.19.5:6379
```