redis作为历史已久的工具,在数据缓存方案有比较成熟的验证。这里做一些小实验
,验证对游戏玩家存储可行性。
用法:
local conf = { host = "127.0.0.1" , port = 6379 , db = 0, auth = "secret-password" } local redis = require "skynet.db.redis" local db = redis.connect(conf) db:set('C', 'hello world')
其中
db
参数功能是已经废弃的,不应该使用他,默认为第0个database。如果需要密码就要设置好auth
参数。
当拿到redis对象,调用方法会触发元表函数,提取方法名当作redis命令名。方法参数当作命令的参数。
每个方法都会调用socketchannel的阻塞方法request.- 有多条redis命令需要原子执行时,可以合并了一个Lua代码后用
eval
命令执行。或者使用multi-exec
命令。 - 当使用订阅发布时,创建出来的新对象也会产生一个redis连接。
因为redis使用全加载,即使使用限制内存的方式(maxmemory参数)来实现LRU加载。不适合大数据保存。这里验证LRU换出内存时不持久化,即内存数据和存盘数据一致。设置内存上限为1MB,在超过时默认是拒绝写入的,这个修改为LRU淘汰。因为权衡性能,redis并不是严格的LRU,但对使用无影响。
redis-cli config set requirepass p@ss$12E45 redis-cli auth p@ss$12E45 redis-cli flushall redis-cli config set maxmemory 1mb redis-cli config set maxmemory-policy allkeys-lru redis-cli -r 100000 lpush mylist lichengman redis-cli llen mylist redis-cli dbsize redis-cli info keyspace redis-cli shutdown
虽然插入了10W次数据,但是实测重启后保留了7.7K个数据。另外动态设置的配置在重启后会丢失。
redis和mongodb读写对比, 测试用例来自stackoverflow讨论例子。 可以使用skynet进行一次对比测试。
#!/usr/bin/env python2.7 import sys, time from pymongo import Connection import redis # connect to redis & mongodb redis = redis.Redis() mongo = Connection().test collection = mongo['test'] collection.ensure_index('key', unique=True) def mongo_set(data): for k, v in data.iteritems(): collection.insert({'key': k, 'value': v}) def mongo_get(data): for k in data.iterkeys(): val = collection.find_one({'key': k}, fields=('value',)).get('value') def redis_set(data): for k, v in data.iteritems(): redis.set(k, v) def redis_get(data): for k in data.iterkeys(): val = redis.get(k) def do_tests(num, tests): # setup dict with key/values to retrieve data = {'key' + str(i): 'val' + str(i)*100 for i in range(num)} # run tests for test in tests: start = time.time() test(data) elapsed = time.time() - start print "Completed %s: %d ops in %.2f seconds : %.1f ops/sec" % (test.__name__, num, elapsed, num / elapsed) if __name__ == '__main__': num = 1000 if len(sys.argv) == 1 else int(sys.argv[1]) tests = [mongo_set, mongo_get, redis_set, redis_get] # order of tests is significant here! do_tests(num, tests)
- 相对于本地内存存储,redis提供了跨进程,多进程网络访问的能力,以及更多的数据结构,支持分布式部署,持久化。如果数据缓存只在本地访问,是没有必要用redis的。
尝试把玩家对象和非玩家数据放到redis中,然后看内存占用情况。
- 因为redis无法把灵活结构的json,即不能直接使用内嵌的数据结构来存储。测试时只能把mongo中的数据序列化为json字符串,单个玩家数据比较大时,编码和解码都比较消耗CPU。
- 另外一种方案是把一个玩家数据拆分一个哈希表中,每个模块使用json字符串存储。缺点是哈希表中也是不能内嵌才使用Json编码。
第三种方案是,每个玩家身上拆出多个哈希表,每个模块自己一个表,最后大概是user:${uid}:${mod_name}, 因为模块是固定的,所以可以找到本模块自己对应的数据。这里产生另外一个缺陷,加载一个玩家时需要从redis进行多次访问才能拿到一个玩家的完整数据,拖慢了玩家登录速度,比如玩家表、任务表、英雄表、道具表等等。还一个缺陷是不能使用LRU等机制淘汰数据,因为可能把玩家部分数据丢失了。要不不丢失,要么全丢,更好处理。
小结:
- redis因内存因素无法存储大数据。(暂不考虑使用cluster模式时水平扩展内存),可以当作中小量数据缓存使用。
- 不适合存储schemaless样式数据。更适合传统关系型数据样式的存储。
更新于2021-03-12:
一般游戏服中Redis可用于热数据的存储,不可能当作大型DB使用。
于是有了一种常见有用法。根据游戏业务,我们把在线玩家数据认为是一个有限的热点数据。他会被频繁的访问。所以游戏中对线上玩家数据是可以缓存在Redis中。
- 读请求:玩家登录时发现Redis没有玩家数据,则DB_PROXY进程从DB中加载数据,写入Redis,然后返回数据加载结果。
- 写请求:所有进程都通过DB_PROXY进程从Redis取或者更新数据。最后DB_PROXY进程定时把Redis数据写入DB中。
- 回收: 当不需要数据时,需要及时清理Redis数据并落地。
可以看到上面这种方式下,DB_PROXY进程肩负Redis数据生命周期管理的角色,其他进程不能绕过DB_PROXY进程操作数
据,以避免一致性问题。
这个方案一个明显的优点是,DB_PROXY进程的状态完全使用Redis保存,可以进一步实现中断重启不丢失数据。
评论 (0)