.NET Core中反解ObjectId
 更新时间:2020年08月03日 10:19:41   作者:Ron.Liang  

这篇文章主要介绍了.NET Core中实现ObjectId反解的方法,文中讲解非常细致,代码帮助大家更好的理解和学习,感兴趣的朋友可以了解下

前言
在设计数据库的时候,我们通常需要给业务数据表分配主键,很多时候,为了省事,我都是直接使用 GUID/UUID 的方式,但是在 MonggoDB 中,其内部实现了 ObjectId(以下统称为Oid)。并且在.NETCore 的驱动中给出了源代码的实现。
经过仔细研读官方的源码后发现,其实现原理非常的简单易学,在最新的版本中,阉割了 UnPack 函数,可能是官方觉得解包是没什么太多的使用场景的,但是我们认为,对于数据溯源来说,解包的操作实在是非常有必要,特别是在目前的微服务大流行的背景下。
为此,在参考官方代码的基础上进行了部分改进,增加了一下自己的需求。本示例代码增加了解包的操作、对 string 的隐式转换、提供读取解包后数据的公开属性。
ObjectId 的数据结构
首先,我们来看 Oid 的数据结构的设计。

从上图可以看出,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 碰撞的问题。
实现思路
先来看一下代码实现后的类结构图。

通过上图可以发现,类图主要由两部分组成,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; }
}

ObjectId 的代码量看起来稍微多一些,但是实际上,核心的实现方法就只有 ReverseHex() 方法,该方法在内部反向了 ObjectIdFactory.NewId() 的过程,使得调用者可以通过调用 ObjectId.Timestamp 等公开属性反向追溯 Oid 的生产过程。
其它的对象比较、到 string/ObjectId 的隐式转换,则是一些语法糖式的工作,都是为了提高编码效率的。
需要注意的是,在类 ObjectId 的内部,创建了静态对象 ObjectIdFactory,我们还记得在 ObjectIdFactory 的构造函数内部的初始化工作,这里创建的静态对象,也是为了提高生产效率的设计。
调用示例
在完成了代码改造后,我们就可以对改造后的代码进行调用测试,以验证程序的正确性。
NewId
我们尝试生产一组 Oid 看看效果。

for (int i = 0; i < 100; i++)
{
var oid = ObjectId.NewId();
Console.WriteLine(oid);
}

输出

通过上图可以看到,输出的这部分 Oid 都是有序的,这应该也可以成为替换 GUID/UUID 的一个理由。
生产/解包

var sourceId = ObjectId.NewId();
var reverseId = new ObjectId(sourceId);

通过解包可以看出,上图两个红框内的值是一致的,解包成功!
隐式转换

var sourceId = ObjectId.NewId();

// 转换为 string
var stringId = sourceId;
string userId= ObjectId.NewId();

// 转换为 ObjectId
ObjectId id = stringId;

隐式转换可以提高编码效率哟!
结束语
通过上面的代码实现,融入了一些自己的需求。现在,可以通过解包来实现业务的追踪和日志的排查,在某些场景下,是非常有帮助的,增加的隐式转换语法糖,也可以让编码效率得到提高;同时将代码优化到 .NETCore 3.1,也使用了一些 C# 的语法糖。
以上就是.NET Core中实现ObjectId反解的方法的详细内容,更多关于.NET Core ObjectId反解的资料请关注华域联盟其它相关文章!

您可能感兴趣的文章:Node.js使用MongoDB的ObjectId作为查询条件的方法深究从MongoDB的ObjectId中获取时间信息MongoDB中ObjectId的误区及引起的一系列问题python将MongoDB里的ObjectId转换为时间戳的方法python根据时间生成mongodb的ObjectId的方法java查询mongodb中的objectid示例关于C#生成MongoDB中ObjectId的实现方法

.net
core
ObjectId

相关文章
ASP.NET 绑定DataSet中的多个表今天在论坛遇到有人问如何在ASP.NET的数据控件中如何一次绑定多个表? 2008-12-12
asp.net core mvc实现伪静态功能这篇文章主要为大家详细介绍了asp.net core mvc实现伪静态功能的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 2017-02-02
asp.net 获取指定文件夹下所有子目录及文件(树形)asp.net获取指定文件夹下所有子目录及文件,现在有asp.net的站的就是用这样的原理显示文件的 2008-07-07
Asp.Net超大文件上传问题解决Asp.Net超大文件上传问题解决,需要的朋友可以参考一下 2013-06-06
ASP.NET Gridview与checkbox全选、全不选实现代码ASP.NET Gridview checkbox全选与全不选实现代码,其实原理就是利用js来实现的,但需要简单的设置下回传。 2010-04-04
asp.net中for和do循环语句用法分享文章介绍了两个实例一个是FOR循环创建一个Mandelbrot图像,循环结构之DO语句,根据布尔值的测试结果,执行相应代码,有需要的朋友可参考一下 2012-04-04
asp.net 弹出对话框返回多个值这是我写的第一篇文章,呵呵。所以写的详细希望能帮到某些兄弟。前段时间做过一个项目。需要用到选择对话框。当单击选择按钮时要弹出一个网页包含GridView。当单击选择时返回GridView中单元格的值。 2009-11-11
ASP.NET中的Cache使用介绍这篇文章主要介绍了ASP.NET中的Cache使用介绍,本文讲解了Cache 是怎么工作的、Cache 怎么创建及怎么销毁、什么时候用cache、cache 调用注意事项等内容,需要的朋友可以参考下 2015-06-06
详解.Net单元测试方法本篇文章给大家详细讲述了.NET单元测试的详细方法和步骤,有需要的朋友参考学习下。 2018-07-07
在ashx文件中使用session的解决思路如果你要保证数据的安全性,你可以在ashx中使用session验证如:你的index.aspx中使用jquery回调ashx数据,那么在index.aspx page_load时session[checked]="true",在ashx中验证session是否存在 2013-01-01

最新评论

声明:本站(华域联盟www.cnhackhy.com)所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。