这时候我发现了SynCache这个接口并不需要被外部调用,在Get Set方法时,已经在内部处理了缓存。
因此我们可以直接把这个接口干掉。
针对问题二,我们可以抽象出一个Clear的接口,入参是key,清理掉cache中所有key值的消息。
type CacheServer interface {
Get(key string) ([]cacheValue, error)
Set(key string, kvalue interface{}) error
Clear(key string) error
GetAll() ([]cacheValue, error)
}
func (s *cache) Clear(key string) error {
s.GetLastWriterFromRedis()
s.clearOutTimeCache()
newCache := make([]cacheValue, 0)
for i := 0; i < len(s.Cache); i++ {
if s.Cache[i].Key != key {
newCache = append(newCache, s.Cache[i])
}
}
s.Cache = newCache
s.SetLastWriterToRedis()
return nil
}
那我们在业务层当中的调用也可以修改为Get,然后Clear。这样做有没有问题呢?
当然抽象出这个方法是没有问题的。但是在业务层中连续调用就有问题了。因为业务层可能存在并发的读写,cache在读写时都是需要加锁的(当然我现在还没加上),在业务层调用时,在Get之后,Clear之前锁是会放开的。此时如果有消息写入,并且key正好是clear的key。那么消息就会在没有被get到的情况下clear掉。
好那么说到锁我们就把锁加上。一样加入cache,New的时候加上。对外提供的Get Set Getall,Clear四个方法里加上,defer解锁。需要注意一下不要加进GetLastWriterFromRedis这些内部互相调用的方法里去,不然就直接死锁了。
type cache struct {
Name string
Cache []cacheValue
LocalIp string
Timeout time.Duration
RedisClient RedisServer
Lock sync.Mutex
}
func NewCache(name string, timeout time.Duration, localIp string, RedisClient RedisServer) CacheServer {
return &cache{
Name: name,
Cache: make([]cacheValue, 0),
Timeout: timeout,
RedisClient: RedisClient,
LocalIp: localIp,
Lock: sync.Mutex{},
}
}
s.Lock.Lock()
defer s.Lock.Unlock()
既然上面说在业务层实现会有漏洞。那么我们可以想到在cacheServer内部引入config,在New的时候传入config,这样就能满足不同业务逻辑的需求。
为了满足可选Config传入的需求,我们定义了一种配置Config的函数。
type option func(*cache)
在我们的NewCache时,必选参数还是用原来的方式传入,可选参数先给上一个缺省值,并由最后传入的options方法来修改。
func NewCache(name string, timeout time.Duration, localIp string, RedisClient RedisServer, options ...option) CacheServer {
newCache := cache{
Name: name,
Cache: make([]cacheValue, 0),
Timeout: timeout,
RedisClient: RedisClient,
LocalIp: localIp,
Lock: sync.Mutex{},
ClearAfterGet: false,
SyncFromOtherCache: false,
}
for _, option := range options {
option(&newCache)
}
return &newCache
}
这边我们有几个可选参数就定义几个方法供外部调用。
func DialClearAfterGet(sign bool) option {
return func(c *cache) {
c.ClearAfterGet = sign
}
}
func DialSyncFromOtherCache(sign bool) option {
return func(c *cache) {
c.SyncFromOtherCache = sign
}
}
在main函数中修改
MessageCache := cacheServer.NewCache("MessageCache", time.Second*5, "http://localhost:8080", RedisClient, cacheServer.DialClearAfterGet(true), cacheServer.DialSyncFromOtherCache(true))
这样我们的config配置就基本上完成了。在包内加上对config的判断。
func (s *cache) Get(key string) ([]cacheValue, error) {
s.Lock.Lock()
defer s.Lock.Unlock()
s.getLastWriterFromRedis()
s.clearOutTimeCache()
rlt := make([]cacheValue, 0)
if s.ClearAfterGet {
newCache := make([]cacheValue, 0)
for i := 0; i < len(s.Cache); i++ {
if s.Cache[i].Key == key {
rlt = append(rlt, s.Cache[i])
} else {
newCache = append(newCache, s.Cache[i])
}
}
s.Cache = newCache
} else {
for i := 0; i < len(s.Cache); i++ {
if s.Cache[i].Key == key {
rlt = append(rlt, s.Cache[i])
}
}
}
s.setLastWriterToRedis()
return rlt, nil
}
这里直接把对配置项的处理写到最后调用的方法里了。避免配置项的判断都堆叠在一起。
func (s *cache) getLastWriterFromRedis() {
if s.SyncFromOtherCache {
IP, err := s.RedisClient.Get(s.Name)
if err == nil {
if IP != s.LocalIp {
resp, err := http.Get(IP + "/syncCache")
if err != nil {
return
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
var res []cacheValue
json.Unmarshal([]byte(body), &res)
s.Cache = res
}
}
}
}
func (s *cache) setLastWriterToRedis() error {
if s.SyncFromOtherCache {
return s.RedisClient.Set(s.Name, s.LocalIp)
} else {
return nil
}
}
好了跑一下代码试了一下没有问题。
这样我们一个简单的Config配置的功能也实现了。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。