在现代软件开发中,唯一标识符(Unique Identifier,简称 UUID)扮演着非常重要的角色,用于保证数据的唯一性和标识对象。UUID 是一串由 16 字节组成的字符序列,通常使用标准的 36 个字符表示法(例如:550e8400-e29b-41d4-a716-446655440000)。然而,传统的 UUID 存在一些问题,比如无法按时间排序、长度较长等。
为了解决这些问题,ULID(Universally Unique Lexicographically Sortable Identifier)应运而生。ULID 是由 Alizain Feerasta 在 2016 年提出的一种可排序的随机标识生成方式。与传统的 UUID 相比,ULID 具有以下优点:
1. ULID 与 UUID 的对比
1.1 可排序性
ULID 能够根据生成时的时间戳进行排序,使得生成的标识能够按照时间顺序排列,并且支持范围查询和快速索引。
1.2 长度更短
ULID 使用 26 个字符的 Base32 编码表示,相比标准的 UUID 使用的 36 个字符长度更短,减少网络传输和存储的开销。
1.3 高性能
由于 ULID 可以根据时间戳排序,它非常适合在分布式系统中使用,尤其是在高并发环境下,可以有效减少冲突和竞争。
尽管 ULID 的冲突概率很低,但并不能保证完全唯一。因此,在高度依赖唯一性的场景中,仍建议使用更长的 UUID。
2. ULID 的结构
ULID 的结构由两部分组成:时间部分和随机数部分。如果在短时间内生成多个 ULID,那么结果可能是这样:
01H69Y96VND6PYACNN2P0RX2RV
01H69Y96VN8QBBYS7QNFK3BC6F
01H69Y96VNM8CMCY0Y2PMPYJY7
01H69Y96VNDD5D8PNMR736TB84
01H69Y96VN9KY23BVBW1Y8NQJV
01H69Y96VNQVGT2V9TCWV0RNYM
01H69Y96VNFYXZT4HCB4Q51H98
01H69Y96VNSFZ8C18AXHB9JHVE
01H69Y96VNWN1FV7QYWN8806WV
01H69Y96VNHPW743GANKP6F3BY
具体而言,一个 ULID 标识符包含了以下信息:
2.1 时间戳
ULID 使用 48 位存储时间戳,精确到毫秒级别。时间戳表示生成 ULID 的时间,且是自增的,因此可以用来对 ULID 进行排序。
2.2 随机数
ULID 使用 80 位的随机数部分来提高唯一性。这部分随机数通过加密强度较高的随机数生成器产生,确保了生成的 ULID 具有高度的唯一性。
为了更好地可视化 ULID,通常将其表示为一个由 26 个字符组成的字符串。其中,前面部分包含了时间戳信息,后面部分是随机数,均使用 Base32 编码表示。
3. 在 .NET 中使用 ULID
在 .NET 中,我们可以使用 Ulid NuGet 包来轻松地生成和使用 ULID 标识。首先,在项目中引入 Ulid NuGet 包:
https://www.nuget.org/packages/Ulid
然后就可以通过以下代码示例生成 ULID:
// 生成一个新的 ULID
Ulid ulid = Ulid.NewUlid();
// 将 ULID 转换为字符串形式
string ulidString = ulid.ToString();
除了生成 ULID,我们还可以在 .NET 中对 ULID 进行解析和比较:
// 解析 ULID 字符串
string ulidString = "01ARYZ6S41TSV4RRFFQ69G5FAV";
Ulid ulid = Ulid.Parse(ulidString);
// 比较 ULID
Ulid ulid1 = Ulid.NewUlid();
Ulid ulid2 = Ulid.NewUlid();
int comparisonResult = ulid1.CompareTo(ulid2);
bool isEqual = ulid1.Equals(ulid2);
Console.WriteLine($"ULID1: {ulid1}");
Console.WriteLine($"ULID2: {ulid2}");
Console.WriteLine($"Comparison result: {comparisonResult}");
Console.WriteLine($"Equal?: {isEqual}");
一个简单的方法列表如下(方法含义不言自明,且和 Guid 相似):
Ulid.NewUlid()
Ulid.Parse()
Ulid.TryParse()
new Ulid()//以下为实例方法
.ToString()
.ToByteArray()
.TryWriteBytes()
.TryWriteStringify()
.ToBase64()
.Time
.Random
4. Ulid 与其他组件集成
4.1 System.Text.Json
System.Text.Json 是 .NET 中的 JSON 序列化库,我们可以使用自定义的转换器将 ULID 对象序列化和反序列化为字符串。
NuGet: Ulid.SystemTextJson
您可以使用自定义 Ulid 转换器 – Cysharp.Serialization.Json.UlidJsonConverter。
var options = new JsonSerializerOptions()
{
Converters =
{
new Cysharp.Serialization.Json.UlidJsonConverter()
}
};
JsonSerializer.Serialize(Ulid.NewUlid(), options);
如果应用程序的目标框架是 netcoreapp3.0,转换器就是内置的,不需要添加 Ulid.SystemTextJson 包,也不需要使用自定义选项。
4.2 Dapper
Dapper 是一个简单、快速的 ORM(对象关系映射)工具,我们可以使用 ULID 作为实体类的标识,并在数据库操作中无缝使用 ULID。
对于 Dapper 或其他 ADO.NET 数据库映射器,可注册 Ulid 到二进制或 Ulid 到字符串的自定义转换器。
public class BinaryUlidHandler : TypeHandler<Ulid>
{
public override Ulid Parse(object value)
{
return new Ulid((byte[])value);
}
public override void SetValue(IDbDataParameter parameter, Ulid value)
{
parameter.DbType = DbType.Binary;
parameter.Size = 16;
parameter.Value = value.ToByteArray();
}
}
public class StringUlidHandler : TypeHandler<Ulid>
{
public override Ulid Parse(object value)
{
return Ulid.Parse((string)value);
}
public override void SetValue(IDbDataParameter parameter, Ulid value)
{
parameter.DbType = DbType.StringFixedLength;
parameter.Size = 26;
parameter.Value = value.ToString();
}
}
// setup handler
Dapper.SqlMapper.AddTypeHandler(new BinaryUlidHandler());
4.3 Entity Framework Core
Entity Framework Core 是 .NET 的对象关系映射框架,它提供了对多种数据库的支持。
在 EF 中使用时,需要创建 ValueConverter 并将其绑定。
public class UlidToBytesConverter : ValueConverter<Ulid, byte[]>
{
private static readonly ConverterMappingHints defaultHints = new ConverterMappingHints(size: 16);
public UlidToBytesConverter(ConverterMappingHints mappingHints = null)
: base(
convertToProviderExpression: x => x.ToByteArray(),
convertFromProviderExpression: x => new Ulid(x),
mappingHints: defaultHints.With(mappingHints))
{
}
}
public class UlidToStringConverter : ValueConverter<Ulid, string>
{
private static readonly ConverterMappingHints defaultHints = new ConverterMappingHints(size: 26);
public UlidToStringConverter(ConverterMappingHints mappingHints = null)
: base(
convertToProviderExpression: x => x.ToString(),
convertFromProviderExpression: x => Ulid.Parse(x),
mappingHints: defaultHints.With(mappingHints))
{
}
}
总结
总结而言,ULID 是一种可排序的随机标识生成方式,相比传统的 UUID 具有更短的长度、可排序性和高性能等优点。在 .NET 中,我们可以使用开源库轻松地生成、解析和比较 ULID。这使得在分布式系统中处理唯一标识符变得更加方便和高效。同时,借助强大的开源社区,我们也可以很容易的将 ULID 与其他第三方组件集成。因此,对于需要在 .NET 中生成唯一标识符的开发者来说,ULID 是一个值得考虑的选择。