网站不能粘贴怎么做,专业的网站建设价格,深圳网站的优化公司,seo网站外链工具一、MySQL实现方法
假设我们要设计一款排名系统#xff0c;那必然要涉及到两大类数据#xff1a;武器数据和非武器的通用数据#xff0c;它他通常有一个共用的属性#xff1a;那就是主键唯一的#xff0c;例如玩家的数字编号#xff0c;通常在MySQL中是自增的无符号整数…一、MySQL实现方法
假设我们要设计一款排名系统那必然要涉及到两大类数据武器数据和非武器的通用数据它他通常有一个共用的属性那就是主键唯一的例如玩家的数字编号通常在MySQL中是自增的无符号整数字段。
非武器的通用数据可以理解为跟武器没有任何关联的数据例如玩家ID昵称签名注册时间登录时间等等在MySQL中就类似如下 武器数据这里每一个武器对应的种类就相当于MYSQL中的一个字段大致类似如下 这个表单数据通常会只会增加并不会出现减少的现象如果用MYSQL来做排名系统的话当玩家数量越来越多成几何增长的时候它会暴露出两个问题
1、效率过低大量的select与update语句就可能会显得非常臃肿
2、不易扩展如果需要实时增加新的武器数据类型(字段)可能会不方面
这时候如果用redis来做数据存储就会显示格外合适仿佛天生就是用来干这个事情的 二、用Redis实现方法
1、存储前字符串编码转换 通常不建议在REDIS中直接存储明文字符串建议采用网页编码来存储转换函数源码如下
// 十六进制字符表
const UCHAR g_szHexTable[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F };//
// 获取网页编码
//
int getUrlCode(const UCHAR *pszBuff, const int nSize, char *pszOutput, const int nMaxSize)
{int i 0;char *pszTemp NULL;if (pszBuff NULL || nSize 0)return 0;if (pszOutput NULL || nMaxSize 0)return 0;memset(pszOutput, 0x00, nMaxSize);pszTemp pszOutput;for (int i 0; i nSize; i){pszTemp[0] %;pszTemp[1] %;pszTemp[2] g_szHexTable[(pszBuff[i] 4) 0x0F];pszTemp[3] g_szHexTable[pszBuff[i] 0x0F];pszTemp 4;}return nSize * 4;
}// 测试函数
void test_url_code()
{UCHAR szInput[] 我要打10个;char szOutput[128] { 0x00 };getUrlCode(szInput, strlen((char *)szInput), szOutput, 128);printf(%s\n, szOutput);
} 2、用户表操作 我们可以采用Redis Hset 命令用于为哈希表中的字段赋值如果哈希表不存在一个新的哈希表被创建并进行 HSET 操作如果字段已经存在于哈希表中旧值将被覆
HSET KEY_NAME FIELD VALUE KEY_NAME: 键值
FIELD: 字段
VALUE: 值(字符传、数字、浮点)我们在KEY_NAME这里键值的时候用一个组合方法来区分
// KEY_NAME type : id
#define STATS_ALL a // 总榜 a:1
#define STATS_YEAR y // 年榜 y2022:1
#define STATS_MONTH m // 月榜 m202205:1
#define STATS_DAY d // 日榜 d20220501:1
#define STATS_SESSION s // 季榜 s1_2022:1
#define STATS_USER user // 用户信息 user:1那么SQL中修改的语句修改用户名的时候像这样的
UPDATE dbuser SET name aa WHERE index 1;
UPDATE dbuser SET signature 我要打10个 WHERE index 1;
UPDATE dbuser SET regdate 2023/10/1 WHERE index 1; 而在Redis中应该像这样
// 注意这里的KEY是组合出来的// 设置玩家ID1的游戏昵称为aa
HSET user:1 name %%61%%61// 设置玩家ID1的游戏昵称为我要打10个
HSET user:1 signature %%CE%%D2%%D2%%AA%%B4%%F2%%31%%30%%B8%%F6%%A3%%A1// 设置玩家ID1的注册时间为2023/10/1
HSET user:1 regdate %%32%%30%%32%%33%%2F%%31%%30%%2F%%31// 设置玩家ID1的经验值时间为100
HSET user:1 exp 100// 上面redis命令也可以优化成下面的一句执行完
HMSET user:1 name %%61%%61 signature %%CE%%D2%%D2%%AA%%B4%%F2%%31%%30%%B8%%F6%%A3%%A1 regdate %%32%%30%%32%%33%%2F%%31%%30%%2F%%31 exp 100C代码片段如下 wsprintf(szCommand, /*HSET %s:%u %s %s*/XorStr0xAB, 17, 0x72ACBFF2(\xE3\xFF\xE8\xFA\x8F\x95\xC2\x88\x96\xC1\x95\x93\xC4\x98\x9C\xC9 0x72ACBFF2).s, STATS_USER, pInfo-m_nDbUid, g_szUserInfo[nPos], szValue);reply (redisReply *)redisCommand(pRedis, szCommand);if (NULL reply) goto STEP_END;if (REDIS_REPLY_ERROR reply-type)goto STEP_END;bRet TRUE;STEP_END:if (NULL ! reply){freeReplyObject(reply);reply NULL;}3、武器数据操作 在这方面数据操作就比较容易实现了而且能随时扩充字段个数不像SQL那样固定的个数另外武器的字段的数值操作都是自加来累计而不会产生添加或删除操作比如当网关服务器提交一条玩家ID1武器2累计杀敌总数自加3的命令时通常对应的SQL语句如下
// 修改总榜数据
UPDATE dbweapon_all SET kill kill 3 where index 1 AND wid 2;// 修改年榜数据
UPDATE dbweapon_2023 SET kill kill 3 where index 1 AND wid 2;// 修改赛季榜数据
UPDATE dbweapon_s1_2023 SET kill kill 3 where index 1 AND wid 2;而在Redis中应该像这样
// HINCRBY命令原型
// 参考地址https://www.runoob.com/redis/hashes-hincrby.html
HINCRBY KEY_NAME FIELD_NAME INCR_BY_NUMBER// redis执行3条命令
HINCRBY a:1 1:kill 3
HINCRBY y2023:1 1:kill 3
HINCRBY s1_2023:1 1:kill 3键值KEY_NAME注释
a:1 代表总榜
y2023:1 外表年榜
s1_2023:1 代表2023第一赛季榜字段FIELD_NAME注释
1:kill 武器编号为1类型为kill字段INCR_BY_NUMBER代表自加或自减数值4、MySQL修改积分查询排名 通常来说需要写一个复杂的MySQL语句来计算玩家的积分大致如下
// 修改积分
UPDATE SET db_user exp exp 3.0 WHERE index 1;// 查询积分
SELECT exp FROM db_user WHERE index 1;// 查询排名
SELECT COUNT(*) 1 AS rank
FROM db_user
WHERE exp (SELECT exp FROM db_user WHERE index 1);而在redis中本身就有了现成的通常在网关中处理武器数据时会同时修改该榜单的积分值 5、Redis Zincrby 修改积分 Redis Zincrby 命令对有序集合中指定成员的分数加上增量 increment可以通过传递一个负数值 increment 让分数减去相应的值比如 ZINCRBY key -5 member 就是让 member 的 score 值减去 5 当 key 不存在或分数不是 key 的成员时 ZINCRBY key increment member 等同于 ZADD key increment member当 key 不是有序集类型时返回一个错误分数值可以是整数值或双精度浮点数。
命令原型如下
ZINCRBY key increment memberkey 同上面的武器榜单
increment 自增或自减的积分
member 玩家id// 假设1号武器杀敌一次累计3分那对应命令如下
ZINCRBY a:1 3.0 1
ZINCRBY y2023:1 3.0 1
ZINCRBY s1_2023:1 3.0 1C代码片段如下 // 总榜记录wsprintf(szCommand, /*ZINCRBY %s %s %u*/XorStr0x09, 17, 0x5A9256F5(\x53\x43\x45\x4F\x5F\x4C\x56\x30\x34\x61\x33\x31\x66\x36\x32\x6D 0x5A9256F5).s, STATS_ALL, szScore, nDbUid);bRet redisGetReply(m_pRedis, (void **)reply);if (bRet ! REDIS_OK)goto STEP_END;if (reply){freeReplyObject(reply);reply NULL;}6、Redis Zscore 查询积分 Redis 有序集合(sorted set)Redis Zscore 命令返回有序集中成员的分数值。 如果成员元素不是有序集 key 的成员或 key 不存在返回 nil 。
命令原型如下
ZSCORE key memberkey 同上面的武器榜单
member 玩家id// 查询不同榜单的积分命令
ZSCORE a:1 1
ZSCORE y2023:1 1
ZSCORE s1_2023:1 1C操作代码片段如下 // 获取玩家积分(插件中只获取总榜记录)wsprintf(szCommand, /*ZSCORE %s %u*/XorStr0x40, 13, 0xF87B9E21(\x1A\x12\x01\x0C\x16\x00\x66\x62\x3B\x69\x6F\x3E 0xF87B9E21).s, STATS_ALL, nDbUid);reply (redisReply *)redisCommand(pRedis, szCommand);if (NULL reply) goto STEP_END;if (REDIS_REPLY_ERROR reply-type)goto STEP_END;if (REDIS_REPLY_STRING reply-type){fScore strtod(reply-str, NULL);}if (NULL ! reply){freeReplyObject(reply);reply NULL;}7、Redis Zrevrank 查询排名 Redis 有序集合(sorted set)Redis Zrevrank 命令返回有序集中成员的排名。其中有序集成员按分数值递减(从大到小)排序排名以 0 为底也就是说 分数值最大的成员排名为 0 使用 ZRANK 命令可以获得成员按分数值递增(从小到大)排列的排名。
命令原型如下
Zrevrank key memberkey 同上面的武器榜单
member 玩家id// 查询不同榜单的排名命令
ZREVRANK a:1 1
ZREVRANK y2023:1 1
ZREVRANK s1_2023:1 1C操作代码片段如下 // 获取在玩家排名(插件中只获取总榜记录)wsprintf(szCommand, /*ZREVRANK %s %u*/XorStr0x01, 15, 0x9CCD219A(\x5B\x50\x46\x52\x57\x47\x49\x43\x29\x2F\x78\x2C\x28\x7B 0x9CCD219A).s, STATS_ALL, nDbUid);reply (redisReply *)redisCommand(pRedis, szCommand);if (NULL reply) goto STEP_END;if (REDIS_REPLY_ERROR reply-type)goto STEP_END;if (REDIS_REPLY_NIL reply-type){nRank pServer-m_nMaxPlayers;bRet TRUE;}if (REDIS_REPLY_INTEGER reply-type){nRank (reply-integer 0xFFFFFFFF) 1;bRet TRUE;}if (NULL ! reply){freeReplyObject(reply);reply NULL;}三、总结避坑 1、尽量不要使用固定字符串做KEY
在编写排名的时候一般武器数据的KEY中不会用到像kill这种字符类型通常用一个数字代替方面扩展时只需要修改enum宏即可武器 数据类型 来拼接 %u:%u 的KEY_NAME类似下面这种
// push数据类别 // 命令 字符 积分
enum EnumPushType{ EPT_UNKNOW , // 未知 64 0EPT_KILL, // 杀敌 65 A 2EPT_SHOT, // 射击 66 B 0EPT_HEADSHOT, // 爆头 67 C 1EPT_HIT, // 击中 68 D 0EPT_DAMAGE, // 伤害 69 E 0EPT_DEATH, // 死亡 70 F -2EPT_FIRSTKILL, // 首杀 71 G 1EPT_FIRSTDEATH, // 首死 72 H 0EPT_BOMB_DEFUSION, // 拆除C4 73 I 2EPT_BOMB_PLANTING, // 安装C4 74 J 2EPT_TIME_ONLINE, // 在线 75 K 0 每秒0.002分(每小时7.2分)EPT_KILL_WORLD, // 摔死 76 L 0EPT_KILL_SELF, // 自杀次数 77 M 0EPT_MAX_PLAYER, // 最大玩家 78 N 0EPT_RANK, // 当前排名 79 O 0EPT_SCORE, // 当前积分 80 P 0// 身体伤害EPT_DMAGE_NONE, // 击中空枪 81 Q 0EPT_DMAGE_HEAD, // 击中头部 82 R 0EPT_DMAGE_CHEST, // 击中胸部 83 S 0EPT_DMAGE_STOMACH, // 击中胃部 84 T 0EPT_DMAGE_LEFTARM, // 击中左臂 85 U 0EPT_DMAGE_RIGHTARM, // 击中右臂 86 V 0EPT_DMAGE_LEFTEG, // 击中左脚 87 W 0EPT_DMAGE_RIGHTEG, // 击中右脚 88 X 0EPT_DMAGE_SHIELD, // 击中盾牌 89 Y 0// 武器BKILLEPT_BKILL, // 被击杀 90 Z 0EPT_BHEAD, // 被爆头 91 [ 0// 击中次数EPT_HIT_NONE, // 击中空枪 92 \ 0EPT_HIT_HEAD, // 击中头部 93 ] 0EPT_HIT_CHEST, // 击中胸部 94 ^ 0EPT_HIT_STOMACH, // 击中胃部 95 _ 0EPT_HIT_LEFTARM, // 击中左臂 96 0EPT_HIT_RIGHTARM, // 击中右臂 97 a 0EPT_HIT_LEFTEG, // 击中左脚 98 b 0EPT_HIT_RIGHTEG, // 击中右脚 99 c 0EPT_HIT_SHIELD, // 击中盾牌 100 d 0// 混战参数 add by MT 2023-09-30EPT_ROUND, // 总回合EPT_RWIN_T, // 回合T杀完胜利EPT_RWIN_BOOM, // 回合T爆炸胜利EPT_RWIN_CT, // 回合CT杀完胜利EPT_RWIN_DEFUSE, // 回合CT爆炸胜利EPT_RWIN_SAVED, // 回合CT时间结束胜利EPT_RWIN_RESCUE, // 回合CT解救人质胜利EPT_RWIN_NOT_RESCUE, // 回合CT未解救人质胜利EPT_RWIN_TYPE1, // 回合保留胜利1EPT_RWIN_TYPE2, // 回合保留胜利2EPT_RLOSE, // 失败回合EPT_REVEN, // 平局回合// 比赛参数EPT_SESSION, // 总场次EPT_SWIN, // 胜利场次EPT_SLOSE, // 失败场次EPT_SEVEN, // 平局场次EPT_MVP, // 最佳次数EPT_RWS, // 每局贡献评分(伤害占比)// 每回合杀敌统计EPT_KILL_0, // 0K 酱油局EPT_KILL_1, // 1KEPT_KILL_2, // 2KEPT_KILL_3, // 3KEPT_KILL_4, // 3KEPT_KILL_5, // 5KEPT_KILL_6, // 6KEPT_KILL_7, // 7KEPT_KILL_8, // 8KEPT_KILL_9, // 9KEPT_KILL_10, // 10KEPT_KILL_11, // 11KEPT_KILL_12, // 12KEPT_KILL_13, // 13KEPT_KILL_14, // 14KEPT_KILL_15, // 15KEPT_KILL_16, // 16K// 残局统计EPT_1V1, // 1v1EPT_1V2, // 1v2EPT_1V3, // 1v3EPT_1V4, // 1v4EPT_1V5, // 1v5EPT_1V6, // 1v6EPT_1V7, // 1v7EPT_1V8, // 1v8EPT_1V9, // 1v9EPT_1V10, // 1v10EPT_1V11, // 1v11EPT_1V12, // 1v12EPT_1V13, // 1v13EPT_1V14, // 1v14EPT_1V15, // 1v15EPT_1V16, // 1v16// 残局信息EPT_1ROUND, // 残局场次EPT_1RWIN, // 残局胜利EPT_ASSIST, // 助攻次数EPT_ADR, // 场均实际伤害占比累计// 穿墙信息EPT_WALL_HIT, // 穿墙累计命中次数EPT_WALL_DAMAGE, // 穿墙累计射击伤害EPT_WALL_HEAD, // 穿墙累计爆头次数EPT_WALL_KILL, // 穿墙累计击杀次数EPT_BWALL_HIT, // 被穿墙累计命中次数EPT_BWALL_DAMAGE, // 被穿墙累计射击伤害EPT_BWALL_HEAD, // 被穿墙累计爆头次数EPT_BWALL_KILL, // 被穿墙累计击杀次数
};这一个宏再对应一个double数组来计算得分如此只需要一个这样的函数作为网关总接口即可
// 提交排名数据
UINT redis_PushCommand(REDIS_SERVER *pServer, const UINT nDataType, const UINT nWeapon, UINT nDbUid, UINT nValue); 2、尽量用事务方式读写
最后注意一下用事务处理的方法操作可以加快读写效率普通的10倍以上示例代码如下 // 新版采用事务方式读取,加快速度for (i 0; i MAX_WEAPONS; i){nWeapon i EPT_UNKNOW;for (j 0; j MAX_OPT_COUNT; j){wsprintf(szCommand, /*HGET %s:%u %u:%u*/XorStr0x18, 17, 0xF6DE906E(\x50\x5E\x5F\x4F\x3C\x38\x6D\x25\x05\x54\x02\x06\x51\x1F\x03\x52 0xF6DE906E).s, STATS_ALL, nDbUid, , EPT_UNKNOW j, nWeapon);redisAppendCommand(pRedis, szCommand); // 事务读取}}// 遍历所有武器数据for (i 0; i MAX_WEAPONS; i){// 遍历所有参数for (j 0; j MAX_OPT_COUNT; j){nRet redisGetReply(pRedis, (void **)reply);if (nRet ! REDIS_OK)continue;if (reply-type REDIS_REPLY_STRING){sData.m_nValue[i][j] atoi(reply-str);}if (reply-type REDIS_REPLY_INTEGER){sData.m_nValue[i][j] reply-integer 0xFFFFFFFF;}}}