.NET Core中反解ObjectId

 更新时间:2021年9月22日 10:00  点击:1655

前言

在设计数据库的时候,我们通常需要给业务数据表分配主键,很多时候,为了省事,我都是直接使用 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反解的资料请关注猪先飞其它相关文章!

[!--infotagslink--]

相关文章

  • ASP.NET购物车实现过程详解

    这篇文章主要为大家详细介绍了ASP.NET购物车的实现过程,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-09-22
  • .NET Core下使用Kafka的方法步骤

    这篇文章主要介绍了.NET Core下使用Kafka的方法步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-09-22
  • 在ASP.NET 2.0中操作数据之七十二:调试存储过程

    在开发过程中,使用Visual Studio的断点调试功能可以很方便帮我们调试发现程序存在的错误,同样Visual Studio也支持对SQL Server里面的存储过程进行调试,下面就让我们看看具体的调试方法。...2021-09-22
  • Win10 IIS 安装.net 4.5的方法

    这篇文章主要介绍了Win10 IIS 安装及.net 4.5及Win10安装IIS并配置ASP.NET 4.0的方法,本文给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下...2021-09-22
  • 详解.NET Core 3.0 里新的JSON API

    这篇文章主要介绍了详解.NET Core 3.0 里新的JSON API,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-09-22
  • .net数据库操作框架SqlSugar的简单入门

    这篇文章主要介绍了.net数据库操作框架SqlSugar的简单入门,帮助大家更好的理解和学习使用.net技术,感兴趣的朋友可以了解下...2021-09-22
  • ASP.NET Core根据环境变量支持多个 appsettings.json配置文件

    这篇文章主要介绍了ASP.NET Core根据环境变量支持多个 appsettings.json配置文件,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-09-22
  • 记一次EFCore类型转换错误及解决方案

    这篇文章主要介绍了记一次EFCore类型转换错误及解决方案,帮助大家更好的理解和学习使用asp.net core,感兴趣的朋友可以了解下...2021-09-22
  • .NET C#利用ZXing生成、识别二维码/条形码

    ZXing是一个开放源码的,用Java实现的多种格式的1D/2D条码图像处理库,它包含了联系到其他语言的端口。这篇文章主要给大家介绍了.NET C#利用ZXing生成、识别二维码/条形码的方法,文中给出了详细的示例代码,有需要的朋友们可以参考借鉴。...2020-06-25
  • 详解ASP.NET Core 中基于工厂的中间件激活的实现方法

    这篇文章主要介绍了ASP.NET Core 中基于工厂的中间件激活的实现方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-09-22
  • C#使用Ado.Net更新和添加数据到Excel表格的方法

    这篇文章主要介绍了C#使用Ado.Net更新和添加数据到Excel表格的方法,较为详细的分析了OLEDB的原理与使用技巧,可实现较为方便的操作Excel数据,需要的朋友可以参考下...2020-06-25
  • asp.net通过消息队列处理高并发请求(以抢小米手机为例)

    这篇文章主要介绍了asp.net通过消息队列处理高并发请求(以抢小米手机为例),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-09-22
  • Underscore源码分析

    Underscore 是一个 JavaScript 工具库,它提供了一整套函数式编程的实用功能,但是没有扩展任何 JavaScript 内置对象。这篇文章主要介绍了underscore源码分析相关知识,感兴趣的朋友一起学习吧...2016-01-02
  • ASP.NET 2.0中的数据操作:使用两个DropDownList过滤的主/从报表

    在前面的指南中我们研究了如何显示一个简单的主/从报表, 该报表使用DropDownList和GridView控件, DropDownList填充类别,GridView显示选定类别的产品. 这类报表用于显示具有...2016-05-19
  • ASP.NET单选按钮控件RadioButton常用属性和方法介绍

    RadioButton又称单选按钮,其在工具箱中的图标为 ,单选按钮通常成组出现,用于提供两个或多个互斥选项,即在一组单选钮中只能选择一个...2021-09-22
  • 详解.NET Core 使用HttpClient SSL请求出错的解决办法

    这篇文章主要介绍了.NET Core 使用HttpClient SSL请求出错的解决办法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2021-09-22
  • Python调用.NET库的方法步骤

    这篇文章主要介绍了Python调用.NET库的方法步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-05-09
  • ASP.NET中iframe框架点击左边页面链接 右边显示链接页面内容

    这篇文章主要介绍了ASP.NET中iframe框架点击左边页面链接,右边显示链接页面内容的实现代码,感兴趣的小伙伴们可以参考一下...2021-09-22
  • 创建一个完整的ASP.NET Web API项目

    ASP.NET Web API具有与ASP.NET MVC类似的编程方式,ASP.NET Web API不仅仅具有一个完全独立的消息处理管道,而且这个管道比为ASP.NET MVC设计的管道更为复杂,功能也更为强大。下面创建一个简单的Web API项目,需要的朋友可以参考下...2021-09-22
  • ASP.NET连接MySql数据库的2个方法及示例

    这篇文章主要介绍了ASP.NET连接MySql数据库的2个方法及示例,使用的是MySQL官方组件和ODBC.NET,需要的朋友可以参考下...2021-09-22