当前位置:首页 > 问答 > 正文

用Ruby配Redis搞消息通知,简单又实用的入门分享

(引用来源:基于常见的Ruby与Redis应用场景及官方文档基础概念整合)

今天聊个实在的,就是用Ruby和Redis弄个消息通知功能,别想得太复杂,不是什么高深的消息队列,就是比如用户A干了件事,咱们能立刻通知到用户B这种简单需求,用Redis来做,主要是因为它快,而且数据结构简单,搞这种活儿特别顺手。

咱们先得把家伙事儿准备好,你电脑上得安装并运行着Redis,这个去Redis官网下载安装就行,安装后用redis-server命令就能启动,在你的Ruby项目里,需要加上redis这个gem,在Gemfile里写一句gem 'redis',然后跑一下bundle install就行了。

核心思想特别简单,就是把Redis的列表(List)当成一个管道,想象一下,我们有好多管道,每个管道对应一个用户,当有人想给这个用户发通知时,就把通知内容像塞小纸条一样,从管道的左边塞进去,而用户那边呢,就时不时地从管道的右边把纸条取出来看,这个“管道”,在Redis里就是一个列表数据结构,用LPUSH命令(从左边塞入)和RPOP命令(从右边取出)来操作。

用Ruby配Redis搞消息通知,简单又实用的入门分享

先来看看发送通知的Ruby代码怎么写,假设我们有一个通知中心类:

(引用来源:Redis官方文档对LPUSH命令的说明)

require 'redis'
class NotificationService
  def initialize
    @redis = Redis.new # 默认连接本地的6379端口
  end
  def send_notification(to_user_id, message)
    # 关键的一步:把消息塞进属于这个用户的列表里。
    # 我们给每个用户的列表起个名字,"notifications:user_1"
    list_key = "notifications:user_#{to_user_id}"
    # LPUSH 的意思是从列表左边插入新消息
    @redis.lpush(list_key, message)
    puts "消息已发送给用户 #{to_user_id}: #{message}"
  end
end

你看,发送通知就这么简单两三行代码。@redis.lpush(key, value)是核心,它把消息内容(message)存到了以用户ID命名的那个Redis列表的最前面。

用Ruby配Redis搞消息通知,简单又实用的入门分享

发出去的消息得有人收啊,接收通知的一方,比如我们有一个后台任务或者一个API接口,专门帮用户查有没有新通知,我们来写个接收的方法:

(引用来源:Redis官方文档对RPOP命令的说明)

class NotificationService
  # ... 上面的初始化代码和发送代码 ...
  def get_notifications(user_id)
    list_key = "notifications:user_#{user_id}"
    notifications = []
    # 这里我们一次把当前列表里所有的消息都取出来。
    # 因为RPOP每次只取一个,所以我们用循环,直到取不到为止(即列表为空)。
    while message = @redis.rpop(list_key)
      notifications << message
    end
    if notifications.empty?
      puts "用户 #{user_id} 没有新通知。"
    else
      puts "用户 #{user_id} 的新通知有:"
      notifications.each { |msg| puts " - #{msg}" }
    end
    return notifications
  end
end

这个get_notifications方法也很直白,它用一个循环,不断地用@redis.rpop(list_key)从列表的右边取出消息,直到什么都取不出来(rpop返回nil)为止,这样就能一次性拿到所有积压的通知,取出的同时,RPOP命令会把消息从列表中删除,这样就避免了重复通知。

用Ruby配Redis搞消息通知,简单又实用的入门分享

我们来模拟一下整个流程,写段代码跑跑看:

# 新建一个通知服务
notifier = NotificationService.new
# 用户1给用户2发一条通知
notifier.send_notification(2, "你好!你的订单已经发货了。")
# 用户3也给用户2发一条通知
notifier.send_notification(2, "系统提醒:您的会员即将到期。")
# 让用户2来查看他的通知
user2_notifications = notifier.get_notifications(2)
# 控制台会打印:
# 用户 2 的新通知有:
#  - 系统提醒:您的会员即将到期。
#  - 你好!你的订单已经发货了。

你会发现,后发出的通知“会员到期”反而显示在前面了,这是因为我们是用LPUSH从左边插入的,类似于栈的结构,后进先出,如果你希望最早的通知显示在前面,可以在取出通知后.reverse一下数组,或者考虑使用RPUSHLPOP的组合,这取决于你的业务需求。

这种模式简单好用,但它有个小缺点:接收通知的一方需要主动来“拉取”消息,如果想让消息能“推送”到客户端(比如网页浏览器),就需要更复杂的技术,比如WebSocket,但就服务器内部的服务之间通信,或者处理一些不需要极速响应的后台通知,这个模式已经非常够用了。

再提一个实用的小改进,如果担心消息堆积太多,可以给列表设置一个长度限制,只保留最新的N条消息,Redis提供了一个LTRIM命令,可以在每次插入新消息后执行一下,比如_redis.ltrim(list_key, 0, 99),这样就只保留最新的100条消息,防止Redis内存被撑爆。

用Ruby配Redis搞消息通知,核心就是利用Redis的List充当临时管道,用LPUSH发消息,用RPOP收消息,代码简单明了,性能也高,对于很多入门和中等规模的应用来说,是一个非常实际和有效的解决方案,你先把这个基础打牢,以后万一业务变复杂了,再去看那些专业的消息队列系统,也会更容易理解。