feat(id): refactor to ID naming, define Epoch, and optimize defaultIncr
This commit is contained in:
parent
f7b7402911
commit
5de6610626
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
go.sum
|
||||||
@ -1,5 +1,11 @@
|
|||||||
# Changelog: @go/id
|
# Changelog: @go/id
|
||||||
|
|
||||||
|
## [v1.0.1] - 2026-05-01
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- 重构:将 IdMaker 重命名为 IDMaker,符合 Go 命名规范。
|
||||||
|
- 优化:使用 Epoch 常量替代硬编码时间戳,优化 defaultIncr 控制流。
|
||||||
|
|
||||||
## [v1.0.0] - 2026-04-22
|
## [v1.0.0] - 2026-04-22
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
14
TEST.md
14
TEST.md
@ -1,27 +1,27 @@
|
|||||||
# Test Report: @go/id
|
# Test Report: @go/id
|
||||||
|
|
||||||
## 📋 测试概览
|
## 📋 测试概览
|
||||||
- **测试时间**: 2026-04-22
|
- **测试时间**: 2026-05-01
|
||||||
- **测试环境**: darwin/amd64 (Intel i9-9980HK)
|
- **测试环境**: darwin/amd64 (Intel i9-9980HK)
|
||||||
- **Go 版本**: 1.25.0
|
- **Go 版本**: 1.25.0
|
||||||
|
|
||||||
## ✅ 功能测试 (Functional Tests)
|
## ✅ 功能测试 (Functional Tests)
|
||||||
| 场景 | 状态 | 描述 |
|
| 场景 | 状态 | 描述 |
|
||||||
| :--- | :--- | :--- |
|
| :--- | :--- | :--- |
|
||||||
| `TestMakeId` | PASS | 生成长度符合预期的通用 ID。 |
|
| `TestMakeID` | PASS | 生成长度符合预期的通用 ID。 |
|
||||||
| `TestGetForMysql` | PASS | 生成 MySQL 友好主键,包含右旋散列逻辑。 |
|
| `TestGetForMysql` | PASS | 生成 MySQL 友好主键,包含右旋散列逻辑。 |
|
||||||
| `TestGetForPostgreSQL`| PASS | 生成 PostgreSQL 友好主键,无右旋散列。 |
|
| `TestGetForPostgreSQL`| PASS | 生成 PostgreSQL 友好主键,无右旋散列。 |
|
||||||
|
|
||||||
## ⚡ 性能基准 (Benchmarks)
|
## ⚡ 性能基准 (Benchmarks)
|
||||||
| 函数 | 平均耗时 | 内存分配 |
|
| 函数 | 平均耗时 |
|
||||||
| :--- | :--- | :--- |
|
| :--- | :--- |
|
||||||
| `MakeId-10` | **1523 ns/op** | 912 B/op (9 allocs/op) |
|
| `MakeID-10` | **1572 ns/op** |
|
||||||
| `GetForMysql-10` | **1505 ns/op** | 896 B/op (9 allocs/op) |
|
| `GetForMysql-10` | **1552 ns/op** |
|
||||||
|
|
||||||
* 集成 `@go/rand` 的 `FastInt` 优化,在高并发下彻底规避了锁竞争。
|
* 集成 `@go/rand` 的 `FastInt` 优化,在高并发下彻底规避了锁竞争。
|
||||||
* ID 生成性能稳定,满足高性能主键生成场景。
|
* ID 生成性能稳定,满足高性能主键生成场景。
|
||||||
|
|
||||||
## 🛡️ 鲁棒性防御 (Robustness)
|
## 🛡️ 鲁棒性防御 (Robustness)
|
||||||
- **并发安全**:核心计数器使用 `sync.Mutex` 保护,生成器随机数部分使用高性能无锁池。
|
- **并发安全**:核心计数器使用 `sync.Mutex` 保护。
|
||||||
- **碰撞防御**:秒级重置机制配合随机偏移初始化,极大地降低碰撞概率。
|
- **碰撞防御**:秒级重置机制配合随机偏移初始化,极大地降低碰撞概率。
|
||||||
- **扩展性**:支持自定义钩子,轻松对接 Redis 等分布式协调服务。
|
- **扩展性**:支持自定义钩子,轻松对接 Redis 等分布式协调服务。
|
||||||
|
|||||||
4
go.sum
4
go.sum
@ -1,4 +0,0 @@
|
|||||||
apigo.cc/go/encoding v1.0.0 h1:NFb658uGqyh8hKKK9EYqQ6ybmcIOslV57Tdqvd0+z6Y=
|
|
||||||
apigo.cc/go/encoding v1.0.0/go.mod h1:V5CgT7rBbCxy+uCU20q0ptcNNRSgMtpA8cNOs6r8IeI=
|
|
||||||
apigo.cc/go/rand v1.0.2 h1:dJsm607EynJOAoukTvarrUyvLtBF7pi27A99vw2+i78=
|
|
||||||
apigo.cc/go/rand v1.0.2/go.mod h1:mZ/4Soa3bk+XvDaqPWJuUe1bfEi4eThBj1XmEAuYxsk=
|
|
||||||
37
id.go
37
id.go
@ -8,45 +8,50 @@ import (
|
|||||||
"apigo.cc/go/encoding"
|
"apigo.cc/go/encoding"
|
||||||
)
|
)
|
||||||
|
|
||||||
type IdMaker struct {
|
const Epoch = 946656000
|
||||||
|
|
||||||
|
type IDMaker struct {
|
||||||
secCurrent uint64
|
secCurrent uint64
|
||||||
secIndexNext uint64
|
secIndexNext uint64
|
||||||
secIndexLock sync.Mutex
|
secIndexLock sync.Mutex
|
||||||
Incr func(sec uint64) uint64
|
Incr func(sec uint64) uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIdMaker(incr func(sec uint64) uint64) *IdMaker {
|
func NewIDMaker(incr func(sec uint64) uint64) *IDMaker {
|
||||||
return &IdMaker{Incr: incr, secIndexNext: 1}
|
return &IDMaker{Incr: incr, secIndexNext: 1}
|
||||||
}
|
}
|
||||||
|
|
||||||
var DefaultIdMaker = NewIdMaker(nil)
|
var DefaultIDMaker = NewIDMaker(nil)
|
||||||
|
|
||||||
func MakeId(size int) string {
|
func MakeID(size int) string {
|
||||||
return DefaultIdMaker.Get(size)
|
return DefaultIDMaker.Get(size)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (im *IdMaker) defaultIncr(sec uint64) uint64 {
|
func (im *IDMaker) defaultIncr(sec uint64) uint64 {
|
||||||
im.secIndexLock.Lock()
|
im.secIndexLock.Lock()
|
||||||
defer im.secIndexLock.Unlock()
|
defer im.secIndexLock.Unlock()
|
||||||
|
|
||||||
if im.secCurrent == sec {
|
if im.secCurrent == sec {
|
||||||
im.secIndexNext++
|
im.secIndexNext++
|
||||||
} else {
|
return im.secIndexNext
|
||||||
|
}
|
||||||
|
|
||||||
if im.secCurrent == 0 {
|
if im.secCurrent == 0 {
|
||||||
im.secIndexNext = rand.FastInt(uint64(1000), uint64(1999))
|
im.secIndexNext = uint64(rand.FastInt(1000, 1999))
|
||||||
} else {
|
} else {
|
||||||
im.secIndexNext = 1
|
im.secIndexNext = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
im.secCurrent = sec
|
im.secCurrent = sec
|
||||||
}
|
|
||||||
return im.secIndexNext
|
return im.secIndexNext
|
||||||
}
|
}
|
||||||
|
|
||||||
func (im *IdMaker) get(size int, ordered bool, hashToHead bool) string {
|
func (im *IDMaker) get(size int, ordered bool, hashToHead bool) string {
|
||||||
tm := time.Now()
|
tm := time.Now()
|
||||||
|
|
||||||
nowSec := uint64(tm.Unix() - 946656000)
|
nowSec := uint64(tm.Unix() - Epoch)
|
||||||
var n, sec uint64
|
var n, sec uint64
|
||||||
secCapacity := uint64(901356495)
|
const secCapacity = 901356495
|
||||||
if nowSec < 11*secCapacity {
|
if nowSec < 11*secCapacity {
|
||||||
n = nowSec / secCapacity
|
n = nowSec / secCapacity
|
||||||
sec = (nowSec % secCapacity) + 14776336
|
sec = (nowSec % secCapacity) + 14776336
|
||||||
@ -94,14 +99,14 @@ func (im *IdMaker) get(size int, ordered bool, hashToHead bool) string {
|
|||||||
return string(uid)
|
return string(uid)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (im *IdMaker) Get(size int) string {
|
func (im *IDMaker) Get(size int) string {
|
||||||
return im.get(size, false, false)
|
return im.get(size, false, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (im *IdMaker) GetForMysql(size int) string {
|
func (im *IDMaker) GetForMysql(size int) string {
|
||||||
return im.get(size, true, true)
|
return im.get(size, true, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (im *IdMaker) GetForPostgreSQL(size int) string {
|
func (im *IDMaker) GetForPostgreSQL(size int) string {
|
||||||
return im.get(size, true, false)
|
return im.get(size, true, false)
|
||||||
}
|
}
|
||||||
|
|||||||
16
id_test.go
16
id_test.go
@ -5,36 +5,36 @@ import (
|
|||||||
"apigo.cc/go/id"
|
"apigo.cc/go/id"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMakeId(t *testing.T) {
|
func TestMakeID(t *testing.T) {
|
||||||
uid := id.MakeId(10)
|
uid := id.MakeID(10)
|
||||||
if len(uid) != 10 {
|
if len(uid) != 10 {
|
||||||
t.Errorf("expected length 10, got %d", len(uid))
|
t.Errorf("expected length 10, got %d", len(uid))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetForMysql(t *testing.T) {
|
func TestGetForMysql(t *testing.T) {
|
||||||
uid := id.DefaultIdMaker.GetForMysql(10)
|
uid := id.DefaultIDMaker.GetForMysql(10)
|
||||||
if len(uid) != 10 {
|
if len(uid) != 10 {
|
||||||
t.Errorf("expected length 10, got %d", len(uid))
|
t.Errorf("expected length 10, got %d", len(uid))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetForPostgreSQL(t *testing.T) {
|
func TestGetForPostgreSQL(t *testing.T) {
|
||||||
uid := id.DefaultIdMaker.GetForPostgreSQL(10)
|
uid := id.DefaultIDMaker.GetForPostgreSQL(10)
|
||||||
if len(uid) != 10 {
|
if len(uid) != 10 {
|
||||||
t.Errorf("expected length 10, got %d", len(uid))
|
t.Errorf("expected length 10, got %d", len(uid))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkIdMaker(b *testing.B) {
|
func BenchmarkIDMaker(b *testing.B) {
|
||||||
b.Run("MakeId-10", func(b *testing.B) {
|
b.Run("MakeID-10", func(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
_ = id.MakeId(10)
|
_ = id.MakeID(10)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
b.Run("GetForMysql-10", func(b *testing.B) {
|
b.Run("GetForMysql-10", func(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
_ = id.DefaultIdMaker.GetForMysql(10)
|
_ = id.DefaultIDMaker.GetForMysql(10)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user