Open geektutu opened 4 years ago
写的很好,个人感觉Getter可以不要,用户直接传入匿名函数即可
@wen-nan 请问一下,groups的设置,感觉没有使用到呀,既然有多个Group,Get()方法还是查找特定key,而不是查找指定group的特定key。
group确定了,查找key就是该group的缓存呀
@PenguinCats NewGroup 是否需要判断 group 是否已经存在?
好像是这样,否则就变成了覆盖了,相应的数据不就丢失了?
var dbtest = map[string][]byte{
"Tom": []byte("630"),
"Jack": []byte("589"),
"Sam": []byte("567"),
}
func TestGet(t *testing.T) {
loadCounts := make(map[string]int, len(dbtest))
gee := NewGroup("scores", 2<<10, GetterFunc(
func(key string) ([]byte, error) {
log.Println("[SlowDB] search key", key)
if v, ok := dbtest[key]; ok {
if _, ok := loadCounts[key]; !ok {
loadCounts[key] = 0
}
loadCounts[key]++
return v, nil
}
return nil, fmt.Errorf("%s not exist", key)
}))`
for k, v := range dbtest {
if view, err := gee.Get(k); err == nil || view.String() == string(v) {
str := "1000"
view.b = []byte(str) //getLocally中,value := ByteView{b: bytes},切片没有copy,赋值操作结果没有影响到db???
fmt.Println("sucess")
}
if view, err := gee.Get(k); err != nil || view.String() != string(v) {
t.Fatal("failed to get value of Tom")
}
if _, err := gee.Get(k); err != nil || loadCounts[k] > 1 {
t.Fatalf("cache %s miss", k)
}
}
for k, v := range dbtest {
fmt.Println(k, string(v))
}
if view, err := gee.Get("unknown"); err == nil {
t.Fatalf("the value of unknow should be empty, but %s got", view)
}
}```
修改getLocally中,value := ByteView{b: bytes},不使用copy,切片没有copy,赋值操作结果没有影响到dbtest,有疑问
@liujing-siyang
因为 []byte(str)
生成了一个新的切片,所以不会改变源数据
@DurantVivado 写的很好,个人感觉Getter可以不要,用户直接传入匿名函数即可
确实 直接传函数 好理解一点 不会那么绕。。不过这里能体现 接口型函数的设计吧
这个group 可以类比为redis中的db吗
@ZAKLLL 这个group 可以类比为redis中的db吗
等同 redis 中的 db
@whoisapig 为什么不在接受操作指令执行前,通过 channe 限制处理的单线程 而是通过锁来做并发啊?
缓存没有命中的时候,调用回调函数获取数据是不是应该加个锁避免重复获取?
@LLoyou00 缓存没有命中的时候,调用回调函数获取数据是不是应该加个锁避免重复获取?
应该是需要加锁的,同步的数据获取操作可能会消耗掉不少时间,这个可以交给传入的接口实现,也可以添加同步逻辑,相同的key之间使用channel同步,具体实现可以参考Go语言圣经9.7节
var set = make(map[int]bool, 0)
func printOnce(num int) {
if _, exist := set[num]; !exist {
fmt.Println(num)
}
set[num] = true
}
改成如下代码,是不是更优雅一些 ^_^
var set = make(map[int]struct{}, 0) // value 类型由 bool 改为 struct{},内存占用更少
func printOnce(num int) {
if _, exist := set[num]; exist {
return // 如果key存在直接返回,不存在再打印和添加
}
fmt.Println(num)
set[num] = stuct{}{}
}
ByteView结构体在定义方法的时候可以用指针吗?比如
func (v ByteView) ByteSlice() []byte {
return cloneBytes(v.b)
}
修改为
func (v *ByteView) ByteSlice() []byte {
return cloneBytes(v.b)
}
@ZyhHelen
var set = make(map[int]bool, 0) func printOnce(num int) { if _, exist := set[num]; !exist { fmt.Println(num) } set[num] = true } 改成如下代码,是不是更优雅一些 ^_^ var set = make(map[int]struct{}, 0) // value 类型由 bool 改为 struct{},内存占用更少 func printOnce(num int) { if _, exist := set[num]; exist { return // 如果key存在直接返回,不存在再打印和添加 } fmt.Println(num) set[num] = stuct{}{} }
并不好,可读性并不强,有点多此一举的感觉.
@RookieCoder99 ByteView结构体在定义方法的时候可以用指针吗?比如
func (v ByteView) ByteSlice() []byte { return cloneBytes(v.b) }
修改为
func (v *ByteView) ByteSlice() []byte { return cloneBytes(v.b) }
无所谓 都可以
为什么不用读写锁而是使用互斥锁呢?缓存不应该是读多写少吗
@OSinoooO 为什么不用读写锁而是使用互斥锁呢?缓存不应该是读多写少吗
sync.Mutex 与sync.RWMutex是不同的,sync.RWMutex的加锁解锁应该是读写锁。
开头的示例是不是也能用sync.Map
来处理:
package main
import (
"fmt"
"sync"
"time"
)
var set sync.Map
func printOne(num int){
if _,exist:=set.Load(num);!exist{
fmt.Println(num)
}
set.Store(num,true)
}
func main() {
for i:=0;i<10;i++{
go printOne(100)
}
time.Sleep(time.Second)
}
@RookieCoder99 ByteView结构体在定义方法的时候可以用指针吗?比如
func (v ByteView) ByteSlice() []byte { return cloneBytes(v.b) }
修改为
func (v *ByteView) ByteSlice() []byte { return cloneBytes(v.b) }
无所谓 都可以
我认为不是都可以的,不应该使用指针receiver 而要用value receiver, v ByteView means that the method operates on a copy of the ByteView instance. Any modifications made to v inside the method will not affect the original ByteView instance. 这才符合设计逻辑和大佬说的“通过 ByteSlice() 或 String() 方法取到缓存值的副本。只读属性,是设计 ByteView 的主要目的之一”
https://geektutu.com/post/geecache-day2.html
7天用 Go语言/golang 从零实现分布式缓存 GeeCache 教程(7 days implement golang distributed cache from scratch tutorial),动手写分布式缓存,参照 groupcache 的实现。本文介绍了 sync.Mutex 互斥锁的使用,并发控制 LRU 缓存。实现 GeeCache 核心数据结构 Group,缓存不存在时,调用回调函数(callback)获取源数据。