From 1005d7458d2e32c45a170449b0fe5b276d6d39ea Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Mon, 21 Aug 2023 11:04:34 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=20counter=20?= =?UTF-8?q?=E5=8C=85=EF=BC=8C=E7=94=A8=E4=BA=8E=E5=88=9B=E5=BB=BA=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E7=89=B9=E5=AE=9A=E6=97=B6=E9=97=B4=E5=86=85=E5=8E=BB?= =?UTF-8?q?=E9=87=8D=E7=9A=84=E8=AE=A1=E6=95=B0=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/counter/counter.go | 162 ++++++++++++++++++++++++++++++++++ utils/counter/counter_test.go | 19 ++++ utils/counter/shadow.go | 53 +++++++++++ 3 files changed, 234 insertions(+) create mode 100644 utils/counter/counter.go create mode 100644 utils/counter/counter_test.go create mode 100644 utils/counter/shadow.go diff --git a/utils/counter/counter.go b/utils/counter/counter.go new file mode 100644 index 0000000..eb18d07 --- /dev/null +++ b/utils/counter/counter.go @@ -0,0 +1,162 @@ +package counter + +import ( + "github.com/kercylan98/minotaur/utils/generic" + "sync" + "time" +) + +// NewCounter 创建一个计数器 +func NewCounter[K comparable, V generic.Number]() *Counter[K, V] { + c := &Counter[K, V]{ + c: make(map[K]any), + dr: make(map[K]int64), + } + return c +} + +// newSubCounter 创建一个子计数器 +func newSubCounter[K comparable, V generic.Number]() *Counter[K, V] { + c := &Counter[K, V]{ + c: make(map[K]any), + dr: make(map[K]int64), + sub: true, + } + return c +} + +// Counter 计数器 +type Counter[K comparable, V generic.Number] struct { + lock sync.RWMutex + sub bool + dr map[K]int64 + c map[K]any +} + +// Add 添加计数 +// - 当设置了 expired 时,在 expired 时间内,不会重复计数 +// - 需要注意的是,当第一次设置了 expired,第二次未设置时,第二次的计数将生效 +func (slf *Counter[K, V]) Add(key K, value V, expired ...time.Duration) { + slf.lock.Lock() + if len(expired) > 0 { + now := time.Now().Unix() + expiredTime, exist := slf.dr[key] + if exist { + if expiredTime > now { + slf.lock.Unlock() + return + } + } + slf.dr[key] = now + int64(expired[0]/time.Second) + } + + v, exist := slf.c[key] + if !exist { + slf.c[key] = value + slf.lock.Unlock() + return + } + if v, ok := v.(V); !ok { + slf.lock.Unlock() + panic("counter value is sub counter") + } else { + slf.c[key] = v + value + } + slf.lock.Unlock() +} + +// Get 获取计数 +func (slf *Counter[K, V]) Get(key K) V { + slf.lock.RLock() + v, exist := slf.c[key] + if !exist { + slf.lock.RUnlock() + return 0 + } + if v, ok := v.(V); !ok { + slf.lock.RUnlock() + panic("counter value is sub counter") + } else { + slf.lock.RUnlock() + return v + } +} + +// Reset 重置特定 key 的计数 +// - 当 key 为一个子计数器时,将会重置该子计数器 +func (slf *Counter[K, V]) Reset(key K) { + slf.lock.Lock() + delete(slf.c, key) + delete(slf.dr, key) + slf.lock.Unlock() +} + +// ResetExpired 重置特定 key 的过期时间 +func (slf *Counter[K, V]) ResetExpired(key K) { + slf.lock.Lock() + delete(slf.dr, key) + slf.lock.Unlock() +} + +// ResetAll 重置所有计数 +func (slf *Counter[K, V]) ResetAll() { + slf.lock.Lock() + clear(slf.c) + clear(slf.dr) + slf.lock.Unlock() +} + +// SubCounter 获取子计数器 +func (slf *Counter[K, V]) SubCounter(key K) *Counter[K, V] { + slf.lock.Lock() + v, exist := slf.c[key] + if !exist { + counter := newSubCounter[K, V]() + slf.c[key] = counter + slf.lock.Unlock() + return counter + } + if v, ok := v.(*Counter[K, V]); !ok { + slf.lock.Unlock() + panic("counter value is count value") + } else { + slf.lock.Unlock() + return v + } +} + +// GetCounts 获取计数器的所有计数 +func (slf *Counter[K, V]) GetCounts() map[K]V { + counts := make(map[K]V) + slf.lock.RLock() + for k, v := range slf.c { + if v, ok := v.(V); !ok { + continue + } else { + counts[k] = v + } + } + slf.lock.RUnlock() + return counts +} + +// GetSubCounters 获取计数器的所有子计数器 +func (slf *Counter[K, V]) GetSubCounters() map[K]*Counter[K, V] { + counters := make(map[K]*Counter[K, V]) + slf.lock.RLock() + for k, v := range slf.c { + if v, ok := v.(*Counter[K, V]); !ok { + continue + } else { + counters[k] = v + } + } + slf.lock.RUnlock() + return counters +} + +// Shadow 获取该计数器的影子计数器,影子计数器的任何操作都不会影响到原计数器 +// - 适用于持久化等场景 +func (slf *Counter[K, V]) Shadow() *Shadow[K, V] { + return newShadow(slf) +} diff --git a/utils/counter/counter_test.go b/utils/counter/counter_test.go new file mode 100644 index 0000000..082c34e --- /dev/null +++ b/utils/counter/counter_test.go @@ -0,0 +1,19 @@ +package counter_test + +import ( + "fmt" + "github.com/kercylan98/minotaur/utils/counter" + "github.com/kercylan98/minotaur/utils/times" + "testing" + "time" +) + +func TestCounter_Add(t *testing.T) { + c := counter.NewCounter[string, int64]() + + c.Add("login_count", 1, times.GetNextDayInterval(time.Now())) + c.Add("login_count", 1, times.GetNextDayInterval(time.Now())) + c.SubCounter("live").Add("login_count", 1, times.GetNextDayInterval(time.Now())) + + fmt.Println(c) +} diff --git a/utils/counter/shadow.go b/utils/counter/shadow.go new file mode 100644 index 0000000..e8bc4ec --- /dev/null +++ b/utils/counter/shadow.go @@ -0,0 +1,53 @@ +package counter + +import ( + "github.com/kercylan98/minotaur/utils/generic" + "github.com/kercylan98/minotaur/utils/hash" +) + +func newShadow[K comparable, V generic.Number](counter *Counter[K, V]) *Shadow[K, V] { + counter.lock.Lock() + shadow := &Shadow[K, V]{ + Sub: counter.sub, + DeduplicationRecord: hash.Copy(counter.dr), + Counter: counter.GetCounts(), + } + for k, c := range counter.GetSubCounters() { + shadow.SubCounters[k] = newShadow(c) + } + counter.lock.Unlock() + return shadow +} + +// Shadow 计数器的影子计数器 +type Shadow[K comparable, V generic.Number] struct { + Sub bool // 是否为子计数器 + DeduplicationRecord map[K]int64 // 最后一次写入时间 + Counter map[K]V // 计数器 + SubCounters map[K]*Shadow[K, V] // 子计数器 +} + +// ToCounter 将影子计数器转换为计数器 +func (slf *Shadow[K, V]) ToCounter() *Counter[K, V] { + return slf.toCounter(nil) +} + +// toCounter 将影子计数器转换为计数器 +func (slf *Shadow[K, V]) toCounter(parent *Counter[K, V]) *Counter[K, V] { + counter := &Counter[K, V]{ + sub: slf.Sub, + c: map[K]any{}, + } + if slf.Sub { + counter.dr = parent.dr + } else { + counter.dr = hash.Copy(slf.DeduplicationRecord) + } + for k, v := range slf.Counter { + counter.c[k] = v + } + for k, s := range slf.SubCounters { + counter.c[k] = s.toCounter(counter) + } + return counter +}