前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Redis 关键知识

Redis 关键知识

作者头像
Check King
发布2021-08-09 10:41:54
3830
发布2021-08-09 10:41:54
举报
文章被收录于专栏:互联网后台技术专栏

Redis 基本架构

  • 网络层

Redis 作为一个网络存储服务,需要独立部署,业务侧通过网络访问,这样业务服务和数据存储可以解耦。Redis是一个单线程的网络IO模型,如何做到高性能呢? 后面会详细解释。

  • 逻辑处理层

Redis支持多种命令,对外提供的协议也是自定义的RESP协议,客户端传过来的请求包需要经过解析识别出命令和参数,然后执行相应的逻辑。

  • 存储层
  1. 索引模块

内存键值数据库一般都会用哈希表作为索引,因为键值都是存储在内存中,内存的随机访问性能高,可以与哈希表的O(1)操作复杂度相匹配。

2. 存储模块

redis支持多种value数据结构,内存数据的维护是由内存分配器来维护的,对于GET/HGET等查询操作来说,根据value的存储位置返回value值即可。

对于SET/HSET 一个新值来说,内存分配器需要为其分配内存空间。

对于DELETE操作,内存分配器需要释放(或回收)内存空间。

因为Redis是基于内存的, 如果Redis节点重启了,数据就会丢失,Redis提供了持久化的能力, 包括内存快照(RDB)和持久化日志(AOF),以便Redis重启能根据快照和日志快速恢复数据。

3. 高可用支撑模块

如果只有一个Redis实例,这个实例挂掉了,就会影响整体服务,为了提高可用性,Redis通常部署多个实例,并且用主从架构,这样主库和从库之间有数据同步,主库挂了从库可以被选择成主库。如何感知主库挂了,怎么选举主库,这就引入了哨兵机制。

4. 集群可扩展

如果每个实例包含全量数据,单机内存是有限的,这样对于需要存储大规模的缓存数据是不满足要求的。为了能够横向可扩展,Redis引入数据分片(redis cluster)集群方案,将数据分片,每个节点存储部分数据,如果单个节点存储的内存过多,继续引入新节点进行分片,将全量数据分散到不同的节点上。

Redis高性能网络IO模型

首先,说明一点,Redis是单线程的,主要是指Redis的网络IO和键值读写是由一个线程来处理的,也就是Redis对外提供服务的主要流程由一个线程来处理。但是,Redis的其他功能,比如持久化,异步删除,集群数据同步等,都是由额外的进程或线程来执行的。

  • 为什么Redis用单线程?

这是因为如果用多线程,会有两个主要问题。一个是,多线程数据访问存在竞争,需要加锁,会带来额外的开销,特别是如果设计的不好,锁粒度控制的不好,系统吞吐率没有因为线程的增加而增加。 另外一个是,多线程会降低系统代码的易调试性和可维护性。

  • 为什么单线程的Redis这么快?

Redis单线程的模型,处理能力竟然能达到每秒十万级。一方面,Redis的所有请求处理都是基于内存操作的,并且Redis设计的数据结构都是很高效的。另一方面,Redis采用了IO多路复用机制,并发的处理大量的客户端请求, 并且大部分的请求都是比较轻量的操作,实现高吞吐率。Redis 网络框架调用epoll机制,让内核监听多个网络IO套接字,而epoll提供了事件回调机制,也就是针对不同的事件,调用相应的处理函数, 无需一直轮训套接字上是否有请求发生,可以避免造成CPU资源浪费。

Redis高可用设计-单实例

Redis数据都是存储在内存中,为了down机之后能快速恢复内存数据,Redis提供了AOF日志和内存快照RDB机制。

  • AOF 日志

与数据的写前日志(Write Ahead Log, WAL) 不同的是,Redis AOF(Append Only File)是写后日志。为了避免额外开销,Redis在向AOF文件记录日志的时候,并不会对这些命令进行语法检查,索引只有正确执行的命令请求才会被写入AOF文件。并且后写AOF可以避免阻塞当前请求。

AOF三种写会策略:

配置项

写回时机

优点

缺点

Always

同步写回

可靠性高,数据基本不会丢失

每个写命令都要落盘,会阻塞主线程

Everysec

每秒写回

性能适中

宕机丢失1秒内数据

No

操作系统控制写回

性能好

宕机时丢失数据较多

AOF重写机制:

AOF文件不能太大,首先文件系统对文件大小有限制,其次,文件太大,append效率也会低,最后,如果发生宕机,恢复内存数据过程会很缓慢。

AOF重写其实就是将Redis内存中当前所有键值创建一条命令记录它的写入。比如:

代码语言:javascript
复制
重写前: SET a 1,SET a 2,SET a 3
重写后: SET a 3

AOF重写会阻塞主线程吗:

AOF重写由后台子进程bgrewriteaof来完成的, 这也是为了避免阻塞主线程。

AOF重写的过程:

  1. 主线程fork出后台的bgrewriteaof 子进程,子进程拥有主进程的一份内存拷贝(实际上是copy on write)
  2. 子进程利用内存拷贝生成AOF重写日志, 写入写的AOF日志。
  3. 主线程并未阻塞,继续处理读写请求。如果有写入操作,当前正在使用的AOF日志仍要写入,因此先写入这个日志的缓冲区, 以避免AOF重写失败时宕机数据恢复不全。另外, 新的AOF日志也需要记录,因此这个写操作也会先写入到新的AOF日志的缓冲区。
  4. 当AOF日志重写完成后,重写日志记录的这些最新操作也会写入新的AOF文件,保证新的AOF文件有最新的Redis内存数据状态。此时,可以用新的AOF文件替换就的文件了。

其实,AOF重写还是会有可能阻塞主线程的。虽然AOF重写时fork子进程是copy on write机制,但是如果Redis占用内存过大,fork一瞬间也会阻塞主线程,因为fork需要拷贝一些必要的数据结构,比如拷贝内存页表,拷贝过程会消耗大量CPU资源,这一瞬间会阻塞主线程。

  • RDB

Redis 提供了两个命令来生成RDB文件,分别是save和bgsave, 一个在主线程中执行,一个是创建子进程执行。默认是bgsave的方式,毕竟要不能阻塞主线程。bgsave也是采用copy on write机制,在执行快照的同时,正常处理读写。通用,bgsave也有AOF 重写的fork一瞬间可能会阻塞主线程的问题。

RDB虽然能恢复内存数据,但生成RDB是有成本的,因此不能执行过于频繁,但是这样又不能拿到最近的内存状态数据。在实际应用中,一般是结合RDB和AOF日志的方式来做内存数据恢复。也就是,RDB记录某个时间点的内存快照,再加上AOF这个时间点之后的操作来恢复内存数据。

Redis高可用设计-集群

如果Redis只有单个实例,这个实例宕机,服务就不可用。为了避免这种情况,通常是增加副本做冗余,将一份数据同时保存在多个实例上。即使一个实例出故障,其他实例也可以提供服务。

Redis提供主从模式,以保证数据副本的一致,主从库之间采用的是读写分离的方式,主库可读可写,从库只能读。写操作经过主库同步给从库。

  • 主从库间第一次同步
  1. 从库与主库建立连接, 给主库发送psync命令
代码语言:javascript
复制
psync ? -1

其中有两个参数,第一个是runID, 每个Redis实例启动时都会生成的一个随机ID, 第一次同步,从库不知道主库的runID, 因此传入?。第二个参数offset表示复制进度,第一次传-1,表示从头开始复制全量。

2. 主库收到psync命令后,会用FULLRESYNC响应命令带上两个参数: 主库runID和复制进度offset,返回给从库。第一次复制,主库会给从库复制全量数据。

代码语言:javascript
复制
+FULLRESYNC {runID} {offset}

3. 主库执行bgsave命令,生成RDB文件, 并发送给从库。这个过程主库不会阻塞,仍然接收请求,为了保证主从数据的一致性,这期间收到的写请求会记录在专门的replication buffer。

4. 从库收到RDB文件和随后的replication buffer, 完成与主库的数据同步。

  • 主从级联模式

如果从库较多,启动的时候都从主库同步数据,会对主库压力很大。可以采用“主-从-从”模式来缓解,也就是从库也可以作为其他的实例的复制目标,这样将生产RDB和传输RDB的压力,以级联的方式分散到从库上。

  • 增量复制

主从网络断开连接后重新连上,如果每次都要全量复制,这个成本太高。主从库断连后,主库会把断连期间受到的写操作,写入replication buffer, 同时也写入repl_backlog_buffer (环形)这个缓冲区。从库重新连接后,根据repl_backlog_buffer 同步断开连接期间的操作。

------分割线--------

前面时候说主从架构解决单点问题,一个实例挂了,其他实例能够提供服务。其实,如果某一个从库挂了,整个redis集群可以提供读写服务,但是,如果主库挂了,redis集群就只能提供读服务了,因为写操作只有在主库上。这样就需要选举新的主库来提供写请求了。

  • 哨兵机制

哨兵其实是一个运行在特殊模式下的Redis进程,主要负责三个任务: 监控、选主和通知。

  1. 监控: 主观下线和客观下线

哨兵进程会用PING检测主库、从库的网络连接情况,用来判断实例状态。如果发现主库或者从库PING相应超时了,哨兵就会把它标记“主观下线”。

这里要避免误判的情况,比如主库没有下线,单纯由于哨兵某一次PING失败了,断定主库挂了,就启动主从切换,这样显然不合理。通常采用哨兵集群的模式来部署,相当于让多个哨兵同时做决策,这样误判率就能降低。只有大多数(N/2+1)哨兵实例都判断主库已经“主观下线”, 主库才会被标记为“客观下线”,才会进一步触发主从切换流程。

2. 选主: 筛选+打分

选主是由哨兵来完成,分为筛选和打分环节。

筛选:过滤非运行状态的,过滤容易出现网络故障的。

打分:从库优先级>从库复制进度>从库ID号。

3. 通知: 让从库执行replicaof,与新主库建立连接,通知客户端,与新主库连接。

哨兵是如何组成集群的呢?哨兵在启动的时候,只是跟主库建立连接,并不感知其他哨兵的存在,他是通过订阅主库的"__sentinel__:hello" 的频道,哨兵与主库建立连接时,就会把自己的IP和端口发布到这个频道上,所有哨兵都订阅这个频道,就能感知其他哨兵的存在了。

哨兵怎么知道从库信息的? 哨兵启动的时候只知道主库的IP和端口,与主库建立连接之后,通过给主库发送INFO, 获得从库信息, 并与从库建立连接。

哨兵怎么把通知发送给客户端? 哨兵上有多个消息频道,包括主库下线,从库重新配置事件,新主库切换等,客户端需要订阅这些消息频道,从而获取集群状态信息。

  • 由哪个哨兵执行主从切换?

也是通过选举的方式,如果某个哨兵获得n/2+1的得票,就成为leader哨兵,具有主从切换的操作权限。

Redis高可扩展性设计-集群分片

TODO

期待与您交流...

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Redis 基本架构
  • Redis高性能网络IO模型
  • Redis高可用设计-单实例
  • Redis高可用设计-集群
  • Redis高可扩展性设计-集群分片
相关产品与服务
云数据库 Redis
腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档