前言
在设计数据库的时候,我们通常需要给业务数据表分配主键,很多时候,为了省事,我都是直接使用 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 的代码实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
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 的代码实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
|
类 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 看看效果。
1
2
3
4
5
|
for ( int i = 0; i < 100; i++)
{
var oid = objectid.newid();
console.writeline(oid);
}
|
输出
通过上图可以看到,输出的这部分 oid 都是有序的,这应该也可以成为替换 guid/uuid 的一个理由。
生产/解包
1
2
|
var sourceid = objectid.newid();
var reverseid = new objectid(sourceid);
|
通过解包可以看出,上图两个红框内的值是一致的,解包成功!
隐式转换
1
2
3
4
5
6
7
8
|
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反解的资料请关注服务器之家其它相关文章!
原文链接:https://www.cnblogs.com/viter/p/13419920.html