2024年1月

介绍

毕业于成都理工电气专业,大学毕业后进入了一家电气公司,月薪2000元。一直对互联网行业感兴趣,但由于没有相关专业背景,所以一直没有勇气转行。

转行契机

公司的书记想搞一个内部生产管理系统,看我们4个应届毕业生每天都在打酱油,就找到我们想让我们研究一下看能不能做出这个系统(公司里面没有搞IT的)。

当时小伙伴A说他以前了解过
winform
,随便拖拖控件就可以做一个页面。看了一本
C# 从入门到精通
,研究了一段时间
winform
后搞了一个demo页面给书记看,书记看了后觉得挺满意的,就让我们继续搞这个。

又继续研究一段时间后发现原来
winform
做桌面应用程序已经过时了,并且
C#
也没有前途了,还是搞
web前端

java后端
有前途。当时我们的工资只有2000,我们一致觉得目前这一行没有“钱途”,还是需要转行到互联网来才行。小伙伴A选择自学java,小伙伴B选择自学嵌入式,小伙伴C和我选择自学前端。后面我们都转行成功。

开始自学前端

决定开始自学前端后,就从网上百度找自学路线(那会儿也不会使用梯子),结果一顿百度后发现大都是培训机构的广告。其余的大部分文章也都只讲了前端需要学习哪些知识,但是我一个零基础的初学者完全不知道从哪里下手,那段时间学习的非常累,而且还没有效果。

后来听小伙伴C说他花了不少钱在网上报了一个前端网课培训班,每天晚上老师会在网上直播授课。并且会将课程视频给录制下来,这样就算晚上没时间看直播也可以看录制下来的视频。小伙伴C的这些话给我了一些灵感,淘宝上面能不能花钱买那些培训机构的录制视频?

上淘宝搜索“前端培训视频”,结果搜出来很多相关的商品,并且价格也普遍都只在几块钱到几十块钱。挑了一个“某马2016年前端培训视频”花了9.9元拍了下来,然后卖家丢给我一个百度网盘链接。在花了9.9元买了一套前端培训视频后,我的自学之旅变得轻松了许多。这套视频涵盖了前端开发的所有基础知识,包括 HTML、CSS、JavaScript、Vue.js 等。通过系统的学习,我对前端开发有了全面的了解,也掌握了基本的开发技能。并且在这期间看了两本js的书籍:
javascript dom编程艺术

红宝石

第一次投递简历碰壁

学完vue后,我开始投递简历。然而,现实却给了我当头一棒。由于我没有项目经验和工作经验,简历都是石沉大海。唯一收到的面试通知都是培训机构,过去就PUA说我技术不行,需要培训一下才行,培训出来后工资轻轻松松2W+。

优化求职策略

从培训机构面试回来后开始反思自己到底问题出在哪里,得出结论是自己可能脸皮不够厚。然后就从招聘网站上面找到需要招人的公司,从公司官网找到邮箱和电话,将自己的简历发送到邮箱,然后再打电话过去说自己在找前端开发的工作,结果从网站上面找到的电话大部分都是商务合作电话。

重新制作简历和上线个人网站

经过上面的一番折腾后还是没有接到面试通知,才意识到自己的简历有问题。简历包装的太差了,HR一眼就识别出来了。那就干脆不包装了,直接在简历里面写明自己是跨行自学前端。在简历里面弱化项目经验和工作经验,主要重点突出自己的自学能力。那会儿看别人搞了一个网站当作自己的简历,自己也照猫画虎去注册了一个域名和买了一个服务器搞了一个自己的网站当作简历,并且还在网上抄了一个
PHP
留言的demo,给自己的网站加了一个留言功能。

优化简历和上线了自己的网站后又继续投递简历,智联招聘和前程无忧上面陆陆续续开始收到了面试通知,拉勾网上面还是没有收到面试通知。将那些面试都尽可能约到了同一天,然后请假到成都面试。面试的时候尽可能的将话题引导到我是如何自学前端的话题,总体上来说面试官对我的印象都挺不错的。但是谈到入职时间的时候,我这边需要一个月公司才会让离职,基本都接受不了。

找到了第一份前端6.5k的工作

能够收到面试邀请心里就有底气了,打定主意明天上班就找领导提离职的事情。回彭州当天晚上又就接到一个电话面试,面试官说他在拉钩网上面看到了我投递的简历,觉得我的学习能力很不错。

问了我如何自学前端和一些基础的问题,然后又问了如何手写轮播图(现在都还记得这个面试题)。然后面试官说:我面试不喜欢问一些叼钻的问题,更倾向于观察候选者学习能力和性格是否合适,一个月后入职也没关系,我们也不是很急着要人。一面你已经通过了,后续我们前端leader会跟你联系进行二面,也有可能会让你来公司一趟进行二面。

第二天就收到了公司前端leader的微信好友申请,leader在微信上指出了我的网站目前存在的问题。又接着问了一些问题,就问我期望薪资是多少,我回答期望是6k(当时工资只有2k,6k对于当时的我来说已经是狮子大开口了)。leader很痛快的回答说,那给你6.5k吧,offer后续就会发到你的邮箱。

后面入职后和面试官混熟了才知道原来我的那个岗位最低都是给9k,面试官还说,我当时没有给你说让你最少要9k吗?至此我开始了我的程序员职业生涯。

作者:俊达

引言

在大多数情况下,我们不需要自己编译MySQL源码,因为编译的MySQL和二进制包的内容基本一致。然而,有些特殊情况可能需要我们采用源码编译的方式安装MySQL:

  • 安装非标准版本的MySQL
    :有些特殊的应用场景会使用到MySQL的非标准版本,这时候我们就需要编译源码来安装。
  • 安装社区的patch、bugfix、扩展插件
    :通过源码编译,可以轻松地添加社区提供的补丁、修复程序和扩展插件,以满足特定需求。
  • 禁用二进制版本中的某些特性
    :源码编译可以让用户选择禁用或启用某些特定功能,以满足自己的需求。
  • 使用未编译的功能
    :有些功能可能在二进制版本中并没有被编译进去,而通过源码编译可以启用这些功能。
  • 使用其他编译器优化插件
    :源码编译可以让用户选择使用其他编译器进行编译,或者进行一些个性化的优化设置。例如,如果我们想要使用Intel的C编译器。
  • 链接一些其他基础库
    :源码编译可以允许用户链接其他基础库,例如使用tcmalloc等。
  • 编译debug版本
    :通过源码编译可以生成debug版本的MySQL,用于调试和分析问题。

因此,了解MySQL源码编译安装对于学习MySQL运维来说,确实是非常有帮助的。这不仅可以帮助我们更好地理解MySQL的工作原理,还可以让我们在面对一些特殊需求时,能够更加灵活地处理。

1 下载源码

https://downloads.mysql.com/archives/community/

选择对应版本的源码包并下载

[root@box1 software]# wget https://downloads.mysql.com/archives/get/p/23/file/mysql-boost-5.7.32.tar.gz
--2021-03-29 05:50:54--  https://downloads.mysql.com/archives/get/p/23/file/mysql-boost-5.7.32.tar.gz
正在解析主机 downloads.mysql.com (downloads.mysql.com)... 137.254.60.14
正在连接 downloads.mysql.com (downloads.mysql.com)|137.254.60.14|:443... 已连接。
已发出 HTTP 请求,正在等待回应... 302 Found
位置:https://cdn.mysql.com/archives/mysql-5.7/mysql-boost-5.7.32.tar.gz [跟随至新的 URL]
--2021-03-29 05:50:55--  https://cdn.mysql.com/archives/mysql-5.7/mysql-boost-5.7.32.tar.gz
正在解析主机 cdn.mysql.com (cdn.mysql.com)... 184.27.193.3
正在连接 cdn.mysql.com (cdn.mysql.com)|184.27.193.3|:443... 已连接。
已发出 HTTP 请求,正在等待回应... 200 OK
长度:52882168 (50M) [application/x-tar-gz]
正在保存至: “mysql-boost-5.7.32.tar.gz”

100%[================================================================================================================================================================================>] 52,882,168  2.76MB/s 用时 22s

2021-03-29 05:51:17 (2.32 MB/s) - 已保存 “mysql-boost-5.7.32.tar.gz” [52882168/52882168])


[root@box1 software]# md5sum mysql-boost-5.7.32.tar.gz
fd78ae35fb019656a7a9cb5361e96201  mysql-boost-5.7.32.tar.gz

2 解压代码

[root@box1 software]# tar xzf mysql-5.7.32.tar.gz

[root@box1 software]# ls -l
总用量 54844
drwxr-xr-x. 35 7161 31415     4096 9月  23 2020 mysql-5.7.32
-rw-r--r--.  1 root root  56154080 3月  29 05:04 mysql-5.7.32.tar.gz

3 编译安装

编译依赖条件

软件
cmake
make
c/c++编译器
boost c++库
ssl库

编译选项
进入mysql源码目录

# cmake -L

CMake Error at cmake/boost.cmake:88 (MESSAGE):
  You can download it with -DDOWNLOAD_BOOST=1 -DWITH_BOOST=<directory>

  This CMake script will look for boost in <directory>.  If it is not there,
  it will download and unpack it (in that directory) for you.

-- Cache values
CMAKE_BUILD_TYPE:STRING=RelWithDebInfo
CMAKE_INSTALL_PREFIX:PATH=/usr/local/mysql
COMMUNITY_BUILD:BOOL=ON
DOWNLOAD_BOOST:BOOL=OFF
DOWNLOAD_BOOST_TIMEOUT:STRING=600
ENABLED_PROFILING:BOOL=ON
ENABLE_DTRACE:BOOL=ON
ENABLE_GCOV:BOOL=OFF
ENABLE_GPROF:BOOL=OFF
ENABLE_MEMCACHED_SASL:BOOL=OFF
ENABLE_MEMCACHED_SASL_PWDB:BOOL=OFF
FEATURE_SET:STRING=community
INSTALL_LAYOUT:STRING=STANDALONE
MYSQL_DATADIR:PATH=/usr/local/mysql/data
MYSQL_KEYRINGDIR:PATH=/usr/local/mysql/keyring
OPTIMIZER_TRACE:BOOL=ON
REPRODUCIBLE_BUILD:BOOL=OFF
TMPDIR:PATH=P_tmpdir
WITH_ARCHIVE_STORAGE_ENGINE:BOOL=ON
WITH_ASAN:BOOL=OFF
WITH_ASAN_SCOPE:BOOL=OFF
WITH_AUTHENTICATION_LDAP:BOOL=OFF
WITH_BLACKHOLE_STORAGE_ENGINE:BOOL=ON
WITH_BOOST:PATH=
WITH_CLIENT_PROTOCOL_TRACING:BOOL=ON
WITH_DEBUG:BOOL=OFF
WITH_DEFAULT_COMPILER_OPTIONS:BOOL=ON
WITH_DEFAULT_FEATURE_SET:BOOL=ON
WITH_EDITLINE:STRING=bundled
WITH_EMBEDDED_SERVER:BOOL=ON
WITH_EXTRA_CHARSETS:STRING=all
WITH_FEDERATED_STORAGE_ENGINE:BOOL=ON
WITH_INNOBASE_STORAGE_ENGINE:BOOL=ON
WITH_INNODB_MEMCACHED:BOOL=OFF
WITH_LZ4:STRING=bundled
WITH_MSAN:BOOL=OFF
WITH_PARTITION_STORAGE_ENGINE:BOOL=ON
WITH_RAPID:BOOL=ON
WITH_SSL:STRING=system
WITH_TEST_TRACE_PLUGIN:BOOL=OFF
WITH_UBSAN:BOOL=OFF
WITH_UNIT_TESTS:BOOL=ON
WITH_VALGRIND:BOOL=OFF
WITH_ZLIB:STRING=bundled

下载boost

下载 boost_1_59_0.tar.gz

解压

/root/software/boost_1_59_0

执行cmake

cmake . -DCMAKE_BUILD_TYPE=Debug \
    -DMAKE_INSTALL_PREFIX=/app/dtstack/mysql \
    -DCOMPILATION_COMMENT="lazybug" \
    -DDEFAULT_CHARSET=utf8mb4 \
    -DENABLED_LOCAL_INFILE=1 \
    -DWITH_BOOST=/root/software/boost_1_59_0 \
    -DWITH_SSL=system
    
-- Running cmake version 2.8.12.2
-- Configuring with MAX_INDEXES = 64U
-- CMAKE_GENERATOR: Unix Makefiles
-- SIZEOF_VOIDP 8
-- MySQL 5.7.32
-- Packaging as: mysql-5.7.32-Linux-x86_64
-- Local boost dir /root/software/boost_1_59_0
-- Found /root/software/boost_1_59_0/boost/version.hpp
-- BOOST_VERSION_NUMBER is #define BOOST_VERSION 105900
-- BOOST_INCLUDE_DIR /root/software/boost_1_59_0
....

-- COMPILE_DEFINITIONS: _GNU_SOURCE;_FILE_OFFSET_BITS=64;HAVE_CONFIG_H
-- CMAKE_C_FLAGS:  -Wall -Wextra -Wformat-security -Wvla -Wwrite-strings -Wdeclaration-after-statement -Werror
-- CMAKE_CXX_FLAGS:  -Wall -Wextra -Wformat-security -Wvla -Woverloaded-virtual -Wno-unused-parameter -Werror
-- CMAKE_C_LINK_FLAGS:
-- CMAKE_CXX_LINK_FLAGS:
-- CMAKE_C_FLAGS_DEBUG: -g -fabi-version=2 -fno-omit-frame-pointer -fno-strict-aliasing -DENABLED_DEBUG_SYNC -DSAFE_MUTEX
-- CMAKE_CXX_FLAGS_DEBUG: -g -fabi-version=2 -fno-omit-frame-pointer -fno-strict-aliasing -DENABLED_DEBUG_SYNC -DSAFE_MUTEX
-- Configuring done
-- Generating done
-- Build files have been written to: /root/software/mysql-5.7.32

执行make

make -j 4

Scanning dependencies of target INFO_SRC
Scanning dependencies of target INFO_BIN
Scanning dependencies of target abi_check
Scanning dependencies of target zlib
[  0%] Built target INFO_SRC
[  0%] [  0%] Built target INFO_BIN
[  0%] Building C object zlib/CMakeFiles/zlib.dir/adler32.o
...
[100%] Building CXX object sql/CMakeFiles/mysqld.dir/main.cc.o
Linking CXX executable mysqld
[100%] Building CXX object storage/perfschema/unittest/CMakeFiles/pfs_connect_attr-t.dir/pfs_connect_attr-t.cc.o
[100%] Building CXX object storage/perfschema/unittest/CMakeFiles/pfs_connect_attr-t.dir/__/__/__/sql/sql_builtin.cc.o
[100%] Building C object storage/perfschema/unittest/CMakeFiles/pfs_connect_attr-t.dir/__/__/__/mysys/string.c.o
Linking CXX executable pfs_connect_attr-t
[100%] Built target mysql_embedded
[100%] Built target mysqltest_embedded
[100%] Built target mysqld
[100%] Built target pfs_connect_attr-t

执行make install 安装

root@box1 mysql-5.7.32]# make install
[  0%] Built target INFO_BIN
[  0%] Built target INFO_SRC
[  0%] Built target abi_check
[  0%] Built target zlib
[  2%] Built target edit
...
-- Up-to-date: /usr/local/mysql/mysql-test/mysql-test-run
-- Installing: /usr/local/mysql/mysql-test/lib/My/SafeProcess/my_safe_process
-- Up-to-date: /usr/local/mysql/mysql-test/lib/My/SafeProcess/my_safe_process
-- Installing: /usr/local/mysql/mysql-test/lib/My/SafeProcess/Base.pm
-- Installing: /usr/local/mysql/support-files/mysqld_multi.server
-- Installing: /usr/local/mysql/support-files/mysql-log-rotate
-- Installing: /usr/local/mysql/support-files/magic
-- Installing: /usr/local/mysql/share/aclocal/mysql.m4
-- Installing: /usr/local/mysql/support-files/mysql.server

安装的文件

[root@box1 mysql]# ls
bin  docs  include  lib  LICENSE  man  mysql-test  README  README-test  share  support-files
目录 内容
bin mysql的可执行文件和工具,脚本
mysqld
mysql
mysqlbinlog
mysqldump
mysqladmin
mysqld_safe
docs /
include 头文件,编译时依赖
lib mysql client依赖库
mysql plugin
man /
share mysql error信息
mysql系统初始化文件
support 一些脚本

4 初始化数据库 & 启动MySQL

初始化数据库和启动MySQL的步骤和上一篇二进制安装方式一样(
MySQL运维实战(1.2)安装部署:使用二进制安装部署
),这里不再重复。

更多技术信息请查看云掣官网
https://yunche.pro/?t=yrgw

vs code 其实有很多实用的技巧可以在日常工作中带来很大的提效,但可能是开发中没有相应的痛点场景,因此有些技巧接触的人不多

本篇就来介绍下多光标的批量操作和模板代码两种技巧在日常工作中的提效

涉及的 vs code 技巧

不要看快捷键好像很多哈,主要是了解几种可以进入多光标批量操作模式的用法

进入多光标之后的快捷键就是日常使用的选中、复制、粘贴了,只是以前是针对当前单个光标的操作,现在变成了针对多个光标的批量操作而已

如何进入多光标的批量操作模式

Ctrl + 鼠标左键(手动添加多个光标)

按住
Ctrl
键后,在哪里点击鼠标左键就会增加一个光标,在原本就有光标的位置点击鼠标左键,是取消当前这个光标

Ctrl + D(自动在满足匹配规则的内容处添加光标)

当我们选中一定内容时,vs code 其实已经将全文里面满足相同规则的内容都会给稍微呈现暗色来提示这是跟当前选中内容一样的内容

比如上图中但选中
</el-check
内容时,其他相同的内容颜色上明显可以看出有点暗色样式

Ctrl + D
就是将这些满足相同匹配规则的内容自动加上个光标,按一次往下处理一次,按一次处理一次

如果想一次性把所有满足的都加上光标处理,快捷键是
Ctrl + Shift + L

Ctrl + Shift + Alt + 上下方向键(在当前光标上一行或下一行同列的地方添加光标)

如果你要添加多光标的场景刚好的相邻行里同列的地方,那可以直接通过这个快捷键快速添加上多行的光标,就不用再鼠标左键一个个点过去了,也不用去寻找每一行的相同匹配内容了

多光标后的批量操作

简单说,你正常能做的操作,在多光标批量操作模式里也可以进行,区别只是变成了你的操作同时在多个光标中批量进行

所以当你有需要在当前代码文件里进行多个重复操作时,就可以考虑多光标的批量操作模式了,比如国际化场景,下面会举个实操场景

Shift + 左右方向键(左右移动选中单个字符)

Shift + Ctrl + 左右方向键(左右移动选中整个单词)

Shift + End(选中从当前光标到当前行末尾)

Shift + Home(选中从当前光标到当前行开头)

Ctrl + C/V(批量复制粘贴所有光标选中的内容)

【User Snippets】如何配置模板代码

我们可以配置一些代码片段来自动快速生成模板代码

比如国际化工作中需要给在 template 代码里的中文词条进行
{{ $t('xxx') }}
处理,常规来说是不是需要先剪切词条,手动输入
{{ $t('') }}
,最后再粘贴,如:

最后代码不多,但也需要敲打好几次键盘,如果用上模板代码呢。

先看下如何配置模板代码:

因为我已经创建过了,所以上面动图直接给你呈现模板配置代码,你要创建的话,就是 New 一个自己的模板配置,然后参考类似的模板配置

创建完后,在代码里只要输入我们配置好的代码前缀,再按
Tab
键,就会自动生成代码了,如:

可能从上面的例子你没觉得提效多少,那假如模板代码很多呢?比如你看看这个:

可以把日常常用的一些代码模板配置起来,输入几个前缀就可以自动生成部分代码,比如请求的代码,全局弹窗的代码等等,这样还省得我们自己敲,或者导出去复制粘贴

甚至说,你忘记我这篇主要介绍的是什么了吗?当配合多光标的批量操作时,简直是神兵利器,非常提效

实操场景

将代码中的中文词条都包裹上 $t 处理

这就是批量操作的提效!

上面动图里,我用上了上面介绍的所有快捷键,全程没有鼠标操作,一气呵成,不知道为什么,这操作下来有种莫名的满足

我先是用
Shift + 方向键
选中内容,然后
Ctrl + D
来快速往我想要批量操作的内容加上多光标,接着通过
Ctrl + 方向键
快速移动光标到中文词条首个字母上,接着
Shift + End
选中光标到末尾,接着
Shift + Ctrl + 方向键
取消多余的选中内容,让每一行的光标都只选中了中文词条,接着
Ctrl + X
剪切,再输入
t1 + Tab
触发模板代码生成,最后
Ctrl + V
批量粘贴,搞定!

所以,当你熟能生巧之后,相信我,你会爱上批量操作的,不仅提效快捷,还能一定程度上解放鼠标,敲打到一半被迫中断去操作鼠标是很难受的

将 el-checkbox 的 label 和 value 关系复制到一个对象里

批量复制,粘贴,是不是很提效!

本文将为您介绍如何设计在线手机游戏排行榜。

什么是排行榜?在游戏或其他地方,排行榜是非常常见的,用于显示哪些玩家在比赛中处于领先地位。用户完成任务或挑战后被分配积分,谁的积分最多谁就在排行榜的顶部。下图显示了一个示例。

候选人:排行榜的得分是如何计算的?

面试官:用户在赢得比赛时获得积分。我们可以采用一个简单的积分系统,每次用户赢得比赛时,我们将相应的积分加到他们的总得分中。

候选人:排行榜中是否包括所有玩家?

面试官:是的。

候选人:排行榜是否与时间段相关联?

面试官:每个月都会开始一个新的锦标赛,启动一个新的排行榜。

候选人:在排行榜中,可以假设只关心前10名用户吗?

面试官:我们希望显示前10名用户以及特定用户在排行榜上的位置。另外,我们还可以讨论一下如何返回距离特定用户上下几名的用户。

候选人:一个锦标赛中有多少用户?

面试官:平均每天有超过500万的日活用户(DAU)和2500万的月活跃用户(MAU)。

候选人:在一个锦标赛期间平均进行多少场比赛?

面试官:每个玩家平均每天进行10场比赛。

候选人:如果两名玩家得分相同,我们如何确定排名?

面试官:在这种情况下,他们的排名是相同的。

候选人:排行榜是否需要实时更新?

面试官:是的,我们希望呈现实时结果。

总结一下,列出功能性需求:

  1. 显示排行榜上的前10名玩家。
  2. 显示用户的具体排名。
  3. 显示距离所需用户上下四名的玩家。

除了功能性需求外,以下非功能性需求也很重要。

  1. 分数的实时更新。
  2. 可伸缩性、可用性和可靠性要求。

系统QPS估算

有500万日活跃用户(DAU),如果在24小时内玩家均匀分布,我们将每秒平均有50名用户(5,000,000 / 24 / 60 / 60 = 50)。然而,我们知道实际情况不是均匀分布的,可能在晚上有高峰期。考虑到这一点,我们可以假设峰值负载是平均负载的5倍。因此,我们希望允许每秒最多250名用户的峰值负载。

对于获得积分的用户:如果用户平均每天玩10场比赛,每秒获得积分的QPS是:50(平均值) x 10 = 500。峰值QPS是平均值的5倍:500 x 5 = 2500。

获取前10名排行榜的QPS:假设用户每天只打开游戏一次,并且仅在用户首次打开游戏时加载前10名排行榜。这个QPS大约是50。

总体设计

在这一部分,我们将讨论API设计、整体架构和数据模型。

API设计

总的来说,我们需要以下三个API:

POST /v1/scores

当用户赢得一场比赛时,更新用户在排行榜上的位置。这应该是一个内部API,在游戏服务器间调用。客户端不应该直接更新排行榜分数。

请求参数:

  1. user_id:赢得比赛的用户ID。
  2. points:用户通过赢得比赛获得的积分。

响应:

  1. 200 OK:成功更新用户的分数。
  2. 400 Bad Request:无法更新用户的分数。

GET /v1/scores

从排行榜中获取前10名玩家。

响应示例:

{
"data": [
{
"user_id": 666666,
"user_name": "alice",
"rank": 1,
"score": 976
},
{
"user_id": 112,
"user_name": "bob",
"rank": 2,
"score": 965
},
// ... (可能还有其他用户数据)
],
"total": 10
}

GET /v1/scores/{:user_id}

获取特定用户的排名。

响应示例:

{
"user_info": {
"user_id": 5,
"score": 940,
"rank": 6
}
}

 

总体设计图如下图所示。在这个设计中有两个服务。游戏服务允许用户玩游戏,而排行榜服务则创建并显示排行榜。

  1. 当玩家赢得一场游戏时,游戏客户端发送请求到游戏服务。
  2. 游戏服务调用排行榜服务更新分数。
  3. 排行榜服务持久化存储分数。
  4. 玩家调用排行榜服务以获取排行榜数据,包括:

(a) 前10名的排行榜。

(b) 玩家在排行榜上的排名。

游戏客户端直接调用排行榜服务?

如上图所示设计,分数由游戏客户端设置。这个选项不安全,因为它容易受到中间人攻击(
https://en.wikipedia.org/wiki/Man-in-the-middle_attack )的影响,玩家可以使用代理更改分数。因此,我们需要在服务器端设置分数。

我们是否需要在游戏服务和排行榜服务之间使用消息队列?

这个问题完全取决于游戏积分的使用方式。

如果该数据在其他地方使用或支持多个功能,那么将数据放入Kafka可能是有意义的,如下图所示。这样,同一份数据可以被多个消费者使用,比如排行榜服务、分析服务、通知服务等。

当游戏是一个回合制或多人游戏时,我们需要通知其他玩家有关分数更新的情况,这一点尤为重要。

假设在之前与面试官的对话中没有明确的要求,我们在设计中没有使用消息队列。

数据模型

系统中的关键组件之一是排行榜存储。我们将讨论三种潜在的解决方案:关系数据库、Redis和NoSQL。

基于关系数据库的方案

如果用户规模不是太大,我们很可能选择使用关系数据库系统(RDS)实现一个简单的排行榜解决方案。

每个月的排行榜可以被表示为一个包含用户ID和分数列的数据库表。当用户赢得比赛时,为用户更新积分。为了确定用户在排行榜上的排名,我们将按分数降序对表进行排序。

实际上,排行榜表还包含其他信息,比如game_id、timestamp等。然而,查询和更新排行榜的基本逻辑保持不变。为简单起见,我们假设只有当前月份的排行榜数据存储在排行榜表中。

赢得积分

假设每次胜利赢得1分数。如果在当月的排行榜中为新用户:

INSERT INTO leaderboard (user_id, score) VALUES ('mary1934', 1);

如果是老用户:

UPDATE leaderboard SET score = score + 1 WHERE user_id = 'mary1934';

查找用户在排行榜中的位置

为了获取用户的排名,我们将对排行榜表按照分数排序:

SELECT (@rownum := @rownum + 1) AS rank, user_id, scoreFROMleaderboardORDER BY score DESC;
 

SQL查询的结果类似于这样:

这个解决方案在数据集较小时是没有什么问题的,但是当有数百万行数据时,查询可能变得非常慢。让我们看看为什么。

这个SQL查询实际上需要全表扫描。另外请注意,分数可能重复,所以排名可以不是用户在列表中的位置。我们需要基于上面的SQL查询结果进行进一步处理。

当我们需要处理大量不断变化的信息时,SQL数据库性能不佳。由于数据不断更新变化,在这里,使用缓存也不太合适。

我们可以进行优化:添加索引,并使用LIMIT子句限定结果集。查询看起来像这样:

SELECT (@rownum := @rownum + 1) AS rank, user_id, scoreFROMleaderboardORDER BY score DESCLIMIT10;
 

然而,这种方法的扩展性并不好。首先,它本质上还是需要对表进行扫描以确定排名。其次,这种方法没有获取到不在排行榜顶部的用户的排名情况。

基于Redis的解决方案

我们希望找到一种解决方案,即使对于数百万用户,也能提供可预测的性能,并且允许我们轻松进行常见的排行榜操作,而无需依赖复杂的数据库查询。

Redis为我们的问题提供了一个潜在的解决方案。Redis是一个支持键值对的内存数据存储组件。由于它在内存中工作,它允许快速读写。Redis有一个特定的数据类型称为
sorted sets
,非常适合解决排行榜系统设计问题。

有序集合是一种类似于集合的数据类型。有序集合的每个成员与一个分数相关联。集合的成员必须是唯一的,但分数可以重复。分数用于按升序排列有序集。

这里,我们的排行榜用例场景完美匹配Redis sorted sets。

Redis sorted sets有序集由两个数据结构实现:哈希表和跳表。哈希表将用户映射到分数,而跳表将分数映射到用户。在有序集中,用户按分数排序。

理解有序集的一种好方法是将其想象成一个包括分数和用户两列的表,如下图所示。表按分数降序排序。

我们从总体上看一下有序集的实现思想。

跳表是一种允许快速搜索的列表结构。它由一个基本排序链表和多级索引组成。

看一个例子。

如下图所示,基本列表是一个有序的单向链表。插入、删除和搜索操作的时间复杂度是O(n)。

如何使得这些操作变得更快呢?一个想法是快速到达中间,就像二分搜索算法一样。

为了实现这一点,我们添加一个索引:每隔一个节点,选取一个节点。类似的方式,再添加一个索引。以此类推,我们不断引入额外的索引。

当节点之间的距离为n/2 - 1 时,停止添加更多的索引。其中n是总节点数。如图所示,当我们有多级索引时,搜索数字45就变得更快了。

有序集比关系数据库性能好,那是因为每个元素在插入或更新时都会自动按正确的顺序定位。

在有序集中进行添加或查找操作的复杂度是对数级别的:O(log(n))。

相比之下,在关系数据库中计算特定用户的排名,我们需要运行嵌套查询:

SELECT COUNT(*) FROM leaderboard lb2 WHERE lb2.score >= lb1.score) ASRANKFROMleaderboard lb1WHERE lb1.user_id = {:user_id};

这个查询使用了子查询来计算比当前用户分数高的其他用户数量,从而确定当前用户在排行榜上的排名。这样的查询结构在大规模数据集上的性能可能较差。

使用Redis有序集实现

基本操作:

  1. ZADD :如果用户尚不存在,则将用户插入集合中。否则,更新用户的分数。执行时间复杂度为O(log(n))。
  2. ZINCRBY :将用户的分数增加指定的增量。如果用户在集合中不存在,则分数从0开始。执行时间复杂度为O(log(n))。
  3. ZRANGE/ZREVRANGE :按分数排序获取一定范围内的用户。我们可以指定排序顺序(range vs. revrange)、条目数以及要从哪个位置开始。执行时间复杂度为O(log(n) + m),其中m是要获取的条目数(在我们的场景中通常很小),n是有序集中的条目数。
  4. ZRANK/ZREVRANK :以对数时间复杂度获取任何用户在升序/降序排序中的位置。

下面看看具体工作流程。

1,用户赢得积分

每个月我们都会创建一个新的排行榜有序集,之前的排行榜将被作为历史数据存储。

当用户赢得一场比赛时,得到1积分;因此,我们调用ZINCRBY来在当月的排行榜中将用户的分数增加1。ZINCRBY的语法如下:

ZINCRBY <key> <increment> <user>

例如,以下命令为用户mary1934赢得比赛后为其增加1分:

ZINCRBY leaderboard_feb_2021 1 mary1934

2,用户获取全球前10名排行榜

调用ZREVRANGE以降序获取用户列表。

例如,以下命令获取2021年2月排行榜的前10名玩家:

ZREVRANGE leaderboard_feb_2021 0 9 WITHSCORES

将返回一个类似这样的列表:

[(user2, score2), (user1, score1), (user5, score5), ...]

3,用户想要获取他们在排行榜中的位置

要获取用户在排行榜上的位置,我们将调用ZREVRANK来检索他们在排行榜上的排名。

ZREVRANK leaderboard_feb_2021 mary1934

4,获取用户在排行榜中的相对位置

我们可以通过ZREVRANGE轻松获取用户的相对位置。

例如,如果用户'E.Elliott'的排名是47,我们想要获取在他上面和下面的4名玩家,我们将运行以下命令。

ZREVRANGE leaderboard_feb_2021 43 51

存储需求

我们需要存储用户ID和分数。最坏的情况是,所有2500万月活跃用户都赢得了至少一场比赛,他们都在该月的排行榜中有条目。

假设ID是一个24个字符的字符串,分数是一个16位整数(2字节),每个排行榜条目需要26字节的存储空间。

在最坏的情况下,每个MAU一个排行榜条目,我们需要26字节 x 2500万 = 650百万字节,约为650MB。

即使考虑到跳表和有序集的哈希开销,我们将内存使用量翻倍,目前一般的Redis服务器足以容纳这些数据。

关于Redis服务器CPU和I/O。我们根据简易估算得到的峰值是每秒更新2500次。这远远低于单个Redis服务器的性能范围。

关于Redis缓存的一个担忧是数据持久化,因为Redis节点可能会发生故障。幸运的是,Redis是支持数据持久化的。通常,可以为Redis配置读取副本,当主实例宕机时,读取副本会被提升为主实例。

在关系型数据库中,我们需要两个表(用户表和积分表)。用户表将存储用户ID和姓名等。积分表将包含用户ID、分数和时间戳等。这可以用于其他业务功能,比如游戏历史记录,并且在系统故障的情况下也可以用来重新创建Redis缓存中的排行榜。

另外,创建一个额外的缓存存储用户详细信息应该比较有用,比如前10名玩家的详细信息,因为它们频繁访问。

扩展


Redis集群有可能遭遇大规模故障,需要使用一种机制确保用户数据不丢失。

用户每次赢得比赛,获得积分,我们可以使用关系型数据库如MySQL在数据库添加一条记录。这样,在大规模故障的情况下,我们可以为每个用户遍历此表,为每个条目调用一次ZINCRBY。从而,离线重建Redis缓存中的排行榜。


系列文章总目录

一边
会员救园
,一边尝试开网店,为了让这破园子能生存下去,我们在努力着。

万事开头难,开头起名难,而我们却在开头第一步就摔了一跤,起了一个
糟糕的名字
。在园友们雪亮的评论中我们意识到自己的错误,于是改繁归简,返璞归真,淘宝店铺名称用了最简单最熟悉的“
博客园
”。原来开头并不难,是我们自找难题。

接下来的难题是第一款商品的选品。我们的要求并不高,即中看又中用,价格不高,大多数程序员会用到。想来想去,讨论来讨论去,最终做出了一个朴素的选择,选择了自从有PC以来,历史悠久、大众化、很普通却很重要的小配角——鼠标垫,控制电脑的大多数鼠标必不可少的依靠。

再接下来的难题就是鼠标垫的设计,开始让鼠标垫合作厂家帮忙设计,设计效果不理想,后来在一位海外朋友的帮助下终于完成了设计,今天发出来给大家点评,下图就是鼠标垫的设计图。

另外,统计一下有兴趣购买园子第一款第一批鼠标垫的大概人数,我们会根据统计人数确定第一批鼠标垫的印制数量,如果您有兴趣购买,欢迎在评论中留言。