玩家数据模型设计
概述
玩家数据是游戏服务器的核心资产:金币、等级、背包、任务、成就、好友关系……它们的组织方式决定了读写性能、扩展性与开发效率。本文设计一套玩家数据模型:Player 核心实体、各子模块划分、E-R 模型与字段规划,并给出"内存对象 + 落库表"的双层映射。
一、设计原则
1.1 玩家数据的特征
高并发读写:
在线玩家数据常驻内存,每次操作都在改
低频落库:
不需要每次操作都写数据库
按玩家隔离:
绝大多数数据只属于一个玩家,天然分片设计原则:
按玩家聚合(一行/一块)→ 查询高效
热数据与冷数据分离 → 内存/存储分层
版本号保护 → 防并发覆盖
可扩展 → 新玩法模块可平滑添加1.2 分层模型
内存态(运行期):Player 对象(含全部子模块)
存储态(落库):MySQL 表 / Redis 缓存
Player 内存对象 ↔ Player 存储记录 双向映射
内存态特点:
直接操作,毫秒级
崩溃丢数据 → 靠定时/事件落库
存储态特点:
可靠持久,恢复用二、Player 核心实体
2.1 核心字段
java
public class Player {
private long playerId; // 玩家唯一 ID(主键)
private String nickname; // 昵称
private int level; // 等级
private long exp; // 经验
private long coin; // 金币(软通货)
private long diamond; // 钻石(硬通货)
private long energy; // 体力
private long lastEnergyTime; // 体力最后恢复时间
private int vipLevel; // VIP 等级
private long createTime; // 创建时间
private long lastLoginTime; // 最后登录时间
private long lastLogoutTime; // 最后下线时间
private String channel; // 注册渠道
private long version; // 数据版本号(乐观锁)
private PlayerProfile profile; // 个人资料子模块
private PlayerBag bag; // 背包子模块
private PlayerQuest quest; // 任务子模块
private PlayerAchievement achievement; // 成就子模块
private PlayerFriends friends; // 好友子模块
}字段规划要点:
playerId 全服务器唯一(雪花 ID / 自增 + 分表)
货币区分软硬通货(见经济系统篇)
体力带"最后恢复时间"实现离线累积
version 用于并发控制2.2 唯一标识生成
| 方案 | 说明 | 适用 |
|---|---|---|
| 数据库自增 | 简单,但分表后需全局方案 | 单体小规模 |
| 雪花算法 | 趋势递增、无中心 | 分布式推荐 |
| Redis INCR | 简单全局递增 | 中低量级 |
| UUID | 无顺序、索引差 | 不推荐做主键 |
雪花 ID 结构:
时间戳(41位) + 机器ID(10位) + 序列号(12位)
单机单毫秒可生成 4096 个 ID三、子模块划分
3.1 模块清单
| 模块 | 内容 | 关联系统 |
|---|---|---|
| 个人资料 | 头像、签名、性别、地区 | 社交 |
| 背包 | 道具、数量、有效期 | 道具系统 |
| 任务 | 每日/每周/主线进度 | 任务系统 |
| 成就 | 达成进度、奖励领取状态 | 成就系统 |
| 好友 | 好友列表、申请、分组 | 好友系统 |
| 邮箱 | 邮件、附件状态 | 邮件系统 |
| 排行 | 榜单分数、上次排名 | 排行榜 |
| 战斗 | 段位、胜场、胜率 | 对战系统 |
| 经济流水 | 货币增减日志 | 经济系统 |
模块化收益:
各模块独立开发、独立落库
新玩法加新模块,不动核心表
按模块分表,降低单表压力3.2 背包(道具)模型
道具两种存储:
堆叠道具:itemId + count(合成类)
独立道具:itemId + uid(装备、有绑定)
表/对象设计:
BagEntry {
long uid; // 道具实例 ID
int itemId; // 道具配置 ID
int count; // 数量
long expireAt; // 过期时间(0 永久)
boolean bound; // 是否绑定
long acquireTime; // 获得时间
}3.3 任务与成就模型
任务进度模型:
QuestProgress {
int questId;
int state; // 未接/进行中/可领取/已完成
Map<String, Long> progress; // 各目标进度
}
成就模型:与任务结构类似,状态只有"未达成/可领取/已领取"
通用点:进度记录 + 状态机 + 领取幂等四、E-R 模型
4.1 核心关系
Player 1 ─── 1 Profile(资料)
Player 1 ─── N BagEntry(背包道具)
Player 1 ─── N QuestProgress(任务)
Player 1 ─── N AchievementRecord(成就)
Player 1 ─── N FriendRelation(好友:playerId + friendId)
Player 1 ─── N MailBox(邮件)
Player 1 ─── N CurrencyLog(货币流水)
Player 1 ─── 1 BattleStat(对战统计)4.2 主要表结构
sql
-- 玩家主表:热数据聚合
CREATE TABLE player (
player_id BIGINT PRIMARY KEY,
nickname VARCHAR(32) NOT NULL,
level INT NOT NULL DEFAULT 1,
exp BIGINT NOT NULL DEFAULT 0,
coin BIGINT NOT NULL DEFAULT 0,
diamond BIGINT NOT NULL DEFAULT 0,
energy INT NOT NULL DEFAULT 100,
last_energy_time BIGINT NOT NULL DEFAULT 0,
vip_level INT NOT NULL DEFAULT 0,
channel VARCHAR(16),
create_time BIGINT NOT NULL,
last_login_time BIGINT NOT NULL,
last_logout_time BIGINT NOT NULL,
version BIGINT NOT NULL DEFAULT 0
) ENGINE=InnoDB;
-- 背包表
CREATE TABLE player_bag (
uid BIGINT PRIMARY KEY,
player_id BIGINT NOT NULL,
item_id INT NOT NULL,
count INT NOT NULL,
expire_at BIGINT NOT NULL DEFAULT 0,
bound TINYINT NOT NULL DEFAULT 0,
acquire_time BIGINT NOT NULL DEFAULT 0,
KEY idx_player (player_id)
) ENGINE=InnoDB;4.3 好友关系表
sql
-- 好友关系:双向各存一行 or 一行存双方(推荐一行)
CREATE TABLE player_friend (
player_id BIGINT NOT NULL,
friend_id BIGINT NOT NULL,
group_name VARCHAR(16) DEFAULT '',
create_time BIGINT NOT NULL,
PRIMARY KEY (player_id, friend_id)
) ENGINE=InnoDB;好友查询:
SELECT friend_id FROM player_friend WHERE player_id = ?
好友列表按 playerId 分片友好五、内存对象与存储的同步
5.1 同步策略
写路径:
业务改内存 Player 对象 → 标记脏数据 → 异步落库
读路径:
登录时加载到内存 → 后续操作全部读内存
同步时机(详见持久化策略篇):
定时批量落库(如 30s)
关键操作即时落库(充值、重要消耗)
下线时强制落库5.2 版本号保护
乐观锁:
UPDATE player SET coin=?, version=version+1
WHERE player_id=? AND version=?
更新行数为 0 → 版本冲突 → 重试/告警
适用场景:
跨节点重复落库、后台修改与在线冲突
防"最后写入覆盖"六、模块扩展示例
6.1 新玩法模块接入
以"宠物系统"为例:
1. 新增 PlayerPet 对象,挂到 Player
2. 新增 player_pet 表(player_id 主键维度)
3. 登录加载、定时落库、下线落库各加一段
4. 新玩法 Handler 直接操作 Player.pet
→ 核心 Player 与已有模块零改动6.2 字段命名规范
统一约定:
主键:xxx_id
时间:全部存 BIGINT 毫秒时间戳(避免时区问题)
数量:long(防溢出)
状态:TINYINT + 枚举常量类
布尔:TINYINT(1)七、常见问题
| 问题 | 处理 |
|---|---|
| 单行数据过大 | 拆子模块表,只查需要的模块 |
| 道具量级大 | 背包分页加载 / 冷道具归档 |
| 好友量大 | 分表分页,缓存热好友 |
| 数据不一致 | 版本号 + 落库重试 |
| 新玩法改动核心表 | 模块化,避免改核心字段 |
八、小结
玩家数据模型以 Player 为核心聚合体:playerId 全局唯一,核心字段覆盖身份、等级、货币、体力、时间线,各玩法以子模块形式挂载(背包、任务、成就、好友、邮件等)。设计上坚持"按玩家聚合、冷热分离、模块化扩展、版本号保护"四条原则,内存对象负责运行期读写,MySQL 表负责可靠持久,两者通过登录加载、定时落库、下线落库完成同步。这样的模型既能支撑高并发在线操作,又能平滑承接后续十几周的玩法系统。