使用PostgreSQL替代Redis实现队列、分布式锁和发布/订阅


两种常用架构:

  • 用于数据存储的 PostgreSQL
  • Redis 用于协调后台作业队列(以及一些有限的原子操作)

Redis 非常棒,但如果我告诉你这个堆栈最常见的用例实际上可以只使用 PostgreSQL 来实现呢?很有可能您使用 Redis 做的事情实际上也适用于 PostgreSQL。跳过Redis并节省依赖多个数据服务的运营成本和开发复杂性可能是一个值得的权衡。(banq注:扩展性从小到大:本文方式<Redis<kafka,复杂性是倒过来的)
 
用例 1:作业队列
也许我见过的 Redis 最常见的用途是协调从您的 Web 服务到后台工作人员池的作业分派。这个概念是,您希望记录执行某些后台工作的愿望(可能带有一些输入数据),并确保您的众多后台工作人员中只有一个会监听它。Redis 对此有所帮助,因为它为其数据结构提供了一组丰富的原子操作。
但是自从 9.5 版引入后,PostgreSQL 就有了语句的SKIP LOCKED选项SELECT ... FOR ...(这里是文档)。当指定这个选项时,PostgreSQL 将忽略任何需要等待被释放锁的行。
BEGIN;

WITH job AS (
  SELECT
    id
  FROM
    jobs
  WHERE
    status = 'pending'
  LIMIT 1
  FOR UPDATE SKIP LOCKED
)
UPDATE
  jobs
SET
  status = 'running'
WHERE
  jobs.id = job.id
RETURNING
  jobs.*;

COMMIT;

通过指定FOR UPDATE SKIP LOCKED,将为从 返回的任何行隐式获取行级锁SELECT。此外,因为您指定了SKIP LOCKED,此语句不可能阻塞另一个事务。如果有另一个作业准备处理,它将被返回。由于行级锁定,运行此命令的多个工作人员无需担心接收同一行。
这种技术的最大警告是,如果您有大量工作人员试图退出此队列,并且有大量工作为他们提供服务,他们可能会花一些时间单步执行工作并尝试获取锁。在实践中,我开发的大多数应用程序的后台工作人员都不到十几个,而且成本可能不会很高。
 
用例 2:应用程序锁
假设您有一个与第三方服务的同步例程,并且您只想为所有服务器进程中的任何给定用户运行它的一个实例。这是我见过的 Redis 的另一个常见应用:分布式锁定。
PostgreSQL 也可以使用它的Advisory锁来实现这一点。Advisory锁允许您利用 PostgreSQL 在内部使用的相同锁定引擎来实现您自己的应用程序定义的目的。
 
用例 3:发布/订阅
我把最酷的例子留到最后:将事件推送到您的活跃客户。例如,假设您需要通知用户他们有新消息可供阅读。或者,您可能希望在数据可用时将数据流式传输到客户端。通常,Web 套接字是这些事件的传输层,而 Redis 作为 Pub/Sub 引擎。
但是,从版本 9 开始,PostgreSQL 也通过LISTENandNOTIFY语句提供了此功能。任何 PostgreSQL 客户端都可以订阅 ( LISTEN) 到一个特定的消息通道,它只是一个任意字符串。当任何其他客户端NOTIFY在该频道上发送消息 ( ) 时,所有其他订阅的客户端都会收到通知。或者,可以附加一条小消息。
如果您碰巧使用 Rails 和 ActionCable,那么使用 PostgreSQL 甚至是开箱即用的。