基础知识
Redis
1、Redis为何快
1)基于内存;
2)单线程减少上下文切换,同时保证原子性;
3)IO多路复用;
4)高级数据结构(如 SDS、Hash以及跳表等)。
2、为何使用单线程
- 因为 Redis 是基于内存的操作,CPU 不会成为 Redis 的瓶颈,而最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且 CPU 不会成为瓶颈,那就顺理成章地采用单线程的方案了。
2.1 单线程为什么那么快
- 不需要各种锁的性能消耗
- Redis 的数据结构并不全是简单的 Key-Value,还有 List,Hash 等复杂的结构,这些结构有可能会进行很细粒度的操作,比如在很长的列表后面添加一个元素,在hash当中添加或者删除一个对象。这些操作可能就需要加非常多的锁,导致的结果是同步开销大大增加。
- 大部分操作在内存上进行,且拥有高效的数据结构。
- redis采用了多路复用机制使其在网络IO操作中能并发处理大量的客户端请求,实现高吞吐率。
3、缓存三大问题及解决方案
3.1 缓存穿透
- 主要问题:查询数据不存在。
- **解决方法:**1、缓存空值;2、key值校验
3.2 缓存击穿
- 主要问题:缓存过期,伴随大量对该 key 的请求
- **解决方法:**1)互斥锁;2)热点数据永不过期;3)熔断降级
3.3 缓存雪崩
- 主要问题:同一时间大批量的 key 过期
- **解决方法:**1)热点数据不过期;2)随机分散过期时间
4、先删后写还是先写后删
4.1 先删缓存后写 DB
产生脏数据的概率较大(若出现脏数据,则意味着再不更新的情况下,查询得到的数据均为旧的数据)。
比如两个并发操作,一个是更新操作,另一个是查询操作,更新操作删除缓存后,查询操作没有命中缓存,先把老数据读出来后放到缓存中,然后更新操作更新了数据库。于是,在缓存中的数据还是老的数据,导致缓存中的数据是脏的,而且还一直这样脏下去了。
4.2 先写 DB 再删缓存
产生脏数据的概率较小,但是会出现一致性的问题;若更新操作的时候,同时进行查询操作并命中,则查询得到的数据是旧的数据。但是不会影响后面的查询。
比如一个是读操作,但是没有命中缓存,然后就到数据库中取数据,此时来了一个写操作,写完数据库后,让缓存失效,然后之前的那个读操作再把老的数据放进去,所以会造成脏数据。
4.3 延时双删
理论上不会产生脏数据,具体操作为:先把redis缓存的数据删掉,然后在修改数据库数据,这时候sleep一下(即延迟),然后在把redis缓存的数据删掉,就OK了。
4.4 解决方案
1)缓存设置过期时间,实现最终一致性;
2)使用 Cannel 等中间件监听 binlog 进行异步更新;
3)通过 2PC 或 Paxos 协议保证一致性。
5、Redis 如何保证原子性
答案很简单,因为 Redis 是单线程的,所以 Redis 提供的 API 也是原子操作。
但我们业务中常常有先 get 后 set 的业务常见,在并发下会导致数据不一致的情况。
如何解决
1)使用 incr、decr、setnx 等原子操作;
2)客户端加锁;
3)使用 Lua 脚本实现 CAS 操作。
6、Redis数据结构有哪些应用场景
Redis 在互联网产品中使用的场景实在是太多太多,这里分别对 Redis 几种数据类型做了整理:
1)String:缓存、限流、分布式锁、计数器、分布式 Session 等。
2)Hash:用户信息、用户主页访问量、组合查询等。
3)List:简单队列、关注列表时间轴。
4)Set:赞、踩、标签等。
5)ZSet:排行榜、好友关系链表。
7、常用命令
7.1 Redis常用操作
redis-cli -h 127.0.0.1 -p 6379
https://www.cnblogs.com/cxxjohnson/p/9072383.html
8、如何保证Redis的高并发
- 单机的redis几乎不太可能说QPS超过10万+,一般在几万。除非一些特殊情况,比如你的机器性能特别好,配置特别高,物理机,维护做的特别好,而且你的整体的操作不是太复杂。
- Redis主从架构,一主多从,可以满足高并发。出现实例宕机自动进行主备切换,配置读写分离缓解Master读写压力。Redis 的集群模式。
8.1 主从复制如何实现
点击展开内容
- 第一阶段是主从库间建立连接、协商同步的过程,主要是为全量复制做准备。在这一步,从库和主库建立起连接,并告诉主库即将进行同步,主库确认回复后,主从库间就可以开始同步了。
- 具体来说,从库给主库发送psync命令,表示要进行数据同步,主库根据这个命令的参数来启动复制。psync命令包含了主库的runID和复制进度offset两个参数。
- runID,是每个Redis实例启动时都会自动生成的一个随机ID,用来唯一标记这个实例。当从库和主库第一次复制时,因为不知道主库的runID,所以将runID设为“?”。
- offset,此时设为-1,表示第一次复制。
- 主库收到psync命令后,会用FULLRESYNC响应命令带上两个参数:主库runID和主库目前的复制进度offset,返回给从库。从库收到响应后,会记录下这两个参数。
这里有个地方需要注意,FULLRESYNC响应表示第一次复制采用的全量复制,也就是说,主库会把当前所有的数据都复制给从库。
- 在第二阶段,主库将所有数据同步给从库。从库收到数据后,在本地完成数据加载。这个过程依赖于内存快照生成的RDB文件。
- 具体来说,主库执行bgsave命令,生成RDB文件,接着将文件发给从库。从库接收到RDB文件后,会先清空当前数据库,然后加载RDB文件。这是因为从库在通过replicaof命令开始和主库同步前,可能保存了其他数据。为了避免之前数据的影响,从库需要先把当前数据库清空。
- 在主库将数据同步给从库的过程中,主库不会被阻塞,仍然可以正常接收请求。否则,Redis的服务就被中断了。但是,这些请求中的写操作并没有记录到刚刚生成的RDB文件中。为了保证主从库的数据一致性,主库会在内存中用专门的replication buffer,记录RDB文件生成后收到的所有写操作。
8.1 核心机制
- redis采用异步方式复制数据到slave节点,不过redis 2.8开始,slave node会周期性地确认自己每次复制的数据量。
- 一个master node是可以配置多个slave node的 (3)slave node也可以连接其他的slave node。
- slave node做复制的时候,是不会block master node的正常工作的。
- slave node在做复制的时候,也不会block对自己的查询操作,它会用旧的数据集来提供服务; 但是复制完成的时候,需要删除旧数据集,加载新数据集,这个时候就会暂停对外服务了。
- slave node主要用来进行横向扩容,做读写分离,扩容的slave node可以提高读的吞吐量。
8.2 主从架构的核心原理
- 当启动一个slave node的时候,它会发送一个PSYNC命令给master node
- 如果这是slave node重新连接master node,那么master node仅仅会复制给slave部分缺少的数据; 否则如果是slave node第一次连接master node,那么会触发一次full resynchronization
- 开始full resynchronization的时候,master会启动一个后台线程,开始生成一份RDB快照文件,同时还会将从客户端收到的所有写命令缓存在内存中。RDB文件生成完毕之后,master会将这个RDB发送给slave,slave会先写入本地磁盘,然后再从本地磁盘加载到内存中。然后master会将内存中缓存的写命令发送给slave,slave也会同步这些数据。
- slave node如果跟master node有网络故障,断开了连接,会自动重连。master如果发现有多个slave node都来重新连接,仅仅会启动一个rdb save操作,用一份数据服务所有slave node。
8.3 断点续传
- master node会在内存中常见一个backlog,master和slave都会保存一个replica offset还有一个master id,offset就是保存在backlog中的。如果master和slave网络连接断掉了,slave会让master从上次的replica offset开始继续复制。
- 当主从库断连后,主库会把断连期间收到的写操作命令,写入replication buffer,同时也会把这些操作命令也写入repl_backlog_buffer这个缓冲区。repl_backlog_buffer是一个环形缓冲区,主库会记录自己写到的位置,从库则会记录自己已经读到的位置。
- 但是如果没有找到对应的offset,那么就会执行一次resynchronization。
(1)master和slave都会维护一个offset
master会在自身不断累加offset,slave也会在自身不断累加offset slave每秒都会上报自己的offset给master,同时master也会保存每个slave的offset
这个倒不是说特定就用在全量复制的,主要是master和slave都要知道各自的数据的offset,才能知道互相之间的数据不一致的情况
(2)backlog
master node有一个backlog,默认是1MB大小 master node给slave node复制数据时,也会将数据在backlog中同步写一份 backlog主要是用来做全量复制中断后的增量复制的
(3)master run id
info server,可以看到master run id 如果根据host+ip定位master node,是不靠谱的,如果master node重启或者数据出现了变化,那么slave node应该根据不同的run id区分,run id不同就做全量复制 如果需要不更改run id重启redis,可以使用redis-cli debug reload命令
(4)psync
从节点使用psync从master node进行复制,psync runid offset master node会根据自身的情况返回响应信息,可能是FULLRESYNC runid offset触发全量复制,可能是CONTINUE触发增量复制
8.4 无磁盘化复制
- master在内存中直接创建rdb,然后发送给slave,不会在自己本地落地磁盘了
- repl-diskless-sync repl-diskless-sync-delay,等待一定时长再开始复制,因为要等更多slave重新连接过来
8.5 过期key处理
- slave不会过期key,只会等待master过期key。如果master过期了一个key,或者通过LRU淘汰了一个key,那么会模拟一条del命令发送给slave。
9、如何保证Redis的高可用
- 使用官方推荐的哨兵(sentinel)机制就能实现,当主节点出现故障时,由Sentinel自动完成故障发现和转移,并通知应用方,实现高可用性。它有四个主要功能:
- 集群监控,负责监控redis master和slave进程是否正常工作。
- 消息通知,如果某个redis实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
- 故障转移,如果master node挂掉了,会自动转移到slave node上。
- 配置中心,如果故障转移发生了,通知client客户端新的master地址。