场景(可以略过不看,只是引入的场景)
邮件订阅的,在博客首页放入一个文本框供访客输入自己的邮箱地址,提交后博客会将该地址存入redis的一个集合类型的键中(使用集合类型是为了保证同一邮箱地址不会存储多个),每当发布新文章时,就向收集到的邮箱地址发送通知邮件。
问题:输入邮箱地址提交后,页面需要很久时间才能载入完。
原因:原来小白为了保证用户没有输入他人的邮箱,在提交之后程序会向用户输入的邮箱发送一封包含确认信息的邮件,只有用户单击这个连接后对应的邮箱地址才会被记录。可是由于发送邮件需要连接到一个远程的邮件发送服务器,至少需要2秒。
结果就是,每次用户提交邮箱后页面都需要等待程序发送邮件才能加载出来,加载出来的页面只是提示用户确认邮箱地址。完全可以等页面加载出来,再发送邮件,这样用户就不需要等待了。
任务队列
网站开发中,当页面需要进行如发送邮件、复杂数据运算等耗时较长的操作时会阻塞页面的渲染。为了避免用户等待太久,应该使用独立的线程来完成这类操作。就上面发邮件的例子来说,设想有一个进程能够完成发送邮件的功能,那么在页面中只需要想办法通知这个进程向指定的地址发送邮件就可以了。
通知的过程可以借助任务队列来实现。任务队列,就是传递任务的队列。与任务队列进行交互的实体有两类,一类是生产者,一类是消费者。生产者将任务放到任务队列中,消费者则不断从任务队列中读取任务并执行。
就上面邮件的例子来说,页面将收件地址、邮件主题和邮件正文组装成任务后存入任务队列中。同时发邮件的进程不断检查任务队列并执行。
优点:
1> 松耦合。生产者和消费者无需知道彼此的实现细节,只要约定好任务的描述格式,使得生产者和消费者可以有不同的编程语言编写。
2> 易于扩展。消费者可以有多个,而且可以分布在不同的服务器中。可以降低单台服务器的负载。
如何使用redis实现任务队列
前面有介绍过redis的列表类型可以实现队列,lpush命令加入任务,rpop命令取出任务。每次去取任务时候需要检查
是否有任务,取出任务实例等待1秒,在去消费(避免频繁消费任务)
优化
借助brpop命令,实现一旦有新任务加入任务队列就通知消费者。BRPOP和RPOP命令的,唯一区别是当列表中没有元素时候,
BROP命令会阻塞住连接,直到有新元素加入。
loop
#如果任务队列中没有新任务,BRPOP命令会一直阻塞,不会执行execute()
$task = BRPOP queue,0
#返回值是一个数组,数组第二个元素是我们需要的任务
execute($task[1])
BRPOP命令接受两个参数,第一个是键名,第二个是超时时间,单位是秒。当超过了时间仍然没有获得新元素的话就会返回nil
,超时时间是0,表示不限制等待时间,永远阻塞下去。
优先级队列
BRPOP命令可以同时接收多个键,其完整的命令格式为BLPOP key[key...] timeout, 如BLPOP queue:1 queue:2 0 意义是同时检测多个键,如果所有键都没有元素则阻塞,如果其中一个键有元素则会从该键中弹出元素。
并且存在多个键都有元素的情况,则按照从左到右的顺序取第一个键中的一个元素。这样就实现了优先队列
发布/订阅模式
发布/订阅模式也能实现进程间的消息传递,原理:
发布/订阅模式中包含两种角色,分别是发布者和订阅者。订阅者可以订阅一个或者若干个频道,而发布者可以向指定的频道发送消息,所有订阅此频道的订阅者都会受到消息。
发布:PUBLISH 用法是: PUBLISH channel message , 返回值表示收到这条消息的订阅者数量。
因为此时没有客户端订阅channel1.1 ,所以返回0,发出去的消息不会被持久化,也就是说当有客户端订阅channel1.1后只能收到后续发布到该频道的消息,之前发送的就收不到了。
订阅SUBSCRIBE,可以同时订阅多个频道,用法是SUBSCRIBE channel[channel ...]
SUBSCRIBE channel1.1 在执行subscribe命令后客户端会进入订阅状态,处在此状态下客户端不能使用除了
SUBSCRIBE,UNSUBSCRIBE,PSUBSCRIBE和PUNSUBSCRIBE这4个属于发布/订阅模式命令之外的命令,否则会报错
进入订阅状态后的客户端可能收到3中类型的回复,每种类型的回复都包含3个值,第一个值是消息的类型,根据消息类型的不同,第二、第三值的含义不同。消息类型的可能取值:
1> subscribe。表示订阅成功的反馈信息。第二个值订阅成功的频道名称。第三个值是当前客户端订阅频道数量
2> message。这种类型的回复是我们最关心的。表示接受到的消息。第二个值表示产生消息的频道名称。第三个值为消息内容
3> unsubscribe。表示成功取消订阅某个频道。第二值是对应的频道名称。第三个值是客户端订阅的频道数量。当此值为0时客户端会退出订阅状态,就能执行其他非发布/订阅命令。
UNSUBSCRIBE命令取消订阅的频道。UNSUBSCRIBE [channel ...],不指定频道则会取消订阅所有频道。
按照规则订阅
PSUBSCRIBE命令订阅指定的规则,支持glob风格通配符格式
PSUBSCRIBE channel.?*, 规则channel.?* 可以匹配channel1.1和channel1.10,但是不会匹配channel.。
如果有 redis客户端B,发布消息PUBLIST channel1.1 hi!
客户端C通过psubscribe订阅了channel,就会收到消息回复。
1> pmessage (表示通过PSUBSCRIBE命令订阅频道收到的)
2> channel1.?*(表示订阅使用的通配符)
3> channel1.1(表示实际收到消息的频道命令)
4> hi!(消息内容)
note :
1. 使用PSUBSCRIBE命令可以重复订阅一个频道,如果客户端执行了PSUBSCRIBE channel1.? channel1.?*,这时候向channel1.2发布消息后会收到两条消息,并且PUBLISH命令的返回值是2
同样的,如果有另一个客户端执行了subscribe channel1.10 和 psubscribe channel.?*的话,向channel1.19发送消息,改客户端也会收到两条消息(message和pmessage)
2. PUNSUBSCRIBE命令可以推定指定的规则,PUNSUBSCRIBE [pattern] ,无参数,则会退订所有规则
note: punsubscribe命令只能退订通过psubscribe命令订阅的规则,不影响subscribe订阅的频道,同样unsubscribe命令也不会影响psubscribe命令订阅的规则。容易出错的一点是使用punsubscribe命令退订某个规则是不会将其中的通配符展开,而是进行严格的字符串匹配。PUNSUBSCRIBE * 无法退订channel.* 规则,必须使用PUNSUBSCRIBE channel.*才能退订
管道
redis的底层通信协议对管道提供了支持,通过管道可以一次发送多个命令,并执行完后一次性将结果返回,当一组命令中每个命令都不会依赖之前命令的执行,就可借助管道,减少客户端和redis的通信次数
省空间
精简键名和键值
内部编码优化(redis会自动调整)