如何在多个JVM节点上实现计数器的最简单/最快方法

5
考虑以下问题:
我有n个Tomcat节点,用于提供一些无状态内容的Web应用程序。例如,前1000个请求必须响应'a',接下来的10000个请求必须响应'b',其余请求必须响应'c'。
首先我考虑了消息传递:应用程序从某个存储中获取总服务计数 -> 如果小于n,则提供内容'a' -> 一旦内容被提供,应用程序发送一条消息 -> 消息被消费 -> 总服务计数在某些存储上增加 -> ...但在这种情况下,由于消息提供事件和存储上计数器增量之间存在轻微(或巨大的峰值负载时间)延迟,很可能会出现超调的情况。
然后我考虑设置memcached-session-manager以将计数器存储在共享会话中。但对于我的简单情况来说,这似乎过于繁重。
请问是否有任何直接的方法可以使多个JVM实例相互通信(适用于我的情况)?

一致性有多重要?如果你想在前10000个请求之后改变行为,但实际上是在第10010个请求之后行为才发生了变化,那会发生什么呢?你能容忍多少误差? - Christopher Schultz
3个回答

2
如果您需要确保正确性并且不想延迟,我认为 Redis 或者 Hazlecast 是您最好的选择。特别是 Redis,因为它具有原子计数操作。虽然理论上您也可以使用 memcache 来实现相同的功能,但 Redis 更适合此用例(统计计数器)。
您还可以使用内存数据库,例如 H2,或者将 Postgres 表设置为 unlogged 或适用于您的 RDBMS 的其他选项,以保留一个伪内存表。关于 RDBMS 的烦人之处在于,并非所有 RDBMS 都支持一致的 upserting,也就是 MERGE

1
首先,您可以在Tomcat实例之间共享会话。接收请求的Tomcat服务器基本上会将其会话复制到所有其他Tomcat服务中。
我不禁想到您有一些未表达的需求驱动此请求,但希望仅询问如何实现而不是如何满足需求。在这种情况下,需求没有得到满足,但请求通常是满足的。
例如,与其担心对一个服务器的1000个请求,然后进行轮换,不如简单地将多个IP地址配置为DNS主机名,以循环方式分发请求。
您还可以将会话与数据库协调。数据库提供良好的存储能力和读取一致性。通过正确的配置,处理节点可以简单地读取“下一个数字”。
最后,还有其他手段,利用分布式计算。例如,请求可以由内部请求中继处理,该中继启动类似Paxos的协议,以保证所有处理节点都具有新的“下一个”号码。
所有这些技术都是直接的。然而,您很快就会因为它们对您来说似乎不太简单而将它们轻易地拒之门外。好吧,也许您正在寻找更简单的替代方案,这并没有什么坏处;但是,让两台或更多计算机在同一时间一致、可靠地达成某个共识比我们想象的要棘手一些。请随意在这个领域发起新的努力,但也许您只会发现存在额外开销和复杂性的真正原因。这不是一个琐碎的问题。
---更新---
如果您可以以轮询方式处理请求,并放松在服务器之间排序的需求,并且知道您只有N个服务器,那么您可以实现N个不同的请求计数器。
- 服务器1通过N递增,确保count%N == 0 - 服务器2通过N递增,确保count%N == 1 - ... - 服务器N-1通过N递增,确保count%N = N-2 - 服务器N通过N递增,确保count%N = N-1 当然,在短会话中,跨服务器计数可能会失去全局顺序,但您可能会迅速获得所需的一部分:
  • 每个请求的唯一计数
  • 每个服务器基础上请求的排序
  • 保证在所有服务器上唯一的计数
  • 快速确定处理请求的服务器

您将缺少

  • 跨服务器请求的真正排序

会话不是与“无状态”不兼容吗? - Christopher Schultz
“有一个递增计数器的想法是否与“无状态”的想法不兼容?” 无状态操作可以证明对于相同的输入产生相同的输出,而不考虑之前的调用历史记录。除非您将状态处理逻辑远离Web处理逻辑,否则不可能拥有无状态计数器,那么您如何满足您的要求呢?远程项直到很久以后才能开始“计数”您的会话。” - Edwin Buck
@ChristopherSchultz 我更新了一下。这种技术并不是新的,但它依赖于服务器之间不需要真正的排序。它提供了一个非常简单的架构,可以满足你的部分需求;然而,它并不能提供你所需要的全部功能。如果解决方案的简单性变得比完全和绝对的排序更重要,那么它可能会有用。祝好运。 - Edwin Buck
@ChristopherSchultz 另一个想法是通过一个消息重写器来传递所有的消息,在将消息分发到服务器群之前附加消息编号。然而,这可能会复杂化回复路径。 - Edwin Buck

0

以下是按照设置所需的最小工作量排序的所有选项:

  1. 使用 memcached-session-manager 在不同的Tomcats之间存储会话
  2. 使用轻量级数据库,如sqlite,并在表/集合中存储计数器
  3. 使用共享文件系统,并在文本文件中存储计数器
  4. 使用像Redis、Memcahed、Ehcache或Hazelcast等轻量级缓存提供程序
  5. 使用JMS等消息传递,保持计数器传递

上述第三点在Java中实际上相当困难:如何获得文件的独占访问权?我认为,在这里使用JDBC是最直接(或不太高效)的策略之一,但您根本没有列出它。 - Christopher Schultz
我没有意识到 SQLite 是多进程的。谢谢。 - Christopher Schultz
我很惊讶他没有提到Redis (INCR)。如果他提到了Redis,我就会删除我的回答。 - Adam Gent
@AdamGent:编辑后的列表中包括Redis。 - anubhava

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接