1:什么是缓存
在数据库与服务器逻辑之间加入的数据层
2:作用
减少数据库操作
服务器使用mysql作为数据库,mysql每秒钟并发数量有限,所以我们要减少mysql的操作。
3:erlang的缓存
erlang 在内存中可用 进程字典/ gen state / ets 储存变量,理论上三种方式都可以作为缓存
4:缓存实现方案
方案 1
原理
读数据时从缓存内读取,如果缓存不存在则从数据库中筛选,并放入缓存内
数据变更时直接操作数据库,并清除缓存
实现方式
启动一个gen_server进程将所有 ets建立起来,ets会分为 ets_player,ets_bag 等将数据分开储存到ets表中,一般以玩家id作为ets的key,读取某个数据会优先访问ets表中数据,再从数据库筛选
这是我们最初的方案,缺点十分明显,只能减少部分读取操作,在频繁更改数据的情况下,缓存几乎没有作用,缓存在不停的被清理,当需要读取数据时又需要重新从数据库中拉取。
方案2
原理
读取策略与方案1一致
数据变更时变更缓存内数据,不操作数据库,当玩家下线时再将缓存数据同步到数据库中
实现方式
同样会启动一个gen_server进程将所有 ets建立起来,不同的是ets中的每一行多了一个是否修改的标识位,玩家读取数据依然优先访问ets表中数据
当玩家修改数据,例如升级后,修改ets_player中对应的数据,并将ets中修改标识设为已修改
玩家下线后,顺序检查各个ets中的数据,将标识为已修改的数据同步到数据库中
方案2与方案1相比,最大的变更在于数据更改时不清理缓存,而是修改缓存,这就避免了频繁的从数据库中筛选数据。
将数据库同步操作积累到玩家下线而不是立即写库,可以减少许多sql语句,例如玩家在上线期间由 1级升到5级,下线写库只会执行一条sql,而即时写库则需要4条,对2,3,4,5分别执行update
最初我们以为这套方案已经可以满足游戏线上运行需求,直到我们做了一个压力测试(膝盖中了一箭?),例如你有10多个系统,玩家,竞技场,背包,抽奖,等等等等那么在玩家下线时会出现数据库操作高峰。在压力测试时峰值十分明显,尤其是开服初期导量十分多的情况。
方案3
原理
由于玩家下线时间可能会出现峰值,所以我们想到了使用定时写库这种方式,将写库时间设定为我们认为合适的间隔
实现方式
每一张数据表都启动一个对应的gen_server来管理数据,进程内部会创建一个ets表,玩家读取/修改数据时都会访问对应的gen_server
当玩家修改信息后,gen_server内部会记录修改的信息。
gen_server内部启动一个定时器,每隔一段时间将修改的数据同步到数据库中
与方案2相比,方案3将写库时间控制在自己手里,在游戏开新服导量期间,可能设定数小时同步一次数据,可以很大程度减少sql压力
但这套方案也有缺陷,由于玩家查询/修改数据都需要访问同一个gen_server,部分gen_server 如玩家信息,就会出现瓶颈,出现超时现象
方案4
原理
为了解决方案3中gen_server访问的瓶颈问题,我们在gen_server外层又添加了一层缓存
实现方式
在方案3的基础上,又将方案1,2中的cache_process添加回来,玩家读取数据会先访问cache_proces中的数据,如果其中没有再访问对应的gen_server进程
最终 读取数据流程 玩家获取数据 -> cache_process中查找 -> gen_server 中查找 -> 数据库中查找
玩家更改数据 -> 更改cache_process
-> 更改 gen_server 中数据 -> 一定时间后 gen_server将数据写回数据库