客户端连接到服务器并调用方法。紧接着,客户端和服务器之间的连接中断并且客户端获得了一个
RemoteException
,但是被调用的方法继续工作并改变了一个文件,然后返回并获取异常(假设在写入套接字响应时会意识到连接丢失)。RemoteException
,但是被调用的方法继续工作并改变了一个文件,然后返回并获取异常(假设在写入套接字响应时会意识到连接丢失)。这在实践中非常复杂。您需要实现一个两阶段提交协议,其中客户端可以获得指示器,表明服务器可以保证提交,然后按顺序进行提交。
有一个现成的协议称为XA,用于管理此过程,Java Transaction API支持它。基本上,您可以为文件构建一个XA兼容资源管理器。
这不是一项微不足道的任务,但如果您不介意在基础设施中包含应用程序服务器或事务管理器,则不会超出可能性的范围。这种方法的优点是,您可以连接到成熟的、经过调试的事务管理基础设施。
一些替代方案可能是:
实现您自己的两阶段提交协议,或
结构化文件更新代码,使其具备幂等性(多次调用相同的更新只会执行一次该更新操作)。在这种情况下,您可以重试更新操作,直到客户端从服务器获得成功指示为止。
请注意,除非您能保证来自客户端的连续写入,否则您还必须想出一种锁定和/或管理相同记录上的冲突更新的协议。根据您的应用程序,这可能是一种乐观的并发协议,但在对其进行操作时仍需要锁定您正在操作的范围以使写入变为原子操作。
你正在处理分布式处理。这意味着你无法保证特定的消息(方法调用等)会到达另一端;连接总是可以任意断开。因此,你需要一种缓解策略,不依赖于任何特定的消息到达。
其中一种策略是使用可靠的消息系统,它基本上将数据库放置在保存消息队列的位置,然后按顺序可靠地传递这些消息(通过重复发送带有序列标识符的消息,直到收到确认),但这是一个很大的开销,应该仅用于关键事项(例如金融交易)。
另一种策略是使用分布式事务管理器,但这些存在相当大的问题(除非你实现了分布式共识系统,但这很复杂,仍然存在潜在的故障模式)。
我认为最简单的方法是重新组织被认为是决定性的内容。让客户端与服务器通信,以组装在服务器上要提交的瞬态操作的描述(包括唯一标识符,例如UUID),然后客户端可以发送一个启动提交的短消息;此时,服务器需要记录它已经开始处理该UUID。如果无法发送响应,则仍然在提交之后。如果任何消息丢失,那没关系:要么是在提交开始之前(在这种情况下,它没有发生,可以重试),要么是在提交之后,在这种情况下,将有一个永久记录表明已经尝试过,因此很容易报告发生了什么(“我还在处理中”,“我成功了”,“我失败了”)。客户端需要记住的唯一状态是UUID,而且可以在事务之外分配(这是UUID的一个美妙属性,虽然只是概率上的真实,但问题的几率确实微不足道)。
您可以在每个操作中从客户端传递一个顺序计数器参数到服务器。根据您的使用场景,您的服务器将需要记住最近发送的一个或多个计数器。如果看到已经发生过的值,则可以返回成功(或其他内容)。如果需要跨多个客户端保持唯一性,则可以使用类似UUID(由另一个答案提到)的东西。很多情况下这取决于返回值和调用序列的样子。
例如,我在rmiio项目中实现了这个想法来解决这个问题。在rmiio库中,您只需要处理“当前”操作可能重复的可能性,因此只需要跟踪最后一个计数器值。
如果您选择自己编写伪事务系统,以下是基本步骤:
在服务器端:
在客户端: