实现原理

将从数据库中加载到的数据临时缓存到程序中,避免在同一时间再次从数据库中加载数据

用法

  1. 目录结构:

    |—— singleflight/       // 根目录
    ****|—— server/
    ********|—— main.go     // 使用了缓存的 http 服务器
    ****|—— test/
    ********|—— main.go     // 模拟并发访问
    ****|—— go.mod
  2. singleflight/server/main.go:

     package main
    
     import (
         "errors"
         "fmt"
         "github.com/gin-gonic/gin"
         "golang.org/x/sync/singleflight"
         "math/rand"
         "strconv"
         "time"
     )
    
     var sf singleflight.Group
    
     func main() {
         router := gin.Default()
         router.GET("/", func(c *gin.Context) {
             cacheKey := c.DefaultQuery("key", "")
             // 模拟从缓存中读取数据(缓存未命中)
             data, err := loadFromCache(cacheKey)
             if err != nil {
                 // 利用 singleflight 减少数据库请求
                 value, err, _ := sf.Do(cacheKey, func() (interface{}, error) {
                     // 模拟从数据库中加载数据(正常情况下该方法只会被调用一次,且结果会被 go程序缓存起来)
                     data, err = loadFromDB()
                     if err != nil {
                         return nil, err
                     }
                     // 模拟写入缓存
                     setCache(cacheKey, data)
                     return data, nil
                 })
                 if err != nil {
                     panic(err)
                 }
                 data = value.(string)
             }
             c.JSON(200, gin.H{
                 "data": data,
             })
         })
         fmt.Println("server run at: http://localhost:8080")
         router.Run()
     }
    
     func loadFromCache(key string) (string, error) {
         return "", errors.New("缓存未命中")
     }
    
     func setCache(key, data string) {}
    
     func loadFromDB() (string, error) {
         fmt.Println("load from database.")
         rand.Seed(time.Now().UnixNano())
         randInt := rand.Intn(999999999)
         return strconv.Itoa(randInt), nil
     }
  3. singleflight/test/main.go:

     package main
    
     import (
         "fmt"
         "io/ioutil"
         "net/http"
         "sync"
     )
    
     func HttpGet(uri string) (string, error) {
         resp, err := http.Get(uri)
         if resp != nil {
             defer resp.Body.Close()
         }
         if err != nil {
             return "", err
         } else {
             if resp.StatusCode == 200 {
                 bodyReader := resp.Body
                 body, err := ioutil.ReadAll(bodyReader)
                 if err != nil {
                     return "", err
                 } else {
                     return string(body), nil
                 }
             } else {
                 return "", fmt.Errorf("服务器异常")
             }
         }
     }
    
     func main() {
         uri := "http://localhost:8080?key=cached-key"
         wg := sync.WaitGroup{}
         for i := 0; i < 1000; i++ {
             wg.Add(1)
             go func() {
                 ret, err := HttpGet(uri)
                 if err != nil {
                     fmt.Println("http get error:", err)
                 } else {
                     fmt.Println(ret)
                 }
                 wg.Done()
             }()
         }
         wg.Wait()
     }
文档更新时间: 2024-04-20 10:57   作者:lee