从零设计一个“微Tomcat”:彻底搞懂NIO如何解决C10K问题 + 手写Pipeline责任链 + 类加载隔离(附完整源码)

从零设计一个“微Tomcat”:彻底搞懂NIO如何解决C10K问题 + 手写Pipeline责任链 + 类加载隔离(附完整源码) 关于作者技术博主专注底层原理和实战。更多干货请关注 CSDNCodeStats-CSDN博客 文章目录一、什么是C10K问题为什么NIO能解决二、BIO vs NIO从代码看本质区别三、Tomcat核心架构一张图看懂容器层级四、启动入口Catalina如何组装所有组件五、NIO连接器手写Selector管理万级连接六、HTTP协议解析如何把Socket字节流变成Request对象七、Pipeline-Valve如何用责任链解耦处理逻辑八、Servlet映射如何从URL找到对应的Servlet九、类加载隔离如何让不同应用用不同版本的JAR十、DispatcherServletSpring MVC风格的入口十一、完整请求链路串联十二、运行验证十三、核心代码文件清单一、什么是C10K问题为什么NIO能解决1.1 C10K问题的由来C10K Concurrent 10K connections即单机同时处理1万个网络连接。这个问题的提出者 Dan Kegel 在 2003 年指出当时的 Web 服务器Apache使用BIO阻塞I/O模型每个连接占用一个线程当并发达到 1 万时需要 1 万个线程每个线程栈默认 1MB →10GB 内存CPU 大量时间花在线程上下文切换上服务器直接崩溃或响应极慢1.2 BIO 的瓶颈本质java// BIO 模型伪代码 ServerSocket serverSocket new ServerSocket(8080); while (true) { Socket socket serverSocket.accept(); // ① 阻塞等待连接 new Thread(() - { while (true) { socket.getInputStream().read(); // ② 阻塞等待数据 // 处理业务... } }).start(); }阻塞点accept()阻塞 → 线程闲等新连接read()阻塞 → 线程闲等数据到来结论BIO 的线程利用率极低大部分时间在空等。1.3 NIO 如何解决问题NIO 的核心是Selector多路复用器非阻塞 Channeljava// NIO 模型核心思想 Selector selector Selector.open(); ServerSocketChannel serverChannel ServerSocketChannel.open(); serverChannel.configureBlocking(false); // 非阻塞 serverChannel.register(selector, SelectionKey.OP_ACCEPT); while (true) { selector.select(); // 只阻塞到有事件发生 for (SelectionKey key : selector.selectedKeys()) { if (key.isAcceptable()) { // 有新连接 → 注册到 Selector SocketChannel client serverChannel.accept(); client.configureBlocking(false); client.register(selector, SelectionKey.OP_READ); } else if (key.isReadable()) { // 有数据可读 → 交给线程池处理 threadPool.submit(() - handle(key)); } } }核心突破对比项BIONIO线程模型1连接 → 1线程1线程 → 万级连接阻塞点accept()、read()只有 select()1万连接内存~10GB~16MBCPU消耗大量线程切换少量事件处理二、BIO vs NIO从代码看本质区别2.1 BIO 版服务器CodeStats 对比示例java// BIO 版本 - 每个请求一个线程 public class BioServer { public static void main(String[] args) throws IOException { ServerSocket serverSocket new ServerSocket(8080); ExecutorService threadPool Executors.newCachedThreadPool(); while (true) { Socket socket serverSocket.accept(); // 阻塞点1 threadPool.submit(() - { try { BufferedReader reader new BufferedReader( new InputStreamReader(socket.getInputStream())); String line; while ((line reader.readLine()) ! null !line.isEmpty()) { // 阻塞点2read() 等待数据 } // 处理请求... } catch (IOException e) { e.printStackTrace(); } }); } } }2.2 NIO 版服务器CodeStats 自研实现java// CodeStats NIO 连接器核心 public class Connector { private Selector selector; private ServerSocketChannel serverChannel; private ExecutorService threadPool; public void start() throws IOException { serverChannel ServerSocketChannel.open(); serverChannel.configureBlocking(false); // 关键非阻塞 serverChannel.bind(new InetSocketAddress(8080)); selector Selector.open(); serverChannel.register(selector, SelectionKey.OP_ACCEPT); while (true) { selector.select(); // 阻塞但有事件就返回 IteratorSelectionKey iter selector.selectedKeys().iterator(); while (iter.hasNext()) { SelectionKey key iter.next(); iter.remove(); if (key.isAcceptable()) { handleAccept(); } else if (key.isReadable()) { threadPool.submit(() - handleRead(key)); } } } } }关键差异BIO 的每个线程都在read()上阻塞NIO 只有极少数线程在select()上阻塞。三、Tomcat核心架构一张图看懂容器层级text┌─────────────────────────────────────────────────────────────────┐ │ Catalina │ │ (启动入口 组装器) │ └─────────────────────────────┬───────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ Server │ │ (顶层容器管理Service) │ └─────────────────────────────┬───────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ Service │ │ (包含 Connector Engine) │ └──────────────┬──────────────────────────────┬───────────────────┘ │ │ ▼ ▼ ┌────────────────┐ ┌─────────────────┐ │ Connector │ │ Engine │ │ (NIO连接器) │◄────────────►│ (引擎容器) │ │ - 接收请求 │ │ - 管理Host │ │ - 解析HTTP │ └────────┬────────┘ └────────────────┘ │ ▼ ┌─────────────────┐ │ Host │ │ (虚拟主机) │ │ - 管理Context │ └────────┬────────┘ │ ▼ ┌─────────────────┐ │ Context │ │ (Web应用) │ │ - 类加载隔离 │ │ - Servlet映射 │ └────────┬────────┘ │ ▼ ┌─────────────────┐ │ Wrapper │ │ (Servlet) │ │ - service() │ └─────────────────┘一句话总结每个组件持有子组件形成树形结构请求从 Connector 进入逐层向下传递。四、启动入口Catalina如何组装所有组件java// 源码Catalina.java public class Catalina { private Server server; public void load() { // 1. 创建 Server StandardServer server new StandardServer(); // 2. 创建 Service添加到 Server StandardService service new StandardService(); server.addService(service); // 3. 创建 ConnectorNIO 连接器 Connector connector new Connector(28080); service.setConnector(connector); // 4. 创建容器层级Engine → Host → Context → Wrapper StandardEngine engine new StandardEngine(); service.setEngine(engine); StandardHost host new StandardHost(); host.setAppBase(webapps); engine.addChild(host); StandardContext context new StandardContext(); context.setDocBase(webapps/demo); host.addChild(context); // 5. 注册 DispatcherServlet StandardWrapper wrapper new StandardWrapper(); wrapper.setServletClass(com.omni.framework.spring.mvc.DispatcherServlet); context.addChild(wrapper); context.addServletMapping(/*, wrapper.getName()); this.server server; } public void start() { server.init(); server.start(); } }五、NIO连接器手写Selector管理万级连接java// 源码Connector.java - 完整NIO实现 public class Connector implements Lifecycle { private ServerSocketChannel serverChannel; private Selector selector; private ExecutorService threadPool; private volatile boolean running true; Override public void init() { threadPool Executors.newCachedThreadPool(); } Override public void start() { new Thread(() - { try { // 1. 初始化 NIO 组件 serverChannel ServerSocketChannel.open(); serverChannel.configureBlocking(false); serverChannel.socket().bind(new InetSocketAddress(port)); selector Selector.open(); serverChannel.register(selector, SelectionKey.OP_ACCEPT); log.info(Connector listening on {}, port); // 2. NIO 事件循环 while (running) { selector.select(); // 阻塞到有事件发生 IteratorSelectionKey iterator selector.selectedKeys().iterator(); while (iterator.hasNext()) { SelectionKey key iterator.next(); iterator.remove(); if (key.isAcceptable()) { // 接受新连接 SocketChannel client serverChannel.accept(); client.configureBlocking(false); client.register(selector, SelectionKey.OP_READ); log.debug(New connection from {}, client.getRemoteAddress()); } else if (key.isReadable()) { // 有数据可读交给线程池处理 SocketChannel client (SocketChannel) key.channel(); threadPool.submit(() - processRequest(client)); } } } } catch (IOException e) { log.error(Connector error, e); } }).start(); } private void processRequest(SocketChannel client) { try (InputStream in Channels.newInputStream(client); OutputStream out Channels.newOutputStream(client)) { // 解析 HTTP 请求 Request request new Request(in); request.parse(); // 处理响应 Response response new Response(out); // 调用 Pipeline 处理 service.getEngine().getPipeline().invoke(request, response); response.send(); } catch (Exception e) { log.error(Request processing failed, e); } } }六、HTTP协议解析如何把Socket字节流变成Request对象java// 源码Request.java public class Request implements HttpServletRequest { private String method; private String uri; private String queryString; private MapString, String headers new HashMap(); private MapString, String parameters new HashMap(); private String body; public void parse() throws Exception { BufferedReader reader new BufferedReader(new InputStreamReader(input)); // 1. 解析请求行GET /demo/user/name?nameTom HTTP/1.1 String requestLine reader.readLine(); if (requestLine null) return; String[] parts requestLine.split( ); method parts[0]; // 2. 解析 URI 和 QueryString String fullUri parts[1]; int qIdx fullUri.indexOf(?); if (qIdx 0) { queryString fullUri.substring(qIdx 1); fullUri fullUri.substring(0, qIdx); parseQueryString(queryString, parameters); } uri fullUri; // 3. 解析 Headers String line; while ((line reader.readLine()) ! null !line.isEmpty()) { int colon line.indexOf(:); if (colon 0) { String name line.substring(0, colon).trim().toLowerCase(); String value line.substring(colon 1).trim(); headers.put(name, value); // 特殊处理 Content-Type if (content-type.equals(name) value.startsWith(multipart/form-data)) { isMultipart true; } } } // 4. 解析 BodyPOST 请求 if (POST.equalsIgnoreCase(method) !isMultipart) { StringBuilder bodyBuilder new StringBuilder(); while (reader.ready()) { bodyBuilder.append((char) reader.read()); } body bodyBuilder.toString(); // 表单格式也解析到 parameters String contentType headers.get(content-type); if (contentType ! null contentType.startsWith(application/x-www-form-urlencoded)) { parseQueryString(body, parameters); } } } private void parseQueryString(String query, MapString, String target) { if (query null) return; for (String pair : query.split()) { int eq pair.indexOf(); if (eq 0) { try { String key URLDecoder.decode(pair.substring(0, eq), UTF-8); String val URLDecoder.decode(pair.substring(eq 1), UTF-8); target.put(key, val); } catch (Exception ignored) {} } } } }七、Pipeline-Valve如何用责任链解耦处理逻辑java// 源码SimplePipeline.java public class SimplePipeline implements Pipeline { private ListValve valves new ArrayList(); private Valve basic; private Container container; public SimplePipeline(Container container) { this.container container; } Override public void invoke(Request request, Response response) throws Exception { new ValveChainImpl().invokeNext(request, response); } // 责任链核心递归调用 private class ValveChainImpl implements Valve.ValveChain { private int index 0; Override public void invokeNext(Request request, Response response) throws Exception { if (index valves.size()) { // 执行当前 Valve并传入链对象 valves.get(index).invoke(request, response, this); } else if (basic ! null) { basic.invoke(request, response, this); } else { // 没有更多 Valve交给容器处理 dispatchToContainer(request, response); } } } // 容器处理逻辑Engine → Host → Context → Wrapper private void dispatchToContainer(Request request, Response response) throws Exception { if (container instanceof Engine) { // Engine → 找到默认 Host String hostName ((Engine) container).getDefaultHost(); Container host container.findChild(hostName); host.getPipeline().invoke(request, response); } else if (container instanceof Host) { // Host → 根据 Context Path 找到 Context String contextPath request.getContextPath(); Container context container.findChild(contextPath); if (context ! null) { context.getPipeline().invoke(request, response); } else { response.setStatus(404, Context not found); } } else if (container instanceof Context) { // Context → 根据 URI 找到 Servlet String servletName ((Context) container).findServletMapping(request.getUri()); Container wrapper container.findChild(servletName); if (wrapper ! null) { wrapper.getPipeline().invoke(request, response); } else { response.setStatus(404, Servlet not found); } } else if (container instanceof Wrapper) { // Wrapper → 调用 Servlet ((Wrapper) container).invokeServlet(request, response); } } } // 自定义 Valve 示例日志 Valve public class AccessLogValve implements Valve { Override public void invoke(Request request, Response response, ValveChain chain) throws Exception { long start System.currentTimeMillis(); chain.invokeNext(request, response); long elapsed System.currentTimeMillis() - start; System.out.printf(%s %s %dms%n, request.getMethod(), request.getUri(), elapsed); } }八、Servlet映射如何从URL找到对应的Servletjava// 源码StandardContext.java public class StandardContext extends ContainerBase implements Context { private MapString, String servletMappings new HashMap(); private String defaultServletName; Override public void addServletMapping(String pattern, String servletName) { servletMappings.put(pattern, servletName); if (/*.equals(pattern)) { this.defaultServletName servletName; } } Override public String findServletMapping(String uri) { // 1. 精确匹配/user/info String servletName servletMappings.get(uri); if (servletName ! null) return servletName; // 2. 路径前缀匹配/user/* → 匹配 /user/info、/user/list for (Map.EntryString, String entry : servletMappings.entrySet()) { String pattern entry.getKey(); if (pattern.endsWith(/*)) { String prefix pattern.substring(0, pattern.length() - 2); if (uri.startsWith(prefix)) { return entry.getValue(); } } } // 3. 后缀匹配*.do扩展实现 for (Map.EntryString, String entry : servletMappings.entrySet()) { String pattern entry.getKey(); if (pattern.startsWith(*.)) { String suffix pattern.substring(1); if (uri.endsWith(suffix)) { return entry.getValue(); } } } // 4. 默认 Servlet/* return defaultServletName; } }九、类加载隔离如何让不同应用用不同版本的JARjava// 源码WebappClassLoader.java public class WebappClassLoader extends URLClassLoader { public WebappClassLoader(ClassLoader parent, URL[] urls) { super(urls, parent); } Override protected Class? loadClass(String name, boolean resolve) throws ClassNotFoundException { // 1. 已加载过的类直接返回 Class? clazz findLoadedClass(name); if (clazz ! null) return clazz; // 2. 系统类Java 核心库委托给父加载器 if (name.startsWith(java.) || name.startsWith(javax.) || name.startsWith(sun.)) { return super.loadClass(name, resolve); } // 3. 优先从当前 Web 应用加载打破双亲委派 try { clazz findClass(name); if (clazz ! null) { if (resolve) resolveClass(clazz); return clazz; } } catch (ClassNotFoundException e) { // 当前应用没有继续委托父加载器 } // 4. 委托给父加载器 return super.loadClass(name, resolve); } }为什么打破双亲委派双亲委派子 → 父 → 祖父向上委托Tomcat 需要Web 应用优先加载自己的类WEB-INF/classes、WEB-INF/lib这样应用 A 可以用 Guava 23.0应用 B 可以用 Guava 31.0互不冲突十、DispatcherServletSpring MVC风格的入口java// 源码DispatcherServlet.java public class DispatcherServlet extends HttpServlet { private ListHandlerMapping handlerMappings new ArrayList(); private ObjectMapper objectMapper new ObjectMapper(); Override public void init() throws Exception { // 从 IoC 容器获取所有 Controller AbstractApplicationContext ctx SpringContextHolder.getApplicationContext(); ConfigurableListableBeanFactory beanFactory ctx.getBeanFactory(); MapString, Object controllers beanFactory.getBeansOfType(Object.class); for (Object controller : controllers.values()) { Class? clazz controller.getClass(); // 获取类上的 RequestMapping 路径 String basePath ; if (clazz.isAnnotationPresent(RequestMapping.class)) { basePath clazz.getAnnotation(RequestMapping.class).value(); if (!basePath.startsWith(/)) basePath / basePath; } // 遍历方法注册 RequestMapping for (Method method : clazz.getDeclaredMethods()) { if (method.isAnnotationPresent(RequestMapping.class)) { String methodPath method.getAnnotation(RequestMapping.class).value(); String fullPath (basePath methodPath).replaceAll(//, /); boolean isResponseBody method.isAnnotationPresent(ResponseBody.class); handlerMappings.add(new HandlerMapping(fullPath, controller, method, isResponseBody)); log.debug(Mapped {} - {}, fullPath, method); } } } } Override protected void doGet(HttpServletRequest req, HttpServletResponse res) throws Exception { processRequest(req, res); } Override protected void doPost(HttpServletRequest req, HttpServletResponse res) throws Exception { processRequest(req, res); } private void processRequest(HttpServletRequest req, HttpServletResponse res) throws Exception { String uri req.getUri(); for (HandlerMapping hm : handlerMappings) { if (hm.path.equals(uri)) { // 1. 解析方法参数RequestParam、PathVariable、RequestBody Object[] args ParamBinder.resolveParameters(hm.method, req, res, new HashMap()); // 2. 反射调用 Controller 方法 Object result hm.method.invoke(hm.controller, args); // 3. 处理返回值 if (hm.isResponseBody) { res.setContentType(application/json; charsetutf-8); String json result instanceof String ? (String) result : toJson(result); res.write(json); } else { if (result ! null) res.write(result.toString()); } res.send(); return; } } // 404 res.setStatus(404, Not Found); res.write(h1404 - Not Found/h1); res.send(); } private String toJson(Object obj) { try { return objectMapper.writeValueAsString(obj); } catch (Exception e) { return JsonUtil.toJson(obj); // 降级方案 } } }十一、完整请求链路串联text┌─────────────────────────────────────────────────────────────────────┐ │ 1. 浏览器发送请求 │ │ GET /omni/demo/user/name HTTP/1.1 │ └─────────────────────────────┬───────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ 2. ConnectorNIO接收 Socket 请求 │ │ - Selector 监听 OP_ACCEPT → 接受新连接 │ │ - Selector 监听 OP_READ → 线程池处理读取 │ └─────────────────────────────┬───────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ 3. Request.parse() 解析 HTTP 协议 │ │ - 解析请求行 → methodGET, uri/omni/demo/user/name │ │ - 解析 Headers → Content-Type, Host... │ │ - 解析 BodyPOST时 │ └─────────────────────────────┬───────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ 4. Engine.Pipeline 处理 │ │ - AccessLogValve记录开始时间 │ │ - 根据 Host 头找到对应 Host │ └─────────────────────────────┬───────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ 5. Host.Pipeline 处理 │ │ - 根据 Context Path/omni找到 Context │ └─────────────────────────────┬───────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ 6. Context.Pipeline 处理 │ │ - WebappClassLoader 加载 Web 应用类 │ │ - 根据 URI 匹配 Servlet 映射 → /* → DispatcherServlet │ └─────────────────────────────┬───────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ 7. Wrapper.Pipeline 处理 │ │ - 调用 servlet.service(request, response) │ └─────────────────────────────┬───────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ 8. DispatcherServlet 分发到 Controller │ │ - 匹配 RequestMapping(/user/name) │ │ - 反射调用 userController.getName() │ │ - ResponseBody → JSON 序列化 │ └─────────────────────────────┬───────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ 9. Response 构建 HTTP 响应 │ │ - 设置状态码 200 │ │ - 设置 Content-Type: application/json │ │ - 写入响应体 Jerry │ │ - Socket 返回 │ └─────────────────────────────────────────────────────────────────────┘十二、运行验证12.1 下载与启动bash# 克隆项目 git clone https://gitee.com/zhouzuoli/code-stats.git # 进入目录 cd code-stats # Maven 编译 mvn clean package # 启动服务 java -jar target/CodeStats.jar12.2 验证 NIO 连接器bash# 测试 GET 请求 curl http://localhost:28080/omni/demo/user/name # 返回: Jerry ✅ # 测试带参数的 GET curl http://localhost:28080/omni/demo/hello/text?nameTom # 返回: Hello, Tom! ✅ # 测试 POST 请求 curl -X POST http://localhost:28080/omni/demo/user/manage/create \ -H Content-Type: application/json \ -d {name:张三,age:25} # 返回: {success:true} ✅12.3 观察启动日志text[INFO] Connector listening on 28080 [INFO] DispatcherServlet 初始化完成共映射 15 个处理器 [INFO] Tomcat started in 234 ms十三、核心代码文件清单文件路径作用核心代码行数tomcat/Catalina.java组装所有组件~80tomcat/connector/Connector.javaNIO 连接器~120tomcat/connector/Request.javaHTTP 协议解析~100tomcat/container/SimplePipeline.java责任链实现~80tomcat/container/StandardContext.javaServlet 映射~100tomcat/loader/WebappClassLoader.java类加载隔离~50spring/mvc/DispatcherServlet.javaSpring MVC 入口~150开源项目地址Giteehttps://gitee.com/zhouzuoli/code-stats.git如果本文让你真正搞懂了 Tomcat 和 NIO 解决 C10K 的原理请 Star 支持一下欢迎在评论区讨论你遇到过的最大并发量是多少用什么方案解决的