Skip to content

kwseeker/framework-src-guide

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

90 Commits
 
 
 
 
 
 

Repository files navigation

后端技术栈框架源码分析

这里主要分析Java和Go后端技术栈常用框架的源码,从整体到局部探究内部工作原理,并输出可视化流程图。

源码分析输出主要为drawio流程图(.drawio文件)和 Markdown文本,以流程图为主(主要展示框架主流程),Markdown作为补充,详细内容参考docs

这里的流程图不是常规流程图,实际是借鉴的时序图的编排方式,另外还添加了简单的UML图。

后面列举的框架并非都分析过源码(心有余而力不足),有些只是计划,已经分析过源码的都有流程图链接。

有些分析流程图之前记录在其他仓库后续会转移到这里,慢慢补充吧

分类

  • 语言

    • Java

      • SDK
      • 网关
      • 服务调用
      • 负载均衡
      • 消息队列
      • 作业调度
      • 服务器
      • Web框架
      • 微服务框架
      • 注册中心/配置中心
      • 分布式协调
      • 服务保障
      • 链路追踪
      • 监控系统
      • 安全框架
      • ORM框架
      • 数据库相关
      • 分布式事务
      • 搜索引擎
      • 规则引擎
      • 工作流引擎
      • 缓存
      • 分布式
      • 命令行
      • 日志
      • 序列化
      • 工具类
      • 语法解析器
    • Go

      • SDK
      • 注册中心/配置中心
      • 数据库相关
  • Resources

Java

SDK

JDK 一些类的源码少于2000行的一般没必要画流程图,数据结构和逻辑不画图也能梳理清楚,时间久了忘记了重新看也花不了多长时间。

网关

  • Spring Cloud Gateway

    源码流程图:

    原理简述:

    本质是一个 WebFlux 应用,内部处理逻辑和 Spring MVC 也有点类似,主要也是定义一组请求处理映射(用于定义路由处理,路由包括一组断言和过滤器),通过断言进行请求与路由的匹配,通过路由的过滤器对请求进行处理(比如:修改路径、进行转发等)。

  • Zuul

服务调用

负载均衡

消息队列

作业调度

服务器

  • Jetty

  • Netty

    源码流程图:

  • Reactor-Netty

    基于 Project Reactor 对 Netty 的响应式封装。

    源码流程图:

  • Tomcat

    之前看过源码但是没有画图时间久了就细节全忘记了,TODO 有空重看下并画下图。

    原理简述: 1、创建一个 Acceptor 线程来接收用户连接,接收到之后扔到 events queue 队列里面,默认情况下只有一个线程来接收; 2、创建 Poller 线程,数量 <= 2;Poller 对象是 NIO 的核心,在Poller中,维护了一个 Selector 对象;当 Poller 从队列中取出 Socket 后,注册到该 Selector 中;然后通过遍历 Selector,找出其中可读的 Socket,然后扔到线程池中处理相应请求,这就是典型的NIO多路复用模型。 3、扔到线程池中的 SocketProcessorBase 处理请求。

    不过 Tomcat 虽然使用了 NIO 模型,只是优化了请求处理流程中部分操作由阻塞转成了非阻塞,比如 “读请求头”、“等待下一个请求”、“SSL握手”;“读请求体”、“写响应头”、“写响应体”依然是阻塞的(有种说法是需要遵循传统的接口规范以致于无法对所有操作进行非阻塞改写)。

    参考:Connector Comparsion

Web框架

  • Spring

    • 基于注解的应用上下文

      这里主要分析 IOC 和 Spring Bean 生命周期。

      源码流程图:

    • 切面

      • Spring CgLib and ASM

        Spring 没有直接引入CgLib和ASM的依赖,而是将部分源码直接迁移到了Spring核心,细节还是挺多的,比较枯燥,流程图省略了很多细节。

      • AOP

        参考声明式事务源码分析。

    • 事务

      源码流程图:

      注意:

      如果有两个操作 a(),b(),a() 调用 b(),a() 在事务中执行,b() 方法无论是新建事务执行还是普通执行,如果产生异常没有被捕获,都会被 a() 所在事务捕获到,进而导致 a() 所在事务也回滚

      正确的处理方式:b() 中应该捕获异常,然后通过是否设置 rollbackOnly 标志决定是否让 a() 所在事务也回滚。

      事务传播个人认为只是强调多个操作是纳入一个事务管理还是多个事务分开管理,而不是说互不干扰

      初学者容易认为 REQUIRED_NEW 新建事务后,即使抛出异常也不会影响之前的事务,这是不对的(对可能存在外部事务的传播类型都做了测试无一例外);原因无论新建事务执行还是普通执行,抛出的异常只要不处理都会最终抛到外部事务中(Spring事务源码发现异常即使捕获也会再次抛出)。

    • 测试

    • 其他

      分析IOC时有涉及但是不够详细,所以这里重新绘制单独的流程图。

  • Spring MVC

    这里只是分析请求处理流程,Servlet容器初始化流程看Spring Boot的部分(XML配置方式已经不流行了),初始化流程应该也可以参考WebFlux流程图估计差别不大。

    源码流程图:

    原理简述:

    本质是向Servlet容器(比如Tomcat)注册了一个名为 DispatcherServlet 的Servlet,通过这个类处理HTTP请求。

    1. Tomcat 经过一系列处理 Connector -> Processor -> Valve -> Filter链 -> DispatcherServlet,即最终将请求交给DispatcherServlet 处理;

      在 Spring MVC中注册的过滤器在会添加到Filter链。

    2. DispatcherServlet先从请求处理器映射中根据请求方法类型、参数、请求头、请求与响应类型等信息匹配请求处理器;

    3. 然后装配上与请求匹配的拦截器链,生成 HandlerExecutionChain,然后根据请求处理器定义方式获取合适的请求处理适配器,常用的是 RequestMappingHandlerAdapter;

      不同的Controller定义方式处理方式不同,现在常用的定义方式是通过 @RequestMapping, 对应的请求处理器适配器是 RequestMappingHandlerAdapter。

    4. 处理请求前先遍历执行所有匹配的拦截器的 preHandle();

    5. 通过请求处理器适配器调用请求处理方法,处理流程主要包括:方法参数解析(包括请求消息转换成方法参数类型)、反射调用

      Controller业务方法、方法返回数据类型转成响应消息类型,最终通过 HttpResponse 写响应状态、响应主体;

    6. 遍历执行所有匹配的拦截器的 postHandle();

    像 Model View 在前后端分离的项目基本不会使用,暂略。

  • Spring Boot

    之前看源码输出到了Markdown文件,但是内容多了Markdown可读性很差,待补充原理图。

  • Spring WebFlux

    源码流程图:

    原理简述:

    和 SpringMVC 处理流程基本一致,只不过 WebFlux 默认使用 Reactor-Netty 作为Web容器,接口都基于 ProjectReactor 封装成了响应式接口,支持 HTTP 协议 和 自定义协议通信。

    与 SpringMVC 的 DispatcherServlet 对应有一个 DispatcherHandler 类,这个类定义了请求处理的主要流程。

    前面说 Tomcat NIO 模式下请求处理的部分操作依然是阻塞的,只要有操作是阻塞的就会白占线程,降低系统吞吐量,而通过多创建线程提升系统吞吐量则又会引入线程上下文切换的开销。

响应式

响应式编程框架有点类似工具类框架没有主线或者说只有各个接口类实现的独立的主线。

  • Reactor

    源码流程图:

    要求:

    所有操作都不阻塞,项目中要用响应式,需要将项目中所有组件的阻塞操作都进行异步化改造,这样才能实现更少的线程更大的吞吐量(同时更少的线程也能减少线程上下文切换)。

    只要项目中混入了任何阻塞式操作的组件,都会让对应调用链的性能大打折扣,因此使用响应式不是引入Reactor 、WebFlux 这些响应式框架就行了,还需要整个项目生态的组件全部做异步化改造,这也是主要导致 WebFlux 久久无法火起来的原因(其他原因响应式规范不符合人类思维习惯、调试困难[函数式接口、被拆散的流程])。

    不过现在JDK21也开始支持虚拟线程了,类似Go协程的概念,从语言层面作出了优化,感觉这东西在后端更难流行起来了。

    说所有操作都不阻塞感觉有点不是很严谨,像 Mono.delay() 其实相当于将阻塞操作从本线程中移到了ScheduledThreadPoolExecutor的工作者线程,即工作者线程中为了实现延迟还是有阻塞的,不过多个 Mono.delay() 调用可以复用工作者线程统一处理阻塞操作;有点像 Reactor 模式线程复用的思想。

  • RxJava

微服务框架

  • Helidon

  • Micronaut

  • Quarkus

  • Spring Cloud

注册中心/配置中心

分布式协调

  • Zookeeper

服务保障

链路追踪

监控系统

安全框架

ORM框架

  • Mybatis

    • 主流程

      源码流程图:

      也是好久之前画的图,UML不详细,SQL前后置处理以及连接池部分还有细节逻辑没有梳理,TODO 重画。

    • Mybatis-Spring

  • Mybatis Plus

数据库相关

  • Canal

    之前梳理了个半成品,TODO 重画。

  • Sharding-JDBC

    TODO 流程图迁移。

  • 连接池

    • Druid
    • HikariCP

分布式事务

搜索引擎

  • ElasticSearch

规则引擎

  • Drools

工作流引擎

  • Activiti
  • Spring Flowable

缓存

  • Caffeine
  • Ehcache

分布式

  • 一致性协议

    • Raft

      说实话想自己按 Raft 论文写一个实现也不是个简单的事,关键是需要将论文中的所有细节理解清楚,里面有一些证明能否理解透彻,即便写出来,怎么测试实现的没有漏洞也是个问题。

      • BRaft

        Raft协议的C++实现。

      • JRaft

        看 Nacos 当前最新版本(2.3.2)中依赖的是 SOFAJRaft,简称JRaft,是参考 BRaft 的 Java 实现。

      • Raft4J

      • Raft-Java

        这个只是DEMO性质的实现,主要是代码实现简单(5K多行,其他生产级别的实现都是几W行),适合快速学习Raft协议细节。

        源码流程图:

命令行

  • JCommander
  • CLI

日志

  • Slf4j

  • Log4j2

  • Logback

    虽然 logback-core、logback-classic 代码量加起来有 6W 行,但是核心流程比较简单,且数据结构很清晰,直接看源码可能比看官方文档和书能更快地理解 Logback 的工作原理和使用细节。

    源码流程图:

序列化

  • Fastjson

    基于FastJson2源码分析,从源码实现上看主体逻辑其实比较简单,之所以源码代码量很高更多地是因为编程语言语法的复杂性(需要适配各种各样的类型和语法,而且要用ASM定义ObectWriter动态类,调试时可以发现有很多if判断,还有一些对特定类型的定制实现,但是实际的类型可能只会用到其中少部分处理逻辑)。

    序列化主体逻辑可以分为3部分(以 ObjectWriterCreatorASM 为例): 1)通过Class解析一个类型需要序列化的部分(比如公共基本类型字段、有实现 Getter 方法的私有基本类型字段、对象类型字段、继承的字段等等),针对每个字段会创建一个 FieldWriter 实现对这个字段的序列化;

    2)为此类型通过 ASM 字节码动态生成一个专属的 ObjectWriter 类进而加载并实例化,用于专门实现对这个类型对象的序列化写操作,通过这种方式避免了反射的低效性,而且 Fastjson 会缓存这些动态生成的 ObjectWriter 类,后续再序列化会很快;

    3)使用 ObjectWriter 对每个字段进行序列化并追加到最终的序列化结果。

    源码流程图:

  • Hessian

  • Jackson

  • Kryo

  • Protostuff

工具类

语法解析器

  • Antlr

    Sharding-JDBC 中 借助 Antlr 实现对 SQL 语句的重构(原SQL -> AST -> 新SQL),将 SQL 转换成针对某个分表的 SQL。

Go

SDK

注册中心/配置中心

  • Consul
  • Etcd

数据库相关

微服务

  • Istio

  • K8S

分布式事务

  • DTM

Rust

  • 异步
    • Tokio

说明

读框架源码的初衷

  • 解决生产环境BUG

    不熟悉源码原理,可能即使定位到问题所在代码行也不知道是什么原因应该怎么改。

  • 从0到1构建项目时,定位导致产生不符合预期结果的原因

    这个碰到太多了,通过Google、官方文档可以解决大部分,但还是有一些无法查到的问题。

  • 拓展框架功能(很多框架都有预留一些扩展点)

    拓展方式比如接口、SPI、插件、JVMTI Agent什么的。

  • 更好更合理地使用框架

    几乎所有框架的文档都没法将框架所有功能的细节都讲解清楚,熟悉原理可以帮助开发时避坑。

  • 满足对框架内部工作原理的好奇心,也便于后期出现BUG排查BUG

  • 学习代码架构设计、代码风格规范、对依赖框架的封装和使用、提取轮子等

  • 以不变应万变

    对框架源码有清晰认识之后,很多问题没必要死记硬背(不是天天用肯定会忘),用到再看下对应模块源码即可,有什么坑怎么解决源码会告诉我们一切。

对框架源码的认识

  • 读源码需要画图辅助记忆和理解

    这一点是最关键的,解决看源码看了后面的逻辑忘了前面的逻辑的问题(面向生产的框架代码量一般都比较大,应该没有人看了成千上万行代码后对前面的流程细节还记得清清楚楚的吧),这个问题是制约我刚开始学后端迟迟无法深入源码的主要问题。

    曾经为了解决这个问题,试着写过源码分析文档(Markdown,写的多了回顾发现和重新看源码一样无法立刻回想起这段代码的作用和其他模块的关系)、画过思维导图、流程图(流程复杂了看着很乱)、时序图(绘制复杂且空间利用率低,即不方便看)但是效果都不好,最后突然想到为何不按时序图的编排方式画流程图,即使流程复杂也不会乱且格式较时序图更紧凑且方便绘制,后来因为流程图只能体现”算法“逻辑无法体现”数据结构“,又添加进去了简化的UML图,最终形成了现在的流程图。

    通过现在的流程图,可以快速回顾框架的主流程以及架构。

  • 大部分框架源码是复杂但不是难,只是梳理过程比较费时间

  • 大部分框架都有一个主流程逻辑,主流程逻辑占全体代码量一般并不高,也是主要要看的;除了一些工具类框架,比如HutoolRedisson

    面向生产的框架,里面很多功能点,一般都会为功能点提供较全的实现方案,它们的接口和功能类似,通常只需要梳理一种方案实现即可。

    比如框架中常见的配置解析,可能支持Properties 、Json、Yaml、Toml等配置方式;又比如通信框架中可能支持TCP UDP HTTP WebSocket等各种协议实现,梳理主流程时只需要关注其中一种实现。

  • 需要清楚代码阅读边界

    框架里面可能依赖其他框架,不熟悉被依赖的框架,不要立即跳转进去看源码,先通过被依赖框架官方文档了解接口功能即可,以当前框架为主。

  • 很多框架在代码架构上有用一些相同的架构、模式

  • 想了解某一部分源码的逻辑可以结合相应的单元测试代码调试

  • 检验对源码的熟悉度可以通过尝试从中提取Mini版的框架

  • 读源码时组件初始化代码可以先不看细节,在分析流程逻辑时用到再回来找

文档规范

后面新 Markdown 文档统一使用 4W2H 规则编写。

4W2H:What (是什么)、When/Where(什么时候使用或者用在哪里)、Why(为什么选择这个)、How(怎么实现的)、How(怎么使用), 即 介绍(包括功能)、场景优劣原理应用

资源

书籍

有电子版的书基本上在 ZLibrary 都能找到。

About

常用后端框架源码分析

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published