- 前面讲到redis也可以用列表做消息队列,配合lredis发布订阅(pub/sub)使用可以搞个聊天系统。那么redis发布订阅是什么呢?它是一种消息通信模式,发送者(pub)发送消息,订阅者(sub)接收消息。
- 可以想象这么一个场景。我们抖音、微博关注了哪个博主,当他发布一条文章时,系统就会给我们推送他发布的消息内容。此时,博主=r消息发送者,我们用户=消息订阅者,我们关注博主的这个动作=订阅频道。当然像抖音等这种大型复杂的功能用redis来实现有些吃力,只能说redis可以实现这么一个功能,一般都会用专门的MQ,像rabbitmq、kafka等。
- 还是上面这个场景,我们可以关注很多个博主,美食博主、健身博主等等。他们发布内容我们都可以接收到信息。所以在redis中,redis客户端可以订阅任意数量的频道。
SUBSCRIBE命令:可以让客户端订阅任意数量的频道,每当有新消息发送到被订阅的频道时,信息就会发送给订阅这个频道的所有客户端。
命令格式:SUBSCRIBE [频道1名称] [频道2名称]
- 首先Redis服务器结构是redis.h/redisServer结构表示的,而结构的 pubsub_channels 属性是一个字典{channel:[client1,client2…]},它的key为正在被订阅的频道,value为链表保存订阅该频道的客户端。所以这个字典是用来保存订阅频道的客户端。
- 当SUBSCRIBE命令执行成功时,程序会检查出命令中指定频道的键,判断是否是pubsub_channels 字典的键,就可以知道该频道就已经被客户端订阅了。这时取出键值, 就可以得到所有订阅该频道的客户端信息。
- 返回的客户端信息包括:该客户端订阅的频道、目前已订阅的频道数量以及频道发过来的信息。
PUBLISH命令:客户端将信息发送到指定的频道。
命令格式: PUBLISH [频道名称] [发送内容]
上面这个redis-cli就相当于博主,下面这个redis-cli就相当于用户。当博主发布文章后,关注博主的用户就会收到他的文章。
当调用 PUBLISH channel message 命令, 程序根据 channel 定位到字典的键, 然后将信息发送给字典值链表中的所有客户端。
unsubscribe 命令: 退出给定的频道。
命令格式: UNSUBSCRIBE [频道名称]
- 命令执行成功后,从 pubsub_channels 字典的给定频道(key)中,删除关于当前客户端的信息(value)。
- 因为redis的订阅操作是阻塞式的,当频道被退订后就不会再发送给这个客户端了。所以当我们Ctrl+c时,就直接退订频道了。
psubscribe 命令: 订阅一个或多个符合给定模式的频道。、
- 命令格式: PSUBSCRIBE pattern [pattern …]
- 示例:PSUBSCRIBE qingjun* 订阅以qingjun为开头的所有频道。
在说命令实行原理之前,我们需要了解前提背景条件。
执行背景条件:
redisServer.pubsub_patterns 属性是一个链表,链表中保存着所有和模式相关的信息。链表中的每个节点都包含一个 redis.h/pubsubPattern 结构:
- client 属性保存着订阅模式的客户端。
- pattern 属性则保存着被订阅的模式。
执行流程:
- 当PSUBSCRIBE 命令执行成功订阅一个模式时,程序会创建一个包含客户端信息和被订阅模式的 pubsubPattern 结构,并将该结构添加到redisServer.pubsub_patterns 链表中。
- 然后程序通过遍历整个 pubsub_patterns 链表,就可以检查出所有正在被订阅的模式,以及订阅这些模式的客户端。
当客户端同时订阅了模式和符合该模式的某个频道,那么发送给这个频道的消息将被客户端接收到两次,只不过这两条消息类型不同,一个是message类型,一个是pmessage类型,但其内容相同。
pubsub 命令: 查看订阅与发布系统状态
- 命令格式:PUBSUB CHANNELS [pattern]
- 例子:
- PUBSUB CHANNELS 列出订阅与发布系统中的所有活跃频道。
- PUBSUB CHANNELS news.i* 列出以news.i*开头的所有活跃频道。
- 返回由活跃频道组成的列表,即可以查询订阅与发布系统的状态。
PUBLISH命令:客户端将信息发送到指定的频道。和频道订阅命令相同,但是运行机制不同。
命令格式: PUBLISH [频道名称] [发送内容]
PUBLISH命令时, 除了将消息发送到该订阅频道的所有客户端之外(频道的订阅过程),同时它还会将频道和 pubsub_patterns 中的模式进行对比,如果频道和某个模式匹配的话,还会把消息发送到订阅这个模式的客户端。
示例:
- 当有信息发送到 qingjun.shop.2频道时, 信息除了发送给 client4、client5、client6 之外, 还会发送给订阅 qingjun.shop.* 模式的 client7和 client8 。
- punsubscribe命令: 退订所有给定模式的频道。
- 命令格式:PUNSUBSCRIBE [pattern [pattern …]]
命令执行成功后,程序会删除redisServer.pubsub_patterns 链表中,所有和被退订模式相关联的 pubsubPattern 结构,这样客户端就不会再收到和模式相匹配的频道发来的信息。
从以上的分析来看,我么可以来总结一下。
特性:
- 执行SUBSCRIBE,PSUBSCRIBE,UNSUBSCRIBE和PUNSUBSCRIBE命令,返回结果都包含了该客户端当前订阅的频道和模式的数量。当这个数量变为0时,该客户端会自动退出订阅状态。
- 使用PUBLISH和SUBSCRIBE命令的客户端必须一直在线,断线可能会导致客户端丢失消息。
- redis无法对消息持久化存储,一旦消息被发送,如果没有订阅者接收,那么消息就会丢失。
- redis没有提供消息传输保障,当客户端连接超时或事务回滚等情况发生时,消息会存在丢失。
使用场景:
- 构建实时消息系统,比如普通的即时聊天,群聊等功能。
- 遇到读写分离的场景,在写入的过程中可以使用redis发布订阅,使得写入值及时发布到各个读的程序中,就可以保证数据的完整一致性。
- 微信公众号订阅功能。有粉丝订阅了你的公众号,当你发布新文章,就可以推送消息给粉丝们功能。
- 还有很多场景可以使用,但是数据过于复杂redis作为消息中间件是不太好用的,这时我们可以去使用专门消息队列中间件MQ。
当一个redis服务宕机后,会存在数据丢失情况。所以项目上也是使用redis集群来对数据进行全方位保护。在讲redis基础1时,mysql单机时代的落寞就是因为单节点性能处理不好高并发等问题,渐而有了读写分离的出现。
基础概念:
- redis集群是采用主从复制、读写分离和哨兵模式来实现的,主节点(master)负责写,从节点(slave)负责读。
- 主节点的数据复制到其他从节点,这个过程称作主从复制。过程中的数据复制只能是单向的,只能由主节点到从节点。
- 一个主节点可以有多个从节点(一个redis-server也可以当作主节点),但一个从节点只能有一个主节点。
- 至于哨兵模式是个重点,我们后面单独拿出来讲。
集群作用:
- 数据冗余。主从复制实现了数据的热备,是持久化之外的一种数据冗余方式。
- 故障恢复。当主节点宕机时,从节点升为主节点继续提供服务,实现快速的故障恢复。
- 负载均衡高可用。在主从复制+读写分离,由主节点提供写服务,从节点提供读服务,分担服务器负载。这种架构常常用于“写少读多”的场景下,通过多个从节点分担读负载,可以大大提高 Redis 服务器的并发量。
启动一个redis服务,查看集群信息。可以看出一个redis服务(一个redis进程)也是一个主节点,默认为主节点。
1、拷贝多个redis.conf文件,为各个redis服务启动做准备。
redis.conf文件,为主节点启动文件。
redis_6380.conf文件,为第一个从节点启动文件。
redis_6381.conf文件,为第二个从节点启动文件。
redis_6382.conf文件,为第三个从节点启动文件。
- 修改daemonize参数为yes,保证redis服务为后台运行。
- 修改pid文件名称,不然几个redis服务启动后读取conf文件里的默认pid文件名会冲突,所以得保证每个redis服务的pid文件名称不一样。
- 修改文件日志名称,默认是不输出日志文件的,所以我们要给这四个conf文件里的logfile参数配上,保证四个日志文件名称不一样。
- 修改持久化文件名称,也是防止冲突。
#redis_6379主节点配置文件
daemonize yes
pidfile /var/run/redis_6379.pid
logfile "redis_6379.log"
dbfilename dump_6379.rdb
#redis_6380从节点配置文件
daemonize yes
pidfile /var/run/redis_6380.pid
logfile "redis_6380.log"
dbfilename dump_6380.rdb
#redis_6381从节点配置文件
daemonize yes
pidfile /var/run/redis_6381.pid
logfile "redis_6381.log"
dbfilename dump_6381.rdb
#redis_6382从节点配置文件
daemonize yes
pidfile /var/run/redis_6382.pid
logfile "redis_6382.log"
dbfilename dump_6382.rdb
3、启动服务
1. 启动主节点:redis-server redis.conf
2. 启动第一个从节点:redis-server redis_6380.conf
3. 启动第二个从节点:redis-server redis_6381.conf
4. 启动第三个从节点:redis-server redis_6382.conf
可以看出,主节点和三个从节点的信息显示都是主节点,因为redis服务默认自己就是主节点。
- 此时修改了第一个从节点redis_6380.conf配置文件,重启该节点。
- 连接6380从节点,Info看到,6380主节点已经变成从节点。
- 同时再看6379主节点,Info看到,6379主节点已经有一个从节点及其信息。
- 也可以通过命令:slaveof [主机地址] [端口号] 来设置,但是重启后会失效,不推荐。
redis集群可以有多种模式。
- 一主多从,所有从机的数据都从主机来。
- 层层链路,第一个主机把数据传给第一个从机,第一个从机当作第二个主机把数据床给第二个从机。
- 当主节点写入数据时,会把数据同步给各个从节点。
1、主节点插入一个键值。
主节点能读能写,从节点只能读不能写。
当主机宕机时,从机依旧连接到主机;当主机恢复时,从机依旧可以从主机获取数据。
- 当从机宕机再启动成功连接到主机后,会发送一个sync同步命令。
- 主接收到sync命令后,会启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台执行完成后,主机把整个数据文件传给从机,并完成一次完全同步。
- 从机接收到数据库文件数据后,就会把数据写入磁盘再加载到内存中。(全量复制)
- 后续主机会继续将新的所有收集到的修改命令依次传给从机,从机接到数据存盘再加载到内存中。(增量复制)
所以,从机宕机期间主机写入的数据,会在从机重新连回主机后执行依次完全同步(全量复制)。
此时的6379是主节点,6380、6381、6382三个都是从节点不能写,但都可以收到主节点的数据,从而实现主从复制。
当主节点宕机时,所有从节点还是连接的主节点,此时是无法继续接受新数据的。所以我们可以手动选主,从众多的从节点中选一个主出来,但是这个手动选出来的主节点并没有得到其他从节点的“认可”。
例如:6379(主)宕机后,6380(从)还是连接的6379(主),6381(从)还是连接的6382(从)。此时6382(从)自己通过命令“slaveod no one”选取自己当主节点成为6382(主),此时我们可以看出,6380(从)并没有跟随6382(主),因为这个6382(主)是手动设置的,所以其他的从节点要想"跟随"6382(主),只能通过slaveof命令设置指定主机。这种手动设置的方式比较麻烦,所以后来就推出了哨兵模式。
前面我们讲到,主节点宕机之后只能手动设置主节点,这是比较麻烦的,所以从redis 从 2.8 开始正式提供了 Sentinel(哨兵) 架构来解决这个问题。也就是自动选主,既然是自动选,那是怎么个自动法儿?咱们来研究学习一下。
- 哨兵模式是一种特殊的模式,而且Redis 也提供了哨兵的命令,哨兵是一个独立的进程,它会独立运行,能够后台监控主机是否故障,如果故障了根据投票数自动将从机转换为主机。
哨兵通过发送命令,等待 Redis 服务器响应,从而监控运行的多个 Redis 实例。
- 发送哨兵命令后,让各个redis服务器返回监控其运行状态,包括主机和从机。
- 当哨兵监测到 master 宕机,会自动将 slave 切换成 master,然后通过发布订阅模式通知其他的从机修改配置文件,让它们切换主机。
因为一个哨兵进程对 Redis 服务器进行监控,哨兵本身可能会出现问题。所以,我们可以使用多个哨兵进行监控,渐而演进出多哨兵模式。
- 当主节点宕机后,哨兵1会先检测到这个结果,这时并不会立刻进行 failover(故障转移) 过程,因为这仅仅是哨兵1主观的认为主节点不可用,这个现象称为主观下线。
- 等后面的哨兵也检测到主机不可用,并且哨兵数量达到一定值时(达到这个值就判断出主节点确实已经宕机了),哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,这时候再进行 failover(故障转移) 过程。
- 故障转移完成后,就会通过发布订阅模式,通知让其他各个哨兵把自己监控的从节点更换指定的主节点,这个过程称为客观下线。
当前我们的集群是一主多从模式。
- 哨兵通过redis-sentinel命令来运行,跟启动redis服务一样,运行时需要制定一个配置文件,而这个配置文件就需要我们来写。
- 在配置文件里可以配置哨兵的所有规则,这里只实操其中的一个规则,也是最基本、最核心的配置规则。
哨兵配置文件规则:
- 文件名称必须是sentinel.conf,否则会启动哨兵失败。
- sentinel monitor 为固定格式;
- qingjun是自己命名,为哨兵的名字;
- 127.0.0.1是监控主机;
- 6379 是监控的端口;
1 代表选举规则,选举规则多杂,这里就用默认的。
启动命令:redis-sentinel sentinei.conf
2. 等待了一段时间(该时间属于哨兵规则之一,也可以配置),6381从节点被选举主节点,6380从节点和6382从节点指向了6381主节点。怎么选举的?是调用的选举算法,比较多不细讲。
3. 当6379主节点回来后,会自动成为6381主节点的从节点。
- port参数,默认指定的是26379。
- 当有哨兵集群时,就要生成多个sentinei.conf文件,配置不同的端口信息。
dir参数,指定哨兵的运行文件。
规则格式: sentinel monitor < master-name> < ip> < redis-port> < quorum>
- 哨兵 sentinel 监控的 redis 主节点的 ip port
- master-name :可以自己命名的主节点名字,只能由字母 A-z、数字 0-9、".-_"这三个字符组成。
- quorum: 配置多少个 sentinel 哨兵统一认为 master 主节点失联,配置1就表示第一个哨兵认为主节点下线就是宕机了。
规则格式: sentinel auth-pass < master-name> < password>
- 当 Redis设置了连接密码,这里就填那个连接密码。
- 所以设置哨兵 sentinel 连接主从的密码,必须为主从设置一样的密码。
规则格式: sentinel down-after-milliseconds < master-name> < milliseconds>
- 指定多少毫秒之后,主节点没有应答哨兵 sentinel,此时,哨兵主观上认为主节点下线,默认 30 秒。
规则格式: sentinel parallel-syncs < master-name> < numslaves>
- 这个配置项指定了在发生 failover 主备切换时最多可以有多少个 slave 同时对新的 master 进行同步。
- 数字越小,完成 failover 所需的时间就越长,但是如果这个数字越大,就意味着越多的 slave 因为 replication 而不可用。
- 通常设为 1 ,保证每次只有一个 slave 处于不能处理命令请求的状态。
规则格式: sentinel failover-timeout < master-name> < milliseconds>
- 故障转移的超时时间 failover-timeout 可以用在以下这些方面:
- 同一个 sentinel 对同一个 master 两次 failover 之间的间隔时间。
- 当一个 slave 从一个错误的 master 那里同步数据开始计算时间。直到 slave 被纠正为向正确的 master 那里同步数据时。
- 当想要取消一个正在进行的 failover 所需要的时间。
- 当进行 failover 时,配置所有 slaves 指向新的 master 所需的最大时间。
不过,即使过了这个超时,slaves 依然会被正确配置为指向 master,但是就不按 parallel-syncs 所配置的规则来了
默认三分钟
规则格式: sentinel notification-script < master-name> < script-path>
- 配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。
- 对于脚本的运行结果有以下规则:
- 若脚本执行后返回 1,那么该脚本稍后将会被再次执行,重复次数目前默认为 10
- 若脚本执行后返回 2,或者比 2 更高的一个返回值,脚本将不会重复执行。
- 如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为 1 时的行为相同。
- 一个脚本的最大执行时间为 60s,如果超过这个时间,脚本将会被一个 SIGKILL 信号终止,之后重新执行。
- 通知型脚本:
- 当 sentinel 有任何警告级别的事件发生时(比如说 redis 实例的主观失效和客观失效等),将会去调用这个脚本
- 这时这个脚本应该通过邮件,SMS 等方式去通知系统管理员关于系统不正常运行的信息。
- 调用该脚本时,将传给脚本两个参数,一个是事件的类型,一个是事件的描述。
- 如果 sentinel.conf 配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则 sentinel 无法正常启动成功。
规则格式: sentinel client-reconfig-script < master-name> < script-path>
- 当一个 master 由于 failover 而发生改变时,这个脚本将会被调用,通知相关的客户端关于 master 地址已经发生改变的信息。
以下参数将会在调用脚本时传给脚本:
< master-name> < role> < state> < from-ip> < from-port> < to-ip> < to-port>
- 目前 < state> 总是 “failover”,
- < role> 是 “leader” 或者 “observer” 中的一个。
- from-ip,from-port,to-ip,to-port是用来和旧的 master 和新的 master (即旧的 slave)通信的
这个脚本应该是通用的,能被多次调用,不是针对性的。
优点:
- 哨兵集群模式是基于主从模式的,所有主从的优点,哨兵模式同样具有。
- 主从可以切换,故障可以转移,系统可用性更好。
- 哨兵模式是主从模式的升级,系统更健壮,可用性更高。
缺点:
- redis集群在线扩容难,在集群容量达到上限时在线扩容会变得很复杂。
- 实现哨兵模式的配置也不简单,甚至可以说有些繁琐。