Redis原理简述及发布订阅消息队列
目录
1 什么是Redis
2 Redis 非阻塞IO内部原理
2.1 IO多路复用策略
2.2 Reactor设计模式
3 基于PubSub的消息队列(发布-订阅)
由于集群之后存在多台服务器,并且不同客户端连接的可能是不同的服务器,因此在聊天过程中涉及到服务器之间的通信。
如果单纯的将各服务器之间建立TCP连接进行通信,相当于在服务器网络之间进行广播,会造成各服务器之间耦合度太高、业务实现复杂、应对故障能力差,不利于系统扩展,并且会占用系统大量的socket资源,各服务器之间的带宽压力太大,不能够节省资源给更多的客户端提供服务。
集群部署的服务器之间进行通信,最好的方式就是引入中间件消息队列,解耦各个服务器,使得整个系统松耦合,提高服务器的响应能力,节省服务器的带宽资源:
在集群分布式环境下,常用的中间件消息队列有ActiveMQ、RabbitMQ、Kafka等,redis属于比较轻量级的中间件。
1 什么是Redis
全称是Remote Dictionary Server(远程字典服务器),是一个开源的高性能键值对(key-value)存储系统。Redis具有以下特点来支持其高并发、分布式高性能的特性:
- 数据库保存在内存中:仅使用磁盘进行持久化,具有快速读写的高性能,数据存储在内存中,类似于HashMap,使得数据的查找和操作时间复杂度为O(1)。Redis支持将数据持久化到硬盘上,以防止数据丢失。它提供了两种持久化方式:RDB(Redis Database)和AOF(Append Only File)。RDB是将数据定期保存到磁盘文件中,而AOF则是将每个写操作追加到文件的末尾。
- 基于字典结构存储:Redis的底层存储结构主要依赖于字典,也称为散列表(hash)。字典是一种键值对的存储结构,用于在内存中快速查找和访问数据。整个Redis数据库就是通过字典来存储数据的,每个键值对都被存储在字典中。因此,在对Redis进行CURD操作时,实际上是对字典中的数据进行操作。
- 支持多种数据结构:不仅仅支持简单的key-value类型的数据,还提供了list、set、zset和hash等更复杂的数据结构的存储。
- 支持数据备份:可以通过master-slave模式进行数据备份,增加了数据的可靠性和安全性。
- 单线程模型:它在任何给定的时间只处理一条命令,这样可以避免了多线程之间的线程同步和竞争条件问题。但是,可以通过异步I/O和事件驱动模型来提高并发读写的能力,从而实现了高性能的读写操作。
- 非阻塞IO:Redis使用多路复用IO模型来处理IO操作,实现了非阻塞IO。它通过监听多个socket,一次性接受多个客户端请求,并将请求放入队列中。当完整的命令到达服务端后,再去处理请求,不需要等待客户端的传输。这种方式可以提高Redis的并发处理能力。
基于以上特性,Redis在多种应用场景使用:
- 发布订阅系统:Redis提供了发布订阅(Pub/Sub)机制,可以用于实现消息队列、实时聊天等功能。通过订阅感兴趣的频道,客户端可以接收到相应的消息。
- 地图信息分析:Redis提供了地理位置相关的数据结构和命令,可以用于存储和查询地理位置信息。这使得它在地图信息分析方面具有很大的优势。
- 计时器和计数器:Redis提供了丰富的命令,可以用于实现计时器和计数器功能,比如记录用户的浏览量、点赞次数等。
- 分布式锁:Redis的setnx命令可以用于实现分布式锁。在分布式应用中,为了保证同一时刻只有一个线程执行关键代码,可以使用Redis的分布式锁功能来实现。
- 实时数据分析:由于Redis具有高性能和低延迟的特点,适合用于实时数据分析。可以将实时产生的数据存储在Redis中,并通过Redis提供的相关命令进行实时的数据查询和统计分析。
2 Redis 非阻塞IO内部原理
Redis内部实现采用epoll+Reactor 设计模式。 epoll中的读、写、关闭、连接都转化成了事件,利用epoll的多路复用特性结合事件驱动提高网络性能。
Redis的实现模式类似于muduo库,都是使用epoll+Reactor。因为 Redis 需要在多个平台上运行,同时为了最大化执行的效率与性能,所以会根据编译平台的不同选择不同的 I/O 多路复用函数作为子模块,提供给上层统一的接口。
2.1 IO多路复用策略
redis的多路复用, 提供了select, epoll, evport, kqueue几种选择,在编译的时候来选择一种。
select是POSIX提供的, 一般的操作系统都有支撑;
epoll 是LINUX系统内核提供支持的;
evport是Solaris系统内核提供支持的;
kqueue是Mac 系统提供支持的;
为了将所有 IO 复用统一,Redis 为所有 IO 复用统一了类型名 aeApiState,对于 epoll 而言,类型成员就是调用 epoll_wait所需要的参数
接下来就是一些对epoll接口的封装了:
包括创建 epoll(epoll_create)
注册事件(epoll_ctl)
删除事件(epoll_ctl)
阻塞监听(epoll_wait)等
创建 epoll 就是简单的为 aeApiState 申请内存空间,然后将返回的指针保存在事件驱动循环中,注册事件和删除事件就是对 epoll_ctl 的封装,根据操作不同选择不同的参数,阻塞监听是对 epoll_wait 的封装,在返回后将激活的事件保存在事件驱动中。
2.2 Reactor设计模式
Redis 服务采用 Reactor 的方式来实现文件事件处理器(每一个网络连接其实都对应一个文件描述符)
当 main 函数初始化工作完成后,就需要进行事件驱动循环,而在循环中,会调用 IO 复用函数进行监听在初始化完成后,main 函数调用了 aeMain 函数,传入的参数就是服务器的事件驱动
Redis 对于时间事件是采用链表的形式记录的,这导致每次寻找最早超时的那个事件都需要遍历整个链表,容易造成性能瓶颈。而 libevent 是采用最小堆记录时间事件,寻找最早超时事件只需要 O(1) 的复杂度。
通过Reactor的方式,可以将用户线程轮询IO操作状态的工作统一交给handle_events事件循环进行处理。
用户线程注册事件处理器之后可以继续执行做其他的工作(异步),而Reactor线程负责调用内核的select/epoll函数检查socket状态。当有socket被激活时,则通知相应的用户线程(或执行用户线程的回调函数),执行handle_event进行数据读取、处理的工作。由于select/epoll函数是阻塞的,因此多路IO复用模型也被称为异步阻塞IO模型。注意,这里的所说的阻塞是指select函数执行时线程被阻塞,而不是指socket。一般在使用IO多路复用模型时,socket都是设置为NONBLOCK的,不过这并不会产生影响,因为用户发起IO请求时,数据已经到达了,用户线程一定不会被阻塞。
3 基于PubSub的消息队列(发布-订阅)
发布订阅模式是一种消息传递模式,其中发布者发送消息而不直接指定接收者,订阅者通过注册感兴趣的主题来接收消息。当发布者发布某个主题的消息时,所有订阅该主题的消费者都会收到该消息。这种模式有效地实现了解耦,使得生产者和消费者之间的交互更加灵活,常用于事件驱动架构和消息队列系统中。
发布/订阅模式可以 1:N 的消息发布/订阅。发布者将消息发布到指定的频道频道(channel),订阅相应频道的客户端都能收到消息。
PubSub(发布订阅)是Redis2.0版本引入的消息传递模型。
优缺点:
优点:
实时性:Pub/Sub 模式允许消息的即时传递,订阅者能够快速接收到发布者发送的消息,适合需要快速响应的应用场景,如实时聊天、在线游戏等。
解耦合:发布者和订阅者之间是松耦合的,发布者无需知道订阅者的存在,增强了系统的灵活性和可扩展性。
多频道支持:可以同时订阅多个频道,允许订阅者接收来自不同频道的消息。
缺点:
消息丢失:如果在发布消息时没有任何订阅者在线,消息将被丢弃,无法持久化。这意味着在网络故障或 Redis 宕机时,消息会丢失。
缺乏确认机制:Pub/Sub 模式没有消息确认机制,无法保证消息是否被成功接收和处理。这可能导致消费者在处理消息时出现问题而无法重试。
性能压力:当订阅者数量较多时,发布大量消息可能会对 Redis 服务器造成较大负担,影响系统性能。
常用命令:
命令 | 用法 | 描述 |
SUBSCRIBE |
| 订阅一个或多个频道,以接收该频道的消息。 |
UNSUBSCRIBE |
| 取消对一个或多个频道的订阅。 |
PUBLISH |
| 向指定的频道发布消息。 |
PSUBSCRIBE |
| 订阅符合特定模式的频道(支持通配符,如 )。 |
PUNSUBSCRIBE |
| 取消对符合特定模式的频道的订阅。 |
PUBSUB |
| 查看当前订阅的活跃频道。 |
| 返回指定频道的订阅者数量。 | |
| 返回当前通过模式订阅(PSUBSCRIBE)的数量。 |
说明:
支持通配符的命令:PSUBSCRIBE
和 PUNSUBSCRIBE
使用通配符(如 news.*
)匹配频道名。
PUBSUB 子命令:
CHANNELS
:列出当前活跃的频道。
NUMSUB
:查询指定频道的订阅者数量。
NUMPAT
:统计模式订阅的数量。
注意:UNSUBSCRIBE
和 PUNSUBSCRIBE
若不指定参数,默认取消所有订阅。