你好,我是华仔。今天这期“特别放送”,我想和你聊聊如何高效地学习开源项目,一方面澄清开源项目学习过程当中的几个误区,另外一方面谈谈我本身具体实践时的一套方法论。html
得益于开源运动的蓬勃发展,众多技术顶尖的公司、团队或者我的经过开源的方式向技术社区贡献了许多优秀的开源项目,一方面大大促进了总体技术的发展,另外一方面大大减轻了中小公司和团队在技术方面的投入压力,让团队可以更加聚焦于业务。前端
开源项目对团队和业务有很大好处,但对于技术人员来讲,若是只是简单的采起“拿来主义”,那就变成一个陷阱:看似很快的用开源项目实现了需求,但本身的技术水平并无什么提高;甚至可能出现看起来用了不少开源项目,知道不少项目名称,但技术水平止步不前的窘境。redis
所以,对于开源项目,不能简单的采起“拿来主义”,而要比较深刻的去学习开源项目,作到“知其然,知其因此然”,一方面是为了更好地应用这些开源项目,另外一方面也是为了经过学习优秀的开源项目来提高本身的能力。算法
不少技术同窗确实也想深刻学习一些业界成熟和优秀的开源项目,例如 Nginx、Redis、Netty 等,可是在具体实践的时候,经常由于一些不正确的观点而误入歧途,例如:数据库
只有开发这些开源项目的人才能真正理解,我无法参与这个项目开发,所以我很难深刻理解。编程
个人项目没有用 Redis,不用的话很难深刻理解。服务器
数据结构和算法很重要,因此我只要研究其数据结构和算法就够了,例如 Nginx 用的红黑树。网络
“Talk is cheap, show me the code”,一头扎进源码逐行阅读。数据结构
这些观点要么让本身望而生畏从而轻易放弃,要么让本身浪费大量时间而没有多大收获。那究竟要怎样作才是正确的呢?下面我结合本身的经验谈谈我对如何学习开源项目的见解。多线程
例如,要理解 Redis 的网络模型,咱们不须要成为 Redis 的开发者,也不须要必定要用到 Redis,只要具有必定的网络编程基础,再经过阅读 Redis 的源码,均可以学习 Redis 这种单进程的 Reactor 模型。
例如,Nginx 使用红黑树来管理定时器,对于绝大部分人来讲,只要知道这点就够了,并不须要去研究 Nginx 实现红黑树的源码是如何写的,除非你须要修改这部分,但我认为极少人会有这个需求。
不要一上来就去看源码,而是要基本掌握了功能、原理、关键设计以后再去看源码,看源码的主要目的是为了学习其代码的写做方式,以及关键技术的实现。
例如,Redis 的 RDB 持久化模式“会将当前内存中的数据库快照保存到磁盘文件中”,那这里所谓的“数据库快照”究竟是怎么作的呢?在 Linux 平台上其实就是 fork 一个子进程来保存就能够了;那为什么 fork 子进程就生成了数据库快照了呢?这又和 Linux 的父子进程机制以及 copy-on-write 技术相关了。
经过这种方式,既可以快速掌握系统设计的关键点(Redis 的 RDB 模式),又可以掌握具体的编程技巧(内存快照)。
接下来我详细谈谈“自顶向下”的学习方法和步骤。
不少人看到“安装”这个步骤均可能会以为有点不觉得然:“不就是对照手册执行一下命令么,没什么技术含量,用的时候装一下就能够了”。事实上,安装步骤远远不止这么简单,经过具体的安装过程,你能够获取到以下一些关键信息:
以 Nginx 为例,源码安装 Nginx 依赖的库有 pcre、pcre-devel、openssl、openssl-devel、zlib,光从名字上看都可以了解一些信息,例如 openssl 可能和 https 有关,zlib 可能和压缩有关。
再以 Memcache 为例,最大的依赖就是 libevent,而根据 libevent 是一个高性能的网络库,咱们就能大概推测 Memcache 的网络实现应该是 Reactor 模型的。
例如,Nginx 安装完成后,目录以下:
这个目录提供的信息有:conf 是存放配置文件的,logs 是存放日志的,sbin 是运行程序,可是 html 是什么呢?这个疑问会促使你继续去研究和学习。
再来看看 Redis,安装完成后,目录下只有一个 bin 目录,具体以下:
我相信大部分人看到这目录都会感到有点惊讶:这也太简单了吧,尤为是与 Nginx 相比!所以也会天然而然的有一些疑问,例如 Redis 如何配置?Redis 日志保存在哪里?这些疑问一样会促使你继续去研究和学习,带着问题去学习效率是最高的。
一样以 Redis 为例,你能够看到 redis-benchmark、redis-check-aof 等程序,从名字可以大概猜出这些工具的基本使用场景,而这些工具在后面故障定位和处理、性能测试等场景可能很是方便。
安装完成后,咱们须要真正将系统运行起来,运行系统的时候有两个地方要特别关注:命令行和配置文件,它们主要提供了两个很是关键的信息:系统具有哪些能力和系统将会如何运行。这些信息是咱们窥视系统内部运行机制和原理的一扇窗口。
例如,下面是 Memcache 的启动参数一部分:
经过这几个启动参数,你能够获取以下一些信息:
Memcache 支持 UNIX socket 通讯和 TCP 通讯。
Memcache 能够指定内存大小。
lock memory 看起来和内存有关,但具体是什么意思?配置和不配置有什么区别么?
一般状况下,若是咱们将每一个命令行参数和配置项的做用和原理都所有掌握清楚了的话,基本上对系统已经很熟悉了。个人一个习惯是无论三七二十一,先把全部的配置项所有研究一遍,包括配置项的原理、做用、影响,而且尝试去修改配置项而后看看系统会有什么变化。例如,将 Memcache 的“--conn-limit”改成 1 后,查看多个链接请求时 Memecache 会返回什么错误、记录什么日志等。
完成前两个步骤后,咱们对系统已经有了初步的感受和理解,此时能够更进一步去研究其原理。其实在研究命令行和配置项的时候已经涉及一部分原理了,可是还不系统,所以咱们要专门针对原理进行系统性的研究。这里的关键就是“系统性”三个字,怎么才算系统性呢?主要体如今以下几个方面:
每一个流行的开源项目之因此可以受到大众的欢迎,确定是有一些卖点的,常见的有高性能、高可用、可扩展等特性,那到底这些项目是如何作到其所宣称的那么牛的呢?这些牛 X 的技术实现就是咱们要学习的地方。
例如,Memcache 的高性能具体是怎么作到的呢?首先是基于 libevent 实现了高性能的网络模型,其次是内存管理 Slab Allocator 机制。为了完全理解 Memcache 的高性能网络模型,咱们须要掌握不少知识:多路复用、Linux epoll、Reactor 模型、多线程等,经过研究 Memcache 的高性能网络模型,咱们可以学习一个具体的项目中如何将这些东西所有串起来实现了高性能。
再以 React 为例,Virtual DOM 的实现原理是什么、为什么要实现 Virtual DOM、React 是如何构建 Virtual DOM 树、Virtual DOM 与 DOM 什么关系等,经过研究学习 Virtual DOM,即便不使用 React,咱们也可以学习如何写出高性能的前端的代码。
这是我想特别强调的一点,只有清楚掌握技术方案的优缺点后才算真正的掌握这门技术,也只有掌握了技术方案的优缺点后才能在架构设计的时候作出合理的选择。
优缺点主要经过对比来分析,即:咱们将两个相似的系统进行对比,看看它们的实现差别,以及不一样的实现优缺点都是什么。
典型的对比有 Memcache 和 Redis,例如(仅举例说明,实际上对比的点不少),Memcache 用多线程,Redis 用单进程,各有什么优缺点?Memcache 和 Redis 的集群方式,各有什么优缺点?
即便是 Redis 自身,咱们也能够对比 RDB 和 AOF 两种模式的优缺点。
在你了解了什么是“系统性”后,我来介绍一下原理研究的手段,主要有三种:
通读项目的设计文档:例如 Kafka 的设计文档,基本涵盖了消息队列设计的关键决策部分;Disruptor 的设计白皮书,详细的阐述了 Java 单机高性能的设计技巧。
阅读网上已有的分析文档:一般状况下比较热门的开源项目,都已经有很是多的分析文档了,咱们能够站在前人的基础上,避免大量的重复投入。但须要注意的是,因为经验、水平、关注点等差别,不一样的人分析的结论可能有差别,甚至有的是错误的,所以不能彻底参照。一个比较好的方式就是多方对照,也就是说看不少篇分析文档,比较它们的内容共同点和差别点。
Demo 验证:若是有些技术点难以查到资料,本身又不肯定,则能够真正去写 Demo 进行验证,经过打印一些日志或者调试,能清晰的理解具体的细节。例如,写一个简单的分配内存程序,而后经过日志和命令行(jmap、jstat、jstack 等)来查看 Java 虚拟机垃圾回收时的具体表现。
一般状况下,若是你真的准备在实际项目中使用某个开源项目的话,必须进行测试。有的同窗可能会说,网上的分析和测试文档不少,直接找一篇看就能够了?若是只是本身学习和研究,这样作是能够的,由于构建完整的测试用例既须要耗费较多时间,又须要较多机器资源,若是每一个项目都这么作的话,投入成本有点大;但若是是要在实践项目中使用,必须本身进行测试,由于网上搜的测试结果,不必定与本身的业务场景很契合,若是简单参考别人的测试结果,极可能会得出错误的结论。例如,开源系统的版本不一样,测试结果可能差别较大。一样是 K-V 存储,别人测试的 value 是 128 字节,而你的场景 value 都达到了 128k 字节,二者的测试结果也差别很大,不能简单照搬。
测试阶段须要特别强调的一点就是:测试必定要在原理研究以后作,不能安装完成立马就测试!缘由在于若是对系统不熟悉,极可能出现命令行、配置参数没用对,或者运行模式选择不对,致使没有根据业务的特色搭建正确的环境、没有设计合理的测试用例,从而使得最终的测试结果得出了错误结论,误导了设计决策。曾经有团队安装完成 MySQL 5.1 后就进行性能测试,测试结果出来让人大跌眼镜,通过定位才发现 innodb_buffer_pool_size 使用的是默认值 8M。
源码研究的主要目的是学习原理背后的具体编码如何实现,经过学习这些技巧来提高咱们本身的技术能力。例如 Redis 的 RDB 快照、Nginx 的多 Reactor 模型、Disruptor 如何使用 volatile 以及 CAS 来作无锁设计、Netty 的 Zero-Copy 等,这些技巧都很精巧,掌握后可以大大提高本身的编码能力。
一般状况下,不建议通读全部源码,由于想掌握每行代码的含义和做用仍是很是耗费时间的,尤为是 MySQL、Nginx 这种规模的项目,即便是他们的开发人员,都不必定每一个人都掌握了全部代码。带着明确目的去研究源码,作到有的放矢,才能事半功倍,这也是源码研究要放在最后的缘由。
对于一些基础库,除了阅读源码外,还能够本身写个 Demo 调用基础库完成一些简单的功能,而后经过调试来看具体的调用栈,经过调用栈来理解基础库的处理逻辑和过程,这比单纯看代码去理解逻辑要高效一些。例如,下面是 Netty 4.1 版本的 telnet 服务器样例调试的堆栈,经过堆栈咱们能够看到完整的调用栈:
前面介绍的“自顶向下”5 个步骤,完整执行下来须要花费较长时间,而时间又是大部分技术人员比较稀缺的资源。不少人在学习技术的时候都会反馈说时间不够,版本进度很紧,很难有大量的时间进行学习,但若是不学习感受本身又很难提高?面对这种两难问题,具体该如何作呢?
一般状况下,以上 5 个步骤的前 3 个步骤,无论是已经成为架构师的技术人员,仍是立志成为架构师的技术人员,在研究开源项目的时候都必不可少;第四步能够在准备采用开源项目的时候才实施,第五步能够根据你的时间来进行灵活安排。这里的“灵活安排”不是说省略不去作,而是在本身有必定时间和精力的时候作,由于只有这样才能真正理解和学到具体的技术。
若是感受本身时间和精力不够,与其走马观花每一个开源项目都去简单了解一下,还不如集中精力将一个开源项目研究通透,就算是每一个季度只学习一个开源项目,积累几年后这个数量也是很客观的;并且一旦你将一个项目研究透之后,再去研究其余相似项目,你会发现本身学习的很是快,由于共性的部分你已经都掌握了,只须要掌握新项目差别的部分便可。