Redis原理浅析(二):消息通知、持久化、集群、redis遇到的问题及解决方法


上一节介绍了数据基本类型,事务,以及对数据惊醒排序的操作,这一节我们来介绍Redis的进阶操作:消息通知、持久化、集群、Redis作为缓存会出现的问题以及解决方法。

消息通知

生产者消费者模式

生产者生产消息放到队列中,多个消费者同时监听队列,谁先抢到消息谁就会从队列中取走消息,即对于每个消息最多只能被一个消费者拥有。

  • 具体的方法就是创建一个任务队列,生产者主动lpush消息,而消费者去rpop数据。
  • 但是这样存在一个问题,就是消费者需要主动去请求数据,周期性的请求会造成资源的浪费,如果可以实现一旦有新消息加入队列就通知消费者就好了。
  • 这时借助brpop命令就可以实现这样的需求。brpop和rpop命令相似,唯一区别就是当列表中没有元素时,brpop命令会一直阻塞住连接,直到有新元素加入。

BRPOP key timeout

brpop命令接收两个参数,第一个参数key为键值,第二个参数timeout为超时时间。BRPOP命令取数据时候,如果暂时不存在数据,该命令会一直阻塞直到达到超时时间。如果timeout设置为0,那么就会无限等待下去。

任务队列模式示意图:
Redis原理浅析(二):消息通知、持久化、集群、redis遇到的问题及解决方法
任务队列:
(1)实例B中对队列queue进行监听
Redis原理浅析(二):消息通知、持久化、集群、redis遇到的问题及解决方法
(2)实例A中将“task”入队
Redis原理浅析(二):消息通知、持久化、集群、redis遇到的问题及解决方法
(3)实例B马上返回结果
Redis原理浅析(二):消息通知、持久化、集群、redis遇到的问题及解决方法
(4)queue中元素被取走
Redis原理浅析(二):消息通知、持久化、集群、redis遇到的问题及解决方法
优先级队列:
(1)BLPOP可以同时接收多个键
Redis原理浅析(二):消息通知、持久化、集群、redis遇到的问题及解决方法
(2)再另一个实例中执行入队操作
Redis原理浅析(二):消息通知、持久化、集群、redis遇到的问题及解决方法
(3)返回结果
Redis原理浅析(二):消息通知、持久化、集群、redis遇到的问题及解决方法
(4)如果多个键都有元素,则按照从左到右的顺序取第一个键中的一个元素
Redis原理浅析(二):消息通知、持久化、集群、redis遇到的问题及解决方法
Redis原理浅析(二):消息通知、持久化、集群、redis遇到的问题及解决方法
优点:

  1. 松耦合。生产者和消费者只需按照约定的任务描述格式,进行编写代码。
  2. 易于扩展。多消费者模式下,消费者可以分布在多个不同的服务器中,由此降低单台服务器的负载

发布者订阅者模式

发布者生产消息放到队列里,多个监听队列的订阅者都会受到同一份消息。

生产者使用下面命令来发布消息:

PUBLISH CHANNEL MESSAGE

订阅者通过下面的命令来订阅消息,执行subscribe命令后,客户端进入订阅状态,处于此状态的客户端不能使用除SUBSCRIBE、UNSUBSCRIBE、PSUBSCRIBE、PUNSUBSCRIBE这4个属于“发布/订阅”模型的命令之外的命令。另外,可以使用subscribe channel1.1 channel1.2 … 同时订阅多个频道。

SUBSCRIBE CHANNEL

Redis原理浅析(二):消息通知、持久化、集群、redis遇到的问题及解决方法
Redis原理浅析(二):消息通知、持久化、集群、redis遇到的问题及解决方法
Redis原理浅析(二):消息通知、持久化、集群、redis遇到的问题及解决方法

Redis持久化

  • RDB方式:根据指定的规则“定时”将内存中的数据存储在硬盘上。
  • AOF方式:每次执行命令后将命令本身记录下来。

RDB方式

RDB是通过快照(snapshotting)完成的
快照原理:

①redis使用fork函数(写时复制策略)复制一份当前进程的副本;
②父进程继续接收并处理客户端发来的请求,而子进程开始将内存中的数据写入硬盘中的临时文件;
③当子进程写完所有数据后会用该临时文件替换旧的RDB文件,至此一次快照操作完成。

进行快照的几种情况:

①根据配置规则进行自动快照;
②用户执行SAVE或BGSAVE命令;
③执行FLUSHALL命令;
④执行复制(replication)时。

RDB方式存在的问题:

  • 如果数据量很大的话,RDB它要保存一个完整的数据集是一个大的工作 。
  • 如果时间间隔设置的太短,那么严重影响redis的性能;但是按照常规设置的话,如5分钟一次,那么如果宕机或者重启,就会基于上次做RDB的时间从而丢失分钟级的数据。

AOF方式

当用redis存储非临时的数据时,一般需要打开AOF持久化来降低进程中止导致的数据丢失。AOF可以将redis执行的每一条写命令追加到硬盘文件中,这一过程显然会降低redis的性能,但是大部分情况下这个影响是可以接受的,另外使用较快的硬盘可以提高AOF的性能。

同步硬盘数据:

由于系统的缓存机制,AOF文件并没有真正写入硬盘,而是进入了系统的硬盘缓存,在默认情况下系统每30秒执行一次同步操作。

在redis中我们可以通过appendfsync参数设置同步的时机:

  • appendfsync always #每次执行写入时同步
  • appendfsync everysec #每秒执行一次同步操作
  • appendfsync no #交操作系统来做

AOF方式存在的问题:

  • AOF文件的大小会随着时间线性增长,一段时间之后就会变得很大 。
  • 如果要在一端以AOF的形式来恢复数据,那么由于AOF文件的巨大体积,可能会让进程如同假死一样,十分的慢。

Redis集群

复制

数据存储在一台服务器上,如果这台服务器出现硬盘故障等问题,也会导致数据丢失,为了避免单点故障通常的做法是将数据库复制多个副本以部署在不同的服务器上。

复制(replication)可以实现当一台数据库中的数据更新后,自动将更新的数据同步到其他数据库上。
Redis原理浅析(二):消息通知、持久化、集群、redis遇到的问题及解决方法

哨兵

哨兵:一个独立的进程
作用:监控redis系统的运行状况

功能:

(1)监控主数据库和从数据库是否正常运行;
(2)主数据库出现故障时自动将从数据库转换为主数据库。

哨兵原理:

  1. 每10秒向所有主从数据库发送INFO命令来获取信息更新并进行相应操作
    Redis原理浅析(二):消息通知、持久化、集群、redis遇到的问题及解决方法
  2. 每2秒向所有主从数据库的_sentinel_:hello频道发送自己的信息。
    Redis原理浅析(二):消息通知、持久化、集群、redis遇到的问题及解决方法
  3. 每1秒向所有主从数据库和其他哨兵发送PING命令
    Redis原理浅析(二):消息通知、持久化、集群、redis遇到的问题及解决方法
    故障恢复:
    领头哨兵将从停止服务的主数据库的从数据库中挑选一个当新的主数据库:

①选出一个从数据库后,领头哨兵将向其发送SLAVEOF NO ONE命令使其升格为主数据库。
②而后领头哨兵向其他从数据库发送SLAVEOF命令,使其成为新主数据库的从数据库。
③最后更新内部的记录,将已经停止服务的旧的主数据库更新为新的主数据库的从数据库,使得当其恢复服务时自动以从数据库的身份继续服务。

集群

即使使用哨兵,集群的每个数据库依然存有集群中的所有数据,从而导致集群的总数据量受限于可用受限内存最小的数据库节点,形成木桶效应。

集群(cluster):拥有和单机实例同样的性能,同时在网络分区后能够提供一定的可访问性以及对主数据库故障恢复的支持。对于涉及多键的命令(如MGET),如果每个键都位于同一个节点中,则可以正常支持,否则会提示错误。

(1)配置集群:

①配置主数据库和从数据库(至少需要3个主数据库才能正常运行)
②为每个主数据库分配插槽。

*Redis将每个键的键名的有效部分使用CRC16算法计算出散列值,对16384(共16384个插槽)取余,然后将其分配到相应的插槽中,最后将插槽分配到对应的节点中(主数据库)。

(2)增加节点:
Redis原理浅析(二):消息通知、持久化、集群、redis遇到的问题及解决方法
(3)命令处理流程:
Redis原理浅析(二):消息通知、持久化、集群、redis遇到的问题及解决方法
(4)故障恢复:
Redis原理浅析(二):消息通知、持久化、集群、redis遇到的问题及解决方法

redis遇到的问题及解决方法

缓存和数据库双写一致性问题

分析:一致性问题是分布式常见问题,还可以再分为最终一致性和强一致性。数据库和缓存双写,就必然会存在不一致的问题。答这个问题,先明白一个前提。就是如果对数据有强一致性要求,不能放缓存。我们所做的一切,只能保证最终一致性。另外,我们所做的方案其实从根本上来说,只能说降低不一致发生的概率,无法完全避免。

回答:首先,采取正确更新策略,先更新数据库,再删缓存。其次,因为可能存在删除缓存失败的问题,提供一个补偿措施即可,例如利用消息队列。

缓存穿透

即黑客故意去请求缓存中不存在的数据,导致所有的请求都发到数据库上,从而数据库连接异常。

解决方案:
(一)利用互斥锁,缓存失效的时候,先去获得锁,得到锁了,再去请求数据库。没得到锁,则休眠一段时间重试
(二)采用异步更新策略,无论key是否取到值,都直接返回。value值中维护一个缓存失效时间,缓存如果过期,异步起一个线程去读数据库,更新缓存。需要做缓存预热(项目启动前,先加载缓存)操作。
(三)提供一个能迅速判断请求是否有效的拦截机制,比如,利用布隆过滤器,内部维护一系列合法有效的key。迅速判断出,请求所携带的Key是否合法有效。如果不合法,则直接返回。

缓存雪崩

即缓存同一时间大面积的失效,这个时候又来了一波请求,结果请求都怼到数据库上,从而导致数据库连接异常。

解决方案:
(一)给缓存的失效时间,加上一个随机值,避免集体失效。
(二)使用互斥锁,但是该方案吞吐量明显下降了。
(三)双缓存。我们有两个缓存,缓存A和缓存B。缓存A的失效时间为20分钟,缓存B不设失效时间。自己做缓存预热操作。然后细分以下几个小点

I 从缓存A读数据库,有则直接返回
II A没有数据,直接从B读数据,直接返回,并且异步启动一个更新线程。
III 更新线程同时更新缓存A和缓存B。