Java最全面试被问Tomcat整体架构设计,我哭的像个孩子,学Java必看书籍

最后

毕竟工作也这么久了 ,除了途虎一轮,也七七八八面试了不少大厂,像阿里、饿了么、美团、滴滴这些面试过程就不一一写在这篇文章上了。我会整理一份详细的面试过程及大家想知道的一些问题细节

美团面试经验

美团面试
字节面试经验
字节面试
菜鸟面试经验
菜鸟面试
蚂蚁金服面试经验
蚂蚁金服
唯品会面试经验
唯品会

因篇幅有限,图文无法详细发出

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

io 和线程模型

同样一个颜色的是内部类的关系

  1. Http11NioProtocol start 时会分别启动 poller 和 acceptor 线程
  2. acceptor 持有 ServerSocket/ServerSocketChannel, 负责监听新的连接,并将得到的 Socket 注册到 Poller 上
  3. Poller 持有 Selector, 负责selector.select() 监听读写事件,将新的 socket 注册到 selector 上,以及其它通过 addEvent 加入到 Poller 中的 event
  4. Http11NioProcessor 封装了 http 1.1 的协议处理部分,比如 parseRequestLine,连接出问题时 response 设置状态码为 503 或 400 等。以读事件为例, 最终会将 数据读取到 Request 对象的 inputBuffer 中

线程数量:

public class NioEndpoint extends AbstractEndpoint {
private Executor executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);

private int pollerThreadCount = Math.min(2,Runtime.getRuntime().availableProcessors()); // new Thread().start() 的方式
protected int acceptorThreadCount = 0; // new Thread().start() 的方式

// poller 内部除了 selector.select() 逻辑外,一般通过executor 异步执行
// acceptor 就是简单的 accept 一个socket 并将其 加入到poller 的event 队列中( 以将socket 注册到selector)所以没有用到executor
}

业务处理

container 架构

Tomcat 设计了 4 种容器,分别是 EngineHostContext 和 Wrapper。这 4 种容器是父子关系,形成一个树形结构。Tomcat 是用组合模式来管理这些容器的,具体实现方法是,所有容器组件都实现了 Container 接口。

public interface Container extends Lifecycle {
public void setName(String name);
public Container getParent();
public void setParent(Container container);
public void addChild(Container child);
public void removeChild(Container child);
public Container findChild(String name);
}

假如有用户访问一个 URL:http://user.shopping.com:8080/order/buy,Tomcat 如何将这个 URL 定位到一个 Servlet 呢?Tomcat 是用 Mapper 组件。

  1. 根据协议和端口号选定 Service 和 Engine。
  2. 根据域名选定 Host。
  3. 根据 URL 路径找到 Context 组件。
  4. 根据 URL 路径找到 Wrapper(Servlet)。

为了更清晰一点,上图只画出了 Host 类族,Engine、Context、Wrapter 与 Host 类似。黄色部分组成了一个 pipeline,可以看到 Engine、Context、Wrapter 和 Host 作为容器,并不亲自“干活”,而是交给对应的 pipeline。

public class CoyoteAdapter implements Adapter {
// 有读事件时会触发该操作
public boolean event(org.apache.coyote.Request req,
org.apache.coyote.Response res, SocketStatus status) {

// 将读取的数据写入到 request inputbuffer
request.read();

// 触发filter、servlet的执行
connector.getService().getContainer().getPipeline().getFirst().event(request, response, request.getEvent());

}
}

pipeline 逐步传递请求直到 Servlet

Pipeline-Valve 是责任链模式,责任链模式是指在一个请求处理的过程中有很多处理者依次对请求进行处理,每个处理者负责做自己相应的处理,处理完之后将再调用下一个处理者继续处理。Valve 表示一个处理点,比如权限认证和记录日志。

每一个容器都有一个 Pipeline 对象,只要触发这个 Pipeline 的第一个 Valve,这个容器里 Pipeline 中的 Valve 就都会被调用到。不同容器的 Pipeline 是怎么链式触发的呢?Pipeline 中还有个 getBasic 方法。这个 BasicValve 处于 Valve 链表的末端,它是 Pipeline 中必不可少的一个 Valve,负责调用下层容器的 Pipeline 里的第一个 Valve。

Wrapper 容器的最后一个 Valve 会创建一个 Filter 链,并调用 doFilter 方法,最终会调到 Servlet 的 service 方法。

那 Valve 和 Filter 有什么区别吗?Valve 是 Tomcat 的私有机制,与 Tomcat 的基础架构 /API 是紧耦合的。Servlet API 是公有的标准,所有的 Web 容器包括 Jetty 都支持 Filter 机制。

Tomcat 的类加载

Tomcat 热部署与热加载[1] 值得细读

Tomcat 并没有完全遵循类加载的双亲委派机制,考虑几个问题:

  1. 如果在一个 Tomcat 内部署多个应用,多个应用内使用了某个类似的几个不同版本,如何互不影响?org.apache.catalina.loader.WebappClassLoader
  2. 如果多个应用都用到了某类似的相同版本,是否可以统一提供,不在各个应用内分别提供,占用内存呢?common ClassLoader 其实质是一个指定了 classpath(classpath 由 catalina.properties 中的 common.loader 指定common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar")的 URLClassLoader

public final class Bootstrap {
ClassLoader commonLoader = null;
ClassLoader catalinaLoader = null;
public void init() throws Exception {
initClassLoaders();
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);

}
private void initClassLoaders() {

commonLoader = createClassLoader(“common”, null);

catalinaLoader = createClassLoader(“server”, commonLoader);
}
}

热部署和热加载是类似的,都是在不重启 Tomcat 的情况下,使得应用的最新代码生效。热部署表示重新部署应用,它的执行主体是 Host,表示主机。热加载表示重新加载 class,它的执行主体是 Context,表示应用。

Sprint Boot 如何利用 Tomcat 加载 Servlet?

在内嵌式的模式下,Bootstrap 和 Catalina 的工作就由 Spring Boot 来做了,Spring Boot 调用了 Tomcat 的 API 来启动这些组件。

Tomcat 源码中直接提供 Tomcat 类,其 java doc 中有如下表述:Tomcat supports multiple styles of configuration and startup - the most common and stable is server.xml-based,implemented in org.apache.catalina.startup.Bootstrap. Tomcat is for use in apps that embed Tomcat. 从 Tomcat 类的属性可以看到,该有的属性都有了,内部也符合 Server ==> Service ==> connector + Engine ==> Host ==> Context ==> Wrapper 的管理关系,下图绿色部分是通用的。

所以 Minimal 情况下 new 一个 Tomcat 即可启动一个 Tomcat。

Tomcat Tomcat = new Tomcat();
Tomcat.setXXX;
Tomcat.start();

所以 spring-boot-starter-web 主要体现在 创建 并配置 Tomcat 实例,具体参见SpringBoot 中内嵌 Tomcat 的实现原理解析[2]

Tomcat 如何支持异步 Servlet?

从上文类图可知,NioEndpoint 中有一个 Executor,selector.select 之后,Executor 异步处理 Socket.read + 协议解析 + Servlet.service,如果 Servlet 中的处理逻辑耗时越长就会导致长期地占用 Executor,影响 Tomcat 的整体处理能力。为此一个解决办法是

public class AsyncServlet extends HttpServlet {
Executor executor = xx
public void doGet(HttpServletRequest req, HttpServletResponse res) {
AsyncContext asyncContext = req.startAsync(req, res);
executor.execute(new AsyncHandler(asyncContext));
}
}
public class AsyncHandler implements Runnable {
private AsyncContext ctx;
public AsyncHandler(AsyncContext ctx) {
this.ctx = ctx;
}
@Override
public void run() {
//耗时操作
PrintWriter pw;
try {
pw = ctx.getResponse().getWriter();
pw.print(“done!”);
pw.flush();
pw.close();
} catch (IOException e) {
e.printStackTrace();
}
ctx.complete();
}
}

startAsync 方法其实就是创建了一个异步上下文 AsyncContext 对象,该对象封装了请求和响应对象。然后创建一个任务用于处理耗时逻辑,后面通过 AsyncContext 对象获得响应对象并对客户端响应,输出“done!”。完成后要通过 complete 方法告诉 Tomcat 已经处理完,Tomcat 就会请求对象和响应对象进行回收处理或关闭连接。

public class Request
implements HttpServletRequest {
public AsyncContext startAsync(ServletRequest request,
ServletResponse response) {

asyncContext = new AsyncContextImpl(this);

asyncContext.setStarted(getContext(), request, response,
requestgetRequest() && responsegetResponse().getResponse());
asyncContext.setTimeout(getConnector().getAsyncTimeout());
return asyncContext;
}
}

写回数据由 Response 完成,从代码看,AsyncContextImpl.complete 方法表示 Tomcat 可以重新开始关注该 socket read 事件了(之前一直在等 socket 写回客户端数据)。

其它

Tomcat 为什么运行 war 而不是 jar

如果一个项目打成 jar 包,那么 Tomcat 在启动时 就要去分析下 这个 jar 是一个 web 项目还是一个 普通二方库。

安全

如果你在 Servlet 代码中直接 加入System.exit(1) 你会发现,仅仅是作为一个 Tomcat 上层的一个“业务方”,却有能力干掉 java 进程,即 Tomcat 的运行。

public class XXServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException{
System.exit(1);
xxx
}
}

最后

看完上述知识点如果你深感Java基础不够扎实,或者刷题刷的不够、知识不全面

小编专门为你量身定制了一套<Java一线大厂高岗面试题解析合集:JAVA基础-中级-高级面试+SSM框架+分布式+性能调优+微服务+并发编程+网络+设计模式+数据结构与算法>

image

针对知识面不够,也莫慌!还有一整套的<Java核心进阶手册>,可以瞬间查漏补缺

image

全都是一丢一丢的收集整理纯手打出来的

更有纯手绘的各大知识体系大纲,可供梳理:Java筑基、MySQL、Redis、并发编程、Spring、分布式高性能架构知识、微服务架构知识、开源框架知识点等等的xmind手绘图~

image

image

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

0ULlKdS-1715362073599)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值