1
0
mirror of https://github.com/charlienet/go-mixed.git synced 2025-07-18 08:32:40 +08:00

52 Commits

Author SHA1 Message Date
38f7cc75c9 set 2024-05-28 04:25:17 +08:00
b4ac1cc449 locker 2024-05-28 04:23:30 +08:00
1abde30d8f locker 2024-05-28 04:17:26 +08:00
822932fe15 use base locker 2024-05-28 04:14:08 +08:00
85c5a611e1 bbb 2024-05-28 04:12:50 +08:00
fe5c0b54b6 locker 2024-05-28 04:08:50 +08:00
54fbe8eb0a locker 2024-05-28 04:05:37 +08:00
2d851d4872 fix error 2024-04-11 10:45:33 +08:00
a83ccf7c00 config 2023-12-13 17:26:23 +08:00
bb979f5ccb config 2023-12-13 17:08:56 +08:00
330d9f78d3 添加锁 2023-11-06 10:09:40 +08:00
5428b530b2 hash function 2023-11-03 16:27:14 +08:00
249d3b4682 delay 2023-11-03 15:48:33 +08:00
bdbf18969e update 2023-11-03 15:48:14 +08:00
01f426c5b2 use redis function(Version 7 and above is required
)
2023-11-03 15:46:45 +08:00
2f2af226ee update redis 2023-11-03 15:04:40 +08:00
5f065de145 redis rename key 2023-11-01 16:32:58 +08:00
f3918dd02b use redis function 2023-10-27 17:33:11 +08:00
1c5a32e828 添加redis function 2023-10-27 16:35:28 +08:00
5a17236fd7 优化redis eval的key重命名 2023-10-26 15:47:33 +08:00
91a5a7d612 update 2023-10-26 14:42:56 +08:00
0d124c0b79 更新ID生成器 2023-10-26 14:42:23 +08:00
6647f96978 rename 2023-10-13 14:38:42 +08:00
d1c269ed90 更新redis 2023-10-13 10:52:36 +08:00
e83db7daee 添加分布式锁实现-使用Redis 2023-10-12 15:25:13 +08:00
69690da6b4 更新库 2023-10-12 15:02:04 +08:00
1203e27c7e add DB 2023-10-12 15:01:16 +08:00
42b5cef555 u 2023-10-12 15:00:23 +08:00
bcfb177fd9 use redis 2023-10-12 14:46:30 +08:00
0df55ed551 使用接口返回 2023-10-12 14:45:50 +08:00
95ad0941a8 update redis client 2023-10-12 14:37:10 +08:00
165fc91f9b 添加订阅 2023-10-12 14:29:42 +08:00
b0a97978d8 update 2023-08-25 15:31:00 +08:00
04aecd4abc new 2022-11-21 16:18:54 +08:00
1f8789e7eb RemoveAt 2022-11-18 17:35:01 +08:00
299e09f8e3 ignore 2022-11-18 16:58:00 +08:00
ea846a321a cache 2022-11-18 16:57:23 +08:00
278c8b4cb7 maps 2022-11-18 16:57:15 +08:00
cf30b4eb4c collection 2022-11-18 16:56:56 +08:00
f3ca69f159 list 2022-11-18 16:56:44 +08:00
bd85140a78 redis 2022-11-18 16:56:12 +08:00
b76be4ce6b update 2022-11-18 16:53:59 +08:00
6e24cf5bdc boom filter 2022-11-18 16:52:43 +08:00
abe445f5e6 list 2022-11-16 17:31:28 +08:00
ac346274c1 update 2022-11-10 10:37:47 +08:00
823cd62148 update 2022-11-09 17:28:42 +08:00
d49c02924c expr 2022-11-09 17:21:30 +08:00
9203c3719f 日历相关计算 2022-11-09 17:16:24 +08:00
132bb0d0e2 add lock 2022-10-10 11:29:07 +08:00
716a199c9b 添加存储 2022-10-10 11:07:59 +08:00
f043d2e5a7 布隆过滤器 2022-09-23 10:22:23 +08:00
5b4f8097d6 update 2022-09-01 12:37:57 +08:00
159 changed files with 6414 additions and 786 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
*.o *.o
fs/logs/** fs/logs/**
.idea/**

View File

@ -1,54 +1,121 @@
package bloom package bloom
import "github.com/bits-and-blooms/bitset" import (
"context"
"math"
"github.com/charlienet/go-mixed/bytesconv"
"github.com/charlienet/go-mixed/hash"
"github.com/charlienet/go-mixed/redis"
)
const DEFAULT_SIZE = 2 << 24 const DEFAULT_SIZE = 2 << 24
var seeds = []uint{7, 11, 13, 31, 37, 61} var seeds = []uint{7, 11, 13, 31, 37, 61, 79, 97}
type simplehash struct { type bitStore interface {
cap uint Clear()
seed uint Set(ctx context.Context, pos ...uint) error
Test(pos ...uint) (bool, error)
} }
type BloomFilter struct { type BloomFilter struct {
set *bitset.BitSet bits uint // 布隆过滤器大小
funcs [6]simplehash funcs uint // 哈希函数数量
store bitStore // 位图存储
} }
func NewBloomFilter() *BloomFilter { type bloomOptions struct {
bf := new(BloomFilter) redisClient redis.Client
for i := 0; i < len(bf.funcs); i++ { redisKey string
bf.funcs[i] = simplehash{DEFAULT_SIZE, seeds[i]} }
type option func(*bloomOptions)
func WithRedis(redis redis.Client, key string) option {
return func(bo *bloomOptions) {
bo.redisClient = redis
bo.redisKey = key
} }
bf.set = bitset.New(DEFAULT_SIZE) }
// New 初始化布隆过滤器
// https://pages.cs.wisc.edu/~cao/papers/summary-cache/node8.html
func New(expectedInsertions uint, fpp float64, opts ...option) *BloomFilter {
opt := &bloomOptions{}
for _, f := range opts {
f(opt)
}
bits := optimalNumOfBits(expectedInsertions, fpp)
k := optimalNumOfHashFunctions(bits, expectedInsertions)
if k > uint(len(seeds)) {
k = uint(len(seeds))
}
bf := &BloomFilter{
bits: bits,
funcs: k,
store: createBitStore(opt, bits),
}
return bf return bf
} }
func (bf *BloomFilter) Add(value string) { func (bf *BloomFilter) Add(ctx context.Context, data string) {
funcs := bf.funcs[:] offsets := bf.geOffsets([]byte(data))
for _, f := range funcs { bf.store.Set(ctx, offsets...)
bf.set.Set(f.hash(value))
}
} }
func (bf *BloomFilter) Contains(value string) bool { func (bf *BloomFilter) ExistString(data string) (bool, error) {
if value == "" { return bf.Exists(bytesconv.StringToBytes(data))
return false
}
ret := true
funcs := bf.funcs[:]
for _, f := range funcs {
ret = ret && bf.set.Test(f.hash(value))
}
return ret
} }
func (s simplehash) hash(value string) uint { func (bf *BloomFilter) Exists(data []byte) (bool, error) {
var result uint = 0 if len(data) == 0 {
for i := 0; i < len(value); i++ { return false, nil
result = result*s.seed + uint(value[i])
} }
return (s.cap - 1) & result
offsets := bf.geOffsets(data)
isSet, err := bf.store.Test(offsets...)
if err != nil {
return false, err
}
return isSet, nil
}
func (bf *BloomFilter) geOffsets(data []byte) []uint {
offsets := make([]uint, bf.funcs)
for i := uint(0); i < bf.funcs; i++ {
offsets[i] = uint(hash.Murmur3(append(data, byte(seeds[i]))) % uint64(bf.bits))
}
return offsets
}
// 清空布隆过滤器
func (bf *BloomFilter) Clear() {
bf.store.Clear()
}
func createBitStore(opt *bloomOptions, bits uint) bitStore {
if opt.redisClient != nil {
return newRedisStore(opt.redisClient, opt.redisKey, bits)
}
return newMemStore(bits)
}
// 计算优化的位图长度,
// n 期望放置元素数量,
// p 预期的误判概率
func optimalNumOfBits(n uint, p float64) uint {
return (uint)(-float64(n) * math.Log(p) / (math.Log(2) * math.Log(2)))
}
// 计算哈希函数数量
func optimalNumOfHashFunctions(m, n uint) uint {
return uint(math.Round(float64(m) / float64(n) * math.Log(2)))
} }

20
bloom/bloom.lua Normal file
View File

@ -0,0 +1,20 @@
#!lua name=charlie_bloom
local function set_bit(keys, args)
for _, offset in ipairs(args) do
redis.call("setbit", keys[1], offset, 1)
end
end
local function test_bit(keys, args)
for _, offset in ipairs(args) do
if tonumber(redis.call("getbit", keys[1], offset)) == 0 then
return false
end
end
return true
end
redis.register_function('set_bit',set_bit)
redis.register_function('test_bit',test_bit)

View File

@ -1,20 +1,125 @@
package bloom_test package bloom_test
import ( import (
"context"
"fmt" "fmt"
"math"
"strconv" "strconv"
"testing" "testing"
"github.com/charlienet/go-mixed/bloom" "github.com/charlienet/go-mixed/bloom"
"github.com/charlienet/go-mixed/rand"
"github.com/charlienet/go-mixed/redis"
"github.com/charlienet/go-mixed/sys"
"github.com/stretchr/testify/assert"
) )
const ()
func TestBloom(t *testing.T) { func TestBloom(t *testing.T) {
b := bloom.NewBloomFilter() b := bloom.New(1000, 0.03)
for i := 0; i < 1000000; i++ { for i := 0; i < 1000000; i++ {
b.Add(strconv.Itoa(i)) b.Add(context.Background(), strconv.Itoa(i))
} }
fmt.Println(b.Contains(strconv.Itoa(9999))) v := "6943553521463296-1635402930"
fmt.Println(b.Contains("ss"))
t.Log(b.ExistString(v))
b.Add(context.Background(), v)
t.Log(b.ExistString(v))
isSet, err := b.ExistString(strconv.Itoa(9999))
fmt.Println("过滤器中包含值:", isSet, err)
isSet, err = b.ExistString("ss")
fmt.Println("过滤器中未包含:", isSet, err)
t.Log(sys.ShowMemUsage())
}
func TestOptimize(t *testing.T) {
expectedInsertions := 1000000 // 期望存储数据量
falseProbability := 0.00002 // 预期误差
bits := uint(float64(-expectedInsertions) * math.Log(falseProbability) / (math.Log(2) * math.Log(2)))
hashSize := uint(math.Round(float64(bits) / float64(expectedInsertions) * math.Log(2)))
t.Log(bits)
t.Log(hashSize)
}
func TestRedis(t *testing.T) {
client := redis.New(&redis.RedisOption{
Addrs: []string{"192.168.2.222:6379"},
Password: "123456",
})
bf := bloom.New(10000, 0.03, bloom.WithRedis(client, "bloom:test"))
for i := 0; i < 100; i++ {
bf.Add(context.Background(), strconv.Itoa(i))
}
for i := 0; i < 100; i++ {
isSet, err := bf.ExistString(strconv.Itoa(i))
if err != nil {
t.Fatal(err)
}
if !isSet {
t.Log(i, isSet)
}
}
for i := 101; i < 200; i++ {
isSet, err := bf.ExistString(strconv.Itoa(i))
t.Log(isSet, err)
}
}
func TestClear(t *testing.T) {
bf := bloom.New(1000, 0.03)
v := "abc"
bf.Add(context.Background(), v)
isSet, _ := bf.ExistString(v)
assert.True(t, isSet)
bf.Clear()
isSet, _ = bf.ExistString(v)
assert.False(t, isSet)
}
func TestParallel(t *testing.T) {
f := bloom.New(1000, 0.03)
for i := 0; i < 10000; i++ {
v := rand.Hex.Generate(10)
f.Add(context.Background(), v)
isSet, _ := f.ExistString(v)
assert.True(t, isSet)
}
}
func BenchmarkFilter(b *testing.B) {
f := bloom.New(1000, 0.03)
b.RunParallel(func(p *testing.PB) {
for p.Next() {
v := rand.Hex.Generate(10)
f.Add(context.Background(), v)
f.ExistString(v)
// assert.True(b, f.Contains(v))
// assert.True(b, f.Contains(v))
}
})
} }

52
bloom/mem_store.go Normal file
View File

@ -0,0 +1,52 @@
package bloom
import (
"context"
"sync"
"github.com/bits-and-blooms/bitset"
)
type memStore struct {
size uint
set *bitset.BitSet // 内存位图
lock sync.RWMutex // 同步锁
}
func newMemStore(size uint) *memStore {
return &memStore{
size: size,
set: bitset.New(size),
}
}
func (s *memStore) Clear() {
s.lock.Lock()
defer s.lock.Unlock()
s.set.ClearAll()
}
func (s *memStore) Set(ctx context.Context, offsets ...uint) error {
s.lock.Lock()
defer s.lock.Unlock()
for _, p := range offsets {
s.set.Set(p)
}
return nil
}
func (s *memStore) Test(offsets ...uint) (bool, error) {
s.lock.RLock()
defer s.lock.RUnlock()
for _, p := range offsets {
if !s.set.Test(p) {
return false, nil
}
}
return true, nil
}

100
bloom/redis_store.go Normal file
View File

@ -0,0 +1,100 @@
package bloom
import (
"context"
"errors"
"strconv"
"sync"
"time"
_ "embed"
"github.com/charlienet/go-mixed/redis"
)
//go:embed bloom.lua
var redis_bloom_function string
var once sync.Once
var ErrTooLargeOffset = errors.New("超出最大偏移量")
var _ bitStore = &redisBitSet{}
// 使用Redis存储位图
type redisBitSet struct {
store redis.Client
key string
bits uint
}
func newRedisStore(store redis.Client, key string, bits uint) *redisBitSet {
once.Do(func() { store.LoadFunction(redis_bloom_function) })
return &redisBitSet{
store: store,
key: key,
bits: bits,
}
}
func (s *redisBitSet) Set(ctx context.Context, offsets ...uint) error {
args, err := s.buildOffsetArgs(offsets)
if err != nil {
return err
}
_, err = s.store.FCall(ctx, "set_bit", []string{s.key}, args...).Result()
//底层使用的是go-redis,redis.Nil表示操作的key不存在
//需要针对key不存在的情况特殊判断
if err == redis.Nil {
return nil
} else if err != nil {
return err
}
return nil
}
func (s *redisBitSet) Test(offsets ...uint) (bool, error) {
args, err := s.buildOffsetArgs(offsets)
if err != nil {
return false, err
}
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*500)
defer cancel()
resp, err := s.store.FCall(ctx, "test_bit", []string{s.key}, args...).Result()
// key 不存在,表示还未存放任何数据
if err == redis.Nil {
return false, nil
} else if err != nil {
return false, err
}
exists, ok := resp.(int64)
if !ok {
return false, nil
}
return exists == 1, nil
}
func (s *redisBitSet) Clear() {
}
func (r *redisBitSet) buildOffsetArgs(offsets []uint) ([]any, error) {
args := make([]any, 0, len(offsets))
for _, offset := range offsets {
if offset >= r.bits {
return nil, ErrTooLargeOffset
}
args = append(args, strconv.FormatUint(uint64(offset), 10))
}
return args, nil
}

28
bloom/redis_store_test.go Normal file
View File

@ -0,0 +1,28 @@
package bloom
import (
"context"
"testing"
"time"
"github.com/charlienet/go-mixed/redis"
"github.com/charlienet/go-mixed/tests"
)
func TestRedisStore(t *testing.T) {
tests.RunOnDefaultRedis(t, func(client redis.Client) {
store := newRedisStore(client, "abcdef", 10000)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
err := store.Set(ctx, 1, 2, 3, 9, 1223)
if err != nil {
t.Fatal(err)
}
t.Log(store.Test(1))
t.Log(store.Test(1, 2, 3))
t.Log(store.Test(4, 5, 8))
})
}

View File

@ -5,10 +5,29 @@ import (
"encoding/hex" "encoding/hex"
) )
const hextable = "0123456789ABCDEF" const hexTable = "0123456789ABCDEF"
type BytesResult []byte type BytesResult []byte
func FromString(s string) BytesResult {
return (BytesResult)([]byte(s))
}
func FromBytes(b []byte) BytesResult {
return BytesResult(b)
}
// FromHexString 从十六进制获取
func FromHexString(s string) (BytesResult, error) {
b, err := hex.DecodeString(s)
return BytesResult(b), err
}
func FromBase64String(s string) (BytesResult, error) {
b, err := base64.StdEncoding.DecodeString(s)
return BytesResult(b), err
}
func (r BytesResult) Hex() string { func (r BytesResult) Hex() string {
return hex.EncodeToString(r) return hex.EncodeToString(r)
} }
@ -19,8 +38,8 @@ func (r BytesResult) UppercaseHex() string {
re := r[:] re := r[:]
for _, v := range re { for _, v := range re {
dst[j] = hextable[v>>4] dst[j] = hexTable[v>>4]
dst[j+1] = hextable[v&0x0f] dst[j+1] = hexTable[v&0x0f]
j += 2 j += 2
} }

View File

@ -5,6 +5,8 @@ import (
"github.com/charlienet/go-mixed/bytesconv" "github.com/charlienet/go-mixed/bytesconv"
"github.com/charlienet/go-mixed/rand" "github.com/charlienet/go-mixed/rand"
"golang.org/x/net/html/charset"
"golang.org/x/text/encoding/unicode"
) )
func TestHexUppercase(t *testing.T) { func TestHexUppercase(t *testing.T) {
@ -16,3 +18,20 @@ func TestHexUppercase(t *testing.T) {
u := bytesconv.BytesResult(b).UppercaseHex() u := bytesconv.BytesResult(b).UppercaseHex()
t.Log(u) t.Log(u)
} }
func TestHexToBase64(t *testing.T) {
v := "abc"
unicode.UTF16(unicode.BigEndian, unicode.IgnoreBOM)
t.Log(bytesconv.FromString(v).Base64())
b, _ := rand.RandBytes(43)
t.Log(bytesconv.FromBytes(b).Base64())
c, n := charset.Lookup("utf8")
t.Log(c, n)
}

13
bytesconv/readme.md Normal file
View File

@ -0,0 +1,13 @@
功能列表
1. 无内存消耗的字符串转字节数组,字节数组转字符串
StringToBytes、BytesToString
2. 字节数组转换函数
ByteResult
From
To
3. 对象的二进制序列化和反序列化
Encode、Decode

View File

@ -20,3 +20,8 @@ func Decode(b []byte, out any) error {
dec := gob.NewDecoder(buf) dec := gob.NewDecoder(buf)
return dec.Decode(out) return dec.Decode(out)
} }
func MsgPackage() {
// msgpack.NewEncoder()
}

View File

@ -1,8 +1,10 @@
package bytesconv package bytesconv
import ( import (
"encoding/hex"
"encoding/json" "encoding/json"
"testing" "testing"
"time"
) )
type SimpleUser struct { type SimpleUser struct {
@ -25,3 +27,18 @@ func TestGob(t *testing.T) {
t.Logf("%+v", u2) t.Logf("%+v", u2)
} }
type delayTask struct {
message string
delay time.Time
execute func()
}
func TestMarshal(t *testing.T) {
d := delayTask{
message: "sssssssss",
}
b, err := Encode(d)
t.Log(hex.EncodeToString(b), err)
}

View File

@ -1,4 +1,4 @@
package cache package bigcache
import ( import (
"errors" "errors"
@ -8,8 +8,6 @@ import (
"github.com/charlienet/go-mixed/logx" "github.com/charlienet/go-mixed/logx"
) )
var _ MemCache = &bigCacheClient{}
type BigCacheConfig struct { type BigCacheConfig struct {
Shards int Shards int
LifeWindow time.Duration LifeWindow time.Duration
@ -49,8 +47,13 @@ func NewBigCache(c BigCacheConfig) (*bigCacheClient, error) {
}, nil }, nil
} }
func (c *bigCacheClient) Get(key string) ([]byte, error) { func (c *bigCacheClient) Get(key string) ([]byte, bool) {
return c.cache.Get(key) b, err := c.cache.Get(key)
if err == nil {
return b, false
}
return b, true
} }
func (c *bigCacheClient) Set(key string, entry []byte, expire time.Duration) error { func (c *bigCacheClient) Set(key string, entry []byte, expire time.Duration) error {
@ -68,9 +71,23 @@ func (c *bigCacheClient) Delete(keys ...string) error {
return nil return nil
} }
func (c *bigCacheClient) Exist(key string) { func (c *bigCacheClient) Exist(key string) bool {
_, err := c.cache.Get(key)
if err == nil {
return true
}
return !errors.Is(err, bigcache.ErrEntryNotFound)
}
func (c *bigCacheClient) Clear() {
} }
func (c *bigCacheClient) IsNotFound(err error) bool { func (c *bigCacheClient) IsNotFound(err error) bool {
return errors.Is(err, bigcache.ErrEntryNotFound) if err == nil {
return true
}
return !errors.Is(err, bigcache.ErrEntryNotFound)
} }

27
cache/bigcache/big_cache_test.go vendored Normal file
View File

@ -0,0 +1,27 @@
package bigcache
import (
"testing"
"time"
"github.com/stretchr/testify/require"
)
func TestBigCache(t *testing.T) {
r := require.New(t)
c, err := NewBigCache(BigCacheConfig{})
r.Nil(err)
cacheKey := "a"
cacheValue := "bbb"
c.Set(cacheKey, []byte(cacheValue), time.Second*5)
r.True(c.Exist(cacheKey))
r.False(c.Exist("abb"))
b, ok := c.Get(cacheKey)
r.True(ok)
r.Equal(cacheValue, string(b))
}

106
cache/cache.go vendored
View File

@ -6,60 +6,73 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/charlienet/go-mixed/bytesconv"
"github.com/charlienet/go-mixed/locker" "github.com/charlienet/go-mixed/locker"
"github.com/charlienet/go-mixed/logx" "github.com/charlienet/go-mixed/logx"
"golang.org/x/sync/singleflight"
) )
var ErrNotFound = errors.New("key not found") var ErrNotFound = errors.New("key not found")
// 数据加载函数定义
type LoadFunc func(context.Context) (any, error) type LoadFunc func(context.Context) (any, error)
type ICache interface {
}
type Cache struct { type Cache struct {
prefix string // 键前缀 prefix string // 键前缀
retry int // 资源获取时的重试次数 retry int // 资源获取时的重试次数
mem MemCache // 内存缓存 mem MemCache // 内存缓存
distributdCache DistributdCache // 分布式缓存 rds DistributedCache // 远程缓存
publishSubscribe PublishSubscribe // 发布订阅 publishSubscribe PublishSubscribe // 发布订阅
group singleflight.Group // singleflight.Group
lock locker.ChanLocker // 资源锁 lock locker.ChanLocker // 资源锁
stats *Stats // 缓存命中计数
qps *qps // 访问计数 qps *qps // 访问计数
logger logx.Logger // 日志记录 logger logx.Logger // 日志记录
} }
func NewCache(opts ...option) *Cache { func New(opts ...option) (*Cache, error) {
c := acquireDefaultCache() c := acquireDefaultCache()
for _, f := range opts { for _, f := range opts {
if err := f(c); err != nil { if err := f(c); err != nil {
return c return c, nil
} }
} }
go c.subscribe() // 未设置内存缓存时,添加默认缓存
if c.mem == nil {
c.mem = NewTinyLFU(1<<12, time.Second*30)
}
return c return c, nil
} }
func (c *Cache) Set(key string, value any, expiration time.Duration) error { func (c *Cache) Set(ctx context.Context, key string, value any, expiration time.Duration) error {
if c.mem != nil { buf, err := Marshal(value)
bytes, err := bytesconv.Encode(value)
if err != nil { if err != nil {
return err return err
} }
c.mem.Set(key, bytes, expiration) if c.mem != nil {
c.mem.Set(key, buf, expiration)
}
if c.rds != nil {
c.rds.Set(ctx, key, buf, expiration)
} }
return nil return nil
} }
func (c *Cache) Get(key string, out any) error { func (c *Cache) Get(ctx context.Context, key string, out any) error {
if c.mem != nil { if c.mem != nil {
c.getFromMem(key, out) c.getFromMem(key)
} }
if c.distributdCache != nil { if c.rds != nil {
if err := c.distributdCache.Get(key, out); err != nil { if err := c.rds.Get(ctx, key, out); err != nil {
} }
} }
@ -68,7 +81,7 @@ func (c *Cache) Get(key string, out any) error {
} }
func (c *Cache) GetFn(ctx context.Context, key string, out any, fn LoadFunc, expiration time.Duration) (bool, error) { func (c *Cache) GetFn(ctx context.Context, key string, out any, fn LoadFunc, expiration time.Duration) (bool, error) {
c.Get(key, out) c.Get(ctx, key, out)
// 多级缓存中未找到时,放置缓存对象 // 多级缓存中未找到时,放置缓存对象
ret, err := fn(ctx) ret, err := fn(ctx)
@ -76,7 +89,7 @@ func (c *Cache) GetFn(ctx context.Context, key string, out any, fn LoadFunc, exp
return false, err return false, err
} }
c.Set(key, ret, expiration) c.Set(ctx, key, ret, expiration)
return false, nil return false, nil
} }
@ -85,32 +98,69 @@ func (c *Cache) Exist(key string) (bool, error) {
return false, nil return false, nil
} }
func (c *Cache) Delete(key ...string) error { func (c *Cache) Delete(ctx context.Context, key ...string) error {
if c.mem != nil { if c.mem != nil {
c.mem.Delete(key...) c.mem.Delete(key...)
} }
if c.distributdCache != nil { if c.rds != nil {
c.distributdCache.Delete(key...) c.rds.Delete(ctx, key...)
}
for _, k := range key {
c.group.Forget(k)
} }
return nil return nil
} }
func (c *Cache) subscribe() { // 清除本地缓存
func (c *Cache) ClearMem() {
if c.mem != nil {
c.mem.Clear()
}
} }
func (c *Cache) getFromMem(key string, out any) error { func (c *Cache) Clear() {
bytes, err := c.mem.Get(key)
if err != nil { }
return err
func (c *Cache) Disable() {
}
func (c *Cache) Enable() {
}
func (c *Cache) getOnce(ctx context.Context, key string) (b []byte, cached bool, err error) {
if c.mem != nil {
b, ok := c.mem.Get(key)
if ok {
return b, true, nil
}
}
c.group.Do(key, func() (any, error) {
if c.mem != nil {
b, ok := c.mem.Get(key)
if ok {
return b, nil
}
} }
if err := bytesconv.Decode(bytes, out); err != nil { if c.rds != nil {
return err c.rds.Get(ctx, key, nil)
} }
return nil return nil, nil
})
return
}
func (c *Cache) getFromMem(key string) ([]byte, bool) {
bytes, cached := c.mem.Get(key)
return bytes, cached
} }
// 从缓存加载数据 // 从缓存加载数据
@ -123,8 +173,6 @@ func (c *Cache) getFromCache() {
// 从数据源加载数据 // 从数据源加载数据
func (c *Cache) getFromSource(ctx context.Context, key string, fn LoadFunc) error { func (c *Cache) getFromSource(ctx context.Context, key string, fn LoadFunc) error {
// 1. 尝试获取资源锁,如成功获取到锁加载数据
// 2. 未获取到锁,等待从缓存中获取
ch, ok := c.lock.Get(key) ch, ok := c.lock.Get(key)
if ok { if ok {
defer c.lock.Release(key) defer c.lock.Release(key)

123
cache/cache_builder.go vendored
View File

@ -1,6 +1,14 @@
package cache package cache
import "github.com/charlienet/go-mixed/logx" import (
"context"
"time"
"github.com/charlienet/go-mixed/cache/bigcache"
"github.com/charlienet/go-mixed/cache/freecache"
"github.com/charlienet/go-mixed/logx"
"github.com/charlienet/go-mixed/redis"
)
const defaultPrefix = "cache" const defaultPrefix = "cache"
@ -10,90 +18,59 @@ type options struct {
Prefix string Prefix string
} }
func acquireDefaultCache() *Cache { func WithRedis(rdb redis.Client) option {
return &Cache{ return func(c *Cache) error {
prefix: defaultPrefix, rds := NewRedis(rdb)
qps: NewQps(), c.rds = rds
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
defer cancel()
return rds.Ping(ctx)
} }
} }
type cacheBuilder struct { func WithBigCache(opts bigcache.BigCacheConfig) option {
prefix string return func(c *Cache) error {
redisOptions RedisConfig mem, err := bigcache.NewBigCache(opts)
bigCacheConfig BigCacheConfig
freeSize int c.mem = mem
publishSubscribe PublishSubscribe return err
log logx.Logger }
} }
func NewCacheBuilder() *cacheBuilder { func WithFreeCache(size int) option {
return &cacheBuilder{} return func(c *Cache) error {
} mem := freecache.NewFreeCache(size)
c.mem = mem
func (b *cacheBuilder) WithLogger(log logx.Logger) *cacheBuilder { return nil
b.log = log }
return b
}
func (b *cacheBuilder) WithPrefix(prefix string) *cacheBuilder {
b.prefix = prefix
return b
}
func (b *cacheBuilder) WithRedis(opts RedisConfig) *cacheBuilder {
b.redisOptions = opts
return b
}
func (b *cacheBuilder) WithBigCache(opts BigCacheConfig) *cacheBuilder {
b.bigCacheConfig = opts
return b
}
func (b *cacheBuilder) WithFreeCache(size int) *cacheBuilder {
b.freeSize = size
return b
} }
// 使用自定义分布式缓存 // 使用自定义分布式缓存
func WithDistributedCache(c DistributdCache) { func WithDistributedCache(rds DistributedCache) option {
return func(c *Cache) error {
c.rds = rds
return nil
}
} }
func (b *cacheBuilder) WithPublishSubscribe(p PublishSubscribe) *cacheBuilder { func WithPublishSubscribe(p PublishSubscribe) option {
b.publishSubscribe = p return func(c *Cache) error {
return b return nil
}
} }
func (b cacheBuilder) Build() (*Cache, error) { func WithLogger(log logx.Logger) option {
var err error return func(c *Cache) error {
cache := acquireDefaultCache() c.logger = log
if len(b.prefix) > 0 {
cache.prefix = b.prefix return nil
}
}
func acquireDefaultCache() *Cache {
return &Cache{
qps: NewQps(),
} }
b.redisOptions.Prefix = cache.prefix
redis := NewRedis(b.redisOptions)
if err := redis.Ping(); err != nil {
return cache, err
}
var mem MemCache
if b.freeSize > 0 {
mem = NewFreeCache(b.freeSize)
} else {
if b.log != nil {
b.bigCacheConfig.log = b.log
}
mem, err = NewBigCache(b.bigCacheConfig)
}
cache.distributdCache = redis
cache.mem = mem
cache.publishSubscribe = b.publishSubscribe
cache.logger = b.log
return cache, err
} }

View File

@ -3,27 +3,38 @@ package cache
import ( import (
"testing" "testing"
"time" "time"
"github.com/charlienet/go-mixed/logx"
) )
func TestBuilder(t *testing.T) { func TestBuilder(t *testing.T) {
cache, err := NewCacheBuilder(). now := time.Now()
WithLogger(logx.NewLogrus(logx.WithFormatter(logx.NewNestedFormatter(logx.NestedFormatterOption{ t.Log(now)
Color: true, t1, _ := time.ParseDuration("9h27m")
})))). t1 += time.Hour * 24
WithRedis(RedisConfig{ t2, _ := time.ParseDuration("16h28m")
Addrs: []string{"192.168.2.222:6379"}, t.Log(t1)
Password: "123456", t.Log(t2)
}).
WithBigCache(BigCacheConfig{}).
// WithFreeCache(10 * 1024 * 1024).
Build()
if err != nil { f := time.Date(2022, time.December, 12, 8, 0, 0, 0, time.Local)
t.Fatal(err) t.Log(f.Sub(time.Now()))
}
u := SimpleUser{FirstName: "Radomir", LastName: "Sohlich"} // cache, err := New(
t.Log(cache.Set(defaultKey, u, time.Minute*10)) // WithLogger(logx.NewLogrus(logx.WithNestedFormatter(logx.NestedFormatterOption{
// Color: true,
// }))).
// UseRedis(RedisConfig{
// Addrs: []string{"192.168.2.222:6379"},
// Password: "123456",
// }).
// UseBigCache(bigcache.BigCacheConfig{}).
// Build()
// if err != nil {
// t.Fatal(err)
// }
// ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
// defer cancel()
// u := SimpleUser{FirstName: "Radomir", LastName: "Sohlich"}
// t.Log(cache.Set(ctx, defaultKey, u, time.Minute*10))
} }

12
cache/cache_preload.go vendored Normal file
View File

@ -0,0 +1,12 @@
package cache
import "context"
// PreLoadItem 预加载数据项
type PreLoadItem struct {
Key string
Value any
}
// PreloadFunc 数据预加载函数定义
type PreloadFunc func(context.Context) ([]PreLoadItem, error)

93
cache/cache_test.go vendored
View File

@ -7,8 +7,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/charlienet/go-mixed/bytesconv" "github.com/charlienet/go-mixed/redis"
"github.com/charlienet/go-mixed/logx"
) )
var ( var (
@ -16,25 +15,26 @@ var (
) )
func TestNewCache(t *testing.T) { func TestNewCache(t *testing.T) {
c, err := NewCacheBuilder(). // c, err := NewCacheBuilder().
WithRedis(RedisConfig{ // UseRedis(RedisConfig{
Addrs: []string{"192.168.2.222:6379"}, // Addrs: []string{"192.168.2.222:6379"},
Password: "123456", // Password: "123456",
}). // }).
WithPrefix("cache_test"). // WithPrefix("cache_test").
WithLogger(logx.NewLogrus()). // WithLogger(logx.NewLogrus()).
Build() // Build()
if err != nil { // if err != nil {
t.Fatal(err) // t.Fatal(err)
} // }
c.Set("abc", "value", time.Minute*10) // ctx := context.Background()
// c.Set(ctx, "abc", "value", time.Minute*10)
var s string // var s string
c.Get("abc", &s) // c.Get(ctx, "abc", &s)
t.Log(s) // t.Log(s)
} }
type SimpleUser struct { type SimpleUser struct {
@ -43,45 +43,45 @@ type SimpleUser struct {
} }
func TestMemCache(t *testing.T) { func TestMemCache(t *testing.T) {
b, _ := NewBigCache(BigCacheConfig{}) // b, _ := bigcache.NewBigCache(bigcache.BigCacheConfig{})
var mems = []MemCache{ // var mems = []MemCache{
NewFreeCache(10 * 1024 * 1024), // NewFreeCache(10 * 1024 * 1024),
b, // b,
} // }
u := SimpleUser{FirstName: "Radomir", LastName: "Sohlich"} // u := SimpleUser{FirstName: "Radomir", LastName: "Sohlich"}
encoded, _ := bytesconv.Encode(u) // encoded, _ := bytesconv.Encode(u)
for _, m := range mems { // for _, m := range mems {
m.Set(defaultKey, encoded, time.Second) // m.Set(defaultKey, encoded, time.Second)
ret, err := m.Get(defaultKey) // ret, _ := m.Get(defaultKey)
if err != nil {
t.Fatal(err)
}
var u2 SimpleUser // var u2 SimpleUser
bytesconv.Decode(ret, &u2) // bytesconv.Decode(ret, &u2)
t.Log(u2) // t.Log(u2)
} // }
} }
func TestDistributedCache(t *testing.T) { func TestDistributedCache(t *testing.T) {
c := NewRedis(RedisConfig{Addrs: []string{"192.168.2.222:6379"}, DB: 6, Password: "123456", Prefix: "abcdef"}) c := NewRedis(redis.New(&redis.ReidsOption{
Addrs: []string{"192.168.2.222:6379"}, DB: 6, Password: "123456", Prefix: "abcdef",
}))
if err := c.Ping(); err != nil { ctx := context.Background()
if err := c.Ping(ctx); err != nil {
t.Fatal(err) t.Fatal(err)
} }
t.Log(c.Exist(defaultKey)) t.Log(c.Exist(ctx, defaultKey))
u := SimpleUser{FirstName: "redis client"} u := SimpleUser{FirstName: "redis client"}
var u2 SimpleUser var u2 SimpleUser
c.Get(defaultKey, &u2) c.Get(ctx, defaultKey, &u2)
c.Set(defaultKey, u, time.Minute*10) c.Set(ctx, defaultKey, u, time.Minute*10)
t.Log(c.Exist(defaultKey)) t.Log(c.Exist(ctx, defaultKey))
if err := c.Get(defaultKey, &u2); err != nil { if err := c.Get(ctx, defaultKey, &u2); err != nil {
t.Fatal("err:", err) t.Fatal("err:", err)
} }
t.Logf("%+v", u2) t.Logf("%+v", u2)
@ -136,10 +136,13 @@ func load() (any, error) {
} }
func buildCache() *Cache { func buildCache() *Cache {
c, err := NewCacheBuilder(). c, err := New(
WithFreeCache(10 * 1024 * 1024). WithFreeCache(10*1024*1024),
WithRedis(RedisConfig{Addrs: []string{"192.168.2.222:6379"}, DB: 6, Password: "123456"}). WithRedis(redis.New(&redis.ReidsOption{
Build() Addrs: []string{"192.168.2.222:6379"},
DB: 6,
Password: "123456",
})))
if err != nil { if err != nil {
panic(err) panic(err)

View File

@ -1,10 +0,0 @@
package cache
import "time"
type DistributdCache interface {
Get(key string, out any) error
Set(key string, value any, expiration time.Duration) error
Delete(key ...string) error
Ping() error
}

14
cache/distributed_cache.go vendored Normal file
View File

@ -0,0 +1,14 @@
package cache
import (
"context"
"time"
)
// 分布式缓存接口
type DistributedCache interface {
Get(ctx context.Context, key string, out any) error
Set(ctx context.Context, key string, value any, expiration time.Duration) error
Delete(ctx context.Context, key ...string) error
Ping(ctx context.Context) error
}

10
cache/empty_cache_adaper.go vendored Normal file
View File

@ -0,0 +1,10 @@
package cache
import "context"
// var emptyCache DistributedCache = &emptyCacheAdapter{}
type emptyCacheAdapter struct {
}
func (*emptyCacheAdapter) Delete(ctx context.Context, keys ...string) {}

View File

@ -1,4 +1,4 @@
package cache package freecache
import ( import (
"errors" "errors"
@ -10,8 +10,6 @@ import (
const defaultSize = 10 * 1024 * 1024 // 10M const defaultSize = 10 * 1024 * 1024 // 10M
var _ MemCache = &freeCache{}
type freeCache struct { type freeCache struct {
cache *freecache.Cache cache *freecache.Cache
} }
@ -29,8 +27,12 @@ func NewFreeCache(size int) *freeCache {
} }
} }
func (c *freeCache) Get(key string) ([]byte, error) { func (c *freeCache) Get(key string) ([]byte, bool) {
return c.cache.Get([]byte(key)) b, err := c.cache.Get([]byte(key))
if err != nil {
return b, false
}
return b, true
} }
func (c *freeCache) Set(key string, value []byte, d time.Duration) error { func (c *freeCache) Set(key string, value []byte, d time.Duration) error {
@ -54,6 +56,10 @@ func (c *freeCache) Exist(key string) error {
return nil return nil
} }
func (c *freeCache) Clear() {
}
func (c *freeCache) IsNotFound(err error) bool { func (c *freeCache) IsNotFound(err error) bool {
return errors.Is(err, freecache.ErrNotFound) return errors.Is(err, freecache.ErrNotFound)
} }

1
cache/freecache/free_cache_test.go vendored Normal file
View File

@ -0,0 +1 @@
package freecache

2
cache/lru.go vendored Normal file
View File

@ -0,0 +1,2 @@
package cache

5
cache/mem.go vendored
View File

@ -3,7 +3,8 @@ package cache
import "time" import "time"
type MemCache interface { type MemCache interface {
Get(key string) ([]byte, error) Get(key string) ([]byte, bool)
Set(key string, entry []byte, expire time.Duration) error Set(key string, b []byte, expire time.Duration) error
Delete(key ...string) error Delete(key ...string) error
Clear()
} }

43
cache/msg_pack.go vendored Normal file
View File

@ -0,0 +1,43 @@
package cache
import (
"github.com/vmihailenco/msgpack/v5"
)
func Marshal(v any) ([]byte, error) {
switch v := v.(type) {
case nil:
return nil, nil
case []byte:
return v, nil
case string:
return []byte(v), nil
}
b, err := msgpack.Marshal(v)
if err != nil {
return nil, err
}
return b, err
}
func Unmarshal(b []byte, v any) error {
if len(b) == 0 {
return nil
}
switch v := v.(type) {
case nil:
return nil
case *[]byte:
clone := make([]byte, len(b))
copy(clone, b)
*v = clone
case *string:
*v = string(b)
return nil
}
return msgpack.Unmarshal(b, v)
}

6
cache/qps.go vendored
View File

@ -31,7 +31,9 @@ func (q *qps) statisticsTotal() {
} }
}() }()
ticker := time.NewTicker(time.Second) ticker := time.NewTicker(time.Minute)
defer ticker.Stop()
for range ticker.C { for range ticker.C {
q.all.viewTotal = atomic.SwapInt64(&q.all.total, 0) q.all.viewTotal = atomic.SwapInt64(&q.all.total, 0)
q.memoryTotal.viewTotal = atomic.SwapInt64(&q.memoryTotal.total, 0) q.memoryTotal.viewTotal = atomic.SwapInt64(&q.memoryTotal.total, 0)
@ -39,5 +41,7 @@ func (q *qps) statisticsTotal() {
q.redisTotal.viewTotal = atomic.SwapInt64(&q.redisTotal.total, 0) q.redisTotal.viewTotal = atomic.SwapInt64(&q.redisTotal.total, 0)
q.redisHit.viewTotal = atomic.SwapInt64(&q.redisHit.total, 0) q.redisHit.viewTotal = atomic.SwapInt64(&q.redisHit.total, 0)
q.sourceTotal.viewTotal = atomic.SwapInt64(&q.sourceTotal.total, 0) q.sourceTotal.viewTotal = atomic.SwapInt64(&q.sourceTotal.total, 0)
// percnt := 0
} }
} }

19
cache/readme.md vendored
View File

@ -1,4 +1,7 @@
# 级缓存模块 # 级缓存模块
提供本地缓存和分布式缓存组合的缓存模块,可以只使用本地缓存或分布式缓存。并可全局禁用缓存。
1. 一级缓存可使用freecache或bigcache作为本地缓存当数据在本地缓存不存在时会向二级缓存请求数据 1. 一级缓存可使用freecache或bigcache作为本地缓存当数据在本地缓存不存在时会向二级缓存请求数据
2. 二级缓存使用redis作为缓存模块当数据在二级缓存不存在时向资源请求数据。 2. 二级缓存使用redis作为缓存模块当数据在二级缓存不存在时向资源请求数据。
@ -11,3 +14,17 @@
3. 缓存穿透;从数据源中未找到数据时,在缓存中缓存空值。 3. 缓存穿透;从数据源中未找到数据时,在缓存中缓存空值。
4. 缓存雪崩;为防止缓存雪崩将资源放入缓存时,对过期时间添加一个随机过期时间,防止缓存同时过期。 4. 缓存雪崩;为防止缓存雪崩将资源放入缓存时,对过期时间添加一个随机过期时间,防止缓存同时过期。
5. 自动续期;当访问二级缓存时对使用的资源进行延期。 5. 自动续期;当访问二级缓存时对使用的资源进行延期。
## 使用方式
创建
```go
cache.New().UseRedis().UseBigCache().Build()
```
```go
Cache.Get(key, dist, func() (bool,error){}, options func(){})
Cache.GetFn(context, key, dist, func() (bool, error))
```

49
cache/redis.go vendored
View File

@ -9,52 +9,25 @@ import (
"github.com/charlienet/go-mixed/bytesconv" "github.com/charlienet/go-mixed/bytesconv"
"github.com/charlienet/go-mixed/json" "github.com/charlienet/go-mixed/json"
"github.com/charlienet/go-mixed/rand" "github.com/charlienet/go-mixed/rand"
"github.com/go-redis/redis/v8" "github.com/charlienet/go-mixed/redis"
) )
const redisEmptyObject = "redis object not exist" const redisEmptyObject = "redis object not exist"
type RedisConfig struct {
Prefix string // key perfix
Addrs []string
// Database to be selected after connecting to the server.
// Only single-node and failover clients.
DB int
Username string
Password string
MaxRetries int
MinRetryBackoff time.Duration
MaxRetryBackoff time.Duration
DialTimeout time.Duration
ReadTimeout time.Duration
WriteTimeout time.Duration
}
type redisClient struct { type redisClient struct {
client redis.UniversalClient client redis.Client
emptyStamp string // 空对象标识,每个实例隔离 emptyStamp string // 空对象标识,每个实例隔离
prefix string // 缓存键前缀 prefix string // 缓存键前缀
} }
func NewRedis(c RedisConfig) *redisClient { func NewRedis(c redis.Client) *redisClient {
client := redis.NewUniversalClient(&redis.UniversalOptions{
Addrs: c.Addrs,
DB: c.DB,
Username: c.Username,
Password: c.Password,
})
return &redisClient{ return &redisClient{
emptyStamp: fmt.Sprintf("redis-empty-%d-%s", time.Now().Unix(), rand.Hex.Generate(6)), emptyStamp: fmt.Sprintf("redis-empty-%d-%s", time.Now().Unix(), rand.Hex.Generate(6)),
prefix: c.Prefix, client: c,
client: client,
} }
} }
func (c *redisClient) Get(key string, out any) error { func (c *redisClient) Get(cxt context.Context, key string, out any) error {
val, err := c.client.Get(context.Background(), c.getKey(key)).Result() val, err := c.client.Get(context.Background(), c.getKey(key)).Result()
if errors.Is(err, redis.Nil) { if errors.Is(err, redis.Nil) {
return ErrNotFound return ErrNotFound
@ -72,23 +45,23 @@ func (c *redisClient) Get(key string, out any) error {
return json.Unmarshal(bytesconv.StringToBytes(val), out) return json.Unmarshal(bytesconv.StringToBytes(val), out)
} }
func (c *redisClient) Set(key string, value any, expiration time.Duration) error { func (c *redisClient) Set(ctx context.Context, key string, value any, expiration time.Duration) error {
j, _ := json.Marshal(value) j, _ := json.Marshal(value)
return c.client.Set(context.Background(), c.getKey(key), j, expiration).Err() return c.client.Set(context.Background(), c.getKey(key), j, expiration).Err()
} }
func (c *redisClient) Exist(key string) (bool, error) { func (c *redisClient) Exist(ctx context.Context, key string) (bool, error) {
val, err := c.client.Exists(context.Background(), c.getKey(key)).Result() val, err := c.client.Exists(context.Background(), c.getKey(key)).Result()
return val > 0, err return val > 0, err
} }
func (c *redisClient) Delete(key ...string) error { func (c *redisClient) Delete(ctx context.Context, key ...string) error {
keys := make([]string, 0, len(key)) keys := make([]string, 0, len(key))
for _, k := range key { for _, k := range key {
keys = append(keys, c.getKey(k)) keys = append(keys, c.getKey(k))
} }
_ , err := c.client.Del(context.Background(), keys...).Result() _, err := c.client.Del(context.Background(), keys...).Result()
if err != nil { if err != nil {
return err return err
} }
@ -96,8 +69,8 @@ func (c *redisClient) Delete(key ...string) error {
return nil return nil
} }
func (c *redisClient) Ping() error { func (c *redisClient) Ping(ctx context.Context) error {
_, err := c.client.Ping(context.Background()).Result() _, err := c.client.Ping(ctx).Result()
return err return err
} }

20
cache/stats.go vendored Normal file
View File

@ -0,0 +1,20 @@
package cache
import "sync/atomic"
type Stats struct {
Hits uint64
Misses uint64
}
func (s *Stats) IncrementHits() {
atomic.AddUint64(&s.Hits, 1)
}
func (s *Stats) IncrementMisses() {
atomic.AddUint64(&s.Misses, 1)
}
func (c *Cache) Stats() *Stats {
return c.stats
}

64
cache/tiny_lfu.go vendored Normal file
View File

@ -0,0 +1,64 @@
package cache
import (
"time"
"github.com/charlienet/go-mixed/locker"
"github.com/vmihailenco/go-tinylfu"
)
var _ MemCache = &TinyLFU{}
type TinyLFU struct {
mu locker.Locker
lfu *tinylfu.T
ttl time.Duration
}
func NewTinyLFU(size int, ttl time.Duration) *TinyLFU {
return &TinyLFU{
mu: locker.NewLocker(),
lfu: tinylfu.New(size, 100000),
ttl: ttl,
}
}
func (c *TinyLFU) Set(key string, b []byte, expire time.Duration) error {
c.mu.Lock()
defer c.mu.Unlock()
c.lfu.Set(&tinylfu.Item{
Key: key,
Value: b,
ExpireAt: time.Now().Add(c.ttl),
})
return nil
}
func (c *TinyLFU) Get(key string) ([]byte, bool) {
c.mu.Lock()
defer c.mu.Unlock()
val, ok := c.lfu.Get(key)
if !ok {
return nil, false
}
return val.([]byte), true
}
func (c *TinyLFU) Delete(keys ...string) error {
c.mu.Lock()
defer c.mu.Unlock()
for _, k := range keys {
c.lfu.Del(k)
}
return nil
}
func (c *TinyLFU) Clear() {
}

57
cache/tiny_lfu_test.go vendored Normal file
View File

@ -0,0 +1,57 @@
package cache_test
import (
"context"
"fmt"
"testing"
"time"
"github.com/charlienet/go-mixed/cache"
"github.com/charlienet/go-mixed/rand"
)
func TestTinyGet(t *testing.T) {
strFor := func(i int) string {
return fmt.Sprintf("a string %d", i)
}
keyName := func(i int) string {
return fmt.Sprintf("key-%00000d", i)
}
mycache := cache.NewTinyLFU(1000, 1*time.Second)
size := 50000
// Put a bunch of stuff in the cache with a TTL of 1 second
for i := 0; i < size; i++ {
key := keyName(i)
mycache.Set(key, []byte(strFor(i)), time.Second*2)
}
// Read stuff for a bit longer than the TTL - that's when the corruption occurs
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
done := ctx.Done()
loop:
for {
select {
case <-done:
// this is expected
break loop
default:
i := rand.Intn(size)
key := keyName(i)
b, ok := mycache.Get(key)
if !ok {
continue loop
}
got := string(b)
expected := strFor(i)
if got != expected {
t.Fatalf("expected=%q got=%q key=%q", expected, got, key)
}
}
}
}

210
calendar/calendar.go Normal file
View File

@ -0,0 +1,210 @@
package calendar
import (
"time"
)
var WeekStartDay time.Weekday = time.Sunday
type Calendar struct {
time.Time
weekStartsAt time.Weekday
}
func BeginningOfMinute() Calendar {
return Create(time.Now()).BeginningOfMinute()
}
func BeginningOfHour() Calendar {
return Create(time.Now()).BeginningOfHour()
}
func BeginningOfDay() Calendar {
return Create(time.Now()).BeginningOfDay()
}
func BeginningOfWeek() Calendar {
return Create(time.Now()).BeginningOfWeek()
}
func BeginningOfMonth() Calendar {
return Create(time.Now()).BeginningOfMonth()
}
func BeginningOfQuarter() Calendar {
return Create(time.Now()).BeginningOfQuarter()
}
func BeginningOfYear() Calendar {
return Create(time.Now()).BeginningOfYear()
}
func EndOfMinute() Calendar {
return Create(time.Now()).EndOfMinute()
}
func EndOfHour() Calendar {
return Create(time.Now()).EndOfHour()
}
func EndOfDay() Calendar {
return Create(time.Now()).EndOfDay()
}
func EndOfWeek() Calendar {
return Create(time.Now()).EndOfWeek()
}
func EndOfMonth() Calendar {
return Create(time.Now()).EndOfMonth()
}
func EndOfQuarter() Calendar {
return Create(time.Now()).EndOfQuarter()
}
func EndOfYear() Calendar {
return Create(time.Now()).EndOfYear()
}
func (c Calendar) WeekStartsAt(day time.Weekday) Calendar {
return Calendar{
Time: c.Time,
weekStartsAt: day,
}
}
func (c Calendar) BeginningOfMinute() Calendar {
return Calendar{Time: c.Truncate(time.Minute)}
}
func (c Calendar) BeginningOfHour() Calendar {
y, m, d := c.Date()
return Calendar{
Time: time.Date(y, m, d, c.Hour(), 0, 0, 0, c.Location()),
weekStartsAt: c.weekStartsAt,
}
}
func (c Calendar) BeginningOfDay() Calendar {
y, m, d := c.Date()
return Calendar{
Time: time.Date(y, m, d, 0, 0, 0, 0, c.Location()),
}
}
func (c Calendar) BeginningOfWeek() Calendar {
t := c.BeginningOfDay()
weekday := int(t.Weekday())
if c.weekStartsAt != time.Sunday {
weekStartDayInt := int(c.weekStartsAt)
if weekday < weekStartDayInt {
weekday = weekday + 7 - weekStartDayInt
} else {
weekday = weekday - weekStartDayInt
}
}
return Calendar{
Time: t.AddDate(0, 0, -weekday),
weekStartsAt: c.weekStartsAt,
}
}
func (c Calendar) BeginningOfMonth() Calendar {
y, m, _ := c.Date()
return Calendar{
Time: time.Date(y, m, 1, 0, 0, 0, 0, c.Location()),
weekStartsAt: c.weekStartsAt,
}
}
func (c Calendar) BeginningOfQuarter() Calendar {
month := c.BeginningOfMonth()
offset := (int(month.Month()) - 1) % 3
return Calendar{
Time: month.AddDate(0, -offset, 0),
weekStartsAt: c.weekStartsAt,
}
}
func (c Calendar) BeginningOfYear() Calendar {
y, _, _ := c.Date()
return Calendar{
Time: time.Date(y, time.January, 1, 0, 0, 0, 0, c.Location()),
weekStartsAt: c.weekStartsAt}
}
func (c Calendar) EndOfMinute() Calendar {
n := c.BeginningOfMinute()
return Calendar{
Time: n.Add(time.Minute - time.Nanosecond),
weekStartsAt: c.weekStartsAt,
}
}
func (c Calendar) EndOfHour() Calendar {
n := c.BeginningOfHour()
return Calendar{
Time: n.Add(time.Hour - time.Nanosecond),
weekStartsAt: c.weekStartsAt,
}
}
func (c Calendar) EndOfDay() Calendar {
y, m, d := c.Date()
return Calendar{
Time: time.Date(y, m, d, 23, 59, 59, int(time.Second-time.Nanosecond), c.Location()),
weekStartsAt: c.weekStartsAt,
}
}
func (c Calendar) EndOfWeek() Calendar {
n := c.BeginningOfWeek()
return Calendar{
Time: n.AddDate(0, 0, 7).Add(-time.Nanosecond),
weekStartsAt: c.weekStartsAt,
}
}
func (c Calendar) EndOfMonth() Calendar {
n := c.BeginningOfMonth()
return Calendar{
Time: n.AddDate(0, 1, 0).Add(-time.Nanosecond),
weekStartsAt: c.weekStartsAt,
}
}
func (c Calendar) EndOfQuarter() Calendar {
n := c.BeginningOfQuarter()
return Calendar{
Time: n.AddDate(0, 3, 0).Add(-time.Nanosecond),
weekStartsAt: c.weekStartsAt,
}
}
func (c Calendar) EndOfYear() Calendar {
n := c.BeginningOfYear()
return Calendar{
Time: n.AddDate(1, 0, 0).Add(-time.Nanosecond),
weekStartsAt: c.weekStartsAt,
}
}
func (c Calendar) ToTime() time.Time {
return c.Time
}

38
calendar/calendar_test.go Normal file
View File

@ -0,0 +1,38 @@
package calendar_test
import (
"testing"
"time"
"github.com/charlienet/go-mixed/calendar"
"github.com/stretchr/testify/assert"
)
var format = "2006-01-02 15:04:05.999999999"
func TestToday(t *testing.T) {
t.Log(calendar.Today())
}
func TestBeginningOf(t *testing.T) {
a := assert.New(t)
n := time.Date(2022, 11, 9, 14, 28, 34, 123456789, time.UTC)
a.Equal("2022-11-06 00:00:00", calendar.Create(n).BeginningOfWeek().String())
a.Equal("2022-11-07 00:00:00", calendar.Create(n).WeekStartsAt(time.Monday).BeginningOfWeek().Format(format))
a.Equal("2022-11-09 14:00:00", calendar.Create(n).BeginningOfHour().Format(format))
a.Equal("2022-11-01 00:00:00", calendar.Create(n).BeginningOfMonth().Format(format))
a.Equal("2022-10-01 00:00:00", calendar.Create(n).BeginningOfQuarter().Format(format))
}
func TestEndOf(t *testing.T) {
a := assert.New(t)
n := time.Date(2022, 11, 9, 14, 28, 34, 123456789, time.UTC)
a.Equal("2022-11-09 14:28:59.999999999", calendar.Create(n).EndOfMinute().Format(format))
a.Equal("2022-11-09 14:59:59.999999999", calendar.Create(n).EndOfHour().Format(format))
a.Equal("2022-11-09 23:59:59.999999999", calendar.Create(n).EndOfDay().Format(format))
a.Equal("2022-11-30 23:59:59.999999999", calendar.Create(n).EndOfMonth().Format(format))
a.Equal("2022-12-31 23:59:59.999999999", calendar.Create(n).EndOfQuarter().Format(format))
a.Equal("2022-12-31 23:59:59.999999999", calendar.Create(n).EndOfYear().Format(format))
}

40
calendar/creator.go Normal file
View File

@ -0,0 +1,40 @@
package calendar
import "time"
func Now() Calendar {
return Create(time.Now())
}
func Today() Calendar {
return Now().BeginningOfDay()
}
func Create(t time.Time) Calendar {
return Calendar{
Time: t,
weekStartsAt: WeekStartDay,
}
}
func CreateFromTimestamp(timestamp int64) Calendar {
return Create(time.Unix(timestamp, 0))
}
func CreateFromTimestampMilli(timestamp int64) Calendar {
return Create(time.Unix(timestamp/1e3, (timestamp%1e3)*1e6))
}
func CreateFromTimestampMicro(timestamp int64) Calendar {
return Create(time.Unix(timestamp/1e6, (timestamp%1e6)*1e3))
}
func CreateFromTimestampNano(timestamp int64) Calendar {
return Create(time.Unix(timestamp/1e9, timestamp%1e9))
}
func create(year, month, day, hour, minute, second, nanosecond int) Calendar {
return Calendar{
Time: time.Date(year, time.Month(month), day, hour, minute, second, nanosecond, time.Local),
}
}

24
calendar/duration.go Normal file
View File

@ -0,0 +1,24 @@
package calendar
import "time"
func ParseDuration(s string) (time.Duration, error) {
if len(s) == 0 {
return time.Duration(0), nil
}
return time.ParseDuration(s)
}
func ParseDurationDefault(s string, d time.Duration) time.Duration {
if len(s) == 0 {
return d
}
ret, err := time.ParseDuration(s)
if err != nil {
return d
}
return ret
}

98
calendar/output.go Normal file
View File

@ -0,0 +1,98 @@
package calendar
import "time"
// 布局模板常量
const (
ANSICLayout = time.ANSIC
UnixDateLayout = time.UnixDate
RubyDateLayout = time.RubyDate
RFC822Layout = time.RFC822
RFC822ZLayout = time.RFC822Z
RFC850Layout = time.RFC850
RFC1123Layout = time.RFC1123
RFC1123ZLayout = time.RFC1123Z
RssLayout = time.RFC1123Z
KitchenLayout = time.Kitchen
RFC2822Layout = time.RFC1123Z
CookieLayout = "Monday, 02-Jan-2006 15:04:05 MST"
RFC3339Layout = "2006-01-02T15:04:05Z07:00"
RFC3339MilliLayout = "2006-01-02T15:04:05.999Z07:00"
RFC3339MicroLayout = "2006-01-02T15:04:05.999999Z07:00"
RFC3339NanoLayout = "2006-01-02T15:04:05.999999999Z07:00"
ISO8601Layout = "2006-01-02T15:04:05-07:00"
ISO8601MilliLayout = "2006-01-02T15:04:05.999-07:00"
ISO8601MicroLayout = "2006-01-02T15:04:05.999999-07:00"
ISO8601NanoLayout = "2006-01-02T15:04:05.999999999-07:00"
RFC1036Layout = "Mon, 02 Jan 06 15:04:05 -0700"
RFC7231Layout = "Mon, 02 Jan 2006 15:04:05 GMT"
DayDateTimeLayout = "Mon, Jan 2, 2006 3:04 PM"
DateTimeLayout = "2006-01-02 15:04:05"
DateTimeMilliLayout = "2006-01-02 15:04:05.999"
DateTimeMicroLayout = "2006-01-02 15:04:05.999999"
DateTimeNanoLayout = "2006-01-02 15:04:05.999999999"
ShortDateTimeLayout = "20060102150405"
ShortDateTimeMilliLayout = "20060102150405.999"
ShortDateTimeMicroLayout = "20060102150405.999999"
ShortDateTimeNanoLayout = "20060102150405.999999999"
DateLayout = "2006-01-02"
DateMilliLayout = "2006-01-02.999"
DateMicroLayout = "2006-01-02.999999"
DateNanoLayout = "2006-01-02.999999999"
ShortDateLayout = "20060102"
ShortDateMilliLayout = "20060102.999"
ShortDateMicroLayout = "20060102.999999"
ShortDateNanoLayout = "20060102.999999999"
TimeLayout = "15:04:05"
TimeMilliLayout = "15:04:05.999"
TimeMicroLayout = "15:04:05.999999"
TimeNanoLayout = "15:04:05.999999999"
ShortTimeLayout = "150405"
ShortTimeMilliLayout = "150405.999"
ShortTimeMicroLayout = "150405.999999"
ShortTimeNanoLayout = "150405.999999999"
)
func String(t time.Time) string {
return ToDateTimeString(t)
}
func ToDateTimeString(t time.Time) string {
return Create(t).ToDateTimeString()
}
func Format(t time.Time, layout string) string {
return Create(t).Format(layout)
}
func ToDateTimeInt(t time.Time) int {
return Create(t).ToDateTimeInt()
}
func ToShortDateInt(t time.Time) int {
return Create(t).ToShortDateInt()
}
func ToMonthInt(t time.Time) int {
return Create(t).ToMonthInt()
}
func (c Calendar) String() string {
return c.ToDateTimeString()
}
func (c Calendar) ToDateTimeString() string {
return c.Format(DateTimeLayout)
}
func (c Calendar) ToDateTimeInt() int {
return c.ToShortDateInt()*1000000 + c.Hour()*10000 + c.Minute()*100 + c.Second()
}
func (c Calendar) ToShortDateInt() int {
return c.Year()*10000 + int(c.Month())*100 + c.Day()
}
func (c Calendar) ToMonthInt() int {
return c.Year()*100 + int(c.Month())
}

19
calendar/output_test.go Normal file
View File

@ -0,0 +1,19 @@
package calendar_test
import (
"testing"
"time"
"github.com/charlienet/go-mixed/calendar"
"github.com/stretchr/testify/assert"
)
func TestDayInt(t *testing.T) {
assert := assert.New(t)
n := time.Date(2022, 11, 9, 14, 28, 34, 123456789, time.UTC)
assert.Equal(20221109, calendar.Create(n).ToShortDateInt())
assert.Equal(202211, calendar.Create(n).ToMonthInt())
assert.Equal(20221109142834, calendar.Create(n).ToDateTimeInt())
}

View File

@ -0,0 +1,14 @@
package calendar
import "time"
type ScheduledExecutor struct {
}
func NewScheduledExecutor() *ScheduledExecutor {
return &ScheduledExecutor{}
}
func (e *ScheduledExecutor) Schedule(i any, duration time.Duration) {
}

View File

@ -0,0 +1,14 @@
package calendar_test
import (
"testing"
"time"
"github.com/charlienet/go-mixed/calendar"
)
func TestExecutor(t *testing.T) {
executor := calendar.NewScheduledExecutor()
executor.Schedule(nil, time.Minute)
}

View File

@ -0,0 +1,15 @@
package deque
import "github.com/charlienet/go-mixed/locker"
type Deque[T any] struct {
locker locker.RWLocker
}
func New[T any]() *Deque[T] {
return &Deque[T]{
locker: locker.EmptyLocker,
}
}

View File

@ -0,0 +1 @@
package deque_test

View File

@ -0,0 +1,195 @@
package list
const minCapacity = 16
type ArrayList[T any] struct {
buf []T
head int
tail int
minCap int
list[T]
}
func NewArrayList[T any](elems ...T) *ArrayList[T] {
minCap := minCapacity
size := len(elems)
for minCap < size {
minCap <<= 1
}
var tail int = size
var buf []T
if len(elems) > 0 {
buf = make([]T, minCap)
copy(buf, elems)
}
l := &ArrayList[T]{
list: list[T]{size: size},
buf: buf,
tail: tail,
minCap: minCap,
}
// for _, v := range elems {
// l.PushBack(v)
// }
return l
}
func (l *ArrayList[T]) PushFront(v T) {
l.mu.Lock()
defer l.mu.Unlock()
l.grow()
l.head = l.prev(l.head)
l.buf[l.head] = v
l.size++
}
func (l *ArrayList[T]) PushBack(v T) {
l.mu.Lock()
defer l.mu.Unlock()
l.grow()
l.buf[l.tail] = v
l.tail = l.next(l.tail)
l.size++
}
func (l *ArrayList[T]) PopFront() T {
l.mu.Lock()
defer l.mu.Unlock()
if l.size <= 0 {
panic("list: PopFront() called on empty list")
}
ret := l.buf[l.head]
var zero T
l.buf[l.head] = zero
l.head = l.next(l.head)
l.size--
l.shrink()
return ret
}
func (l *ArrayList[T]) PopBack() T {
l.mu.Lock()
defer l.mu.Unlock()
l.tail = l.prev(l.tail)
ret := l.buf[l.tail]
var zero T
l.buf[l.tail] = zero
l.size--
l.shrink()
return ret
}
func (l *ArrayList[T]) RemoveAt(at int) T {
if at < 0 || at >= l.Size() {
panic(ErrorOutOffRange)
}
l.mu.Lock()
defer l.mu.Unlock()
rm := (l.head + at) & (len(l.buf) - 1)
if at*2 < l.size {
for i := 0; i < at; i++ {
prev := l.prev(rm)
l.buf[prev], l.buf[rm] = l.buf[rm], l.buf[prev]
rm = prev
}
return l.PopFront()
}
swaps := l.size - at - 1
for i := 0; i < swaps; i++ {
next := l.next(rm)
l.buf[rm], l.buf[next] = l.buf[next], l.buf[rm]
rm = next
}
return l.PopBack()
}
func (l *ArrayList[T]) Front() T {
l.mu.RLock()
defer l.mu.RUnlock()
return l.buf[l.head]
}
func (l *ArrayList[T]) Back() T {
l.mu.RLock()
defer l.mu.RUnlock()
return l.buf[l.tail]
}
func (l *ArrayList[T]) ForEach(fn func(T)) {
l.mu.RLock()
defer l.mu.RUnlock()
n := l.head
for i := 0; i < l.size; i++ {
fn(l.buf[n])
n = l.next(n)
}
}
func (q *ArrayList[T]) prev(i int) int {
return (i - 1) & (len(q.buf) - 1)
}
func (l *ArrayList[T]) next(i int) int {
return (i + 1) & (len(l.buf) - 1)
}
func (l *ArrayList[T]) grow() {
if l.size != len(l.buf) {
return
}
if len(l.buf) == 0 {
if l.minCap == 0 {
l.minCap = minCapacity
}
l.buf = make([]T, l.minCap)
return
}
l.resize()
}
func (l *ArrayList[T]) shrink() {
if len(l.buf) > l.minCap && (l.size<<2) == len(l.buf) {
l.resize()
}
}
// resize resizes the list to fit exactly twice its current contents. This is
// used to grow the list when it is full, and also to shrink it when it is
// only a quarter full.
func (l *ArrayList[T]) resize() {
newBuf := make([]T, l.size<<1)
if l.tail > l.head {
copy(newBuf, l.buf[l.head:l.tail])
} else {
n := copy(newBuf, l.buf[l.head:])
copy(newBuf[n:], l.buf[:l.tail])
}
l.head = 0
l.tail = l.size
l.buf = newBuf
}

View File

@ -0,0 +1,43 @@
package list_test
import (
"testing"
"github.com/charlienet/go-mixed/collections/list"
)
func TestNewArrayList(t *testing.T) {
l := list.NewArrayList(1, 2, 3)
l.ForEach(func(i int) {
t.Log(i)
})
}
func TestArrayPushBack(t *testing.T) {
l := list.NewArrayList[int]()
l.PushBack(1)
l.PushBack(2)
l.PushBack(3)
l.ForEach(func(i int) {
t.Log(i)
})
}
func TestArrayPushFront(t *testing.T) {
l := list.NewArrayList[int]()
l.PushFront(1)
l.PushFront(2)
l.PushFront(3)
l.PushBack(99)
l.PushBack(88)
l.ForEach(func(i int) {
t.Log(i)
})
}

View File

@ -0,0 +1,155 @@
package list
type LinkedList[T any] struct {
list[T]
front, tail *LinkedNode[T]
}
type LinkedNode[T any] struct {
Value T
Prev, Next *LinkedNode[T]
}
// NewLinkedList 初始化链表
func NewLinkedList[T any](elems ...T) *LinkedList[T] {
l := &LinkedList[T]{}
for _, e := range elems {
l.pushBackNode(&LinkedNode[T]{Value: e})
}
return l
}
func (l *LinkedList[T]) PushBack(v T) *LinkedList[T] {
l.mu.Lock()
defer l.mu.Unlock()
l.pushBackNode(&LinkedNode[T]{Value: v})
return l
}
func (l *LinkedList[T]) PushFront(v T) *LinkedList[T] {
l.mu.Lock()
defer l.mu.Unlock()
l.pushFrontNode(&LinkedNode[T]{Value: v})
return l
}
func (l *LinkedList[T]) FrontNode() *LinkedNode[T] {
return l.front
}
func (l *LinkedList[T]) Front() T {
return l.FrontNode().Value
}
func (l *LinkedList[T]) BackNode() *LinkedNode[T] {
return l.tail
}
func (l *LinkedList[T]) Back() T {
if l.size == 0 {
panic(ErrorOutOffRange)
}
return l.tail.Value
}
func (l *LinkedList[T]) ForEach(fn func(T) bool) {
l.mu.RLock()
defer l.mu.RUnlock()
for current := l.front; current != nil; current = current.Next {
if fn(current.Value) {
break
}
}
}
func (l *LinkedList[T]) GetAt(i int) T {
if i <= l.Size() {
for n, current := 0, l.front; current != nil; current, n = current.Next, n+1 {
if n == i {
return current.Value
}
}
}
return *new(T)
}
func (l *LinkedList[T]) Remove(n *LinkedNode[T]) {
l.mu.Lock()
defer l.mu.Unlock()
if n.Next != nil {
n.Next.Prev = n.Prev
} else {
l.tail = n.Prev
}
if n.Prev != nil {
n.Prev.Next = n.Next
} else {
l.front = n.Next
}
n.Next = nil
n.Prev = nil
l.size--
}
func (l *LinkedList[T]) RemoveAt(index int) {
l.mu.Lock()
defer l.mu.Unlock()
var i int
for current := l.front; current != nil; current = current.Next {
if i == index {
// 重连接
current.Prev.Next = current.Next
current.Next.Prev = current.Prev
current.Prev = nil
current.Next = nil
l.size--
break
}
i++
}
}
func (l *LinkedList[T]) pushBackNode(n *LinkedNode[T]) {
n.Next = nil
n.Prev = l.tail
if l.tail != nil {
l.tail.Next = n
} else {
l.front = n
}
l.tail = n
l.size++
}
func (l *LinkedList[T]) pushFrontNode(n *LinkedNode[T]) {
n.Next = l.front
n.Prev = nil
if l.front != nil {
l.front.Prev = n
} else {
l.tail = n
}
l.front = n
l.size++
}

View File

@ -0,0 +1,103 @@
package list_test
import (
"testing"
"github.com/charlienet/go-mixed/collections/list"
"github.com/stretchr/testify/assert"
)
func TestPushBack(t *testing.T) {
l := list.NewLinkedList[int]()
l.PushBack(1)
l.PushBack(2)
l.PushBack(3)
l.ForEach(func(i int) bool {
t.Log(i)
return false
})
}
func TestPushFront(t *testing.T) {
l := list.NewLinkedList[int]()
l.PushFront(1)
l.PushFront(2)
l.PushFront(3)
l.ForEach(func(i int) bool {
t.Log(i)
return false
})
}
func TestRemoveAt(t *testing.T) {
l := list.NewLinkedList[int]()
l.PushBack(1)
l.PushBack(2)
l.PushBack(3)
l.RemoveAt(1)
l.ForEach(func(i int) bool {
t.Log(i)
return false
})
t.Log()
l.RemoveAt(0)
l.ForEach(func(i int) bool {
t.Log(i)
return false
})
}
func TestSize(t *testing.T) {
l := list.NewLinkedList[int]()
l.PushBack(1)
l.PushBack(2)
l.PushBack(3)
assert.Equal(t, 3, l.Size())
l.RemoveAt(0)
assert.Equal(t, 2, l.Size())
}
func TestLinkedListToSlice(t *testing.T) {
l := list.NewLinkedList[int]()
l.PushBack(1)
l.PushBack(2)
l.PushBack(3)
s := l.ToSlice()
t.Log(s)
}
func BenchmarkLinkedList(b *testing.B) {
l := list.NewLinkedList[int]()
l.Synchronize()
for i := 0; i < b.N; i++ {
l.PushBack(i)
}
}
func TestRemoveNode(t *testing.T) {
l := list.NewLinkedList(1, 2, 3, 4, 5)
// l.ForEach(func(i int) bool {
// t.Log(i)
// return false
// })
l.RemoveAt(1)
for currnet := l.FrontNode(); currnet != nil; currnet = currnet.Next {
t.Logf("%p %+v", currnet, currnet)
}
}

35
collections/list/list.go Normal file
View File

@ -0,0 +1,35 @@
package list
import (
"errors"
"github.com/charlienet/go-mixed/locker"
)
var ErrorOutOffRange = errors.New("out of range")
type List[T any] interface {
}
type list[T any] struct {
size int
mu locker.WithRWLocker
}
func (l *list[T]) Synchronize() {
l.mu.Synchronize()
}
func (l *list[T]) ForEach(fn func(T) bool) { panic("Not Implemented") }
func (l *LinkedList[T]) ToSlice() []T {
s := make([]T, 0, l.Size())
l.ForEach(func(t T) bool {
s = append(s, t)
return false
})
return s
}
func (l *list[T]) Size() int { return l.size }

View File

@ -1 +0,0 @@
package collections

View File

@ -1,11 +0,0 @@
package collections
import "github.com/charlienet/go-mixed/locker"
type options struct {
mu locker.RWLocker
}
func emptyLocker() locker.RWLocker {
return locker.EmptyLocker
}

View File

@ -1,6 +1,8 @@
package collections package collections
import "sync" import (
"sync"
)
var _ Queue[string] = &ArrayQueue[string]{} var _ Queue[string] = &ArrayQueue[string]{}

View File

@ -0,0 +1,35 @@
package queue
import (
"github.com/charlienet/go-mixed/collections/list"
)
type Queue[T any] struct {
list list.LinkedList[T]
}
func NewQueue[T any]() *Queue[T] {
return &Queue[T]{}
}
func (q *Queue[T]) Synchronize() *Queue[T] {
q.list.Synchronize()
return q
}
func (q *Queue[T]) Push(v T) {
}
func (q *Queue[T]) Pop(v T) {
}
func (q *Queue[T]) Size() int {
return q.list.Size()
}
func (q *Queue[T]) IsEmpty() bool {
return false
}

View File

@ -0,0 +1 @@
package queue_test

View File

@ -0,0 +1,10 @@
package rbtree
type color bool
const (
black, red color = true, false
)
type TreeNode[K any, V any] struct {
}

View File

@ -0,0 +1 @@
package rbtree

View File

@ -1,42 +0,0 @@
package collections
import "sync"
type rw_queue[T any] struct {
q Queue[T]
mu sync.Mutex
}
func (q *rw_queue[T]) Push(v T) {
q.mu.Lock()
q.q.Put(v)
q.mu.Unlock()
}
func (q *rw_queue[T]) Pop() T {
q.mu.Lock()
defer q.mu.Unlock()
return q.q.Poll()
}
func (q *rw_queue[T]) Peek() T {
q.mu.Lock()
defer q.mu.Unlock()
return q.q.Peek()
}
func (q *rw_queue[T]) Size() int {
q.mu.Lock()
defer q.mu.Unlock()
return q.q.Size()
}
func (q *rw_queue[T]) IsEmpty() bool {
q.mu.Lock()
defer q.mu.Unlock()
return q.q.IsEmpty()
}

View File

@ -1,4 +1,4 @@
package collections package stack
import "sync" import "sync"

View File

@ -1,13 +1,13 @@
package collections_test package stack_test
import ( import (
"testing" "testing"
"github.com/charlienet/go-mixed/collections" "github.com/charlienet/go-mixed/collections/stack"
) )
func TestStack(t *testing.T) { func TestStack(t *testing.T) {
arrayStack := collections.NewArrayStack[string]() arrayStack := stack.NewArrayStack[string]()
arrayStack.Push("cat") arrayStack.Push("cat")
arrayStack.Push("dog") arrayStack.Push("dog")
arrayStack.Push("hen") arrayStack.Push("hen")

View File

@ -10,12 +10,16 @@ import (
var s = "^aaa^" var s = "^aaa^"
func TestPutGet(t *testing.T) { func TestPutGet(t *testing.T) {
b := NewCompiledBuffer(func(s string) (*regexp.Regexp, error) { return regexp.Compile(s) }) b := NewCompiledBuffer(func(s string) (*regexp.Regexp, error) {
t.Log("init")
return regexp.Compile(s)
})
t.Log(b.Put(s)) t.Log(b.Put(s))
r, _ := b.Get(s) r, _ := b.Get(s)
t.Log(r.Match([]byte("abc"))) t.Log(r.Match([]byte("abc")))
t.Log(r.Match([]byte("abc")))
} }
func BenchmarkGet(b *testing.B) { func BenchmarkGet(b *testing.B) {
@ -28,7 +32,7 @@ func BenchmarkGet(b *testing.B) {
} }
}) })
b.Run("buf", func(b *testing.B) { b.Run("buf-nocom", func(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
regexp.Compile(s) regexp.Compile(s)
} }

View File

@ -2,7 +2,6 @@ package compress
import ( import (
"archive/zip" "archive/zip"
"io/ioutil"
"os" "os"
) )
@ -49,7 +48,7 @@ func (z *zipPackage) Write(out *os.File) error {
return err return err
} }
in, err := ioutil.ReadFile(f.filename) in, err := os.ReadFile(f.filename)
if err != nil { if err != nil {
return err return err
} }

View File

@ -0,0 +1,75 @@
package delayqueue
import (
"context"
"time"
"github.com/charlienet/go-mixed/locker"
)
type store[T Delayed] interface {
Push(context.Context, T) error
Pop() (T, error)
Peek() (T, bool)
IsEmpty() bool // 队列是否为空
}
type delayQueue[T Delayed] struct {
mu locker.RWLocker
store store[T]
}
type Delayed interface {
Delay() time.Time
}
func New[T Delayed]() *delayQueue[T] {
return &delayQueue[T]{
mu: locker.NewRWLocker(),
store: newMemStore[T](),
}
}
func (q *delayQueue[T]) UseStore(s store[T]) *delayQueue[T] {
q.store = s
return q
}
func (q *delayQueue[T]) Push(task T) error {
q.mu.Lock()
defer q.mu.Unlock()
return q.store.Push(context.Background(), task)
}
func (q *delayQueue[T]) Peek() (T, bool) {
q.mu.RLock()
defer q.mu.RUnlock()
return q.store.Peek()
}
func (q *delayQueue[T]) Pop() (T, error) {
q.mu.Lock()
defer q.mu.Unlock()
return q.store.Pop()
}
func (q *delayQueue[T]) Channel(size int) <-chan T {
out := make(chan T, size)
go func() {
for {
entry, _ := q.Pop()
out <- entry
}
}()
return out
}
func (q *delayQueue[T]) IsEmpty() bool {
q.mu.RLock()
defer q.mu.RUnlock()
return q.store.IsEmpty()
}

View File

@ -0,0 +1,48 @@
package delayqueue_test
import (
"testing"
"time"
delayqueue "github.com/charlienet/go-mixed/concurrent/delay_queue"
)
type delayTask struct {
message string
delay time.Time
}
func (t delayTask) Delay() time.Time {
return t.delay
}
func TestDelayQueue(t *testing.T) {
queue := delayqueue.New[delayTask]()
queue.Push(delayTask{})
}
func TestDelayedFunc(t *testing.T) {
q := delayqueue.New[delayTask]()
q.Push(delayTask{})
}
func TestDelayedChannel(t *testing.T) {
q := delayqueue.New[delayTask]()
c := q.Channel(10)
q.Push(delayTask{message: "abc", delay: time.Now().Add(time.Second)})
q.Push(delayTask{message: "abcaaa", delay: time.Now().Add(time.Second * 3)})
for {
if q.IsEmpty() {
t.Log("队列为空,退出")
break
}
select {
case task := <-c:
t.Log(task)
case <-time.After(time.Second * 2):
}
}
}

View File

@ -0,0 +1,37 @@
package delayqueue
import (
"context"
"github.com/charlienet/go-mixed/errors"
)
type kafkaStore[T Delayed] struct {
}
func (s *delayQueue[T]) UseKafka() *delayQueue[T] {
s.UseStore(newKafka[T]())
panic(errors.NotImplemented)
// return s.UseStore(newKafka[T]())
}
func newKafka[T Delayed]() *kafkaStore[T] {
return &kafkaStore[T]{}
}
func (*kafkaStore[T]) Push(context.Context, T) error {
return nil
}
func (*kafkaStore[T]) Pop() (T, error) {
return *new(T), nil
}
func (*kafkaStore[T]) Peek() (T, bool) {
return *new(T), false
}
func (*kafkaStore[T]) IsEmpty() bool {
return false
}

View File

@ -0,0 +1,88 @@
package delayqueue
import (
"container/heap"
"context"
"sync"
"time"
)
type delayedQueue []Delayed
type memStore[T Delayed] struct {
mu sync.Mutex
h *delayedQueue
wakeup chan struct{}
}
func (q delayedQueue) Len() int {
return len(q)
}
func (q delayedQueue) Less(i, j int) bool {
return q[i].Delay().Before(q[j].Delay())
}
func (q delayedQueue) Swap(i, j int) {
q[i], q[j] = q[j], q[i]
}
func (q *delayedQueue) Push(x any) {
*q = append(*q, x.(Delayed))
}
func (h *delayedQueue) Pop() interface{} {
old := *h
n := len(old)
x := old[n-1]
*h = old[0 : n-1]
return x
}
func newMemStore[T Delayed]() *memStore[T] {
store := &memStore[T]{
h: new(delayedQueue),
wakeup: make(chan struct{}, 1),
}
heap.Init(store.h)
return store
}
func (s *memStore[T]) Push(ctx context.Context, v T) error {
s.mu.Lock()
defer s.mu.Unlock()
heap.Push(s.h, v)
return nil
}
func (s *memStore[T]) Pop() (T, error) {
for {
_, exist := s.Peek()
if exist {
return s.h.Pop().(T), nil
}
time.Sleep(time.Millisecond * 10)
}
}
func (s *memStore[T]) Peek() (T, bool) {
s.mu.Lock()
defer s.mu.Unlock()
if s.h.Len() > 0 {
return (*s.h)[0].(T), true
}
return *new(T), false
}
func (s *memStore[T]) Len() int {
return s.h.Len()
}
func (s *memStore[T]) IsEmpty() bool {
return s.Len() == 0
}

View File

@ -0,0 +1,119 @@
package delayqueue
import (
"context"
"encoding/json"
"fmt"
"testing"
"time"
"github.com/charlienet/go-mixed/calendar"
"github.com/charlienet/go-mixed/rand"
)
type delayTask struct {
Message string
At time.Time
}
func (t delayTask) Delay() time.Time {
return t.At
}
func (t delayTask) execute() {
println(t.Message)
}
// var _ encoding.BinaryMarshaler = new(myStruct)
// var _ encoding.BinaryUnmarshaler = new(myStruct)
func (t delayTask) BinaryUnmarshaler(data []byte, v any) {
json.Unmarshal(data, v)
}
func (t delayTask) MarshalBinary() (data []byte, err error) {
return json.Marshal(t)
}
func TestMemStore(t *testing.T) {
s := newMemStore[delayTask]()
for i := 0; i < 10; i++ {
s.Push(
context.Background(),
delayTask{
Message: "tesss",
At: time.Now().Add(-time.Minute * time.Duration(rand.Intn(20))),
})
}
t.Log("count:", s.Len())
v, exists := s.Peek()
t.Logf("Peek %v:%v %v", exists, v.Message, calendar.Create(v.Delay()).ToDateTimeString())
for i := 0; i < 10; i++ {
v, _ := s.Pop()
t.Logf("POP:%v %v", v.Message, calendar.Create(v.Delay()).ToDateTimeString())
}
v, exists = s.Peek()
t.Logf("Peek %v:%v %v", exists, v.Message, calendar.Create(v.At).ToDateTimeString())
}
func TestMemPush(t *testing.T) {
s := newMemStore[delayTask]()
for i := 0; i < 10; i++ {
s.Push(
context.Background(),
delayTask{
Message: fmt.Sprintf("abc:%d", i),
At: time.Now().Add(time.Second * time.Duration(rand.IntRange(5, 30))),
})
}
now := time.Now()
delay, _ := s.Pop()
after := delay.Delay().Sub(now)
t.Log("after:", calendar.String(now), calendar.String(delay.Delay()), after)
}
func TestExecute(t *testing.T) {
s := newMemStore[delayTask]()
s.Push(context.Background(),
delayTask{
Message: "这是消息",
At: time.Now().Add(time.Second * 2),
})
s.Push(context.Background(),
delayTask{
Message: "这是消息",
At: time.Now().Add(time.Second * 4),
})
t.Log("start:", calendar.String(time.Now()))
for {
if s.IsEmpty() {
break
}
task, _ := s.Pop()
for {
if task.Delay().Before(time.Now()) {
task.execute()
t.Log("end:", calendar.String(time.Now()))
break
}
time.Sleep(time.Millisecond * 20)
}
}
}

View File

@ -0,0 +1,154 @@
package delayqueue
import (
"context"
"encoding"
"encoding/json"
"strconv"
"time"
goredis "github.com/redis/go-redis/v9"
"github.com/charlienet/go-mixed/hash"
"github.com/charlienet/go-mixed/redis"
)
// 使用Redis存储队列
type redisStore[T Delayed] struct {
rdb redis.Client
delayQueue string
executeQueue string
delayTaskSet string
}
func (q *delayQueue[T]) UseRedis(delayQueueName, executeQueueName, delayTaskName string, rdb redis.Client) *delayQueue[T] {
q.store = newRedisStroe[T](delayQueueName, executeQueueName, delayTaskName, rdb)
return q
}
func newRedisStroe[T Delayed](delayQueueName, executeQueueName, delayTaskName string, rdb redis.Client) *redisStore[T] {
store := &redisStore[T]{
delayQueue: delayQueueName,
executeQueue: executeQueueName,
delayTaskSet: delayTaskName,
rdb: rdb,
}
go func() {
for {
store.pushToExecute()
time.Sleep(time.Millisecond * 100)
}
}()
return store
}
func (s *redisStore[T]) Push(ctx context.Context, v T) error {
o := any(v).(encoding.BinaryMarshaler)
bytes, err := o.MarshalBinary()
if err != nil {
return err
}
tx := s.rdb.TxPipeline()
tx.HSet(context.Background(), s.delayTaskSet, hash.Sha1(bytes).Hex(), bytes)
tx.Exec(context.Background())
tx.HSet(context.Background(), s.delayTaskSet)
// tx.Exec()
ret := s.rdb.ZAdd(ctx, s.delayQueue, goredis.Z{
Score: float64(v.Delay().Unix()),
Member: v,
})
return ret.Err()
}
func (s *redisStore[T]) pushToExecute() error {
now := time.Now().Unix()
ret, err := s.rdb.ZRangeByScore(
context.Background(),
s.delayQueue,
&goredis.ZRangeBy{
Min: "-inf",
Max: strconv.FormatInt(now, 10),
}).Result()
if err != nil {
return err
}
if len(ret) > 0 {
pipe := s.rdb.TxPipeline()
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
defer cancel()
pipe.LPush(ctx, s.executeQueue, ret)
pipe.ZRem(ctx, s.delayQueue, ret)
if _, err := pipe.Exec(ctx); err != nil {
return err
}
}
return nil
}
func (s *redisStore[T]) Pop() (T, error) {
for {
v, err := s.rdb.RPop(context.Background(), s.executeQueue).Result()
if err != nil {
if err == redis.Nil {
time.Sleep(time.Millisecond * 10)
continue
}
return *new(T), err
}
if len(v) > 0 {
var task T
if err := json.Unmarshal([]byte(v), &task); err != nil {
return *new(T), err
}
return task, nil
}
}
}
func (s *redisStore[T]) Peek() (t T, r bool) {
m, err := s.rdb.ZRange(context.Background(), s.delayQueue, 0, 0).Result()
if err != nil {
return *new(T), false
}
if len(m) == 1 {
var t T
s := m[0]
if err := json.Unmarshal([]byte(s), &t); err != nil {
return *new(T), false
}
return t, true
}
return *new(T), false
}
func (s *redisStore[T]) Clear() {
s.rdb.Del(context.Background(), s.delayQueue)
s.rdb.Del(context.Background(), s.executeQueue)
}
func (s *redisStore[T]) IsEmpty() bool {
n, _ := s.rdb.LLen(context.Background(), s.executeQueue).Result()
m, _ := s.rdb.ZCard(context.Background(), s.delayQueue).Result()
return (m + n) == 0
}

View File

@ -0,0 +1,89 @@
package delayqueue
import (
"context"
"fmt"
"testing"
"time"
"github.com/charlienet/go-mixed/redis"
"github.com/charlienet/go-mixed/tests"
"github.com/stretchr/testify/assert"
)
const (
redisAddr = "192.168.123.100:6379"
delay_queue = "delay_queue"
execute_queue = "execute_queue"
delay_task_set = "task_set"
)
func TestRedis(t *testing.T) {
tests.RunOnRedis(t, func(client redis.Client) {
defer client.Close()
q := New[delayTask]().UseRedis(delay_queue, execute_queue, delay_task_set, client)
err := q.Push(delayTask{
Message: "abc1111111111111",
At: time.Now().Add(time.Second * 2)})
if err != nil {
t.Fatal(err)
}
t.Log(time.Now())
task, _ := q.Pop()
t.Logf("%+v", task)
t.Log(time.Now())
task.execute()
}, redis.RedisOption{Addr: redisAddr, Prefix: "redis_test"})
}
func TestMutiTask(t *testing.T) {
tests.RunOnRedis(t, func(client redis.Client) {
defer client.Close()
timer := time.NewTimer(time.Second)
ticker := time.NewTicker(time.Second)
timer.Reset(time.Microsecond)
ticker.Reset(time.Millisecond)
store := newRedisStroe[delayTask](delay_queue, execute_queue, delay_task_set, client)
for i := 1; i <= 5; i++ {
store.Push(context.Background(), delayTask{
Message: fmt.Sprintf("abc:%d", i),
At: time.Now().Add(time.Second * time.Duration(i)),
})
}
for !store.IsEmpty() {
v, err := store.Pop()
assert.Nil(t, err)
t.Log(time.Now(), v)
}
})
}
func TestIsEmpty(t *testing.T) {
tests.RunOnRedis(t, func(client redis.Client) {
defer client.Close()
store := newRedisStroe[delayTask](delay_queue, execute_queue, delay_task_set, client)
store.Clear()
assert.True(t, store.IsEmpty())
store.Push(context.Background(), delayTask{Message: "bbb", At: time.Now().Add(time.Second)})
assert.False(t, store.IsEmpty())
}, redis.RedisOption{
Addrs: []string{"redis-10448.c90.us-east-1-3.ec2.cloud.redislabs.com:10448"},
Password: "E7HFwvENEqimiB1EG4IjJSa2IUi0B22o",
})
}

20
concurrent/readme.md Normal file
View File

@ -0,0 +1,20 @@
延迟队列
包含以下实现模型
1. 内存模式。
2. Redis模式
3. MQ模式
内存模式,在队列中放入。定时检查过期时间,过期时间到达时取出并使用协程执行。
Redis模式使用ZSET存储任务队列。定时取出规则内的任务。取出后使用ZREM删除并放入执行队列LPUSH。放入成功后在任务执行通道发送消息执行通道使用LPOP取出并执行。
```
queue := delayqueue.New()
```

7
configure/config.toml Normal file
View File

@ -0,0 +1,7 @@
Mode = "dev"
[Nacos]
Address = "192.168.2.121"
Port = 8848
Namespace = "8560b58d-87d0-4b85-8ac5-f2d308c6669e"
Group = "dev"

46
configure/configure.go Normal file
View File

@ -0,0 +1,46 @@
package configure
import (
"github.com/charlienet/go-mixed/expr"
"github.com/spf13/viper"
)
type NotifyFunc func(Configure) error
type Configure interface {
Load(dataId string, v any, onChanged ...NotifyFunc) error
GetString(string, string) string
GetInt(key string, defaultValue int) int
}
type conf struct {
viper *viper.Viper //
nacos *nacos //
onChangeNotifies map[string][]NotifyFunc // 已经注册的配置变更通知
nacosOptions *NacosOptions //
useNacos bool //
}
func (c *conf) GetString(key string, defaultValue string) string {
if c.viper.IsSet(key) {
return c.viper.GetString(key)
}
return defaultValue
}
func (c *conf) GetInt(key string, defaultValue int) int {
return expr.Ternary(c.viper.IsSet(key), c.viper.GetInt(key), defaultValue)
}
func (c *conf) Load(dataId string, v any, onChanged ...NotifyFunc) error {
if err := c.nacos.Load(dataId, v); err != nil {
return err
}
if len(onChanged) > 0 {
c.onChangeNotifies[dataId] = onChanged
}
return nil
}

View File

@ -0,0 +1,100 @@
package configure
import (
"github.com/fsnotify/fsnotify"
"github.com/spf13/viper"
)
func New() *conf { return &conf{viper: viper.New(), onChangeNotifies: make(map[string][]NotifyFunc)} }
func (c *conf) AddConfigPath(in ...string) *conf {
for _, v := range in {
c.viper.AddConfigPath(v)
}
return c
}
func (c *conf) SetConfigName(in string) *conf {
c.viper.SetConfigName(in)
return c
}
func (c *conf) SetConfigFile(f string) *conf {
c.viper.SetConfigFile(f)
return c
}
func (c *conf) SetDefault(key string, value any) *conf {
c.viper.SetDefault(key, value)
return c
}
func (c *conf) AutomaticEnv() *conf {
c.viper.AutomaticEnv()
return c
}
func (c *conf) Read() (*conf, error) {
// 从本地配置读取
if err := c.viper.ReadInConfig(); err != nil {
return nil, err
}
c.viper.WatchConfig()
c.viper.OnConfigChange(c.OnViperChanged)
// 初始化Nacos客户端
if err := c.createNacosClient(); err != nil {
return nil, err
}
return c, nil
}
func (c *conf) OnViperChanged(in fsnotify.Event) {
}
func (c *conf) createNacosClient() error {
opt := c.getNacosOptions()
if opt == nil {
return nil
}
nc, err := createNacosClient(opt.Address, opt.Port, opt.Namespace, opt.Group)
if err != nil {
return err
}
c.nacos = &nacos{client: nc, group: opt.Group, onChanged: c.onNacosChanged}
return nil
}
func (c *conf) onNacosChanged(dataId, data string) {
if fs, ok := c.onChangeNotifies[dataId]; ok {
for _, f := range fs {
if f != nil {
f(c)
}
}
}
}
func (c *conf) getNacosOptions() *NacosOptions {
if c.nacosOptions != nil {
return c.nacosOptions
}
if c.useNacos {
return &NacosOptions{
Address: c.GetString(AddressKey, "127.0.0.1"),
Port: c.GetInt(PortKey, 8848),
Namespace: c.GetString(Namespace, ""),
Group: c.GetString(Group, ""),
}
}
return nil
}

View File

@ -0,0 +1,42 @@
package configure_test
import (
"testing"
"github.com/charlienet/go-mixed/configure"
"github.com/charlienet/go-mixed/json"
"github.com/stretchr/testify/assert"
)
func TestLoadSpecifiedFile(t *testing.T) {
conf, err := configure.New().SetConfigFile("config.toml").Read()
t.Log(err)
assert.Equal(t, "192.168.2.121", conf.GetString("nacos.address", ""))
_ = conf
}
func TestNewConfigure(t *testing.T) {
}
func TestNacos(t *testing.T) {
conf, err := configure.
New().
AddConfigPath(".").
WithNacos().
Read()
assert.Nil(t, err)
t.Log(conf.GetString("nacos.address", ""))
type redis struct {
Addrs string
}
r := &redis{}
t.Log(conf.Load("redis", r))
t.Log(json.StructToJsonIndent(r))
}

96
configure/nacos.go Normal file
View File

@ -0,0 +1,96 @@
package configure
import (
"encoding/json"
"fmt"
"github.com/nacos-group/nacos-sdk-go/v2/clients"
"github.com/nacos-group/nacos-sdk-go/v2/clients/config_client"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
"github.com/nacos-group/nacos-sdk-go/v2/vo"
)
const (
AddressKey = "Nacos.Address"
PortKey = "Nacos.Port"
Namespace = "Nacos.Namespace"
Group = "Nacos.Group"
)
type nacos struct {
client config_client.IConfigClient
onChanged func(string, string)
group string
}
type NacosOptions struct {
Address string
Port int
Namespace string
Group string
}
func (c *conf) WithNacosOptions(options *NacosOptions) *conf {
c.nacosOptions = options
return c
}
func (c *conf) WithNacos() *conf {
c.useNacos = true
return c
}
func (n *nacos) Load(dataId string, v any) error {
voParam := vo.ConfigParam{
DataId: dataId,
Group: n.group,
OnChange: n.onChange,
}
content, err := n.client.GetConfig(voParam)
if err != nil {
return err
}
if len(content) == 0 {
return fmt.Errorf("parameters not configured:%s", dataId)
}
if err := json.Unmarshal([]byte(content), v); err != nil {
return err
}
n.client.ListenConfig(voParam)
return nil
}
func (n *nacos) onChange(namespace, group, dataId, data string) {
n.onChanged(dataId, data)
}
func createNacosClient(addr string, port int, namespace, group string) (config_client.IConfigClient, error) {
sc := []constant.ServerConfig{{
IpAddr: addr,
Port: uint64(port),
}}
cc := constant.ClientConfig{
NamespaceId: namespace,
TimeoutMs: 5000,
LogDir: "logs",
CacheDir: "cache",
LogLevel: "info",
NotLoadCacheAtStart: true,
}
configClient, err := clients.CreateConfigClient(map[string]any{
"serverConfigs": sc,
"clientConfig": cc,
})
if err != nil {
return nil, err
}
return configClient, nil
}

View File

@ -1,4 +1,4 @@
package crypto package sm2
import ( import (
"crypto/rand" "crypto/rand"
@ -14,7 +14,7 @@ var (
C1C2C3 = 1 C1C2C3 = 1
) )
var _ IAsymmetric = &sm2Instance{} type option func(*sm2Instance) error
type sm2Instance struct { type sm2Instance struct {
mode int mode int
@ -22,9 +22,42 @@ type sm2Instance struct {
puk *s.PublicKey puk *s.PublicKey
} }
type option func(*sm2Instance) error func WithSm2PrivateKey(p []byte, pwd []byte) option {
return func(so *sm2Instance) error {
priv, err := x.ReadPrivateKeyFromPem(p, pwd)
if err != nil {
return err
}
func NewSm2(opts ...option) (*sm2Instance, error) { so.prk = priv
return nil
}
}
func WithSm2PublicKey(p []byte) option {
return func(so *sm2Instance) error {
if len(p) == 0 {
return nil
}
pub, err := x.ReadPublicKeyFromPem(p)
if err != nil {
return err
}
so.puk = pub
return nil
}
}
func WithMode(mode int) option {
return func(so *sm2Instance) error {
so.mode = mode
return nil
}
}
func New(opts ...option) (*sm2Instance, error) {
o := &sm2Instance{ o := &sm2Instance{
mode: defaultMode, mode: defaultMode,
} }
@ -51,37 +84,6 @@ func NewSm2(opts ...option) (*sm2Instance, error) {
return o, nil return o, nil
} }
func ParseSm2PrivateKey(p []byte, pwd []byte) option {
return func(so *sm2Instance) error {
priv, err := x.ReadPrivateKeyFromPem(p, pwd)
if err != nil {
return err
}
so.prk = priv
return nil
}
}
func ParseSm2PublicKey(p []byte) option {
return func(so *sm2Instance) error {
pub, err := x.ReadPublicKeyFromPem(p)
if err != nil {
return err
}
so.puk = pub
return nil
}
}
func WithMode(mode int) option {
return func(so *sm2Instance) error {
so.mode = mode
return nil
}
}
func (o *sm2Instance) Encrypt(msg []byte) ([]byte, error) { func (o *sm2Instance) Encrypt(msg []byte) ([]byte, error) {
return s.Encrypt(o.puk, msg, rand.Reader, o.mode) return s.Encrypt(o.puk, msg, rand.Reader, o.mode)
} }

View File

@ -1,20 +1,34 @@
package crypto_test package sm2
import ( import (
"crypto/rand"
"crypto/x509" "crypto/x509"
"encoding/hex" "encoding/hex"
"encoding/pem" "encoding/pem"
"fmt" "fmt"
"testing" "testing"
"github.com/charlienet/go-mixed/crypto" "github.com/tjfoc/gmsm/sm2"
x "github.com/tjfoc/gmsm/x509"
) )
func TestPem(t *testing.T) {
key, _ := sm2.GenerateKey(rand.Reader)
prv, _ := x.WritePrivateKeyToPem(key, []byte{})
pub, _ := x.WritePublicKeyToPem(key.Public().(*sm2.PublicKey))
t.Log(x.WritePublicKeyToHex(&key.PublicKey))
t.Log(string(prv))
t.Log(string(pub))
}
func TestNewSm2(t *testing.T) { func TestNewSm2(t *testing.T) {
o, err := crypto.NewSm2() o, err := New()
t.Logf("%+v, %v", o, err) t.Logf("%+v, %v", o, err)
t.Log(crypto.NewSm2(crypto.ParseSm2PrivateKey([]byte{}, []byte{}))) t.Log(New(WithSm2PrivateKey([]byte{}, []byte{})))
msg := []byte("123456") msg := []byte("123456")
sign, err := o.Sign(msg) sign, err := o.Sign(msg)
@ -49,9 +63,9 @@ hslcifiQY8173nHtaB3R6T0PwRQTwKbpdec0dwVCpvVcdzHtivndlG0mqQ==
) )
func TestPrivatePem(t *testing.T) { func TestPrivatePem(t *testing.T) {
signer, err := crypto.NewSm2( signer, err := New(
crypto.ParseSm2PrivateKey([]byte(privPem), []byte{}), WithSm2PrivateKey([]byte(privPem), []byte{}),
crypto.ParseSm2PublicKey([]byte(pubPem))) WithSm2PublicKey([]byte(pubPem)))
t.Log(signer, err) t.Log(signer, err)
if err != nil { if err != nil {
@ -67,9 +81,9 @@ func TestPrivatePem(t *testing.T) {
} }
func TestBadPublicPem(t *testing.T) { func TestBadPublicPem(t *testing.T) {
signer, err := crypto.NewSm2( signer, err := New(
crypto.ParseSm2PrivateKey([]byte(privPem), []byte{}), WithSm2PrivateKey([]byte(privPem), []byte{}),
crypto.ParseSm2PublicKey([]byte(badPubPem))) WithSm2PublicKey([]byte(badPubPem)))
t.Log(signer, err) t.Log(signer, err)

View File

@ -1,12 +0,0 @@
package dateconv_test
import (
"testing"
"github.com/charlienet/go-mixed/dateconv"
)
func TestToday(t *testing.T) {
today := dateconv.Today()
t.Log(dateconv.TimeToString(&today))
}

View File

@ -1,44 +0,0 @@
package dateconv
import (
"time"
)
const (
layoutDate = "2006-01-02"
layoutTime = "2006-01-02 15:04:05"
layoutTimeMilli = "2006-01-02 15:04:05.000"
layoutChineseDate = "2006年01月02日"
layoutChineseTime = "2006年01月02日 15:04:05"
)
func Today() time.Time {
t := time.Now()
year, month, day := t.Date()
return time.Date(year, month, day, 0, 0, 0, 0, t.Location())
}
// 日期转换为整数(如:20211222
func DateToInt(date time.Time) int {
return date.Year()*10000 + int(date.Month())*100 + date.Day()
}
// 日期转换为字符串
func DateToString(date *time.Time) string { return formatTime(date, layoutDate) }
// 时间转换为字符串
func TimeToString(date *time.Time) string { return formatTime(date, layoutTime) }
// 日期转换为中文
func DateToChinese(t *time.Time) string { return formatTime(t, layoutChineseDate) }
// 时间转换为中文
func TimeToChinese(t *time.Time) string { return formatTime(t, layoutChineseTime) }
func formatTime(t *time.Time, f string) string {
if t == nil || t.IsZero() {
return ""
}
return t.Format(f)
}

View File

@ -1,11 +0,0 @@
package dateconv
import "time"
func ParseDuration(s string) (time.Duration, error) {
if len(s) == 0 {
return time.Duration(0), nil
}
return time.ParseDuration(s)
}

View File

@ -1,10 +0,0 @@
package dateconv
import (
"testing"
)
func TestParseDuration(t *testing.T) {
t.Log(ParseDuration(""))
t.Log(ParseDuration("abc"))
}

3
db/readme.md Normal file
View File

@ -0,0 +1,3 @@
# 数据访问层,创建
使用gorm作为数据访问层

View File

@ -0,0 +1 @@
package locker

View File

@ -0,0 +1,166 @@
package locker
import (
"context"
"errors"
"strings"
"sync"
"time"
"github.com/charlienet/go-mixed/rand"
"github.com/charlienet/go-mixed/redis"
goredis "github.com/redis/go-redis/v9"
)
const (
// 加锁(可重入)
lockCmd = `if redis.call("GET", KEYS[1]) == ARGV[1] then
redis.call("SET", KEYS[1], ARGV[1], "PX", ARGV[2])
return "OK"
else
return redis.call("SET", KEYS[1], ARGV[1], "NX", "PX", ARGV[2])
end`
// 解锁
delCmd = `if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return '0'
end`
// 延期
incrCmd = `
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('expire', KEYS[1], ARGV[2])
else
return '0'
end`
)
const (
defaultExpire = time.Second * 10
retryInterval = time.Millisecond * 10
)
var (
once sync.Once
ErrContextCancel = errors.New("context cancel")
)
type distributedlock struct {
clients []redis.Client // redis 客户端
ctx context.Context //
key string // 资源键
rand string // 随机值
unlocked bool // 是否已解锁
expire time.Duration // 过期时间
}
func NewDistributedLocker(ctx context.Context, key string, clients ...redis.Client) *distributedlock {
expire := defaultExpire
if deadline, ok := ctx.Deadline(); ok {
expire = time.Until(deadline)
}
locker := &distributedlock{
ctx: ctx,
clients: clients,
key: key,
rand: rand.Hex.Generate(24),
expire: expire,
}
return locker
}
func (locker *distributedlock) Lock() error {
for {
select {
case <-locker.ctx.Done():
return ErrContextCancel
default:
if locker.TryLock() {
return nil
}
}
time.Sleep(retryInterval)
}
}
func (locker *distributedlock) TryLock() bool {
results := locker.Eval(locker.ctx, lockCmd, []string{locker.key}, locker.rand, locker.expire.Milliseconds())
if !isSuccess(results) {
locker.Unlock()
return false
}
locker.expandLockTime()
return true
}
func (locker *distributedlock) Unlock() {
locker.Eval(locker.ctx, delCmd, []string{locker.key}, locker.rand)
locker.unlocked = true
}
func (l *distributedlock) expandLockTime() {
once.Do(func() {
go func() {
for {
time.Sleep(l.expire / 3)
if l.unlocked {
break
}
l.resetExpire()
}
}()
})
}
func (locker *distributedlock) resetExpire() {
locker.Eval(locker.ctx,
incrCmd,
[]string{locker.key},
locker.rand,
locker.expire.Seconds())
}
func (locker *distributedlock) Eval(ctx context.Context, cmd string, keys []string, args ...any) []*goredis.Cmd {
results := make([]*goredis.Cmd, 0, len(locker.clients))
var wg sync.WaitGroup
wg.Add(len(locker.clients))
for _, rdb := range locker.clients {
go func(rdb redis.Client) {
defer wg.Done()
results = append(results, rdb.Eval(ctx, cmd, keys, args...))
}(rdb)
}
wg.Wait()
return results
}
func isSuccess(results []*goredis.Cmd) bool {
successCount := 0
for _, ret := range results {
resp, err := ret.Result()
if err != nil || resp == nil {
return false
}
reply, ok := resp.(string)
if ok && strings.EqualFold(reply, "OK") {
successCount++
}
}
return successCount >= len(results)/2+1
}

View File

@ -0,0 +1,87 @@
package locker
import (
"context"
"log"
"sync"
"testing"
"time"
"github.com/charlienet/go-mixed/redis"
"github.com/charlienet/go-mixed/tests"
)
func TestDistributedLock(t *testing.T) {
tests.RunOnRedis(t, func(rdb redis.Client) {
lock := NewDistributedLocker(context.Background(), "lock_test", rdb)
lock.Lock()
lock.Unlock()
})
}
func TestConcurrence(t *testing.T) {
tests.RunOnRedis(t, func(rdb redis.Client) {
count := 5
var wg sync.WaitGroup
wg.Add(count)
for i := 0; i < count; i++ {
go func(i int) {
defer wg.Done()
locker := NewDistributedLocker(context.Background(), "lock_test", rdb)
for n := 0; n < 5; n++ {
locker.Lock()
t.Logf("协程%d获取到锁", i)
time.Sleep(time.Second)
t.Logf("协程%d释放锁", i)
locker.Unlock()
}
}(i)
}
wg.Wait()
log.Println("所有任务完成")
})
}
func TestTwoLocker(t *testing.T) {
tests.RunOnRedis(t, func(rdb redis.Client) {
l1 := NewDistributedLocker(context.Background(), "lock_test", rdb)
l2 := NewDistributedLocker(context.Background(), "lock_test", rdb)
go func() {
l1.Lock()
println("l1 获取锁")
}()
go func() {
l2.Lock()
println("l2 获取锁")
}()
time.Sleep(time.Second * 20)
l1.Unlock()
l2.Unlock()
})
}
func TestDistributediTryLock(t *testing.T) {
tests.RunOnRedis(t, func(client redis.Client) {
lock := NewDistributedLocker(context.Background(), "lock_test", client)
l := lock.TryLock()
t.Log("尝试加锁结果:", l)
time.Sleep(time.Second * 20)
lock.Unlock()
})
}
func TestLocker(t *testing.T) {
}

View File

@ -12,6 +12,19 @@ const (
defaultErrorCode = "999999" defaultErrorCode = "999999"
) )
var NotImplemented = errors.New("Not Implemented")
type Error interface {
Wraped() []error
Code() string
error
private()
}
var _ Error = &CodeError{}
type CodeError struct { type CodeError struct {
cause error // 原始错误信息 cause error // 原始错误信息
code string // 错误码 code string // 错误码
@ -70,6 +83,10 @@ func (e *CodeError) WithCause(err error) *CodeError {
return new(err, e.code, e.message) return new(err, e.code, e.message)
} }
func (e *CodeError) Wraped() []error {
return []error{}
}
func (e *CodeError) Format(s fmt.State, verb rune) { func (e *CodeError) Format(s fmt.State, verb rune) {
switch verb { switch verb {
case 'v': case 'v':
@ -86,6 +103,8 @@ func (e *CodeError) Format(s fmt.State, verb rune) {
} }
} }
func (*CodeError) private() {}
func new(err error, code string, args ...any) *CodeError { func new(err error, code string, args ...any) *CodeError {
return &CodeError{ return &CodeError{
code: code, code: code,
@ -102,7 +121,7 @@ func newf(err error, code string, format string, args ...any) *CodeError {
} }
} }
func Error(code string, args ...any) *CodeError { func ErrorWithCode(code string, args ...any) *CodeError {
return new(nil, code, args...) return new(nil, code, args...)
} }

View File

@ -16,8 +16,8 @@ const (
) )
var ( var (
globalError = errors.Error(defaultErrorCode, "全局错误对象") globalError = errors.ErrorWithCode(defaultErrorCode, "全局错误对象")
errorbyCode = errors.Error(testCode, "全局错误对象") errorbyCode = errors.ErrorWithCode(testCode, "全局错误对象")
) )
func TestPersetError(t *testing.T) { func TestPersetError(t *testing.T) {
@ -40,7 +40,7 @@ func TestWithMessage(t *testing.T) {
} }
func TestNewError(t *testing.T) { func TestNewError(t *testing.T) {
var e error = errors.Error("123456", "测试") var e error = errors.ErrorWithCode("123456", "测试")
err := e.(*errors.CodeError) err := e.(*errors.CodeError)
t.Log(e, err.Code()) t.Log(e, err.Code())
@ -58,17 +58,17 @@ func TestWithStack(t *testing.T) {
} }
func TestLogMessage(t *testing.T) { func TestLogMessage(t *testing.T) {
t.Logf("%+v", errors.Error("88888", "错误消息")) t.Logf("%+v", errors.ErrorWithCode("88888", "错误消息"))
t.Log(errors.Error("77777")) t.Log(errors.ErrorWithCode("77777"))
t.Log(errors.Error("77777", "测试")) t.Log(errors.ErrorWithCode("77777", "测试"))
} }
func TestIs(t *testing.T) { func TestIs(t *testing.T) {
code1 := "000090" code1 := "000090"
code2 := "000091" code2 := "000091"
e1 := errors.Error(code1) e1 := errors.ErrorWithCode(code1)
e2 := errors.Error(code1) e2 := errors.ErrorWithCode(code1)
e3 := errors.Error(code2) e3 := errors.ErrorWithCode(code2)
t.Log(errors.Is(e1, e2)) t.Log(errors.Is(e1, e2))
t.Log(errors.Is(e1, e3)) t.Log(errors.Is(e1, e3))

View File

@ -1,9 +1,128 @@
package expr package expr
// 如为真返回参数一,否则返回参数二 // 如为真返回参数一,否则返回参数二
func If[T any](e bool, v1, v2 T) T { func Ternary[T any](e bool, v1, v2 T) T {
if e { if e {
return v1 return v1
} }
return v2 return v2
} }
func TernaryF[T any](condition bool, ifFunc func() T, elseFunc func() T) T {
if condition {
return ifFunc()
}
return elseFunc()
}
type ifElse[T any] struct {
result T
done bool
}
func If[T any](condition bool, result T) *ifElse[T] {
if condition {
return &ifElse[T]{result, true}
}
var t T
return &ifElse[T]{t, false}
}
func IfF[T any](condition bool, resultF func() T) *ifElse[T] {
if condition {
return &ifElse[T]{resultF(), true}
}
var t T
return &ifElse[T]{t, false}
}
func (i *ifElse[T]) ElseIf(condition bool, result T) *ifElse[T] {
if !i.done && condition {
i.result = result
i.done = true
}
return i
}
func (i *ifElse[T]) ElseIfF(condition bool, resultF func() T) *ifElse[T] {
if !i.done && condition {
i.result = resultF()
i.done = true
}
return i
}
func (i *ifElse[T]) Else(result T) T {
if i.done {
return i.result
}
return result
}
func (i *ifElse[T]) ElseF(resultF func() T) T {
if i.done {
return i.result
}
return resultF()
}
type switchCase[T comparable, R any] struct {
predicate T
result R
done bool
}
func Switch[T comparable, R any](predicate T) *switchCase[T, R] {
var result R
return &switchCase[T, R]{
predicate,
result,
false,
}
}
func SwitchF[T comparable, R any](predicate func() T) *switchCase[T, R] {
return Switch[T, R](predicate())
}
func (s *switchCase[T, R]) Case(val T, result R) *switchCase[T, R] {
if !s.done && s.predicate == val {
s.result = result
s.done = true
}
return s
}
func (s *switchCase[T, R]) CaseF(val T, cb func() R) *switchCase[T, R] {
if !s.done && s.predicate == val {
s.result = cb()
s.done = true
}
return s
}
func (s *switchCase[T, R]) Default(result R) R {
if !s.done {
s.result = result
}
return s.result
}
func (s *switchCase[T, R]) DefaultF(cb func() R) R {
if !s.done {
s.result = cb()
}
return s.result
}

View File

@ -1,9 +1,31 @@
package expr package expr
import "testing" import (
"testing"
func TestIf(t *testing.T) { "github.com/stretchr/testify/assert"
)
func TestTernary(t *testing.T) {
v1 := 10 v1 := 10
v2 := 4 v2 := 4
t.Log(If(v1 > v2, v1, v2)) t.Log(Ternary(v1 > v2, v1, v2))
}
func TestIf(t *testing.T) {
is := assert.New(t)
is.Equal(1, If(true, 1).ElseIf(false, 2).Else(3))
is.Equal(1, If(true, 1).ElseIf(true, 2).Else(3))
is.Equal(2, If(false, 1).ElseIf(true, 2).Else(3))
is.Equal(3, If(false, 1).ElseIf(false, 2).Else(3))
}
func TestSwitch(t *testing.T) {
is := assert.New(t)
is.Equal(1, Switch[int, int](42).Case(42, 1).Case(1, 2).Default(3))
is.Equal(1, Switch[int, int](42).Case(42, 1).Case(42, 2).Default(3))
is.Equal(1, Switch[int, int](42).Case(1, 1).Case(42, 2).Default(3))
is.Equal(1, Switch[int, int](42).Case(1, 1).Case(1, 2).Default(3))
} }

21
file_store/file_store.go Normal file
View File

@ -0,0 +1,21 @@
package filestore
import (
"errors"
"io"
"os"
)
func New() {
f, err := os.Open("")
_ = err
_ = f
}
func Store(name string, reader io.Reader) error {
return errors.New("abc")
}

6
file_store/oss/oss.go Normal file
View File

@ -0,0 +1,6 @@
package oss
// add two int
func Add(a, b int) {
}

38
file_store/readme.md Normal file
View File

@ -0,0 +1,38 @@
# 文件存储组件
1. 支持磁盘OSSFTP等
2. 支持自定义存储
3. 支持同时存储到不同的存储目标
创建文件存储
```GO
New().WithStore(LocalDisk("D:\abc"))
```
删除文件
```GO
filestore.Delete("filename")
```
文件移动
```GO
filestore.Move("old", "new")
```
文件重命名
```GO
filestore.Rename("old", "new")
```

52
go.mod
View File

@ -1,46 +1,52 @@
module github.com/charlienet/go-mixed module github.com/charlienet/go-mixed
go 1.18 go 1.21.1
require ( require (
github.com/bits-and-blooms/bitset v1.3.0 github.com/alicebob/miniredis/v2 v2.31.0
github.com/cespare/xxhash/v2 v2.1.2 github.com/allegro/bigcache/v3 v3.1.0
github.com/go-playground/universal-translator v0.18.0 github.com/alphadose/haxmap v1.3.0
github.com/antonfisher/nested-logrus-formatter v1.3.1
github.com/bits-and-blooms/bitset v1.10.0
github.com/cespare/xxhash/v2 v2.2.0
github.com/coocood/freecache v1.2.4
github.com/dlclark/regexp2 v1.10.0
github.com/go-playground/universal-translator v0.18.1
github.com/json-iterator/go v1.1.12 github.com/json-iterator/go v1.1.12
github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f
github.com/pkg/errors v0.9.1
github.com/redis/go-redis/v9 v9.3.0
github.com/shopspring/decimal v1.3.1 github.com/shopspring/decimal v1.3.1
github.com/sirupsen/logrus v1.9.3
github.com/spaolacci/murmur3 v1.1.0 github.com/spaolacci/murmur3 v1.1.0
github.com/stretchr/testify v1.8.4
github.com/tjfoc/gmsm v1.4.1 github.com/tjfoc/gmsm v1.4.1
github.com/vmihailenco/go-tinylfu v0.2.2
github.com/vmihailenco/msgpack/v5 v5.4.1
golang.org/x/crypto v0.14.0
golang.org/x/exp v0.0.0-20231006140011-7918f672742d
golang.org/x/net v0.17.0
golang.org/x/sync v0.4.0
golang.org/x/text v0.13.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
) )
require ( require (
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239 // indirect github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239 // indirect
github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 // indirect github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 // indirect
github.com/jonboulle/clockwork v0.3.0 // indirect github.com/jonboulle/clockwork v0.4.0 // indirect
github.com/lestrrat/go-envload v0.0.0-20180220120943-6ed08b54a570 // indirect github.com/lestrrat/go-envload v0.0.0-20180220120943-6ed08b54a570 // indirect
github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 // indirect github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/tebeka/strftime v0.1.5 // indirect github.com/tebeka/strftime v0.1.5 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/yuin/gopher-lua v1.1.0 // indirect
golang.org/x/sys v0.13.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )
require (
github.com/allegro/bigcache/v3 v3.0.2
github.com/antonfisher/nested-logrus-formatter v1.3.1
github.com/coocood/freecache v1.2.1
github.com/dlclark/regexp2 v1.7.0
github.com/go-redis/redis/v8 v8.11.5
github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f
github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.9.0
github.com/stretchr/testify v1.8.0
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0
)

118
go.sum
View File

@ -1,43 +1,53 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/allegro/bigcache/v3 v3.0.2 h1:AKZCw+5eAaVyNTBmI2fgyPVJhHkdWder3O9IrprcQfI= github.com/DmitriyVTitov/size v1.5.0/go.mod h1:le6rNI4CoLQV1b9gzp1+3d7hMAD/uu2QcJ+aYbNgiU0=
github.com/allegro/bigcache/v3 v3.0.2/go.mod h1:aPyh7jEvrog9zAwx5N7+JUQX5dZTSGpxF1LAR4dr35I= github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 h1:uvdUDbHQHO85qeSydJtItA4T55Pw6BtAejd0APRJOCE=
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/miniredis/v2 v2.31.0 h1:ObEFUNlJwoIiyjxdrYF0QIDE7qXcLc7D3WpSH4c22PU=
github.com/alicebob/miniredis/v2 v2.31.0/go.mod h1:UB/T2Uztp7MlFSDakaX1sTXUv5CASoprx0wulRT6HBg=
github.com/allegro/bigcache/v3 v3.1.0 h1:H2Vp8VOvxcrB91o86fUSVJFqeuz8kpyyB02eH3bSzwk=
github.com/allegro/bigcache/v3 v3.1.0/go.mod h1:aPyh7jEvrog9zAwx5N7+JUQX5dZTSGpxF1LAR4dr35I=
github.com/alphadose/haxmap v1.3.0 h1:C/2LboOnPCZP27GmmSXOcwx360st0P8N0fTJ3voefKc=
github.com/alphadose/haxmap v1.3.0/go.mod h1:rjHw1IAqbxm0S3U5tD16GoKsiAd8FWx5BJ2IYqXwgmM=
github.com/antonfisher/nested-logrus-formatter v1.3.1 h1:NFJIr+pzwv5QLHTPyKz9UMEoHck02Q9L0FP13b/xSbQ= github.com/antonfisher/nested-logrus-formatter v1.3.1 h1:NFJIr+pzwv5QLHTPyKz9UMEoHck02Q9L0FP13b/xSbQ=
github.com/antonfisher/nested-logrus-formatter v1.3.1/go.mod h1:6WTfyWFkBc9+zyBaKIqRrg/KwMqBbodBjgbHjDz7zjA= github.com/antonfisher/nested-logrus-formatter v1.3.1/go.mod h1:6WTfyWFkBc9+zyBaKIqRrg/KwMqBbodBjgbHjDz7zjA=
github.com/bits-and-blooms/bitset v1.2.2 h1:J5gbX05GpMdBjCvQ9MteIg2KKDExr7DrgK+Yc15FvIk= github.com/bits-and-blooms/bitset v1.10.0 h1:ePXTeiPEazB5+opbv5fr8umg2R/1NlzgDsyepwsSr88=
github.com/bits-and-blooms/bitset v1.2.2/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/bits-and-blooms/bitset v1.3.0 h1:h7mv5q31cthBTd7V4kLAZaIThj1e8vPGcSqpPue9KVI= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bits-and-blooms/bitset v1.3.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/coocood/freecache v1.2.1 h1:/v1CqMq45NFH9mp/Pt142reundeBM0dVUD3osQBeu/U= github.com/coocood/freecache v1.2.4 h1:UdR6Yz/X1HW4fZOuH0Z94KwG851GWOSknua5VUbb/5M=
github.com/coocood/freecache v1.2.1/go.mod h1:RBUWa/Cy+OHdfTGFEhEuE1pMCMX51Ncizj7rthiQ3vk= github.com/coocood/freecache v1.2.4/go.mod h1:RBUWa/Cy+OHdfTGFEhEuE1pMCMX51Ncizj7rthiQ3vk=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E= github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239 h1:Ghm4eQYC0nEPnSJdVkTrXpu9KtoVCSo1hg7mtI7G9KU= github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239 h1:Ghm4eQYC0nEPnSJdVkTrXpu9KtoVCSo1hg7mtI7G9KU=
github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239/go.mod h1:Gdwt2ce0yfBxPvZrHkprdPPTTS3N5rwmLE8T22KBXlw= github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239/go.mod h1:Gdwt2ce0yfBxPvZrHkprdPPTTS3N5rwmLE8T22KBXlw=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@ -55,8 +65,8 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 h1:IPJ3dvxmJ4uczJe5YQdrYB16oTJlGSC/OyZDqUk9xX4= github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 h1:IPJ3dvxmJ4uczJe5YQdrYB16oTJlGSC/OyZDqUk9xX4=
github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869/go.mod h1:cJ6Cj7dQo+O6GJNiMx+Pa94qKj+TG8ONdKHgMNIyyag= github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869/go.mod h1:cJ6Cj7dQo+O6GJNiMx+Pa94qKj+TG8ONdKHgMNIyyag=
github.com/jonboulle/clockwork v0.3.0 h1:9BSCMi8C+0qdApAp4auwX0RkLGUjs956h0EkuQymUhg= github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4=
github.com/jonboulle/clockwork v0.3.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/lestrrat/go-envload v0.0.0-20180220120943-6ed08b54a570 h1:0iQektZGS248WXmGIYOwRXSQhD4qn3icjMpuxwO7qlo= github.com/lestrrat/go-envload v0.0.0-20180220120943-6ed08b54a570 h1:0iQektZGS248WXmGIYOwRXSQhD4qn3icjMpuxwO7qlo=
@ -70,46 +80,44 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/redis/go-redis/v9 v9.3.0 h1:RiVDjmig62jIWp7Kk4XVLs0hzV6pI3PyTnnL0cnn0u0=
github.com/redis/go-redis/v9 v9.3.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/tebeka/strftime v0.1.5 h1:1NQKN1NiQgkqd/2moD6ySP/5CoZQsKa1d3ZhJ44Jpmg= github.com/tebeka/strftime v0.1.5 h1:1NQKN1NiQgkqd/2moD6ySP/5CoZQsKa1d3ZhJ44Jpmg=
github.com/tebeka/strftime v0.1.5/go.mod h1:29/OidkoWHdEKZqzyDLUyC+LmgDgdHo4WAFCDT7D/Ig= github.com/tebeka/strftime v0.1.5/go.mod h1:29/OidkoWHdEKZqzyDLUyC+LmgDgdHo4WAFCDT7D/Ig=
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
github.com/vmihailenco/go-tinylfu v0.2.2 h1:H1eiG6HM36iniK6+21n9LLpzx1G9R3DJa2UjUjbynsI=
github.com/vmihailenco/go-tinylfu v0.2.2/go.mod h1:CutYi2Q9puTxfcolkliPq4npPuofg9N9t8JVrjzwa3Q=
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/yuin/gopher-lua v1.1.0 h1:BojcDhfyDWgU2f2TOzYK/g5p2gxMrku8oupLDqlnSqE=
github.com/yuin/gopher-lua v1.1.0/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20220713135740-79cabaa25d75 h1:x03zeu7B2B11ySp+daztnwM5oBJ/8wGUSqrwcw9L0RA= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20220713135740-79cabaa25d75/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
@ -119,26 +127,26 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e h1:NHvCuwuS43lGnYhten69ZWqi2QOj/CiDNcKbVqwVoew=
golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@ -161,10 +169,8 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

72
idGenerator/buffer.go Normal file
View File

@ -0,0 +1,72 @@
package idgenerator
import (
"sync"
"time"
"github.com/charlienet/go-mixed/idGenerator/store"
)
type obtainFunc func() (*store.Segment, error)
type doubleBuffer struct {
current *store.Segment // 当前
backup *store.Segment // 备用
obtain obtainFunc // 数据段获取函数
inFill bool // 备用缓冲填充中
isReadly bool // 备用缓冲区填充完成
mu sync.Mutex
}
func newDoubleBuffer(obtainFunc obtainFunc) *doubleBuffer {
b := &doubleBuffer{obtain: obtainFunc}
b.current, _ = b.obtain()
return b
}
func (b *doubleBuffer) allot() (int64, bool) {
if !b.inFill && b.current.IsEnding() {
go b.full() // 填充备用缓冲
}
// 缓冲区耗尽时切换
if b.current.IsEmpty() {
// 检查备用缓冲是否已经填充完成,已完成时切换,否则等待
for !b.isReadly {
time.Sleep(time.Microsecond * 100)
}
b.switchBuf()
}
return b.current.Allot(), b.current.Reback()
}
func (b *doubleBuffer) full() {
b.mu.Lock()
defer b.mu.Unlock()
if !b.inFill {
var err error
b.inFill = true
b.backup, err = b.obtain()
if err != nil {
println("填充失败:", err.Error())
panic(err)
}
b.isReadly = true
}
}
func (b *doubleBuffer) switchBuf() {
b.mu.Lock()
defer b.mu.Unlock()
if b.isReadly {
b.current, b.backup = b.backup, b.current
b.inFill = false
b.isReadly = false
}
}

View File

@ -0,0 +1,51 @@
package idgenerator
import (
"context"
"sync"
"testing"
"time"
"github.com/charlienet/go-mixed/idGenerator/store"
"github.com/charlienet/go-mixed/redis"
"github.com/charlienet/go-mixed/tests"
)
func TestBufferAlloc(t *testing.T) {
tests.RunOnDefaultRedis(t, func(rdb redis.Client) {
f := func() (*store.Segment, error) {
return store.NewRedisStore("sss", rdb).Assign(3, 99, 10)
}
b := newDoubleBuffer(f)
for i := 0; i < 80; i++ {
t.Log(b.allot())
}
})
}
func TestTimeout(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
select {
case <-ctx.Done():
println("协程退出", ctx.Err().Error())
case <-time.After(time.Second * 100):
println("协程超时")
}
}()
wg.Wait()
println("应用退出")
}

132
idGenerator/formater.go Normal file
View File

@ -0,0 +1,132 @@
package idgenerator
import (
"errors"
"math"
"strconv"
"time"
)
const (
YYYYMMDDHHmmss = "20060102150405"
YYYYMMDDHHmm = "200601021504"
YYYYMMDDHH = "2006010215"
YYYYMMDD = "20060102"
)
type Layout int
const (
Binary Layout = iota
Decimal
)
type formater interface {
MaxinalMachineCode() int64
MaximalSequence() int64
Format(machine, serial int64, reback bool) Id
}
type Id int64
func (i Id) String() string {
if i == 0 {
return ""
}
return strconv.FormatInt(int64(i), 10)
}
func (i Id) Int64() int64 {
return int64(i)
}
// 标识生成器
type generator struct {
maximalSequence int64 // 序列段的最大值
maxinalMachineCode int64 // 机器码的最大值
sequenceLength int64 // 序列段长度
machineCodeLength int64 // 机器码长度
lastTimestamp int64 // 最后回绕时间
getTimestampFunc func() int64 // 获取时间段的方法
}
func (g generator) MaxinalMachineCode() int64 {
return g.maxinalMachineCode
}
func (g generator) MaximalSequence() int64 {
return g.maximalSequence
}
func (g *generator) getTimestamp(reback bool) int64 {
newTimestamp := g.getTimestampFunc()
for reback && g.lastTimestamp == newTimestamp {
time.Sleep(time.Microsecond * 10)
newTimestamp = g.getTimestampFunc()
}
g.lastTimestamp = newTimestamp
return newTimestamp
}
type binaryFormater struct {
generator
}
func newBinaryFormatter(start int64, sequenceLength, machineCodeLength int64) (*binaryFormater, error) {
return &binaryFormater{
generator: generator{
maximalSequence: int64(-1 ^ (-1 << sequenceLength)),
maxinalMachineCode: int64(-1 ^ (-1 << machineCodeLength)),
sequenceLength: sequenceLength,
machineCodeLength: machineCodeLength,
getTimestampFunc: func() int64 { return time.Now().Unix() - start },
},
}, nil
}
func (f *binaryFormater) Format(machine, serial int64, reback bool) Id {
timestamp := f.getTimestamp(reback)
return Id(timestamp<<(f.sequenceLength+f.machineCodeLength) | machine<<f.sequenceLength | serial)
}
type decimalFormater struct {
generator
supportReback bool
}
const (
decimalMaxLength = 19
)
func newDecimalFormater(format string, serialLength, machineLength int) (*decimalFormater, error) {
if len(format)+serialLength+machineLength > decimalMaxLength {
return nil, errors.New("the data length is out of limit")
}
serialShift := int64(math.Pow10(serialLength))
machineShift := int64(math.Pow10(machineLength))
return &decimalFormater{
generator: generator{
sequenceLength: serialShift,
maximalSequence: serialShift - 1,
machineCodeLength: machineShift,
maxinalMachineCode: machineShift - 1,
getTimestampFunc: func() int64 {
now := time.Now()
v := now.Format(format)
r, _ := strconv.ParseInt(v, 10, 64)
return r
},
},
supportReback: len(format) == 14,
}, nil
}
func (f *decimalFormater) Format(machine, serial int64, reback bool) Id {
timestamp := f.getTimestamp(reback)
return Id(timestamp*f.sequenceLength*f.machineCodeLength + machine*f.sequenceLength + serial)
}

View File

@ -0,0 +1,34 @@
package idgenerator
import (
"testing"
)
func TestBinary(t *testing.T) {
f, _ := newBinaryFormatter(DefaultStartTimeStamp, 16, 12)
t.Log(f.maxinalMachineCode, f.maximalSequence)
}
func TestDecimal(t *testing.T) {
f, _ := newDecimalFormater(YYYYMMDDHHmmss, 4, 1)
t.Log(f.maxinalMachineCode, f.maxinalMachineCode)
t.Log(f.Format(22333, 9, false))
}
func TestDecimalMonth111(t *testing.T) {
f, _ := newDecimalFormater(YYYYMMDD, 4, 1)
t.Log(f.maxinalMachineCode, f.maxinalMachineCode)
t.Log(f.Format(233, 9, false))
}
func TestBinaryTimestamp(t *testing.T) {
f, _ := newBinaryFormatter(DefaultStartTimeStamp, 10, 4)
for i := 0; i < 100; i++ {
if i%7 == 0 {
t.Log(f.Format(int64(i), 0xF, true))
} else {
t.Log(f.Format(int64(i), 0xF, false))
}
}
}

124
idGenerator/generator.go Normal file
View File

@ -0,0 +1,124 @@
package idgenerator
import (
"time"
"github.com/charlienet/go-mixed/idGenerator/store"
"github.com/charlienet/go-mixed/mathx"
"github.com/charlienet/go-mixed/redis"
)
const (
defaultDoubleBufferStep int64 = 50
)
var DefaultStartTimeStamp = time.Date(2023, 1, 1, 0, 0, 0, 0, time.Local).Unix()
type opt func(*idGenerator) error
type Option struct {
TimeFormat string
SerialLength int
MachineLength int
}
type idGenerator struct {
store storage // 外部存储
formater formater // 格式化器
buffer *doubleBuffer // 序列缓冲
}
func WithMem(machineCode int64) opt {
return WithStore(store.NewMemStore(machineCode))
}
func WithRedis(key string, rdb redis.Client) opt {
return WithStore(store.NewRedisStore(key, rdb))
}
func WithStore(s storage) opt {
return func(ig *idGenerator) error {
ig.store = s
return nil
}
}
func WithDecimalFormater(format string, serialLength, machineLength int) opt {
return func(ig *idGenerator) error {
f, err := newDecimalFormater(format, serialLength, machineLength)
if err != nil {
return err
}
ig.formater = f
return nil
}
}
func WithBinaryFormatter(start int64, serialLength, machineLength int64) opt {
return func(ig *idGenerator) error {
f, err := newBinaryFormatter(start, serialLength, machineLength)
if err != nil {
return err
}
ig.formater = f
return nil
}
}
func New(opts ...opt) (*idGenerator, error) {
g := &idGenerator{}
for _, o := range opts {
err := o(g)
if err != nil {
return nil, err
}
}
if g.store == nil {
g.store = store.NewMemStore(0)
}
_, err := g.store.UpdateMachineCode(g.formater.MaxinalMachineCode()) // 初始化机器码
if err != nil {
return nil, err
}
g.buffer = newDoubleBuffer(g.obtain) // 初始化序列缓冲
return g, nil
}
func (g *idGenerator) WithRedis(key string, rdb redis.Client) *idGenerator {
return g.WithStore(store.NewRedisStore(key, rdb))
}
func (g *idGenerator) WithStore(s storage) *idGenerator {
g.store = s
return g
}
func (g *idGenerator) Next() Id {
serial, reback := g.buffer.allot()
id := g.formater.Format(g.store.MachineCode(), serial, reback)
return id
}
func (g *idGenerator) Close() {
if g.store != nil {
g.store.Close()
}
}
func (g *idGenerator) obtain() (*store.Segment, error) {
step := mathx.Min(defaultDoubleBufferStep, g.formater.MaximalSequence())
s, err := g.store.Assign(0, g.formater.MaximalSequence(), step)
if err != nil {
println("分配失败", err.Error())
}
return s, err
}

View File

@ -0,0 +1,148 @@
package idgenerator_test
import (
"sync"
"testing"
idgenerator "github.com/charlienet/go-mixed/idGenerator"
"github.com/charlienet/go-mixed/redis"
"github.com/charlienet/go-mixed/sets"
"github.com/charlienet/go-mixed/tests"
)
func TestGenerator(t *testing.T) {
tests.RunOnRedis(t, func(rdb redis.Client) {
generator, err := idgenerator.New(
idgenerator.WithDecimalFormater(idgenerator.YYYYMMDDHHmmss, 1, 1),
idgenerator.WithRedis("idgen_test", rdb))
if err != nil {
t.Fatal(err)
}
for i := 0; i < 20; i++ {
t.Log(generator.Next())
}
})
}
func TestDecimalGenerator(t *testing.T) {
tests.RunOnDefaultRedis(t, func(rdb redis.Client) {
generator, err := idgenerator.New(
idgenerator.WithDecimalFormater(idgenerator.YYYYMMDDHHmmss, 3, 1),
idgenerator.WithRedis("idgen_test", rdb))
if err != nil {
t.Fatal(err)
}
for i := 0; i < 200; i++ {
t.Log(generator.Next())
}
})
}
func TestDecimalMonth(t *testing.T) {
tests.RunOnDefaultRedis(t, func(rdb redis.Client) {
generator, err := idgenerator.New(
idgenerator.WithDecimalFormater(idgenerator.YYYYMMDD, 2, 1),
idgenerator.WithRedis("idgen_test", rdb))
if err != nil {
t.Fatal(err)
}
for i := 0; i < 105; i++ {
t.Log(generator.Next())
}
})
}
func TestParallelCreate(t *testing.T) {
tests.RunOnDefaultRedis(t, func(rdb redis.Client) {
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
g1, err := idgenerator.New(
idgenerator.WithDecimalFormater(idgenerator.YYYYMMDDHHmmss, 3, 1),
idgenerator.WithRedis("idgen_testcccc", rdb))
if err != nil {
panic(err)
}
_ = g1.Next().Int64()
}()
go func() {
defer wg.Done()
g2, err := idgenerator.New(
idgenerator.WithDecimalFormater(idgenerator.YYYYMMDDHHmmss, 3, 1),
idgenerator.WithRedis("idgen_testcccc", rdb))
if err != nil {
panic(err)
}
_ = g2.Next().Int64()
}()
wg.Wait()
})
}
func TestParallel(t *testing.T) {
set := sets.NewHashSet[int64]().Sync()
_ = set
f := func() {
tests.RunOnDefaultRedis(t, func(rdb redis.Client) {
generator, err := idgenerator.New(
idgenerator.WithDecimalFormater(idgenerator.YYYYMMDDHHmmss, 3, 1),
idgenerator.WithRedis("idgen_testcccc", rdb))
if err != nil {
t.Fatal(err)
}
defer generator.Close()
generator.Next()
for i := 0; i < 50000; i++ {
id := generator.Next().Int64()
if set.Contains(id) {
panic("生成重复")
}
set.Add(id)
}
})
}
var wg sync.WaitGroup
for i := 0; i < 6; i++ {
wg.Add(1)
go func() {
defer wg.Done()
f()
}()
}
wg.Wait()
}
func BenchmarkGenerator(b *testing.B) {
tests.RunOnDefaultRedis(b, func(rdb redis.Client) {
b.Run("bbb", func(b *testing.B) {
generator, err := idgenerator.New(
idgenerator.WithDecimalFormater(idgenerator.YYYYMMDDHHmmss, 3, 1),
idgenerator.WithRedis("idgen_test", rdb))
if err != nil {
b.Fatal(err)
}
for i := 0; i < 999; i++ {
generator.Next()
}
})
})
}

114
idGenerator/readme.md Normal file
View File

@ -0,0 +1,114 @@
id生成服务
标识生成服务,可用于分布式环境的标识生成服务,本服务可以多实例部署。
要求
1. 全局唯一: 必须保证生成的 ID 是全局性唯一的;
2. 趋势递增: 生成的 ID 需要按照某种规则有序,便于数据库的写入和排序操作;
3. 单调递增: 后生成的标识大于前面生成的标识
3. 可用性: 需要保证高并发下的可用性。
4. 自主性: 分布式环境下不依赖中心认证即可自行生成 ID
5. 安全性: 不暴露系统和业务的信息。在一些业务场景下,需要 ID 无规则或者不规则;
标识组成
1. 时间信息,从当前时间中根据模板获取数据段。
号段模式
![号段模式](assets/双缓存号段分配.webp)
批量获取标识,业务应用使用后临近剩余数量时生成新的号段,使用双标识缓冲区方式保存。在活动缓冲区使用量达到限定值时在备缓冲区放置新的号段。
号段分配器
数据结构
1. 最大序列
2. 步长
3. 版本号
分配步骤
1. 服务端按照KEY存储当前分配的最大值
2. 获取是
序列回绕当序列递增到最大值时恢复为起始值重新开始。在使用中按照业务场景同一时间段中不能耗尽序列值。当序列发生回绕时分配器返回已回绕backward
操作接口
1. 客户端注册标识,客户端使用应用名、规则(前缀、步长、阈值、初始值)进行注册。注册成功后返回标识
标识生成规则,时间段、数字序列
1. 前缀: 使用模板方式生成,生成参数包括当前时间,时间戳。
2. 位数: 表示数据序列包含的位数如4位表示序列
3. 初始值:序列初始值,序列到达最大值时返回到初始值。
标识生成统一包含时间信息,由于在分布式环境中各应用节点时间可能并不同步,可能造成应用节点生成重复。
1. 应用端获取片段时记录当前时间(T1)
2. 请求服务端分配片段
3. 服务端记录报文到达时间(T2)
4. 服务端处理完成后记录离开时间(T3)
5. 应用端接收到响应时,记录时间(T4)
时间周期延时
1. Delay = ( T4 - T1 ) - ( T3 - T2 )
2. Offset = (( T2- T1 ) + ( T3 T4 )) / 2
应用端使用延时和偏移计算当前时间
标识服务服务器多活
微信序列号生成方案
微信序列号跟用户 uin 绑定,具有以下性质:递增的 64 位整形;使用每个用户独立的 64 位 sequence 的体系,而不是用一个全局的 64 位(或更高位) sequence ,很大原因是全局唯一的 sequence 会有非常严重的申请互斥问题,不容易去实现一个高性能高可靠的架构。其实现方式包含如下两个关键点:
1步进式持久化增加一个缓存中间层内存中缓存最近一个分配出现的 sequencecur_seq以及分配上限max_seq分配 sequence 时,将 cur_seq++,与分配上限 max_seq 比较,如果 cur_seq > max_seq将分配上限提升一个步长 max_seq += step并持久化 max_seq重启时读出持久化的 max_seq赋值给 cur_seq。此种处理方式可以降低持久化的硬盘 IO 次数,可以系统的整体吞吐量。
2分号段共享存储引入号段 section 的概念uin 相邻的一段用户属于一个号段,共享一个 max_seq。该处理方式可以大幅减少 max_seq 数据的大小,同时可以进一步地降低 IO 次数。
二进制模式
按照二进制模式处理序列号生成,各数据段按照各自的位长度组装
十进制模式
缺陷
当发生序列回绕时,不同的时间序列有不同的处理。如时间序列精度为日,那么在发生回绕时认为此时的回绕不能发生。如果应用在当日内被重启,并且发生了回绕,当前机制无法检测此问题。
序列记录可以依赖于外部存储如RedisDB等。当外部存储失效时序列可能会发生冲突。此时当时间段没有前进时可能发生重复。一般认为DB为不失效存储。
需要烦精确的选择时间段和序列的相对关系,一般情况下
ID分配器工作流程
初始化
1. 创建生成器
2. 创建外部存储
3. 指定生成器生成规则,机器码长度,序列长度,序列化格式。
4. 创建双缓存,使用外部存储分配数据段。
标识分配
1. 向序列缓冲器获取下一序列
2. 向时间段分配器获取下一时间戳(传入是否回旋)
4. 由标识组装器获取
格式化器
十进制格式化,时间段格式串
二进制格式化,起始时间,时间精度

Some files were not shown because too many files have changed in this diff Show More