Java在多台服务器上的锁定机制

3
我有几个座位需要用户预定。每次只能有一个用户参与预订过程,以便同一座位不被多个用户预订。在我的Java代码中,我使用了“synchronized”关键字来完成这项任务。这有效。
然而,现在我的代码部署在两个服务器上,例如S1和S2。假设现在只剩下最后一个座位。两个用户,U1和U2,都想预订这个座位。负载均衡器可能会将用户U1发送到服务器S1,将用户U2发送到服务器S2。现在,“本地”的同步仍然可以正常工作,但它将无法阻止另一个用户在进行预订时发生冲突。这会导致最后一个座位被两个用户同时预订,这是一个问题。
因此问题是如何确保在多服务器环境中多个用户可以预订座位而不产生冲突?

你正在使用JavaEE和企业级JavaBean吗? - El Hocko
Java语言或库不提供多平台同步。您可以尝试为S1和S2使用的共享数据库进行配置,但这超出了Java的范畴。 - President James K. Polk
是的,我正在使用JavaEE,但不是企业级JavaBean。 - Raj
2个回答

3
在语言层面上很难实现。一些分布式缓存可能会有所帮助,例如Terracotta在集群环境中处理了synchronized关键字,但我没有尝试过,而且肯定会影响性能。如果实例需要等待一段时间,就要非常小心。
否则,可以通过分布式JNDI、分布式单例或类似的方法来实现服务器实例之间的通信和检查。
但是,在实践中,这个问题通常通过进行乐观锁定来避免。您可以简单地假设这种情况只会在一些不寻常/不太可能的时刻发生,并像在一个实例的情况下那样处理事情。并找出当它不发生时该怎么办的方法。这更多地成为业务逻辑场景,最好的方法是找到一种使用户最不可能感到不满意的方法。
通常,持久性/数据库级别保证了一致性,因此您可以将其写入其中。我肯定不会使用synchronized关键字来阻止可能持续几分钟或更长时间的实例,因为这会导致内存泄漏或任何其他资源耗尽。
我个人喜欢在使用JPA/Hibernate时使用L2缓存来处理这个问题。OpenJPA在仅通知其他实例实体已更改并且它们应该从其L1缓存中撤销它的情况下执行L2操作,这可能比使用synchronized Java语言关键字进行“同步”更好。
您考虑如何处理这个问题是很好的态度,根据我的经验,许多应用程序都忽略了它。它往往成为一个难以理解为什么需要投资的低概率场景,尤其对于业务管理人员来说。但如果在生产中出现问题,它具有严重的回击能力。

感谢Marian的帮助。您能否详细说明分布式JNDI?我想那可能是我感兴趣的。 - Raj
这是平台相关的,我知道在Weblogic中,您可以使用配置使不同实例相互了解。 - MarianP
我稍微搜索了一下,Apache Zookeeper 可能会在集群环境中为您提供类似的东西。 - MarianP
再次感谢Marian。我会探索一下,看看它是否能解决我的问题。 - Raj

2
基本上,分布式系统有四种通信方式:
  1. 共享数据库。
  2. 远程过程调用(RPC)。
  3. 消息传递。
  4. 共享文件系统。
考虑使用共享数据库方法。如果您不需要大规模扩展,最简单的方法是使用关系型数据库,因为几乎所有关系型数据库都提供了一定程度的ACID(原子性、一致性、隔离性和持久性)。你可以设置一个事务,在冲突的情况下回滚/处理 - 这被称为乐观锁定策略。
如果您使用Spring + JPA / Hibernate,所有繁重的工作都已经完成,您只需要在更新方法中添加@Transactional注解...有关Spring的一本好书是《Spring in Action》。
另一个选择是使用分布式缓存。

感谢Jasper的帮助。我正在使用Spring+Hibernate。我的数据库架构定义了REGISTRATION和EVENT表。我从EVENT表获取可用座位的总数(A),然后为事件从REGISTRATION表获取注册总数(B)。如果B小于A,那么我就在注册表中插入一条新记录,这样就完成了用户注册。因此,座位是否已满由代码决定。所以我不确定在这里放置@Transactional注释是否有帮助。让我知道你的看法。 - Raj
除非它会影响性能,否则尽量利用你的数据库。这将是最简单的方法。在分布式环境中,你仍然需要一些真相的指引... - Jasper Blues

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