You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

75 lines
9.9 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 13 | 热点问题答疑1如何学习源码
不知道你有没有留意到,不少高端开发岗位在招聘要求里往往会写这么一条:研究过框架和中间件源码的优先考虑。这是因为一切秘密都藏在源码之中,阅读源码会让我们对框架或者中间件的理解更加深刻。有时候即使你阅读了大量原理性的文档,但如果不看源码,可能仍然会觉得还没有理解透。另外如果你能深入源码,招聘者从侧面也能感觉到你的学习热情和探索精神。
今天我们就来聊聊源码学习这个话题。对于Java后端开发来说有不少经典的开源框架和中间件下面我帮你按照后端的分层架构整理出来供你参考。
* **服务接入层**反向代理NginxAPI网关Node.js。
* **业务逻辑层**Web容器Tomcat、Jetty应用层框架Spring、Spring MVC和Spring BootORM框架MyBatis
* **数据缓存层**内存数据库Redis消息中间件Kafka。
* **数据存储层**关系型数据库MySQL非关系型数据库MongoDB文件存储HDFS搜索分析引擎Elasticsearch。
这其中每一层都要支持水平扩展和高可用比如业务层普遍采用微服务架构微服务之间需要互相调用于是就出现了RPC框架Spring Cloud和Dubbo。
除此之外还有两个非常重要的基础组件Netty和ZooKeeper其中Netty用于网络通信ZooKeeper用于分布式协调。其实很多中间件都用到了这两个基础组件并且ZooKeeper的网络通信模块也是通过Netty来实现的。
而这些框架或者中间件并不是凭空产生的,它们是在互联网的演化过程中,为了解决各种具体业务的痛点,一点一点积累进化而来的。很多时候我们把这些“零件”按照成熟的模式组装在一起,就能搭建出一个互联网后台系统。一般来说大厂都会对这些框架或者中间件进行改造,或者完全靠自己来实现。这就对后台程序员提出了更高的要求。
那这么多中间件和框架从哪里入手呢先学哪个后学哪个呢我觉得可以先学一些你熟悉的或者相对来说比较简单的树立起信心后再学复杂的。比如可以先学Tomcat、Jetty和Spring核心容器弄懂了这些以后再扩展到Spring的其他组件。
在这个过程中,我们就会积累一些通用的技术,比如网络编程、多线程、反射和类加载技术等,这些通用的技术在不少中间件和框架中会用到。
先说**网络通信**在分布式环境下信息要在各个实体之间流动到处都是网络通信的场景比如浏览器要将HTTP请求发给Web容器一个微服务要调用另一个微服务Web应用读写缓存服务器、消息队列或者数据库等都需要网络通信。
尽管网络通信的场景很多,但无外乎都要考虑这么几个问题:
* I/O模型同步还是异步是阻塞还是非阻塞
* 通信协议是二进制gRPC还是文本HTTP
* 数据怎么序列化是JSON还是Protocol Buffer
此外服务端的线程模型也是一个重点。我们知道多线程可以把要做的事情“并行化”提高并发度和吞吐量但是线程可能会阻塞一旦阻塞线程资源就闲置了并且会有线程上下文切换的开销浪费CPU资源。而有些任务执行会发生阻塞有些则不会阻塞因此线程模型就是要决定哪几件事情放到一个线程来做哪几件事情放到另一个线程来做并设置合理的线程数量目的就是要让CPU忙起来并且不是白忙活也就是不做无用功。
我们知道服务端处理一个网络连接的过程是:
accept、select、read、decode、process、encode、send。
一般来说服务端程序有几个角色Acceptor、Selector和Processor。
* Acceptor负责接收新连接也就是accept
* Selector负责检测连接上的I/O事件也就是select
* Processor负责数据读写、编解码和业务处理也就是read、decode、process、encode、send。
Acceptor在接收连接时可能会阻塞为了不耽误其他工作一般跑在单独的线程里而Selector在侦测I/O事件时也可能阻塞但是它一次可以检测多个Channel连接其实就是用阻塞它一个来换取大量业务线程的不阻塞那Selector检测I/O事件到了是用同一个线程来执行Processor还是另一个线程来执行呢不同的场景又有相应的策略。
比如Netty通过EventLoop将Selector和Processor跑在同一个线程。一个EventLoop绑定了一个线程并且持有一个Selector。而Processor的处理过程被封装成一个个任务一个EventLoop负责处理多个Channel上的所有任务而一个Channel只能由一个EventLoop来处理这就保证了任务执行的线程安全并且用同一个线程来侦测I/O事件和读写数据可以充分利用CPU缓存。我们通过一张图来理解一下
![](https://static001.geekbang.org/resource/image/f6/f4/f6741dc09985d5e08934ad77cd8c96f4.png)
请你注意这要求Processor中的任务能在短时间完成否则会阻塞这个EventLoop上其他Channel的处理。因此在Netty中可以设置业务处理和I/O处理的时间比率超过这个比率则将任务扔到专门的业务线程池来执行这一点跟Jetty的EatWhatYouKill线程策略有异曲同工之妙。
而Kafka把Selector和Processor跑在不同的线程里因为Kafka的业务逻辑大多涉及与磁盘读写处理时间不确定所以Kafka有专门的业务处理线程池来运行Processor。与此类似Tomcat也采用了这样的策略同样我们还是通过一张图来理解一下。
![](https://static001.geekbang.org/resource/image/74/e4/74f7742142740ae55bcda997a73a37e4.png)
我们再来看看**Java反射机制**几乎所有的框架都用到了反射和类加载技术这是为了保证框架的通用性需要根据配置文件在运行时加载不同的类并调用其方法。比如Web容器Tomcat和Jetty通过反射来加载Servlet、Filter和Listener而Spring的两大核心功能IOC和AOP都用到了反射技术再比如MyBatis将数据从数据库读出后也是通过反射机制来创建Java对象并设置对象的值。
因此你会发现通过学习一个中间件熟悉了这些通用的技术以后再学习其他的中间件或者框架就容易多了。比如学透了Tomcat的I/O线程模型以及高并发高性能设计思路再学Netty的源码就轻车熟路了Tomcat的组件化设计和类加载机制理解透彻了再学Spring容器的源码就会轻松很多。
接下来我再来聊聊具体如何学习源码,有很多同学在专栏里问这个问题,我在专栏的留言中也提到过,但我觉得有必要展开详细讲讲我是如何学习源码的。
学习的第一步首先我们要弄清楚中间件的核心功能是什么我以专栏所讲的Tomcat为例。Tomcat的核心功能是HTTP服务器和Servlet容器因此就抓住请求处理这条线通过什么样的方式接收连接接收到连接后以什么样的方式来读取数据读到数据后怎么解析数据HTTP协议请求数据解析出来后怎么调用Servlet容器Servlet容器又怎么调到Spring中的业务代码。
为了完成这些功能Tomcat中有一些起骨架作用的核心类其他类都是在这个骨架上进行扩展或补充细节来实现。因此在学习前期就要紧紧抓住这些类先不要深入到其他细节你可以先画出一张骨架类图。
![](https://static001.geekbang.org/resource/image/12/9b/12ad9ddc3ff73e0aacf2276bcfafae9b.png)
在此之后我们还需要将源码跑起来打打断点看看变量的值和调用栈。我建议用内嵌式的方式来启动和调试Tomcat体会一下Spring Boot是如何使用Tomcat的这里有[示例源码](https://github.com/heroku/devcenter-embedded-tomcat)。在源码阅读过程中要充分利用IDE的功能比如通过快捷键查找某个接口的所有实现类、查找某个类或者函数在哪些地方被用到。
我们还要带着问题去学习源码比如你想弄清楚Tomcat如何启停、类加载器是如何设计的、Spring Boot是如何启动Tomcat的、Jetty是如何通过Handler链实现高度定制化的如果要你来设计这些功能会怎么做呢带着这些问题去分析相关的源码效率会更高同时你在寻找答案的过程中也会碰到更多问题等你把这些问题都弄清楚了你获得的不仅仅是知识更重要的是你会树立起攻克难关的信心。同时我还建议在你弄清楚一些细节后要及时记录下来画画流程图或者类图再加上一些关键备注以防遗忘。
当然在这个过程中,你还可以看看产品的官方文档,熟悉一下大概的设计思路。在遇到难题时,你还可以看看网上的博客,参考一下别人的分析。但最终还是需要你自己去实践和摸索,因为网上的分析也不一定对,只有你自己看了源码后才能真正理解它,印象才更加深刻。
今天说了这么多,就是想告诉你如果理解透彻一两个中间件,有了一定的积累,这时再来学一个新的系统,往往你只需要瞧上几眼,就能明白它所用的架构,而且你会自然联想到系统存在哪些角色,以及角色之间的关系,包括静态的依赖关系和动态的协作关系,甚至你会不由自主带着审视的眼光,来发现一些可以改进的地方。如果你现在就是这样的状态,那么恭喜你,你的技术水平已经成长到一个新的层面了。
不知道今天的内容你消化得如何?如果还有疑问,请大胆的在留言区提问,也欢迎你把你的课后思考和心得记录下来,与我和其他同学一起讨论。如果你觉得今天有所收获,欢迎你把它分享给你的朋友。