add by zhj: 搜了一下作者,原来之前是网易的大牛。2011年的文章,有些老了,很多地方可以将string类型键转为hash类型,这样更节省内存,将key聚合在一起,也更简练。

原文:http://blog.codingnow.com/2011/11/dev_note_2.html

接上回,按照我们一期项目的需求,昨天我简单设计了数据库里的数据格式。数据库采用的是 Redis ,我把它看成一个远端的数据结构保存设备。它提供基本的 Key-Value 储存功能,没有层级表。如果需要两层结构,可以在 Value 里保存一组 Hashes 。

这是我第一次实战使用 Redis ,没有什么经验。不过类似的设施几年前自己实现过,区别不大。经过这几年,有了 Redis 这个开源项目,就不需要重造轮子了。但其模式还是比较熟悉的。也就是说,是按我历史经验来使用 Redis 。

一期项目需要比较简单,不打算把数据拆分到不同的数据服务器上。但为日后的拆分需求做好设计准备。以后有需要,可以按 Key 的前缀把数据分到不同的位置。例如,account 信息是最可能独立出去的,因为它和具体游戏无关。

用户系统使用 email 来做用户名,但在数据库中的唯一标识是一个 uid 。用户应该允许修改登陆名(用户很可能更换 email)。用户的身份识别是用 id 来定位的。所以,在数据库中就应该有如下几组 Key :

  • account:count id
  • account:userlist set(id)
  • account:email:[email] id

这里,account:userlist 对应的 value 是一个 set ,里面存放了所有存在的 user id 。用于遍历所有的 user 。这个暂时可能用不上,而且当用户量相当大的时候可能有问题。不过暂时不用考虑这么多问题,等以后改进。

account:count 是一个计数器,可以用来生成唯一 id 。

account:email:[email] 用来标示每个注册的 account 的登陆名。[email] 指登陆用 email 地址。

这里,email 内可能也存在符号 ":" ,为了回避这个问题,许多对 email 进行编码。我的方案是,将字母数字 @ . _ 之外的字符编码为 %XX 的形式。用 lua 干这件事情非常简单:

local function _encode(str)
return string.format("%%%02X",string.byte(str))
end function emailEncode(str)
return string.gsub(str,"([^%w_@.])",_encode)
end

当然,解码回来也很简单

local function _decode(str)
return string.char(tonumber(str,16))
end function emailDecode(str)
return string.gsub(str,"%%(%w%w)",_decode)
end

之后,就是 account 下每个 id 的数据:

  • account:[id]:version number
  • account:[id]:email string
  • account:[id]:password string // md5(password..salt)
  • account:[id]:nickname string
  • account:[id]:lastlogin hashes
    • ip string
    • time string
  • account:[id]:history list(string)
  • account:[id]:available enum(open/locked/delete)

其中,密码不想保存为明文。因为任何可能的数据泄露都会导致用户的损失,我也不想任何人看到用户的密码。所以采用 md5(password .. salt) 的风格。

md5 运算前,加一个 salt 后缀,是因为单纯的文本 md5 值也是有数据库可查的。

lastlogin 下保存了用户最后一次登陆的信息,使用了一张 hashes 表,因为这些信息在未来会进一步扩充。

history 保存了用户登陆的所有历史记录,用一个 string 链表记录。

用户删除自己的账户时,不想把数据从数据库删除,只想在 available 下做一个标记。

考虑到数据库内数据结构有可能发生变化,所以加了 version 域做版本标识。


我不想让各种服务可以直接读写这份数据,所以,会单独写一个认证服务器做处理。

认证服务器提供三项服务:

  1. 用户注册

  2. 用户名 密码 认证 (用于 ssl 连接上的 web 服务)

  3. 用户名 密码 挑战式认证 (用于 client 的认证服务)


下面是基本的场景服务用的数据:

  • account:[id]:avatars set(id)
  • avatar:count id
  • avatar:[id]:version number
  • avatar:[id]:account id
  • avatar:[id]:scene string
  • avatar:[id]:available enum(open/delete)
  • avatar:[id]:data hashes
    • name string
    • figure string
  • world:scene hashes
    • [name] id
  • scene:count id
  • scene:[id]:name string
  • scene:[id]:available enum(open/close/delete)
  • scene:[id]:info hashes
    • time string
    • pc number
  • scene:[id]:pc hashes
    • [id] enum[online/offline]
  • scene:[id]:pc:[id] hashes
    • status string

用户账号下可以有许多游戏角色,列表放在 account:[id]:avatars 下。

每个角色也拥有一个唯一 id 。这个 id 原则上和 account id 是独立体系,但是为了人类好区格,avatar:count 的起点和 account:count 不同。

角色所在场景记录一个字符串的场景名 avatar:[id]:scene ,角色的其它各种数据放在一个 hashes 里。

所有的场景索引方在 world:scene 下。如果日后有多个世界,可以采用 world:[id]:scene 。但目前不必考虑。

scene 下面的所有 pc 的在线状态放在 scene:[id]:pc hashes 中,pc 离线也把它的 id 记录在内,只有 pc 转移场景才移除。

每个 PC 的位置状态信息记录在 scene:[id]:pc:[id] 中,第一个 id 是 scene 的 id ,第二个则是 PC 的 avatar id 。

btw. 这是一份草稿,虽然思考不周,但足够满足项目一期的需求。当然许多欠考虑的地方也并非是考虑不到,而是希望尽量简单,以满足一期需求为目的。这个日后修改的代价并不大。


最后吐槽一下 Redis 的 Windows 版。办公室的 Linux 服务器还没有装好,我暂时在 Windows 下做开发。取了一份 google 搜到的 非官方 Redis 的 Windows 版 。为了图方便,使用的是 luajit ffi 去调用 hiredis 的 dll 。一开始怎么都搞不定。建立不了 socket 连接,出错码也取不到。

对比了源代码,发现修改版把 C Struct 结构改了,前面增加了几个域,而我以 hiredis 官方标准来定义的接口。

改好后,能够正确取出出错码了。发现万恶的 Windows socks api 需要调用 WSAStartup 才可以用。而 hiredis 的 Windows 修改版居然没有去调用。让我大费周折才改好,前后折腾了一个多小时。

再吐槽一下 hiredis 的 API 设计,居然依赖 C Struct 的布局。良好的 C 库的接口设计不会这么干的吧。比如 lua ,又比如 zmq 。唉,用这种东西有点小不爽。不过比 C++ 库还是好太多了。

某游戏应用的redis 数据库结构设计(转)的更多相关文章

  1. [转至云风的博客]开发笔记 (2) :redis 数据库结构设计

    接上回,按照我们一期项目的需求,昨天我简单设计了数据库里的数据格式.数据库采用的是 Redis ,我把它看成一个远端的数据结构保存设备.它提供基本的 Key-Value 储存功能,没有层级表.如果需要 ...

  2. Redis 数据库结构设计

    Redis设计参考资料: http://my.oschina.net/fsmwhx/blog/152130 http://my.oschina.net/1123581321/blog/164288 h ...

  3. Redis数据库云端最佳技术实践

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由腾讯云数据库 TencentDB发表于云+社区专栏 邹鹏,腾讯高级工程师,腾讯云数据库Redis负责人,多年数据库.网络安全研发经验. ...

  4. redis数据库-VUE创建项目

    redis数据库 ''' 关系型数据库: mysql, oracle 非关系型数据库(nosql): redis,mongodb (没有表的概念) key-value mongodb: json 数据 ...

  5. 运维实践-最新Nginx二进制构建编译lua-nginx-module动态链接Lua脚本访问Redis数据库读取静态资源隐式展现

    关注「WeiyiGeek」公众号 设为「特别关注」每天带你玩转网络安全运维.应用开发.物联网IOT学习! 希望各位看友[关注.点赞.评论.收藏.投币],助力每一个梦想. 本章目录 目录 0x0n 前言 ...

  6. MySQL、MongoDB、Redis数据库Docker镜像制作

    MySQL.MongoDB.Redis数据库Docker镜像制作 在多台主机上进行数据库部署时,如果使用传统的MySQL的交互式的安装方式将会重复很多遍.如果做成镜像,那么我们只需要make once ...

  7. Spring + Jedis集成Redis(集群redis数据库)

    前段时间说过单例redis数据库的方法,但是生成环境一般不会使用,基本上都是集群redis数据库,所以这里说说集群redis的代码. 1.pom.xml引入jar <!--Redis--> ...

  8. 超强、超详细Redis数据库入门教程

    这篇文章主要介绍了超强.超详细Redis入门教程,本文详细介绍了Redis数据库各个方面的知识,需要的朋友可以参考下 [本教程目录] 1.redis是什么2.redis的作者何许人也3.谁在使用red ...

  9. 深入浅出Redis02 使用Redis数据库(String类型)

    一 String类型 首先使用启动服务器进程 : redis-server.exe 1. Set 设置Key对应的值为String 类型的value. 例子:向 Redis数据库中插入一条数据类型为S ...

随机推荐

  1. 2.Stacks(堆栈)

    一.概述 C++ Stack(堆栈) 是一个容器类的改编,为程序员提供了堆栈的全部功能,也就是说实现了一个先进后出(FILO)的数据结构. 二.常用API empty() 堆栈为空则返回真 pop() ...

  2. UVA Problem B: Fire!

    Problem B: Fire! Joe works in a maze. Unfortunately, portions of the maze have caught on fire, and t ...

  3. CCNA2.0笔记_ACL

    要点: 1.按顺序执行,一旦某条语句匹配,后续语句不再处理. 2.默认ACL 结尾语句是deny any,所以你要记住的是在ACL 里至少要有1 条permit 语句. 3.记得创建了ACL 后要把它 ...

  4. Windows 内核(WRK)简介

    引子 WRK 是微软于 2006 年针对教育和学术界开放的 Windows 内核的部分源码,WRK(Windows Research Kernel)也就是 Windows 研究内核,在 WRK 中不仅 ...

  5. 15.3.14 DP练习2

    拦截导弹 题目 某国为了防御敌国的导弹突击,发展出一种导弹拦截系统. 可是这样的导弹拦截系统有一个缺陷:尽管它的第一发炮弹可以到达随意的高度.可是以后每一发炮弹都不能高于前一发的高度. 某天,雷达捕捉 ...

  6. oracle instant client,tnsping,tnsnames.ora和ORACLE_HOME

    前段时间要远程连接oracle数据库,可是又不想在自己电脑上完整安装oracleclient,于是到oracle官网下载了轻量级clientinstant client. 这玩意没有图形界面,全靠sq ...

  7. AVCapture编程理解

    AVCapture用于媒体采集,在媒体采集的流程中,会存在如下几个对象: AVCaptureDevice.这里代表抽象的硬件设备. AVCaptureInput.这里代表输入设备(可以是它的子类),它 ...

  8. 【shell】使用 /dev/null crontab

    1.linux组成kernel.shell.工具程序有sh.bash 一个例子 !#/bin/bash echo '' 执行之前chmod +x 执行./ 2.一个小窍门 cp /dev/null / ...

  9. 延迟队列DelayQueue

    应用场景:有一批广告需要不定时上下架,有可能上下架的时间间隔很长,就没必要用定时器轮询,用延迟队列进行任务执行. public class Test2 { public static void mai ...

  10. android之ViewPager修改滑动速度

    在android中,使用过viewpager的人都清楚,我们如果使用viewpager进行滑动时,如果通过手指滑动来进行的话,可以根据手指滑动的距离来实现,但是如果通过setCurrentItem函数 ...