抽象缓存接口

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

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
return
}
cache.NewCommentCache().Set(up.RawQuery, string(s))
cache.NewCommentCache().Set(c, up.RawQuery, string(s))
c.Redirect(http.StatusFound, res.Header.Get("Location"))
return
}

View File

@ -76,7 +76,7 @@ func Detail(c *gin.Context) {
if post.PostPassword != "" && pw != post.PostPassword {
plugins.PasswdProjectContent(&post)
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.Header().Set("Content-Type", "text/html; charset=utf-8")
_, 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) {
id := c.Param("id")
if !isCacheExpired(c, cache.PostFeedCache().GetLastSetTime(id)) {
if !isCacheExpired(c, cache.PostFeedCache().GetLastSetTime(c, id)) {
c.Status(http.StatusNotModified)
} else {
s, err := cache.PostFeedCache().GetCache(c, id, time.Second, c, id)
@ -60,7 +60,7 @@ func PostFeed(c *gin.Context) {
c.Error(err)
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 ctx context.Context
func InitActionsCommonCache() {
c := config.Conf.Load()
archivesCaches = &Arch{
@ -50,17 +52,17 @@ func InitActionsCommonCache() {
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)
@ -68,58 +70,61 @@ func InitActionsCommonCache() {
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)
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)
postFeedCache = cache.NewMapCacheByFn[string](postFeed, time.Hour)
postFeedCache = cache.NewMemoryMapCacheByFn[string](postFeed, 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)
headerImagesCache = cache.NewMapCacheByFn[string](getHeaderImages, c.ThemeHeaderImagCacheTime)
headerImagesCache = cache.NewMemoryMapCacheByFn[string](getHeaderImages, c.ThemeHeaderImagCacheTime)
ctx = context.Background()
InitFeed()
}
func ClearCache() {
searchPostIdsCache.ClearExpired()
postsCache.ClearExpired()
postMetaCache.ClearExpired()
postListIdsCache.ClearExpired()
monthPostsCache.ClearExpired()
postContextCache.ClearExpired()
usersCache.ClearExpired()
commentsCache.ClearExpired()
usersNameCache.ClearExpired()
postFeedCache.ClearExpired()
newCommentCache.ClearExpired()
headerImagesCache.ClearExpired()
searchPostIdsCache.ClearExpired(ctx)
searchPostIdsCache.ClearExpired(ctx)
postsCache.ClearExpired(ctx)
postMetaCache.ClearExpired(ctx)
postListIdsCache.ClearExpired(ctx)
monthPostsCache.ClearExpired(ctx)
postContextCache.ClearExpired(ctx)
usersCache.ClearExpired(ctx)
commentsCache.ClearExpired(ctx)
usersNameCache.ClearExpired(ctx)
postFeedCache.ClearExpired(ctx)
newCommentCache.ClearExpired(ctx)
headerImagesCache.ClearExpired(ctx)
}
func FlushCache() {
searchPostIdsCache.Flush()
postsCache.Flush()
postMetaCache.Flush()
postListIdsCache.Flush()
monthPostsCache.Flush()
postContextCache.Flush()
usersCache.Flush()
commentsCache.Flush()
usersCache.Flush()
postFeedCache.Flush()
newCommentCache.Flush()
headerImagesCache.Flush()
searchPostIdsCache.Flush(ctx)
postsCache.Flush(ctx)
postMetaCache.Flush(ctx)
postListIdsCache.Flush(ctx)
monthPostsCache.Flush(ctx)
postContextCache.Flush(ctx)
usersCache.Flush(ctx)
commentsCache.Flush(ctx)
usersCache.Flush(ctx)
postFeedCache.Flush(ctx)
newCommentCache.Flush(ctx)
headerImagesCache.Flush(ctx)
}
func Archives(ctx context.Context) (r []models.PostArchive) {

View File

@ -1,6 +1,7 @@
package plugins
import (
"context"
"fmt"
"github.com/fthvgb1/wp-go/cache"
"github.com/fthvgb1/wp-go/internal/pkg/config"
@ -12,16 +13,18 @@ import (
)
var digestCache *cache.MapCache[uint64, string]
var ctx context.Context
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() {
digestCache.ClearExpired()
digestCache.ClearExpired(ctx)
}
func FlushCache() {
digestCache.Flush()
digestCache.Flush(ctx)
}
func digestRaw(arg ...any) (string, error) {