From d3b71a7eeb92722093f3d2bb17608cc8f93b01ad Mon Sep 17 00:00:00 2001 From: tursom Date: Wed, 26 Apr 2023 17:15:33 +0800 Subject: [PATCH] replace standard library time --- concurrent/Cond.go | 3 +- concurrent/ReentrantRWLock_test.go | 3 +- .../collections/ConcurrentLinkedQueue_test.go | 3 +- concurrent/collections/Park.go | 3 +- concurrent/collections/Park_test.go | 3 +- .../collections/PublisherMessageQueue.go | 3 +- .../collections/PublisherMessageQueue_test.go | 3 +- concurrent/collections/Sequence_test.go | 3 +- concurrent/util/Pipeline_test.go | 3 +- go.sum | 10 ++ lang/Channel.go | 2 +- lang/Channel_test.go | 3 +- lang/ConvChannel.go | 2 +- lang/atomic/Int32_test.go | 3 +- util/Utils.go | 10 ++ util/b62/base62.go | 39 +++++++ util/b62/base62_test.go | 13 +++ util/bloom/Bloom_test.go | 5 +- util/snowflake/snowflake.go | 107 ++++++++++++++++++ util/snowflake/snowflake_test.go | 41 +++++++ util/time/format.go | 42 +++++++ util/time/genzabbrs.go | 11 ++ util/time/sleep.go | 21 ++++ util/time/tick.go | 25 ++++ util/time/time.go | 70 ++++++++++++ util/time/zoneinfo.go | 19 ++++ 26 files changed, 435 insertions(+), 15 deletions(-) create mode 100644 go.sum create mode 100644 util/b62/base62.go create mode 100644 util/b62/base62_test.go create mode 100644 util/snowflake/snowflake.go create mode 100644 util/snowflake/snowflake_test.go create mode 100644 util/time/format.go create mode 100644 util/time/genzabbrs.go create mode 100644 util/time/sleep.go create mode 100644 util/time/tick.go create mode 100644 util/time/time.go create mode 100644 util/time/zoneinfo.go diff --git a/concurrent/Cond.go b/concurrent/Cond.go index ec4808f..c40df87 100644 --- a/concurrent/Cond.go +++ b/concurrent/Cond.go @@ -8,7 +8,8 @@ package concurrent import ( "sync" - "time" + + "github.com/tursom/GoCollections/util/time" ) type ( diff --git a/concurrent/ReentrantRWLock_test.go b/concurrent/ReentrantRWLock_test.go index 5bf561c..56d7efd 100644 --- a/concurrent/ReentrantRWLock_test.go +++ b/concurrent/ReentrantRWLock_test.go @@ -9,7 +9,8 @@ package concurrent import ( "fmt" "testing" - "time" + + "github.com/tursom/GoCollections/util/time" ) func TestReentrantRWLock_RLock(t *testing.T) { diff --git a/concurrent/collections/ConcurrentLinkedQueue_test.go b/concurrent/collections/ConcurrentLinkedQueue_test.go index 4a7fa84..1afc1c0 100644 --- a/concurrent/collections/ConcurrentLinkedQueue_test.go +++ b/concurrent/collections/ConcurrentLinkedQueue_test.go @@ -11,9 +11,10 @@ import ( "math/rand" "sync" "testing" - "time" "unsafe" + "github.com/tursom/GoCollections/util/time" + "github.com/tursom/GoCollections/collections" "github.com/tursom/GoCollections/exceptions" "github.com/tursom/GoCollections/lang" diff --git a/concurrent/collections/Park.go b/concurrent/collections/Park.go index 93b8193..2022d44 100644 --- a/concurrent/collections/Park.go +++ b/concurrent/collections/Park.go @@ -2,7 +2,8 @@ package collections import ( "sync" - "time" + + "github.com/tursom/GoCollections/util/time" ) type Park struct { diff --git a/concurrent/collections/Park_test.go b/concurrent/collections/Park_test.go index 48347c1..1c01bbc 100644 --- a/concurrent/collections/Park_test.go +++ b/concurrent/collections/Park_test.go @@ -2,7 +2,8 @@ package collections import ( "testing" - "time" + + "github.com/tursom/GoCollections/util/time" ) func TestPark_Park(t *testing.T) { diff --git a/concurrent/collections/PublisherMessageQueue.go b/concurrent/collections/PublisherMessageQueue.go index 48b6bd8..cd95511 100644 --- a/concurrent/collections/PublisherMessageQueue.go +++ b/concurrent/collections/PublisherMessageQueue.go @@ -9,7 +9,8 @@ package collections import ( "log" "sync" - "time" + + "github.com/tursom/GoCollections/util/time" "github.com/tursom/GoCollections/concurrent" "github.com/tursom/GoCollections/exceptions" diff --git a/concurrent/collections/PublisherMessageQueue_test.go b/concurrent/collections/PublisherMessageQueue_test.go index e7f4d2d..ab09b3d 100644 --- a/concurrent/collections/PublisherMessageQueue_test.go +++ b/concurrent/collections/PublisherMessageQueue_test.go @@ -9,7 +9,8 @@ package collections import ( "fmt" "testing" - "time" + + "github.com/tursom/GoCollections/util/time" "github.com/tursom/GoCollections/unsafe" ) diff --git a/concurrent/collections/Sequence_test.go b/concurrent/collections/Sequence_test.go index f104b4f..fdd28eb 100644 --- a/concurrent/collections/Sequence_test.go +++ b/concurrent/collections/Sequence_test.go @@ -10,7 +10,8 @@ import ( "fmt" "math/rand" "testing" - "time" + + "github.com/tursom/GoCollections/util/time" ) func TestSequence_Alloc(t *testing.T) { diff --git a/concurrent/util/Pipeline_test.go b/concurrent/util/Pipeline_test.go index 2d2ac0b..682bf27 100644 --- a/concurrent/util/Pipeline_test.go +++ b/concurrent/util/Pipeline_test.go @@ -9,7 +9,8 @@ package util import ( "fmt" "testing" - "time" + + "github.com/tursom/GoCollections/util/time" ) func TestPipeline(t *testing.T) { diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..b654681 --- /dev/null +++ b/go.sum @@ -0,0 +1,10 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/petermattis/goid v0.0.0-20220302125637-5f11c28912df h1:/B1Q9E4W1cmiwPQfC2vymWL7FXHCEsUzg8Rywl5avtQ= +github.com/petermattis/goid v0.0.0-20220302125637-5f11c28912df/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/timandy/routine v1.1.1 h1:6/Z7qLFZj3GrzuRksBFzIG8YGUh8CLhjnnMePBQTrEI= +github.com/timandy/routine v1.1.1/go.mod h1:OZHPOKSvqL/ZvqXFkNZyit0xIVelERptYXdAHH00adQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/lang/Channel.go b/lang/Channel.go index 39e55b2..4895517 100644 --- a/lang/Channel.go +++ b/lang/Channel.go @@ -6,7 +6,7 @@ package lang -import "time" +import "github.com/tursom/GoCollections/util/time" type ( SendChannel[T any] interface { diff --git a/lang/Channel_test.go b/lang/Channel_test.go index 8370061..3644e1e 100644 --- a/lang/Channel_test.go +++ b/lang/Channel_test.go @@ -9,7 +9,8 @@ package lang import ( "fmt" "testing" - "time" + + "github.com/tursom/GoCollections/util/time" ) func TestChannel_Send(t *testing.T) { diff --git a/lang/ConvChannel.go b/lang/ConvChannel.go index 9592b6d..8914cb9 100644 --- a/lang/ConvChannel.go +++ b/lang/ConvChannel.go @@ -7,7 +7,7 @@ package lang import ( - "time" + "github.com/tursom/GoCollections/util/time" ) type ( diff --git a/lang/atomic/Int32_test.go b/lang/atomic/Int32_test.go index 7707c20..dd220b3 100644 --- a/lang/atomic/Int32_test.go +++ b/lang/atomic/Int32_test.go @@ -10,7 +10,8 @@ import ( "fmt" "sync" "testing" - "time" + + "github.com/tursom/GoCollections/util/time" ) func TestInt32_SetBit(t *testing.T) { diff --git a/util/Utils.go b/util/Utils.go index add25d4..1972704 100644 --- a/util/Utils.go +++ b/util/Utils.go @@ -24,3 +24,13 @@ func (s Stringer) String() string { } return s.stringer() } + +func Reverse[T any](s []T) []T { + for i := 0; i < len(s)/2; i++ { + temp := s[i] + s[i] = s[len(s)-i-1] + s[len(s)-i-1] = temp + } + + return s +} diff --git a/util/b62/base62.go b/util/b62/base62.go new file mode 100644 index 0000000..85b049f --- /dev/null +++ b/util/b62/base62.go @@ -0,0 +1,39 @@ +package b62 + +import ( + "bytes" + + "github.com/tursom/GoCollections/util" +) + +var ( + digits = []byte("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz") +) + +func Encode(n uint64) string { + buffer := bytes.NewBuffer(nil) + + for n != 0 { + remainder := n % 62 + buffer.WriteByte(digits[remainder]) + n /= 62 + } + + return string(util.Reverse(buffer.Bytes())) +} + +func Decode(str string) uint64 { + var sum uint64 + var base uint64 = 1 + + for i := range str { + indexByte := bytes.IndexByte(digits, str[len(str)-i-1]) + if indexByte < 0 || indexByte >= 62 { + panic(indexByte) + } + sum += uint64(indexByte) * base + base *= 62 + } + + return sum +} diff --git a/util/b62/base62_test.go b/util/b62/base62_test.go new file mode 100644 index 0000000..fc48a7f --- /dev/null +++ b/util/b62/base62_test.go @@ -0,0 +1,13 @@ +package b62 + +import ( + "math" + "testing" +) + +func TestBase62(t *testing.T) { + encode := Encode(math.MaxUint64) + if Decode(encode) != math.MaxUint64 { + t.Fatal(Decode(encode)) + } +} diff --git a/util/bloom/Bloom_test.go b/util/bloom/Bloom_test.go index efbab1f..f054419 100644 --- a/util/bloom/Bloom_test.go +++ b/util/bloom/Bloom_test.go @@ -12,7 +12,8 @@ import ( "fmt" "math" "testing" - "time" + + "github.com/tursom/GoCollections/util/time" ) func TestBloom_Contains(t *testing.T) { @@ -46,7 +47,7 @@ func TestBloom_miss(t *testing.T) { t1 := time.Now() - for i := 0; i < int(base/1000); i++ { + for i := 0; i < int(base); i++ { bloom.Add([]byte(fmt.Sprintf("%d", i))) } diff --git a/util/snowflake/snowflake.go b/util/snowflake/snowflake.go new file mode 100644 index 0000000..5d2c2a4 --- /dev/null +++ b/util/snowflake/snowflake.go @@ -0,0 +1,107 @@ +package snowflake + +import ( + "github.com/tursom/GoCollections/util/time" + + "github.com/tursom/GoCollections/lang" + "github.com/tursom/GoCollections/lang/atomic" + "github.com/tursom/GoCollections/util/b62" +) + +/** + * 被改造过的雪花ID + * |---------|------------|-----------|------------| + * | 空位(1) | 时间戳(43) | 自增位(7) | 机器ID(13) | + * |---------|------------|-----------|------------| + * 时间戳仅在初始化时使用, 与序列号接壤, 这样做可以避免某一时间内大量请求导致的ID爆炸 + * 当前方案在ID溢出时, 溢出的数据会让时间戳 +1. + * 这样做, 只要节点不重启或者在重启前平均QPS没有超标, 重启后分配的ID仍能唯一 + * + * 当前被调试为平均每毫秒可以相应 128 个消息 + * 如果平均每个人 10 秒发一条消息, 1 秒 128 条消息大约要 1280 人, 1 毫秒 128 条消息就大约需要 128W 用户了 + * 单点无法应付如此巨量的并发, ID生成器保证性能过剩 + * + * 当前最多支持 8192 个节点同时上线, 未来如果节点数超过了 8192 个, 也可以以ID生成的最晚时间为代价提升节点最高数量 + */ + +const ( + incrementBase uint64 = 0x2000 +) + +type ( + Snowflake interface { + New() uint64 + NewStr() string + Close() + } + + snowflake struct { + nodeId uint16 + incrementLength int + seed atomic.UInt64 + closer lang.SendChannel[struct{}] + } +) + +func New(nodeId uint16) Snowflake { + if uint64(nodeId) >= incrementBase { + panic(nodeId) + } + + closer := make(chan struct{}) + + s := &snowflake{ + nodeId: nodeId, + incrementLength: 7, + closer: lang.RawChannel[struct{}](closer), + } + + s.updateTime() + + go func() { + ticker := time.NewTicker(time.Millisecond) + defer ticker.Stop() + + for { + select { + case <-closer: + return + case _, ok := <-ticker.C: + if !ok { + return + } + + s.updateTime() + } + } + }() + + return s +} + +func (s *snowflake) Close() { + s.closer.TrySend(struct{}{}) +} + +func (s *snowflake) updateTime() { + seed := s.newSeed() + old := s.seed.Load() + for old < seed && !s.seed.CompareAndSwap(old, seed) { + seed = s.newSeed() + old = s.seed.Load() + } + +} + +func (s *snowflake) newSeed() uint64 { + return uint64(s.nodeId&0x1fff) | + (uint64(time.Now().UnixMilli()<<(s.incrementLength+13)) & uint64(0x7f_ff_ff_ff_ff_ff_ff_ff)) +} + +func (s *snowflake) NewStr() string { + return b62.Encode(s.New()) +} + +func (s *snowflake) New() uint64 { + return s.seed.Add(incrementBase) +} diff --git a/util/snowflake/snowflake_test.go b/util/snowflake/snowflake_test.go new file mode 100644 index 0000000..4da3a61 --- /dev/null +++ b/util/snowflake/snowflake_test.go @@ -0,0 +1,41 @@ +package snowflake + +import ( + "sync" + "testing" + + "github.com/tursom/GoCollections/util/time" +) + +func Test_snowflake_Close(t *testing.T) { + s := New(0) + s.Close() + s.Close() +} + +func Test_snowflake_New(t *testing.T) { + s := New(1) + + ticker := time.NewTicker(time.Millisecond) + defer ticker.Stop() + + var wg sync.WaitGroup + + for i := 0; i < 16; i++ { + wg.Add(1) + go func() { + old := s.New() + for i := 0; i < 10_000_000; i++ { + n := s.New() + if n <= old { + panic(n) + } + old = n + } + + wg.Done() + }() + } + + wg.Wait() +} diff --git a/util/time/format.go b/util/time/format.go new file mode 100644 index 0000000..46a53e9 --- /dev/null +++ b/util/time/format.go @@ -0,0 +1,42 @@ +package time + +import ( + "time" +) + +const ( + Layout = time.Layout + ANSIC = time.ANSIC + UnixDate = time.UnixDate + RubyDate = time.RubyDate + RFC822 = time.RFC822 + RFC822Z = time.RFC822Z + RFC850 = time.RFC850 + RFC1123 = time.RFC1123 + RFC1123Z = time.RFC1123Z + RFC3339 = time.RFC3339 + RFC3339Nano = time.RFC3339Nano + Kitchen = time.Kitchen + // Handy time stamps. + Stamp = time.Stamp + StampMilli = time.StampMilli + StampMicro = time.StampMicro + StampNano = time.StampNano + DateTime = time.DateTime + DateOnly = time.DateOnly + TimeOnly = time.TimeOnly +) + +type ParseError = time.ParseError + +func Parse(layout, value string) (Time, error) { + return time.Parse(layout, value) +} + +func ParseInLocation(layout, value string, loc *Location) (Time, error) { + return time.ParseInLocation(layout, value, loc) +} + +func ParseDuration(s string) (Duration, error) { + return time.ParseDuration(s) +} diff --git a/util/time/genzabbrs.go b/util/time/genzabbrs.go new file mode 100644 index 0000000..adeca9b --- /dev/null +++ b/util/time/genzabbrs.go @@ -0,0 +1,11 @@ +//go:build ignore + +package main + +import ( + "time" +) + +type MapZone = time.MapZone + +type SupplementalData = time.SupplementalData diff --git a/util/time/sleep.go b/util/time/sleep.go new file mode 100644 index 0000000..88613ed --- /dev/null +++ b/util/time/sleep.go @@ -0,0 +1,21 @@ +package time + +import "time" + +func Sleep(d Duration) { + time.Sleep(d) +} + +type Timer = time.Timer + +func NewTimer(d Duration) *Timer { + return time.NewTimer(d) +} + +func After(d Duration) <-chan Time { + return time.After(d) +} + +func AfterFunc(d Duration, f func()) *Timer { + return time.AfterFunc(d, f) +} diff --git a/util/time/tick.go b/util/time/tick.go new file mode 100644 index 0000000..1f2e8aa --- /dev/null +++ b/util/time/tick.go @@ -0,0 +1,25 @@ +package time + +import ( + "time" + unsafe2 "unsafe" + + "github.com/tursom/GoCollections/unsafe" +) + +type Ticker struct { + time.Ticker +} + +func NewTicker(d Duration) *Ticker { + return &Ticker{*time.NewTicker(d)} +} + +func (t *Ticker) Stop() { + t.Ticker.Stop() + close(*unsafe.ForceCast[chan time.Time](unsafe2.Pointer(&t.C))) +} + +func Tick(d Duration) <-chan Time { + return time.Tick(d) +} diff --git a/util/time/time.go b/util/time/time.go new file mode 100644 index 0000000..d703020 --- /dev/null +++ b/util/time/time.go @@ -0,0 +1,70 @@ +package time + +import ( + "time" +) + +type Time = time.Time + +// A Month specifies a month of the year (January = 1, ...). +type Month = time.Month + +const ( + January = time.January + February = time.February + March = time.March + April = time.April + May = time.May + June = time.June + July = time.July + August = time.August + September = time.September + October = time.October + November = time.November + December = time.December +) + +// A Weekday specifies a day of the week (Sunday = 0, ...). +type Weekday = time.Weekday + +const ( + Sunday = time.Sunday + Monday = time.Monday + Tuesday = time.Tuesday + Wednesday = time.Wednesday + Thursday = time.Thursday + Friday = time.Friday + Saturday = time.Saturday +) + +type Duration = time.Duration + +const ( + Nanosecond = time.Nanosecond + Microsecond = time.Microsecond + Millisecond = time.Millisecond + Second = time.Second + Minute = time.Minute + Hour = time.Hour +) + +// Now returns the current local time. +func Now() Time { + return time.Now() +} + +func Unix(sec int64, nsec int64) Time { + return time.Unix(sec, nsec) +} + +func UnixMilli(msec int64) Time { + return time.UnixMilli(msec) +} + +func UnixMicro(usec int64) Time { + return time.UnixMicro(usec) +} + +func Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) Time { + return time.Date(year, month, day, hour, min, sec, nsec, loc) +} diff --git a/util/time/zoneinfo.go b/util/time/zoneinfo.go new file mode 100644 index 0000000..23503b7 --- /dev/null +++ b/util/time/zoneinfo.go @@ -0,0 +1,19 @@ +package time + +import ( + "time" +) + +type Location = time.Location + +var UTC = time.UTC + +var Local = time.Local + +func FixedZone(name string, offset int) *Location { + return time.FixedZone(name, offset) +} + +func LoadLocation(name string) (*Location, error) { + return time.LoadLocation(name) +}