抽象缓存接口

This commit is contained in:
xing 2023-02-02 19:16:18 +08:00
parent dc04b8dc2b
commit 490abf0bf6
8 changed files with 209 additions and 174 deletions

16
cache/interface.go vendored Normal file
View File

@ -0,0 +1,16 @@
package cache
import (
"context"
"time"
)
type Cache[K comparable, V any] interface {
Get(ctx context.Context, key K) (V, bool)
Set(ctx context.Context, key K, val V, expire time.Duration)
Ttl(ctx context.Context, key K, expire time.Duration) time.Duration
Ver(ctx context.Context, key K) int
Flush(ctx context.Context)
Delete(ctx context.Context, key K)
ClearExpired(ctx context.Context, expire time.Duration)
}

189
cache/map.go vendored
View File

@ -4,42 +4,32 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"github.com/fthvgb1/wp-go/safety" "github.com/fthvgb1/wp-go/helper/slice"
"sync" "sync"
"time" "time"
) )
type MapCache[K comparable, V any] struct { type MapCache[K comparable, V any] struct {
data safety.Map[K, mapCacheStruct[V]] data Cache[K, V]
mutex *sync.Mutex mux sync.Mutex
cacheFunc func(...any) (V, error) cacheFunc func(...any) (V, error)
batchCacheFn func(...any) (map[K]V, error) batchCacheFn func(...any) (map[K]V, error)
expireTime time.Duration expireTime time.Duration
} }
func NewMapCache[K comparable, V any](expireTime time.Duration) *MapCache[K, V] {
return &MapCache[K, V]{expireTime: expireTime}
}
type mapCacheStruct[T any] struct {
setTime time.Time
incr int
data T
}
func (m *MapCache[K, V]) SetCacheFunc(fn func(...any) (V, error)) { func (m *MapCache[K, V]) SetCacheFunc(fn func(...any) (V, error)) {
m.cacheFunc = fn m.cacheFunc = fn
} }
func (m *MapCache[K, V]) GetLastSetTime(k K) (t time.Time) { func (m *MapCache[K, V]) GetLastSetTime(ctx context.Context, k K) (t time.Time) {
r, ok := m.data.Load(k) tt := m.data.Ttl(ctx, k, m.expireTime)
if ok { if tt <= 0 {
t = r.setTime return
} }
return return time.Now().Add(m.data.Ttl(ctx, k, m.expireTime))
} }
func (m *MapCache[K, V]) SetCacheBatchFunc(fn func(...any) (map[K]V, error)) { func (m *MapCache[K, V]) SetCacheBatchFn(fn func(...any) (map[K]V, error)) {
m.batchCacheFn = fn m.batchCacheFn = fn
if m.cacheFunc == nil { if m.cacheFunc == nil {
m.setCacheFn(fn) m.setCacheFn(fn)
@ -68,102 +58,56 @@ func (m *MapCache[K, V]) setCacheFn(fn func(...any) (map[K]V, error)) {
} }
} }
func NewMapCacheByFn[K comparable, V any](fn func(...any) (V, error), expireTime time.Duration) *MapCache[K, V] { func NewMemoryMapCacheByFn[K comparable, V any](fn func(...any) (V, error), expireTime time.Duration) *MapCache[K, V] {
return &MapCache[K, V]{ return &MapCache[K, V]{
mutex: &sync.Mutex{}, data: NewMemoryMapCache[K, V](),
cacheFunc: fn, cacheFunc: fn,
expireTime: expireTime, expireTime: expireTime,
data: safety.NewMap[K, mapCacheStruct[V]](), mux: sync.Mutex{},
} }
} }
func NewMapCacheByBatchFn[K comparable, V any](fn func(...any) (map[K]V, error), expireTime time.Duration) *MapCache[K, V] { func NewMemoryMapCacheByBatchFn[K comparable, V any](fn func(...any) (map[K]V, error), expireTime time.Duration) *MapCache[K, V] {
r := &MapCache[K, V]{ r := &MapCache[K, V]{
mutex: &sync.Mutex{}, data: NewMemoryMapCache[K, V](),
batchCacheFn: fn, batchCacheFn: fn,
expireTime: expireTime, expireTime: expireTime,
data: safety.NewMap[K, mapCacheStruct[V]](), mux: sync.Mutex{},
} }
r.setCacheFn(fn) r.setCacheFn(fn)
return r return r
} }
func (m *MapCache[K, V]) Flush() { func (m *MapCache[K, V]) Flush(ctx context.Context) {
m.mutex.Lock() m.mux.Lock()
defer m.mutex.Unlock() defer m.mux.Unlock()
m.data = safety.NewMap[K, mapCacheStruct[V]]() m.data.Flush(ctx)
} }
func (m *MapCache[K, V]) Get(k K) (V, bool) { func (m *MapCache[K, V]) Get(ctx context.Context, k K) (V, bool) {
r, ok := m.data.Load(k) return m.data.Get(ctx, k)
if ok {
return r.data, ok
}
var rr V
return rr, false
} }
func (m *MapCache[K, V]) Set(k K, v V) { func (m *MapCache[K, V]) Set(ctx context.Context, k K, v V) {
m.set(k, v) m.data.Set(ctx, k, v, m.expireTime)
}
func (m *MapCache[K, V]) SetByBatchFn(params ...any) error {
m.mutex.Lock()
defer m.mutex.Unlock()
r, err := m.batchCacheFn(params...)
if err != nil {
return err
}
for k, v := range r {
m.set(k, v)
}
return nil
}
func (m *MapCache[K, V]) set(k K, v V) {
data, ok := m.data.Load(k)
t := time.Now()
if !ok {
data.data = v
data.setTime = t
data.incr++
m.data.Store(k, data)
} else {
m.data.Store(k, mapCacheStruct[V]{
data: v,
setTime: t,
})
}
} }
func (m *MapCache[K, V]) GetCache(c context.Context, key K, timeout time.Duration, params ...any) (V, error) { func (m *MapCache[K, V]) GetCache(c context.Context, key K, timeout time.Duration, params ...any) (V, error) {
data, ok := m.data.Load(key) data, ok := m.data.Get(c, key)
if !ok {
data = mapCacheStruct[V]{}
}
now := time.Duration(time.Now().UnixNano())
var err error var err error
expired := time.Duration(data.setTime.UnixNano())+m.expireTime < now if !ok || m.data.Ttl(c, key, m.expireTime) <= 0 {
//todo 这里应该判断下取出的值是否为零值,不过怎么操作呢? ver := m.data.Ver(c, key)
if !ok || (ok && m.expireTime >= 0 && expired) {
t := data.incr
call := func() { call := func() {
m.mutex.Lock() m.mux.Lock()
defer m.mutex.Unlock() defer m.mux.Unlock()
da, ok := m.data.Load(key) if m.data.Ver(c, key) > ver {
if ok && da.incr > t { data, _ = m.data.Get(c, key)
return return
} else {
da = data
} }
r, er := m.cacheFunc(params...) data, err = m.cacheFunc(params...)
if err != nil { if err != nil {
err = er
return return
} }
m.set(key, r) m.Set(c, key, data)
data.data = r
} }
if timeout > 0 { if timeout > 0 {
ctx, cancel := context.WithTimeout(c, timeout) ctx, cancel := context.WithTimeout(c, timeout)
@ -183,48 +127,42 @@ func (m *MapCache[K, V]) GetCache(c context.Context, key K, timeout time.Duratio
} }
} }
return data.data, err return data, err
} }
func (m *MapCache[K, V]) GetCacheBatch(c context.Context, key []K, timeout time.Duration, params ...any) ([]V, error) { func (m *MapCache[K, V]) GetCacheBatch(c context.Context, key []K, timeout time.Duration, params ...any) ([]V, error) {
var needFlush []K
var res []V var res []V
t := 0 ver := 0
now := time.Duration(time.Now().UnixNano()) needFlush := slice.FilterAndMap(key, func(k K) (r K, ok bool) {
for _, k := range key { if _, ok := m.data.Get(c, k); !ok {
d, ok := m.data.Load(k) return k, true
if !ok {
needFlush = append(needFlush, k)
continue
} }
expired := time.Duration(d.setTime.UnixNano())+m.expireTime < now ver += m.data.Ver(c, k)
if expired { return
needFlush = append(needFlush, k) })
}
t = t + d.incr
}
var err error var err error
//todo 这里应该判断下取出的值是否为零值,不过怎么操作呢?
if len(needFlush) > 0 { if len(needFlush) > 0 {
call := func() { call := func() {
m.mutex.Lock() m.mux.Lock()
defer m.mutex.Unlock() defer m.mux.Unlock()
tt := 0
for _, dd := range needFlush { vers := slice.Reduce(needFlush, func(t K, r int) int {
if ddd, ok := m.data.Load(dd); ok { r += m.data.Ver(c, t)
tt = tt + ddd.incr return r
} }, 0)
}
if tt > t { if vers > ver {
return return
} }
r, er := m.batchCacheFn(params...) r, er := m.batchCacheFn(params...)
if err != nil { if err != nil {
err = er err = er
return return
} }
for k, v := range r { for k, v := range r {
m.set(k, v) m.Set(c, k, v)
} }
} }
if timeout > 0 { if timeout > 0 {
@ -244,23 +182,14 @@ func (m *MapCache[K, V]) GetCacheBatch(c context.Context, key []K, timeout time.
call() call()
} }
} }
for _, k := range key { res = slice.FilterAndMap(key, func(k K) (V, bool) {
d, ok := m.data.Load(k) return m.data.Get(c, k)
if ok { })
res = append(res, d.data)
}
}
return res, err return res, err
} }
func (m *MapCache[K, V]) ClearExpired() { func (m *MapCache[K, V]) ClearExpired(ctx context.Context) {
now := time.Duration(time.Now().UnixNano()) m.mux.Lock()
m.mutex.Lock() defer m.mux.Unlock()
defer m.mutex.Unlock() m.data.ClearExpired(ctx, m.expireTime)
m.data.Range(func(k K, v mapCacheStruct[V]) bool {
if now > time.Duration(v.setTime.UnixNano())+m.expireTime {
m.data.Delete(k)
}
return true
})
} }

82
cache/memorymapcache.go vendored Normal file
View File

@ -0,0 +1,82 @@
package cache
import (
"context"
"github.com/fthvgb1/wp-go/helper/number"
"github.com/fthvgb1/wp-go/safety"
"time"
)
type MemoryMapCache[K comparable, V any] struct {
safety.Map[K, mapVal[V]]
}
func NewMemoryMapCache[K comparable, V any]() *MemoryMapCache[K, V] {
return &MemoryMapCache[K, V]{Map: safety.NewMap[K, mapVal[V]]()}
}
type mapVal[T any] struct {
setTime time.Time
ver int
data T
}
func (m *MemoryMapCache[K, V]) Get(_ context.Context, key K) (r V, ok bool) {
v, ok := m.Load(key)
if ok {
return v.data, true
}
return
}
func (m *MemoryMapCache[K, V]) Set(_ context.Context, key K, val V, _ time.Duration) {
v, ok := m.Load(key)
t := time.Now()
if ok {
v.data = val
v.setTime = t
v.ver++
} else {
v = mapVal[V]{
setTime: t,
ver: 1,
data: val,
}
}
m.Store(key, v)
}
func (m *MemoryMapCache[K, V]) Ttl(_ context.Context, key K, expire time.Duration) time.Duration {
v, ok := m.Load(key)
if !ok {
return -1
}
return number.Max(time.Duration(0), expire-time.Duration(time.Now().UnixNano()-v.setTime.UnixNano()))
}
func (m *MemoryMapCache[K, V]) Ver(_ context.Context, key K) int {
v, ok := m.Load(key)
if !ok {
return -1
}
return v.ver
}
func (m *MemoryMapCache[K, V]) Flush(context.Context) {
m.Map = safety.NewMap[K, mapVal[V]]()
}
func (m *MemoryMapCache[K, V]) Delete(_ context.Context, key K) {
m.Map.Delete(key)
}
func (m *MemoryMapCache[K, V]) ClearExpired(_ context.Context, expire time.Duration) {
now := time.Duration(time.Now().UnixNano())
m.Range(func(k K, v mapVal[V]) bool {
if now > time.Duration(v.setTime.UnixNano())+expire {
m.Map.Delete(k)
}
return true
})
}

View File

@ -111,7 +111,7 @@ func PostComment(c *gin.Context) {
err = er err = er
return return
} }
cache.NewCommentCache().Set(up.RawQuery, string(s)) cache.NewCommentCache().Set(c, up.RawQuery, string(s))
c.Redirect(http.StatusFound, res.Header.Get("Location")) c.Redirect(http.StatusFound, res.Header.Get("Location"))
return return
} }

View File

@ -76,7 +76,7 @@ func Detail(c *gin.Context) {
if post.PostPassword != "" && pw != post.PostPassword { if post.PostPassword != "" && pw != post.PostPassword {
plugins.PasswdProjectContent(&post) plugins.PasswdProjectContent(&post)
showComment = false showComment = false
} else if s, ok := cache.NewCommentCache().Get(c.Request.URL.RawQuery); ok && s != "" && (post.PostPassword == "" || post.PostPassword != "" && pw == post.PostPassword) { } else if s, ok := cache.NewCommentCache().Get(c, c.Request.URL.RawQuery); ok && s != "" && (post.PostPassword == "" || post.PostPassword != "" && pw == post.PostPassword) {
c.Writer.WriteHeader(http.StatusOK) c.Writer.WriteHeader(http.StatusOK)
c.Writer.Header().Set("Content-Type", "text/html; charset=utf-8") c.Writer.Header().Set("Content-Type", "text/html; charset=utf-8")
_, err = c.Writer.WriteString(s) _, err = c.Writer.WriteString(s)

View File

@ -50,7 +50,7 @@ func setFeed(s string, c *gin.Context, t time.Time) {
func PostFeed(c *gin.Context) { func PostFeed(c *gin.Context) {
id := c.Param("id") id := c.Param("id")
if !isCacheExpired(c, cache.PostFeedCache().GetLastSetTime(id)) { if !isCacheExpired(c, cache.PostFeedCache().GetLastSetTime(c, id)) {
c.Status(http.StatusNotModified) c.Status(http.StatusNotModified)
} else { } else {
s, err := cache.PostFeedCache().GetCache(c, id, time.Second, c, id) s, err := cache.PostFeedCache().GetCache(c, id, time.Second, c, id)
@ -60,7 +60,7 @@ func PostFeed(c *gin.Context) {
c.Error(err) c.Error(err)
return return
} }
setFeed(s, c, cache.PostFeedCache().GetLastSetTime(id)) setFeed(s, c, cache.PostFeedCache().GetLastSetTime(c, id))
} }
} }

View File

@ -43,6 +43,8 @@ var allUsernameCache *cache.VarCache[map[string]struct{}]
var headerImagesCache *cache.MapCache[string, []models.PostThumbnail] var headerImagesCache *cache.MapCache[string, []models.PostThumbnail]
var ctx context.Context
func InitActionsCommonCache() { func InitActionsCommonCache() {
c := config.Conf.Load() c := config.Conf.Load()
archivesCaches = &Arch{ archivesCaches = &Arch{
@ -50,17 +52,17 @@ func InitActionsCommonCache() {
setCacheFunc: dao.Archives, setCacheFunc: dao.Archives,
} }
searchPostIdsCache = cache.NewMapCacheByFn[string](dao.SearchPostIds, c.SearchPostCacheTime) searchPostIdsCache = cache.NewMemoryMapCacheByFn[string](dao.SearchPostIds, c.SearchPostCacheTime)
postListIdsCache = cache.NewMapCacheByFn[string](dao.SearchPostIds, c.PostListCacheTime) postListIdsCache = cache.NewMemoryMapCacheByFn[string](dao.SearchPostIds, c.PostListCacheTime)
monthPostsCache = cache.NewMapCacheByFn[string](dao.MonthPost, c.MonthPostCacheTime) monthPostsCache = cache.NewMemoryMapCacheByFn[string](dao.MonthPost, c.MonthPostCacheTime)
postContextCache = cache.NewMapCacheByFn[uint64](dao.GetPostContext, c.ContextPostCacheTime) postContextCache = cache.NewMemoryMapCacheByFn[uint64](dao.GetPostContext, c.ContextPostCacheTime)
postsCache = cache.NewMapCacheByBatchFn(dao.GetPostsByIds, c.PostDataCacheTime) postsCache = cache.NewMemoryMapCacheByBatchFn(dao.GetPostsByIds, c.PostDataCacheTime)
postMetaCache = cache.NewMapCacheByBatchFn(dao.GetPostMetaByPostIds, c.PostDataCacheTime) postMetaCache = cache.NewMemoryMapCacheByBatchFn(dao.GetPostMetaByPostIds, c.PostDataCacheTime)
categoryAndTagsCaches = cache.NewVarCache(dao.CategoriesAndTags, c.CategoryCacheTime) categoryAndTagsCaches = cache.NewVarCache(dao.CategoriesAndTags, c.CategoryCacheTime)
@ -68,58 +70,61 @@ func InitActionsCommonCache() {
recentCommentsCaches = cache.NewVarCache(dao.RecentComments, c.RecentCommentsCacheTime) recentCommentsCaches = cache.NewVarCache(dao.RecentComments, c.RecentCommentsCacheTime)
postCommentCaches = cache.NewMapCacheByFn[uint64](dao.PostComments, c.PostCommentsCacheTime) postCommentCaches = cache.NewMemoryMapCacheByFn[uint64](dao.PostComments, c.PostCommentsCacheTime)
maxPostIdCache = cache.NewVarCache(dao.GetMaxPostId, c.MaxPostIdCacheTime) maxPostIdCache = cache.NewVarCache(dao.GetMaxPostId, c.MaxPostIdCacheTime)
usersCache = cache.NewMapCacheByFn[uint64](dao.GetUserById, c.UserInfoCacheTime) usersCache = cache.NewMemoryMapCacheByFn[uint64](dao.GetUserById, c.UserInfoCacheTime)
usersNameCache = cache.NewMapCacheByFn[string](dao.GetUserByName, c.UserInfoCacheTime) usersNameCache = cache.NewMemoryMapCacheByFn[string](dao.GetUserByName, c.UserInfoCacheTime)
commentsCache = cache.NewMapCacheByBatchFn(dao.GetCommentByIds, c.CommentsCacheTime) commentsCache = cache.NewMemoryMapCacheByBatchFn(dao.GetCommentByIds, c.CommentsCacheTime)
feedCache = cache.NewVarCache(feed, time.Hour) feedCache = cache.NewVarCache(feed, time.Hour)
postFeedCache = cache.NewMapCacheByFn[string](postFeed, time.Hour) postFeedCache = cache.NewMemoryMapCacheByFn[string](postFeed, time.Hour)
commentsFeedCache = cache.NewVarCache(commentsFeed, time.Hour) commentsFeedCache = cache.NewVarCache(commentsFeed, time.Hour)
newCommentCache = cache.NewMapCacheByFn[string, string](nil, 15*time.Minute) newCommentCache = cache.NewMemoryMapCacheByFn[string, string](nil, 15*time.Minute)
allUsernameCache = cache.NewVarCache(dao.AllUsername, c.UserInfoCacheTime) allUsernameCache = cache.NewVarCache(dao.AllUsername, c.UserInfoCacheTime)
headerImagesCache = cache.NewMapCacheByFn[string](getHeaderImages, c.ThemeHeaderImagCacheTime) headerImagesCache = cache.NewMemoryMapCacheByFn[string](getHeaderImages, c.ThemeHeaderImagCacheTime)
ctx = context.Background()
InitFeed() InitFeed()
} }
func ClearCache() { func ClearCache() {
searchPostIdsCache.ClearExpired() searchPostIdsCache.ClearExpired(ctx)
postsCache.ClearExpired() searchPostIdsCache.ClearExpired(ctx)
postMetaCache.ClearExpired() postsCache.ClearExpired(ctx)
postListIdsCache.ClearExpired() postMetaCache.ClearExpired(ctx)
monthPostsCache.ClearExpired() postListIdsCache.ClearExpired(ctx)
postContextCache.ClearExpired() monthPostsCache.ClearExpired(ctx)
usersCache.ClearExpired() postContextCache.ClearExpired(ctx)
commentsCache.ClearExpired() usersCache.ClearExpired(ctx)
usersNameCache.ClearExpired() commentsCache.ClearExpired(ctx)
postFeedCache.ClearExpired() usersNameCache.ClearExpired(ctx)
newCommentCache.ClearExpired() postFeedCache.ClearExpired(ctx)
headerImagesCache.ClearExpired() newCommentCache.ClearExpired(ctx)
headerImagesCache.ClearExpired(ctx)
} }
func FlushCache() { func FlushCache() {
searchPostIdsCache.Flush() searchPostIdsCache.Flush(ctx)
postsCache.Flush() postsCache.Flush(ctx)
postMetaCache.Flush() postMetaCache.Flush(ctx)
postListIdsCache.Flush() postListIdsCache.Flush(ctx)
monthPostsCache.Flush() monthPostsCache.Flush(ctx)
postContextCache.Flush() postContextCache.Flush(ctx)
usersCache.Flush() usersCache.Flush(ctx)
commentsCache.Flush() commentsCache.Flush(ctx)
usersCache.Flush() usersCache.Flush(ctx)
postFeedCache.Flush() postFeedCache.Flush(ctx)
newCommentCache.Flush() newCommentCache.Flush(ctx)
headerImagesCache.Flush() headerImagesCache.Flush(ctx)
} }
func Archives(ctx context.Context) (r []models.PostArchive) { func Archives(ctx context.Context) (r []models.PostArchive) {

View File

@ -1,6 +1,7 @@
package plugins package plugins
import ( import (
"context"
"fmt" "fmt"
"github.com/fthvgb1/wp-go/cache" "github.com/fthvgb1/wp-go/cache"
"github.com/fthvgb1/wp-go/internal/pkg/config" "github.com/fthvgb1/wp-go/internal/pkg/config"
@ -12,16 +13,18 @@ import (
) )
var digestCache *cache.MapCache[uint64, string] var digestCache *cache.MapCache[uint64, string]
var ctx context.Context
func InitDigestCache() { func InitDigestCache() {
digestCache = cache.NewMapCacheByFn[uint64](digestRaw, config.Conf.Load().DigestCacheTime) ctx = context.Background()
digestCache = cache.NewMemoryMapCacheByFn[uint64](digestRaw, config.Conf.Load().DigestCacheTime)
} }
func ClearDigestCache() { func ClearDigestCache() {
digestCache.ClearExpired() digestCache.ClearExpired(ctx)
} }
func FlushCache() { func FlushCache() {
digestCache.Flush() digestCache.Flush(ctx)
} }
func digestRaw(arg ...any) (string, error) { func digestRaw(arg ...any) (string, error) {