项目中使用进程内缓存的一些经验及注意事项
前言
对于项目中一些比较简单的功能,比如在项目启动的时候将简单的userId与configId的对应关系存放的缓存从而减少RDS中的IO操作,这种简单的应用场景我们可以使用进程缓存来代替redis达到同样的效果。
本文分享一下进程内缓存的使用以及存在的问题分析。
直接使用全局变量的方法
使用进程内缓存最简单的方式就是定义一个全局的变量,在项目启动的生命周期内将数据存放在这个全局变量下即可:
var globalMap map[string]interface{}
并发情况下使用sync.Map
在并发情况下,多个goroutine同时操作golang原生的map会出现竞争条件(race condition)问题。
如果需求是要将进程内缓存存放在一个map中,并且项目会遇到多个goroutine同时操作这个map的情况,最简单的可以使用golang内置的sync.Map去替代原生的map,可以避免race condition问题。
关于sync.Map的操作可以参考这篇文章:go sync.Map使用和介绍
使用go-cache第三方包
有一个很好用的第三方包go-cache它也实现了进程内缓存,并且使用加锁的方式确保了多goroutine下的并发安全(看一下源码就知道了,对外暴露的方法都有加锁的操作)。
说明文档如下:go-cache包说明文档
自己做了一下简单的练习:
package test1 import ( "fmt" "github.com/patrickmn/go-cache" "testing" ) func TestInnerProcessCache(t *testing.T) { // 永久生效,除非重启服务 cacheBaby := cache.New(0, 0) cacheBaby.Set("name", "whw", 0) if name, ok := cacheBaby.Get("name"); ok { fmt.Println("name: ", name) } else { fmt.Println("nothing...") } }
进程缓存存在的问题
这里先引用一下上面介绍的go-cache包的一个介绍(这个作者还是十分严谨的):
多台机器做负载均衡存在的缓存同步问题
设想一下,如果我们的项目部署在了多台服务器上做负载均衡,像下面这样:
那么问题就来了,项目启动时在后面2台EC2中分别存了一份缓存数据,如果其中一个EC2的缓存数据更新了,对于数据实时性要求特别高的服务来说如何“瞬间”将这个改动同步到另外一台机器上是一个很棘手的问题(即时使用MQ这样的订阅功能也会存在网络传输的时延)。
所以对于数据实时性要求特别高的服务,并且服务器也做了负载均衡,如果业务中有需要对缓存更新的操作的话,笔者不建议使用进程内缓存作为缓存数据的方式,推荐使用像redis这样的第三方服务去做。
当然如果只是将一些固定的数据作为缓存的话,并且数据的更新依赖于项目重启,那么这种场景下使用进程内缓存还是十分方便的。
进程缓存的其他使用场景
之前在做项目的时候有用到一个自定制的LRU cache的包,挺不错的:使用golang实现一个LRU进程缓存模块
参考文章