【问题标题】:How to make sure my event is handled by only one instance of my app?如何确保我的事件仅由我的应用程序的一个实例处理?
【发布时间】:2016-11-28 21:50:17
【问题描述】:

在我们的架构中,我们有一个 Redis 服务器,用于缓存和发布事件。

我的问题如下

  • 我有一条名为“CustomerUpdate”的消息
  • 我有 1 个应用程序正在监听此消息
  • 正在执行此应用程序的 3 个实例(服务器)以实现可扩展性
  • 1 个数据库实例正在运行
  • 此消息的处理程序之一将更新数据库
  • 其他一些处理程序将擦除内存缓存或对实例执行本地操作

是否有任何模式可以确保数据库不会被应用程序的每个实例更新?

【问题讨论】:

  • 你在redis中使用什么数据结构来实现这个?队列还是 pub sub?
  • pub sub,redis中没有队列(afaik)
  • 我很困惑我在哪里发布了上面的 cmets 粗鲁。但事实是实际答案的数量,而我的 cmets 都不是答案,这表明 redis 用于执行您描述的任务的普遍程度。祝你好运坚持糟糕的设计,即使这是我的粗鲁。
  • 请以“redis 不是你的好工具,你可以使用 XXX 并做 YYY”开头的答案。我不是想弄清楚我的设计决定是否好。我只是想知道制作某物的“模式”或“工具”。

标签: events redis queue channel


【解决方案1】:

您可以使用 redis 键/值作为阻止程序。当实例接收到来自订阅的消息时,在 redis 中执行 LUA 脚本以检查它是否已经存在进程。

服务器从订阅接收消息 使用 redis 脚本事务检查是否已经存在此消息的锁(类似于 get receiveMessageId:XXX)。如果 value 已经以 false 退出,则在服务器上不执行任何操作。如果该值不存在,则设置它并返回 true。然后你的服务器就可以处理这个消息了。

由于 Redis 是单线程的,因此如果消息被其他服务器接收,所有其他服务器都会得到 false。

要删除此密钥,您可以设置一个足够大的 TTL 以避免从其他服务器获取消息。

【讨论】:

  • 如果 Message id 不唯一怎么办?您最终将为 n 条类似消息处理 1 个事件。
  • @khanou 确实是个好主意,只要你不使用集群,但这里不是这样。但是,添加唯一消息 ID 的义务是一个更大的问题:redis 中的事件只是一条消息,因此您不能添加诸如此类的元数据(但您可以发布包含所有需要的信息的序列化“EventData” )。
【解决方案2】:

一个更简单的想法

1) 不要将您的事件发布到“CustomerUpdate”频道,而是将它们放入队列中,并使用唯一的通知器通知它的操作类型

LPUSH CustomerUpdate type1$somework

这里的type1可以是发送邮件、进入db等等,somework是你需要处理的工作。

2) 在您的应用程序逻辑中,使用阻塞 rpop。

BRPOP CustomerUpdate

在您的应用逻辑中拆分工作类型和工作。如果返回 type1 则执行该操作,如果返回 type2 则执行该操作,依此类推。然后进行这项工作。

示例 sn-p:

String message = jedis.brpop("CustomerUpdate",1000);
if(message.startsWith("type1$"))
sendMail(message.split("$")[1]);
else if(message.startsWith("type2$"))
sendAck(message.split("$")[1]);

优点:

  • 不需要键空间通知
  • 即使应用服务器宕机了 当它重新启动时,可以像在队列中一样工作,而在 pub/sub 您无法获取已发布的消息。
  • 即使redis宕机了几秒,也能获取到数据 已启用持久性
  • 简单的数据结构(只有一个队列)

【讨论】:

  • 有了这个解决方案,我每次有新类型时都必须更新我的发布者,不是吗?
  • 是的,你必须这样做。无论如何,您要在消费者端为它添加一个实现?在此期间,您也可以更新发布者。
  • 问题不在于编码工作。问题是耦合。我们使用事件来减少耦合(例如管理客户数据的模块不知道电子邮件模块),如果它现在知道所有其他模块,那么使用事件就没那么有趣了。
  • 只是想知道您将如何使用单个事件或发布/订阅频道来处理这个问题?订阅者将只有收到的消息字符串。你将如何识别它属于哪个模块?消费者方面应该有一些验证器来检测这种风险吗?如果我错了,请纠正我。
  • 您不必确定谁提出了业务事件,这就是使用事件的全部意义:发布者和订阅者彼此不了解,他们只知道它为您提供的消息代理在你的模块之间有一个非常松散的耦合。想要在客户更新时做某事的人只需要了解该事件并忽略发布此事件的所有模块(或其他订阅者)
【解决方案3】:

一种简单的方法,而不是在消息中发送事件数据,而是发送包含此类数据的列表的名称,然后消息的第一个接收者将在此类列表上执行LPOP,并且只有它将接收事件数据。

简而言之:

  • 您的客户像SUBSCRIBE CustomerUpdate 一样订阅。
  • 发布者执行RPUSH CustomerUpdateList <data>; PUBLISH CustomerUpdate CustomerUpdateList
  • 所有客户端都会收到MESSAGE CustomerUpdate CustomerUpdateList,但只有第一个执行LPOP CustomerUpdateList的客户端会收到消息<data>

但是,从您在服务器中执行LPOP 的那一刻起,该消息将被处理或永远丢失。例如,如果连接在LPOP 之后立即断开,则消息将丢失。

在 Redis 中实现可靠的消息传递很困难,因此您最好看看以下项目:https://github.com/resque/resquehttps://github.com/seomoz/qless

或者,如果您想自己动手,请查看此演示文稿,其中作者对他们遵循的方法进行了很好的解释:https://www.percona.com/news-and-events/percona-university-smart-data-raleigh/using-redis-reliable-work-queue

PS:虽然我的建议是为这类事情使用 RabbitMQ 之类的东西。

【讨论】:

    【解决方案4】:

    我会为 CustomerUpdate 的可操作任务设置一个或多个列表作为队列。相反(或同时)发布 CustomerUpdate,您会将LPUSH 放到列表中。每个列表元素的值都会对更新的参数进行编码。

    然后在每个需要竞争作业的处理程序中循环使用BRPOP。这是一个带有超时的阻塞弹出,专为这样的用例而设计。 http://redis.io/commands/brpop

    优点:没有键空间通知,许多作业处理程序可以在没有竞争的情况下弹出,可以将必要的任务分成单独的列表,BRPOP 一次可以有多个列表。

    缺点:需要更改发布 CustomerUpdate 的任何内容,并且可能执行多个 LPUSHs,可能需要 MULTI/EXEC 或类似内容。如果您无法更改此方面,则需要另一个进程(不同的客户端)来订阅 CustomerUpdates,并推送作业。

    【讨论】:

      猜你喜欢
      • 2014-11-05
      • 2021-12-21
      • 2011-07-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多