处理网络协议设计中的竞争条件/并发

时间:2016-07-14 17:55:37

标签: networking concurrency protocols race-condition

我正在寻找可以优雅地处理网络协议设计中的竞争条件的技术。我发现在某些情况下,同步两个节点以进入特定的协议状态特别困难。这是一个有这种问题的示例协议。

让我们说A和B处于ESTABLISHED状态并交换数据。 A或B发送的所有消息都使用单调递增的序列号,这样A可以知道B发送的消息的顺序,并且A可以知道B发送的消息的顺序。在这种状态下的任何时候,A都是或者B可以向另一个发送ACTION_1消息,以便进入一个需要严格顺序交换消息的不同状态:

发送ACTION_1 recv ACTION_2 发送ACTION_3

但是,A和B可能同时发送ACTION_1消息,导致他们两个都收到ACTION_1消息,而他们希望在发送ACTION_1时收到ACTION_2消息。

以下是一些可行的方法:

1)将ACTION_1发送到ACTION_1_SENT后更改状态。如果我们在此状态下收到ACTION_1,我们会检测竞争条件,然后继续仲裁谁开始序列。但是,我不知道如何公平地对此进行仲裁。由于两端很可能几乎同时检测到竞争状态,因此随后的任何动作都会出现其他类似的竞争条件,例如再次发送ACTION_1。

2)复制整个消息序列。如果我们在ACTION_1_SENT状态下收到ACTION_1,我们会在ACTION_2消息中包含其他ACTION_1消息的数据等。这只有在无需确定谁是"所有者时才会起作用。这个动作,因为两端最终会相互采取相同的行动。

3)使用绝对时间戳,但是,准确的时间同步根本不是一件容易的事。

4)使用lamport clock,但据我所知,这些仅适用于与因果关系相关的事件。由于在这种情况下ACTION_1消息与因果关系不相关,因此我不知道它如何帮助解决首先发现哪一个发生丢弃第二个消息的问题。

5)使用一些预定义的方法在两端收到时丢弃两条消息中的一条。但是,我无法找到一种无瑕疵的方法。一个天真的想法是在两边都包含一个随机数,并选择编号最高的消息作为"胜利者",丢弃编号最小的一个。但是,如果两个数字相等,我们就会有一个平局,然后我们需要另一种方法来从中恢复。可能的改进是在连接时处理仲裁一次并重复类似的顺序,直到两个中的一个赢得#34;将其标记为最喜欢的。每当领带发生时,最喜欢的胜利。

有没有人对如何处理这个问题有进一步的想法?

编辑:

这是我提出的当前解决方案。由于我无法找到100%安全的方法来防止关系,我决定让我的协议选出一个最喜欢的"在连接序列期间。选择这个最喜欢的东西需要打破可能的联系,但在这种情况下,协议将允许多次尝试选择喜欢,直到达成共识。在选出最爱之后,所有进一步的联系都通过支持当选的最爱来解决。这隔离了可能与协议的单个部分相关的问题。

至于选举过程的公平性,我根据每个客户端/服务器数据包中发送的两个值写了一些相当简单的东西。在这种情况下,这个数字是一个以随机值开始的序列号,但它们可以是任何东西,只要这些数字是相当随机的公平。

当客户端和服务器必须解决冲突时,它们都使用send(它们的值)和recv(其他值)值来调用此函数。收藏夹调用此函数,并将收藏夹参数设置为TRUE。保证此功能在两端都给出相反的结果,这样就可以在不重传新消息的情况下打破平局。

BOOL ResolveConflict(BOOL favorite, UINT32 sendVal, UINT32 recvVal)
{
    BOOL winner;
    int sendDiff;
    int recvDiff;
    UINT32 xorVal;

    xorVal = sendVal ^ recvVal;

    sendDiff = (xorVal < sendVal) ? sendVal - xorVal : xorVal - sendVal;
    recvDiff = (xorVal < recvVal) ? recvVal - xorVal : xorVal - recvVal;

    if (sendDiff != recvDiff)
        winner = (sendDiff < recvDiff) ? TRUE : FALSE; /* closest value to xorVal wins */
    else
        winner = favorite; /* break tie, make favorite win */

    return winner;
}

让我们说两端在发送ACTION_1消息后进入ACTION_1_SENT状态。两者都将在ACTION_1_SENT状态下收到ACTION_1消息,但只有一个会赢。失败者接受ACTION_1消息并进入ACTION_1_RCVD状态,而获胜者丢弃传入的ACTION_1消息。序列的其余部分继续,好像失败者从未在胜利者的竞争条件下发送ACTION_1。

让我知道您的想法,以及如何进一步改进。

2 个答案:

答案 0 :(得分:2)

对我来说,这个ACTION_1 - ACTION_2 - ACTION_3握手必须按顺序发生而没有其他信息介入的整个想法是非常繁重的,并且根本不符合网络(或一般的分布式系统)的现实。您提出的一些解决方案的复杂性使我们有理由退后一步,重新思考。

在处理通过网络分布的系统时,存在各种复杂因素:不会到达,迟到,无序到达,到达重复,不同步的时钟,向后的​​时钟的数据包有时,崩溃/重启的节点等等。您希望您的协议在任何这些不利条件下都是健壮的,并且您希望知道确定它是健壮的。这意味着使其足够简单,您可以考虑可能发生的所有可能情况。

这也意味着放弃了总会有一个真正的状态&#34;由所有节点共享,以及您可以在一个非常受控制的,精确的&#34;发条中使事情发生的想法。序列。您希望针对节点在其共享状态上达成一致的情况进行设计,并使系统在该条件下自我修复。您还必须假设任何可能的消息可能以任何顺序发生。

在这种情况下,问题在于声称所有权&#34;共享剪贴板这是您需要首先考虑的基本问题:

  1. 如果所涉及的所有节点都无法在某个时间点进行通信,那么尝试拥有所有权的节点是否应该继续并且表现得好像它是所有者? (这意味着系统在网络关闭时不会冻结,但这意味着您 有多个&#34;所有者&#34;有时,将 strong>对剪贴板进行不同的更改,必须合并或以其他方式修复#34;稍后。)
  2. 或者,除非从所有其他节点收到确认,否则没有节点会假设它是所有者? (这意味着系统有时会冻结,或者只是响应非常缓慢,但是你不会有奇怪的情况,会有不同的变化。)
  3. 如果您的回答是#1:请不要过分关注声明所有权的协议。想出一些简单的东西,可以减少两个节点成为&#34;所有者&#34;同时,但要明确可以多个所有者。当解决分歧发生时,将更多的精力放在解决分歧的过程中。通过额外仔细考虑这一部分,并确保多个所有者将始终融合。应该没有任何情况下,他们可能陷入无限循环试图收敛但失败。

    如果你的答案是#2:这里是龙!你正试图做一些与某些基本限制有关的事情。

    非常明确地指出一个节点是&#34;寻求所有权&#34;但尚未获得它的状态。

    当节点正在寻求所有权时,我会说它应该每隔一段时间向所有其他节点发送请求(如果另一个节点错过了第一个请求)。在每个此类请求上放置一个唯一标识符,该请求在回复中重复(因此延迟回复不会被误解为应用于稍后发送的请求)。

    要成为所有者,节点应在特定时间段内收到来自所有其他节点的肯定回复。在该等待期间,它应该拒绝向任何其他节点授予所有权。另一方面,如果某个节点同意将所有权授予另一个节点,则它不应该在另一个时间段内请求所有权(必须稍长一些)。

    如果某个节点认为它是所有者,则应通知其他节点,并定期重复通知。

    您需要处理两个节点同时尝试寻求所有权以及NAK(拒绝所有权)的情况。你必须避免出现超时,重试,然后再次互相攻击的情况(意味着没有人会获得所有权)。

    您可以使用指数退避,或者您可以制定一个简单的打破平局规则(它不必公平,因为应该很少发生)。为每个节点赋予优先级(您必须弄清楚如何推导优先级),并说如果寻求所有权的节点收到来自更高优先级节点的所有权请求,它将立即停止寻求所有权并授予它而是改为高优先级节点。

    这不会导致多个节点成为所有者,因为如果高优先级节点先前已经确认了低优先级节点发送的请求,那么它将不会发送自己的请求,直到有足够的时间过去它确定以前的ACK不再有效。

    你还必须考虑如果节点成为所有者会发生什么,然后&#34;变暗&#34; - 停止响应。在什么时候允许其他节点假设所有权是#34; up for grabs&#34;再次?这是一个非常棘手的问题,我怀疑你找不到任何可以同时拥有多个所有者的解决方案。

    可能所有节点都需要&#34; ping&#34;彼此不时。 (不是指ICMP回声,而是内置于您自己的协议中的内容。)如果剪贴板所有者无法在一段时间内与其他人联系,则必须假定它不再是所有者。如果其他人无法在更长的时间内与所有者联系,他们可以认为所有权可用且可以请求。

答案 1 :(得分:0)

以下是此处感兴趣的协议的简化答案。

在这种情况下,只有客户端和服务器通过TCP进行通信。该协议的目标是两个系统剪贴板。在特定序列之外的常规状态只是&#34; CLIPBOARD_ESTABLISHED&#34;。

每当两个系统中的一个系统将某些内容粘贴到其剪贴板上时,它就会发送一个ClipboardFormatListReq消息,并转换为CLIPBOARD_FORMAT_LIST_REQ_SENT状态。此消息包含在发送ClipboardFormatListReq消息时递增的序列号。在正常情况下,不会发生竞争条件,并且会发回ClipboardFormatListRsp消息以确认新的序列号和所有者。请求中包含的列表用于公开所有者提供的剪贴板数据格式,远程系统上的应用程序可以请求任何这些格式。

当应用程序从剪贴板所有者请求其中一种数据格式时,将发送带有序列号的ClipboardFormatDataReq消息,并从列表中发送格式id,状态将更改为CLIPBOARD_FORMAT_DATA_REQ_SENT。在正常情况下,在此期间剪贴板所有权没有变化,并且数据在ClipboardFormatDataRsp消息中返回。如果没有从其他系统足够快地发送响应,则应使用计时器超时,如果时间过长,则中止序列。

现在,对于特殊情况:

如果我们在CLIPBOARD_FORMAT_LIST_REQ_SENT状态下收到ClipboardFormatListReq,则表示两个系统都试图同时获得所有权。应该只选择一个所有者,在这种情况下,我们可以保持简单,选择客户端作为默认赢家。将客户端作为默认所有者,服务器应使用ClipboardFormatListRsp响应客户端,将客户端视为新的所有者。

如果我们在CLIPBOARD_FORMAT_LIST_REQ_SENT状态下收到ClipboardFormatDataReq,则意味着我们刚刚收到了来自上一个数据格式列表的数据请求,因为我们刚刚发送了一个请求,成为新的所有者,并带有新的数据格式列表。我们可以立即回复失败,序列号也不匹配。

等等。我试图解决的主要问题是从这些状态快速恢复,进入重试循环直到它工作。立即重审的主要问题是它会在可能导致新的竞争条件的时机发生。我们可以通过期望这种不一致的状态来解决问题,只要我们在检测它们时能够回到正确的协议状态即可。问题的另一部分是选择一个赢家&#34;将接受其请求而不重新发送新消息。默认情况下可以选择默认赢家,例如客户端或服务器,或者某种随机投票系统可以使用默认收藏来实现打破关系。