工作两年总结 2019 年参加腾讯的暑期实习面试认识了现在的总监,那段时间退租了原有的公寓寄宿在同学家里,面试当天又刚参加完 GoogleCloudOnBoard 在回去的路上,整个面试的过程非常仓促,气喘吁吁地拿着手机跟面试官打了两小时微信语音,虽然后来计划有变并没有在假期回国实习,但也因此获得了之后校招被总监直接内推到现在项目组的机会。于是后来在经历了四轮技术面试之后,某天在图书馆突然接到总监的微信语音,接着又是 HR 的沟通电话,才在回国的几个月前确定了要来深圳的天美。
两年的时间里经历了很多工作和生活上的试错和反馈,在此尝试总结和反思。
impact 刚上班的时候,认为工作无非就是做好老板安排的事情,月底的时候老板为你在这个月完成工作所付出的时间支付等价的薪资。那么理所应当地,如果你每天花更多的时间在工作上,每天加班到晚上 10 点,周六周日不间断工作,为老板创造更多的收益,那么老板就会更乐意为你支付更高的薪资,这也是为什么国内会有那么多人乐此不疲地内卷的原因。
实际上有些人虽然每天从不加班到点就走,不仅能拿到高绩效和更多的奖金,还能创造出很大的 impact。用李开复的话来说,impact 就是世界有你和没有你之间的区别。举个例子,你女朋友很爱去图书馆自习室,需要每天晚上 9 点登录学校图书馆网站,预约第二天的自习室名额,如果你花几小时写个脚本放在服务器上每天定时自动抢名额,为她省下了每天那几分钟的时间和精力,那么(为她每天省下的时间和精力 * 天数)就是你写脚本这件事的 impact;同时你还可以顺带把脚本给你爱学习的朋友们用,那么这个数值还可以乘上使用的人数。套用李沐大神的定义,impact 等于(受益人数 * 人均时间 * 单位时间价值差),单位时间价值差取决于你对这件事完成的好坏,如果有人写了一个更易用,响应更快的新的脚本,你的朋友们都去用这个新脚本了,那么这个脚本开发者创造的单位时间价值差就比你高。无论是工作产出、技术分享、指导新人、为团队规划和确定方向,都可以套用这个公式,来评判自己创造的 impact,提高创造 impact 的能力比不断付出劳动时间更能带来持续性收益。
后台开发 后台开发无非两部分:增删改查和高并发架构,前者需要数据结构和算法的知识,后者需要系统设计的能力,包括缓存设计、云原生、异步存储、数据拆分之类的。而游戏后台相较于互联网还多出了有状态服务和延迟敏感两个特点,并且在系统设计方面也有很大的细分差异。
游戏后台习惯于先在内存上读写数据,再把有状态的内存数据持久化到 db,而不是每次都去操作 cache / db,这和现在的互联网后台架构的“业务逻辑和数据分离”的指导思想有很大的区别。
服务器上云依靠 service mesh 进行通信,sidecar 注入和劫持流量,拿到数据包之后还要考虑做零拷贝来降低单机延迟,经过这些步骤之后如果延迟是 100ms ,对互联网业务来说或许是可以接受的,但对手游来说已经几乎高到无法容忍了;除此之外把功能不同的模块拆分成微服务后,还会极大地增加不同模块间通信的网络开销。
游戏后台进行云上扩缩容还要考虑 k8s 资源调度的效率,有状态服务的迁移流程,迁移时要保证不可服务的时间和粒度尽量小。
不同的业务有各自的刚需和痛点,把这些互联网时代催生的技术应用到游戏业务上时也要结合其对产品的价值来进行相应的调整,而不是生搬硬套。
游戏行业 2021 年的未成年人保护法和版号停发让国内各家游戏公司都在推进所谓精品化和出海两个方案。
因为版号少了,游戏公司一方面要投入更多资源到长线运营,也就是氪金上来保证现金流,另一方面只能保守地复制成功经验(moba,卡牌,打枪)来抢存量市场保证新游戏不会没人玩。长此以往玩家逐渐审美疲劳,也就遏制了青少年玩家对于游戏的兴趣,有关部门也能逐步加大力度断绝国内游戏行业的前途,最后再将其连根拔起,从此没了教培和游戏,年轻人也就能专心谈恋爱,从而提高生育率,解决人口危机了。
至于出海,目前有资金和技术储备跟海外厂商直接竞争的国内游戏公司也就只有腾讯了,不过对有关部门来说,制裁一个公司可比监管整个行业简单多了。
技术和产品 游戏后台最重视 n 个 9 的高可用,最害怕上线之后出现 bug。这两年里印象最深的外网 bug 有两次,一次是入职半年做 crud 时,因为没有仔细阅读文件里的多个名称相近的接口导致调用了错误的一个;另一次是某个元旦凌晨上线的功能,因为错误地估计了某个途径获取的数据条目数量导致预留的数组长度不够,后者在当时登陆高峰的时间段内造成了短暂的不可用。这些当然都不是因为技术水平不够,但确实会对个人口碑造成实实在在的打击。以前刷题或者竞赛时如果遇到这种小问题,最严重的后果无非是 ac 率降低和 5 分钟的罚时,但一旦转换到工作上,由于对技术审查的不严谨则会非常严重的影响产品和整个团队。
员工和公司 刚上班时会把公司当作学校,把工位当作图书馆或者实验室,手头没工作并且又没其他私事的时候,包括工作日晚上和周末,就喜欢在工位上看书学习。长此以往会牺牲不少个人生活的时间,同时也会让同事和经理都认为你是一个非常卷的人,从而影响对你工作态度和结果的评价,所以只要跟工作无关的活动一律不应在公司进行。
员工与公司之间的劳资关系也会影响工作效率。在互联网企业里劳资关系无非是简单的雇佣或合作关系,但腾讯的很多部门,尤其是在司庆到春节的一段时间里,喜欢以“家人们”来互相称呼,听到的时候会让人非常反感,这并不是健康的劳资关系。
刚入职的时候毛星云大神坐在离我不到 10 米的地方,经常和客户端开发的同事们有说有笑,或是讨论技术。他出事之后办公室里的几百号人却像无事发生过一样,大都自顾自地重复着前一天的动作,不免让人感到残酷和凄凉。大二的时候要不是偶然间搜到浅墨在 CSDN 上的 DX 教程,可能我的计算机图形学也就挂了。引用 Youtuber Vincent Chan 的一段想法:...
《深入理解 RPC 框架原理与实现》读书笔记 概念 RPC (Remote Procedure Call) 叫作远程过程调用,它是利用网络从远程计算机上请求服务:可以理解为把程序的一部分放到其他远程计算机上执行。通过网络通信将调用请求发送至远程计算机后,利用远程计算机的系统资源执行这部分程序,最终返回远程计算机上的执行结果。
将“远程过程调用”概念分解为“远程过程”和“过程调用”来理解更加直观:
远程过程:远程过程是相对于本地过程而言的,本地过程也可以认为是本地函数调用,发起调用的方法和被调用的方法都在同一个地址空间或者内存空间内。而远程过程是指把进程内的部分程序逻辑放到其他机器上,也就是现在常说的业务拆解,让每个服务仅对单个业务负责,让每个服务具备独立的可扩展性、可升级性,易维护。在每个机器上提供的服务被称为远程过程,这个概念使正确地构建分布式计算更加容易,为后续的服务化架构风格奠定了基础
过程调用:这个概念非常通俗易懂,它包含我们平时见到的方法调用、函数调用,并且用于程序的控制和数据的传输。而当“过程调用”遇到 “远程过程”时,意味着过程调用可以跨越机器、网络进行程序的控制和数据的传输
选型 RPC 选型的衡量角度
使用 RPC 框架无非三个选择
自研RPC框架,可以从投开始设计一款符合业务特征和场景的 RPC 框架,但是自研框架需要有足够的资金和人力支持 基于开源的 RPC框架进行改造,让改造后的 RPC 框架更加适合业务场景。这种做法相较于第一种做法,人力成本没有那么高。但是这种做法需要经常与开源社区保持同步更新,一旦不再和社区版本同步,也许到某一个版本后,公司内部改造的 RPC 框架再也不能合并社区最新版本的特性,这种现象最终会导致慢慢向第一种选择选择靠近 完全使用开源的 RPC框架,并且定期与社区版本进行同步。这种选择的好处在于要投人的人力威本最低,一些问题可以借助社区的力量进行解决。但是由于业务场景的不同直接将开源的 RPC 框架拿过来用,这种选择往往存在很多局限性。框架各部分的设计都是为了更加优雅地解决业务场景的问题,而不是反过来让业务场景去适应 RPC 框架。而且 RPC 框架有自己的定位及未来的规划,所以很多规模不是太小的公司都选择在 RPC 框架上做些许改造来适应自己的业务场景 Java 对 I/O 模型的封装 NIO Java NIO 中最核心的就是 selector, 每当连接事件、接收连接事件、读事件和写事件中的一种事件就绪时,相关的事件处理器就会执行对应的逻辑,这种基于事件驱动的模式叫作 Reactor 模式。Reactor模式的核心思想就是减少线程的等待。当遇到需要等待的 IO 操作时,先释放资源,而在 IO 操作完成时,再通过事件驱动的方式,继续接下来的处理,这样从整体上减少了资源的消耗。
以下是 Reactor模式的五种重要角色:
Handle(在 Linux 下称为描述符):它是资源在操作系统层面上的一种抽象,表示一种由操作系统提供的资源,比如前面提到的网络编程中的 socket 描达符或者文件描达符。该资源与事件绑定在一起,也可用于表示一个个事件,比如前面提到的客户端的连接事件、服务端的接收连接事件、写数据事件等 Synchronous Event Demultiplexer(同步事件分离器):Handle 代表的事件会被班注册到同步事件分离器上,当事件就绪时,同步事件分离器会分发和处理这些事件。它的本质是一个系统调用,用于等待事件的发生。调用方在调用它的时候会被阻塞,一直到同步事件分离器上有时间就绪为止。在 Linux 中,同步事件分离器就是常用的 I/O 多路复用,比如 select, poll, epoll 等系统调用,用来等待一个或多个事件发生。在 Java NIO 领域中,同步事件分离器对应的组件就是 Selector,对应的阻塞方法就是 select 方法 Event Handler(事件处理器):它由多个回调方法构成,这些回调方法就是对某个事件的逻辑反馈,事件处理器一般都是抽象接口。比如当 Channel 被注册到 Selector 时的回调方法、连接事件发生时的回调方法、写事件发生时的回调方法等都是事件处理器,我们可以实现这些回调方法来达到对某一个事件进行特定反馈的目的。在 Java NIO 中,并没有提供事件处理器的抽象供我们使用 Concrete Event Handler(具体的事件处理器):它是事件处理器的实现。它本身实现了事件处理器所提供的各种回调方法,从而实现了特定的业务逻辑。比如针对连接事件需要打印一条日志,就可以在连接事件的回调方法里实现打印日志的逻辑 Initiation Dispatcher(初始分发器):可以把它看作 Reactor, 它规定了事件的调度策略,并且用于管理事件处理器,提供了事件处理器的注册、删除等方法,事件处理器需要注册到 Initiation Dispatcher 上才能生效。它是整个事件处理器的核心,Initiation Dispatcher 会通过 Synchronous Event Demultiplexer来等待事件的发生。一旦事件发生,Initiation Dispatcher 首先会分离出每一个事件,然后找到相应的事件处理器,最后调用相关的回调方法处理这些事件 Reactor 的三种模型 单 Reactor 单线程模型 单 Reactor 单线程模型就是在设计中只会有一个 Reactor,并且无论与 IO 相关的读/写,还 是与 IO 无关的编/解码或计算,都在一个 Handler 线程上完成。...
2022 年初 | 后端开发两年经验社招面经 字节 一面 coding: 对于一个数组,仅用一次遍历,等概率地随机出一个元素(对于每一个元素,从全局看,他们被选择的概率都应该是 1/n)
对于第 i 个元素,它在第 i 轮被选中的概率是 1/i 往后,只要选择了新的元素,它就会被淘汰;以第 i+1 轮为例,它被淘汰的概率是 1/(i+1),那么反过来它被留下的概率就是 1 - 1/(i+1) 最终每一个元素被选择的概率如下,第一个 1/i 代表它在第 i 次被选中,其他数代表它在后续的每一轮被留下 followup:等概率地随机出 k 个元素
对于第 i 个元素,它在第 i 轮被选中的概率是 k/i 往后,它唯一会被淘汰的场景是:选择了新的元素,同时从已有的选择中,等概率地选择到了它;以第 i+1 轮为例,它被淘汰的概率是 k/(i+1) * 1/k = 1/(i+1),那么反过来它被留下的概率就是 1 - k/(i+1) * 1/k = 1 - 1/(i+1) 最终每一个元素被选择的概率如下,第一个 k/i 代表它在第 i 次被选中,其他数代表它在后续的每一轮被留下 coding: 实现 Fisher–Yates Suffle
void shuffle(vector<int> v) { int n = v.size(); for (int i = n - 1; i >= 1; --i) { int j = rand() % (i + 1); swap(v[i], v[j]); } } 二面 system design:主播开播,如何把这个消息推送给他的千万级 follower 设计数据仓库(数据库也可以) 消息队列 followup:推送之后,主播已经下播,怎么处理 followup:怎么保证消息推送或被消费 followup:如何保证消息不重复 三面 DB 迁移流程...
小米 AX1800 使用 ShellClash 科学上网 本文介绍如何在小米 AX1800 上使用 ShellClash 科学上网。
1 固件降级 小米 AX1800 1.0.336 版本固件
在 MiWiFi 后台的常用设置 -> 系统状态中点击手动升级,使用 1.0.336 版本的固件对路由器进行降级,整个过程不需要任何额外的操作。
完成后第一次进入后台配置时选择不自动更新,或在小米 Wi-Fi 手机 App 中关闭自动更新功能。
更新完成后路由器会重置为路由器底部贴条的设置。
2 解锁 SSH 首先登陆小米路由器后台 192.168.31.1,将浏览器地址栏中 stok= 后面的一部分的即为拷贝下来。
获取 SSH 权限 在浏览器中打开一个新页签,在地址栏中输入下面的链接,将其中 stok=...... 中后半部分的六个点换成刚才拷贝的内容,页面上显示 {"code":0} 即代表成功,这样就解锁了 SSH 到路由器上的权限。
http://192.168.31.1/cgi-bin/luci/;stok=....../api/misystem/set_config_iotdev?bssid=Xiaomi&user_id=longdike&ssid=-h%3B%20nvram%20set%20ssh_en%3D1%3B%20nvram%20commit%3B%20sed%20-i%20's%2Fchannel%3D.*%2Fchannel%3D%5C%22debug%5C%22%2Fg'%20%2Fetc%2Finit.d%2Fdropbear%3B%20%2Fetc%2Finit.d%2Fdropbear%20start%3B 修改 root 密码 在浏览器中打开一个新页签,在地址栏中输入下面的链接,将其中 stok=...... 中后半部分的六个点换成刚才拷贝的内容,页面上显示 {"code":0} 即代表成功,这样就将路由器上 root 账户的密码改为了 admin。
http://192.168.31.1/cgi-bin/luci/;stok=....../api/misystem/set_config_iotdev?bssid=Xiaomi&user_id=longdike&ssid=-h%3B%20echo%20-e%20'admin%5Cnadmin'%20%7C%20passwd%20root%3B 连接 SSH 打开 terminal 或者 powershell,ssh 到路由器的 root 账户上,密码是 admin。
ssh root@192....
Kafka 入门 概述 Kafka 最初是为了解决 LinkedIn 数据管道问题应运而生的。它的设计目的是提供一个高性能的消息系统,可以处理多种数据类型,并能够实时提供纯净且结构化的用户活动数据和系统度量指标。
它不只是一个数据存储系统(类似于传统的关系型数据库、键值存储引擎、搜索引擎或缓存系统),还是一个持续变化和不断增长的流处理系统。现在 Kafka 已经被广泛地应用在社交网络的实时数据流处理当中,成为了下一代数据架构的基础。Kafka 经常会被拿来与现有的企业级消息系统、大数据系统(如 Hadoop)和数据集成 ETL 工具等技术作比较。
从发布和订阅消息流的角度来看,Kafka 类似于 ActiveMQ、RabbitMQ 或 IBM 的 MQSeries 等产品,其特点在于它以集群的方式运行,可以自由伸缩,处理大量的应用程序;其次,Kafka 可以按照要求持久化数据,即提供了数据传递的保证——可复制、持久化,保留多长时间完全可以由开发者决定。此外,消息系统只会传递消息,而 Kafka 的流式处理能力让我们只用很少的代码就能够动态地处理派生流和数据集。
1 基础概念 消息代理 在一个基于发布与订阅的消息系统中,数据消息的发送者不直接把消息发送给接收者,而是通过一个消息代理 message broker 传递消息,接收者订阅消息代理,并以特定的方式接收消息。Kafka 就是一个消息代理。
消息代理 message broker 是一种针对处理消息流而优化的数据库,它作为独立的中间服务运行,生产者和消费者作为客户端连接到消息代理服务,在使用消息代理的架构中主要有 3 种角色:
生产者将消息写入消息代理;生产者一般是异步架构的,当生产者发送消息时,它只会等待消息代理确认消息已经被缓存,而不等待消息被消费者处理 消息代理负责消息的存储,发送、重传等,一般会包含多个消息队列 message queue 消费者从消息代理接收消息并进行处理;消费者只依赖于消息代理,与生产者完全隔离 消息代理的优势主要有以下几点:
实现异步处理,提升性能
把消息处理流程使用消息代理异步化,不会阻塞生产者服务,生产者服务可以在得到处理结果之前继续执行,并提高其并发处理的能力。
提高系统的可伸缩性
生产者将大量消息推送到消息代理中,消息代理可以将这些消息分发给不同的消费者,使得多个消费者并行地处理消息,当消费者负载变化时,可以很容易地对消费者服务进行水平伸缩
削峰填谷
当生产者推送消息的速度比消费者处理消息的速度更快时,可以使用消息队列作为消息的缓冲,来削弱峰值流量,防止系统被短时间内的流量冲垮
应用解耦
使用消息代理后,生产者和消费者即可解耦,不再需要有任何联系,也不需要受对方的影响,只要保持使用一致的消息格式即可。
消息和批次 Kafka 的数据单元被称为消息,消息类似于关系型数据库里的一个数据行或一条记录;消息由字节数组组成,当消息以一种可控的方式写入不同的分区时,会用到 key,Kafka 会为 key 生成一个一致性散列值,然后使用散列值对主题分区数进行取模,为消息选取分区。这样可以保证具有相同 key 的消息总是被写到相同的分区上。
如果每一个消息都单独发送,会导致大量的网络开销。为了提高效率,消息会被分批次写入Kafka;批次 batch 是一组消息,这些消息属于同一个主题和分区;批次数据在传输时会被压缩,这样可以提升数据的传输和存储能力;单个 batch 的消息数量越大,单位时间内处理的消息就越多,但单个 batch 的传输时间就越长,因此需要在时延和吞吐量之间作出权衡。
主题和分区 Kafka 的消息通过主题 topic 进行分类,主题就好比关系型数据库的表,或者文件系统里的目录;同一个主题可以被分为若干个分区 partition,一个 partition 即一个提交日志,消息以追加的方式写入 partition,然后以先入先出的顺序读取。一个 topic 一般包含多个 partition,因此无法在整个 topic 的维度保证消息的顺序,只能保证消息在单个 partition 内的顺序。...