【问题标题】:Generate unique sequence number across threads跨线程生成唯一序列号
【发布时间】:2023-04-15 15:49:02
【问题描述】:

我需要从多个线程生成一个唯一的序列号。我在下面创建了一个简单的类,它似乎可以工作,但我不确定我是否可以依赖序列号是唯一的。

此外,如果数字超过 999999,我需要能够让数字回到 0。我预计它不会在很长一段时间内翻转,因为该方法每次调用的次数可能少于 100 次天。我知道系统会在有机会达到 999999 之前定期关闭进行维护。

GetSequenceNumber 方法将从 BizTalk 映射中的 xslt 转换调用,并且该方法可以同时调用多次(在同一个 BizTalk 主机实例中)。

在我的开发系统上,即使 BizTalk 同时多次调用该方法,它似乎也可以正常工作并生成不同的值。开发系统仅运行一个 BizTalk 主机实例。

但是,在生产系统上,有两台服务器。我是否认为这种方法不能保证跨服务器的唯一性,因为它们在不同的应用程序域中运行?

我不能使用 guid,因为序列号限制为 16 个字符。

public class HelperMethods
{
    private static int sequenceNumber = 0;

    public string GetSequenceNumber()
    {
        string result = null;

        int seqNo = Interlocked.Increment(ref sequenceNumber);

        result = string.Format("{0:MMddHHmmss}{1:000000}", DateTime.Now, seqNo);

        return result;
    }
}

我认为我可以使用服务器计算机名称并添加一些任意字符,这样即使一台服务器上生成的序列号与另一台服务器上生成的序列号相同,它仍然会有所不同,但我不是确定它会有多独特。像这样的:

    string seqNumber = (MachineName == "Blah" ? "A" : "B") + GetSequenceNumber();

有人对我如何创建唯一的序列号有任何建议吗?它不必是完全独特的,我只需要非常不可能发生碰撞。另外,如果以线程安全的方式达到 1000000,如何将数字重置回 0?

【问题讨论】:

  • 是否需要在1000000 处重置,还是您选择的任意高数字。例如,在int.MaxValue 处滚动是否也可以接受?负数呢?
  • 如果您确定永远无法到达int.MaxValue,请像现在一样增加计数器,但返回seqNo % 1000000
  • 这对你有用吗? Convert.ToBase64String(Guid.NewGuid().ToByteArray()).Substring(0, 16)
  • @Enigmativity 据我所知,简单地截断 GUID 会有效地破坏您通过使用 GUID 获得的任何非冲突“保证”。见here
  • 通常当您希望每条消息都有一个唯一编号时,您希望发送系统设置它。否则,在重试时,可能会为同一消息提供两个或多个数字。

标签: c# biztalk


【解决方案1】:

这应该可以很好地返回一个唯一的 16 个字符的字符串。它基于 Guid 是唯一的。由于 Guid 在使用 Convert.ToBase64String 时会转换为 24 个字符的字符串,因此它会使用 XOR 将字节折叠到自身之上,以确保唯一性分布在所需的 16 个字符中。

Guid gd = Guid.NewGuid();

byte[] ba = gd.ToByteArray();

ba = ba.Zip(ba.Reverse(), (b0, b1) => (byte)(b0 ^ b1)).ToArray();

string mostLikelyUnique16 = Convert.ToBase64String(ba).Substring(0, 16);

我得到类似8QBIi7JpCeHhCWmy的结果。

无法保证唯一性,但我认为它会相当不错,尤其是考虑到您的要求允许偶尔发生碰撞。

我做了一个简单的测试,生成了 100 万个值,没有任何碰撞。

【讨论】:

  • 这很酷,但如果您不打算使用一组有序的整数,为什么不直接使用 GUID?
  • @DanField - 因为 OP 对唯一标识符有 16 个字符的限制,并且当 uuencoded 时 Guid 变成 24 个字符。
  • 是的,如果他真的需要一个唯一的 16 个字符的字符串,这是可行的,但我的印象是他需要一个可以循环的数字(一个真正的序列)。我也觉得数字的字符长度不如它具有数值重要 - 但同样,如果 16 个字符的字符串有效,这是一个很好的解决方案。
【解决方案2】:

如果事先知道服务器的数量并且不是很大,那么您可以将最高有效数字分配给“服务器 ID”,该 ID 是在每个服务器的配置中设置的。因此,例如,在一组 10 台服务器中,其中一台只能使用 3000000-3999999 范围内的数字。对于一组 100 台服务器中的另一个实例,它的范围是 4200000-4299999。

主要是在配置中包含区分服务器的信息。

【讨论】:

  • 这不是一个坏主意,除了 BizTalk 将在他无法控制的单独 AppDomain 中运行他的地图,服务器可能会在没有通知的情况下将其拆除,破坏他用来跟踪的任何静态数据他的序列在哪里。
【解决方案3】:

你会遇到几个问题。

BizTalk 管道和编排在单独的 AppDomain 中运行,编排 AppDomain 有时会在编排的水合之间被拆除。即使目前这个地图只在编排xor管道中运行,在某些时候有人可能会将它放在另一个(或另一个主机实例)上,并且您的解决方案会蓬勃发展。当您开始在多服务器环境中运行任何东西时,请忘记它。

BizTalk 地图的底线是它们通常不应以任何使用外部资源的方式不确定。如果您只想生成一个 GUID(不确定),那没关系。如果您想跨多个映射生成序列,那不是一个好主意。您将不得不依赖一些外部系统,(它实际上应该在您运行的 BizTalk 应用程序域的外部才能真正正常工作 - 例如单例 WCF 服务或使用 SQL Server SEQUENCE),这通常是地图中的一个坏主意(尤其是要在多个系统上并行执行的地图)。

那你能做什么?

  1. 选择一种不同的方式来识别您的分批邮件。可能是批次 ID 和个人 ID - 也可能是 GUID(或者至少是从 GUID 派生出来的、仍然足够具有唯一性的东西)。
  2. 在批处理之前或在出路时对您的消息进行排序,通过有序传递端口单件编排(实际上是单件队列)。这有一些明显的缺点,但你可以用它来获得一点创意——首先映射你的整个批次以对其进行排序,然后再分批;或重新设计您的分批流程,以便在分批时在消息中构造一个序列;或者将所有内容放入带有 SEQUENCE 的 SQL (2012+) 表中,该表根据您的要求循环,然后按照您的序列将其拉回。
  3. 重新考虑您的要求。要求这个的终端系统是什么?您为什么认为需要在地图中进行操作?

【讨论】:

  • 返回一系列序列的网络服务怎么样?
  • 这只能解决部分问题 - 你仍然不知道单个地图将在哪个 AppDomain 上执行,如果你要这样做,你必须调用它无论如何在执行单个地图之前,这导致了在分批之前或重新分批阶段之后只进行排序的想法