迁移自简书,格式可能未经校对。
最近看到了几种加缓存的方法,整理对比一下。
拿一个case来说,我们要去数据库取一条用户记录,迫于性能,还要加一层缓存。我们针对这个问题看看几种使用姿势的对比。
Laravel 中 Facades 做法
$person = Cache::remember("person.{$id}", 5, function () use ($id) {
return PersonDao::find($id);
});
Spring Cache 的做法
@Cache(key = "person#id", ttl = 5)
public Person getPerson(Integer id) {
return PersonDao.find(id);
}
Person person = repository.getPerson();
备注:
- PersonDao.find 表示从DB里去拿数据
这两种方法看起来都很简单,除了必要的语法格式,你需要写的代码就是:
- cache 函数或标记,表明需要缓存
- key 不解释
- ttl 不解释
- func… 回源数据
基本上可以说是要啥写啥了,不啰嗦。
其实 Php 和 Java 的语法很接近,两种方法在两种语言里都适用。不过 Php 需要第三方的注解支持;Java 需要 8 以上来支持 lambda。
简单的东西一定面临扩展性的问题,我们来看一看他们的可能性。
如果我们要更换缓存驱动怎么办?
Laravel
Cache::store('redis')->remember(...)
Spring
@Cache(driver = redisCache.class)
依然很简单。
有些时候,在使用 redis 作为缓存的时候,我们会用不同的编码
Laravel
Cache::store('redis')->encoding('json')->remember(...)
Spring
@Cache(encoding = JsonEncoding.class)
方法其实是相似的,一般的,Lavavel 利用自己习惯的链式操作和 Php 的不定参数,可以让你随时传入自己个性化的需求。Spring 也利用 Annotation 来实现类似的效果。
更多的,Laravel 和 Spring Boot 都遵循了约定优于配置的原则,使得在大多数情况下,你都不需要传这些,只需要使用全局的默认配置就能满足需求。也就是上面的最方便的办法。
简单的方法介绍完了,我们来聊聊 Go 里的做法
刚刚接手一个 Go 项目,里面看到是这样处理缓存的:
proxy := Proxy{
Prefered: RedisAdapter{
RedisClient
},
Backup: DaoAdapter{
PersonDao
}
}
person := proxy.Get('xxx').(Person)
是不是一下看懵逼了,我也是,这还是简化的版本。真正实现一个这样的功能,大约新增了三个实现了数个空接口新类和几个方法。
更蛋疼的,这三个类都是类型相关的,换句话说,list/detail 两种功能各自都需要3个类,换个 model 也不能复用。更悲催的,因为 IDE 对 Go 的 interface 分析都不太好,当你阅读别人的代码的时候,你完全不知道哪里是哪里。
所以,这里想尝试一下,能否在 Go 里使用上面的简单方法处理缓存。
id := 9
person := remember("key", 30 * time.Second, func() interface{} {
return PersonDao.find(id)
}).(*Person)
// 或者更 Go 一点
var person Person
remember(&person, "key", 30 * time.Second, func(iface interface{}) {
*iface.(*Person) = *PersonDao.find(id)
})
比较烦的是,Go 不支持泛型,定义函数的时候要尽可能少依赖类型。常用的办法是把类型传入。
前者看起来简单,但有个很要命的地方,你需要很严格的把 Person 类进行序列化。否则从 cache 里取出来后类型可能会丢,导致断言失败。
那么,第二种办法可以吗?或者说,在 Go 里能不能通过简单标记的办法来实现多态?
我只能说,不好弄。
Spring 里很多注解效果,都是靠动态代理实现的(相当于 Php 里阉割版的 __call )。但遗憾的是,Go 目前不能支持这一特性。如果要硬上的话,也可以,搞出来可能跟我接的代码挺像的。