写点什么

Redis6.0 新特性、剖析线程模型 (单线程和多线程)

作者:C++后台开发
  • 2022-11-15
    湖南
  • 本文字数:3681 字

    阅读完需:约 12 分钟

Redis6.0新特性、剖析线程模型(单线程和多线程)

一. Redis6.0 新特性

1. 多线程 IO

 redis6.0 引入多线程 IO,只是用来处理网络数据的读写和协议的解析,而执行命令依旧是单线程,所以不需要去考虑 set/get、事务、lua 等的并发问题。(详细的线程模型见后面)


2. ACL 精细化权限控制

 在 Redis 5 版本之前,Redis 安全规则只有密码控制 还有通过 rename 来调整高危命令比如 flushdb , KEYS* , shutdown 等。

 Redis 6 则提供 ACL 的功能对用户进行更细粒度的权限控制 ,支持对客户端的权限控制,实现对不同的 key 授予不同的操作权限:

(1)接入权限: 用户名和密码 (2)可以执行的命令 (3)可以操作的 KEY

相关指令如下:

3. 支持 RESP3

 RESP(Redis Serialization Protocol)是 Redis 服务端与客户端之间通信的协议。

 Redis 5 使用的是 RESP2,而 Redis 6 开始在兼容 RESP2 的基础上,开始支持 RESP3。推出 RESP3 的目的:一是因为希望能为客户端提供更多的语义化响应,以开发使用旧协议难以实现的功能;另一个原因是实现 Client-side-caching(客户端缓存)功能。

4. 重新设计了客户端缓存

 基于 RESP3 协议实现的客户端缓存功能。为了进一步提升缓存的性能,将客户端经常访问的数据 cache 到客户端,减少 TCP 网络交互,提升 RT。

5. 提升了 RDB 的加载速度

 根据文件的实际组成(较大或较小的值),可以预期 20/30%的改进。当有很多客户机连接时,信息也更快了,这是一个老问题,现在终于解决了。

6. 支持 SSL  

 连接支持 SSL,更加安全。

7. Redis Cluster proxy(集群代理)

 antirez 开发了 Proxy 功能,让 Cluster 拥有像单实例一样的接入方式,降低大家使用 cluster 的门槛。不过需要注意的是代理不改变 Cluster 的功能限制,不支持的命令还是不会支持,比如跨 slot 的多 Key 操作。

8. Modules API

 Redis 6 中模块 API 开发进展非常大,因为 Redis Labs 为了开发复杂的功能,从一开始就用上 Redis 模块。Redis 可以变成一个框架,利用 Modules 来构建不同系统,而不需要从头开始写然后还要 BSD 许可。Redis 一开始就是一个向编写各种系统开放的平台。

 Disque 作为一个 Redis Module 使用足以展示 Redis 的模块系统的强大。集群消息总线 API、屏蔽和回复客户端、计时器、模块数据的 AOF 和 RDB 等等。

【文章福利】另外还整理一些C++后台开发架构师 相关学习资料,面试题,教学视频,以及学习路线图,免费分享有需要的可以点击领取

二. 剖析线程模型

1. 灵魂拷问

(1). 为什么说 redis6.0 之前是单线程的?

 redis 执行客户端命令的请求从: 获取 (socket 读)→解析→执行→内容返回 (socket 写) 等等都是由一个线程处理,所有操作是一个个挨着串行执行的 (主线程),这就是称 redis 是单线程的原因。

但是:redis 从 4.0 开始,也有后台线程在工作,处理一些较为缓慢的操作,例如无用连接的释放、大 key 的删除等等,严格意义上来说,redis6.0 之前也不完全是单线程的。

(2). 单线程非常快的原因是什么?

 A. 纯内存操作,避免大量访问数据库,减少直接读取磁盘数据,redis 将数据储存在内存里面,读写数据的时候都不会受到硬盘 I/O 速度的限制,所以速度快.

 B. 单线程操作,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗.

 C. 采用了非阻塞 I/O 多路复用机制.

(3). redis 在 6.0 之前为什么一直坚持单线程?

 官方曾做过类似问题的回复:使用 Redis 时,几乎不存在 CPU 成为瓶颈的情况, Redis 主要受限于内存和网络。例如在一个普通的 Linux 系统上,Redis 通过使用 pipelining 每秒可以处理 100 万个请求,所以如果应用程序主要使用 O(N)或 O(log(N))的命令,它几乎不会占用太多 CPU。

 使用了单线程后,可维护性高。多线程模型虽然在某些方面表现优异,但是它却引入了程序执行顺序的不确定性,带来了并发读写的一系列问题,增加了系统复杂度、同时可能存在线程切换、甚至加锁解锁、死锁造成的性能损耗。Redis 通过 AE 事件模型以及 IO 多路复用等技术,处理性能非常高,因此没有必要使用多线程。单线程机制使得 Redis 内部实现的复杂度大大降低,Hash 的惰性 Rehash、Lpush 等等 “线程不安全” 的命令都可以无锁进行。

(4). redis6.0 引入多线程机制的背景是什么?

 从 Redis 自身角度来说,因为读写网络的 read/write 系统调用占用了 Redis 执行期间大部分 CPU 时间,瓶颈主要在于网络的 IO 消耗, 优化主要有两个方向:

  (1). 提高网络 IO 性能,典型的实现比如使用 DPDK 来替代内核网络栈的方式

  (2). 使用多线程充分利用多核,典型的实现比如 Memcached。

 协议栈优化的这种方式跟 Redis 关系不大,支持多线程是一种最有效最便捷的操作方式。所以总结起来,redis 支持多线程主要就是两个原因:

  (1). 可以充分利用服务器 CPU 资源,目前主线程只能利用一个核

  (2). 多线程任务可以分摊 Redis 同步 IO 读写负荷

2. redis 单线程模型(6.0 之前)

 Redis 客户端对服务端的每次调用都经历了发送命令,执行命令,返回结果三个过程。其中执行命令阶段,由于 Redis 是单线程来处理命令的,所有每一条到达服务端的命令不会立刻执行,所有的命令都会进入一个队列中,然后逐个被执行。并且多个客户端发送的命令的执行顺序是不确定的。但是可以确定的是不会有两条命令被同时执行,不会产生并发问题,这就是 Redis 的单线程基本模型。

PS:可以修改 redis 的最大链接数,默认为 10000,如下图,如果要修改的话,直接修改配置文件重点 maxclients 即可。

(1). 什么是非阻塞 IO?

 非阻塞 IO 在 Socket 对象上提供了一个选项 Non_Blocking ,当这个选项打开时,读写方法不会阻塞,而是能读多少读多少,能写多少写多少。

 能读多少取决于内核为 Socket 分配的读缓冲区的大小,能写多少取决于内核为 Socket 分配的写缓冲区的剩余空间大小。读方法和写方法都会通过返回值来告知程序实际读写了多少字节数据。

 有了非阻塞 IO 意味着线程在读写 IO 时可以不必再阻塞了,读写可以瞬间完成然后线程可以继续干别的事了。

补充阻塞 IO 概念:

 当我们调用 Scoket 的读写方法,默认它们是阻塞的。

 read() 方法要传递进去一个参数 n,表示读取这么多字节后再返回,如果没有读够 n 字节线程就会阻塞,直到新的数据到来或者连接关闭了, read 方法才可以返回,线程才能继续处理。

 write() 方法会首先把数据写到系统内核为 Scoket 分配的写缓冲区中,当写缓存区满溢,即写缓存区中的数据还没有写入到磁盘,就有新的数据要写道写缓存区时,write() 方法就会阻塞,直到写缓存区中有空闲空间。

(2). 什么是 IO 多路复用?

背景:

 非阻塞 IO 有个问题,那就是单个线程要处理多个读写请求,处理某个客户端的的读数据的请求,结果读了一部分就返回了,线程如何知道什么时候才应该继续读数据。处理写请求的时候,如果缓冲区满了,写不完,剩下的数据何时才应该继续写?在什么时候处理什么请求?redis 单线程处理多个 IO 请求时就用到了 IO 多路复用技术。

原理:

 如下图,redis 需要处理 3 个 IO 请求,同时把 3 个请求的结果返回给客户端,所以总共需要处理 6 个 IO 事件,由于 redis 是单线程模型,同一时间只能处理一个 IO 事件,于是 redis 需要在合适的时间暂停对某个 IO 事件的处理,转而去处理另一个 IO 事件,这样 redis 就好比一个开关,当开关拨到哪个 IO 事件这个电路上,就处理哪个 IO 事件,其他 IO 事件就暂停处理了。这就是 IO 多路复用技术。

 以上是大致的理解下 IO 多路复用技术,在系统底层,IO 多路复用有 3 种实现机制:select、poll、epoll。


(3). 什么是文件处理器?

 A. Redis 基于 Reactor 模式开发了自己的网络事件处理器: 这个处理器被称为文件事件处理器(file event handler)

 B. 文件事件处理器使用 I/O 多路复用(multiplexing)程序来同时监听多个套接字, 并根据套接字目前执行的任务来为套接字关联不同的事件处理器。

 C. 当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关闭(close)等操作时, 与操作相对应的文件事件就会产生, 这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。

 D. 文件事件处理器以单线程方式运行, 但通过使用 I/O 多路复用程序来监听多个套接字, 文件事件处理器既实现了高性能的网络通信模型, 又可以很好地与 redis 服务器中其他同样以单线程方式运行的模块进行对接, 这保持了 Redis 内部单线程设计的简单性。

3. redis 多线程模型(6.0 开始)

(1). 流程如下:(如下图)

  • 主线程获取 socket 放入等待列表

  • 将 socket 分配给各个 IO 线程(并不会等列表满)

  • 主线程阻塞等待 IO 线程(多线程)读取 socket 完毕

  • 主线程执行命令 - 单线程(如果命令没有接收完毕,会等 IO 下次继续)

  • 主线程阻塞等待 IO 线程(多线程)将数据回写 socket 完毕(一次没写完,会等下次再写)

  • 解除绑定,清空等待队列

(2). 特点如下:

  • IO 线程要么同时在读 socket,要么同时在写,不会同时读或写

  • IO 线程只负责读写 socket 解析命令,不负责命令处理(主线程串行执行命令)

  • IO 线程数可自行配置


原文链接:第十一节:Redis6.0 新特性、剖析线程模型(单线程和多线程) - Yaopengfei - 博客园

用户头像

C/C++后台开发技术交流qun:720209036 2022-05-06 加入

还未添加个人简介

评论

发布
暂无评论
Redis6.0新特性、剖析线程模型(单线程和多线程)_redis_C++后台开发_InfoQ写作社区