溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

.NET Core中如何反解ObjectId

發(fā)布時(shí)間:2021-03-05 15:57:29 來源:億速云 閱讀:164 作者:TREX 欄目:開發(fā)技術(shù)

這篇文章主要講解了“.NET Core中如何反解ObjectId”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“.NET Core中如何反解ObjectId”吧!

前言

在設(shè)計(jì)數(shù)據(jù)庫的時(shí)候,我們通常需要給業(yè)務(wù)數(shù)據(jù)表分配主鍵,很多時(shí)候,為了省事,我都是直接使用 GUID/UUID 的方式,但是在 MonggoDB 中,其內(nèi)部實(shí)現(xiàn)了 ObjectId(以下統(tǒng)稱為Oid)。并且在.NETCore 的驅(qū)動(dòng)中給出了源代碼的實(shí)現(xiàn)。

經(jīng)過仔細(xì)研讀官方的源碼后發(fā)現(xiàn),其實(shí)現(xiàn)原理非常的簡單易學(xué),在最新的版本中,閹割了 UnPack 函數(shù),可能是官方覺得解包是沒什么太多的使用場景的,但是我們認(rèn)為,對于數(shù)據(jù)溯源來說,解包的操作實(shí)在是非常有必要,特別是在目前的微服務(wù)大流行的背景下。

為此,在參考官方代碼的基礎(chǔ)上進(jìn)行了部分改進(jìn),增加了一下自己的需求。本示例代碼增加了解包的操作、對 string 的隱式轉(zhuǎn)換、提供讀取解包后數(shù)據(jù)的公開屬性。

ObjectId 的數(shù)據(jù)結(jié)構(gòu)

首先,我們來看 Oid 的數(shù)據(jù)結(jié)構(gòu)的設(shè)計(jì)。

.NET Core中如何反解ObjectId

從上圖可以看出,Oid 的數(shù)據(jù)結(jié)構(gòu)主要由四個(gè)部分組成,分別是:Unix時(shí)間戳、機(jī)器名稱、進(jìn)程編號、自增編號。Oid 實(shí)際上是總長度為12個(gè)字節(jié)24的字符串,易記口訣為:4323,時(shí)間4字節(jié),機(jī)器名3字節(jié),進(jìn)程編號2字節(jié),自增編號3字節(jié)。

1、Unix時(shí)間戳:Unix時(shí)間戳以秒為記錄單位,即從1970/1/1 00:00:00 開始到當(dāng)前時(shí)間的總秒數(shù)。
2、機(jī)器名稱:記錄當(dāng)前生產(chǎn)Oid的設(shè)備號
3、進(jìn)程編號:當(dāng)前運(yùn)行Oid程序的編號
4、自增編號:在當(dāng)前秒內(nèi),每次調(diào)用都將自動(dòng)增長(已實(shí)現(xiàn)線程安全)

根據(jù)算法可知,當(dāng)前一秒內(nèi)產(chǎn)生的最大 id 數(shù)量為 2^24=16777216 條記錄,所以無需過多擔(dān)心 id 碰撞的問題。

實(shí)現(xiàn)思路

先來看一下代碼實(shí)現(xiàn)后的類結(jié)構(gòu)圖。

.NET Core中如何反解ObjectId

通過上圖可以發(fā)現(xiàn),類圖主要由兩部分組成,ObjectId/ObjectIdFactory,在類 ObjectId 中,主要實(shí)現(xiàn)了生產(chǎn)、解包、計(jì)算、轉(zhuǎn)換、公開數(shù)據(jù)結(jié)構(gòu)等操作,而 ObjectIdFactory 只有一個(gè)功能,就是生產(chǎn) Oid。

所以,我們知道,類 ObjectId 中的 NewId 實(shí)際是調(diào)用了 ObjectIdFactory 的 NewId 方法。

為了生產(chǎn)效率的問題,在 ObjectId 中聲明了靜態(tài)的 ObjectIdFactory 對象,有一些初始化的工作需要在程序啟動(dòng)的時(shí)候在 ObjectIdFactory 的構(gòu)造函數(shù)內(nèi)部完成,比如獲取機(jī)器名稱和進(jìn)程編號,這些都是一次性的工作。

類 ObjectIdFactory 的代碼實(shí)現(xiàn)

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>
  /// 產(chǎn)生一個(gè)新的 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 的內(nèi)部實(shí)現(xiàn)非常的簡單,但是也是整個(gè) Oid 程序的核心,在構(gòu)造函數(shù)中獲取機(jī)器名稱和進(jìn)程編號以備后續(xù)生產(chǎn)使用,在核心方法 NewId 中,依次將 Timestamp、machineHash、pidHex、increment 寫入數(shù)組中,最后調(diào)用 new ObjectId(hex) 返回生產(chǎn)好的 Oid。

類 ObjectId 的代碼實(shí)現(xiàn)

類 ObjectId 的代碼實(shí)現(xiàn)
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 的代碼量看起來稍微多一些,但是實(shí)際上,核心的實(shí)現(xiàn)方法就只有 ReverseHex() 方法,該方法在內(nèi)部反向了 ObjectIdFactory.NewId() 的過程,使得調(diào)用者可以通過調(diào)用 ObjectId.Timestamp 等公開屬性反向追溯 Oid 的生產(chǎn)過程。

其它的對象比較、到 string/ObjectId 的隱式轉(zhuǎn)換,則是一些語法糖式的工作,都是為了提高編碼效率的。

需要注意的是,在類 ObjectId 的內(nèi)部,創(chuàng)建了靜態(tài)對象 ObjectIdFactory,我們還記得在 ObjectIdFactory 的構(gòu)造函數(shù)內(nèi)部的初始化工作,這里創(chuàng)建的靜態(tài)對象,也是為了提高生產(chǎn)效率的設(shè)計(jì)。

調(diào)用示例

在完成了代碼改造后,我們就可以對改造后的代碼進(jìn)行調(diào)用測試,以驗(yàn)證程序的正確性。

NewId

我們嘗試生產(chǎn)一組 Oid 看看效果。

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

輸出

通過上圖可以看到,輸出的這部分 Oid 都是有序的,這應(yīng)該也可以成為替換 GUID/UUID 的一個(gè)理由。

生產(chǎn)/解包

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

.NET Core中如何反解ObjectId

通過解包可以看出,上圖兩個(gè)紅框內(nèi)的值是一致的,解包成功!

隱式轉(zhuǎn)換

var sourceId = ObjectId.NewId();

// 轉(zhuǎn)換為 string
var stringId = sourceId;
string userId= ObjectId.NewId();

// 轉(zhuǎn)換為 ObjectId
ObjectId id = stringId;

隱式轉(zhuǎn)換可以提高編碼效率喲!

結(jié)束語

通過上面的代碼實(shí)現(xiàn),融入了一些自己的需求。現(xiàn)在,可以通過解包來實(shí)現(xiàn)業(yè)務(wù)的追蹤和日志的排查,在某些場景下,是非常有幫助的,增加的隱式轉(zhuǎn)換語法糖,也可以讓編碼效率得到提高;同時(shí)將代碼優(yōu)化到 .NETCore 3.1,也使用了一些 C# 的語法糖。

感謝各位的閱讀,以上就是“.NET Core中如何反解ObjectId”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對.NET Core中如何反解ObjectId這一問題有了更深刻的體會,具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識點(diǎn)的文章,歡迎關(guān)注!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI