Redis分布式锁&消息队列

  2019-8-28 


分布式锁&异步消息队列

分布式锁

setnx(set if not exists)setnx name name1是获取锁,如果获取失败返回0,同时为了防止程序异常导致无法释放锁(del name不执行),那么我们要【设置一个锁持有期限】expire name 5,这里就有一个问题了(一般这个key是一个专门的锁变量,获取了才可访问接下来的内容):如果设置期限语句无法执行怎么办?(比如expire的时候宕机)

无法用事务解决获取、设置期限的原子性问题,因为事务无法做到if-else判断(if拿到锁,然后设置期限;这里没有拿到锁的话,setnx还是相当于执行了,那就不能设置期限!),要么都执行,要么都不执行

后来redis提供了扩展setnx方法,在参数中可以设置,【使得 setnx 和 expire 指令可以一起执行,变成了一个原子指令】,彻底解决了分布式锁的问题

分布式锁是一种悲观锁,还可以使用watch这种乐观锁,watch必须在mutl之前设置,它观察设置的变量是否改变,若改变了就返回null表示执行失败

异步消息队列

Redis 的 list(列表) 数据结构常用来作为异步消息队列使用(避免使用比如Kafka这种复杂的专业消息队列中间件)

  1. 传统生产者消费者问题+sleep避免空轮询(延时队列)

    使用lpop,rpop,lpush,rpush进行操作。

    但是问题在于这样会造成空轮询(比如队列空,pop没有,返回null,那就一直pop一直pop,CPU耗费高)

    于是我们使用延时队列,即一次查不到就sleep一段时间(sleep即延时),但这样一来如果消息多了就会延时很长

    延时队列可以通过 Redis 的 zset(有序列表) 来实现,value记录消息,score记录延时

    多线程环境下,当线程抢到消息之后,使用zset的zerm移除队列中的其它成员

    像前面如果分布式锁获取失败,就可以将请求扔进异步消息队列(延时队列)中

  2. 阻塞读/写

    可以使用blpop、brpop来阻塞读,blpop、brpop阻塞读在队列没有数据的时候会进入休眠状态,一旦数据到来,则立刻醒过来。这样就避免在队列为空的时候消费者的空轮询(阻塞太久会断开连接)做到了零延时但这样就只能让一个消费者消费了,要多个消费者消费得用pub/sub模式

pub/sub:主题订阅模式

Redis消息队列不支持消息的多播机制:消息多播允许生产者生产一次消息,中间件负责将消息复制到【多个消息队列】,每个消息队列由相应的消费者组进行消费

订阅发布模式定义了一种一对多的依赖关系,让多个订阅者对象同时监听某一个主题对象。这个主题对象在自身状态变化时,会通知所有订阅者对象,使它们能够自动更新自己的状态。

消费者可订阅多个主题(消费队列),生产者向主题发布

缺点:此模式下发布的消息无法保证可达性,即如果某消费者客户端宕机了,生产者服务端会继续发消息,然后这个消费者客户端就收不到该消息了(没有消费者,该消息被直接丢弃)

Stream (redis5.0新发布)

支持消息多播,支持持久化消息队列

持久化机制和普通数据结构一样:RDB+AOF

Stream采用一个消息链表,所有消息都串在链表上,且都有一个唯一的ID;在这个Stream消息链表上可以挂多个消费组,在链表上往前移动,表示该消费组已经消费到哪条消息了。每个消费者组都是独立的,也就是说Stream上的消息会被所有消费者组消费到,但是消费者组内部的消费者是竞争关系

消费者会记录已经被读取了的信息

创建Stream的时候可以定义Stream链表最大长度以删除老消息

注意

别把Redis的非阻塞单线程IO和异步消息队列搞混了。

单线程指的是网络请求模块使用了一个线程,即一个线程处理所有网络请求,借由此点,依然可以做到请求处理的高并发,只是其IO处理底层是单线程程序

而消息队列是Redis里队列数据结构的一种应用,是可以支持多个线程(消费者,生产者)访问的。


且听风吟