learn - 分布式内存缓存的妙用
learn - 分布式内存缓存的妙用
分布式内存缓存
背景:系统中为了降低数据存储的压力,业务与数据层之间会增加一层或两层缓存来用。然后数据层里面便包含了:缓存层+数据持久化层。
缓存分类:
- 在业务机器之外申请一个缓存,例如Redis/Memcache。优点是降低耦合,提高并发;缺点是增加资源成本。
- 在当前业务机器上,同一进程内或外,增加便利内存式缓存。优点是降低资源成本,能显著提高并发;缺点是增加了部分开发维护工作来保证集群情况下的数据一致性。
内存缓存LRU
优良的设计:可以是一个循环连表,容量大小由开发人控制。过期和超出容量时失效策略也是由程序员来定(最近最少使用:Least Recently Used )。
项目使用方案
抽象出一个lru要干什么
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// LRUCache is the interface for simple LRU cache.
type LRUCache interface {
// Adds a value to the cache, returns true if an eviction occurred and
// updates the "recently used"-ness of the key.
// 目的:添加一条本地缓存记录
Set(key, value interface{}, expiresIn int64) bool
// Returns key's value from the cache and
// updates the "recently used"-ness of the key. #value, isFound
// 目的:拿到一条本地缓存(内部校验是否过期,并且设置最近使用)
Get(key interface{}) (value interface{}, ok bool)
// Checks if a key exists in cache without updating the recent-ness.
// 目的:仅仅判断是否有该缓存被设置(不影响最近试用使用)
Contains(key interface{}) (ok bool)
// Returns key's value without updating the "recently used"-ness of the key.
// 目的:拿到一条本地缓存(不影响最近试用使用,用的时候频率不高)
Peek(key interface{}) (value interface{}, ok bool)
// Removes a key from the cache.
// 目的:删除缓存
Remove(key interface{}) bool
// Removes the oldest entry from cache.
// 目的:删除一条最不常用的旧记录,返回该记录的key, value, isFound
RemoveOldest() (interface{}, interface{}, bool)
// Returns the oldest entry from the cache. #key, value, isFound
// 目的:返回一条最不常用的旧记录的key, value, isFound。并不影响最近使用情况
GetOldest() (interface{}, interface{}, bool)
// Returns a slice of the keys in the cache, from oldest to newest.
// 目的:返回所有的key列表,从最旧未使用到最近使用过的
Keys() []interface{}
// Returns the number of items in the cache.
// 目的:返回当前的缓存使用长度
Len() int
// Clears all cache entries.
// 目的:清空缓存
Purge()
// Resizes cache, returning number evicted
// 目的:重新设置缓存容量
Resize(int) int
}
具体实现上,可以自己写一个循环列表,或者用包 “container/list” 列表数据结构,进行包装。
软件设计架构上的运用
我不建议直接在model里面把 实体(entry)、映射关系(mapping)、数据访问(dal)、数据处理(dml)放在同一个文件内。有条件的话可以单独放在不同作用的package中。
例如包的目录结构建议:
1
2
3
4
5
6
7
8
9
10
./service/user/user_dto/ # 数据传输实体对象,有接收request也有resp,也有合并多个持久层数据后的中间结构 例如UserFullDTO 包含user基础信息/开关信息/资产信息等
./service/user/internal/dal # 对持久层的访问,包括增删改查,不做非访问的事情,dml可直接使用。让使用者无需关心持久层是怎么存储的(例如mysql/mssql/es/redis)。
./service/user/internal/dml # 这里说仅给user服务提供的 数据处理和数据转换(数据持久层与业务数据操作。包含是否底层要增加缓存),若要与底持久层数据打交道,统统转交给dal,二次数据处理,例如redis/lru等
./service/user/usersrv.go # 这里说提供user共同服务的封装,可以输出成resp数据,或者dto给其他logic用
./logic/user/user.go. # 这里 user相关的逻辑处理,可以给handle用
./handle/web/user.go # web控制器
./handle/admin/user.go # admin控制器
./handle/pc/user.go # pc控制器
./router/ ## 路由
引发lru变化
最后关心的是,分布式微服务下,如何去保证我们的本地内存缓存一致性。
参考如下图:
要求有一处负责更改数据,其他端负责监听数据的变动心跳即可。
本文由作者按照 CC BY 4.0 进行授权
