This commit introduces several architectural improvements and optimizations across the codebase: - **Performance & Reliability**: - Implemented Redis pipelining in `ConversationCache.CacheMessage` to reduce network round-trips. - Added a circuit breaker to the JPush client to prevent cascading failures. - Introduced batch deletion and batch member addition capabilities in repositories. - Added message idempotency support using `client_msg_id` and a Redis-based cache. - Optimized WebSocket handling with connection limits (total and per-user) and improved error logging. - **Code Refactoring**: - Refactored `Router` to use a `RouterDeps` struct, simplifying the constructor and improving maintainability. - Unified model ID generation logic using new `id_helper.go` (supporting UUID and Snowflake). - Standardized JSON serialization/deserialization in models using `json_helper.go`. - Refactored DTO conversion logic, specifically for `UserResponse` (using functional options) and `Report` responses. - Removed redundant/deprecated DTOs like `PostDetailResponse` and `TradeItemDetailResponse`. - **Cache Improvements**: - Enhanced `LayeredCache` with `SetRaw` to avoid double-encoding when promoting values from Redis to local cache. - Added `DeleteBatch` support to the cache interface. - **Other Changes**: - Cleaned up `config.go` by removing redundant default values and explicit environment variable overrides. - Improved WebSocket registration flow to handle connection limits gracefully.
177 lines
3.6 KiB
Go
177 lines
3.6 KiB
Go
package cache
|
|
|
|
import (
|
|
"math"
|
|
"runtime"
|
|
"time"
|
|
|
|
"github.com/cespare/xxhash/v2"
|
|
)
|
|
|
|
type LRUStriped struct {
|
|
buckets []*LRU
|
|
name string
|
|
}
|
|
|
|
type LRUStripedOptions struct {
|
|
Size int
|
|
DefaultExpiry time.Duration
|
|
Name string
|
|
Buckets int
|
|
}
|
|
|
|
func NewLRUStriped(opts *LRUStripedOptions) *LRUStriped {
|
|
if opts.Buckets <= 0 {
|
|
opts.Buckets = runtime.NumCPU()
|
|
}
|
|
if opts.Buckets < 4 {
|
|
opts.Buckets = 4
|
|
}
|
|
if opts.Size < opts.Buckets {
|
|
opts.Size = opts.Buckets * 100
|
|
}
|
|
|
|
opts.Size += int(math.Ceil(float64(opts.Size) * 10.0 / 100.0))
|
|
bucketSize := (opts.Size / opts.Buckets) + (opts.Size % opts.Buckets)
|
|
|
|
buckets := make([]*LRU, opts.Buckets)
|
|
for i := 0; i < opts.Buckets; i++ {
|
|
buckets[i] = NewLRU(&LRUOptions{
|
|
Size: bucketSize,
|
|
DefaultExpiry: opts.DefaultExpiry,
|
|
Name: opts.Name,
|
|
})
|
|
}
|
|
|
|
return &LRUStriped{
|
|
buckets: buckets,
|
|
name: opts.Name,
|
|
}
|
|
}
|
|
|
|
func (l *LRUStriped) hashKey(key string) uint64 {
|
|
return xxhash.Sum64String(key)
|
|
}
|
|
|
|
func (l *LRUStriped) keyBucket(key string) *LRU {
|
|
return l.buckets[l.hashKey(key)%uint64(len(l.buckets))]
|
|
}
|
|
|
|
func (l *LRUStriped) Set(key string, value any, ttl time.Duration) {
|
|
l.keyBucket(key).Set(key, value, ttl)
|
|
}
|
|
|
|
func (l *LRUStriped) Get(key string) (any, bool) {
|
|
return l.keyBucket(key).Get(key)
|
|
}
|
|
|
|
func (l *LRUStriped) Delete(key string) {
|
|
l.keyBucket(key).Delete(key)
|
|
}
|
|
|
|
func (l *LRUStriped) DeleteByPrefix(prefix string) {
|
|
for _, bucket := range l.buckets {
|
|
bucket.DeleteByPrefix(prefix)
|
|
}
|
|
}
|
|
|
|
func (l *LRUStriped) Clear() {
|
|
for _, bucket := range l.buckets {
|
|
bucket.Clear()
|
|
}
|
|
}
|
|
|
|
func (l *LRUStriped) Exists(key string) bool {
|
|
return l.keyBucket(key).Exists(key)
|
|
}
|
|
|
|
func (l *LRUStriped) Increment(key string) int64 {
|
|
return l.keyBucket(key).Increment(key)
|
|
}
|
|
|
|
func (l *LRUStriped) IncrementBy(key string, value int64) int64 {
|
|
return l.keyBucket(key).IncrementBy(key, value)
|
|
}
|
|
|
|
func (l *LRUStriped) Len() int {
|
|
var total int
|
|
for _, bucket := range l.buckets {
|
|
total += bucket.Len()
|
|
}
|
|
return total
|
|
}
|
|
|
|
func (l *LRUStriped) Name() string {
|
|
return l.name
|
|
}
|
|
|
|
func (l *LRUStriped) DeleteMulti(keys []string) error {
|
|
for _, key := range keys {
|
|
l.Delete(key)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (l *LRUStriped) DeleteBatch(keys []string) {
|
|
for _, key := range keys {
|
|
l.Delete(key)
|
|
}
|
|
}
|
|
|
|
func (l *LRUStriped) SetRaw(key string, raw []byte, ttl time.Duration) {
|
|
l.keyBucket(key).SetRaw(key, raw, ttl)
|
|
}
|
|
|
|
func (l *LRUStriped) GetMulti(keys []string, values []any) []error {
|
|
errs := make([]error, len(values))
|
|
for i, key := range keys {
|
|
if val, ok := l.Get(key); ok {
|
|
values[i] = val
|
|
} else {
|
|
errs[i] = ErrKeyNotFound
|
|
}
|
|
}
|
|
return errs
|
|
}
|
|
|
|
func (l *LRUStriped) SetMulti(items map[string]any, ttl time.Duration) error {
|
|
for key, value := range items {
|
|
l.Set(key, value, ttl)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (l *LRUStriped) Keys() []string {
|
|
keys := make([]string, 0)
|
|
for _, bucket := range l.buckets {
|
|
bucket.lock.RLock()
|
|
for key, ent := range bucket.items {
|
|
e := ent.Value.(*lruEntry)
|
|
if e.generation == bucket.currentGeneration && (e.expires.IsZero() || !time.Now().After(e.expires)) {
|
|
keys = append(keys, key)
|
|
}
|
|
}
|
|
bucket.lock.RUnlock()
|
|
}
|
|
return keys
|
|
}
|
|
|
|
type LRUStripedInterface interface {
|
|
Set(key string, value any, ttl time.Duration)
|
|
Get(key string) (any, bool)
|
|
Delete(key string)
|
|
DeleteByPrefix(prefix string)
|
|
Clear()
|
|
Exists(key string) bool
|
|
Increment(key string) int64
|
|
IncrementBy(key string, value int64) int64
|
|
Len() int
|
|
Name() string
|
|
DeleteMulti(keys []string) error
|
|
GetMulti(keys []string, values []any) []error
|
|
SetMulti(items map[string]any, ttl time.Duration) error
|
|
Keys() []string
|
|
}
|
|
|
|
var _ LRUStripedInterface = (*LRUStriped)(nil)
|