goredis

作者: 次序 | 来源:发表于2020-07-19 11:31 被阅读0次
package main

import (
    "encoding/json"
    "fmt"
    "math/rand"
    "time"

    "github.com/go-redis/redis"

    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/mysql"
)

type Thread struct {
    ThreadID int32 `gorm:"primary_key"`
    Text     string
    CTime    time.Time
}

func (this *Thread) RedisKey() string {
    return fmt.Sprintf("thread:%d", this.ThreadID)
}

func (this *Thread) RedisValue() []byte {
    b, _ := json.Marshal(this)
    return b
}

func (this *Thread) RedisTTL() time.Duration {
    return 24 * 60 * 60 * time.Second
}

func (this *Thread) RedisFailTTL() time.Duration {
    return 30 * time.Second
}

func AddThread(o *gorm.DB, c *redis.Client, thread *Thread) error {
    if err := o.Create(thread).Error; err != nil {
        return err
    }
    if err := c.Set(
        thread.RedisKey(),
        thread.RedisValue(),
        thread.RedisTTL()).Err(); err != nil {
        return err
    }
    return nil
}

func SetThread(o *gorm.DB, c *redis.Client, thread *Thread) error {
    if err := o.Save(thread).Error; err != nil {
        return err
    }
    if err := c.Set(
        thread.RedisKey(),
        thread.RedisValue(),
        thread.RedisTTL()).Err(); err != nil {
        return err
    }
    return nil
}

func GetThreads(o *gorm.DB, c *redis.Client, ids []int32) ([]*Thread, error) {
    threads := make([]*Thread, len(ids))
    for i := range threads {
        threads[i] = &Thread{ThreadID: ids[i]}
    }

    // Load From Cache
    keys := make([]string, len(ids))
    for i := range keys {
        keys[i] = threads[i].RedisKey()
    }
    vals, err := c.MGet(keys...).Result()
    if err != nil {
        return nil, err
    }
    failIds := []int32{}
    succeedIdxs := []int{}
    for i := range vals {
        val, ok := vals[i].(string)
        if ok {
            err := json.Unmarshal([]byte(val), threads[i])
            if err != nil {
                ok = false
            }
        }
        if ok {
            succeedIdxs = append(succeedIdxs, i)
        } else {
            failIds = append(failIds, ids[i])
        }
    }

    // Refresh Expire
    {
        pl := c.Pipeline()
        for _, idx := range succeedIdxs {
            pl.Expire(threads[idx].RedisKey(), threads[idx].RedisTTL())
        }
        _, _ = pl.Exec() // TODO: waring log
    }

    // Load From Database
    if len(failIds) != 0 {
        fmt.Println("Cache Miss:", failIds)

        failThreads := make([]*Thread, 0, len(failIds))
        err = o.Where("thread_id IN (?)", failIds).Find(&failThreads).Error
        if err != nil {
            return nil, err
        }

        pl := c.Pipeline()
        for i := range failThreads {
            pl.Set(failThreads[i].RedisKey(), failThreads[i].RedisValue(), failThreads[i].RedisTTL())
        }
        _, _ = pl.Exec() // TODO: waring log

        for i := range failThreads {
            for j := range threads {
                if failThreads[i].ThreadID == threads[j].ThreadID {
                    threads[j] = failThreads[i]
                }
            }
        }

        // Double Fail
        if len(failThreads) != len(failIds) {
            doubleFailIds := []int32{}
            for i := range failIds {
                fail := true
                for j := range failThreads {
                    if failThreads[j].ThreadID == failIds[i] {
                        fail = false
                        break
                    }
                }
                if fail {
                    doubleFailIds = append(doubleFailIds, failIds[i])
                }
            }

            // avoid pass through
            if len(doubleFailIds) != 0 {
                pl := c.Pipeline()
                for i := range doubleFailIds {
                    t := &Thread{ThreadID: doubleFailIds[i]}
                    pl.Set(t.RedisKey(), "{}", t.RedisFailTTL())
                }
                _, err := pl.Exec()
                if err != nil {
                    return nil, err
                }
            }
        }
    }

    return threads, nil
}

func init() {
    redis.SetLogger(nil)
    fmt.Println("redis.Nil", redis.Nil)
}

func main() {
    o, err := gorm.Open("mysql", "root:nikki@(127.0.0.1:3306)/student?timeout=5s&parseTime=true&loc=Local&charset=utf8")
    if err != nil {
        panic(err)
    }
    defer o.Close()
    o.AutoMigrate(&Thread{})

    c := redis.NewClient(&redis.Options{
        Addr: "localhost:6379",
    })
    defer c.Close()

    rand.Seed(time.Now().UnixNano())

    //AddThread(o, c, &Thread{Text: "first", CTime: time.Now()})
    //AddThread(o, c, &Thread{Text: "second", CTime: time.Now().Add(time.Second)})
    //AddThread(o, c, &Thread{Text: "third", CTime: time.Now().Add(time.Second)})

    threads, err := GetThreads(o, c, []int32{1, 2, -1, 3})
    if err != nil {
        panic(err)
    }

    for _, thread := range threads {
        fmt.Println(thread.ThreadID, thread.Text)
    }

    // Delete Cache
    err = c.Del((&Thread{ThreadID: 1}).RedisKey()).Err()
    if err != nil {
        panic(err)
    }
    fmt.Println("\nAfter Delete Cache:")

    threads, err = GetThreads(o, c, []int32{1, 2, -1, 3})
    if err != nil {
        panic(err)
    }

    for _, thread := range threads {
        fmt.Println(thread.ThreadID, thread.Text)
    }

    // Modify Cache
    SetThread(o, c, &Thread{ThreadID: 1, Text: "first" + fmt.Sprintf("_%d", rand.Intn(100)), CTime: time.Now()})
    fmt.Println("\nAfter Modify Cache:")

    threads, err = GetThreads(o, c, []int32{1, 2, -1, 3})
    if err != nil {
        panic(err)
    }

    for _, thread := range threads {
        fmt.Println(thread.ThreadID, thread.Text)
    }
}

相关文章

网友评论

      本文标题:goredis

      本文链接:https://www.haomeiwen.com/subject/fuulkktx.html