第83期:《程序员进阶之路:缓存、网络、内存与案例》

一个应届毕业生如何从没有任何实战经验成长为可以独当一面的工程师?

写了几年的业务逻辑,如何打破天花板,摘掉“CRUD(增删改查)Boy”标签,成长为更高级的软件开发工程师?

……

相信不少小伙伴在自己职业生涯中都有类似的困惑,本文源自《程序员进阶之路:缓存、网络、内存与案例》一书作者邓中华老师的真实经历,希望可以给各位追求成长的小伙伴一些启发和鼓励!

文/邓中华

入行游戏业

记得毕业那年,找了好一段时间的工作,公司都想要一上来就能干活的人,然而对于我一个刚毕业的学生来说根本做不到。最后终于有一家创业的游戏公司招聘了我,愿意培养应届生,做的是一款FPS微端游戏,可能很多人不了解什么是微端,简单讲就是轻量级的端游,有点像页游但不需要浏览器,后来随着手游的风口,微端改为了手游,我的工作是游戏服务端开发。

01 如饥似渴

刚开始工作时,心里很着急,想要尽快学习工作中用到的知识,尽快完成身份的转换,我想这是很多毕业生的共性。想要一口气看完整个服务端的代码,但是看完这块忘了那块,根本联系不起来,就好像脑袋中的内存不够一样。

那会儿我的导师(G)跟我说:别太着急,一点点来,慢慢就都了解了,一个月业务逻辑就差不多弄明白了,3个月就可以独当一面。

说实话当时我是不自信的,怀疑他说的话在我身上是否成立。

随着导师的耐心指导,再加上自己潜心学习,3个月后,自己真的可以独挡一面,自己接需求,并完成开发任务。

这里要提一点,刚毕业遇到一位好的导师是很重要的一件事情,我非常感谢他对我的帮助。

第一任导师会影响整个后面的工作心态,所以我也会善待我带的应届生。

我真的见过一个没被善待的应届生是如何不善待他带的应届生的。

但是话说回来,导师是分配的,对于刚毕业的应届生来说没得选,靠的是缘分。这里也呼吁一下带应届生的同行,请善待他们,别给他们的整个职业生涯留下心里阴影,我们要传递善意。

02 一夜暴富

每个刚进入游戏行业的朋友多多少少都是奔着游戏爆火、一夜暴富的目的。

但现实却很残酷。

我从事了6年游戏行业,一个游戏都没有大卖,所以说不要抱有任何幻想,成功的概率太低了,这也是后面我转战互联网的一个原因,有点心灰意冷了。

但是还真有特例,我的一个同事(他是被我内推到那个项目的)做的游戏还真的大卖了,每个月发的奖金比工资都多,后来在北京买了房,实属羡慕。

03 业务主力

从事了6年游戏行业后,一直在写业务逻辑,基本上在性能优化等方面没有任何建树,并且逐渐觉得开发业务逻辑没有意思,仅仅是体力劳动罢了,没有挑战,也没有成长空间了,认识到写5年和10年业务逻辑在个人成长方面没有什么差别,只不过业务逻辑更熟练罢了。因此开始琢磨转战互联网,想要从事高并发和更接近底层的工作,因为业务逻辑真的写吐了。

有人可能会问,为啥不在游戏行业从事这些工作呢,因为游戏行业注重开发进度,大量堆砌业务逻辑,开发节奏非常快,哪里会给你优化和从事底层的机会,即使有,需要的人也是少数,轮不到你。

你见过有2万行代码的任务系统吗?我就见过,而且开发、重构和维护过,真的是牵一发动全身!

你要相信从游戏行业出来的人的逻辑开发能力,他们都是经过了具有天马行空想法的策划同仁的考验,才会站在你面前。以至于后来从事互联网业务开发,觉得互联网的业务也太简单了,都不费什么脑力。

转行互联网

下定决心转行做互联网服务端后,我面试了很多互联网公司,人家都说我做的是业务开发,没有做过什么有挑战的工作,例如内存泄露问题和性能优化问题等工作,将我拒之门外。即使拿到Offer了,很多公司也压职级和薪资。后来遇到了一个互联网大厂,面试效果比较好,愿意接受我的转行。

这里有些仅从事过游戏行业或者互联网行业的朋友可能不清楚为啥游戏到互联网需要转行,不都是写代码吗。

还真不是写代码那么简单的事,需要思维上的转变。

游戏更注重复杂业务逻辑的开发,每组服务器,也就是一个区,玩家数量是比较少的,一个用户频繁与服务端通信,服务端性能不够那就多开几个区;而互联网的服务器注重性能,基本没有分区的概念,用户不需要选择登录哪个区,只不过有集群的概念,服务的用户数量非常多,每个用户与服务端通信却没那么频繁。有的互联网公司的面试官会觉得游戏服务器承载这么点用户量没有技术含量,这就说明隔行如隔山。

01 如鱼得水

因为本人喜欢做有挑战的工作,加上前面提到6年业务开发早已厌倦,非常想要做性能优化等偏离业务本身的底层工作。来到新的公司后,领导(L)给机会研究网络库和状态机等核心C/C++底层库,我非常感谢这位领导。这段时间我非常开心,终于如偿所愿。积累的游戏业务经验没有用了,不过经过之前的业务逻辑的锻炼,互联网业务逻辑手拿把掐,但是需要补齐互联网的工作流程和思维。

新公司的第一个任务是排查一个C++服务的内存泄露问题,这个问题已经困扰他们2周了。说实话我是没有信心的,因为之前从来没有做过这类事情,但是我的内心又是欣喜的,我喜欢底层的、偏离业务的和有挑战的工作。虽然我没有排查过内存泄露问题,但是我可以研究啊,哪里不会就学习哪里,武装自己的技能背包。

1. 背景

我原来有一个使用C++编写的业务服务,迟迟不敢上线,原因是内存泄漏问题一直解决不了。具体现象是,该服务上线后,内存每隔几秒上涨 4/8KB,该服务不停止运行,内存就一直上涨。

2. 分析过程

使用Valgrind工具多次运行该服务,在结果中没有发现内存泄漏,但是发现有很多没有被释放的内存和疑似内存泄漏。

既然使用工具分析失败,那么就逐个模块查看代码,并且编写Demo逐个验证该模块是否有内存泄漏(使用Valgrind工具检测内存泄漏),很遗憾,最后还是没有找到内存泄漏。

这个时候两周过去了,领导说:“找不到内存泄漏那就先去做别的任务吧”,感觉到一丝凉意,我说:“再给我点时间,快找到了”。这样顶着巨大压力加班加点,对比多次数据结果,第一次Valgrind运行10分钟,第二次Valgrind运行20分钟,查看有哪些差异或异常,寻找蛛丝马迹。遗憾的是,还是没有发现内存泄漏在哪里。

功夫不负有心人,查看了N份Valgrind的运行结果后,对一个队列产生了疑问,它为什么这么长?队列长度为1000万个元素,直觉告诉我,这里不正常。

在代码中查找这个队列,发现在初始化队列的时候将队列设置为1000万个元素,这个长度值太大了。

3. 定位

新创建(new或者malloc)的对象在入队列时,因为访问了虚拟内存地址,所以需要把虚拟地址映射到物理地址,因此物理内存就会增加,但是当对象出队列并调用释放内存函数(delete或者free)时,物理内存不会立刻回收和取消虚拟内存到物理内存的映射关系,而是保留给程序一段时间(当系统内存紧张时会主动回收),目的是让程序再次使用之前的虚拟地址和其映射到的物理内存,避免内存申请和缺页中断的开销。

当服务启动时,程序在这1000万个元素的队列上一直不停地进/出队列,有点像貔貅,只进不出,自然会导致物理内存一直上涨,直到貔貅跑到了队尾,物理内存才会达到顶峰,物理内存的增长和回收开始处在一个平衡点。

然而每次服务上线还没等到达物理内存增长和回收的平衡点就下线了,担心服务内存一直增加,为了避免出事故就停止运行服务了。解决办法就是把队列长度调小,最后队列长度调整为2万个元素,再上线,貔貅很快跑到了队尾,达到了平衡点,内存就不再增加了。

4. 总结

其实,本来就没有内存泄漏,这就是伪内存泄漏。一直不敢上线的服务终于可以正式上线了。

尝试自媒体

我一直想自己设计一个无锁的多生产者、多消费者队列。无锁编程涉及很多缓存和内存屏障的知识,不是一件容易的事情,没有技术功底是不行的。所以我查了很多资料,深入学习了很多理论知识,弄清楚每个细节,刨根问底,追本溯源,从CAS、内存屏障、缓存一致性协议,再到缓存原理,甚至底层硬件,终于弄明白了无锁编程的底层实现。然后把学习到的知识汇总整理成了《CPU缓存一致性:从理论到实战》——这是我在网上发布的第一篇技术文章。

01 公众号&知乎

我将这篇文章发布到公众号和知乎上后,获得了非常多的点赞,我也收获了很多粉丝。就这样,我发现了自己的另一面—除了可以自己写代码,还可以教别人写代码。

读者的认可激发了我想要创作更多的技术文章的热情。所以我后来又写了网络、TCP、UDP、端口、分布式和相关工作经历等的文章。 

我深知仅有一颗上进的心是不够的,还是得有人教和有人带才能成长得更快,仅靠自己的摸索很难走出困境。虽然码龄一年一年地增长,但是层次可能一直停留在初级阶段而不自知。

02 程序员进阶之路

在我工作满十周年之际,借此契机,我将这些文章整理为“十年码农内功”系列,例如其中的“十年码农内功:缓存”。后面我又写了网络收发包详细过程和内存等文章,填补了“十年码农内功”系列的最后几块拼图。再后来有几个出版社联系了我,想要将“十年码农内功”系列文章出版为图书,最后我选择了最早与我联系的电子工业出版社。将网络文章变成正式的出版物可不是一件容易的事情,在此期间我做了大量的修改工作来完善本书的内容,最终出版《程序员进阶之路:缓存、网络、内存与案例》

d2b5ca33bd20240816105854

本书内容涉及大量的代码和Linux命令,希望读者自己运行其中的代码和相关的命令,达到学以致用、有的放矢的目的。

本书特色

计算机技术发展飞快、日新月异,很多面试“八股文”可能早已过时。本书基于Linux 6.0及以上版本来讲解书中涉及的各个模块,有助于读者理解现代Linux内核,掌握实用的技术知识。

网上技术文章的质量参差不齐并且不成体系,还有存在错误的情况,就连ChatGPT给出的答案也会有错误,导致我们的学习成本比较高。本书详细地介绍了计算机系统中的核心知识,可以有效降低我们的学习成本。

作为工作十余年的技术老兵,我深刻体会到写五年业务逻辑和写十年业务逻辑没有太大的差异,只不过是对业务的熟练度有差异罢了。本书可以帮助那些想要摘掉“CRUD(增删改查)Boy”标签的程序员掌握技术的底层原理。

本书涉及的内容偏底层并且比较“硬核”,更适合有一定基础的学生和程序员阅读,从而进阶为更高级的软件开发工程师,打破“天花板”。

全书共有150多张示例图,图文并茂,有助于读者更容易理解本书的内容。同时还提供了大量的实战和测试代码,不仅有助于读者理解理论,还可以对理论进行练习和验证,达到学以致用和有的放矢的目的。

本书结构

第1章首先介绍存储体系结构、缓存原理、缓存一致性协议、内存屏障、CAS原理和原子操作等理论知识,然后介绍如何运用这些理论知识实现一个高性能无锁多生产者、多消费者队列,该队列在单生产者、单消费者场景下可以达到600万QPS。

第2章介绍网络接口层、网络层、套接字编程和虚拟网卡等内容,还介绍了一些网络工具,例如tcpdump和ethtool,以及如何使用这些工具进行网络分析和调优。

第3章介绍TCP的协议体、有限状态机、建立/关闭连接的不同阶段,以及流量控制(滑动窗口)和拥塞控制(CUBIC算法)。

第4章首先介绍UDP的协议体、特点和应用场景,以及介绍保障可靠传输的两种机制:ACK(消息确认)和FEC(前向纠错)。

第5章首先通过几个问题引出地址/端口复用的结论。然后介绍地址复用和端口复用的应用场景。最后介绍了TCP和UDP本来就可以同时绑定同一个端口。

第6章首先介绍网络在收发数据包之前的准备工作,包括网卡驱动的加载、初始化和网卡的启动。然后依据Linux 6.0内核源码详细介绍数据包从网卡硬件、网络接口层、网络层、传输层(UDP)、套接字层再到应用层的整个收包流程。

第7章依据Linux 6.0内核源码详细介绍数据包从应用层、套接字层、传输层(UDP)、网络层、邻居子系统、网络接口层再到网卡硬件的整个发包流程。

第8章首先通过Linux 6.8内核源码中的物理内存节点、物理内存区域和物理内存页及实际的物理内存空间布局来介绍物理内存。然后通过Linux 6.8内核源码中的虚拟内存空间和虚拟内存区域,以及虚拟内存的布局和虚拟内存的申请来介绍虚拟内存。最后通过Linux 6.8内核源码中的正向与反向映射来介绍物理内存与虚拟内存之间的互为映射关系。

第9章主要分享我在实际工作中遇到的两个难题,以及难题是如何解决的。通过这两个案例可以看到具体的解题方法,给读者在解决实际工作中遇到的问题时提供借鉴和启发。

社区赠书

本次福利将送出:《程序员进阶之路:缓存、网络、内存与案例》 * 5 本,点击链接也可以5折购买

截止时间还需要支付积分,就还有赠额度,先到先得

领取方式

  1. 支付积分即可兑换,复制“ 积分支付订单号 ”
  2. 在本站中私信我这些内容:积分支付订单号、活动书名、快递信息(姓名、电话、地址)

没有积分怎么办?

社区鼓励高质量的技术交流,所以只要发布有价值内容均可获得积分!

哪些内容算高质量?

1. 高质量的技术分享,原创、翻译、转载均可

2. 分享优质Java与 Spring 的相关开源项目、开发工具、日常使用的效率工具

另外,内容不错,获得精华贴、或者热门帖还有额外加分哦

THE END
喜欢就支持一下吧
点赞7 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容