前言
在开发系统时,配置管理是十分重要的一部分。由于配置不经常变动,并且大多数情况下实时性要求不是特别高,因此将配置存在缓存中是很常用的一种方法。存在缓存中能够提高查找效率,但是也带来了新的问题:配置何时更新?怎么更新?
一、配置的使用和更新
许多服务器使用配置时,会选择将配置存在内存中。并且为了避免被多次初始化,读取配置的对象采用单例模式生成。同时应该满足以下特点:
1.配置缓存有过期时间,一定时间后缓存过期,再次拉取数据库数据刷新缓存。
2.一旦配置修改后,能够在可接受的时间内同步到缓存中。
3.不应频繁读取数据库更新缓存。
容易看出,如果每次读取配置,都去读取数据库,然后同步到内存中,能够轻易满足第1,2个特点,但是这违反了第3个特点,使得内存缓存失去了其存在的意义。而为了满足第3个特点,又不违反前两个特点,则要保证只有当配置修改或者内存缓存过期的时候才去读取数据库并且同步到内存中。这里需要做两件事情:监测缓存过期和监测配置修改。
二、具体实现
首先定义内存缓存对象如下
type ConfigCache struct {
UpdateTime int64 // 更新缓存时间
CheckTime int64 // 检查缓存时间
UpdateExpiryTime int64 // 更新失效时间
CheckExpiryTime // 检查失效时间
ConfigMap map[string]interface{} // 存放配置的map
VersionNo string
}
1.监测缓存过期
如上所示,当每次同步内存缓存的时候,将对象中的UpdateTime更新为当前时间。
当读取内存缓存时,校验now - UpdateTime是否大于UpdateExpiryTime。如果是,说明内存缓存失效了,去数据库拉取配置并且同步。
代码如下(示例):
## 2.读入数据
func (c *ConfigCache) isExpiry() bool {
// 过期
return now - c.UpdateTime > UpdateExpiryTime
}
func (c *ConfigCache) SyncData() {
// 从数据库中读取数据,并且存放在ConfigMap中
c.UpdateTime = now
}
func (c *ConfigCache) GetCacheMap() map[string]interface{}{
if c.isExpiry() {
c.SyncData()
}
return c.ConfigMap
}
2.监测配置修改
监测配置修改,需要用到外部缓存。假设有外部缓存externalCache。当初始化时,获得此时配置的版本号v1,将版本号存在externalCache中。之后每当配置被修改后,版本号被修改,都将版本号同步到外部缓存externalCache中。
对于内存缓存ConfigCache,每次读取缓存的时候,除了校验缓存是否过期,同时以一定时间间隔CheckExpiryTime校验自身的版本号和externalCache中的版本号是否一致。假设外部缓存中的版本号与当前自身的版本号不一致,则说明配置更新了,需要同步到内存缓存中。只要CheckExpiryTime合适,就能保证一旦配置更新后能够在可接受的时间内更新到内存缓存中。同时版本号从外部缓存中获取,其速度也比读取数据库快得多。
结合后代码如下(示例):
## 2.读入数据
func (c *ConfigCache) isExpiry() bool {
// 过期
if now - c.UpdateTime > UpdateExpiryTime {
return true
}
// 一定时间内没有检查版本号
if now - c.CheckTime > CheckExpiryTime {
versionNo := externalCache.get(versionNo)
if versionNo != c.VersionNo {
return true
}
c.CheckTime = now
}
return false
}
func (c *ConfigCache) SyncData() {
// 省略从数据库中读取数据,并且存放在ConfigMap中
c.UpdateTime = now
c.CheckTime = now
}
func (c *ConfigCache) GetCacheMap() map[string]interface{}{
if c.isExpiry() {
c.SyncData()
}
return c.ConfigMap
}
总结
内存缓存的使用能够提高查找效率,但是也要注意更新的问题。一旦缺少正确更新的方式,可能导致更新滞后,多台实例之间存在长时间差异等问题。需要结合实际合理设计更新策略。