Local Cache要存对象还是序列化数据?

序言

偶然间登录掘金账号,想起了周末去咖啡店写文章的我,现在改为了健身(健康为主,力争满头秀发),还是觉得惭愧,既然路过就写一段吧。深度不深,只是通过一个小案例引发的一些思考,不喜勿喷~

背景

在日常工作中,我们经常会遇到需要使用缓存的场景,例如存一些临时数据,减少下游存储介质访问等。

首先,我们要抉择remote cache还是local cache, remote cache通常以中间件的形式在其mem中存储,服务集群中的各个节点都可以通过相同的key访问remote cache拿到数据;local cache则采用该服务的内存资源,服务集群的各个节点存储自己所需要的数据,各个节点的key分布各不相同(随着业务而定)。

其次,该以什么形式存储缓存数据,remote cache比较简单,像redis会规定存序列化数据;而local cache属于自由发挥,我们可以直接set一个对象进去,get时也不用反序列化。

那local cache存对象不是一劳永逸吗?既节省耗时又省序列化开销。

Local Cache存对象

简单用golang来释义(省略了error等其他逻辑),实际工作当中,看到了如下代码:

scss 复制代码
// lru cache实现
func (l *lruCache) Set(key string, val *Data) {
    l.lock.Lock()
    defer l.lock.Unlock()
    
    l.Set(key, val) 
}

func (l *lruCache) Get(key string) *Data {
    l.lock.Lock()
    defer l.lock.Unlock()
    
    return l.Get(key)
}

// 业务
data := l.Get(key)
// 拿到data,继续写逻辑
...

可以看到,local cache存了对象,看起来没啥问题,因为从cache里拿到对象继续写业务逻辑,也对并发场景用mutex进行了处理。

注意点:怎么保证从cache里拿到的对象是immutable?

如果仔细看就会发现,我虽然在cache实现里用锁做了控制,但我拿到的data指针是不可控的,其他人很可能就改了里面的数据。

kotlin 复制代码
// 业务
data := l.Get(key)
// 拿到data,我要改里面的value
...
data.value = b
...

这样的情况下,cache里的数据也被更改,在并发环境中甚至会热key会被频繁更改,导致线上问题。 当然,像C++/Java为对象和成员变量提供了些immutable语义,但这种设计是否把"锅"甩到了cache方法之外呢? 如果必须要改数据(很有可能过了一阵子有一个新需求),后续业务逻辑根据场景处理浅拷贝/深拷贝,保证cache里的数据不被变更。

Local Cache存序列化数据

存序列化数据就和remote cache实现方式差不多了(error处理等逻辑略过)。

scss 复制代码
// lru cache实现
func (l *lruCache) Set(key string, val *Data) {

    bytes, _ := marshal(&val)
    
    l.lock.Lock()
    defer l.lock.Unlock()
    
    l.Set(key, bytes) 
}

func (l *lruCache) Get(key string) *Data {
    l.lock.Lock()
    bytes, _ := l.Get(key)
    l.lock.Unlock()
    
    d, _:= unmarshal(bytes)
    return d
}

// 业务
data := l.Get(key)
// 拿到data,继续写逻辑
...

这样,后续业务逻辑就不会担心对对象做任何操作,因为相当于进行了一次深拷贝,拿到的就是新的对象。

总结

从上述小的例子可以看出,使用local cache也要注意场景,特别是关注cache中取出来的数据有无变更,导致影响业务逻辑。

相关推荐
谭光志4 小时前
Vibe Coding 时代,程序员该何去何从
前端·后端·ai编程
GoGeekBaird5 小时前
我用 BeeWeave 跑完了一次完整写作闭环
后端·github
仿生狮子6 小时前
别再说“全栈”了,AI 时代团队只认这 5 种人
前端·人工智能·后端
Reart8 小时前
Leetcode 213.打家劫舍2(内含闲谈,打劫真是技术活,好题,716)
后端·算法
触底反弹9 小时前
🔥 RAG 到底是怎么工作的?掰开揉碎了给你讲明白!
javascript·人工智能·后端
techdashen9 小时前
Go 1.26 新增 `bytes.Buffer.Peek`:只看数据,不移动读取位置
开发语言·后端·golang
Reart9 小时前
Leetcode 198.打家劫舍(716)
后端·算法
Java编程爱好者10 小时前
Spring6.0+Boot3.0:秒级启动、万级并发的开发新姿势
后端
YYYing.10 小时前
【C++大型项目之高性能服务器框架 (七) 】Socket与ByteArray模块
服务器·c++·后端·框架·高性能·c/c++
青山木10 小时前
一把 Redis 分布式锁,踩透四个坑:锁争抢、僵尸锁、锁过期、锁丢失
java·数据库·redis·后端