标签搜索

Redis在游戏中应用(一)

anker
2021-06-27 / 0 评论 / 17 阅读 / 正在检测是否收录...

redis作为历史已久的工具,在数据缓存方案有比较成熟的验证。这里做一些小实验
,验证对游戏玩家存储可行性。

  1. 用法:

    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.

  2. 有多条redis命令需要原子执行时,可以合并了一个Lua代码后用eval命令执行。或者使用multi-exec命令。
  3. 当使用订阅发布时,创建出来的新对象也会产生一个redis连接。
  4. 因为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个数据。另外动态设置的配置在重启后会丢失。

  5. 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)
  6. 相对于本地内存存储,redis提供了跨进程,多进程网络访问的能力,以及更多的数据结构,支持分布式部署,持久化。如果数据缓存只在本地访问,是没有必要用redis的。
  7. 尝试把玩家对象和非玩家数据放到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

评论 (0)

取消