赞
踩
扛数据高并发读写,减少mysql数据库压力
单线程:并发安全;高性能;原语与数据结构丰富;采用广泛,踩坑成本低
引申Q:Redis高性能的原因大概可以讲一些?(Redis高并发快的原因?)
首先我们知道数据存储和访问之间的结构大致分为三层,永久存储区域(磁盘、网络设备),临时存储区域(高速缓存L1/L2/L3,和物理内存和虚拟内存),和CPU寄存器。
他们的执行顺序CPU寄存器>临时存储区域>永久存储区域。
- Redis是纯内存数据库,一般都是简单的存取操作,线程占用的时间很多,时间的花费主要集中在IO上,所以读取速度快。
- 再说一下IO,Redis使用的是非阻塞IO,IO多路复用,使用了单线程来轮询描述符,将数据库的开、关、读、写都转换成了事件,减少了线程切换时上下文的切换和竞争。
- Redis采用了单线程的模型,保证了每个操作的原子性,也减少了线程的上下文切换和竞争。
- 另外,数据结构也帮了不少忙,Redis全程使用hash结构,读取速度快,还有一些特殊的数据结构,对数据存储进行了优化,如压缩表,对短数据进行压缩存储,再如,跳表,使用有序的数据结构加快读取的速度。
- 还有一点,Redis采用自己实现的事件分离器,效率比较高,内部采用非阻塞的执行方式,吞吐能力比较大。
引申Q:讲讲多路复用模型(redis的io模型讲讲)?
多路-指的是多个socket连接,复用-指的是复用一个线程。多路复用主要有三种技术:select,poll,epoll。epoll是最新的也是目前最好的多路复用技术。
这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络IO的时间消耗),且Redis在内存中操作数据的速度非常快(内存内的操作不会成为这里的性能瓶颈),主要以上两点造就了Redis具有很高的吞吐量。
redis 的线程模型
redis 内部使用文件事件处理器 file event handler,这个文件事件处理器是单线程的,所以 redis 才叫做单线程的模型。它采用 IO 多路复用机制同时监听多个 socket,根据 socket 上的事件来选择对应的事件处理器进行处理。
文件事件处理器的结构包含 4 个部分:
多个 socket
IO 多路复用程序
文件事件分派器
事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)
多个 socket 可能会并发产生不同的操作,每个操作对应不同的文件事件,但是 IO 多路复用程序会监听多个 socket,会将 socket 产生的事件放入队列中排队,事件分派器每次从队列中取出一个事件,把该事件交给对应的事件处理器进行处理。
客户端与 redis 的一次通信过程:
客户端 socket01 向 redis 的 server socket 请求建立连接,此时 server socket 会产生一个 AE_READABLE 事件,IO 多路复用程序监听到 server socket 产生的事件后,将该事件压入队列中。文件事件分派器从队列中获取该事件,交给连接应答处理器。连接应答处理器会创建一个能与客户端通信的 socket01,并将该 socket01 的 AE_READABLE 事件与命令请求处理器关联。
假设此时客户端发送了一个 set key value 请求,此时 redis 中的 socket01 会产生 AE_READABLE 事件,IO 多路复用程序将事件压入队列,此时事件分派器从队列中获取到该事件,由于前面 socket01 的 AE_READABLE 事件已经与命令请求处理器关联,因此事件分派器将事件交给命令请求处理器来处理。命令请求处理器读取 socket01 的 key value 并在自己内存中完成 key value 的设置。操作完成后,它会将 socket01 的 AE_WRITABLE 事件与命令回复处理器关联。
如果此时客户端准备好接收返回结果了,那么 redis 中的 socket01 会产生一个 AE_WRITABLE 事件,同样压入队列中,事件分派器找到相关联的命令回复处理器,由命令回复处理器对 socket01 输入本次操作的一个结果,比如 ok,之后解除 socket01 的 AE_WRITABLE 事件与命令回复处理器的关联。
这样便完成了一次通信。
引申Q:redis为什么选择单线程?
官方答案:
因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案。
性能每秒10w+的请求
详细原因
不需要各种锁的性能消耗
Redis的数据结构并不全是简单的Key-Value,还有list,hash等复杂的结构,这些结构有可能会进行很细粒度的操作,比如在很长的列表后面添加一个元素,在hash当中添加或者删除
一个对象。这些操作可能就需要加非常多的锁,导致的结果是同步开销大大增加。
总之,在单线程的情况下,就不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗。
集群化的方案VS多线程
单线程的威力实际上非常强大,每核心效率也非常高,多线程自然是可以比单线程有更高的性能上限,但是在今天的计算环境中,即使是单机多线程的上限也往往不能满足需要了,需要进一步摸索的是多服务器集群化的方案
所以单线程、多进程的集群
采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU。
但是如果CPU成为Redis瓶颈,或者不想让服务器其他CUP核闲置,那怎么办?
可以考虑多起几个Redis进程,Redis是key-value数据库,不是关系数据库,数据之间没有约束。只要客户端分清哪些key放在哪个Redis进程上就可以
string
这是最简单的类型,就是普通的 set 和 get,做简单的 KV 缓存。
set college szu
hash
这个是类似 map 的一种结构,这个一般就是可以将结构化的数据,比如一个对象(前提是这个对象没嵌套其他的对象)给缓存在 redis 里,然后每次读写缓存的时候,可以就操作 hash 里的某个字段。
hset person name bingo
hset person age 20
hset person id 1
hget person name
person = {
“name”: “bingo”,
“age”: 20,
“id”: 1
}
list
list 是有序列表,这个可以玩儿出很多花样。
比如可以通过 list 存储一些列表型的数据结构,类似粉丝列表、文章的评论列表之类的东西。
比如可以通过 lrange 命令,读取某个闭区间内的元素,可以基于 list 实现分页查询,这个是很棒的一个功能,基于 redis 实现简单的高性能分页,可以做类似微博那种下拉不断分页的东西,性能高,就一页一页走。
0开始位置,-1结束位置,结束位置为-1时,表示列表的最后一个位置,即查看所有。
lrange mylist 0 -1
比如可以搞个简单的消息队列,从 list 头怼进去,从 list 尾巴那里弄出来。
lpush mylist 1
lpush mylist 2
lpush mylist 3 4 5
1
rpop mylist
set
set 是无序集合,自动去重。
直接基于 set 将系统里需要去重的数据扔进去,自动就给去重了,如果你需要对一些数据进行快速的全局去重,你当然也可以基于 jvm 内存里的 HashSet 进行去重,但是如果你的某个系统部署在多台机器上呢?得基于 redis 进行全局的 set 去重。
可以基于 set 玩儿交集、并集、差集的操作,比如交集吧,可以把两个人的粉丝列表整一个交集,看看俩人的共同好友是谁?对吧。
把两个大 V 的粉丝都放在两个 set 中,对两个 set 做交集。
#-------操作一个set-------
添加元素
sadd mySet 1
查看全部元素
smembers mySet
判断是否包含某个值
sismember mySet 3
删除某个/些元素
srem mySet 1
srem mySet 2 4
查看元素个数
scard mySet
随机删除一个元素
spop mySet
#-------操作多个set-------
将一个set的元素移动到另外一个set
smove yourSet mySet 2
求两set的交集
sinter yourSet mySet
#求两set的并集
sunion yourSet mySet
求在yourSet中而不在mySet中的元素
sdiff yourSet mySet
sorted set
sorted set 是排序的 set,去重但可以排序,写进去的时候给一个分数,自动根据分数排序。
zadd board 85 zhangsan
zadd board 72 lisi
zadd board 96 wangwu
zadd board 63 zhaoliu
获取排名前三的用户(默认是升序,所以需要 rev 改为降序)
zrevrange board 0 3
获取某用户的排名
zrank board zhaoliu
引申Q:redis的hash数据结构最多能存储多少个元素
Strings类型:一个String类型的value最大可以存储512M
Lists类型:list的元素个数最多为2^32-1个,也就是4294967295个。
Sets类型:元素个数最多为2^32-1个,也就是4294967295个。
Hashes类型:键值对个数最多为2^32-1个,也就是4294967295个。
Sorted sets类型:跟Sets类型相似。
参考链接:https://redis.io/topics/data-types
引申Q:底层的编码有哪些?有序链表采用了哪些不同的编码?(Redis 底层用到了哪些数据结构?使用 Redis 的 set 来做过什么?)
encoding(编码)
REDIS_ENCODING_RAW 0 // 编码为字符串
REDIS_ENCODING_INT 1 // 编码为整数
REDIS_ENCODING_HT 2 // 编码为字典
REDIS_ENCODING_ZIPMAP 3 // 编码为 zipmap
REDIS_ENCODING_LINKEDLIST 4 // 编码为双端链表
REDIS_ENCODING_ZIPLIST 5 // 编码为压缩列表
REDIS_ENCODING_INTSET 6 // 编码为整数集合
REDIS_ENCODING_SKIPLIST 7 // 编码为跳跃表
参考链接:http://www.cnblogs.com/Aiapple/p/7248656.html
参考链接:https://www.cnblogs.com/Aiapple/p/7248968.html
redis常见问题:
阻塞问题
redis使用了单线程来处理请求,为什么单线程可以支持如此高的并发呢?主要有如下几点:
纯内存访问:将所有数据都放到内存中,内存响应时间为100纳秒,是redis达到每秒万级别访问的重要基础
非阻塞IO:redis使用epoll作为I/O多路复用技术,redis自身的事件处理模型将epoll中的连接、读写、关闭都转换为事件,不在网络I/O上浪费过多时间
单线程:避免了线程切换和竞态产生的消耗,简化了数据结构和算法的实现
因此如果某个命令执行时间过长,会造成其他命令阻塞,对redis来说是致命的
产生阻塞场景:
A. API或数据结构使用不合理
a. 避免使用某些易造成阻塞的命令如:keys sort hgetall smembers
执行showlog get [n] 可以获取最近n条执行慢的记录,对于执行超过一定时间
(默认10ms,线上建议设置为1ms)的命令都会记录到一个定长队列(默认128,可调整)中。
b. 防止一次操作获取过多数据:缩减大对象或者把大对象拆分为多个小对象
发现大对象的命令:redis-cli -h{ip} -p{port} bigkeys
内部原理:采用分段进行scan操作,把历史扫描过的大对象统计出来
c. 防止大量key同时过期:如果有很多key在同一秒内过期,超过了所有key的25%,redis主线程就会阻塞直到过期key比例下降到25%以内,
因此要避免同一时间过期大量key,过期时间可做散列处理。
redis4.0引入的lazyfree机制可以避免del、flushdb、flushall、rename等命令引起的redis-server阻塞,提高服务稳定性。
B. CPU饱和
单线程的redis处理命令时只能使用一个CPU,CPU饱和是指redis把单核的CPU使用率跑到接近100%。
首先要确定redis的并发量是否达到极限,通过redis-cli-h{ip} -p{port}–stat 获取redis当前使用情况。
如果达到每秒6w+左右的qps,说明单台已跑到极限,需要水平扩展。
如果qps只有几百或者几千CPU就已经饱和,可能使用了高算法复杂度的命令或者是对内存的过度优化
(如放宽了ziplist的使用条件,虽然使用的内存会变少,但是更耗CPU)。
C. 持久化操作
持久化引起主线程的阻塞操作主要有:fork阻塞、AOF刷盘阻塞、HugePage写操作阻塞
a. fork阻塞
发生在RDB和AOF重写时,redis主线程调用fork操作产生共享内存的子线程,由子线程完成持久化文件的重写工作,若fork操作耗时过长会引起阻塞。
避免使用内存过大的实例。
b. AOF刷盘阻塞
开启AOF持久化功能时,一般会采用1次/s的刷盘方式,后台线程每秒对AOF文件做fsync操作,当硬盘压力过大时fsync操作需要等待直到写入完成。
如果主线程距离上一次的fsync成功超过2s,为了数据安全会阻塞直到后台线程执行完fsync完成。这种阻塞是由于磁盘压力引起。
尽量独立部署
c. HugePage写操作阻塞
子进程在执行重写期间利用linux的copyonwrite机制,会拖慢写操作的执行时间,导致大量写操作慢查询。
优化linux配置
缓存穿透
缓存穿透是指查询一个根本不存在的数据,缓存层和存储层都不命中,且不将空结果写到缓存中。
会导致后端存储负载变大,造成后端存储宕机等问题。可以在程序中分别统计总调用数、缓存命中数、存储命中数,若有大量存储层空命中,可能是出现了缓存穿透。
产生原因:1.自身代码或数据出现问题 2.恶意攻击,爬虫造成空命中
如何解决:
请求参数检查
客户端传来的任何数据都是不可靠的,必须校验。
缓存空对象
每发生一次无效的缓存穿透,则让相同参数的缓存查询在一定时间内无法发生第二次。
存储层不命中,扔将空对象保存到缓存层。
适用场景:数据频繁变化、实时性高
带来问题:
a.缓存了空值,会占用内存空间;可以设置较短过期时间,自动剔除。
b.数据不一致,若存储层添加了此数据,有短暂不一致;可主动清除掉缓存的空对象。
布隆过滤器
在访问缓存层和数据层之前将存在的key用布隆过滤器提前保存起来,做第一层拦截。
同样是查询参数本身是合法的,但对于数据库没有意义的场景,还有另外一种思路,把所有合法的用户id存到一个Set中,在校验请求参数的同时,检查请求参数中的用户id是否存在于这个Set,如果存在则继续,不存在则直接返回。
适用场景:大用户集,实时性要求较低的场景,如有几亿的数据集,每隔一段时间会新增用户进去,在更新之前新用户的访问会存在缓存穿透问题。
缺点:代码维护复杂
缓存并发
缓存并发其实某种程度也是缓存穿透的问题,但为了细化区分,前文中描述的缓存穿透,本质上是客户端发来的“意外”请求产生的,而这里缓存并发产生的穿透,则往往网站业务并发量大的时候产生的,我们来看看它产生的场景。
行为简述
按一般的行为,如果一个缓存key失效了,我们会在重新向DB层请求数据,并降新的数据存如缓存层,但想象一个这样的场景,如果服务的并发访问量很高,同时有1000个客户端向服务器请求查询用户1的信息,而此时凑巧由于某些原因(例如缓存服务刚重启、该缓存刚刚过期),则此时会有多个线程查询key不存在,然后调用数据库查询用户1的情况,则此时会凭空产生999个多余的数据库查询,因为这999个请求只要等一下,等第1个DB查询的请求完成并顺利写入缓存以后,就可以顺利从缓存拿数据了。
基本思路就是利用缓存服务某些带成功操作返回值的操作,例如Redis的SetNX方法,用于模拟锁的实现。
SetNX的含义是“只有在 key 不存在时设置 key 的值。设置成功,返回 1 。 设置失败,返回 0 ”
无底洞问题
随着数据量和访问量的增长,需要增加更多的节点做水平扩容,键值会分布到更多的节点上,若客户端进行批量操作则通常会从不同的节点上获取数据,相比于单机批量操作只涉及一次网络操作,分布式批量操作会涉及多次网络交互。
随着节点数的增多,客户端一次批量操作涉及的网络交互耗时也会不断增大;网络连接数增多,对节点性能也有一定影响。
更多的节点不代表更高的性能,这就是无底洞问题。
雪崩问题
由于缓存层承载着大量请求,有效的保护了存储层,但如果缓存层由于某些原因不能提供服务,所有请求都会压到存储层,存储层流量暴增,导致存储层也会级联宕机。
保证缓存层服务高可用性
Redis Sentinel或者Redis Cluster都实现了高可用
隔离限流降级
对重要的资源Redis、Mysql、外部接口调用都进行隔离,机器、进程、线程等层面都可做隔离。
可使用漏桶、令牌桶等方式进行限流操作,将流量挡在应用上层。
对出现问题的数据或功能做降级处理,友好的展示给用户。
提前演练测试
热点key重建优化
缓存+过期时间策略即可以加速数据读写,又保证数据的定期更新,若出现如下两个问题,可能会对应用产生致命危害:
当前key是一个热点key,并发量非常大
重建缓存不能在短时间内完成,如:复杂的sql、多次IO、多个依赖等。
在缓存失效的瞬间,有大量的线程来创建缓存,造成后端负载加大,甚至导致系统崩溃。
方案:
a.互斥锁
只允许有一个线程去重建数据,其他线程等待构建完缓存,重新从缓存中获取数据。
b.永远不过期
设置逻辑过期时间,判断逻辑时间和当前时间大小,然后异步去构建数据覆盖老数据。
a方案思路简单,能保证一致性;但代码复杂度增大,存在死锁风险,存在线程池阻塞风险。
b方案基本可以杜绝热点key问题;但不保证一致性,逻辑过期时间增加代码维护成本。
引申Q:缓存穿透怎么解决,缓存击穿有哪些方案解决(天猫)
如上解释
引申Q:如何保证redis高可用( 如何保证 Redis 高并发、高可用?)
如果你用 redis 缓存技术的话,肯定要考虑如何用 redis 来加多台机器,保证 redis 是高并发的,还有就是如何让 redis 保证自己不是挂掉以后就直接死掉了,即 redis 高可用。
两个方面:
redis 主从架构
redis 基于哨兵实现高可用
redis 实现高并发主要依靠主从架构,一主多从,一般来说,很多项目其实就足够了,单主用来写入数据,单机几万 QPS,多从用来查询数据,多个从实例可以提供每秒 10w 的 QPS。
如果想要在实现高并发的同时,容纳大量的数据,那么就需要 redis 集群,使用 redis 集群之后,可以提供每秒几十万的读写并发。
redis 高可用,如果是做主从架构部署,那么加上哨兵就可以了,就可以实现,任何一个实例宕机,可以进行主备切换。
引申Q:Redis 的主从复制原理,以及Redis 的哨兵原理?(谈谈Redis哨兵、复制、集群,讲一下Redis的哨兵机制)
该问题对应上面的两个方面主从架构和基于哨兵实现高可用
主从复制原理
Redis 主从架构
单机的 redis,能够承载的 QPS 大概就在上万到几万不等。对于缓存来说,一般都是用来支撑读高并发的。因此架构做成主从(master-slave)架构,一主多从,主负责写,并且将数据复制到其它的 slave 节点,从节点负责读。所有的读请求全部走从节点。这样也可以很轻松实现水平扩容,支撑读高并发。
redis-master-slave
redis replication -> 主从架构 -> 读写分离 -> 水平扩容支撑读高并发
redis replication 的核心机制
redis 采用异步方式复制数据到 slave 节点,不过 redis2.8 开始,slave node 会周期性地确认自己每次复制的数据量;
一个 master node 是可以配置多个 slave node 的;
slave node 也可以连接其他的 slave node;
slave node 做复制的时候,不会 block master node 的正常工作;
slave node 在做复制的时候,也不会 block 对自己的查询操作,它会用旧的数据集来提供服务;但是复制完成的时候,需要删除旧数据集,加载新数据集,这个时候就会暂停对外服务了;
slave node 主要用来进行横向扩容,做读写分离,扩容的 slave node 可以提高读的吞吐量。
注意,如果采用了主从架构,那么建议必须开启 master node 的持久化,不建议用 slave node 作为 master node 的数据热备,因为那样的话,如果你关掉 master 的持久化,可能在 master 宕机重启的时候数据是空的,然后可能一经过复制, slave node 的数据也丢了。
另外,master 的各种备份方案,也需要做。万一本地的所有文件丢失了,从备份中挑选一份 rdb 去恢复 master,这样才能确保启动的时候,是有数据的,即使采用了后续讲解的高可用机制,slave node 可以自动接管 master node,但也可能 sentinel 还没检测到 master failure,master node 就自动重启了,还是可能导致上面所有的 slave node 数据被清空。
redis 主从复制的核心原理
当启动一个 slave node 的时候,它会发送一个 PSYNC 命令给 master node。
如果这是 slave node 初次连接到 master node,那么会触发一次 full resynchronization 全量复制。此时 master 会启动一个后台线程,开始生成一份 RDB 快照文件,同时还会将从客户端 client 新收到的所有写命令缓存在内存中。RDB 文件生成完毕后, master 会将这个 RDB 发送给 slave,slave 会先写入本地磁盘,然后再从本地磁盘加载到内存中,接着 master 会将内存中缓存的写命令发送到 slave,slave 也会同步这些数据。slave node 如果跟 master node 有网络故障,断开了连接,会自动重连,连接之后 master node 仅会复制给 slave 部分缺少的数据。
redis-master-slave-replication
主从复制的断点续传
从 redis2.8 开始,就支持主从复制的断点续传,如果主从复制过程中,网络连接断掉了,那么可以接着上次复制的地方,继续复制下去,而不是从头开始复制一份。
master node 会在内存中维护一个 backlog,master 和 slave 都会保存一个 replica offset 还有一个 master run id,offset 就是保存在 backlog 中的。如果 master 和 slave 网络连接断掉了,slave 会让 master 从上次 replica offset 开始继续复制,如果没有找到对应的 offset,那么就会执行一次 resynchronization。
如果根据 host+ip 定位 master node,是不靠谱的,如果 master node 重启或者数据出现了变化,那么 slave node 应该根据不同的 run id 区分。
无磁盘化复制
master 在内存中直接创建 RDB,然后发送给 slave,不会在自己本地落地磁盘了。只需要在配置文件中开启 repl-diskless-sync yes 即可。
repl-diskless-sync yes
等待 5s 后再开始复制,因为要等更多 slave 重新连接过来
repl-diskless-sync-delay 5
过期 key 处理
slave 不会过期 key,只会等待 master 过期 key。如果 master 过期了一个 key,或者通过 LRU 淘汰了一个 key,那么会模拟一条 del 命令发送给 slave。
复制的完整流程
slave node 启动时,会在自己本地保存 master node 的信息,包括 master node 的host和ip,但是复制流程没开始。
slave node 内部有个定时任务,每秒检查是否有新的 master node 要连接和复制,如果发现,就跟 master node 建立 socket 网络连接。然后 slave node 发送 ping 命令给 master node。如果 master 设置了 requirepass,那么 slave node 必须发送 masterauth 的口令过去进行认证。master node 第一次执行全量复制,将所有数据发给slave node。而在后续,master node 持续将写命令,异步复制给 slave node。
redis-master-slave-replication-detail
全量复制
master 执行 bgsave ,在本地生成一份 rdb 快照文件。
master node 将 rdb 快照文件发送给 slave node,如果 rdb 复制时间超过 60秒(repl-timeout),那么 slave node 就会认为复制失败,可以适当调大这个参数(对于千兆网卡的机器,一般每秒传输 100MB,6G 文件,很可能超过 60s)
master node 在生成 rdb 时,会将所有新的写命令缓存在内存中,在 slave node 保存了 rdb 之后,再将新的写命令复制给 slave node。
如果在复制期间,内存缓冲区持续消耗超过 64MB,或者一次性超过 256MB,那么停止复制,复制失败。
client-output-buffer-limit slave 256MB 64MB 60
slave node 接收到 rdb 之后,清空自己的旧数据,然后重新加载 rdb 到自己的内存中,同时基于旧的数据版本对外提供服务。
如果 slave node 开启了 AOF,那么会立即执行 BGREWRITEAOF,重写 AOF。
增量复制
如果全量复制过程中,master-slave 网络连接断掉,那么 slave 重新连接 master 时,会触发增量复制。
master 直接从自己的 backlog 中获取部分丢失的数据,发送给 slave node,默认 backlog 就是1MB。
msater就是根据 slave 发送的 psync 中的 offset 来从 backlog 中获取数据的。
heartbeat
主从节点互相都会发送 heartbeat 信息。
master 默认每隔 10秒 发送一次 heartbeat,slave node 每隔 1秒 发送一个 heartbeat。
异步复制
master 每次接收到写命令之后,先在内部写入数据,然后异步发送给 slave node。
redis 如何才能做到高可用
如果系统在 365 天内,有 99.99% 的时间,都是可以哗哗对外提供服务的,那么就说系统是高可用的。
一个 slave 挂掉了,是不会影响可用性的,还有其它的 slave 在提供相同数据下的相同的对外的查询服务。
但是,如果 master node 死掉了,会怎么样?没法写数据了,写缓存的时候,全部失效了。slave node 还有什么用呢,没有 master 给它们复制数据了,系统相当于不可用了。
redis 的高可用架构,叫做 failover 故障转移,也可以叫做主备切换。
master node 在故障时,自动检测,并且将某个 slave node 自动切换位 master node的过程,叫做主备切换。这个过程,实现了 redis 的主从架构下的高可用。
引申Q:谈谈Redis相关的集群有哪些成熟方案?
redis主从方案
redis主从模式是最简单的一种集群方案配置起来也比较简单,它的特点主要有:
一个master可以拥有多个slave
多个slave链接同一个master,也可以链接其它slave
主从复制不会阻塞master,在同步数据时,master可以继续处理client请求.
slave 配置为slave-read-only on需要升级为主节点或者写入配置文件中, 而不能在默认slave情况下直接设置master与slave断开后会检测心跳, 从新建立连接.
可以直接copy DUMP文件从新重启master,在Master为空以后,slave同步数据会抹掉全部数据.
这种简单的主从读写分离方案的缺点也比较多,类似Mysql的主从方案,往Master节点写数据,同时Master节点会异步写入slave节点中。这种方案目前使用的越来越少,不过对于个体开发并且对缓存依赖度不高的系统还是可以使用的,毕竟搭建和维护简单。
redis cluster方案
Redis Cluster是一种服务器Sharding技术,3.0版本开始正式提供。
Redis Cluster中,Sharding采用slot(槽)的概念,一共分成16384个槽,这有点儿类pre sharding思路。对于每个进入Redis的键值对,根据key进行散列,分配到这16384个slot中的某一个中。使用的hash算法也比较简单,就是CRC16后16384取模。
Redis集群中的每个node(节点)负责分摊这16384个slot中的一部分,也就是说,每个slot都对应一个node负责处理。当动态添加或减少node节点时,需要将16384个槽做个再分配,槽中的键值也要迁移。
Redis集群,要保证16384个槽对应的node都正常工作,如果某个node发生故障,那它负责的slots也就失效,整个集群将不能工作。
为了增加集群的可访问性,官方推荐的方案是将node配置成主从结构,即一个master主节点,挂n个slave从节点。这时,如果主节点失效,Redis Cluster会根据选举算法从slave节点中选择一个上升为主节点,整个集群继续对外提供服务,Redis Cluster本身提供了故障转移容错的能力。
Redis Cluster的新节点识别能力、故障判断及故障转移能力是通过集群中的每个node都在和其它nodes进行通信,这被称为集群总线(cluster bus)。它们使用特殊的端口号,即对外服务端口号加10000。例如如果某个node的端口号是6379,那么它与其它nodes通信的端口号是16379。nodes之间的通信采用特殊的二进制协议。
对客户端来说,整个cluster被看做是一个整体,客户端可以连接任意一个node进行操作,就像操作单一Redis实例一样,当客户端操作的key没有分配到该node上时,Redis会返回转向指令,指向正确的node。
redis cluster
从这种redis cluster的架构图中可以很容易的看出首先将数据根据hash规则分配到6个slot中(这里只是举例子分成了6个槽),然后根据CRC算法和取模算法将6个slot分别存储到3个不同的Master节点中,每个master节点又配套部署了一个slave节点,当一个master出现问题后,slave节点可以顶上。这种cluster的方案对比第一种简单的主从方案的优点在于提高了读写的并发,分散了IO,在保障高可用的前提下提高了性能。
具体的redis cluster的搭建方案可以参考官方的搭建方案,链接中是中文版。
redis cluster官方搭建方案
codis集群方案
Codis是一个豌豆荚团队开源的使用Go语言编写的Redis Proxy使用方法和普通的redis没有任何区别,设置好下属的多个redis实例后就可以了,使用时在本需要连接redis的地方改为连接codis,它会以一个代理的身份接收请求 并使用一致性hash算法,将请求转接到具体redis,将结果再返回codis,和之前比较流行的twitter开源的Twemproxy功能类似,但是相比官方的redis cluster和twitter的Twemproxy还是有一些独到的优势,Codis官方功能对比图如下:
下面我们看下如果使用Coids作为缓存集群方案的架构图,简单画了这么个架构图,这个架构是codis保证HA的前提下的最小级,从这张架构图可以看到我们最少需要8台机器,其中一台机器是codis的dashboard用于通过web界面可视化的配置codis group和proxy,也可以查看各个节点的状态;还有两台是用于codis的proxy代理节点,两个节点之间通过pipeline主从互备;还需要至少配置一台zk用于保存slot状态信息,也可以通过etcd存储这些状态信息,方便client请求的路由,也可以配置多台保证高可用;最后就是要配置数据节点来存储数据了,在codis中需要将数据节点都放在codis group中进行管理,每个group至少保留一个节点,该架构图中,为了保证HA,我们每个group都配置了一个master一个slave节点,这里配置了两个group,如果一个group中的master挂了,那么同一个group中的slave节点通过选举算法选出新的master节点,并通知到proxy,如果为了较好的高可用可以增加group的个数和每个group中slave节点的个数
codis
codis方案推出的时间比较长,而且国内很多互联网公司都已经使用了该集群方案,所以该方案还是比较适合大型互联网系统使用的,毕竟成功案例比较多,但是codis因为要实现slot切片,所以修改了redis-server的源码,对于后续的更新升级也会存在一定的隐患。,但是codis的稳定性和高可用确实是目前做的最好的,只要有足够多的机器能够做到非常好的高可用缓存系统。
哨兵机制:
Redis 哨兵集群实现高可用
哨兵的介绍
sentinel,中文名是哨兵。哨兵是 redis 集群机构中非常重要的一个组件,主要有以下功能:
集群监控:负责监控 redis master 和 slave 进程是否正常工作。
消息通知:如果某个 redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。
配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。
哨兵用于实现 redis 集群的高可用,本身也是分布式的,作为一个哨兵集群去运行,互相协同工作。
故障转移时,判断一个 master node 是否宕机了,需要大部分的哨兵都同意才行,涉及到了分布式选举的问题。
即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的,因为如果一个作为高可用机制重要组成部分的故障转移系统本身是单点的,那就很坑爹了。
哨兵的核心知识
哨兵至少需要 3 个实例,来保证自己的健壮性。
哨兵 + redis 主从的部署架构,是不保证数据零丢失的,只能保证 redis 集群的高可用性。
对于哨兵 + redis 主从这种复杂的部署架构,尽量在测试环境和生产环境,都进行充足的测试和演练。
哨兵集群必须部署 2 个以上节点,如果哨兵集群仅仅部署了 2 个哨兵实例,quorum = 1。
配置 quorum=1,如果 master 宕机, s1 和 s2 中只要有 1 个哨兵认为 master 宕机了,就可以进行切换,同时 s1 和 s2 会选举出一个哨兵来执行故障转移。但是同时这个时候,需要 majority,也就是大多数哨兵都是运行的
如果此时仅仅是 M1 进程宕机了,哨兵 s1 正常运行,那么故障转移是 OK 的。但是如果是整个 M1 和 S1 运行的机器宕机了,那么哨兵只有 1 个,此时就没有 majority 来允许执行故障转移,虽然另外一台机器上还有一个 R1,但是故障转移不会执行。
经典的 3 节点哨兵集群是这样的:
配置 quorum=2,如果 M1 所在机器宕机了,那么三个哨兵还剩下 2 个,S2 和 S3 可以一致认为 master 宕机了,然后选举出一个来执行故障转移,同时 3 个哨兵的 majority 是 2,所以还剩下的 2 个哨兵运行着,就可以允许执行故障转移
redis 哨兵主备切换的数据丢失问题
两种情况和导致数据丢失
主备切换的过程,可能会导致数据丢失:
异步复制导致的数据丢失
因为 master->slave 的复制是异步的,所以可能有部分数据还没复制到 slave,master 就宕机了,此时这部分数据就丢失了。
async-replication-data-lose-case
脑裂导致的数据丢失
脑裂,也就是说,某个 master 所在机器突然脱离了正常的网络,跟其他 slave 机器不能连接,但是实际上 master 还运行着。此时哨兵可能就会认为 master 宕机了,然后开启选举,将其他 slave 切换成了 master。这个时候,集群里就会有两个 master ,也就是所谓的脑裂。
此时虽然某个 slave 被切换成了master,但是可能 client 还没来得及切换到新的 master,还继续向旧 master 写数据。因此旧 master 再次恢复的时候,会被作为一个 slave 挂到新的 master 上去,自己的数据会清空,重新从新的 master 复制数据。而新的 master 并没有后来 client 写入的数据,因此,这部分数据也就丢失了
数据丢失问题的解决方案
min-slaves-to-write 1
min-slaves-max-lag 10
表示,要求至少有 1 个 slave,数据复制和同步的延迟不能超过 10 秒。
如果说一旦所有的 slave,数据复制和同步的延迟都超过了 10 秒钟,那么这个时候,master 就不会再接收任何请求了。
减少异步复制数据的丢失
有了 min-slaves-max-lag 这个配置,就可以确保说,一旦 slave 复制数据和 ack 延时太长,就认为可能 master 宕机后损失的数据太多了,那么就拒绝写请求,这样可以把 master 宕机时由于部分数据未同步到 slave 导致的数据丢失降低的可控范围内。
减少脑裂的数据丢失
如果一个 master 出现了脑裂,跟其他 slave 丢了连接,那么上面两个配置可以确保说,如果不能继续给指定数量的 slave 发送数据,而且 slave 超过 10 秒没有给自己 ack 消息,那么就直接拒绝客户端的写请求。因此在脑裂场景下,最多就丢失 10 秒的数据。
sdown 和 odown 转换机制
sdown 是主观宕机,就一个哨兵如果自己觉得一个 master 宕机了,那么就是主观宕机
odown 是客观宕机,如果 quorum 数量的哨兵都觉得一个 master 宕机了,那么就是客观宕机
sdown 达成的条件很简单,如果一个哨兵 ping 一个 master,超过了 is-master-down-after-milliseconds 指定的毫秒数之后,就主观认为 master 宕机了;如果一个哨兵在指定时间内,收到了 quorum 数量的 其它哨兵也认为那个 master 是 sdown 的,那么就认为是 odown 了。
哨兵集群的自动发现机制
哨兵互相之间的发现,是通过 redis 的 pub/sub 系统实现的,每个哨兵都会往__sentinel__:hello这个 channel 里发送一个消息,这时候所有其他哨兵都可以消费到这个消息,并感知到其他的哨兵的存在。
每隔两秒钟,每个哨兵都会往自己监控的某个 master+slaves 对应的__sentinel__:hello channel 里发送一个消息,内容是自己的 host、ip 和 runid 还有对这个 master 的监控配置。
每个哨兵也会去监听自己监控的每个 master+slaves 对应的__sentinel__:hello channel,然后去感知到同样在监听这个 master+slaves 的其他哨兵的存在。
每个哨兵还会跟其他哨兵交换对 master 的监控配置,互相进行监控配置的同步。
slave 配置的自动纠正
哨兵会负责自动纠正 slave 的一些配置,比如 slave 如果要成为潜在的 master 候选人,哨兵会确保 slave 复制现有 master 的数据; 如果 slave 连接到了一个错误的 master 上,比如故障转移之后,那么哨兵会确保它们连接到正确的 master 上。
slave->master 选举算法
如果一个 master 被认为 odown 了,而且 majority 数量的哨兵都允许主备切换,那么某个哨兵就会执行主备切换操作,此时首先要选举一个 slave 来,会考虑 slave 的一些信息:
跟 master 断开连接的时长
slave 优先级
复制 offset
run id
如果一个 slave 跟 master 断开连接的时间已经超过了down-after-milliseconds的 10 倍,外加 master 宕机的时长,那么 slave 就被认为不适合选举为 master。
(down-after-milliseconds * 10) + milliseconds_since_master_is_in_SDOWN_state
接下来会对 slave 进行排序:
按照 slave 优先级进行排序,slave priority 越低,优先级就越高。
如果 slave priority 相同,那么看 replica offset,哪个 slave 复制了越多的数据,offset 越靠后,优先级就越高。
如果上面两个条件都相同,那么选择一个 run id 比较小的那个 slave。
quorum 和 majority
每次一个哨兵要做主备切换,首先需要 quorum 数量的哨兵认为 odown,然后选举出一个哨兵来做切换,这个哨兵还得得到 majority 哨兵的授权,才能正式执行切换。
如果 quorum < majority,比如 5 个哨兵,majority 就是 3,quorum 设置为2,那么就 3 个哨兵授权就可以执行切换。
但是如果 quorum >= majority,那么必须 quorum 数量的哨兵都授权,比如 5 个哨兵,quorum 是 5,那么必须 5 个哨兵都同意授权,才能执行切换。
configuration epoch
哨兵会对一套 redis master+slaves 进行监控,有相应的监控的配置。
执行切换的那个哨兵,会从要切换到的新 master(salve->master)那里得到一个 configuration epoch,这就是一个 version 号,每次切换的 version 号都必须是唯一的。
如果第一个选举出的哨兵切换失败了,那么其他哨兵,会等待 failover-timeout 时间,然后接替继续执行切换,此时会重新获取一个新的 configuration epoch,作为新的 version 号。
configuraiton 传播
哨兵完成切换之后,会在自己本地更新生成最新的 master 配置,然后同步给其他的哨兵,就是通过之前说的 pub/sub 消息机制。
这里之前的 version 号就很重要了,因为各种消息都是通过一个 channel 去发布和监听的,所以一个哨兵完成一次新的切换之后,新的 master 配置是跟着新的 version 号的。其他的哨兵都是根据版本号的大小来更新自己的 master 配置的。
引申Q:你的 redis 是主从架构?集群架构?用了哪种集群方案?有没有做高可用保证?有没有开启持久化机制确保可以进行数据恢复?线上 redis 给几个 G 的内存?设置了哪些参数?压测后你们 redis 集群承载多少 QPS?
前提了解:
SERVER为单线程处理模式,在处理用户请求的过程中,还会定期插入定时任务,比如:
1)过期KEY的删除
2)链接超时检查
3)AOF文件重写
4)扩容存放数据的dic容量
这些定期任务大概100ms会触发一次。当有大量的KEY同时过期时,删除过期KEY的任务可能会执行约20ms后才会退出。 大KEY(线上看到过list 的elements超过百万的)删除时会阻塞比较长的时间.
大KEY的危害:
1)OPS低也会导致流量大,比如一次取走100K的数据,当OPS为1000时,就会产生100M/s的流量
2)如果为list,hash等数据结构,大量的elements需要多次遍历,多次系统调用拷贝数据消耗时间
3)主动删除、被动过期删除、数据迁移等,由于处理这一个KEY时间长,而发生阻塞
热KEY的危害:
1)请求过于集中,导致单个shard压力过大,不能发挥集群多分片的优势
2)当单个shard无法满足性能时,不能通过扩容解决
redis cluster,132 台机器,132台机器部署了 redis 主实例,每个实例两个分片。共计264 每个节点的读写高峰OPS可能可以达到每秒 5 万,264台分片最多是 1320万读写请求/s。内存每个分片最大10G,最多2640G,也就是2T多内存数据。
机器是什么配置?32G 内存+ 8 核 CPU + 1T 磁盘,但是分配给 redis 进程的是10g内存,一般线上生产环境,redis 的内存尽量不要超过 10g,超过 10g 可能会有问题。
264台分片对外提供读写,一共有 264内存。
因为每个主实例都挂了一个从实例,所以是高可用的,任何一个主实例宕机,都会自动故障迁移,redis 从实例会自动变成主实例继续提供读写服务。
你往内存里写的是什么数据?每条数据的大小是多少?商品数据,每条数据是 10kb。100 条数据是 1mb,10 万条数据是 1g。大促期间内存的是 2000多万条商品数据,占用内存是200g,目前高峰期每秒就是 8w 左右的请求量。
使用的时候有什么注意点?
- 单个 key 热读导致延迟变高。
1)增加多副本,分担读流量
2)不要求强一致的用户,启用客户端本地缓存
3)对于秒杀等流量突增场景,调整链接池,保持少量空闲链接- 单个 key 热写导致延迟变高
1)应避免该情况发生
2)对数据可靠性要求不高场景,建议去掉从副本,降低由于写的过快增量同步跟不上,触发全量同步,进而阻塞master的风险- 大 List. Set. Hash,field 数量巨大。无法横向扩容,迁移. 升级 server 会造成阻塞,获得大量元素时延迟较大阻塞其他命令。建议切割成多个小的 list. set. hash。
- 大 String,value 大于 20K。当 ops 为 10000,流量即为 200M,容易打满单个 server 的带宽配额. 阻塞其他命令执行。一般业务很难有效的控制value较大时key的访问频率,建议value 尽量较小。
- keys 命令会阻塞其他命令。建议用分时机制的 scan 命令代替。
- 局部读写,一段时间仅访问少量的几个分片,客户端会不停的新建. 销毁连接。建议调整 连接池配置. 增大最大空闲连接数. 空闲连接存活时长。
- 一次业务调用需要访问多次 redis服务端,导致 OPS 成倍增长,应尽量避免。
引申Q:redis 集群模式的工作原理能说一下么?在集群模式下,redis 的 key 是如何寻址的?分布式寻址都有哪些算法?了解一致性 hash 算法吗?
思路分析:
如果你的数据量很少,主要是承载高并发高性能的场景,比如你的缓存一般就几个 G,单机就足够了,可以使用 replication,一个 master 多个 slaves,要几个 slave 跟你要求的读吞吐量有关,然后自己搭建一个 sentinel 集群去保证 redis 主从架构的高可用性。
redis cluster,主要是针对海量数据+高并发+高可用的场景。redis cluster 支撑 N 个 redis master node,每个 master node 都可以挂载多个 slave node。这样整个 redis 就可以横向扩容了。如果你要支撑更大数据量的缓存,那就横向扩容更多的 master 节点,每个 master 节点就能存放更多的数据了。
redis cluster 介绍
自动将数据进行分片,每个 master 上放一部分数据
提供内置的高可用支持,部分 master 不可用时,还是可以继续工作的
在 redis cluster 架构下,每个 redis 要放开两个端口号,比如一个是 6379,另外一个就是 加1w 的端口号,比如 16379。
16379 端口号是用来进行节点间通信的,也就是 cluster bus 的东西,cluster bus 的通信,用来进行故障检测、配置更新、故障转移授权。cluster bus 用了另外一种二进制的协议,gossip 协议,用于节点间进行高效的数据交换,占用更少的网络带宽和处理时间。
节点间的内部通信机制
基本通信原理
redis cluster 节点间采用 gossip 协议进行通信
集中式是将集群元数据(节点信息、故障等等)几种存储在某个节点上。集中式元数据集中存储的一个典型代表,就是大数据领域的 storm。它是分布式的大数据实时计算引擎,是集中式的元数据存储的结构,底层基于 zookeeper(分布式协调的中间件)对所有元数据进行存储维护。
redis 维护集群元数据采用另一个方式, gossip 协议,所有节点都持有一份元数据,不同的节点如果出现了元数据的变更,就不断将元数据发送给其它的节点,让其它节点也进行元数据的变更。
集中式的好处在于,元数据的读取和更新,时效性非常好,一旦元数据出现了变更,就立即更新到集中式的存储中,其它节点读取的时候就可以感知到;不好在于,所有的元数据的更新压力全部集中在一个地方,可能会导致元数据的存储有压力。
gossip 好处在于,元数据的更新比较分散,不是集中在一个地方,更新请求会陆陆续续,打到所有节点上去更新,降低了压力;不好在于,元数据的更新有延时,可能导致集群中的一些操作会有一些滞后。
10000 端口
每个节点都有一个专门用于节点间通信的端口,就是自己提供服务的端口号+10000,比如 7001,那么用于节点间通信的就是 17001 端口。每个节点每隔一段时间都会往另外几个节点发送 ping 消息,同时其它几个节点接收到 ping 之后返回 pong。
交换的信息
信息包括故障信息,节点的增加和删除,hash slot 信息 等等。
gossip 协议
gossip 协议包含多种消息,包含 ping,pong,meet,fail 等等。
meet:某个节点发送 meet 给新加入的节点,让新节点加入集群中,然后新节点就会开始与其它节点进行通信。
redis-trib.rb add-node
其实内部就是发送了一个 gossip meet 消息给新加入的节点,通知那个节点去加入我们的集群。
ping:每个节点都会频繁给其它节点发送 ping,其中包含自己的状态还有自己维护的集群元数据,互相通过 ping 交换元数据。
pong:返回 ping 和 meeet,包含自己的状态和其它信息,也用于信息广播和更新。
fail:某个节点判断另一个节点 fail 之后,就发送 fail 给其它节点,通知其它节点说,某个节点宕机啦。
ping 消息深入
ping 时要携带一些元数据,如果很频繁,可能会加重网络负担。
每个节点每秒会执行 10 次 ping,每次会选择 5 个最久没有通信的其它节点。当然如果发现某个节点通信延时达到了 cluster_node_timeout / 2,那么立即发送 ping,避免数据交换延时过长,落后的时间太长了。比如说,两个节点之间都 10 分钟没有交换数据了,那么整个集群处于严重的元数据不一致的情况,就会有问题。所以 cluster_node_timeout 可以调节,如果调得比较大,那么会降低 ping 的频率。
每次 ping,会带上自己节点的信息,还有就是带上 1/10 其它节点的信息,发送出去,进行交换。至少包含 3 个其它节点的信息,最多包含总结点-2 个其它节点的信息。
分布式寻址算法
hash 算法(大量缓存重建)
一致性 hash 算法(自动缓存迁移)+ 虚拟节点(自动负载均衡)
redis cluster 的 hash slot 算法
hash 算法
来了一个 key,首先计算 hash 值,然后对节点数取模。然后打在不同的 master 节点上。一旦某一个 master 节点宕机,所有请求过来,都会基于最新的剩余 master 节点数去取模,尝试去取数据。这会导致大部分的请求过来,全部无法拿到有效的缓存,导致大量的流量涌入数据库。
redis cluster 的 hash slot 算法
redis cluster 有固定的 16384 个 hash slot,对每个 key 计算 CRC16 值,然后对 16384 取模,可以获取 key 对应的 hash slot。
redis cluster 中每个 master 都会持有部分 slot,比如有 3 个 master,那么可能每个 master 持有 5000 多个 hash slot。hash slot 让 node 的增加和移除很简单,增加一个 master,就将其他 master 的 hash slot 移动部分过去,减少一个 master,就将它的 hash slot 移动到其他 master 上去。移动 hash slot 的成本是非常低的。客户端的 api,可以对指定的数据,让他们走同一个 hash slot,通过 hash tag 来实现。
任何一台机器宕机,另外两个节点,不影响的。因为 key 找的是 hash slot,不是机器。
redis cluster 的高可用与主备切换原理
redis cluster 的高可用的原理,几乎跟哨兵是类似的
判断节点宕机
如果一个节点认为另外一个节点宕机,那么就是 pfail,主观宕机。如果多个节点都认为另外一个节点宕机了,那么就是 fail,客观宕机,跟哨兵的原理几乎一样,sdown,odown。
在 cluster-node-timeout 内,某个节点一直没有返回 pong,那么就被认为 pfail。
如果一个节点认为某个节点 pfail 了,那么会在 gossip ping 消息中,ping 给其他节点,如果超过半数的节点都认为 pfail 了,那么就会变成 fail。
从节点过滤
对宕机的 master node,从其所有的 slave node 中,选择一个切换成 master node。
检查每个 slave node 与 master node 断开连接的时间,如果超过了 cluster-node-timeout * cluster-slave-validity-factor,那么就没有资格切换成 master。
从节点选举
每个从节点,都根据自己对 master 复制数据的 offset,来设置一个选举时间,offset 越大(复制数据越多)的从节点,选举时间越靠前,优先进行选举。
所有的 master node 开始 slave 选举投票,给要进行选举的 slave 进行投票,如果大部分 master node(N/2 + 1)都投票给了某个从节点,那么选举通过,那个从节点可以切换成 master。
从节点执行主备切换,从节点切换为主节点。
与哨兵比较
整个流程跟哨兵相比,非常类似,所以说,redis cluster 功能强大,直接集成了 replication 和 sentinel 的功能。
引申Q:Redis缓存和数据库会存在一致性问题吗?怎么解决(如何保证数据库与redis缓存一致的)
最经典的缓存+数据库读写的模式,就是 Cache Aside Pattern。
读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。
更新的时候,先删除缓存,然后更新数据库。
为什么上亿流量高并发场景下,缓存会出现数据不一致这个问题?
只有在对一个数据在并发的进行读写的时候,才可能会出现这种问题。其实如果说你的并发量很低的话,特别是读并发很低,每天访问量就 1 万次,那么很少的情况下,会出现刚才描述的那种不一致的场景。但是问题是,如果每天的是上亿的流量,每秒并发读是几万,每秒只要有数据更新的请求,就可能会出现上述的数据库+缓存不一致的情况。
解决方案
更新数据的时候,根据数据的唯一标识,将操作路由之后,发送到一个 jvm 内部队列中。读取数据的时候,如果发现数据不在缓存中,那么将重新读取数据+更新缓存的操作,根据唯一标识路由之后,也发送同一个 jvm 内部队列中。
一个队列对应一个工作线程,每个工作线程串行拿到对应的操作,然后一条一条的执行。这样的话,一个数据变更的操作,先删除缓存,然后再去更新数据库,但是还没完成更新。此时如果一个读请求过来,读到了空的缓存,那么可以先将缓存更新的请求发送到队列中,此时会在队列中积压,然后同步等待缓存更新完成。
这里有一个优化点,一个队列中,其实多个更新缓存请求串在一起是没意义的,因此可以做过滤,如果发现队列中已经有一个更新缓存的请求了,那么就不用再放个更新请求操作进去了,直接等待前面的更新操作请求完成即可。
待那个队列对应的工作线程完成了上一个操作的数据库的修改之后,才会去执行下一个操作,也就是缓存更新的操作,此时会从数据库中读取最新的值,然后写入缓存中。
如果请求还在等待时间范围内,不断轮询发现可以取到值了,那么就直接返回;如果请求等待的时间超过一定时长,那么这一次直接从数据库中读取当前的旧值。
高并发的场景下,该解决方案要注意的问题:
读请求长时阻塞
由于读请求进行了非常轻度的异步化,所以一定要注意读超时的问题,每个读请求必须在超时时间范围内返回。
该解决方案,最大的风险点在于说,可能数据更新很频繁,导致队列中积压了大量更新操作在里面,然后读请求会发生大量的超时,最后导致大量的请求直接走数据库。务必通过一些模拟真实的测试,看看更新数据的频率是怎样的。
另外一点,因为一个队列中,可能会积压针对多个数据项的更新操作,因此需要根据自己的业务情况进行测试,可能需要部署多个服务,每个服务分摊一些数据的更新操作。如果一个内存队列里居然会挤压 100 个商品的库存修改操作,每隔库存修改操作要耗费 10ms 去完成,那么最后一个商品的读请求,可能等待 10 * 100 = 1000ms = 1s 后,才能得到数据,这个时候就导致读请求的长时阻塞。
一定要做根据实际业务系统的运行情况,去进行一些压力测试,和模拟线上环境,去看看最繁忙的时候,内存队列可能会挤压多少更新操作,可能会导致最后一个更新操作对应的读请求,会 hang 多少时间,如果读请求在 200ms 返回,如果你计算过后,哪怕是最繁忙的时候,积压 10 个更新操作,最多等待 200ms,那还可以的。
读请求并发量过高
这里还必须做好压力测试,确保恰巧碰上上述情况的时候,还有一个风险,就是突然间大量读请求会在几十毫秒的延时 hang 在服务上,看服务能不能抗的住,需要多少机器才能抗住最大的极限情况的峰值。
但是因为并不是所有的数据都在同一时间更新,缓存也不会同一时间失效,所以每次可能也就是少数数据的缓存失效了,然后那些数据对应的读请求过来,并发量应该也不会特别大。
多服务实例部署的请求路由
可能这个服务部署了多个实例,那么必须保证说,执行数据更新操作,以及执行缓存更新操作的请求,都通过 nginx 服务器路由到相同的服务实例上。
热点商品的路由问题,导致请求的倾斜
万一某个商品的读写请求特别高,全部打到相同的机器的相同的队列里面去了,可能造成某台机器的压力过大。就是说,因为只有在商品数据更新的时候才会清空缓存,然后才会导致读写并发,所以更新频率不是太高的话,这个问题的影响并不是特别大,但是的确可能某些机器的负载会高一些。
引申Q:你是怎么控制缓存的更新?(被动方式/主动方式/增量/全量)?
https://coolshell.cn/articles/17416.html
引申Q: Redis 的并发竞争问题是什么?如何解决这个问题?了解 Redis 事务的 CAS 方案吗?
线上非常常见的一个问题,就是多客户端同时并发写一个 key,可能本来应该先到的数据后到了,导致数据版本错了;或者是多客户端同时获取一个 key,修改值之后再写回去,只要顺序错了,数据就错了。
redis setnx就可以解决。
某个时刻,多个系统实例都去更新某个 key。可以基于 zookeeper 实现分布式锁。每个系统通过 zookeeper 获取分布式锁,确保同一时间,只能有一个系统实例在操作某个 key,别人都不允许读和写。
你要写入缓存的数据,都是从 mysql 里查出来的,都得写入 mysql 中,写入 mysql 中的时候必须保存一个时间戳,从 mysql 查出来的时候,时间戳也查出来。
每次要写之前,先判断一下当前这个 value 的时间戳是否比缓存里的 value 的时间戳要新。如果是的话,那么可以写,否则,就不能用旧的数据覆盖新的数据。
问题考察点:
针对的都是 redis 的生产环境可能遇到的一些问题,就是 redis 要是挂了再重启,内存里的数据不就全丢了?能不能重启的时候把数据给恢复了?
持久化主要是做灾难恢复、数据恢复,也可以归类到高可用的一个环节中去,比如你 redis 整个挂了,然后 redis 就不可用了,你要做的事情就是让 redis 变得可用,尽快变得可用。
重启 redis,尽快让它堆外提供服务,如果没做数据备份,这时候 redis 启动了,也不可用啊,数据都没了。
很可能说,大量的请求过来,缓存全部无法命中,在 redis 里根本找不到数据,这个时候就死定了,出现缓存雪崩问题。所有请求没有在 redis 命中,就会去 mysql 数据库这种数据源头中去找,一下子 mysql 承接高并发,然后就挂了…
如果你把 redis 持久化做好,备份和恢复方案做到企业级的程度,那么即使你的 redis 故障了,也可以通过备份数据,快速恢复,一旦恢复立即对外提供服务。
redis 持久化的两种方式
redis 持久化的两种方式
RDB:RDB 持久化机制,是对 redis 中的数据执行周期性的持久化。
AOF:AOF 机制对每条写入命令作为日志,以 append-only 的模式写入一个日志文件中,在 redis 重启的时候,可以通过回放 AOF 日志中的写入指令来重新构建整个数据集。
通过 RDB 或 AOF,都可以将 redis 内存中的数据给持久化到磁盘上面来,然后可以将这些数据备份到别的地方去,比如说阿里云等云服务。
如果 redis 挂了,服务器上的内存和磁盘上的数据都丢了,可以从云服务上拷贝回来之前的数据,放到指定的目录中,然后重新启动 redis,redis 就会自动根据持久化数据文件中的数据,去恢复内存中的数据,继续对外提供服务。
如果同时使用 RDB 和 AOF 两种持久化机制,那么在 redis 重启的时候,会使用 AOF 来重新构建数据,因为 AOF 中的数据更加完整。
RDB 优缺点
RDB会生成多个数据文件,每个数据文件都代表了某一个时刻中 redis 的数据,这种多个数据文件的方式,非常适合做冷备,可以将这种完整的数据文件发送到一些远程的安全存储上去,比如说 Amazon 的 S3 云服务上去,在国内可以是阿里云的 ODPS 分布式存储上,以预定好的备份策略来定期备份redis中的数据。
RDB 对 redis 对外提供的读写服务,影响非常小,可以让 redis 保持高性能,因为 redis 主进程只需要 fork 一个子进程,让子进程执行磁盘 IO 操作来进行 RDB 持久化即可。
相对于 AOF 持久化机制来说,直接基于 RDB 数据文件来重启和恢复 redis 进程,更加快速。
如果想要在 redis 故障时,尽可能少的丢失数据,那么 RDB 没有 AOF 好。一般来说,RDB 数据快照文件,都是每隔 5 分钟,或者更长时间生成一次,这个时候就得接受一旦 redis 进程宕机,那么会丢失最近 5 分钟的数据。
RDB 每次在 fork 子进程来执行 RDB 快照数据文件生成的时候,如果数据文件特别大,可能会导致对客户端提供的服务暂停数毫秒,或者甚至数秒。
AOF 优缺点
AOF 可以更好的保护数据不丢失,一般 AOF 会每隔 1 秒,通过一个后台线程执行一次fsync操作,最多丢失 1 秒钟的数据。
AOF 日志文件以 append-only 模式写入,所以没有任何磁盘寻址的开销,写入性能非常高,而且文件不容易破损,即使文件尾部破损,也很容易修复。
AOF 日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。因为在 rewrite log 的时候,会对其中的指导进行压缩,创建出一份需要恢复数据的最小日志出来。再创建新日志文件的时候,老的日志文件还是照常写入。当新的 merge 后的日志文件 ready 的时候,再交换新老日志文件即可。
AOF 日志文件的命令通过非常可读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复。比如某人不小心用 flushall 命令清空了所有数据,只要这个时候后台 rewrite 还没有发生,那么就可以立即拷贝 AOF 文件,将最后一条 flushall 命令给删了,然后再将该 AOF 文件放回去,就可以通过恢复机制,自动恢复所有数据。
对于同一份数据来说,AOF 日志文件通常比 RDB 数据快照文件更大。
AOF 开启后,支持的写 QPS 会比 RDB 支持的写 QPS 低,因为 AOF 一般会配置成每秒 fsync 一次日志文件,当然,每秒一次 fsync,性能也还是很高的。(如果实时写入,那么 QPS 会大降,redis 性能会大大降低)
以前 AOF 发生过 bug,就是通过 AOF 记录的日志,进行数据恢复的时候,没有恢复一模一样的数据出来。所以说,类似 AOF 这种较为复杂的基于命令日志/merge/回放的方式,比基于 RDB 每次持久化一份完整的数据快照文件的方式,更加脆弱一些,容易有 bug。不过 AOF 就是为了避免 rewrite 过程导致的 bug,因此每次 rewrite 并不是基于旧的指令日志进行 merge 的,而是基于当时内存中的数据进行指令的重新构建,这样健壮性会好很多。
RDB和AOF到底该如何选择
不要仅仅使用 RDB,因为那样会导致你丢失很多数据
也不要仅仅使用 AOF,因为那样有两个问题,第一,你通过 AOF 做冷备,没有 RDB 做冷备,来的恢复速度更快; 第二,RDB 每次简单粗暴生成数据快照,更加健壮,可以避免 AOF 这种复杂的备份和恢复机制的 bug。
redis 支持同时开启开启两种持久化方式,我们可以综合使用 AOF 和 RDB 两种持久化机制,用 AOF 来保证数据不丢失,作为数据恢复的第一选择; 用 RDB 来做不同程度的冷备,在 AOF 文件都丢失或损坏不可用的时候,还可以使用 RDB 来进行快速的数据恢复。
twemproxy
支持一致性Hash,取模等数据分片模式
支持失败节点自动删除
可以设置重新连接该节点的时间
可以设置连接多少次之后删除该节点
该方式适合作为cache存储
支持设置HashTag
通过HashTag可以自己设定将两个KEYhash到同一个实例上去。
减少与redis的直接连接数
保持与redis的长连接
可设置代理与后台每个redis连接的数目
自动分片到后端多个redis实例上
多种hash算法(部分还没有研究明白)
可以设置后端实例的权重
避免单点问题
可以平行部署多个代理层.client自动选择可用的一个
支持redis pipelining request
支持状态监控
可设置状态监控ip和端口,访问ip和端口可以得到一个json格式的状态信息串
可设置监控信息刷新间隔时间
高吞吐量
连接复用,内存复用。
将多个连接请求,组成reids pipelining统一向redis请求。
另外可以修改redis的源代码,抽取出redis中的前半部分,作为一个中间代理层。最终都是通过linux下的epoll 事件机制提高并发效率,其中nutcraker本身也是使用epoll的事件机制。并且在性能测试上的表现非常出色。
配置部署建议: 编译时候打开logging模块。
redis部署知识: AOF;一种记录redis写操作的文件,用于恢复redis数据。
sentinel
redis系统外围组件,用于监控集群系统
Redis Sentinel is a system designed to help managing Redis instances. It performs the following three tasks:
Monitoring. Sentinel constantly check if your master and slave instances are working as expected.
Notification. Sentinel can notify the system administrator, or another computer program, via an API, that something is wrong with one of the monitored Redis instances.
Automatic failover. If a master is not working as expected, Sentinel can start a failover process where a slave is promoted to master, the other additional slaves are reconfigured to use the new master, and the applications using the Redis server informed about the new address to use when connecting.
Configuration provider. Sentinel acts as a source of authority for clients service discovery: clients connect to Sentinels in order to ask for the address of the current Redis master responsible for a given service. If a failover occurs, Sentinels will report the new address.
metaserver
redis之间的数据均衡
https://www.jianshu.com/p/cf8b0d7f2f2c
常见的有两个问题:
往 redis 写入的数据怎么没了?
可能有同学会遇到,在生产环境的 redis 经常会丢掉一些数据,写进去了,过一会儿可能就没了。我的天,同学,你问这个问题就说明 redis 你就没用对啊。redis 是缓存,你给当存储了是吧?
啥叫缓存?用内存当缓存。内存是无限的吗,内存是很宝贵而且是有限的,磁盘是廉价而且是大量的。可能一台机器就几十个 G 的内存,但是可以有几个 T 的硬盘空间。redis 主要是基于内存来进行高性能、高并发的读写操作的。
那既然内存是有限的,比如 redis 就只能用 10G,你要是往里面写了 20G 的数据,会咋办?当然会干掉 10G 的数据,然后就保留 10G 的数据了。那干掉哪些数据?保留哪些数据?当然是干掉不常用的数据,保留常用的数据了。
数据明明过期了,怎么还占用着内存?
这是由 redis 的过期策略来决定。
redis 过期策略
redis 过期策略是:定期删除+惰性删除。
所谓定期删除,指的是 redis 默认是每隔 100ms 就随机抽取一些设置了过期时间的 key,检查其是否过期,如果过期就删除。
假设 redis 里放了 10w 个 key,都设置了过期时间,你每隔几百毫秒,就检查 10w 个 key,那 redis 基本上就死了,cpu 负载会很高的,消耗在你的检查过期 key 上了。注意,这里可不是每隔 100ms 就遍历所有的设置过期时间的 key,那样就是一场性能上的灾难。实际上 redis 是每隔 100ms 随机抽取一些 key 来检查和删除的。
但是问题是,定期删除可能会导致很多过期 key 到了时间并没有被删除掉,那咋整呢?所以就是惰性删除了。这就是说,在你获取某个 key 的时候,redis 会检查一下 ,这个 key 如果设置了过期时间那么是否过期了?如果过期了此时就会删除,不会给你返回任何东西。
获取key 的时候,如果此时 key 已经过期,就删除,不会返回任何东西。
但是实际上这还是有问题的,如果定期删除漏掉了很多过期 key,然后你也没及时去查,也就没走惰性删除,此时会怎么样?如果大量过期 key 堆积在内存里,导致 redis 内存块耗尽了,咋整?
答案是:走内存淘汰机制。
内存淘汰机制
redis 内存淘汰机制有以下几个:
noeviction: 当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧,实在是太恶心了。
allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)
allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个 key,这个一般没人用吧,为啥要随机,肯定是把最近最少使用的 key 给干掉啊。
volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 key(这个一般不太合适)
volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 key。
volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 key 优先移除。
手写一个 LRU 算法 class LRUCache<K, V> extends LinkedHashMap<K, V> { private final int CACHE_SIZE; /** * 传递进来最多能缓存多少数据 * * @param cacheSize 缓存大小 */ public LRUCache(int cacheSize) { // true 表示让 linkedHashMap 按照访问顺序来进行排序,最近访问的放在头部,最老访问的放在尾部。 super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true); CACHE_SIZE = cacheSize; } @Override protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { // 当 map中的数据量大于指定的缓存个数的时候,就自动删除最老的数据。 return size() > CACHE_SIZE; } }
Nosql是非关系型数据库,因为不需要满足关系数据库数据一致性等复杂特性所以速度快;
sql是关系型数据库,功能强大,但是效率上有瓶颈
索引分为聚簇索引和非聚簇索引两种,聚簇索引是按照数据存放的物理位置为顺序的,而非聚簇索引就不一样了;聚簇索引能提高多行检索的速度,而非聚簇索引对于单行的检索很快。
聚簇索引:有主键时,根据主键创建聚簇索引;没有主键时,会用一个唯一且不为空的索引列做为主键,成为此表的聚簇索引;如果以上两个都不满足那innodb自己创建一个虚拟的聚集索引
非聚簇索引:非聚簇索引都是辅助索引,像复合索引、前缀索引、唯一索引
引申Q:到关系型数据库mysql专题
问题描述
任何平台随着用户规模的扩大、功能不断的添加,持久化数据库层承受的读写压力会越来越大,一旦数据库承压过大会导致读写性能陡然下降,严重时会导致大量的业务请求超时,进而发生“雪崩”引发严重的故障。
解决方案
在业务层和数据库持久层之间引入一层内存缓存层,对于复杂且业务逻辑上不会变化的查询结果进行缓存,业务请求再次发起时,每次都先从缓存层中查询,从而大大减少对数据库的查询,减小对数据库的压力。
分布式内存缓存、本地单点缓存、应用层缓存对比
分布式内存缓存系统设计
自定义的客户端协议
业务模块采用自定义应用层协议和cacheProxy交互
整个cache后端采用什么协议,什么存储(redis,memcached等)对业务模块透明
cache后端和业务端进行了隔离,修改互不影响
负载均衡与容错机制
采用一致性hash算法,即使部分节点down机,也不会导致全部的缓存失效,新增节点也不会导致大量缓存失效和重建
一份缓存数据保留两份,当前hash节点和下一个真实的hash节点(超时时间只有设置的超时时间的一半),单个节点down机时,缓存也不会马上失效
cacheMan是一个弱的管理节点,负责监控,删除节点,新增节点,可以任意启停
缓存维护与淘汰机制
redis原生超时机制+三层LRU缓存架构,减少最终穿透到redis实例上的请求。
客户端LRU缓存
cacheProxy代理LRU缓存
redis实例内存总量限制+LRU缓存
安全机制
redis实例都会开启auth功能
redis实例都监听在内网ip
数据类型:
redis支持string/hash/set/sortedset/list五种数据结构
中高级用户再补充HyperLogLog、Geo、Pub/Sub这三种数据结构。
比方说用他的List来做FIFO双向链表,实现一个轻量级的高性 能消息队列服务,用他的Set可以做高性能的tag系统等等
而memcahed支持一种k/v存储
分布式存储:
redis支持数据的备份,及master-slave模式的数据备份
memcache可以使用一致性hash做分布式
存储方式:
Memcache把数据全部存在内存之中,断电后会挂掉,数据不能超 过内存大小。Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启时可以再次加载进行使用。(RDB快照和AOF日志两 种持久化方式)。
使用底层模型不同:
新版本的Redis直接自己构建了VM机制,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。
存储value大小不同:
memcache是一个内存缓存,key的长度小于250字符,单个item存储要小于1M,而redis存储512M。而且在redis中key值元素能存放2^32-1,40多亿个元素。
redis 原生支持集群模式
在 redis3.x 版本中,便能支持 cluster 模式,而 memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据。
性能对比
由于 redis 只使用单核,而 memcached 可以使用多核,所以平均每一个核上 redis 在存储小数据时比 memcached 性能更高。而在 100k 以上的数据中,memcached 性能要高于 redis,虽然 redis 最近也在存储大数据的性能上进行优化,但是比起 memcached,还是稍有逊色。
考前回顾:
1、如果让你写一个消息队列,该如何进行架构设计啊?说一下你的思路。
比如说这个MQ消息队列系统,我们从以下几个角度来考虑一下:
伸缩性:
就是需要的时候快速扩容,就可以增加吞吐量和容量,那怎么搞?设计个分布式的系统呗,参照一下 kafka 的设计理念,broker -> topic -> partition,每个 partition 放一个机器,就存一部分数据。如果现在资源不够了,简单啊,给 topic 增加 partition,然后做数据迁移,增加机器,不就可以存放更多数据,提供更高的吞吐量了?
数据存放磁盘:
那肯定要了,落磁盘才能保证别进程挂了数据就丢了。那落磁盘的时候怎么落啊?顺序写,这样就没有磁盘随机读写的寻址开销,磁盘顺序读写的性能是很高的,这就是 kafka 的思路。
可用性:
具体参考之前可用性那个环节讲解的 kafka 的高可用保障机制。多副本 -> leader & follower -> broker 挂了重新选举 leader 即可对外服务。
消息不丢失:
参考我们之前说的那个 kafka 数据零丢失方案。
消息顺序性:
2、redis如何实现分布式锁(Redis如何解决key冲突)
先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放。
为了防止在setnx之后执行expire之前进程意外crash或者要重启维护了
redis提供setnx和expire合成一条指令
3、如果Redis有1亿个key,使用keys命令是否会影响线上服务
会,会导致中间停顿,因为redis是单线程,当执行1亿个key时候耗时比较严重
keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。
4、如何利用Redis处理热点数据
redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。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(驱逐):禁止驱逐数据
正式开始:
Redis有哪些数据结构?
字符串String、字典Hash、列表List、集合Set、有序集合SortedSet。
如果你是Redis中高级用户,还需要加上下面几种数据结构HyperLogLog、Geo、Pub/Sub。
如果你说还玩过Redis Module,像BloomFilter,RedisSearch,Redis-ML,面试官得眼睛就开始发亮了。
使用过Redis分布式锁么,它是什么回事?
先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放。
这时候对方会告诉你说你回答得不错,然后接着问如果在setnx之后执行expire之前进程意外crash或者要重启维护了,那会怎么样?
这时候你要给予惊讶的反馈:唉,是喔,这个锁就永远得不到释放了。紧接着你需要抓一抓自己得脑袋,故作思考片刻,好像接下来的结果是你主动思考出来的,然后回答:我记得set指令有非常复杂的参数,这个应该是可以同时把setnx和expire合成一条指令来用的!对方这时会显露笑容,心里开始默念:摁,这小子还不错。
假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如果将它们全部找出来?
使用keys指令可以扫出指定模式的key列表。
对方接着追问:如果这个redis正在给线上的业务提供服务,那使用keys指令会有什么问题?
redis是单线程的。keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。
使用过Redis做异步队列么,你是怎么用的?
一般使用list结构作为队列,rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一会再重试。
如果对方追问可不可以不用sleep呢?
list还有个指令叫blpop,在没有消息的时候,它会阻塞住直到消息到来。
如果对方追问能不能生产一次消费多次呢?
使用pub/sub主题订阅者模式,可以实现1:N的消息队列。
如果对方追问pub/sub有什么缺点?
在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如rabbitmq等。
如果对方追问redis如何实现延时队列?
我估计现在你很想把面试官一棒打死如果你手上有一根棒球棍的话,怎么问的这么详细。但是你很克制,然后神态自若的回答道:使用sortedset,拿时间戳作为score,消息内容作为key调用zadd来生产消息,消费者用zrangebyscore指令获取N秒之前的数据轮询进行处理。
如果有大量的key需要设置同一时间过期,一般需要注意什么?
如果大量的key过期时间设置的过于集中,到过期的那个时间点,redis可能会出现短暂的卡顿现象。一般需要在时间上加一个随机值,使得过期时间分散一些。
Redis如何做持久化的?
bgsave做镜像全量持久化,aof做增量持久化。因为bgsave会耗费较长时间,不够实时,在停机的时候会导致大量丢失数据,所以需要aof来配合使用。在redis实例重启时,会使用bgsave持久化文件重新构建内存,再使用aof重放近期的操作指令来实现完整恢复重启之前的状态。
对方追问那如果突然机器掉电会怎样?
取决于aof日志sync属性的配置,如果不要求性能,在每条写指令时都sync一下磁盘,就不会丢失数据。但是在高性能的要求下每次都sync是不现实的,一般都使用定时sync,比如1s1次,这个时候最多就会丢失1s的数据。
对方追问bgsave的原理是什么?
你给出两个词汇就可以了,fork和cow。fork是指redis通过创建子进程来进行bgsave操作,cow指的是copy on write,子进程创建后,父子进程共享数据段,父进程继续提供读写服务,写脏的页面数据会逐渐和子进程分离开来。
Pipeline有什么好处,为什么要用pipeline?
可以将多次IO往返的时间缩减为一次,前提是pipeline执行的指令之间没有因果相关性。使用redis-benchmark进行压测的时候可以发现影响redis的QPS峰值的一个重要因素是pipeline批次指令的数目。
Redis的同步机制了解么?
Redis可以使用主从同步,从从同步。第一次同步时,主节点做一次bgsave,并同时将后续修改操作记录到内存buffer,待完成后将rdb文件全量同步到复制节点,复制节点接受完成后将rdb镜像加载到内存。加载完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。
是否使用过Redis集群,集群的原理是什么?
Redis Sentinal着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务。
Redis Cluster着眼于扩展性,在单个redis内存不足时,使用Cluster进行分片存储。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。