Redis 支持说明
适用范围:NPC 脚本中的 Redis 命令、Lua 与回调结果 示例使用 Redis 7.2.16;其他版本的命令选项可能不同。
使用前须知
1、用途与数据保存
Redis 主要用于跨服共享数据、不同程序之间交换数据,以及临时状态和缓存。 数据可以持久化保存,但需要正确设置 Redis 的持久化选项;命令执行成功 不代表数据已经写入磁盘。相关说明见第十五章。
2、先了解基本概念
新手先认识键名、数据类型、数据库编号和过期时间,再使用 NPC 命令。 可以先完成第六章的入门示例,不必一开始就学习全部命令。 入门参考:https://www.runoob.com/redis/redis-intro.html 命令参考:https://redis.io/docs/latest/commands/ 网上资料介绍的是 Redis 的完整能力,NPC 可用范围以本文为准。
3、解压并启动
解压 Redis 程序包后双击 start.bat,先启动 Redis,再启动游戏服。 启动与连接步骤见第二章。
4、使用工具查看数据
使用 AnotherRedisDesktopManager 查看和管理数据,下载与连接方法见第二章。 熟悉命令行的用户也可了解 iredis,需要 Python 环境;它不是必装工具。 参考:https://github.com/laixintao/iredis
5、部署位置与连接速度
为减少通信延迟,Redis 尽量与游戏服放在同一台服务器,或同一局域网内。 同机连接一般填写 127.0.0.1;不同机器之间填写 Redis 所在机器的可访问地址。 跨机器连接时确认网络、监听地址和防火墙允许访问,不要直接向玩家开放端口。
6、通过回调读取结果
NPC 命令采用异步方式,结果只在指定回调期间读取,不能提交后立即读取。 回调规则见第四章,变量与空值说明见第五章,完整入门示例见第六章。
7、单服连接与多服共享
每个游戏服可以连接独立的 Redis,也可以多个游戏服连接同一个 Redis。 连接配置统一放在 !Setup.txt 的 [Server] 节,具体填写方法见第二章。 多服共享的键名、数据库和迁移约定见第十五章。
8、角色数据、备份与合区
保存角色相关数据时,要规划角色唯一标识、备份与恢复,不能只靠角色名字。 合区不会自动完成 Redis 数据迁移,不要把缓存当成角色资产的唯一凭据。 持久化、备份和多服维护说明见第十五章。
9、可靠队列与消息处理
需要排队处理任务时,应了解“待处理、处理中、完成确认、失败重试”的区别。 普通 LPOP/RPOP 取出后就移除了元素,取出后处理失败可能导致任务丢失。 RPOPLPUSH 可以把任务从待处理列表移到处理中列表;处理成功后再确认删除, 还需要自行约定唯一任务编号、失败回收和防重复处理规则。 支持列表命令不等于已经具备自动确认或可靠投递,也不代表可以使用阻塞命令。 初学者先完成基础读写,再设计队列;相关命令见第十章,支持范围见第十三章。 延伸阅读:https://www.zhihu.com/question/20795043
阅读顺序
第一次使用:先阅读第一至第五章,再复制第六章的完整示例。 查询命令:阅读第七至第十三章。 遇到问题:阅读第十四至第十六章。
- 一、基本概念与键名
- 二、启动 Redis 与连接配置
- 三、NPC 命令格式和引号
- 四、异步回调的使用规则
- 五、返回变量与空值
- 六、可以直接使用的入门 NPC 示例
- 七、字符串与数字命令
- 八、键名、类型与过期时间
- 九、哈希(Hash)
- 十、列表、集合和有序集合
- 十一、消息流(Stream)
- 十二、Lua 与脚本缓存
- 十三、消息发布、清理和不支持的命令
- 十四、常见错误与处理
- 十五、持久化、共享数据与日常维护
- 十六、测试顺序与参考资料
一、基本概念与键名
Redis 是一个独立运行的数据服务。游戏服向它发送命令,以保存或读取数据。
先认识几个名称:
- key(键):一份数据的名称,例如 demo:notice、server1:activity:count。
- value(值):键中保存的内容,例如 hello、100。
- field(字段):哈希中的子名称,例如 role:1001 中的 name、level。
- member(成员):集合或排行榜中的一个元素。
- score(分数):有序集合中用于排序的数字。
- DB:同一个 Redis 服务里的逻辑数据库,DB 0 和 DB 1 的同名键相互独立。
- 回调标签:命令完成后自动执行的 NPC 标签,例如 [@RedisReply]。
同一个键只能保存一种类型。例如先把 demo:data 设置成字符串,再对它执行 HSET,会产生类型错误。字符串、哈希、列表应分别使用不同键名。
键名和字段名区分大小写。demo:Name 和 demo:name 不是同一个键。 命令名可用大小写,本文统一使用大写。
建议键名使用英文、数字、冒号,例如:
| 键名示例 | 用途 |
|---|---|
| server1:activity:count | 单服活动计数 |
| shared:activity:count | 多服共享计数 |
| test:redis:notice | 测试数据 |
多人、多服使用同一个键,就会共同读写同一份数据。不要把通用的 key、num、 test 等名称直接用于正式业务,也不要把不同类型混在同一个键下。
二、启动 Redis 与连接配置
2.1 启动 Redis 和查看数据
-
将 Redis 压缩包解压到固定目录,双击目录中的 start.bat 即可启动。 启动后保留运行窗口,不要关闭。Redis 已经运行时,无需重复启动。
-
可以使用 AnotherRedisDesktopManager 查看、管理 Redis 数据。 下载地址:https://github.com/qishibo/AnotherRedisDesktopManager/releases
-
安装并打开 AnotherRedisDesktopManager,添加连接: 地址:127.0.0.1(Redis 在其他电脑时填写对应地址) 端口:6379(修改过端口时填写实际端口) 密码:没有设置则留空,设置过则填写对应密码。
-
连接成功后,选择与 RedisDBIndex 一致的数据库,例如 DB0。 执行 NPC 测试命令后刷新列表,即可查看键名、类型、内容和过期时间。 可以按测试键前缀查找数据,注意不要误删或修改其他业务数据。
2.2 设置游戏服连接
打开游戏服实际使用的 !Setup.txt,在已有的 [Server] 节加入或修改:
[Server]
RedisHost=127.0.0.1
RedisPort=6379
RedisPassword=
RedisDBIndex=0如果已经有 [Server],只添加四个键,不要再建第二个 [Server] 节。 这些配置放在 [Server],不要放到 [Setup]。
| 配置项 | 说明 |
|---|---|
RedisHost | Redis 地址。127.0.0.1 表示与游戏服在同一台电脑。Redis 在另一台电脑时填写那台电脑的可访问地址。默认空白,表示不启用 Redis 功能。 |
RedisPort | Redis 监听端口,默认 6379。不是游戏网关端口。 |
RedisPassword | Redis 的连接密码。服务没有密码时留空。填写这项只是提供连接密码,不会替 Redis 设置密码。 |
RedisDBIndex | 使用的数据库编号,默认 0。常见默认范围是 0~15,实际以 Redis 的 databases 配置为准。多个游戏服要共享数据,必须连接同一个 Redis、使用相同 DB 和相同键名。 |
修改后重启游戏服。只重载 NPC 脚本不会重新建立这些连接设置。 本文只介绍普通单机 Redis 连接,不提供集群、哨兵、TLS 或用户名 ACL 的配置。
2.3 安装测试 NPC
最方便的方式:备份一个专用测试 NPC 的脚本,替换为第六章完整示例,保留 原文件名和原 NPC 注册信息,然后重载 NPC 或重启游戏服。
新增普通商人 NPC 时:
- 找到游戏服实际环境目录中的 Merchant.txt 和 Market_Def 目录。
- 复制一条已能正常使用的普通 NPC 注册信息,调整脚本名、名称和位置。
- 在 Market_Def 放入对应脚本。普通文件名通常为“脚本名-地图号.txt”。 例如脚本名 RedisDemo、地图号 0,对应 RedisDemo-0.txt。
- 地图、坐标、外观沿用或参照现有有效配置,不要使用不确定的编号。
NPC 文本保存为 ANSI(简体中文环境下为 GBK/CP936),避免用编辑器自动转换。 不要把整份支持文档当成 NPC 脚本,只复制明确标注的完整示例或需要的片段。
三、NPC 命令格式和引号
3.1 普通命令
Redis "命令 参数1 参数2" @回调标签例如:
Redis "SET demo:notice hello" @SetReply
Redis "GET demo:notice" @GetReply
Redis "INCR demo:count" @CountReply对应回调的标签写成 [@SetReply]、[@GetReply]、[@CountReply]。 命令中的回调名带 @,标签声明外面再加方括号。 回调必须放在最后,并且不要包含在命令的双引号里。
只执行命令、不需要读取结果时,可以省略回调:
Redis "SET demo:notice hello"这不会显示执行结果。初学时建议保留回调,便于发现错误。
3.2 参数中有空格或空字符串
外层双引号包住整条命令,值里面有空格时使用单引号:
Redis "SET demo:notice 'hello world'" @SetReply
Redis "SET demo:notice '欢迎进入游戏'" @SetReply
Redis "SET demo:empty ''" @SetReply第三条保存的是长度为零的字符串,不是删除键。 所有引号必须使用英文半角引号,不要使用中文弯引号。 含引号、反斜杠的复杂文本不要直接照搬 SQL 的转义方式,先用测试键验证。
3.3 Lua 专用格式
Redis EVAL "Lua内容" 键数量 "键列表 参数列表" @回调标签
Redis EVALSHA SHA1值 键数量 "键列表 参数列表" @回调标签没有键和参数时可以省略最后的参数字符串:
Redis EVAL "return 123" 0 @LuaReplyLua 内容外层用双引号,内容中的字符串通常用单引号,避免引号冲突。 不要把 EVAL 的全部参数都塞进普通命令的一对双引号里,本文统一使用专用格式。 键数量是键名的数量,不是后面所有参数的总数。
四、异步回调的使用规则
Redis 命令提交后,不会在当前行等待结果。需要结果时,在指定回调标签中读取。
正确示例:
[@ReadNotice]
#ACT
Redis "GET demo:notice" @NoticeReply
[@NoticeReply]
#SAY
读取结果=[<$R_VALUE[demo:notice]>]\
<关闭/@exit>不要在 Redis 命令紧接着的普通语句里就读取返回值,那时命令可能尚未完成。 本文所有结果展示均使用回调标签的 #SAY,照此写法最容易核对。
必须记住:
- $R_ 结果只在当前 Redis 回调期间有效,不是永久角色变量。点击其他标签 或 DelayGoto 后不能再读取原结果。
- 本文不把 $R_ 变量直接用于下一条 Redis 命令参数或条件判断;不要直接将 下文 #SAY 的示例换成 #ACT 中的赋值、比较或发消息语句。
- 每次点击都可能提交一次新请求。INCR、LPUSH、XADD 等会重复改变数据, 必须等回调页面出现后再继续,不能因为没有立即显示就连续点击。
- 玩家退出、重新登录,或 NPC 被删除、脚本重载后,旧请求的回调可能不再 执行。但 Redis 命令可能已经执行,不能认为“没回调就没写入”。
- 回调标签不存在时,不会自动找到名称相似的标签,应检查标签拼写和日志。
一个简单命令的成功回调,不能代表前面所有其他命令也成功。 需要逐项核对时,每条重要命令都设置自己的回调。
五、返回变量与空值
变量应带完整的尖括号,例如 <$R_NUM>。数组编号从 1 开始。
| 变量 | 用途 |
|---|---|
<$R_NUM> | 数量或数值结果,含义取决于命令 |
<$R_STATUS> | 状态文本,例如 OK、PONG、ERR ... |
<$R_VALUE[键名]> | GET 等单值结果,或 MGET 中某个键的值 |
<$R_VALUE[哈希键.字段]> | HGET、HMGET、HGETALL 的字段值 |
<$R_VALUE[源列表.目标列表]> | RPOPLPUSH 返回的元素 |
<$R_VALUES[1]> | 按位置读取返回值,后续为 [2]、[3] ... |
<$R_FIELDS[1]> | HGETALL 的字段名;有序集合带分值查询的成员名 |
<$R_ID> | XADD 返回的新条目 ID |
<$R_IDS[1]> | XRANGE/XREVRANGE 的条目 ID |
<$R_JSONS[1]> | 同一位置条目的字段内容,以 JSON 文本表示 |
<$R_KEY> | 第一个返回值,常用于 RANDOMKEY |
<$R_KEYS[1]> | 按位置读取键名,常用于 KEYS |
<$R_VALUE[load]> | SCRIPT LOAD 返回的 SHA1 |
<$R_VALUE[flush]> | SCRIPT FLUSH 返回的状态文本 |
5.1 R_NUM 不是统一的“成功/失败”标志
| 命令 | R_NUM 返回含义 |
|---|---|
| SET(普通写法) | 成功通常为 1;NX/XX 条件不满足为 0 |
| GET | 存在时为 1,不存在时为 0 |
| INCR/DECR/INCRBY/DECRBY | 返回运算后的整数,可以是 0 或负数 |
| DEL | 返回实际删除的键数;键不存在时为 0 |
| HSET | 返回新增加的字段数,覆盖旧字段可返回 0 |
| MGET/HMGET | 返回位置数,缺失值仍占一个位置 |
| HGETALL | 返回字段和值的对数 |
| ZRANGE ... WITHSCORES | 返回成员和分值的对数 |
| ZRANK | 排名从 0 开始;成员不存在时为 -1 |
| ZINCRBY | 返回运算后的分数,可以带小数 |
| XRANGE/XREVRANGE | 返回条目数 |
| EVAL/EVALSHA | 返回结果的元素数量,整数结果本身读 R_VALUES[1] |
Lua 返回 nil 或 false,R_NUM 是 0,没有可读取的元素;具体示例见第十二章。
5.2 空值与空字符串
GET 不存在的键:R_NUM=0,R_VALUE 为空。 GET 值为空字符串的键:R_NUM=1,R_VALUE 也显示为空。 因此不能只看显示内容是否为空来判断键存在与否。
MGET 按请求顺序返回。例如 a 存在、b 不存在、c 存在:
Redis "MGET a b c" @MultiReply返回三个位置。第二项显示空白,第三项仍是 c 的值,不会向前移动。 在这种数组中,缺失项与真实空字符串都显示为空,单靠该项内容无法区分。
空列表查询可以返回 0 个元素,这属于正常结果,不必然表示错误。 Lua 嵌套数组按顺序展开。例如 {'A',false,{'B','C'}} 显示为 A、空、B、C。 当前返回变量不保留嵌套层级,不要用它传递需要保留层级的复杂结构。
5.3 状态与错误
PING 通常返回 R_STATUS=PONG;普通 SET 通常返回 R_STATUS=OK。 GET、计数、数组等命令成功时,R_STATUS 可以为空。 不能因为 R_STATUS 为空或 R_NUM=0 就一概判断为失败。
命令执行发生错误时,回调中 R_STATUS 以 ERR 开头,R_NUM=0,结果值清空。 如果命令在提交前就因未启用、不支持或参数格式错误而被拒绝,可能只记录 脚本错误,不会进入回调。此时应检查游戏服日志。
六、可以直接使用的入门 NPC 示例
下面从 [@main] 到最后一个标签是一份完整脚本。先备份原脚本再复制。 只操作 test:redis:notice 一个测试键。请一名玩家按顺序操作。
[@main]
#SAY
Redis 入门测试\
请每次等待结果页面出现,再点击下一步。\
<测试连接/@DemoPing>\
<清理测试数据/@DemoDelete>\
<关闭/@exit>
[@DemoPing]
#ACT
Redis "PING" @DemoPingReply
[@DemoPingReply]
#SAY
连接结果=[<$R_STATUS>],预期 PONG。\
<写入测试文字/@DemoSet>\
<返回/@main>
[@DemoSet]
#ACT
Redis "SET test:redis:notice '欢迎进入游戏' EX 600" @DemoSetReply
[@DemoSetReply]
#SAY
写入结果=[<$R_STATUS>],预期 OK。\
数量=[<$R_NUM>],预期 1。\
<读取测试文字/@DemoGet>\
<返回/@main>
[@DemoGet]
#ACT
Redis "GET test:redis:notice" @DemoGetReply
[@DemoGetReply]
#SAY
数量=[<$R_NUM>],预期 1。\
内容=[<$R_VALUE[test:redis:notice]>],预期 欢迎进入游戏。\
<清理测试数据/@DemoDelete>\
<返回/@main>
[@DemoDelete]
#ACT
Redis "DEL test:redis:notice" @DemoDeleteReply
[@DemoDeleteReply]
#SAY
删除数量=[<$R_NUM>]。\
有测试键时通常为 1,已经删除或过期时为 0。\
<返回/@main>\
<关闭/@exit>预期顺序:PONG -> OK/1 -> 1/欢迎进入游戏 -> 删除数量 1。 写入后超过 600 秒再读取时,测试键可能已经过期,读取数量为 0 属于正常现象。
七、字符串与数字命令
以下格式中的 key、value、field 等是占位名称,使用时替换为实际内容。 [参数] 表示可选,方括号本身不输入。这里的方括号与 R_VALUES[1] 的变量写法 不同:变量里的方括号必须保留。 命令示例是片段,须放在 #ACT 中;@StringReply 等回调标签需自行补充。
SET key value [EX 秒数 | PX 毫秒数] [NX | XX]创建或覆盖字符串。键不存在时会创建;不加条件会覆盖旧值。 EX/PX 设置相对过期时间;NX 要求键不存在,XX 要求键已存在。 普通 SET 会清除原有过期时间;需要保留或重设时必须明确选用对应选项。 本文按基础 SET 讲解,不以 SET GET 等选项的旧值返回作为写入成功判据。
例:
Redis "SET demo:notice hello EX 60" @StringReply例:
Redis "SET demo:lock held EX 30 NX" @StringReplyNX 条件不满足是正常的未写入,不是连接错误。
SETNX key value仅键不存在时写入。R_NUM=1 表示写入,0 表示已存在。 本命令不自带过期参数,需要同时设置过期时优先用 SET ... EX ... NX。
GET key获取字符串。通过 R_VALUE[key] 或 R_VALUES[1] 读取。
例:
Redis "GET demo:notice" @StringReplyMSET key1 value1 key2 value2 ...一次写入多个键。普通成功结果 R_STATUS=OK、R_NUM=1。
例:
Redis "MSET demo:a one demo:b two" @StringReplyMGET key1 key2 ...一次读取多个字符串。R_NUM 是位置数,不是存在的键数。
例:
Redis "MGET demo:a demo:b demo:missing" @StringReply读取:R_VALUE[demo:a]、R_VALUES[1]、R_VALUES[2]、R_VALUES[3]。
APPEND key value在字符串末尾追加,键不存在时创建。R_NUM 是追加后的字节长度。
例:
Redis "APPEND demo:notice '_world'" @StringReplySTRLEN key返回字符串字节长度。不存在时为 0;中文不等于一个字节。
GETRANGE key start end获取指定字节范围,包含结束位置。下标从 0 开始,-1 表示最后一个字节。
例:
Redis "GETRANGE demo:notice 0 4" @StringReply按字节截取中文可能破坏字符边界,初学时只对英文或数字使用。
SETRANGE key offset value从字节偏移 offset 开始覆盖内容,不是插入。返回修改后的字节长度。
例:
Redis "SETRANGE demo:notice 0 HELLO" @StringReplyINCR key / DECR key整数加 1 / 减 1,结果读取 R_NUM。 键不存在时以 0 为基础:INCR 后为 1,DECR 后为 -1,并创建键。
INCRBY key increment / DECRBY key decrement整数增加 / 减少指定数量,返回运算后的值。
例:
Redis "INCRBY demo:count 10" @CountReply要求原值是合法整数;文字、小数或超出整数范围都会报错。 不要先 GET 再 SET 来实现多人共享计数,直接使用这些计数命令。
八、键名、类型与过期时间
DEL key [key ...]删除明确指定的键,R_NUM 为实际删除数量。它不把 * 当作通配符删除。
例:
Redis "DEL demo:a demo:b" @KeyReplyEXISTS key [key ...]返回存在的键的数量。检查单键时结果为 1 或 0。
TYPE key返回类型,例如 string、hash、list、set、zset、stream;不存在为 none。 读取 R_VALUE[key]。
RENAME key newkey重命名;目标存在时会覆盖目标,源键不存在时报错。成功状态为 OK。
RENAMENX key newkey只在目标不存在时重命名。R_NUM=1 成功、0 未执行。
KEYS pattern返回匹配键,R_NUM 为数量,R_KEYS[1]、R_KEYS[2] 读取键名。 模式使用通配规则,不是正则表达式:* 匹配任意长度,? 匹配一个字符。
例:
Redis "KEYS test:redis:*" @KeyReply返回顺序不保证固定。此命令会扫描数据库,不应在大量数据上频繁调用。
RANDOMKEY从当前 DB 返回一个随机键,读取 R_KEY;DB 为空时没有结果。 不限定业务前缀,不要拿它寻找某个玩家的专属键。
TTL key / PTTL key返回剩余有效期,分别以秒 / 毫秒为单位。 -1 表示键存在但无过期时间;-2 表示键不存在;非负数表示剩余时间。
EXPIRE key seconds / PEXPIRE key milliseconds设置相对过期时间。R_NUM=1 表示设置成功,0 通常表示键不存在。
例:
Redis "EXPIRE demo:notice 60" @KeyReplyEXPIREAT key timestamp / PEXPIREAT key timestamp设置绝对过期时间,分别使用 Unix 秒 / Unix 毫秒时间戳。 Unix 时间戳表示同一时刻,不因时区不同直接加减 8 小时。 初学时优先使用 EXPIRE 或 SET EX,避免时间戳单位弄错。
PERSIST key移除过期设置。返回 1 表示移除成功,0 表示不存在或原本没有过期设置。 “永久有效”只表示不按 TTL 过期,不等于已经写入磁盘。
MOVE key db将键移到同一 Redis 实例的另一个 DB。R_NUM=1 成功,0 未移动。
例:
Redis "MOVE demo:notice 1" @KeyReply不会改变后续命令使用的 DB;后续命令仍使用配置中的 RedisDBIndex。
九、哈希(Hash)
哈希可以理解为“一个键下面有多个字段”。例如 demo:role 有 name、level。
HSET key field value [field value ...]写入字段,键不存在时创建哈希。R_NUM 是新增字段数,不是修改字段总数。
例:
Redis "HSET demo:role name warrior level 10" @HashReplyHGET key field读取一个字段,使用 R_VALUE[demo:role.name]。
HDEL key field [field ...]删除指定字段,返回实际删除字段数。不是删除其他字段。
HMSET key field value [field value ...]批量写入字段,成功通常返回 OK。新写法也可直接使用多字段 HSET。
HMGET key field [field ...]按指定字段顺序读取;缺失字段保留空位置。
HGETALL key获取全部字段和值。R_NUM 为字段对数。 R_FIELDS[n] 是字段名,R_VALUES[n] 是同一字段的值,不要依赖固定顺序。
展示示例(先用 HSET 写入两个字段):
[@ReadRole]
#ACT
Redis "HMGET demo:role name missing level" @RoleReply
[@RoleReply]
#SAY
返回位置数=[<$R_NUM>],预期 3。\
名字=[<$R_VALUE[demo:role.name]>]\
第一项=[<$R_VALUES[1]>]\
第二项=[<$R_VALUES[2]>],缺失字段为空。\
第三项=[<$R_VALUES[3]>]\
<关闭/@exit>十、列表、集合和有序集合
10.1 列表(List)
列表有顺序,允许重复值。左边是头部,右边是尾部。
LPUSH key value [value ...]从左边加入,返回加入后的列表长度。
RPUSH key value [value ...]从右边加入,返回加入后的列表长度。
LPUSHX key value仅列表存在时从左边加入。
RPUSHX key value仅列表存在时从右边加入。
LPOP key移除并返回左边一个元素。
RPOP key移除并返回右边一个元素。
LRANGE key start stop读取范围,包含 stop,0 是第一项,-1 是最后一项。
LLEN key返回列表长度,不存在时为 0。
LTRIM key start stop只保留指定范围,其余元素被删除,成功状态为 OK。
LREM key count value移除匹配元素,返回实际删除数量。
RPOPLPUSH source destination从源右边移出一个元素,放到目标左边,并返回它。
LREM 的 count:正数从左向右最多移除 count 个;负数从右向左最多移除 绝对值个;0 表示移除所有匹配元素。
LPOP/RPOP/RPOPLPUSH 在没有可取元素时立即返回空结果,不会等待新元素。 移出的值可读 R_VALUES[1];RPOPLPUSH 也可读 R_VALUE[source.destination]。
例:先执行 Redis "RPUSH demo:list red blue",再查询:
Redis "LRANGE demo:list 0 9" @ListReply返回值使用 R_VALUES[1]、R_VALUES[2]。重复执行 RPUSH 会增加重复元素。
10.2 集合(Set)
集合不重复,不保证返回顺序。
SADD key member [member ...]添加成员,返回新增加的数量。
SREM key member [member ...]删除成员,返回实际删除数量。
SMEMBERS key返回所有成员,使用 R_VALUES[n]。
SISMEMBER key member判断成员是否存在,R_NUM 为 1 或 0。
SCARD key返回成员数。
SDIFF key [key ...]返回第一个集合中存在、后续集合中不存在的成员。
SUNION key [key ...]返回所有集合成员的并集,不重复。
SUNIONSTORE destination key [key ...]将并集保存到目标键,覆盖目标原内容,R_NUM 为保存的成员数。
例:
Redis "SADD demo:set red red blue" @SetMembersReply在新集合上返回 2,因为重复的 red 只保存一次。
10.3 有序集合(Sorted Set)
成员唯一,每个成员对应一个分数,适合排行榜。同一成员再次 ZADD 会更新分数。
ZADD key score member [score member ...]添加或更新成员。基础写法返回新增成员数,更新旧成员可返回 0。
ZREM key member [member ...]删除成员,返回实际删除数。
ZCARD key返回成员数。
ZCOUNT key min max返回分数范围内的成员数,基础范围包含两端。
ZRANK key member从低分到高分的排名,第一名是 0,不存在为 -1。
ZRANGE key start stop [WITHSCORES]基础写法按分数从低到高读取名次范围。
ZREVRANGE key start stop [WITHSCORES]按分数从高到低读取名次范围。
ZINCRBY key increment member增加成员分数,R_NUM 为新分数,支持小数。
ZUNIONSTORE destination numkeys key [key ...][WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX] 合并多个有序集合并保存到目标;numkeys 是参与合并的键数。 覆盖目标原内容,R_NUM 是目标成员数。
不带 WITHSCORES:R_VALUES[n] 是成员名。 带 WITHSCORES:R_FIELDS[n] 是成员名,R_VALUES[n] 是对应分数,R_NUM 是成员数。
示例准备:先执行 Redis "ZADD demo:rank 100 alice 200 bob",再进入查询标签。 若测试键中只有这两个成员,预期 bob 排第一、分数 200,alice 排第二、分数 100。
[@ReadRank]
#ACT
Redis "ZREVRANGE demo:rank 0 9 WITHSCORES" @RankReply
[@RankReply]
#SAY
本次返回人数=[<$R_NUM>]\
第一位=[<$R_FIELDS[1]>],分数=[<$R_VALUES[1]>]\
第二位=[<$R_FIELDS[2]>],分数=[<$R_VALUES[2]>]\
<关闭/@exit>十一、消息流(Stream)
消息流可以保存一条条带字段的记录,每条记录有独立 ID。 ID 通常形如 1607488241589-0;使用 * 让 Redis 自动生成。
XADD key [MAXLEN [~] count] * field value [field value ...]新增条目,返回 ID,读取 R_ID。 MAXLEN 限制条目数;带 ~ 表示近似限制,实际数量可能超过指定值。
XDEL key ID [ID ...]按明确 ID 删除条目,返回实际删除数。
XLEN key返回条目数。
XTRIM key MAXLEN [~] count保留较新的条目,返回裁掉的数量。
XRANGE key start end [COUNT count]按 ID 从小到大读取;- 表示最小边界,+ 表示最大边界。
XREVRANGE key end start [COUNT count]按 ID 从大到小读取,注意参数顺序相反,常写成 + -。
写入例:
Redis "XADD demo:stream MAXLEN 100 * name warrior action login" @StreamWriteReply读取例:
[@ReadStream]
#ACT
Redis "XREVRANGE demo:stream + - COUNT 2" @StreamReadReply
[@StreamReadReply]
#SAY
本次返回条目数=[<$R_NUM>]\
第一条 ID=[<$R_IDS[1]>]\
第一条内容=[<$R_JSONS[1]>]\
第二条 ID=[<$R_IDS[2]>]\
第二条内容=[<$R_JSONS[2]>]\
<关闭/@exit>ID 与 JSON 使用相同下标才能对应。条目数不足时,超出位置显示为空。 JSON 是用于展示字段内容的文本,不是另一个需要复制到 NPC 的命令。 这不是订阅功能。需要新记录时再次查询;当前不提供消费者组或阻塞读取。
十二、Lua 与脚本缓存
Lua 在 Redis 服务端执行,用于把多个 Redis 操作组织成一次处理。 它不能调用 NPC 命令、读取游戏对象或直接给玩家发道具。 Lua 中使用 redis.call('命令', 参数...) 调用 Redis 命令。 一次脚本执行期间不会插入其他客户端的命令,但发生运行错误时,已经完成的 写入不会自动回滚。不要把 Lua 错误当成“什么都没有执行”。
12.1 KEYS 和 ARGV
KEYS[1]、KEYS[2] 是传入的键名。 ARGV[1]、ARGV[2] 是键名之后的普通参数。 不要用 KEYS 传递数字常量;键名放 KEYS,增量等参数放 ARGV。
下面把 demo:lua:count 增加 5。不存在时首次结果为 5,重复执行继续累加。
[@LuaAdd]
#ACT
Redis EVAL "return redis.call('INCRBY',KEYS[1],ARGV[1])" 1 "demo:lua:count 5" @LuaAddReply
[@LuaAddReply]
#SAY
返回元素数=[<$R_NUM>],预期 1。\
累加后的值=[<$R_VALUES[1]>]\
<关闭/@exit>12.2 返回值
Redis EVAL "return 'hello'" 0 @LuaValueReply返回一个元素 hello,R_NUM=1。
Redis EVAL "return 123" 0 @LuaValueReply返回一个元素 123,R_NUM=1。
Redis EVAL "return {'A',false,{'B','C'}}" 0 @LuaValueReply返回四个位置:A、空、B、C,R_NUM=4。
Redis EVAL "return nil" 0 @LuaValueReply没有返回元素,R_NUM=0,属于正常空结果。
Lua 数组中需要保留空位时使用 false。不要使用带 nil 空洞的数组来表达 固定长度列表。需要返回精确小数文本时,可使用 tostring,而不是依赖 Lua 数值直接转换后的显示结果。
12.3 SCRIPT LOAD 与 EVALSHA
SCRIPT LOAD 只把脚本放入缓存,不执行它。成功后返回 40 位 SHA1。 EVALSHA 使用这个 SHA1 执行已缓存的脚本。 Lua 内容的空格、换行、引号变化都会改变 SHA1,必须精确对应。
以下是可合并到 NPC 的独立示例。先点加载,看到结果后再点执行。
[@LuaCache]
#ACT
Redis "SCRIPT LOAD" "return ARGV[1]" @LuaLoaded
[@LuaLoaded]
#SAY
SHA=[<$R_VALUE[load]>]\
预期 098e0f0d1448c0a81dafe820f66d460eb09263da\
<执行缓存脚本/@LuaRunCached>\
<关闭/@exit>
[@LuaRunCached]
#ACT
Redis EVALSHA 098e0f0d1448c0a81dafe820f66d460eb09263da 0 "hello" @LuaCachedReply
[@LuaCachedReply]
#SAY
返回元素数=[<$R_NUM>],预期 1。\
内容=[<$R_VALUES[1]>],预期 hello。\
<关闭/@exit>SCRIPT EXISTS sha1 [sha1 ...] 检查脚本缓存。各位置返回 1(存在)或 0(不存在)。
例:
Redis "SCRIPT EXISTS 098e0f0d1448c0a81dafe820f66d460eb09263da" @CacheReply读取 R_VALUES[1]。R_NUM 是检查位置数,不是存在的脚本数。
SCRIPT FLUSH清除整个 Redis 实例的脚本缓存,不限于当前 DB。会影响其他客户端的 EVALSHA。不要把它作为普通 NPC 的“清理测试数据”按钮。
Redis 重启、缓存清理等情况后,EVALSHA 可能返回 NOSCRIPT。 此时重新 SCRIPT LOAD,或使用 EVAL 发送完整脚本,不会自动替你补载脚本。
12.4 Lua 使用注意
脚本保持简短,不写无限循环,也不要把大量数据遍历放进一次执行。 不要在 Lua 中尝试订阅、等待消息或连接其他服务。 脚本报错会进入错误回调,但超时不代表脚本一定停止;涉及计数、领取或扣减 时,先核对实际数据,再决定是否重试。
十三、消息发布、清理和不支持的命令
13.1 PUBLISH channel message
向指定频道发布消息,R_NUM 为 Redis 返回的接收订阅者数量。
例:
Redis "PUBLISH demo:channel hello" @PublishReply返回 0 通常表示没有接收者,不代表发布命令连接失败。 发布订阅的消息不会因为启用了持久化就自动成为离线消息。 当前 NPC 可以发布,但不提供订阅回调;没有约定的外部接收者时,不要把 PUBLISH 当作跨服必达通知。需要保留记录时可考虑 Stream 的存储和查询。
13.2 清理命令
DEL:优先使用,明确写出要删除的测试键。 FLUSHDB:删除当前 DB 的全部键,成功通常返回 OK。 FLUSHALL:删除整个实例所有 DB 的全部键,成功通常返回 OK。
后两项虽然可以使用,但不能放在普通玩家可点击的菜单里,本文也不提供 可直接复制的清空按钮。区分“清理几个测试键”和“清空整个数据库”。
13.3 当前不支持的命令
订阅:SUBSCRIBE、PSUBSCRIBE、UNSUBSCRIBE、PUNSUBSCRIBE。 阻塞操作:BLPOP、BRPOP、BRPOPLPUSH,以及阻塞式消息流读取。 事务:MULTI、EXEC、WATCH、UNWATCH、DISCARD。 游标遍历:SCAN、SSCAN、HSCAN、ZSCAN。 脚本调试控制:SCRIPT KILL、SCRIPT DEBUG。 脚本内切换连接状态:AUTH、SELECT;请通过 [Server] 的配置设置密码和 DB。 其他未在本文支持清单中的命令,也不要直接假定可以使用。
本支持范围中的 SCRIPT 子命令只有 LOAD、EXISTS、FLUSH。 命令名受支持不表示所有新版本附加选项都经过验证,优先使用本文基础格式。
十四、常见错误与处理
Redis 未启用 检查 !Setup.txt 的 [Server] RedisHost 是否为空,并重启游戏服。 只在 [Setup] 配置同名项目不会启用连接。
连接失败、连接被拒绝 检查 Redis 是否运行、地址和端口是否正确。127.0.0.1 只表示本机。 用 redis-cli ping 检查。跨机器连接还需检查监听地址和防火墙。
NOAUTH、WRONGPASS 检查 RedisPassword 是否与 Redis 服务一致。不要在 NPC 中写 AUTH。
DB index is out of range 数据库编号超过 Redis 配置范围,改用有效编号后重启游戏服。
value is not an integer / out of range 对非整数内容执行了 INCR 等,或超出数值范围。先核对原值和输入参数。
Wrong number of args / wrong number of arguments 参数数量不对。检查空格、引号、Lua 的键数量与 KEYS/ARGV 对应关系。 return redis.call('GET') 少传了 key,会产生这个错误。
不支持的命令、参数引号未闭合 命令未被接受,可能没有回调。先核对支持范围和英文半角引号。
回调一直没有显示 检查是否写了 @回调、对应 [@标签] 是否存在、日志是否有提交错误。 玩家退出或 NPC 重载也可能让旧回调不再执行。 不要通过连续点击尝试“催出”结果。
读取是空的 检查 DB、键名大小写、键是否过期、是否在当前回调 #SAY 中读取。 空值本身可能正常,结合 R_NUM 和命令含义判断。
变量原样显示或中文乱码 检查 <$R_...> 写法、脚本是否完整、文本编码是否保持 GBK/CP936。 确认使用的服务端版本提供这些命令和变量。
请求超时或连接中断 不能据此判断写入是否已完成。INCR、列表入队、XADD 等盲目重试可能重复。 先查看 Redis 中的实际结果,再人工决定下一步;涉及业务操作时应设计 可核对的唯一编号,避免一次操作因重复点击被执行多次。
返回数据过大 避免 KEYS *、大范围 LRANGE 和无限量消息流查询。 用较小范围、COUNT 或按业务拆分键来减少一次查询的数据量。
十五、持久化、共享数据与日常维护
Redis 的数据首先在内存中。是否写入磁盘取决于 Redis 自身的持久化配置, 与 NPC 命令是否显示 OK 不是一回事。
RDB:按条件保存快照;异常停止时,最近一次快照之后的修改可能丢失。 AOF:记录写入操作;常见 everysec 策略约每秒同步一次,也不是绝对零丢失。 两种方式可以同时使用,具体取舍根据数据重要性、性能和恢复要求确定。
只读检查运行配置,可在 Redis 所在目录执行:
.\redis-cli.exe CONFIG GET save appendonly appendfsync dir dbfilename
.\redis-cli.exe INFO persistence有密码时加入 --askpass。以上是管理员在终端中执行的命令,不是 NPC 命令。 不要只看 redis.conf 某一行是否被注释;运行时可能采用默认值或其他启动参数。 save 与 appendonly 的实际返回才是核对当前设置的重要依据。
保持数据目录固定;迁移或重装时同时备份数据和配置,并实际测试恢复。 不要仅因存在 dump.rdb 或 AOF 文件就认定最近一次保存一定成功。 RDB/AOF 也不等于独立备份。清空、覆盖等误操作可能被继续持久化。
多个游戏服共享数据时:
- 地址、端口、DB 必须一致。
- 统一键名、类型、单位和过期规则。
- 单服数据使用区服前缀,共享数据使用明确的共享前缀。
- 合区前确认相关工具是否支持 Redis 数据,单独安排迁移、备份和重复键处理。
十六、测试顺序与参考资料
建议按下面顺序验收,每一步都等回调显示后再继续:
- PING:PONG。
- SET/GET:能写入并读回英文、中文和包含空格的文字。
- GET 缺失键:R_NUM=0,不应当作异常。
- MGET/HMGET:中间缺失项保持空位,后续值不向前移动。
- INCR/DECR:不存在的键分别得到 1 / -1;重复执行会继续变化。
- HGETALL:字段和值按相同下标对应。
- 排行榜:成员和分数对应,确认升序/降序选择正确。
- XADD/XRANGE:写入 ID 与读出的条目一致。
- EVAL:用返回整数、字符串、nil 和数组的脚本分别测试。
- SCRIPT LOAD/EVALSHA:加载后执行成功。
- 故意少传参数:出现错误状态;下一条 PING 仍能得到 PONG。
- 清理明确的测试键,确认不会影响其他业务键。
参考资料(命令版本请选择 Redis 7.2,避免误用后续版本新增选项): Redis 命令:https://redis.io/docs/latest/commands/ SET:https://redis.io/docs/latest/commands/set/ KEYS:https://redis.io/docs/latest/commands/keys/ Lua:https://redis.io/docs/latest/develop/programmability/lua-api/ 持久化:https://redis.io/docs/latest/operate/oss_and_stack/management/persistence/