一个字符串类型的值能存储最大容量是多少
https://redis.io/topics/data-types
String类型:一个String类型的value最大可以存储512M
Lists类型:list的元素个数最多为2^32-1个,也就是4294967295个。
Sets类型:元素个数最多为2^32-1个,也就是4294967295个。
Hashes类型:键值对个数最多为2^32-1个,也就是4294967295个。
Sorted sets类型:跟Sets类型相似。
Redis 的持久化机制是什么?各自的优缺点?
https://edgar615.github.io/redis-rdb.html
https://edgar615.github.io/redis-aof.html
RDB快照、AOF日志、RDB和AOF混合持久化,
RDB是全量快照,可以通过配置定时执行,也可以手动通过命令执行。它的文件很小,适合备份,启动时加载RDB文件比加载AOF文件要更快;但是它不能做到实时持久化,数据有丢失风险
AOF将对redis的写入命令追加到日志文件,先写缓冲池,在写磁盘。它将日志最终刷到新磁盘有3种方式:
- appendfsync always #命令写入aof_buf后调用系统fsync同步到AOF文件,fsync完成后线程返回
- appendfsync everysec #命令写入aof_buf后调用系统write操作,write完成后线程返回,fsync同步文件操作由专门的线程每秒调用一次
- appendfsync no #令写入aof_buf后调用系统write操作,不对AOF文件做fsyunc同步,同步磁盘操作由操作系统负责,通常同步周期最长30秒
根据刷盘的设置不同,也会有丢失数据的风险。
AOF也支持重写,用了压缩日志文件
当redis做RDB或AOF重写时,一个必不可少的操作就是执行fork操作创建应子进程。对于大多数操作系统来说fork是一个重量级操作。虽然fork创建的子进程不需要拷贝父进程的物理内存空间,但是会复制父进程的空间内存表。例如对于10GB的redis进程,需要复制大约20MB的内存页表,因此fork操作耗时跟进程总内存量息息相关 fork耗时跟内存量成正比,建议控制每个redis实例的内存(每个页表条目消耗 8 个字节)
Redis 过期键的删除策略
https://edgar615.github.io/redis-expire-policy.html
Redis支持两种方式:
- 定期删除:redis 会把所有过期的键值对加入到 expires字典。redis内部有一个定时任务,100毫秒执行一次,会在定时任务的回调函数中执行清理任务,每次只随机清理部分过期key,每次清理时间不能超过CPU时间的25%,如果消耗过大,会自动退出清理过程。因此定期删除不能保证过期key都被清理。del key 也是利用的expire机制
- 惰性删除 当访问一个 Key 时,才判断该 Key 是否过期,过期则删除。但是这种方式对内存不友好
但是redis的删除策略存在一个问题:如果过期 Key 较多,定期删除漏掉了一部分,而且也没有及时去查,即没有走惰性删除,那么就会有大量的过期 Key 堆积在内存中,导致 Redis 内存耗尽。
Redis 的回收策略(淘汰策略)
https://edgar615.github.io/redis-maxmemory-policy.html
Redis 会在每一次处理命令的时候(processCommand 函数调用 freeMemoryIfNeeded)判断当前 Redis 是否达到了内存的最大限制,如果达到限制,则使用对应的算法去处理需要删除的 Key
- noeviction 默认值,不会删除任何数据,拒绝所有写入操作并返回错误信息。此时redis只响应读操作
- volatile-lru 首先通过LRU算法从设置了过期时间的键集合中驱逐最久没有使用的键,直到腾出足够的空间为止。如果没有可删除的键对象,回退到noeviction策略
- allkeys-lru 首先通过LRU算法驱逐最久没有使用的键根据,不管数据有没有设置超时属性,直到腾出足够空间为止
- allkeys-random 随机删除所有键,直到腾出足够空间为止
- volatile-random 随机删除过期键,直到腾出足够空间为止
- volatile-ttl 根据键值对象的TTL过期时间,删除最近将要过期的数据,如果没有,回退到noeviction策略
- volatile-lfu 从所有配置了过期时间的键中驱逐使用频率最少的键,如果没有,回退到noeviction策略
- allkeys-lfu 从所有键中驱逐使用频率最少的键
简单归纳一下淘汰策略:
- 默认不做任何处理,拒绝写入操作,只响应读操作
- 按照LRU算法淘汰最久没有使用的key,key的选择可以是所有key或者设置了过期时间的key
- 按照LFU算法淘汰使用频率最少的key,key的选择可以是所有key或者设置了过期时间的key
- 按照随机算法淘汰key,key的选择可以是所有key或者设置了过期时间的key
- 根据key的过期时间淘汰最近要过期的key
Pipeline 有什么好处
批量执行指令、节省多次IO的时间
Redis key 永久有效怎么设置
persist key
Redis 的同步机制
https://edgar615.github.io/redis-slave.html
- 从节点执行 slaveof 命令。
- 从节点只是保存了 slaveof 命令中主节点的信息,并没有立即发起复制。
- 从节点内部的定时任务发现有主节点的信息,开始使用 socket 连接主节点。
- 连接建立成功后,发送 ping 命令,希望得到 pong 命令响应,否则会进行重连。
- 如果主节点设置了权限,那么就需要进行权限验证,如果验证失败,复制终止。
- 权限验证通过后,进行数据同步,这是耗时最长的操作,主节点将把所有的数据全部发送给从节点。
- 当主节点把当前的数据同步给从节点后,便完成了复制的建立流程。接下来,主节点就会持续的把写命令发送给从节点,保证主从数据一致性。
redis内部通过psync完成数据同步,当启动slave节点时,它会发送psync
命令给主节点,从向主传递主节点的runid以及自己的偏移量。
在数据同步阶段主节点新写入的命令会单独记录在buffer,然后当RDB文件加载完毕之后,会通过偏移量对比将这个期间产生的写入值同步给slave
redis的复制分为全量复制和部分复制两种
全量复制的开销很大,假如master和slave网络发生了抖动,那一段时间内这些数据就会丢失,对于slave来说这段时间master更新的数据是不知道的。最简单的方式就是再做一次全量复制,从而获取到最新的数据。redis会了优化这个问题,实现了部分复制的方案
部分复制:当从节点再次连上主节点后,如果条件允许,主节点会补发丢失数据给从节点。因为补发的数据远远小于全量数据,可以有效避免全量复制的过高开销,需要注意的是,如果网络中断时间过长,造成主节点没有能够完整地保存中断期间执行的写命令,则无法进行部分复制,仍使用全量复制
复制积压缓冲区:由主节点维护的、固定长度的、先进先出(FIFO)队列,默认大小 1MB。当主节点开始有从节点时创建,其作用是备份主节点最近发送给从节点的数据。注意,无论主节点有一个还是多个从节点,都只需要一个复制积压缓冲区。由于该缓冲区长度固定且有限,因此可以备份的写命令也有限,当主从节点 offset 的差距过大超过缓冲区长度时,将无法执行部分复制,只能执行全量复制。
从节点在心跳中回复主节点偏移量
Redis 常见性能问题和解决方案
通过redis-cli --bigkeys
发现大对象
使用info stats
检查lastest_fork_usec
指标,获取redis最近一次fork操作耗时
检查日志是否有AOF的阻塞日志,然后通过info persistence
检查aof_delay_fsync
指标
检查当前连接数、拒绝连接数
监控内存碎片率,避免swap
哪些办法可以降低 Redis 的内存使用情况
尽量减少key的长度
尽量使用合适的编码方式
- 如果一个字符串对象保存的是一个字符串值,并且长度大于32字节,那么该字符串对象将使用 SDS 进行保存,并将对象的编码设置为 raw。如果字符串的长度小于32字节,那么字符串对象将使用embstr 编码方式来保存。
- 列表对象保存的所有元素的长度都小于 64 字节且元素数量小于512个使用 ziplist 编码,否则使用linkedlist。
- 集合对象保存的元素都是整数且元素数量小于512个使用 intset编码,否则使用dict。
- 有序集合对象保存的所有元素的长度都小于 64 字节且元素数量小于128个使用 ziplist 编码,否则使用linkedlist。
- 哈希对象保存的所有字符串元素的长度都小于 64 字节且键值对数量小于512个使用 ziplist 编码,否则使用dict。
使用过 Redis 分布式锁么
https://edgar615.github.io/distributed-lock.html
加锁
SET resource_name my_random_value NX PX 30000
- my_random_value 在所有的客户端和请求锁的请求中必须唯一,因为加锁和解锁必须是同一个客户端
- NX 在指定的 key 不存在时,为 key 设置指定的值。保证了只有第一个请求的客户端能持有锁,而其它客户端在锁被释放之前都无法获得锁,满足互斥性。
- PX 指定key的过期时间,单位毫秒。即使锁的持有者后续发生崩溃而没有解锁,锁也会因为到了过期时间而自动解锁(即key被删除),不会发生死锁。
释放锁
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
因为释放锁涉及到两个命令,不是原子性,所以需要使用lua来保证原子性
缺陷:如果客户端拿到锁之后设置了超时时长,但是业务执行的时长超过了超时时长,导致客户端还在执行业务但是锁已经被释放,此时其他进程就会拿到锁从而执行相同的业务,此时因为并发导致分布式锁失去了意义
Redis如何解决key冲突 拉链法
一般来说解决key值冲突的方法有俩种,一种是开放地址法,另一种就是拉链法。