golang实现的自带并发控制,服务降级的本地kv缓存

lc (local cache)--http://github.com/simplejia/lc

实现初衷

  • 纯用redis做缓存,相比lc,redis有网络调用开销,反复调用多次,延时急剧增大,当网络偶尔出现故障时,我们的数据接口也就拿不到数据,但lc里的数据就算是超过了设置的过期时间,我们一样能拿到过期的数据做备用
  • 使用mysql,当缓存失效,有数据穿透的风险,lc自带并发控制,有且只允许同一时间同一个key的唯一一个client穿透到数据库,其它直接返回lc缓存数据

特性

  • 本地缓存
  • 支持Get,Set,Mget,Delete操作
  • 当缓存失效时,返回失效标志同时,还返回旧的数据,如:v, ok := lc.Get(key),当key已经过了失效时间了,并且key还没有被lru淘汰掉,v是之前存的值,ok返回false
  • 实现代码没有用到锁
  • 使用到lru,淘汰长期不用的key
  • 结合lm使用更简单快捷

demo

lc_test.go

package lc

import (
    "testing"
    "time"
)

func init() {
    Init(65536) // 使用lc之前必须要初始化
}

func TestGetValid(t *testing.T) {
    key := "k"
    value := "v"
    Set(key, value, time.Second)
    time.Sleep(time.Millisecond * 10) // 给异步处理留点时间
    v, ok := Get(key)
    if !ok || v != value {
        t.Fatal("")
    }
}

同时再帖下我实现的另一个组件,搭配lc使用更方便。

lm (lc+redis+[mysql|http] glue)--http://github.com/simplejia/lm

实现初衷

写redis+mysql代码时(还可能加上lc),示意代码如下:

func orig(key string) (value string) {
value = redis.Get(key)
if value != "" {
return
}
value = mysql.Get(key)
redis.Set(key, value)
return
}
// 如果再加上lc的话
func orig(key string) (value string) {
value = lc.Get(key)
if value != "" {
return
}
value = redis.Get(key)
if value != "" {
lc.Set(key, value)
return
}
value = mysql.Get(key)
redis.Set(key, value)
lc.Set(key, value)
return
}

有了lm,再写上面的代码时,一切变的那么简单 lm_test.go

func tGlue(key, value string) (err error) {
lmStru := &LmStru{
Input:  key,
Output: &value,
Proc: func(p, r interface{}) error {
_r := r.(*string)
*_r = "test value"
return nil
},
Key: func(p interface{}) string {
return fmt.Sprintf("tGlue:%v", p)
},
Mc: &McStru{
Expire: time.Minute,
Pool:   pool,
},
Lc: &LcStru{
Expire: time.Millisecond * 500,
Safety: false,
},
}
err = Glue(lmStru)
if err != nil {
return
}
return
}

功能

  • 自动添加缓存代码,支持lc, redis,减轻你的心智负担,让你的代码更加简单可靠,少了大段的冗余代码,复杂的事全交给lm自动帮你做了
  • 支持Glue[Lc|Mc]及相应批量操作Glues[Lc|Mc],详见lm_test.go示例代码

注意

  • lm.LcStru.Safety,当置为true时,对lc在并发状态下返回的nil值不接受,因为lc.Get在并发状态下,同一个key返回的value有可能是nil,并且ok状态为true,Safety置为true后,对以上情况不接受,会继续调用下一层逻辑

案例分享

  • 一天一个用户只容许投一次票
    func f(uid string) (err error) {
    lmStru := &lm.LmStru{
        Input: uid,
        Output: &struct{}{},
        Proc: func(p, r interface{}) error {
            // 略掉这部分逻辑: 可以把投票入库
            // ...
            return nil
        },
        Key: func(p interface{}) string {
            return fmt.Sprintf("pkg:f:%v", p)
        },
        Mc: &lm.McStru{
            Expire: time.Hour * 24,
            Pool:   pool,
        },
    }
    err = lm.GlueMc(lmStru)
    if err != nil {
        return
    }
    return
    }

0 个评论

要回复文章请先登录注册