1. MySQL implementation method
Suppose we want to design a ranking system, which must involve two major types of data: weapon data and non-weapon general data. They usually have a common attribute: that is, the primary key is unique, such as the player’s numerical number, usually In MySQL, it is an auto-incrementing unsigned integer field.
Non-weapon general data: It can be understood as data that has no connection with weapons, such as player ID, nickname, signature, registration time, login time, etc. In MySQL, it is similar to the following:
Weapon data: The type corresponding to each weapon here is equivalent to a field in MYSQL, roughly similar to the following:
This form data usually only increases and does not decrease. If MYSQL is used as a ranking system, when the number of players increases exponentially, it will expose two problems:
1. The efficiency is too low: a large number of select and update statements may appear very bloated;
2. Not easy to expand: If you need to add new weapon data types (fields) in real time, it may be inconvenient
At this time, it will be particularly suitable to use redis for data storage, as if it was born to do this!
2. Use Redis to implement the method
1. String encoding conversion before storage
It is generally not recommended to store plaintext strings directly in REDIS. It is recommended to use web page encoding to store them. The source code of the conversion function is as follows:
//Hexadecimal character table const UCHAR g_szHexTable[] = { '0', '1', '2', '3', '4', '5', '6', \ '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F ' }; // // Get the web page encoding // 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) & amp; 0x0F]; pszTemp[3] = g_szHexTable[pszBuff[i] & amp; 0x0F]; pszTemp + = 4; } return nSize * 4; } // test function void test_url_code() { UCHAR szInput[] = "I want to hit 10!"; char szOutput[128] = { 0x00 }; getUrlCode(szInput, strlen((char *)szInput), szOutput, 128); printf("%s\\ ", szOutput); }
2. User table operations
We can use the Redis Hset command to assign values to fields in the hash table; if the hash table does not exist, a new hash table is created and the HSET operation is performed; if the field already exists in the hash table, the old value will covered; covered
HSET KEY_NAME FIELD VALUE KEY_NAME: key value FIELD: field VALUE: value (character transfer, number, floating point)
When we use a combination method to distinguish the key value of KEY_NAME:
// KEY_NAME = type : id #define STATS_ALL "a" // Overall list a:1 #define STATS_YEAR "y" // Year list y2022:1 #define STATS_MONTH "m" // Monthly list m202205:1 #define STATS_DAY "d" // Daily list d20220501:1 #define STATS_SESSION "s" // Season standings s1_2022:1 #define STATS_USER "user" // User information user:1
Then when the modified statement in SQL modifies the user name, it looks like this:
UPDATE dbuser SET name = 'aa' WHERE index = 1; UPDATE dbuser SET signature = 'I want to type 10! ' WHERE index = 1; UPDATE dbuser SET regdate = '2023/10/1' WHERE index = 1;
And in Redis it should look like this:
// Note that the KEY here is combined //Set the game nickname of player ID=1 to "aa" HSET user:1 name %a%a //Set the game nickname for player ID=1 as "I want to fight 10!" HSET user:1 signature %?%?%?%?%?%?%1%0%?%?%?%? // Set the registration time of player ID=1 to "2023/10/1" HSET user:1 regdate %2%0%2%3%/%1%0%/%1 //Set the experience value time of player ID=1 to 100 HSET user:1 exp 100 //The above redis command can also be optimized into the following sentence and executed HMSET user:1 name %a%a signature %?%?%?%?%?%?%1%0%?%?%?%? regdate %2%0%2%3%/%1%0% /%1 exp 100
The C++ code snippet is as follows:
wsprintf(szCommand, /*HSET %s:%u %s %s*/XorStr<0xAB, 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. Weapon data operation
In this regard, data operations are relatively easy to implement, and the number of fields can be expanded at any time, unlike the fixed number like SQL; in addition, the numerical operations on the fields of weapons are self-added and cumulative, without adding or deleting operations. , for example, when the gateway server submits a command that player ID=1, weapon=2, and the cumulative number of enemies killed increases by 3, the corresponding SQL statement is usually as follows:
//Modify the overall ranking data UPDATE dbweapon_all SET kill = kill + 3 where index = 1 AND wid = 2; //Modify annual ranking data UPDATE dbweapon_2023 SET kill = kill + 3 where index = 1 AND wid = 2; //Modify season ranking data UPDATE dbweapon_s1_2023 SET kill = kill + 3 where index = 1 AND wid = 2;
And in Redis it should look like this:
// HINCRBY command prototype // Reference address: https://www.runoob.com/redis/hashes-hincrby.html HINCRBY KEY_NAME FIELD_NAME INCR_BY_NUMBER // redis executes 3 commands HINCRBY a:1 1:kill 3 HINCRBY y2023:1 1:kill 3 HINCRBY s1_2023:1 1:kill 3 Key value KEY_NAME annotation: a:1 represents the overall list y2023:1 Appearance Annual List s1_2023:1 represents the 2023 first season rankings Field FIELD_NAME comments: 1:kill The weapon number is 1 and the type is kill. The field INCR_BY_NUMBER represents the self-increasing or self-decrementing value
4. MySQL modify points query ranking
Generally speaking, you need to write a complex MySQL statement to calculate the player’s points, which is roughly as follows:
//Modify points UPDATE SET db_user exp = exp + 3.0 WHERE index = 1; // Query points SELECT exp FROM db_user WHERE index = 1; // Query ranking SELECT COUNT(*) + 1 AS rank FROM db_user WHERE exp >= (SELECT exp FROM db_user WHERE index = 1);
There is a ready-made one in redis itself. Usually when the weapon data is processed in the gateway, the points value of the list will be modified at the same time.
5. Redis Zincrby Modify Points
The Redis Zincrby command adds increment to the score of the specified member in the ordered set. You can pass a negative value increment to subtract the corresponding value from the score. For example, ZINCRBY key -5 member means subtracting 5 from the score value of the member. ; When the key does not exist, or the score is not a member of the key, the ZINCRBY key increment member is equivalent to the ZADD key increment member; when the key is not an ordered set type, an error is returned; the score value can be an integer value or a double-precision floating point number.
The command prototype is as follows:
ZINCRBY key increment member key is the same as the weapon list above increment Points that increase or decrease automatically member player id // Assuming that weapon No. 1 kills an enemy and accumulates 3 points, the corresponding command is as follows: ZINCRBY a:1 3.0 1 ZINCRBYy2023:1 3.0 1 ZINCRBY s1_2023:1 3.0 1
The C++ code snippet is as follows:
// Overall ranking record wsprintf(szCommand, /*ZINCRBY %s %s %u*/XorStr<0x09, 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 **) & amp;reply); if (bRet != REDIS_OK) goto STEP_END; if (reply) { freeReplyObject(reply); reply = NULL; }
6. Redis Zscore query points
Redis ordered set (sorted set), the Redis Zscore command returns the score value of the member in the sorted set. If the member element is not a member of the sorted set key, or key does not exist, nil is returned.
The command prototype is as follows:
ZSCORE key member key is the same as the weapon list above member player id //Query points command for different rankings: ZSCORE a:1 1 ZSCORE y2023:1 1 ZSCORE s1_2023:1 1
The C++ operation code snippet is as follows:
// Get player points (only the overall ranking record is obtained in the plug-in) wsprintf(szCommand, /*ZSCORE %s %u*/XorStr<0x40, 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 query ranking
Redis ordered set (sorted set), the Redis Zrevrank command returns the ranking of members in the sorted set. The members of the ordered set are sorted by decreasing score (from large to small); the ranking is based on 0, that is, the member with the largest score is ranked 0; use the ZRANK command to get the members sorted by increasing score (from small to large) ranking.
The command prototype is as follows:
Zrevrank key member key is the same as the weapon list above member player id // Query the ranking command of different lists: ZREVRANK a:1 1 ZREVRANK y2023:1 1 ZREVRANK s1_2023:1 1
The C++ operation code snippet is as follows:
// Get the player rankings (only the overall ranking records are obtained in the plug-in) wsprintf(szCommand, /*ZREVRANK %s %u*/XorStr<0x01, 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 & amp; 0xFFFFFFFF) + 1; bRet = TRUE; } if (NULL != reply) { freeReplyObject(reply); reply = NULL; }
3. Summarize and avoid pitfalls
1. Try not to use fixed strings as KEY
When writing rankings, generally the character type “kill” is not used in the KEY of weapon data. Usually a number is used instead of aspect expansion. When expanding, you only need to modify the enum macro, weapon + data Typeto splice the KEY_NAME of %u:%u , similar to the following:
// push data category // command character points enum EnumPushType{ EPT_UNKNOW = '@', // Unknown 64 @ + 0 EPT_KILL, // Kill 65 A + 2 EPT_SHOT, // Shoot 66 B + 0 EPT_HEADSHOT, // Headshot 67 C + 1 EPT_HIT, // hit 68 D + 0 EPT_DAMAGE, // Damage 69 E + 0 EPT_DEATH, // Death 70 F -2 EPT_FIRSTKILL, // First kill 71 G + 1 EPT_FIRSTDEATH, // first death 72 H + 0 EPT_BOMB_DEFUSION, // Demolish C4 73 I + 2 EPT_BOMB_PLANTING, // Install C4 74 J + 2 EPT_TIME_ONLINE, // Online 75 K + 0 + 0.002 minutes per second (7.2 minutes per hour) EPT_KILL_WORLD, // Killed 76 L + 0 EPT_KILL_SELF, // Number of suicides 77 M + 0 EPT_MAX_PLAYER, //Maximum player 78 N + 0 EPT_RANK, // Current ranking 79 O + 0 EPT_SCORE, // Current score 80 P + 0 // Bodily harm EPT_DMAGE_NONE, // Hit empty gun 81 Q + 0 EPT_DMAGE_HEAD, // Hit the head 82 R + 0 EPT_DMAGE_CHEST, // hit chest 83 S + 0 EPT_DMAGE_STOMACH, // Hit in the stomach 84 T + 0 EPT_DMAGE_LEFTARM, // Hit left arm 85 U + 0 EPT_DMAGE_RIGHTARM, // Hit the right arm 86 V + 0 EPT_DMAGE_LEFTEG, // Hit the left foot 87 W + 0 EPT_DMAGE_RIGHTEG, // Hit the right foot 88 X + 0 EPT_DMAGE_SHIELD, // hit shield 89 Y + 0 // Weapon + BKILL EPT_BKILL, // Killed 90 Z + 0 EPT_BHEAD, // Headshot 91 [ + 0 \t //Number of hits EPT_HIT_NONE, // Hit empty gun 92 \ + 0 EPT_HIT_HEAD, // Hit the head 93 ] + 0 EPT_HIT_CHEST, // hit chest 94 ^ + 0 EPT_HIT_STOMACH, // Hit in the stomach 95 _ + 0 EPT_HIT_LEFTARM, // Hit left arm 96 ` + 0 EPT_HIT_RIGHTARM, // Hit the right arm 97 a + 0 EPT_HIT_LEFTEG, // Hit the left foot 98 b + 0 EPT_HIT_RIGHTEG, // Hit the right foot 99 c + 0 EPT_HIT_SHIELD, // hit shield 100 d + 0 //Melee parameters add by MT 2023-09-30 EPT_ROUND, // total round EPT_RWIN_T, // Round: T wins after killing EPT_RWIN_BOOM, // Round: T explodes to win EPT_RWIN_CT, // Round: CT kill wins EPT_RWIN_DEFUSE, // Round: CT explosion victory EPT_RWIN_SAVED, // Round: Victory at the end of CT time EPT_RWIN_RESCUE, // Round: CT rescues hostages and wins EPT_RWIN_NOT_RESCUE, // Round: CT wins without rescuing the hostages EPT_RWIN_TYPE1, // Round: Keep Victory 1 EPT_RWIN_TYPE2, // Round: Keep Victory 2 EPT_RLOSE, // failed round EPT_REVEN, // draw round //Contest parameters EPT_SESSION, // total number of sessions EPT_SWIN, // number of wins EPT_SLOSE, // failed sessions EPT_SEVEN, // draw games EPT_MVP, // best times EPT_RWS, //Contribution score per round (damage ratio) //Kill statistics per round EPT_KILL_0, // 0K Soy Sauce Bureau EPT_KILL_1, // 1K EPT_KILL_2, // 2K EPT_KILL_3, // 3K EPT_KILL_4, // 3K EPT_KILL_5, // 5K EPT_KILL_6, // 6K EPT_KILL_7, // 7K EPT_KILL_8, // 8K EPT_KILL_9, // 9K EPT_KILL_10, // 10K EPT_KILL_11, // 11K EPT_KILL_12, // 12K EPT_KILL_13, // 13K EPT_KILL_14, // 14K EPT_KILL_15, // 15K EPT_KILL_16, // 16K // Endgame statistics EPT_1V1, // 1v1 EPT_1V2, // 1v2 EPT_1V3, // 1v3 EPT_1V4, // 1v4 EPT_1V5, // 1v5 EPT_1V6, // 1v6 EPT_1V7, // 1v7 EPT_1V8, // 1v8 EPT_1V9, // 1v9 EPT_1V10, // 1v10 EPT_1V11, // 1v11 EPT_1V12, // 1v12 EPT_1V13, // 1v13 EPT_1V14, // 1v14 EPT_1V15, // 1v15 EPT_1V16, // 1v16 // Endgame information EPT_1ROUND, // endgame games EPT_1RWIN, // Endgame victory EPT_ASSIST, // Number of assists EPT_ADR, // Accumulated average actual damage proportion per game //Wall penetration information EPT_WALL_HIT, // Cumulative number of hits through walls EPT_WALL_DAMAGE, // Accumulated shooting damage through walls EPT_WALL_HEAD, // Cumulative number of headshots through walls EPT_WALL_KILL, // Cumulative number of kills through walls EPT_BWALL_HIT, // Cumulative number of hits through walls EPT_BWALL_DAMAGE, // Accumulated damage from shooting through the wall EPT_BWALL_HEAD, // Cumulative number of headshots shot through the wall EPT_BWALL_KILL, // Cumulative number of kills by passing through the wall };
This macro corresponds to a double array to calculate the score, so only one such function is needed as the gateway’s total interface:
//Submit ranking data UINT redis_PushCommand(REDIS_SERVER *pServer, const UINT nDataType, const UINT nWeapon, UINT nDbUid, UINT nValue);
2. Try to use transactional reading and writing
Finally, please note that using transaction processing can speed up reading and writing efficiency by more than 10 times. The sample code is as follows:
// The new version uses transactional reading to speed up 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*/XorStr<0x18, 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); // transaction reading } } //Loop through all weapon data for (i = 0; i < MAX_WEAPONS; i + + ) { // Iterate through all parameters for (j = 0; j < MAX_OPT_COUNT; j + + ) { nRet = redisGetReply(pRedis, (void **) & amp;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 & amp; 0xFFFFFFFF; } } }