geektutu / blog

极客兔兔的博客,Coding Coding 创建有趣的开源项目。
https://geektutu.com
Apache License 2.0
168 stars 21 forks source link

动手写分布式缓存 - GeeCache第二天 单机并发缓存 | 极客兔兔 #64

Open geektutu opened 4 years ago

geektutu commented 4 years ago

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)获取源数据。

DurantVivado commented 2 years ago

写的很好,个人感觉Getter可以不要,用户直接传入匿名函数即可

liujing-siyang commented 2 years ago

@wen-nan 请问一下,groups的设置,感觉没有使用到呀,既然有多个Group,Get()方法还是查找特定key,而不是查找指定group的特定key。

group确定了,查找key就是该group的缓存呀

liujing-siyang commented 2 years ago

@PenguinCats NewGroup 是否需要判断 group 是否已经存在?

好像是这样,否则就变成了覆盖了,相应的数据不就丢失了?

liujing-siyang commented 2 years ago

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,有疑问
latiaoder commented 2 years ago

@liujing-siyang 因为 []byte(str) 生成了一个新的切片,所以不会改变源数据

dashuaiduan commented 2 years ago

@DurantVivado 写的很好,个人感觉Getter可以不要,用户直接传入匿名函数即可

确实 直接传函数 好理解一点 不会那么绕。。不过这里能体现 接口型函数的设计吧

ZAKLLL commented 2 years ago

这个group 可以类比为redis中的db吗

sphierex commented 2 years ago

@ZAKLLL 这个group 可以类比为redis中的db吗

等同 redis 中的 db

lisigg1998 commented 2 years ago

@whoisapig 为什么不在接受操作指令执行前,通过 channe 限制处理的单线程 而是通过锁来做并发啊?

https://segmentfault.com/a/1190000017890174

LLoyou00 commented 2 years ago

缓存没有命中的时候,调用回调函数获取数据是不是应该加个锁避免重复获取?

ShiMaRing commented 2 years ago

@LLoyou00 缓存没有命中的时候,调用回调函数获取数据是不是应该加个锁避免重复获取?

应该是需要加锁的,同步的数据获取操作可能会消耗掉不少时间,这个可以交给传入的接口实现,也可以添加同步逻辑,相同的key之间使用channel同步,具体实现可以参考Go语言圣经9.7节

ZyhHelen commented 1 year ago
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 commented 1 year ago

ByteView结构体在定义方法的时候可以用指针吗?比如

func (v ByteView) ByteSlice() []byte {
    return cloneBytes(v.b)
}

修改为

func (v *ByteView) ByteSlice() []byte {
    return cloneBytes(v.b)
}
MaXiaoYang6 commented 1 year ago

@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{}{}
}

并不好,可读性并不强,有点多此一举的感觉.

MaXiaoYang6 commented 1 year ago

@RookieCoder99 ByteView结构体在定义方法的时候可以用指针吗?比如

func (v ByteView) ByteSlice() []byte {
  return cloneBytes(v.b)
}

修改为

func (v *ByteView) ByteSlice() []byte {
  return cloneBytes(v.b)
}

无所谓 都可以

OSinoooO commented 10 months ago

为什么不用读写锁而是使用互斥锁呢?缓存不应该是读多写少吗

vapausw commented 10 months ago

@OSinoooO 为什么不用读写锁而是使用互斥锁呢?缓存不应该是读多写少吗

sync.Mutex 与sync.RWMutex是不同的,sync.RWMutex的加锁解锁应该是读写锁。

wongdark2017 commented 6 months ago

开头的示例是不是也能用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)
}
caoyukun0430 commented 2 months ago

@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 的主要目的之一”