.NET Core中反解ObjectId

在设计数据库的时候,我们通常需要给业务数据表分配主键,很多时候,为了省事,我都是直接使用 GUID/UUID 的方式,但是在 MonggoDB 中,其内部实现了 ObjectId(以下统称为Oid)。并且在.NETCore 的驱动中给出了源代码的实现。

经过仔细研读官方的源码后发现,其实现原理非常的简单易学,在最新的版本中,阉割了 UnPack 函数,可能是官方觉得解包是没什么太多的使用场景的,但是我们认为,对于数据溯源来说,解包的操作实在是非常有必要,特别是在目前的微服务大流行的背景下。

为此,在参考官方代码的基础上进行了部分改进,增加了一下自己的需求。本示例代码增加了解包的操作、对 string 的隐式转换、提供读取解包后数据的公开属性。

ObjectId 的数据结构

首先,我们来看 Oid 的数据结构的设计。

.NET Core中反解ObjectId

从上图可以看出,Oid 的数据结构主要由四个部分组成,分别是:Unix时间戳、机器名称、进程编号、自增编号。Oid 实际上是总长度为12个字节24的字符串,易记口诀为:4323,时间4字节,机器名3字节,进程编号2字节,自增编号3字节。

1、Unix时间戳:Unix时间戳以秒为记录单位,即从1970/1/1 00:00:00 开始到当前时间的总秒数。
2、机器名称:记录当前生产Oid的设备号
3、进程编号:当前运行Oid程序的编号
4、自增编号:在当前秒内,每次调用都将自动增长(已实现线程安全)

根据算法可知,当前一秒内产生的最大 id 数量为 2^24=16777216 条记录,所以无需过多担心 id 碰撞的问题。

实现思路

先来看一下代码实现后的类结构图。

.NET Core中反解ObjectId

通过上图可以发现,类图主要由两部分组成,ObjectId/ObjectIdFactory,在类 ObjectId 中,主要实现了生产、解包、计算、转换、公开数据结构等操作,而 ObjectIdFactory 只有一个功能,就是生产 Oid。

所以,我们知道,类 ObjectId 中的 NewId 实际是调用了 ObjectIdFactory 的 NewId 方法。

为了生产效率的问题,在 ObjectId 中声明了静态的 ObjectIdFactory 对象,有一些初始化的工作需要在程序启动的时候在 ObjectIdFactory 的构造函数内部完成,比如获取机器名称和进程编号,这些都是一次性的工作。

类 ObjectIdFactory 的代码实现

public class ObjectIdFactory { private int increment; private readonly byte[] pidHex; private readonly byte[] machineHash; private readonly UTF8Encoding utf8 = new UTF8Encoding(false); private readonly DateTime unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); public ObjectIdFactory() { MD5 md5 = MD5.Create(); machineHash = md5.ComputeHash(utf8.GetBytes(Dns.GetHostName())); pidHex = BitConverter.GetBytes(Process.GetCurrentProcess().Id); Array.Reverse(pidHex); } /// <summary> /// 产生一个新的 24 位唯一编号 /// </summary> /// <returns></returns> public ObjectId NewId() { int copyIdx = 0; byte[] hex = new byte[12]; byte[] time = BitConverter.GetBytes(GetTimestamp()); Array.Reverse(time); Array.Copy(time, 0, hex, copyIdx, 4); copyIdx += 4; Array.Copy(machineHash, 0, hex, copyIdx, 3); copyIdx += 3; Array.Copy(pidHex, 2, hex, copyIdx, 2); copyIdx += 2; byte[] inc = BitConverter.GetBytes(GetIncrement()); Array.Reverse(inc); Array.Copy(inc, 1, hex, copyIdx, 3); return new ObjectId(hex); } private int GetIncrement() => System.Threading.Interlocked.Increment(ref increment); private int GetTimestamp() => Convert.ToInt32(Math.Floor((DateTime.UtcNow - unixEpoch).TotalSeconds)); }

ObjectIdFactory 的内部实现非常的简单,但是也是整个 Oid 程序的核心,在构造函数中获取机器名称和进程编号以备后续生产使用,在核心方法 NewId 中,依次将 Timestamp、machineHash、pidHex、increment 写入数组中,最后调用 new ObjectId(hex) 返回生产好的 Oid。

类 ObjectId 的代码实现

类 ObjectId 的代码实现 public class ObjectId { private readonly static ObjectIdFactory factory = new ObjectIdFactory(); public ObjectId(byte[] hexData) { this.Hex = hexData; ReverseHex(); } public override string ToString() { if (Hex == null) Hex = new byte[12]; StringBuilder hexText = new StringBuilder(); for (int i = 0; i < this.Hex.Length; i++) { hexText.Append(this.Hex[i].ToString("x2")); } return hexText.ToString(); } public override int GetHashCode() => ToString().GetHashCode(); public ObjectId(string value) { if (string.IsNullOrEmpty(value)) throw new ArgumentNullException("value"); if (value.Length != 24) throw new ArgumentOutOfRangeException("value should be 24 characters"); Hex = new byte[12]; for (int i = 0; i < value.Length; i += 2) { try { Hex[i / 2] = Convert.ToByte(value.Substring(i, 2), 16); } catch { Hex[i / 2] = 0; } } ReverseHex(); } private void ReverseHex() { int copyIdx = 0; byte[] time = new byte[4]; Array.Copy(Hex, copyIdx, time, 0, 4); Array.Reverse(time); this.Timestamp = BitConverter.ToInt32(time, 0); copyIdx += 4; byte[] mid = new byte[4]; Array.Copy(Hex, copyIdx, mid, 0, 3); this.Machine = BitConverter.ToInt32(mid, 0); copyIdx += 3; byte[] pids = new byte[4]; Array.Copy(Hex, copyIdx, pids, 0, 2); Array.Reverse(pids); this.ProcessId = BitConverter.ToInt32(pids, 0); copyIdx += 2; byte[] inc = new byte[4]; Array.Copy(Hex, copyIdx, inc, 0, 3); Array.Reverse(inc); this.Increment = BitConverter.ToInt32(inc, 0); } public static ObjectId NewId() => factory.NewId(); public int CompareTo(ObjectId other) { if (other is null) return 1; for (int i = 0; i < Hex.Length; i++) { if (Hex[i] < other.Hex[i]) return -1; else if (Hex[i] > other.Hex[i]) return 1; } return 0; } public bool Equals(ObjectId other) => CompareTo(other) == 0; public static bool operator <(ObjectId a, ObjectId b) => a.CompareTo(b) < 0; public static bool operator <=(ObjectId a, ObjectId b) => a.CompareTo(b) <= 0; public static bool operator ==(ObjectId a, ObjectId b) => a.Equals(b); public override bool Equals(object obj) => base.Equals(obj); public static bool operator !=(ObjectId a, ObjectId b) => !(a == b); public static bool operator >=(ObjectId a, ObjectId b) => a.CompareTo(b) >= 0; public static bool operator >(ObjectId a, ObjectId b) => a.CompareTo(b) > 0; public static implicit operator string(ObjectId objectId) => objectId.ToString(); public static implicit operator ObjectId(string objectId) => new ObjectId(objectId); public static ObjectId Empty { get { return new ObjectId("000000000000000000000000"); } } public byte[] Hex { get; private set; } public int Timestamp { get; private set; } public int Machine { get; private set; } public int ProcessId { get; private set; } public int Increment { get; private set; } }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/cb90f35dce6035da81ac1c13dd305061.html