redis主从是怎么选取的
redis插槽的分配
redis复制的过程
redis主节点宕机了怎么办,还有没有同步的数据怎么办
重入锁是怎么计数的
1、redis 集群部署、只是部署了,核心的哈希槽,故障处理 缺乏运维经验。
zset底层、skiplist
分布式锁
参考自:http://blog.csdn.net/u013322876/article/details/78953360
为阅读方便,个人有删减,原版请访问上面链接。
事件轮询,多路复用,非阻塞IO,
redis的定时任务,维护在
1、Redis有哪些数据结构?
String、Hash、List、Set、Zset。
Bitmaps、HyperLogLog、Geo、Pub/Sub。
如果你说还玩过Redis Module,像BloomFilter,RedisSearch,Redis-ML,面试官得眼睛就开始发亮了。
签到记录:用bitmap位图。
大流量网站的uv统计:用HyperLogLog,需要浪费点空间。
推荐系统的去重:bloom filter,空间换时间,。
附近的人:Geo地图。
2、使用过Redis分布式锁么,它是什么回事?
先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放。
如果在setnx之后执行expire之前进程意外crash或者要重启维护了,那会怎么样?这个锁就永远得不到释放了。
set指令有非常复杂的参数,可以同时把setnx和expire合成一条指令来用的!set(key,value,'EX 60','NX');
但redis锁不能用于耗时较长的任务场景,加随机数?借助lua保证原子串行
可重入锁
锁冲突了怎么办?1)抛异常,让用户稍后重试;2)程序sleep一会,程序重试;3)扔进重试队列,由兜底队列重试。
3、假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如果将它们全部找出来?
keys,redis的单线程的。keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。
用scan指令替代,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。hscan代替hgetall。
4、使用过Redis做异步队列么,你是怎么用的?
一般使用list结构作为队列,rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一会再重试。
如果对方追问可不可以不用sleep呢?list还有个指令叫blpop,在没有消息的时候,它会阻塞住直到消息到来。
阻塞读,解决空轮询问题。如果阻塞时间太长,redis服务器会断开连接,减少闲置资源,所以客户端需要捕获异常,并重试。
如果对方追问能不能生产一次消费多次呢?使用pub/sub主题订阅者模式,可以实现1:N的消息队列。
但redis没有ack机制。
5、pub/sub有什么缺点?
在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如rabbitmq等。
6、如果对方追问redis如何实现延时队列?
使用sortedset,拿时间戳作为score,消息内容作为key。调用zadd来生产消息,消费者用zrangebyscore指令获取N秒之前的数据轮询进行处理。zrem方法是多线程争抢任务的关键,它的返回值决定了当前实例有没有抢到任务,通过zrem来决定唯一的属主。当然其他没抢到锁的,就是一次浪费,可以考虑用lua scripting优化,把zrangebescore和zrem一同挪进一个原子操作。
7、如果有大量的key需要设置同一时间过期,一般需要注意什么?
如果大量的key过期时间设置的过于集中,到过期的那个时间点,redis可能会出现短暂的卡顿现象。一般需要在时间上加一个随机值,使得过期时间分散一些。
8、Redis如何做持久化的?
bgsave做镜像全量持久化,aof做增量持久化。因为bgsave会耗费较长时间,不够实时,在停机的时候会导致大量丢失数据,所以需要aof来配合使用。在redis实例重启时,会使用bgsave持久化文件重新构建内存,再使用aof重放近期的操作指令来实现完整恢复重启之前的状态。
appendfsync属性值:always、everysec、no
9、那如果突然机器掉电会怎样?
取决于aof日志sync属性的配置,如果不要求性能,在每条写指令时都sync一下磁盘,就不会丢失数据。但是在高性能的要求下每次都sync是不现实的,一般都使用定时sync,比如1s1次,这个时候最多就会丢失1s的数据。
10、bgsave的原理是什么?
fork和cow。fork是指redis通过创建子进程来进行bgsave操作,cow指的是copy on write,子进程创建后,父子进程共享数据段,父进程继续提供读写服务,写脏的页面数据会逐渐和子进程分离开来。
11、Pipeline有什么好处,为什么要用pipeline?
批量,将多次IO往返的时间缩减为一次,前提是pipeline执行的指令之间没有因果相关性。使用redis-benchmark进行压测的时候可以发现影响redis的QPS峰值的一个重要因素是pipeline批次指令的数目。
12、Redis的同步机制了解么?
Redis可以使用主从同步,从从同步。第一次同步时,主节点做一次bgsave,并同时将后续修改操作记录到内存buffer,待完成后将rdb文件全量同步到复制节点,复制节点接受完成后将rdb镜像加载到内存。加载完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。
13、是否使用过Redis集群,集群的原理是什么?
Redis Sentinal着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务。
Redis Cluster着眼于扩展性,在单个redis内存不足时,使用Cluster进行分片sharding存储。
codis
twemproxy
14、主从复制的过程?增量复制,slave of psync
主从的复制偏移量 offset
主复制缓冲区 backlog ,大小设置为:2*每秒写数据量*从连主的时间
运行ID
slaveof、bgsave、sockets、ping/pong、auth验证、replconf发送端口信息、psync同步、命令传播
15、性能的瓶颈哪些
(1) Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件
(2) 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次
(3) 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内
(4) 尽量避免在压力很大的主库上增加从库
(5) 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3...
这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1做Master,其他不变。
1).Master写内存快照,save命令调度rdbSave函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务,所以Master最好不要写内存快照。
2).Master AOF持久化,如果不重写AOF文件,这个持久化方式对性能的影响是最小的,但是AOF文件会不断增大,AOF文件过大会影响Master重启的恢复速度。Master最好不要做任何持久化工作,包括内存快照和AOF日志文件,特别是不要启用内存快照做持久化,如果数据比较关键,某个Slave开启AOF备份数据,策略为每秒同步一次。
3).Master调用BGREWRITEAOF重写AOF文件,AOF在重写的时候会占大量的CPU和内存资源,导致服务load过高,出现短暂服务暂停现象。
4). Redis主从复制的性能问题,为了主从复制的速度和连接的稳定性,Slave和Master最好在同一个局域网内
16、阻塞问题
(save阻塞,所以改用bgsave,fork子进程)
17、键过期策略与数据淘汰策略
定时删除:对cpu压力大,但对内存好
惰性删除:对内存不好,浪费
定期删除:折中
redis使用惰性删除与定期删除两种策略。
rdb、aof、复制对过期键的处理
生成rdb不影响,过期的不会保存
载入rdb,若主服务器,删除;若从,不判断全部加载,因为与主同步后,从数据就被清空,从不会自作主张
aof不影响
复制:若从读到过期数据,依然返给客户端,直到接收到主的del才删除
redis 提供 6种数据淘汰策略:
- volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
- volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
- volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
- allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
- allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
- no-enviction(驱逐):禁止驱逐数据
注意这里的6种机制,volatile和allkeys规定了是对已设置过期时间的数据集淘汰数据还是从全部数据集淘汰数据,后面的lru、ttl以及random是三种不同的淘汰策略,再加上一种no-enviction永不回收的策略。
使用策略规则:
1、如果数据呈现幂律分布,也就是一部分数据访问频率高,一部分数据访问频率低,则使用allkeys-lru
2、如果数据呈现平等分布,也就是所有的数据访问频率都相同,则使用allkeys-random
18、redis事务不支持回滚
multi、exec、watch、
pipline
watch乐观锁,可用setnx实现锁来替代
19、skiplist ziplist
redis的sorted set 底层是由skiplist,dict,ziplist来实现的。
当数据较少时,sorted set是由一个ziplist来实现的。
当数据多的时候,sorted set是由一个zset来实现的,它包括dict和一个skiplist。其中dict用来查询data到score的关系,而skiplist用来根据score做范围查找。
ziplist提高了存储效率,是内存紧缩的列表,多个数据在一起的连续空间,不擅长修改,在两端pop,push快。ziplist结构由zip_header、zip_entry、zip_end三部分组成。
一张关于跳表和跳表搜索过程如下图:
在图中,需要寻找 68,在给出的查找过程中,利用跳表数据结构优势,只比较了 3次,横箭头不比较,竖箭头比较。由此可见,跳表预先间隔地保存了有序链表(底层应该是双向链表)中的节点,从而在查找过程中能达到类似于二分搜索的效果,而二分搜索思想就是通过比较中点数据放弃另一半的查找,从而节省一半的查找时间。
缺点即浪费了空间,自古空间和时间两难全.
20、sdshdr结构
/* 字符串结构体(字符串就是字符数组) */
struct sdshdr {
// 字符串当前长度
unsigned int len;
// 剩余可用长度
unsigned int free;
// 字符数组(具体存放字符串的地方)
char buf[];
};
O(1)获取字符串长度
防止缓冲区溢出
减少修改字符串时带来的内存重分配次数:内存预分配、惰性释放
二进制安全
在Redis中,不是靠空字符来判断字符串的结束的,而是通过len这个属性。那么,即便是中间出现了空字符对于SDS来说,读取该字符仍然是可以的。
21、redis异常案例处理
主从复制数据延迟、读过期数据、从节点故障
22、redis监控指标和运维shell命令
23、sentinel哨兵
raft算法选举领头sentinel
24、raft算法实现领导者选举
25、缓存设计
更新策略:低一致性时配置最大内存和淘汰策略结合(LRU/LFU/FIFO算法);高一致性时,设置超时expire和主动更新(mq或其他方式通知缓存更新)
穿透优化:1)缓存空对象null但expire设置更短一些、2)bloom filter在缓存层之前拦截
无底洞优化:1)hash_tag,redis cluster强制将多个key分布在一个节点,容易出现数据倾斜
雪崩优化:1)先保证redis高可用,redis sentinel和redis cluster、2)将上游依赖服务隔离、降级、限流、演练、预案
热点key重建:1)互斥锁mutex key只允许一个线程重建缓存,其余线程等待,缺点是若重建时间长容易死锁或线程阻塞;2)永远不过期,不显示设置expire但逻辑上设置expire,缺点是存在数据不一致情况,而且增大维护成本。
mutex_key='mutex:key:' + key;
if (redis.set(mutex_key, '1', 'ex 180', 'nx')) {
redis.setex(key, timeout, value);
//别忘了删除mutex_key
redis.delete(mutex_key);
} else {
//其他线程sleep 50ms重试
thread.sleep(50);
get(key);
}
26、redis运维
info命令
flush小心
bigkey问题
不要用keys,用scan;
27、redis应用
请用Redis和任意语言实现一段恶意登录保护的代码,限制1小时内每用户Id最多只能登录5次。
用列表实现:列表中每个元素代表登陆时间,只要最后的第5次登陆时间和现在时间差不超过1小时就禁止登陆.用Python写的代码如下:
#!/usr/bin/env python3
import
redis
import
sys
import
time
r = redis.StrictRedis(host=’
127.0
.
0.1
′, port=
6379
, db=
0
)
try
:
id = sys.argv[
1
]
except:
print(‘input argument error’)
sys.exit(
0
)
if
r.llen(id) >=
5
and time.time() –
float
(r.lindex(id,
4
)) <=
3600
:
print(“you are forbidden logining”)
else
:
print(‘you are allowed to login’)
r.lpush(id, time.time())
# login_func()
28、其他常见问题
渐进式rehash策略。rehash过程:h0、h1、rehashinx
一些参考资料
https://www.oschina.net/news/71144/codis-design
https://www.cnblogs.com/George1994/p/7421001.html