Redis 队列、Redis 6 多线程
Redis队列
基于List
- 通过lpush、brpop指令,消费消息延迟几乎为0,但是需要处理闲置连接的问题,避免闲置过久导致异常
- 做消费端确认比较复杂,不能保证消费端消费消息后成功处理(宕机、异常),通常会维护一个待确认列表
- 不能做广播模式,不能重复消费,不支持分组消费
基于ZSet
- 多用来实现延迟队列,当然也可以实现有序的普通的消息队列,但是消费者无法阻塞的获取消息,只能轮询,不允许重复消息
基于Pub/Sub
- 一个消息可以发布到多个消费者,但是一旦消息发布,消费端没处理,消息却已经丢失了,所以适用于即使通讯的场景
基于Stream
-
Redis 5最大的新特性就是多出了一个数据结构Stream(参考Kafka设计),支持多播、可持久化的消息队列,弥补了发布/订阅模式不能持久化的缺陷
-
每个Stream队列以Redis的key作为队列名,并且可以拥有多个消费组
- 每个消费组会维护消费进度(游标last_delivered_id),并且同一个消费组中的消费者都是竞争关系,任意一个消费者抢到消息了就会去维护消费进度
- 每个消费组都是相互独立的,同一份消息每个消费组都能消费到
-
Redis 6 多线程
Redis 6之前的版本真的是多线程吗
- 确切的说不是,在处理客户端请求时,包括获取(
Socket读)、解析、执行、内容返回(Socket写)等都是由一个顺序串行的主线程处理,这就是所谓的单线程,但如果严格来讲Redis 4之后并不是单线程,除了主线程外,它也有后台线程在处理一些较为缓慢的操作,比如清理脏数据,无用连接、大key的删除等等
Redis 6之前为什么一直不使用多线程
-
官方回复,使用Redis时,几乎不存在CPU成为瓶颈的情况,Redis主要受限于内存和网络
- 例如在一个普通的Linux系统上,Redis通过使用
pipelining每秒可以处理100w请求,所以如果应用程序主要使用O(n)或O(logn)的命令,它几乎不会占用太多CPU
- 例如在一个普通的Linux系统上,Redis通过使用
-
使用了单线程后,可维护性高,多线程模型虽然在某些方面表现优异,但是它却引入了程序执行顺序的不确定性,带来了并发读写的一系列问题,增加了
系统复杂度,同时可能存在线程切换、甚至加锁解锁、死锁造成的性能损耗 -
Redis通过AE事件模型以及IO多路复用等技术,处理性能非常高,因此没有必要使用多线程。单线程机制使得Redis内部实现的复杂度大大降低,Hash的惰性Rehash、LPush等等线程不安全的命令都可以无锁化进行
Redis 6为什么要引入多线程
-
详解-
Redis将所有数据放入内存中,内存的响应时长大约为100ns,对于小数据包,Redis服务器可以处理8w到10w并发量,这也是Redis处理的极限了,对于80%的公司来说,单线程的Redis已经足够使用了。但随着越来越复杂的业务场景,有些公司动不动就上亿的交易量,因此需要更大的QPS
-
常见的解决方案是在分布式架构中对数据进行分区并采用多个服务器,但该方案有非常大的缺点,例如要管理的Redis服务器太多,维护代价大。某些适用于单个Redis服务器的命令不适用于数据分区,数据分区无法解决热点读/写问题 -
数据偏斜,重新分配和放大/缩小变得更加复杂等等
-
从Redis自身角度来说,因为读写网络的读写系统调用占用了Redis执行期间大部分的CPU时间,瓶颈主要在于网络的IO消耗,优化主要
有两个方向- 提高网络IO性能,典型的实现比如使用DPDK来替代内核网络栈的方式
- 使用多线程充分利用多核,典型的实现比如Memcached
- 协程栈优化的这种方式跟Redis关系不大,支持多线程是一种最有效最便捷的操作方式
-
-
简概-
Redis支持多线程主要就是两个原因可以充分利用服务器CPU资源,目前主线程只能利用一个核多线程任务可以分摊Redis同步IO读写负荷
-
Redis 6默认是否开启了多线程
-
Redis6的多线程默认是禁用的,只使用主线程
-
如果开启需要修改配置redis.conf中
io-threads-do-reads yes-
开启多线程后,还需要设置线程数,否则是不生效的
-
关于线程数的设置,
官方有个建议- 4核的机器建设设置为2或3个线程、8核的建议设置为6个线程,线程数一定要小于机器核数。还需要注意的是,线程数并不是越大越好,官方认为超过8个基本就没有意义了
-
Redis 6采用多线程后,性能的提升效果如何
- Redis6引入的多线程IO特性对
性能提升至少是一倍以上 - 国内也有大牛曾使用unstable版本在阿里云esc进行过测试,GET/SET命令在4线程IO时性能相比单线程是几乎是翻倍了
- 如果开启多线程,至少要4核的机器,且Redis实例已经占用相当大的CPU耗时的时候才建议采用,否则使用多线程没有意义
Redis 6多线程的实现机制
-
主线程负责接收建立连接请求,获取 socket 放入全局等待读处理队列
-
主线程处理完读事件之后,通过 RR(Round Robin) 将这些连接分配给这些 IO 线程
-
主线程阻塞等待 IO 线程读取 socket 完毕
-
主线程通过单线程的方式执行请求命令,请求数据读取并解析完成,但并不执行回写 socket
-
主线程阻塞等待 IO 线程将数据回写 socket 完毕
-
解除绑定,清空等待队列
-
设计特点
- IO 线程要么同时在读 socket,要么同时在写,不会同时读或写
- IO 线程只负责读写 socket 解析命令,不负责命令处理
-
开启多线程后,是否会存在线程并发安全问题
- 从上面的实现机制可以看出,Redis的多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程顺序执行
所以我们不需要去考虑控制 key、lua、事务,LPUSH/LPOP 等等的并发及线程安全问题
Redis 6的多线程和Memcached多线程模型进行对比
-
Memcached 服务器采用 master-woker 模式进行工作,服务端采用 socket 与客户端通讯。主线程、工作线程 采用 pipe管道进行通讯。主线程采用 libevent 监听 listen、accept 的读事件,事件响应后将连接信息的数据结构封装起来,根据算法选择合适的工作线程,将连接任务携带连接信息分发出去,相应的线程利用连接描述符建立与客户端的socket连接 并进行后续的存取数据操作
-
相同点
- 都采用了 master线程-worker 线程的模型
-
不同点
- Memcached 执行主逻辑也是在 worker 线程里,模型更加简单,实现了真正的线程隔离,符合我们对线程隔离的常规理解
- 而 Redis 把处理逻辑交还给 master 线程,虽然一定程度上增加了模型复杂度,但也解决了线程并发安全等问题