├── README.md ├── _config.yml └── docs ├── Consensus and Distributed Transactions.pdf ├── How to on starting processes.md ├── application_datasource.md ├── application_server_transaction.md ├── atomikos-sample.md ├── bookkeeper.md ├── build_jdk_on_macos.md ├── build_kernel_from_source.md ├── build_perf_from_source.md ├── c_pointers.md ├── container-innovation.md ├── crac.md ├── cs_resources_for_students.md ├── cursor-summary.pdf ├── distributed-transaction.md ├── docker-network-bridge.md ├── docker-overlay-networks.md ├── docker-route-networks.md ├── ftrace.md ├── gcc-loongarch64.md ├── gdb-kernel-debugging.md ├── glassfish_startup_cn.md ├── glassfish_startup_en.md ├── helm-quickstart.md ├── http2.md ├── impl_trait.md ├── insider-docker.md ├── jakarta_ee_transaction.md ├── java-performance-analysis-with-flame-graphs.md ├── kubectl-multiple-clusters.md ├── kubernetes-authentication.md ├── kubernetes-overview.md ├── lambda.md ├── legacy_system.md ├── media ├── aliyun │ ├── aliyun-docker-image-namespace.png │ ├── docker-proxy.png │ └── patriotic-networ-2.png ├── atomikos │ ├── atomikosrestport.png │ ├── demo-services.png │ └── spring-remoting.png ├── container │ ├── 2018-12-15 12.14.10.png │ ├── 2018-12-15 17.41.10.png │ ├── 2018-12-15 22.22.35.png │ ├── 2018-12-15 22.22.49.png │ ├── 2018-12-16 11.16.37.png │ ├── 2018-12-16 15.03.35.png │ ├── 2018-12-16 18.16.08.png │ ├── 2018-12-16 19.01.12-1.png │ ├── 2018-12-16 19.01.12.png │ ├── 2018-12-16 19.35.38.png │ ├── firecracker_host_integration.png │ ├── gvisor-Layers.png │ └── protection_rings.png ├── distributedtransaction │ ├── jta-microservices.png │ ├── jta.png │ ├── jts.png │ ├── local-transaction.png │ ├── seata-architecture.png │ └── seata-microservices.png ├── docker │ ├── 2018-11-10 22.56.04.png │ ├── 2018-11-10 23.39.03.png │ ├── 2018-11-11 10.53.03.png │ ├── 2018-11-11 13.27.42.png │ ├── 2018-11-11 14.22.58.png │ ├── 2018-11-11 18.18.03.png │ ├── 2018-11-11 20.35.36.png │ ├── 2018-11-11 23.12.47.png │ ├── 2018-11-11 23.15.21.png │ ├── 2018-11-12 08.32.09.png │ ├── 2018-11-15 23.42.06.png │ ├── VM-Diagram.png │ └── vxlan.png ├── ftrace │ └── ftrace.png ├── gdb-kernel-debugging │ ├── ip_link.png │ ├── kgdb.png │ ├── menuconfig.png │ ├── networking.png │ ├── vnc-connection.png │ └── vnc-viewer.png ├── http2 │ ├── alpn.png │ ├── binary-framing-layer.png │ ├── chrome.png │ ├── cipher-suites.png │ ├── client-hello.png │ ├── connection-view.png │ ├── ending.jpg │ ├── frame-format.png │ ├── frame-type.png │ ├── google-live.png │ ├── h2-h2c.png │ ├── head-of-line.png │ ├── http-header.png │ ├── http-keep-alive.png │ ├── http-pipeline.png │ ├── http2-connection.png │ ├── http2-vs-http1.gif │ ├── jsse-openssl.png │ ├── magic-frame.png │ ├── quic.png │ ├── server-hello.png │ ├── server-push.png │ ├── setting-frame1.png │ ├── setting-frame2.png │ ├── setting-frame3.png │ ├── start-http2-connection.png │ ├── stream-message-frame.png │ ├── tcp-connection1.png │ ├── tcp-connection2.png │ ├── tcp-tls.png │ ├── tls-1.3-handshake-performance.png │ ├── tls-handshake.png │ └── tlstest.png ├── k8s-auth │ ├── access-control-overview.svg │ ├── ca-chain.png │ ├── root-ca.png │ └── zhihu-crt.png ├── kubernetes │ ├── 2019-02-24 22.58.55.png │ ├── 2019-02-24 23.17.20.png │ ├── 2019-02-24 23.41.52.png │ ├── 2019-02-24 23.56.12.png │ ├── 2019-02-25 00.06.39.png │ └── 2019-02-25 00.36.51.png ├── perf │ ├── async-profiler.png │ ├── bcc-bpftrace.png │ ├── bcc_tracing_tools_2019.png │ ├── before_and_after_using_BPF.png │ ├── flame-graph-demo.png │ ├── java-flamegraph.png │ ├── java-profilers.png │ ├── linux_ebpf_internals.png │ ├── linux_ebpf_support.png │ ├── perf-report-context-switches.png │ ├── perf-report-stdio.png │ ├── perf-report.png │ ├── perf.png │ ├── perf_events_map.png │ ├── tcpdump.png │ └── thread_states.png ├── pid │ ├── pid.png │ ├── pstree.png │ └── session.png ├── psi │ ├── DiscontinuousCrop.png │ ├── FullCrop.png │ └── someCrop.png ├── tomcat │ ├── Threading-EPC.png │ ├── Threading-PEC.png │ ├── adapter.png │ ├── connector.png │ ├── endpoint.png │ ├── ewyk2.png │ ├── netty-thread-model.jpg │ ├── nioendpoint.png │ ├── processor.png │ ├── tomcat-ProtocolHandler.png │ └── tomcat-architecture.png ├── tty │ ├── 1.png │ ├── 2.png │ ├── 3.png │ ├── 4.png │ ├── ASR-33_at_CHM.agr.jpg │ └── WACsOperateTeletype.jpg └── workout │ ├── health.jpeg │ └── vo2max.jpg ├── minikube.md ├── nginx-architecture.md ├── nginx-compile-install.md ├── nginx-macos-debug.md ├── nginx-modules.md ├── nginx-phytium.md ├── nginx-reverse-proxy.md ├── nginx-static-resource-service.md ├── nginx-usage.md ├── numbers_everyone_should_know.md ├── pagecache.md ├── perf_cpu_profiling.md ├── pid.md ├── pin-box-future.md ├── powershell_emacs.md ├── programmer-workout.md ├── psi.md ├── rust-async.md ├── signal.md ├── sudo.md ├── sync-docker-repo.md ├── tomcat-architecture.md ├── tomcat-nioendpoint.md ├── tty-pty-shell.md └── vscode.md /README.md: -------------------------------------------------------------------------------- 1 | ## 欢迎访问个人站点 [mazhen.tech](https://mazhen.tech) 2 | 3 | * [CRaC 技术深度解析](./docs/crac.md) 4 | * [深入理解 Java Lambda 表达式](./docs/lambda.md) 5 | 6 | --- 7 | 8 | * [为 PowerShell 配置 Emacs 模式](./docs/powershell_emacs.md) 9 | * [从源码构建 Linux 内核](./docs/build_kernel_from_source.md) 10 | * [一篇读懂 C 指针](./docs/c_pointers.md) 11 | * [遗留系统](./docs/legacy_system.md) 12 | 13 | --- 14 | 15 | * [深入探索 perf CPU Profiling 实现原理](./docs/perf_cpu_profiling.md) 16 | * [从源码构建 perf](./docs/build_perf_from_source.md) 17 | * [Latency Numbers Every Programmer Should Know](./docs/numbers_everyone_should_know.md) 18 | * [Incomplete List of Computer Science Learning Resources for College Students](./docs/cs_resources_for_students.md) 19 | * [Jakarta EE 应用服务器的事务处理](./docs/jakarta_ee_transaction.md) 20 | * [GlassFish Startup Process](./docs/glassfish_startup_en.md) 21 | * [GlassFish 启动流程](./docs/glassfish_startup_cn.md) 22 | * [在 macOS 上编译和调试 OpenJDK](./docs/build_jdk_on_macos.md) 23 | * [Java EE 应用服务器的事务管理](./docs/application_server_transaction.md) 24 | * [应用服务器整合第三方连接池](./docs/application_datasource.md) 25 | 26 | --- 27 | 28 | * [Nginx 的模块化设计](./docs/nginx-modules.md) 29 | * [在 macOS 上使用 VSCode 调试 NGINX](./docs/nginx-macos-debug.md) 30 | * [Nginx 架构基础](./docs/nginx-architecture.md) 31 | * [Nginx 反向代理配置](./docs/nginx-reverse-proxy.md) 32 | * [Nginx 静态资源服务的配置](./docs/nginx-static-resource-service.md) 33 | * [Nginx 的基本使用](./docs/nginx-usage.md) 34 | * [Nginx 的编译和安装](./docs/nginx-compile-install.md) 35 | 36 | --- 37 | 38 | * [Pin>>解析](./docs/pin-box-future.md) 39 | * [impl Trait 的使用](./docs/impl_trait.md) 40 | * [Rust 异步编程笔记](./docs/rust-async.md) 41 | * [BookKeeper 实现分析](./docs/bookkeeper.md) 42 | * [深入浅出容器技术](./docs/insider-docker.md) 43 | * [GCC 为龙芯 CPU 的预定义宏](./docs/gcc-loongarch64.md) 44 | * [Linux 信号 (Signal)](./docs/signal.md) 45 | * [深入理解 Page Cache](./docs/pagecache.md) 46 | 47 | --- 48 | 49 | * [进程 ID 及进程间的关系](./docs/pid.md) 50 | * [使用 PSI(Pressure Stall Information)监控服务器资源](./docs/psi.md) 51 | * [理解 Linux 终端、终端模拟器和伪终端](./docs/tty-pty-shell.md) 52 | * [从 Ftrace 开始内核探索之旅](./docs/ftrace.md) 53 | * [使用 GDB 调试 Linux 内核](./docs/gdb-kernel-debugging.md) 54 | 55 | --- 56 | 57 | * [Atomikos 在微服务场景下的使用](./docs/atomikos-sample.md) 58 | * [应用服务器的分布式事务支持和 Seata 的对比分析](./docs/distributed-transaction.md) 59 | * [使用火焰图进行 Java 性能分析](./docs/java-performance-analysis-with-flame-graphs.md) 60 | * [Consensus and Distributed Transactions](./docs/Consensus%20and%20Distributed%20Transactions.pdf) 61 | * [Tomcat 的 NioEndpoint 实现分析](./docs/tomcat-nioendpoint.md) 62 | * [Tomcat 系统架构简介](./docs/tomcat-architecture.md) 63 | * [给程序员的健身锻炼指南](./docs/programmer-workout.md) 64 | * [HTTP/2 内部分享](./docs/http2.md) 65 | 66 | --- 67 | 68 | * [自己动手将谷歌 k8s 镜像同步到阿里云](./docs/sync-docker-repo.md) 69 | * [Kubernetes 集群的身份验证](./docs/kubernetes-authentication.md) 70 | * [在国产飞腾平台上编译安装 nginx](./docs/nginx-phytium.md) 71 | * [使用 kubectl 管理多集群](./docs/kubectl-multiple-clusters.md) 72 | * [如何让用户拥有 sudo 权限](./docs/sudo.md) 73 | * [在 Ubuntu 上安装 Minikube](./docs/minikube.md) 74 | * [Helm 的安装和使用](./docs/helm-quickstart.md) 75 | * [VS code 快捷键](./docs/vscode.md) 76 | * [Kubernetes 工作原理概述](./docs/kubernetes-overview.md) 77 | 78 | --- 79 | 80 | * [容器技术创新漫谈](./docs/container-innovation.md) 81 | * [Docker 单机网络模型动手实验](./docs/docker-network-bridge.md) 82 | * [Docker 跨主机 Overlay 网络动手实验](./docs/docker-overlay-networks.md) 83 | * [Docker 跨主机通信路由模式动手实验](./docs/docker-route-networks.md) 84 | 85 | --- 86 | 87 | * [关系数据库查询处理基础知识扫盲](https://github.com/mz1999/Apusic-db-team/blob/master/docs/rdbms-fundamental.md) 88 | * [TiDBCursor 功能实现总结](./docs/cursor-summary.pdf) 89 | * [关于 MySQL XA 事务的隔离级别](https://github.com/mz1999/Apusic-db-team/blob/master/docs/mysql-xa.md) 90 | * [从 RadonDB 看新型数据库中间件的特性](https://github.com/mz1999/Apusic-db-team/blob/master/docs/radondb.md) 91 | * [TiDB 基于代价优化(CBO)实现代码导读](https://github.com/mz1999/Apusic-db-team/blob/master/docs/cbo-guide.md) 92 | * [TiDB Insert 执行流程图](https://github.com/mz1999/Apusic-db-team/blob/master/docs/insert-overview.md) 93 | * [TiDB SQL Parser 的实现](https://github.com/mz1999/Apusic-db-team/blob/master/docs/sql-parser.md) 94 | * [利用 docker compose 在单机上玩转 TiDB](https://github.com/mz1999/Apusic-db-team/blob/master/docs/tidb-docker-compose.md) 95 | * [TiDB 初探](https://github.com/mz1999/Apusic-db-team/blob/master/docs/tidb-glance.md) 96 | 97 | --- 98 | 99 | * [Git Feature Branch Workflow](https://github.com/mz1999/Apusic-db-team/blob/master/docs/github-workflow.md) 100 | * [Golang error 处理实践](https://github.com/mz1999/Apusic-db-team/blob/master/docs/golang-error-handling.md) 101 | * [Go 语言的引用类型](https://github.com/mz1999/Apusic-db-team/blob/master/docs/go-reference-types.md) 102 | 103 | --- 104 | 105 | * [为 Wireshark 编写 HSF2 协议解析插件](https://github.com/mz1999/Apusic-db-team/blob/master/docs/hsf.md) 106 | * [Linux TCP 队列相关参数的总结](https://github.com/mz1999/Apusic-db-team/blob/master/docs/linux-tcp-queue.md) 107 | * [使用 NIO 时慎用 SO_LINGER 选项](https://github.com/mz1999/Apusic-db-team/blob/master/docs/nio-so-linger.md) 108 | * [TCP `SO_LINGER` 选项对 Socket.close 的影响](https://github.com/mz1999/Apusic-db-team/blob/master/docs/so-linger.md) 109 | * [从开发角度看 DNS](https://github.com/mz1999/Apusic-db-team/blob/master/docs/dns.md) 110 | * [应用关闭后占用端口时间过长的问题](https://github.com/mz1999/Apusic-db-team/blob/master/docs/time-wait.md) 111 | * [使用 USE Method 分析系统性能问题](https://github.com/mz1999/Apusic-db-team/blob/master/docs/use-method.md) 112 | * [Linux 内核参数的配置方法](https://github.com/mz1999/Apusic-db-team/blob/master/docs/linux-kernel-parameter.md) 113 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-slate -------------------------------------------------------------------------------- /docs/Consensus and Distributed Transactions.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/Consensus and Distributed Transactions.pdf -------------------------------------------------------------------------------- /docs/application_datasource.md: -------------------------------------------------------------------------------- 1 | # 应用服务器整合第三方连接池 2 | 3 | 数据库连接池是应用服务器的基本功能,但有时用户因为性能、监控等需求,想使用第三方的连接池。如果只是使用第三方连接池管理数据库连接,那么直接在应用中引入就可以了,但如果用户同时还需要应用服务器的分布式事务和安全服务,就没那么简单了。 4 | 5 | 为了讲清楚,首先需要了解一下 JDBC 基本概念。 6 | 7 | ## Connection 8 | 9 | 从 JDBC driver 的角度来看,**Connection** 表示客户端会话。应用程序可以通过以下两种方式获取连接: 10 | 11 | * **DriverManager** 最初的JDBC 1.0 API中被引入,当应用程序首次尝试通过指定URL连接到数据源时,`DriverManager`将自动加载在 CLASSPATH 中找到的任何 JDBC driver。 12 | * **DataSource** 是在 JDBC 2.0 可选包API中引入的接口。它允许应用程序对底层数据源的细节是透明的。`DataSource` 对象的属性被设置为表示特定数据源。当调用其 `getConnection`方法时,`DataSource` 实例将返回到该数据源的连接。通过简单地更改DataSource对象的属性,可以将应用程序定向到不同的数据源;无需更改应用程序代码。同样,可以更改 `DataSource` 实现而不更改使用它的应用程序代码。 13 | 14 | JDBC 还定义了 `DataSource` 接口的两个重要扩展: 15 | 16 | * **ConnectionPoolDataSource** - 支持物理连接的缓存和重用,从而提高应用程序的性能和可扩展性 17 | * **XADataSource** - 提供可以参与分布式事务的连接 18 | 19 | ![datasource](https://cdn.mazhen.tech/images/202303101440425.png) 20 | 21 | 从 **DriverManager** 和 **DataSource** 都可以获得 **Connection**。 22 | 23 | **DataSource**、**ConnectionPoolDataSource** 和 **XADataSource** 都继承自 **CommonDataSource**,但**它们之间没有继承关系**。 24 | 25 | 从 **ConnectionPoolDataSource** 获得的是 **PooledConnection**,**PooledConnection** 并没有继承 **Connection**,但可以获得**Connection**。 26 | 27 | 从 **XADataSource** 获得的是 **XAConnection**,**XAConnection** 继承了 **PooledConnection**,除了能获得 **Connection**,还可以获得 **XAResource**。 28 | 29 | ## Application Server DataSource 30 | 31 | 应用服务器会为其客户端提供了一个 **DataSource** 接口的实现,并通过 JNDI 暴露给用户。这个 DataSource 包装了 jdbc driver 连接数据库的能力,并在此基础上提供连接池、事务和安全等服务。 32 | 33 | 在配置应用服务器的 DataSource 时,一般需要指定 Connection 的获取方式: 34 | 35 | * java.sql.Driver 36 | 37 | * javax.sql.DataSource 38 | 39 | * javax.sql.ConnectionPoolDataSource 40 | 41 | * javax.sql.XADataSource 42 | 43 | 这四种连接获取方式都是 JDBC driver 提供的能力,Driver 和 DataSource 是最基本方式。如果应用服务器的 DataSource 想要具备连接池化、分布式事务等服务,除了自身要实现这些功能以外,还需要底层 driver 提供相应的能力配合。 44 | 45 | ### ConnectionPoolDataSource 46 | 47 | 以连接池为例,JDBC driver 提供了 **ConnectionPoolDataSource** 的实现,应用服务器使用它来构建和管理连接池。客户端在使用相同的 JNDI 和 DataSource API 的同时获得更好的性能和可扩展性。 48 | 49 | ![connection pool](https://cdn.mazhen.tech/images/202303101503461.png) 50 | 51 | 应用服务器维护维护一个从 **ConnectionPoolDataSource** 对象返回的 **PooledConnection** 对象池。应用服务器的实现还可以向 PooledConnection 对象注册**ConnectionEventListener**,以获得连接事件的通知,如连接关闭和错误事件。 52 | 53 | ![ConnectionPoolDataSource](https://cdn.mazhen.tech/images/202303160947605.png) 54 | 55 | 我们看到,应用程序客户端通过 JNDI 查找一个 DataSource 对象,并请求从 DataSource 获得一个连接。当连接池没有可用连接时,DataSource 的实现从 JDBC driver 的 ConnectionPoolDataSource 中请求一个新的 PooledConnection 。应用服务器的 DataSource 实现会向 PooledConnection 注册一个ConnectionEventListener,随后获得一个新的 Connection 对象。应用客户端在完成操作后调用 `Connection.close()`,会生成一个 ConnectionEvent 实例,该实例会返回给应用服务器的数据源实现。在收到连接关闭的通知后,应用服务器可以将连接对象放回连接池中。 56 | 57 | 注意 **ConnectionPoolDataSource 本身不是连接池**,它是 driver 提供给应用服务器的接口契约,意思是你从 ConnectionPoolDataSource 获得的PooledConnection可以放心的缓存起来,同时连接关闭的时候,driver 会发送事件通知给应用服务器,真正的关闭连接还是放回连接池,由你自己决定。 一般 JDBC driver 提供的 ConnectionPoolDataSource 实现并没有内置连接池功能,需要配合应用服务器或其他第三方连接池一起使用。可以参考 [MySQL Connector](https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-usagenotes-j2ee-concepts-connection-pooling.html)的文档。 58 | 59 | ### XADataSource 60 | 61 | 同样,如果想要分布式事务支持,应用服务器的 DataSource 需要依赖 driver 提供的 **XADataSource** 实现,同时通过 XAResource 和 Transaction Manager 交互。 62 | 63 | ![XADataSource](https://cdn.mazhen.tech/images/202303101754420.png) 64 | 65 | **XADataSource** 对象返回 **XAConnection** ,该对象扩展了 PooledConnection ,增加了对分布式事务的参与能力。应用服务器的 DataSource 实现在XAConnection 对象上调用 getXAResource() 以获得传递给事务管理器的 **XAResource** 对象。事务管理器使用 XAResource 来管理分布式事务。 66 | 67 | 就像池化连接一样,这种分布式事务管理的标准API对应用程序客户端也是透明的。因此,应用服务器可以使用不同 JDBC driver 实现的XADataSource, 来组装可扩展的分布式事务支持的数据访问方案。 68 | 69 | ![XADataSource](https://cdn.mazhen.tech/images/202303161008074.png) 70 | 71 | ## 直接整合外部连接池 72 | 73 | 如果想在应用服务器中直接整合第三方的连接池实现是比较困难的,下面分析一下原因。 74 | 75 | JTA 规范要求连接必须能够同时处理多个事务,这个功能被称为事务多路复用或事务交错。我们看一个例子: 76 | 77 | ``` java 78 | 1. UserTransaction ut = getUserTransaction(); 79 | 2. DataSource ds = getDataSource(); 80 | 3. 81 | 4. ut.begin(); 82 | 5. 83 | 6. Connection c1 = ds.getConnection(); 84 | 7. // do some SQL 85 | 8. c1.close(); 86 | 9. 87 | 10. ut.commit(); 88 | ``` 89 | 90 | 在第8行,连接将释放回连接池,另外一个线程就可以通过 `getConnection()` 获得刚释放的连接。但此时 c1 上的事务还没有提交,如果被其他线程获取,就有可能加入另一个事务,这就是为什么连接必须能够一次支持多个事务。 91 | 92 | 大多数数据库都不支持事务多路复用,那么一种变通的做法是**让事务独占连接**,在 JTA 事务完成之前,连接不要释放连接回池中。 93 | 94 | 因此,需要应用服务器的连接池实现能感知到事务,在第8行不会释放连接,而是连接被标记为关闭。在第10行事务提交后,标记为已关闭的所有连接才释放回连接池。 95 | 96 | 现实中,应用服务器管理的连接池都是能够感知事务的存在,并通过 XAResource 和 Transaction Manager 进行交互: 97 | 98 | ![JTA Transaction](https://cdn.mazhen.tech/images/202303102116711.png) 99 | 100 | 另外,应用服务器都实现了对 **JCA(Java EE Connector Architecture)**规范的支持。JCA 将应用服务器的事务、安全和连接管理等功能,与事务资源管理器集成,定义了一个标准的 SPI(Service Provider Interface) ,因此,一般应用服务器的连接池都在 JCA 中实现,JDBC DataSource 作为一种资源,被 JCA 统一管理。 101 | 102 | ![jca](https://cdn.mazhen.tech/images/202303102216362.png) 103 | 104 | 而外部连接池不能感知事务的存在,所以没办法做到事务对连接的独占,因此应用服务器不能简单的直接整合第三方连接池。 105 | 106 | ## 解决方案 107 | 108 | 如果外部连接池实现了 XADataSource,那么我们可以把它当作普通的 JDBC driver,在配置应用服务器的 DataSource 时使用。需要注意几点: 109 | 110 | * 为外部连接池配置真正的 JDBC driver 时,要使用 driver的 XADataSource 作为连接的获取方式 111 | 112 | * 外部连接池作为特殊的 driver,已经内置了池化功能,连接池的相关参数最好和应用服务器的DataSource保持一致,因为连接池的实际大小受到外部连接池的约束 113 | 114 | * 外部连接池在使用前,一般需要进行初始化,同时,应用服务器在关闭 DataSource 时,也要关闭内置的外部连接池,避免连接泄漏。 115 | 116 | 这个解决方案的问题是,应用服务器和外部连接池都对连接做了池化,实际上是建立了两个连接池,存在较大的浪费。一种变通的做法是,设置应用服务器连接池的空闲连接数为0,这样应用服务器的连接池不会持有连接,连接在使用完毕后会释放到外部连接池。连接由外部连接池管理,同时经过应用服务器 datasource的包装,能够享受应用服务器内置的事务和安全服务。 117 | 118 | 当然更优的做法是,对外部连接池进行适当改造,让它能感知事务的存在,例如 [Agroal](https://github.com/agroal/agroal) 连接池能够被注入Transaction Manager,通过 Transaction Manager 感知到事务的存在,做到事务对连接的独占。 119 | -------------------------------------------------------------------------------- /docs/atomikos-sample.md: -------------------------------------------------------------------------------- 1 | # Atomikos在微服务场景下的使用 2 | 3 | [Atomikos](https://github.com/atomikos/transactions-essentials)是一个轻量级的分布式事务管理器,实现了[Java Transaction API (JTA)](https://jcp.org/en/jsr/detail?id=907)规范,可以很方便的和[Spring Boot](https://spring.io/projects/spring-boot)集成,支持微服务场景下跨节点的全局事务。 4 | 5 | 本文为一个微服务的示例应用,通过引入`Atomikos`增加全局事务能力。 6 | 7 | 示例代码可以在[这里](https://github.com/mz1999/atomkos-sample)查看。 8 | 9 | 10 | 11 | 用户访问`Business`服务,它通过`RPC`调用分别调用`Order`和`Storage`创建订单和减库存。三个服务需要加入到一个全局事务中,要么全部成功,任何一个服务失败,都会造成事务回滚,数据的状态始终保持一致性。 12 | 13 | 蚂蚁金服开源的[Seata](http://seata.io/)就是为了解决这类问题,在微服务架构下提供分布式事务服务。传统的应用服务器通过`JTA/JTS`也能解决分布式场景下的事务问题,但需要和`EJB`绑定在一起才能使用。`Atomikos`是一个独立的分布式事务管理器,原先是为`Spring`和`Tomcat`提供事务服务,让用户不必只为了事务服务而引入应用服务器。 14 | 15 | 现在`Atomikos`也能为微服务提供分布式事务服务,这时主要需要两个问题: 16 | 17 | 1. 事务上下文如何通过RPC在服务间传播 18 | 2. 微服务如何参与进两阶段提交协议的过程 19 | 20 | 后面会结合示例应用介绍`Atomikos`是如何解决这两个问题。示例应用`atomkos-sample`的结构如下: 21 | 22 | 1. **api**:定义了服务接口`OrderService`和`StorageService` 23 | 2. **order-service**:`OrderService`的具体实现 24 | 3. **storage-service**:`StorageService`的具体实现 25 | 4. **business-service**:用户访问入口 26 | 27 | ## 事务上下文的传播 28 | 29 | 在项目主工程的pom文件中引入`Atomikos`依赖,注意要包括`transactions-remoting`,正是它才能让事务上下文在`RPC`调用时传递。 30 | 31 | ``` 32 | 33 | com.atomikos 34 | transactions-remoting 35 | 5.0.6 36 | 37 | ``` 38 | 39 | `transactions-remoting`支持`jaxrs`,`Spring Remoting`和`Spring rest`等几种`RPC`方式,我们使用的是`Spring Remoting`。 40 | 41 | 以**order-service**为例,通过`TransactionalHttpInvokerServiceExporter`将`OrderService`[发布为远程服务](https://github.com/mz1999/atomkos-sample/blob/690d6c0026a8f0874de63828023f26ef9210d0dd/order-service/src/main/java/com/apusic/samples/config/ServiceConfig.java#L12): 42 | 43 | ``` 44 | @Bean(name = "/services/order") 45 | TransactionalHttpInvokerServiceExporter orderService(OrderServiceImpl orderService) { 46 | TransactionalHttpInvokerServiceExporter exporter = new TransactionalHttpInvokerServiceExporter(); 47 | exporter.setService(orderService); 48 | exporter.setServiceInterface(OrderService.class); 49 | return exporter; 50 | } 51 | ``` 52 | 53 | `OrderService`的调用者**business-service**使用`HttpInvokerProxyFactoryBean`[引入远程服务](https://github.com/mz1999/atomkos-sample/blob/690d6c0026a8f0874de63828023f26ef9210d0dd/business-service/src/main/java/com/apusic/samples/config/ServiceConfig.java#L14): 54 | 55 | ``` 56 | @Bean 57 | public HttpInvokerProxyFactoryBean orderService() { 58 | HttpInvokerProxyFactoryBean orderService = new HttpInvokerProxyFactoryBean(); 59 | orderService.setHttpInvokerRequestExecutor(httpInvokerRequestExecutor()); 60 | orderService.setServiceUrl("http://localhost:8082/services/order"); 61 | orderService.setServiceInterface(OrderService.class); 62 | return orderService; 63 | } 64 | 65 | @Bean 66 | public TransactionalHttpInvokerRequestExecutor httpInvokerRequestExecutor() { 67 | TransactionalHttpInvokerRequestExecutor httpInvokerRequestExecutor = new TransactionalHttpInvokerRequestExecutor(); 68 | return httpInvokerRequestExecutor; 69 | } 70 | ``` 71 | 72 | **business-service**负责发起全局事务,它使用`Spring`标准的`@Transactional`标记方法[开启事务](https://github.com/mz1999/atomkos-sample/blob/690d6c0026a8f0874de63828023f26ef9210d0dd/business-service/src/main/java/com/apusic/samples/service/BusinessService.java#L19): 73 | 74 | ``` 75 | @Transactional 76 | public void createOrder(String userId, String commodityCode, Integer count) { 77 | orderService.create(userId, commodityCode, count); 78 | storageService.deduct(commodityCode, count); 79 | } 80 | ``` 81 | 82 | `Atomikos`提供了`TransactionalHttpInvokerRequestExecutor`和`TransactionalHttpInvokerServiceExporter`拦截请求和响应,利用`HTTP header`传递事务上下文。 83 | 84 | 85 | 86 | **business-service**在调用远程服务`OrderService`时,请求发送前会经过[TransactionalHttpInvokerRequestExecutor.prepareConnection](https://github.com/atomikos/transactions-essentials/blob/4332faaf7de551e126ab60c6151e66cee2b854ed/public/transactions-remoting/src/main/java/com/atomikos/remoting/spring/httpinvoker/TransactionalHttpInvokerRequestExecutor.java#L30)处理,增加`HTTP header`,携带事务上下文: 87 | 88 | ``` 89 | @Override 90 | protected void prepareConnection(HttpURLConnection con, int contentLength) 91 | throws IOException { 92 | String propagation = template.onOutgoingRequest(); 93 | con.setRequestProperty(HeaderNames.PROPAGATION_HEADER_NAME, propagation); 94 | super.prepareConnection(con, contentLength); 95 | } 96 | ``` 97 | 98 | `OrderService`会使用[TransactionalHttpInvokerServiceExporter.decorateInputStream](https://github.com/atomikos/transactions-essentials/blob/4332faaf7de551e126ab60c6151e66cee2b854ed/public/transactions-remoting/src/main/java/com/atomikos/remoting/spring/httpinvoker/TransactionalHttpInvokerServiceExporter.java#L33)进行请求拦截,能从`HTTP header`中解析出事务上下文: 99 | 100 | 101 | ``` 102 | @Override 103 | protected InputStream decorateInputStream(HttpServletRequest request, InputStream is) throws IOException { 104 | 105 | try { 106 | String propagation = request.getHeader(HeaderNames.PROPAGATION_HEADER_NAME); 107 | template.onIncomingRequest(propagation); 108 | } catch (IllegalArgumentException e) { 109 | ... 110 | } 111 | return super.decorateInputStream(request, is); 112 | } 113 | ``` 114 | 115 | `OrderService`处理完成[返回响应时](https://github.com/atomikos/transactions-essentials/blob/4332faaf7de551e126ab60c6151e66cee2b854ed/public/transactions-remoting/src/main/java/com/atomikos/remoting/spring/httpinvoker/TransactionalHttpInvokerServiceExporter.java#L48),会将该节点加入全局事务包装成`Event`,放入`HTTP header`返回给**business-service**: 116 | 117 | ``` 118 | @Override 119 | protected OutputStream decorateOutputStream(HttpServletRequest request, HttpServletResponse response, 120 | OutputStream os) throws IOException { 121 | 122 | ... 123 | 124 | response.addHeader(HeaderNames.EXTENT_HEADER_NAME, extent); 125 | 126 | ... 127 | 128 | return super.decorateOutputStream(request, response, os); 129 | } 130 | ``` 131 | **business-service**接收到响应,利用[TransactionalHttpInvokerRequestExecutor.validateResponse](https://github.com/atomikos/transactions-essentials/blob/4332faaf7de551e126ab60c6151e66cee2b854ed/public/transactions-remoting/src/main/java/com/atomikos/remoting/spring/httpinvoker/TransactionalHttpInvokerRequestExecutor.java#L39)解析出`Event`,注册进事务管理器,这样在全局事务提交时,可以让该分支参与到两阶段提交协议: 132 | 133 | ``` 134 | @Override 135 | protected void validateResponse(HttpInvokerClientConfiguration config, 136 | HttpURLConnection con) throws IOException { 137 | super.validateResponse(config, con); 138 | String extent = con.getHeaderField(HeaderNames.EXTENT_HEADER_NAME); 139 | template.onIncomingResponse(extent); 140 | } 141 | ``` 142 | 143 | ## 两阶段提交过程 144 | 145 | 在处理`RPC`调用的响应时,`Atomikos`会将参与到全局事务的远程节点注册为`Participants`([Extent.addRemoteParticipants](https://github.com/atomikos/transactions-essentials/blob/4332faaf7de551e126ab60c6151e66cee2b854ed/public/transactions-api/src/main/java/com/atomikos/icatch/Extent.java#L64)),在事务提交时,所有的`Participants`都会[参与到两阶段提交](https://github.com/atomikos/transactions-essentials/blob/4332faaf7de551e126ab60c6151e66cee2b854ed/public/transactions/src/main/java/com/atomikos/icatch/imp/CoordinatorImp.java#L673): 146 | 147 | 148 | ``` 149 | synchronized ( fsm_ ) { 150 | if ( commit ) { 151 | if ( participants_.size () <= 1 ) { 152 | commit ( true ); 153 | } else { 154 | int prepareResult = prepare (); 155 | // make sure to only do commit if NOT read only 156 | if ( prepareResult != Participant.READ_ONLY ) 157 | commit ( false ); 158 | } 159 | } else { 160 | rollback (); 161 | } 162 | ``` 163 | 164 | 可以看出,如果`Participants`大于1,会走`prepare`和`commit`两阶段提交的完整过程。那么`OrderService`和`StorageService`如何参与进两阶段提交呢? 165 | 166 | `Atomikos`提供了`REST`入口[com.atomikos.remoting.twopc.AtomikosRestPort](https://github.com/atomikos/transactions-essentials/blob/master/public/transactions-remoting/src/main/java/com/atomikos/remoting/twopc/AtomikosRestPort.java),你可以将`AtomikosRestPort`注册到`JAX-RS`,例如本示例选择的是[Apache CFX](https://cxf.apache.org/),在`application.properties`进行配置: 167 | 168 | ``` 169 | cxf.path=/api 170 | cxf.jaxrs.classes-scan=true 171 | cxf.jaxrs.classes-scan-packages=com.atomikos.remoting.twopc 172 | ``` 173 | 174 | **business-service**在进行全局事务提交时,会访问所有`Participants`相应的`REST`接口进行两阶段提交: 175 | 176 | 177 | 178 | **business-service**是怎么知道`AtomikosRestPort`的访问地址的呢?上面提到了,**business-service**在访问`OrderService`时,返回的响应`header`中包含了`Event`,地址就随着`Event`返回给了调用者。`AtomikosRestPort`的访问地址配置在`jta.properties`中: 179 | 180 | ``` 181 | com.atomikos.icatch.rest_port_url=http://localhost:8082/api/atomikos 182 | ``` 183 | 184 | 至此,我们解释清楚了`Atomikos`如何为微服务提供分布式事务服务的,主要解决了两个问题:事务上下文如何通过RPC在服务间传播,以及微服务如何参与进两阶段提交协议的过程。 185 | 186 | 下一步我准备为`Atomikos`增加[dubbo](https://dubbo.apache.org)的支持,即事务上下文可以通过`dubbo`进行传播。 -------------------------------------------------------------------------------- /docs/bookkeeper.md: -------------------------------------------------------------------------------- 1 | # BookKeeper实现分析 2 | 3 | ## 读写流程 4 | 5 | `Ledger` 的元数据信息保存在zookeeper中。 6 | 7 | ![ledger](https://cdn.mazhen.tech/images/202209101559715.png) 8 | 9 | ### 写入流程 10 | 11 | 当写入发生时,首先 `entry` 被写入一个 `journal` 文件。`journal` 是一个 `write-ahead log`(`WAL`),它帮助 BookKeeper 在发生故障时避免数据丢失。这与关系型数据库实现数据持久化的机制相同。 12 | 13 | 同时 `entry` 会被加入到 `write cache` 中。`write cache` 中累积的 `entry` 会定期排序,异步刷盘到 `entry log` 文件中。同一个 `ledger` 的 `entry` 排序后会被放在一起,这样有利于提高读取的性能。 14 | 15 | `write cache` 中的 `entry` 也将被写入 `RocksDB`, `RocksDB` 记录了每个 `entry` 在 `entry log` 文件中的位置,是 `(ledgerId, entryId)` 到 `(entry log file, offset)` 的映射,即可以通过`(ledgerId, entryId)`,在 `entry log` 文件中定位到 entry。 16 | 17 | ![bookie](https://cdn.mazhen.tech/images/202209101601220.png) 18 | 19 | ### 读取流程 20 | 21 | 读取时会首先查看 `write cache` ,因为 `write cache` 中有最新的 entry。如果 `write cache` 中没有,那么接着查看 `read cache`。如果 `read cache` 中还是没有,那么就通过 `(ledgerId, entryId)` 在 `RocksDB` 中查找到该 `entry` 所在的 `entry log` 文件及偏移量,然后从 `entry log` 文件中读取该 `entry` ,并更新到 `read cache` 中,以便后续请求能在 `read cache` 中命中。两层缓存让绝大多数的读取通常是从内存获取。 22 | 23 | ### 读写隔离 24 | 25 | ![bookie](https://cdn.mazhen.tech/images/202209101852204.png) BookKeeper 中的写入都是顺序写入 `journal` 文件,该文件可以存储在专用磁盘上,可以获得更大的吞吐量。`write cache` 中的 `entry` 会异步批量写入 `entry log` 文件和 `RocksDB`,通常配置在另外一块磁盘。因此,一个磁盘用于同步写入( `journal` 文件),另一个磁盘用于异步优化写入,以及所有的读取。 26 | 27 | ## 数据一致性 28 | 29 | Bookie 操作基本都是在客户端完成和实现的,比如副本复制、读写 entry 等操作。这些有助于确保 BookKeeper 的一致性。 30 | 31 | 客户端在创建 ledger 时,会出现 Ensemble、Write Quorum 和 Ack Quorum 这些数据指标。 32 | 33 | - **Ensemble** —— 用哪几台 bookie 去存储 ledger 对应的 entry 34 | 35 | - **Write Quorum** ——对于一条 entry,需要存多少副本 36 | 37 | - **Ack Quorum** —— 在写 entry 时,要等几个 response 38 | 39 | 我们会用(5,3,2)的实例进行讲述 40 | 41 | ![bookie](https://cdn.mazhen.tech/images/202209101848214.jpeg) (5,3,2) 代表了对于一个 ledger ,会挑 5 台 bookie 去存储所有的 entry。所以当 entry 0 生成时,可以根据hash模型计算出应该放置到哪台 bookie。比如 E0 对应 B1,B2,B3,那 E1 就对应 B2,B3,B4,以此类推。 42 | 43 | 虽然总体是需要 5 台 bookie,但是每条 entry 只会占用 3 台 bookie 去存放,并只需等待其中的 2 台 bookie 给出应答即可。 44 | 45 | ### LastAddConfirm 46 | 47 | LAC(LastAddConfirm)是由 LAP(LastAddPush) 延伸而来是根据客户端进行维护并发布的最后一条 entry id,从 0 开始递增。所以 LAC 是由应答确认回来的最后一条 entry id 构成,如下图右侧显示。 ![LastAddConfirm](https://cdn.mazhen.tech/images/202209101850540.png) LAC 以前的 entry ID (比它本身小的)都已确认过,它其实是一致性的边界,LAC 之前和之后的都是一致的。 同时 LAC 作为 bookie 的元数据,可以根据此来判断 entry 的确认与否。这样做的好处是,LAC 不会受限于一个集中的元数据管理,可以分散存储在存储节点。 ![LastAddConfirm](https://cdn.mazhen.tech/images/202209101855665.png) 48 | 49 | ### Ensemble change 50 | 51 | 当其中的某个 bookie 挂掉时,客户端会进行一个 ensemble change 的操作,用新的 bookie 替换挂掉的旧 bookie。比如 当bookie 4 挂掉时,可以使用 bookie 6 进行替换。 ![bookie](https://cdn.mazhen.tech/images/202209101857539.png) 整个过程,只要有新的存储节点出现,就会保证不会中断读写操作是,即过程中随时补新。 52 | 53 | Ensemble change 对应到元数据存储,即对元数据的修改。之前的 E0-E6 是写在 B1~B5 上,E7 以后写在了 B1、B2、B3、B6、B5 上。这样就可以通过元数据的方式,看到数据到底存储在那个bookie上。 ![bookie](https://cdn.mazhen.tech/images/202209101858841.png) 54 | 55 | ### Bookie Fencing 56 | 57 | BookKeeper 有一个极其重要的功能,叫做 **fencing**。**fencing** 功能让 BookKeeper 保证只有一个写入者(Pulsar broker)可以写入一个 ledger。 58 | 59 | Broker(B1)挂掉,Broker(B2)接管 B1 上topic X的流程: 60 | 61 | 1. 当前拥有 topic X 所有权的Pulsar Broker(B1)被认为已经挂掉或不可用(通过ZooKeeper)。 62 | 63 | 2. 另一个Broker(B2)将topic X 的当前 ledger 的状态从 OPEN 更新为 IN_RECOVERY。 64 | 65 | 3. B2 向当前 ledger 的所有 bookie 发送 fencing LAC read 请求,并等待(Write Quorum - Ack Quorum)+1的响应。一旦收到这个数量的回应,ledger 就已经被 fencing。B1就算还活着,也不能再写入这个ledger,因为B1无法得到 **Ack Quorum** 的确认。 66 | 67 | 4. B2采取最高的LAC响应,然后开始执行从 LAC+1 的恢复性读取。它确保从该点开始的所有 entry 都被复制到 Write Quorum 个bookie。当 B2 不能再读取和复制任何entry,ledger 完成恢复。 68 | 69 | 5. B2将 ledger 的状态改为 CLOSED。 70 | 71 | 6. B2打开一个新的 ledger,现在可以接受对Topic X的写入。 72 | 73 | 整个失效恢复的过程,是没有回头复用 ledger 的。因为复用意味着所有元素都处于相同状态且都同意后才能继续去读写,这个是很难控制的。 74 | 75 | 我们从主从复制方式进行切入,将其定义为物理文件。数据从主复制到从,由于复制过程的速度差异,为了保证所有的一致性,需要做一些「删除/清空类」的操作。但是这个过程中一旦包含覆盖的操作,就会在过程中更改文件状态,容易出现 bug。 76 | 77 | BookKeeper 在运行的过程中,不是一个物理文件,而是逻辑上的序。同时在失效恢复过程中,没有进行任何的复用,使得数据恢复变得简单又清晰。其次它在整个修复过程中,没有去额外动用 ledger X 的数据。 78 | 79 | ## 自动恢复 80 | 81 | 当一个 bookie 失败时,这个 bookie 上所有 ledger 的 fragments 都将被复制到其他节点,以确保每个 ledger 的复制系数(Write quorum)得到保证。 82 | 83 | recovery 不是 BookKeeper 复制协议的一部分,而是在 BookKeeper 外部运行,作为一种异步修复机制使用。 84 | 85 | 有两种类型的recovery:手动或自动。 86 | 87 | 自动恢复进程 AutoRecoveryMain 可以在独立的服务器上运行,也可以和bookie跑在一起。其中一个自动恢复进程被选为审核员,然后进行如下操作: 88 | 89 | 1. 从 zookeeper 读取所有的 ledger 列表,并找到位于失败 bookie上的 ledger。 90 | 91 | 2. 对于第一步找到的所有ledger,在ZooKeeper上创建一个复制任务。 92 | 93 | AutoRecoveryMain 进程会发现 ZooKeeper 上的复制任务,锁定任务,进行复制,满足Write quorum,最后删除任务。 94 | 95 | 通过自动恢复,Pulsar集群能够在面对存储层故障时进行自我修复,只需确保部署了适量的bookie就可以了。 96 | 97 | 如果审计节点失败,那么另一个节点就会被提升为审计员。 98 | -------------------------------------------------------------------------------- /docs/build_jdk_on_macos.md: -------------------------------------------------------------------------------- 1 | 2 | # 在macOS上编译和调试OpenJDK 3 | 4 | ## 获得源代码 5 | 6 | 首先从 Github 获取 OpenJDK的源代码 7 | 8 | ```bash 9 | git clone https://github.com/openjdk/jdk.git 10 | ``` 11 | 12 | ## 安装必要的软件 13 | 14 | * Xcode 15 | * App Store 中获取 16 | * Xcode Command Line Tools 17 | * 通过 `xcode-select --install` 命令安装 18 | * GNU Autoconf 19 | * 使用 `brew install autoconf` 命令安装 20 | * freetype 21 | * 使用 `brew install freetype` 命令安装 22 | * boot JDK 23 | * 构建 JDK 需要预先存在的JDK,这被称为“boot JDK”。 24 | * 经验法则是,用于构建 JDK 主版本N的 boot JDK应该是主版本 N-1 的 JDK 25 | * 建议使用 [SDKMAN!](https://mahaoliang.tech/p/sdkman%E7%9A%84%E4%BD%BF%E7%94%A8/) 来安装维护 JDK 的多个版本 26 | 27 | ## 配置构建 28 | 29 | 通过运行 `bash configure` 命令来完成配置构建。这个脚本将检查你的系统,确保所有必要的依赖项都已经满足。如果一切顺利,该脚本将汇总build的配置、将使用的工具,以及 build 将使用的硬件资源: 30 | 31 | ``` 32 | Configuration summary: 33 | * Name: macosx-x86_64-server-release 34 | * Debug level: release 35 | * HS debug level: product 36 | * JVM variants: server 37 | * JVM features: server: 'cds compiler1 compiler2 dtrace epsilongc g1gc jfr jni-check jvmci jvmti management parallelgc serialgc services shenandoahgc vm-structs zgc' 38 | * OpenJDK target: OS: macosx, CPU architecture: x86, address length: 64 39 | * Version string: 22-internal-adhoc.mazhen.jdk (22-internal) 40 | * Source date: 1689128166 (2023-07-12T02:16:06Z) 41 | 42 | Tools summary: 43 | * Boot JDK: openjdk version "20.0.1" 2023-04-18 OpenJDK Runtime Environment Temurin-20.0.1+9 (build 20.0.1+9) OpenJDK 64-Bit Server VM Temurin-20.0.1+9 (build 20.0.1+9, mixed mode, sharing) (at /Users/mazhen/.sdkman/candidates/java/20.0.1-tem) 44 | * Toolchain: clang (clang/LLVM from Xcode 14.3.1) 45 | * Sysroot: /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.3.sdk 46 | * C Compiler: Version 14.0.3 (at /usr/bin/clang) 47 | * C++ Compiler: Version 14.0.3 (at /usr/bin/clang++) 48 | 49 | Build performance summary: 50 | * Build jobs: 12 51 | * Memory limit: 16384 MB 52 | ``` 53 | 54 | ## 构建 OpenJDK 55 | 56 | 一旦配置完成,你就可以开始构建 JDK 了。 57 | 58 | ```bash 59 | make images 60 | ``` 61 | 62 | 这个命令将开始构建过程,在完成后生成一个 JDK 的 image。 63 | 64 | ## 验证构建 65 | 66 | 新构建的 JDK 在 `./build/*/images/jdk`目录下,运行命令查看JDK版本 67 | 68 | ```bash 69 | $ ./build/macosx-x86_64-server-release/images/jdk/bin/java -version 70 | openjdk version "22-internal" 2024-03-19 71 | OpenJDK Runtime Environment (build 22-internal-adhoc.mazhen.jdk) 72 | OpenJDK 64-Bit Server VM (build 22-internal-adhoc.mazhen.jdk, mixed mode, sharing) 73 | ``` 74 | 75 | ## 在VS code中调试 OpenJDK 76 | 77 | 首先在 VS code 中安装 [C++ extension for VS Code](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools)。在 VS cod 中配置C++ 开发环境可以参考这篇文档 [Using Clang in Visual Studio Code](https://code.visualstudio.com/docs/cpp/config-clang-mac)。 78 | 79 | 使用 VS code 打开 OpenJDK的源代码,在恰当的位置设置好断点,点击右上角三角运行图标,选择“**Debug C/C++ file**”: 80 | 81 | ![vs code](https://cdn.mazhen.tech/images/202307121108030.png) 82 | 83 | 然后在弹出列表中选择“**(lldb) Launch**“: 84 | 85 | ![vs code](https://cdn.mazhen.tech/images/202307121109401.png) 86 | 87 | 第一次运行会弹出错误信息,我们选择打开 `launch.json`,创建新的 debugger 配置。点击右下角的 “**add configuration...**“,在弹出的列表中选择 "**C/C++: (lldb) Launch**" 88 | 89 | ![vs code](https://cdn.mazhen.tech/images/202307121115054.png) 90 | 91 | VS code会自动添加缺省的配置,我们需要修改的是 program 和 args,设置为上面build好的 OpenJDK,以及准备运行的Java程序。 92 | 93 | ```json 94 | "program": "${workspaceFolder}/build/macosx-x86_64-server-release/images/jdk/bin/java", 95 | "args": [ 96 | "-cp", 97 | "/Users/mazhen/Documents/works/javaprojects/samples/playground/target/classes", 98 | "tech.mazhen.test.Main" 99 | ], 100 | ``` 101 | 102 | 保存文件 `launch.json`,然后重新开始调试。可以在断点处停止,但是不能定位源代码,报错如下: 103 | 104 | ``` 105 | Could not load source 'make/src/java.base/unix/native/libnio/ch/Net.c': 'SourceRequest' not supported.. 106 | ``` 107 | 108 | 为了正确的找到源代码,需要在`launch.json`中配置 [sourceFileMap](https://code.visualstudio.com/docs/cpp/launch-json-reference#_sourcefilemap),将源代码的编译时路径映射到本地源代码位置。完整的配置如下: 109 | 110 | ```json 111 | { 112 | "version": "0.2.0", 113 | "configurations": [ 114 | { 115 | "name": "(lldb) Launch", 116 | "type": "cppdbg", 117 | "request": "launch", 118 | "program": "${workspaceFolder}/build/macosx-x86_64-server-release/images/jdk/bin/java", 119 | "args": [ 120 | "-cp", 121 | "/Users/mazhen/Documents/works/javaprojects/samples/playground/target/classes", 122 | "com.apusic.test.Main" 123 | ], 124 | "stopAtEntry": false, 125 | "cwd": "${fileDirname}", 126 | "environment": [], 127 | "externalConsole": false, 128 | "MIMode": "lldb", 129 | "sourceFileMap": { 130 | "make/": "${workspaceFolder}" 131 | }, 132 | } 133 | ] 134 | } 135 | ``` 136 | 137 | 现在就可以在VS code 中正常调试OpenJDK的C++代码了。 138 | 139 | ![vs code](https://cdn.mazhen.tech/images/202307121407081.png) 140 | -------------------------------------------------------------------------------- /docs/container-innovation.md: -------------------------------------------------------------------------------- 1 | # 容器技术创新漫谈 2 | 3 | `Kubernetes`在2017年赢得了容器编排之战,使得基于`容器`+`Kubernetes`来构建`PaaS`平台成为了云计算的主流方式。在人们把关注的目光都聚焦在`Kubernetes`上时,容器技术领域在2018年也发生了很多创新,包括`amazon`最近开源的轻量级虚拟机管理器 [Firecracker](https://firecracker-microvm.github.io/),`Google`在今年5月份开源的基于用户态操作系统内核的 [gVisor](https://github.com/google/gvisor) 容器,还有更早开源的虚拟化容器项目 [KataContainers](https://katacontainers.io/),可谓百花齐放。一般的开发者可能认为容器就等于`Docker`,没想到容器领域还在发生着这么多创新。我在了解这些项目时,发现如果没有一些背景知识,很难get到它们的创新点。我试着通过这篇文章进行一次背景知识的梳理。让我们先从最基本的问题开始:操作系统是怎么工作的? 4 | 5 | ## 操作系统的工作方式 6 | 7 | 我们不去讨论操作系统的标准定义,而是想想操作系统的本质是什么,它是怎么为应用提供服务的呢? 8 | 9 | 其实操作系统很“懒”,它不是一直运行着主动干活,而是躺在那儿等着被中断“唤醒”,然后根据中断它的事件类型做下一步处理:是不是有键盘敲击、网络数据包到达,还是时间片到了该考虑进程切换,或者是有应用向内核发出了服务请求。内核处理完唤醒它的事件,会将控制权返还给应用程序,然后等着再次被中断“唤醒”。内核就是这样由中断驱动,控制着CPU、内存等硬件资源,为应用程序提供服务。 10 | 11 | ”唤醒“操作系统内核的事件主要分为三类: 12 | 13 | * **中断**:来自硬件设备的处理请求; 14 | * **异常**:当前正在执行的进程,由于非法指令或者其他原因导致执行失败而产生的异常事情处理请求,典型的如缺页异常; 15 | * **系统调用**:应用程序主动向操作系统内核发出的服务请求。系统调用的本质其实也是中断,相对于硬件设备的中断,这种中断被称为`软中断`。 16 | 17 | ### CPU的特权等级 18 | 19 | 内核代码常驻在内存中,被每个进程映射到它的**逻辑地址空间**: 20 | 21 | ![2018-12-15 12.14.10](media/container/2018-12-15%2012.14.10.png) 22 | 23 | 进程`逻辑地址空间`的最大长度与实际可用的物理内存数量无关,由`CPU`的字长决定。进程的`逻辑地址空间`划分为两个部分,分别称为`内核空间`和`用户空间`。`用户空间`是彼此独立的,而逻辑地址空间顶部的`内核空间`是被所有进程共享。 24 | 25 | 从每个进程的角度来看,地址空间中只有自身一个进程,它不会感知到其他进程的存在。由于`内核空间`被所有进程共享,为了防止进程修改彼此的数据而造成相互干扰,用户进程不能直接操作或读取`内核空间`中的数据。同时由于内核管理着所有硬件资源,也不能让用户进程直接执行`内核空间`中的代码。操作系统应该如何做到这种限制呢? 26 | 27 | 实际上,操作系统的实现依赖 `CPU` 提供的功能。现代的`CPU`体系架构都提供几种特权级别,每个特权级别有各种限制。各级别可以看作是环,内环能够访问更多的功能,外环则较少,被称为[protection rings](https://en.wikipedia.org/wiki/Protection_ring): 28 | 29 | ![protection_rings](media/container/protection_rings.png) 30 | 31 | 32 | 33 | `Intel` 的 `CPU` 提供了4种特权级别, `Linux` 只使用了 `Ring0` 和 `Ring3` 两个级别。`Ring 0` 拥有最多的特权,它可以直接和CPU、内存等物理硬件交互。 `Ring 0` 被称为`内核态`,操作系统内核正是运行在`Ring 0`。`Ring 3`被称为`用户态`,应用程序运行在`用户态`。 34 | 35 | ### 系统调用 36 | 37 | 在`用户态`禁止直接访问`内核态`,也就是说不同通过普通的函数调用方式调用内核代码,而必须使用**系统调用**陷入(`trap`)内核,完成从`用户态`到`内核态`的切换。内核首先检查进程是否允许执行想要的操作,然后代表进程执行所需的操作,完成后再返回到`用户态`。 38 | 39 | ![2018-12-15 17.41.10](media/container/2018-12-15%2017.41.10.png) 40 | 41 | 除了代表用户程序执行代码之外,内核还可以由硬件中断激活,然后在`中断上下文`中运行。另外除了普通进程,系统中还有`内核线程`在运行。`内核线程`不与任何特定的用户空间进程相关联。 42 | 43 | `CPU` 在任何时间点上的活动必然为下列三者之一 : 44 | 45 | * 运行于用户空间,执行应用程序 46 | * 运行于内核空间,处于`进程上下文`,即代表某个特定的进程执行 47 | * 运行于内核空间,处于`中断上下文`,与任何进程无关,处理某个特定的中断 48 | 49 | ### 优化系统调用 50 | 51 | 从上面的讨论可以看出,由于`系统调用`会经过`用户态`到`内核态`的切换,开销要比普通函数调用大很多,因此在进行系统级编程时,减少`用户态`与`内核态`之间的切换是一个很重要的优化方法。 52 | 53 | 例如同样是实现 `Overlay网络`,使用 `VXLAN` 完全在内核态完成封装和解封装,要比把数据包从内核态通过虚拟设备`TUN`传入用户态再进行处理要高效很多。 54 | 55 | 对应的也可以从另外一个方向进行优化:使用 [DPDK](https://www.dpdk.org/) 跳过内核网络协议栈,数据从网卡直接到达用户态,在用户态处理数据包,也就是说网络协议栈完全运行在用户态,同样避免了`用户态`和`内核态`的切换。像腾讯开源的 [F-Stack](https://github.com/f-stack/f-stack)就是一个基于[DPDK](https://www.dpdk.org/)运行在用户空间的TCP/IP协议栈。 56 | 57 | 了解了操作系统内核的基本工作方式,我们再看下一个话题:**虚拟化**。 58 | 59 | ## 虚拟化技术 60 | 61 | 为了更高效灵活的使用硬件资源,同时能够实现服务间的安全隔离,我们需要虚拟化技术。运行虚拟化软件([Hypervisor](https://en.wikipedia.org/wiki/Hypervisor)或者叫`VMM`,virtual machine monitor)的物理设施我们称之为`Host`,安装在`Hypervisor`之上的虚拟机称为`Guest`。 62 | 63 | 根据`Hypervisor`在系统中的位置,可以将它归类为`type-1`或`type-2`型。如果`hypervisor`直接运行在硬件之上,它通常被认为是`Type-1`型。如果`hypervisor`作为一个单独的层运行在操作系统之上,它将被认为是`Type 2`型。 64 | 65 | `Type-1`型`Hypervisor`的概念图: 66 | 67 | ![2018-12-15 22.22.35](media/container/2018-12-15%2022.22.35.png) 68 | 69 | `Type-2`型`Hypervisor`的概念图: 70 | 71 | ![2018-12-15 22.22.49](media/container/2018-12-15%2022.22.49.png) 72 | 73 | 74 | ### KVM & QEMU 75 | 76 | 实际上`Type-1`和`Type-2`并没有严格的区分,像最常见的虚拟化软件 [KVM](https://www.linux-kvm.org/)(Kernel-based Virtual Machine)是一个`Linux`内核模块,加载`KVM`后`Linux`内核就转换成了`Type-1 hypervisor`。同时,`Linux`还是一个通用的操作系统,也可以认为`KVM`是运行在`Linux`之上的`Type-2 hypervisor`。 77 | 78 | 为了在`Host`上创建出虚拟机,仅仅有`KVM`是不够的。对于 `I/O` 的仿真,`KVM` 还需要 [QEMU](https://www.qemu.org/)的配合。`QEMU` 是一个运行在用户空间程序,它可以仿真处理器和一系列的物理设备:磁盘、网络、`VGA`、`PCI`、`USB`、串口/并口等等,基于`QEMU`可以构造出一个完整的虚拟PC。 79 | 80 | 值得注意的是,`QEMU` 有两种运行模式:`仿真模式`和`虚拟化模式`。在仿真模式下,`QEMU`可以在一个`Intel`的`Host`上运行`ARM`或`MIPS`虚拟机。这是怎么做到的呢?实际上,`QEMU` 通过 `TCG`([Tiny Code Generator](https://en.wikipedia.org/wiki/QEMU#Tiny_Code_Generator))技术进行了二进制代码转换,可以认为这是一种高级语言的`VM`,就像`JVM`。例如可以将运行在`ARM`上的二进制转换为一种中间字节码,然后让它运行在`Host`的`Intel CPU`上。很明显,这种二进制代码转换有着巨大的性能开销。 81 | 82 | 相对应的,`QEMU`的另一种是虚拟化模式,它借助`KVM`完成处理器的虚拟化。由于和CPU的体系结构紧密关联,虚拟化模式能够带来更好的性能,限制是`Guest`必须使用和`Host`一样的CPU体系机构。这就是我们最常用到的虚拟化技术栈:`KVM/QEMU` 83 | 84 | ![2018-12-16 11.16.37](media/container/2018-12-16%2011.16.37.png) 85 | 86 | `KVM` 和 `QEMU` 有两种交互方式:通过设备文件`/dev/kvm` 和通过内存映射页面。`QEMU` 和 `KVM`之间的大块数据传递会使用内存映射页面。`/dev/kvm`是`KVM`暴露的主要API,它支持一系列`ioctl`接口,`QEMU` 使用这些接口和`KVM`交互。`/dev/kvm` API分为三个层次: 87 | 88 | * **System Level**: 用于`KVM`全局状态的维护,例如创建 `VM`; 89 | * **VM Level**: 用于处理和特定`VM`相关工作的 API,`vCPU` 就是通过这个级别的API创建出来的; 90 | * **vCPU Level**: 这是最细粒度的API,用于和特定`vCPU`的交互。`QEMU`会为每个`vCPU`分配一个专门的线程。 91 | 92 | ### CPU 虚拟化技术 VT-x 93 | 94 | `KVM`和`QEMU`配合完美,但和`CPU`的特权级别在一起就遇到了麻烦。我们知道,`hypervisor`需要管理宿主机的`CPU`、内存、`I/O`设备等资源,因此它需要运行在`ring 0`级别才能执行这些高特权操作。然而运行在`VM`中的操作系统希望得到访问所有资源的权限,它并不知道自己运行在虚拟机中。因为同一时间只有一个内核可以运行在`ring 0`,`Guest OS`不得不被“挤”到了`ring 1`,这一级别不能满足内核的需求。怎么解决? 95 | 96 | 虽然我们可以使用软件的方式进行模拟,让`hypervisor`拦截应用发往`ring 0`的系统调用,再转发给`Guest OS`,但这么做会产生额外的性能损耗,而且方案复杂难以维护。`Intel`和`AMD`认识到了虚拟化的重要性,各自独立创建了`X86`架构的扩展指令集,分别称为 `VT-x` and `AMD-V`,从`CPU`层面支持虚拟化。 97 | 98 | 以`Intel` CPU为例,`VT-x`不仅增加了虚拟化相关的指令集,还将CPU的指令划分会两种模式:`root` 和 `non-root`。`hypervisor`运行在 `root` 模式,而`VM`运行在`non-root`模式。指令在`non-root`模式的运行速度和`root`模式几乎一样,除了不能执行一些涉及CPU全局状态切换的指令。 99 | 100 | ![2018-12-16 15.03.35](media/container/2018-12-16%2015.03.35.png) 101 | 102 | `VMX`(Virtual Machine Extensions)是增加到`VT-x`中的指令集,主要有四个指令: 103 | 104 | * **VMXON**:在这个指令执行之前,CPU还没有`root` 和 `non-root`的概念。`VMXON`执行后,CPU进入虚拟化模式。 105 | * **VMXOFF**:`VMXON`的相反操作,执行`VMXOFF`退出虚拟化模式。 106 | * **VMLAUNCH**:创建一个`VM`实例,然后进入`non-root`模式。 107 | * **VMRESUME**:进入`non-root`模式,恢复前面退出的`VM`实例。当`VM`试图执行一个在`non-root`禁止的指令,CPU立即切换到root模式,类似前面介绍的系统调用`trap`方式,这就是`VM`的退出。 108 | 109 | 这样,`VT-x/KVM/QEMU` 构成了今天应用最广泛的虚拟化技术栈。 110 | 111 | 有了上面的铺垫,终于要谈到容器技术了。 112 | 113 | ## 容器的本质 114 | 115 | 虽然虚拟化技术在灵活高效的使用硬件资源方面前进了一大步,但人们还觉得远远不够。特别是在机器使用量巨大的互联网公司。因为虚拟机一旦创建,为它分配的资源就相对固定,缺乏弹性,很难再提高机器的利用率。而且创建、销毁虚拟机也是相对“重”的操作。这时候容器技术出现了。我们知道,容器依赖的底层技术,`Linux Namesapce`和`Cgroups`都是最早由Google开发,提交进Linux内核的。 116 | 117 | **容器**的本质就是一个进程,只不过对它进行了`Linux Namesapce`隔离,让它“看”不到外面的世界,用`Cgroups`限制了它能使用的资源,同时利用系统调用`pivot_root`或`chroot`切换了进程的根目录,把容器镜像挂载为根文件系统`rootfs`。`rootfs`中不仅有要运行的应用程序,还包含了应用的所有依赖库,以及操作系统的目录和文件。`rootfs`打包了应用运行的完整环境,这样就保证了在开发、测试、线上等多个场景的一致性。 118 | 119 | ![VM-Diagra](media/docker/VM-Diagram.png) 120 | 121 | 从上图可以看出,容器和虚拟机的最大区别就是,每个虚拟机都有独立的操作系统内核`Guest OS`,而容器只是一种特殊的进程,它们共享同一个操作系统内核。 122 | 123 | 看清了容器的本质,很多问题就容易理解。例如我们执行 `docker exec` 命令能够进入运行中的容器,好像登录进独立的虚拟机一样。实际上这只不过是利用系统调用`setns`,让当前进程进入到容器进程的`Namesapce`中,它就能“看到”容器内部的情况了。 124 | 125 | 由于容器就是进程,它的创建、销毁非常轻量,对资源的使用控制更加灵活,因此让`Kubernetes`这种容器编排和资源调度工具可以大显身手,通过合理的搭配,极大的提高了整个集群的资源利用率。 126 | 127 | ## 虚拟化容器技术 128 | 129 | 前面提到,运行在一个宿主机上的所有容器共享同一个操作系统内核,这种隔离级别存在着很大的潜在安全风险。因此在公有云的多租户场景下,还是需要先用虚拟机进行租户强隔离,然后用户在虚拟机上再使用容器+`Kubernetes`部署应用。 130 | 131 | 然而在`Serverless`的场景下,传统的先建虚拟机再创建容器的方式,在灵活性、执行效率方面难以满足需求。随着`Serverless`、`FaaS`(Function-as-a-Service)的兴起,各公有云厂商都将安全性容器作为了创新焦点。 132 | 133 | 一个很自然能想到的方案,是结合虚拟机的强隔离安全性+容器的轻量灵活性,这就是虚拟化容器项目 [KataContainers](https://katacontainers.io/)。 134 | 135 | [OpenStack](https://www.openstack.org/)在2017年底发布的 `KataContainers` 项目,最初是由 `Intel ClearContainer` 和 `Hyper runV` 两个项目合并而产生的。在`Kubernetes`场景下,一个`Pod`对应于`Kata Containers`启动的一个轻量化虚拟机,`Pod`中的容器,就是运行在这个轻量级虚拟机里的进程。每个`Pod`都运行在独立的操作系统内核上,从而达到安全隔离的目的。 136 | 137 | ![2018-12-16 18.16.08](media/container/2018-12-16%2018.16.08.png) 138 | 139 | 可以看出,`KataContainers` 依赖 `KVM/QEMU`技术栈。 140 | 141 | amazon最近开源的[Firecracker](https://firecracker-microvm.github.io/)也是为了实现在 functions-based services 场景下,多租户安全隔离的容器。 142 | 143 | ![firecracker_host_integration](media/container/firecracker_host_integration.png) 144 | 145 | `Firecracker`同样依赖`KVM`,然后它没有用到`QEMU`,因为`Firecracker`本身就是`QEMU`的替代实现。`Firecracker`是一个比`QEMU`更轻量级的`VMM`,它只仿真了4个设备:`virtio-net`,`virtio-block`,`serial console`和一个按钮的键盘,仅仅用来停止`microVM`。理论上,`KataContainers`可以用`Firecracker`换掉它现在使用的`QEMU`,从而将 `Firecracker`整合进`Kubernetes`生态圈。 146 | 147 | 其实Google早就没有使用`QEMU`,而且对`KVM`进行了深度定制。我们可以从这篇介绍看出端倪:[7 ways we harden our KVM hypervisor at Google Cloud: security in plaintext](https://cloud.google.com/blog/products/gcp/7-ways-we-harden-our-kvm-hypervisor-at-google-cloud-security-in-plaintext) 148 | 149 | > Non-QEMU implementation: Google does not use QEMU, the user-space virtual machine monitor and hardware emulation. Instead, we wrote our own user-space virtual machine monitor that has the following security advantages over QEMU 150 | 151 | > ... 152 | 153 | ## gVisor 154 | 155 | Google 开源的[gVisor](https://github.com/google/gvisor)为了实现安全容器另辟蹊径,它用 Go 实现了一个运行在用户态的操作系统内核,作为容器运行的`Guest Kernel`,每个容器都依赖独立的操作系统内核,实现了容器间安全隔离的目的。 156 | 157 | ![gvisor-Layers](media/container/gvisor-Layers.png) 158 | 159 | 虽然 `gVisor` 今年才开源,但它已经在[Google App Engine](https://cloud.google.com/appengine/) 和 [Google Cloud Functions](https://cloud.google.com/functions/docs/)运行了多年。 160 | 161 | ![2018-12-16 19.35.38](media/container/2018-12-16%2019.35.38.png) 162 | 163 | 164 | `gVisor`作为运行应用的安全沙箱,扮演着`Virtual kernel`的角色。同时`gVisor` 包含了一个兼容[Open Container Initiative (OCI)](https://www.opencontainers.org/) 的运行时`runsc`,因此可以用它替换掉 Docker 的 `runc`,整合进`Kubernetes`生态圈,为`Kubernetes`带来另一种安全容器的实现方案。 165 | 166 | `Kata Containers`和`gVisor`的本质都是为容器提供一个独立的操作系统内核,避免容器共享宿主机的内核而产生安全风险。`KataContainers`使用了传统的虚拟化技术,`gVisor`则自己实现了一个运行在用户态、极小的内核。`gVisor`比`KataContainers`更加轻量级,根据这个[分享](https://www.youtube.com/watch?v=pWyJahTWa4I)的介绍,`gVisor`目前只实现了211个Linux系统调用,启动时间150ms,内存占用15MB。 167 | 168 | `gVisor`实现原理,简单来说是模拟内核的行为,使用某种方式拦截应用发起的系统调用,经过`gVisor`的安全控制,代替容器进程向宿主机发起可控的系统调用。目前`gVisor`实现了两种拦截方式: 169 | 170 | * 基于[Ptrace](https://en.wikipedia.org/wiki/Ptrace) 机制的拦截 171 | * 使用 `KVM` 来进行系统调用拦截。 172 | 173 | 因为`gVisor`基于拦截系统调用的实现原理,它并不适合系统调用密集的应用。 174 | 175 | 最后,对于像我这样没有读过Linux内核代码的后端程序员,`gVisor`是一个很好的窥探内核内部实现的窗口,又激起了我研究内核的兴趣。Twitter上看到有人和我有类似的看法: 176 | 177 | ![2018-12-16 19.01.12](media/container/2018-12-16%2019.01.12-1.png) 178 | 179 | 希望下次能分享`gVisor`深入研究系列。保持好奇心,Stay hungry. Stay foolish. 180 | 181 | 182 | -------------------------------------------------------------------------------- /docs/cs_resources_for_students.md: -------------------------------------------------------------------------------- 1 | # Incomplete List of Computer Science Learning Resources for College Students 2 | 3 | cs resource 4 | 5 | *题图使用 [Microsoft Bing 图像创建](https://bing.com/create)器生成。* 6 | 7 | ## 计算机原理/体系结构 8 | 9 | * 极客时间:[深入浅出计算机组成原理](https://time.geekbang.org/column/intro/100026001) 10 | * [Computer Systems: A Programmer's Perspective](http://csapp.cs.cmu.edu/3e/home.html) 从程序员的角度学习计算机系统,了解计算机系统的各个方面,包括硬件、操作系统、编译器和网络。这本书涵盖了数据表示、C语言程序的机器级表示、处理器架构、程序优化、内存层次结构、链接、异常控制流(异常、中断、进程和Unix信号)、虚拟内存和内存管理、系统级I/O、基本的网络编程和并发编程等概念。这些概念由一系列有趣且实践性强的实验室作业支持。 11 | * [Computer Systems: A programmer's Perspective 视频课](https://www.youtube.com/playlist?list=PLyboo2CCDSWnhzzzzDQ3OBPrRiIjl-aIE) 12 | * [编码](https://book.douban.com/subject/4822685/) Code: The Hidden Language of Computer Hardware and Software 13 | * [Computer Science from the Bottom Up](https://bottomupcs.com/index.html) 采用“从下到上”的方法,从最基础的二进制、数据表示开始,逐步深入计算机内部工作原理,目的是帮助读者真正掌握计算机科学的基础知识。 14 | 15 | * [漫画计算机原理](https://book.douban.com/subject/35658408/) 16 | * [趣话计算机底层技术](https://book.douban.com/subject/36428782/) 17 | * [计算机底层的秘密](https://book.douban.com/subject/36370606/) 18 | * [穿越计算机的迷雾](https://book.douban.com/subject/30198087/) 19 | * [嵌入式C语言自我修养](https://book.douban.com/subject/35446929/) 20 | 21 | ## 编程 22 | 23 | * [征服C指针](https://book.douban.com/subject/35384099/) 彻底理解和掌握指针的各种用法和技巧 24 | * [C专家编程](https://book.douban.com/subject/35218533/) Sun公司编译器和OS核心开发团队成员,对C的历史、语言特性、声明、数组、指针、链接、运行时、内存等问题进行了细致的讲解和深入的分析 25 | * [C from Scratch](https://github.com/theokwebb/C-from-Scratch) 一个学习 C 语言的从零开始的路线图,包括推荐的课程、项目和资源,以及进阶到 x86-64 汇编语言和操作系统内部的指导。 26 | * [Online Compiler, Visual Debugger](https://pythontutor.com/) 独特的逐步可视化调试工具,强烈推荐! 27 | * 极客时间:[深入 C 语言和程序运行原理](https://time.geekbang.org/column/intro/100100701) 28 | * [Linux/UNIX系统编程手册](Linux/UNIX系统编程手册) The Linux Programming Interface: A Linux and UNIX System Programming Handbook 29 | * [UNIX环境高级编程](https://book.douban.com/subject/25900403/) Advanced Programming in the UNIX Environment 30 | 31 | ## Linux 32 | 33 | * 极客时间:[Linux 实战技能 100 讲](https://time.geekbang.org/course/intro/100029601) 34 | * [Efficient Linux at the Command Line](https://www.oreilly.com/library/view/efficient-linux-at/9781098113391/) 35 | * [像黑客一样使用命令行](https://selfhostedserver.com/usingcli) 36 | * [Linux是怎么工作的](https://book.douban.com/subject/35768243/) 37 | * [Linux技术内幕](https://book.douban.com/subject/26931513/) 38 | * [Linux Foundation](https://www.linuxfoundation.org/) 的认证考试 [LFCA](https://training.linuxfoundation.org/certification/certified-it-associate/#) 和 [LFCS](https://training.linuxfoundation.org/certification/linux-foundation-certified-sysadmin-lfcs/#) 39 | * [Linux内核设计与实现](https://book.douban.com/subject/6097773/) Linux Kernel Development 40 | * [深入理解Linux网络](https://book.douban.com/subject/35922722/) 41 | * 极客时间:[Linux 内核技术实战课](https://time.geekbang.org/column/intro/100058001) 42 | * 极客时间:[编程高手必学的内存知识](https://time.geekbang.org/column/intro/100094901) 43 | * 极客时间:[容器实战高手课](https://time.geekbang.org/column/intro/100063801) 44 | * [Learning Modern Linux](https://www.oreilly.com/library/view/learning-modern-linux/9781098108939/) 45 | * [交互式的 Linux 内核地图](https://makelinux.github.io/kernel/map/) 46 | * [Linux From Scratch](https://www.linuxfromscratch.org/lfs/) step-by-step instructions for building your own customized Linux system entirely from source. 47 | 48 | ## 网络 49 | 50 | * [趣谈网络协议](https://book.douban.com/subject/35013753/) 51 | * 极客时间:[Web 协议详解与抓包实战](https://time.geekbang.org/course/intro/100026801) 52 | * [图解TCP/IP](https://book.douban.com/subject/24737674/) 53 | * [图解HTTP](https://book.douban.com/subject/25863515/) 54 | * [网络是怎样连接的](https://book.douban.com/subject/26941639/) 55 | 56 | ## 编译原理 57 | 58 | * [程序是怎样跑起来的](https://book.douban.com/subject/26365491/) 59 | * [程序员的自我修养:链接、装载与库](https://book.douban.com/subject/3652388/) 60 | * 如何从对象文件中导入和执行代码 [part1](https://blog.cloudflare.com/how-to-execute-an-object-file-part-1/) [part2](https://blog.cloudflare.com/how-to-execute-an-object-file-part-2/) [part3](https://blog.cloudflare.com/how-to-execute-an-object-file-part-3/) 61 | 62 | ## 数据结构和算法 63 | 64 | * 极客时间:[数据结构与算法之美](https://time.geekbang.org/column/intro/100017301) 65 | * 极客时间:[算法面试通关 40 讲](https://time.geekbang.org/course/intro/100019701) 66 | * 极客时间:[常用算法 25 讲](https://time.geekbang.org/opencourse/intro/100057601) 67 | * 极客时间:算法训练营 68 | * [Hello 算法](https://www.hello-algo.com/) 动画图解、一键运行的数据结构与算法教程 69 | * [通过动画可视化数据结构和算法](https://visualgo.net/zh) 70 | 71 | ## 综合 72 | 73 | * [计算机自学指南](https://csdiy.wiki/) ([GitHub仓库](https://github.com/PKUFlyingPig/cs-self-learning)) 74 | * YouTube视频课:[Crash Course Computer Science Preview](https://www.youtube.com/watch?v=tpIctyqH29Q&list=PL8dPuuaLjXtNlUrzyH5r6jN9ulIgZBpdo) 75 | * [计算机教育中缺失的一课](https://missing-semester-cn.github.io/) 76 | * [Developer Roadmaps](https://roadmap.sh/) 为开发者提供学习路线图和指南 77 | * [Online Coding Classes – For Beginners](https://www.freecodecamp.org/news/online-coding-classes-for-beginners-2022-guide/) 3000 小时的免费课程,涵盖了编程涉及到的方方面面 78 | 79 | ## 在线课程 80 | 81 | * [educative](https://www.educative.io/) 为开发者提供交互式在线课程,重点关注技术领域的知识与技能 82 | * [edX](https://www.edx.org/) 由麻省理工学院(MIT)和哈佛大学共同创立的在线教育平台 83 | * [exercism](https://exercism.org/) 专注于通过有趣且具有挑战性的练习问题、支持建设性同行评审机制来促进积极参与和技能提升,从而培养对各种现代计算范式的熟练掌握。 84 | 85 | ## 技术面试 86 | 87 | * [Leetcode](https://leetcode.com/) 一个广受欢迎的在线编程题库 88 | * [Cracking the coding interview book](https://www.amazon.com/Cracking-Coding-Interview-Programming-Questions/dp/0984782850) 一本深受程序员喜爱的面试指南书 89 | * [Neetcode](https://neetcode.io/) 另一个在线编程练习平台 90 | * [编程面试大学](https://github.com/jwasham/coding-interview-university/blob/main/translations/README-cn.md) 涵盖了算法、数据结构、面试准备和工作机会等主题,帮助你准备大公司的技术面试 91 | * [interviewing.io](https://interviewing.io/) 一个提供模拟技术面试的平台 92 | * [Pramp](https://www.pramp.com/) 一个模拟面试平台 93 | * [Meetapro](https://www.meetapro.com/) 一个可以找到专业人士进行模拟面试的网站 94 | 95 | ## 交互式教程 96 | 97 | * [Grep by example](https://antonz.org/grep-by-example/) 如何使用命令行工具 grep 进行文本搜索的交互式指南 98 | * [Learn Git Branching](https://learngitbranching.js.org/?locale=zh_CN) 一个交互式的在线教程,帮助用户学习并练习 Git 的基本使用方法 99 | 100 | ## 大语言模型 101 | 102 | * [Learn Prompting](https://learnprompting.org/zh-Hans/docs/intro) 一个开源的、多元化社区构建的课程,旨在提供完整、公正的提示工程知识。 103 | * [提示工程指南](https://www.promptingguide.ai/zh) 介绍大语言模型(LLM)相关的论文研究、学习指南、模型、讲座、参考资料、大语言模型能力及其与其他工具的对接。 104 | * [面向开发者的大模型手册](https://datawhalechina.github.io/llm-cookbook/) 基于吴恩达大模型系列课程的翻译和复现项目,涵盖了从 Prompt Engineering 到 RAG 开发的全部流程,为国内开发者提供了学习和入门 LLM 相关项目的方式。 105 | * [LLM 应用开发实践笔记](https://aitutor.liduos.com/) 作者在学习基于大语言模型的应用开发过程中总结出来的经验和方法,包括理论学习和代码实践两部分。 106 | * [动手学大模型应用开发](https://datawhalechina.github.io/llm-universe/) 面向小白开发者的大模型应用开发教程,基于阿里云服务器,结合个人知识库助手项目,通过一个课程完成大模型开发的重点入门。 107 | 108 | ## iOS开发 109 | 110 | * [iOS & Swift - The Complete iOS App Development Bootcamp](https://www.udemy.com/course/ios-13-app-development-bootcamp/) 111 | * [The 100 Days of SwiftUI](https://www.hackingwithswift.com/100/swiftui) 112 | * [Stanford CS193p - Developing Apps for iOS](https://cs193p.sites.stanford.edu/2023) 113 | * [iOS and SwiftUI for Beginners](https://www.kodeco.com/ios/paths/learn) 114 | * [Meta iOS Developer](https://www.coursera.org/professional-certificates/meta-ios-developer) 115 | * [Develop in Swift Tutorials](https://developer.apple.com/tutorials/develop-in-swift-tutorials) 苹果官方教程 116 | * [SwiftUI Tutorials](https://developer.apple.com/tutorials/swiftui) 苹果官方教程 117 | 118 | ## 计算机科学史 119 | 120 | * [信息简史](https://book.douban.com/subject/25752043/) 121 | -------------------------------------------------------------------------------- /docs/cursor-summary.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/cursor-summary.pdf -------------------------------------------------------------------------------- /docs/distributed-transaction.md: -------------------------------------------------------------------------------- 1 | 2 | # 应用服务器的分布式事务支持和Seata的对比分析 3 | 4 | ## 应用服务器的分布式事务支持 5 | 6 | 我们先看一下分布式事务的需求是如何产生的,以及应用服务器是如何支持分布式事务管理的。 7 | 8 | * **单体应用** 9 | 10 | 首先看单体应用,所有的模块部署在一个应用服务器上,业务数据都保存在单个数据库中,这种场景本地事务就可以满足需求。 11 | 12 | local transaction 13 | 14 | * **数据库水平拆分** 15 | 16 | 如果数据库按照业务模块进行水平拆分,完成一个业务请求会涉及到跨库的资源访问和更新,这时候就需要使用应用服务器的`JTA`进行两阶段提交,保证跨库操作的事务完整性。 17 | 18 | jta 19 | 20 | * **应用模块拆分** 21 | 22 | 应用按照业务模块进一步拆分,每一个模块都作为`EJB`,部署在独立的应用服务器中。完成一个业务请求会跨越多个应用服务器节点和资源,如何在这种场景保证业务操作的事务呢?当访问入口`EJB`时`JTA`会自动开启全局事务,**事务上下文**随着`EJB`的远程调用在应用服务器之间传播,让被调用的`EJB`也加入到全局事务中。 23 | 24 | ![jts](./media/distributedtransaction/jts.png) 25 | 26 | 这就是应用因拆分而遇到分布式事务的问题,以及应用服务器是如何解决这个问题的。 27 | 28 | ## 分布式事务中间件 29 | 30 | 微服务时代,没人再使用沉重的`EJB`,都是将`Spring Bean`直接暴露为远程服务。完成一个业务请求需要跨越多个微服务,同样需要面对分布式事务的问题。这时就需要引入**分布式事务中间件**。我们以蚂蚁金服开源的[Seata](https://github.com/seata/seata)为例,看看它是怎么解决微服务场景下的分布式事务问题。 31 | 32 | 将上一小节跑在应用服务器上的业务,使用微服务 + `Seata`的重构后,部署架构如下: 33 | 34 | ![seata-microservices](./media/distributedtransaction/seata-microservices.png) 35 | 36 | 上图中黄色方框(`RM`,`TM`,`TC`)是`Seata`的核心组件,它们配合完成对微服务的分布式事务支持。可以看出,和应用服务器的`EJB`方案架构上类似,只是多了一个独立运行的`TC`组件。 37 | 38 | 我们再看看`Seata`各组件的具体作用。 39 | 40 | ### `Seata`的架构 41 | 42 | ![seata-architecture](./media/distributedtransaction/seata-architecture.png) 43 | 44 | `Seata`由三个组件构成: 45 | 46 | * **Transaction Coordinator (TC)**: 事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚。 47 | * **Transaction Manager (TM)**: 控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议。 48 | * **Resource Manager (RM)**: 控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚。 49 | 50 | 51 | ## `Seata` vs. 应用服务器 52 | 53 | `Seata`和应用服务器的分布式事务支持主要有以下四个差异: 54 | 55 | 1. `Seata`和应用服务器都可以实现**业务无侵入**分布式事务支持。但应用服务器的`XA`方案实现的是**实时一致性**,而`Seata`的`AT 模式`实现的是**最终一致性**。 56 | 2. `Seata`引入了独立运行的`Transaction Coordinator`,维护全局事务的运行状态。而应用服务器的访问入口节点承担了维护全局事务状态的职责。 57 | 3. `Seata`自己实现了`Resource Manager`,不需要依赖数据库的**XA driver**。这样就有可能将没有实现**XA**接口的资源加入的分布式事务中,例如`NoSQL`。同时,**RM**的实现要比`JTA`中的**XAResource**复杂很多。**RM**需要拦截并解析`SQL`,生成回滚语句,在事务`rollback`时自动进行数据还原。**XAResource**是对**XA driver**的包装,资源参与分布式事务的能力,都是由数据库提供的。 58 | 4. **事务上下文**的传播机制不同。应用服务器使用标准的`RMI-IIOP`协议进行**事务上下文**的跨节点传播。`Seata`是对各种`RPC`框架提供了插件,拦截请求和响应,**事务上下文**随着`RPC`调用进行跨节点传播。目前`Seata`已经支持了[dubbo](https://dubbo.apache.org/zh-cn/)、[gRPC](https://grpc.io/)、[Motan](https://github.com/weibocom/motan)和[sofa-rpc](https://github.com/sofastack/sofa-rpc)等多种`RPC`框架。 59 | 60 | `Seata`和应用服务器都支持在分布式场景下的全局事务,都可以做到对业务无侵入。`Seata`实现的是**最终一致性**,因此性能比应用服务器的`XA`方案好很多,具备海量并发处理能力,这也是互联网公司选择它的原因。由于`Seata`不依赖数据库的**XA driver**,只使用数据库的本地事务,就完成了对分布式事务的支持,相当于承担了部分数据库的职责,因此`Seata`的实现难度要比应用服务器的`JTA`大。 61 | 62 | ## 应用服务器进入微服务时代 63 | 64 | 那么应用服务器的分布式事务支持在微服务时代还有用吗?或者说我们应该怎样改进,才能让应用服务器进入微服务时代? 65 | 66 | 首先我们要看到`JTA/XA`的优势:支持数据的实时一致性,对业务开发更加友好。客户对原有的系统进行微服务改造时,如果把业务模型假定成数据最终一致性,客户就不得不做出很大的妥协和变更。特别是有些金融客户对一致性的要求会比较高。 67 | 68 | 我们可以学习`Seata`的架构,抛弃掉沉重的`EJB/RMI-IIOP`,让`Spring Bean`通过`dubbo`等`RPC`框架直接对外暴露服务,同时**事务上下文**可以在`RPC`调用时进行传递: 69 | 70 | ![jta-microservices](./media/distributedtransaction/jta-microservices.png) 71 | 72 | 我们甚至可以将`JTA`独立出来,和`Tomcat`这样的Web容器整合,为微服务架构提供分布式事务支持。相信通过这样的改造,应用服务器的分布式事务能力在微服务时代又能焕发第二春。 -------------------------------------------------------------------------------- /docs/docker-overlay-networks.md: -------------------------------------------------------------------------------- 1 | # Docker跨主机Overlay网络动手实验 2 | 3 | [上一篇文章](./docker-network-bridge.md)我演示了`docker bridge`网络模型的实验,这次我将展示如何利用`Overlay 网络`实现跨主机容器的通信。 4 | 5 | ![2018-11-11 18.18.03](media/docker/2018-11-11%2018.18.03.png) 6 | 7 | 两个容器`docker1`和`docker2`分别位于节点`Node-1`和`Node-2`,如何实现容器的跨主机通信呢?一般来说有两种实现方式: 8 | 9 | * 封包模式:利用`Overlay`网络协议在节点间建立“隧道”,容器之间的网络包被封装在外层的网络协议包中进行传输。 10 | * 路由模式:容器间的网络包传输全部用三层网络的路由转发来实现。 11 | 12 | 本文主要介绍封包模式。`Overlay`网络主要有两种方式,一种是使用UDP在用户态封装,一种是利用`VXLAN` 在内核态封装。由于减少了用户态到内核态的切换,封包解包逻辑都在内核态进行,`VXLAN` 的性能更好,成为了容器网络的主流方案。 13 | 14 | 关于路由模式,会在[下一篇文章](./docker-route-networks.md)介绍。 15 | 16 | ## VXLAN 17 | 18 | `VXLAN`(Virtual Extensible LAN)是一种网络虚拟化技术,它将链路层的以太网包封装到UDP包中进行传输。`VXLAN`最初是由VMware、Cisco开发,主要解决云环境下多租户的二层网络隔离。我们常听到公有云厂商宣称支持`VPC`(virtual private cloud),实际底层就是使用`VXLAN`实现的。 19 | 20 | `VXLAN` packet的结构: 21 | 22 | ![vxlan](media/docker/vxlan.png) 23 | 24 | 我们可以看到,最内部是原始的二层网络包,外面加上一个`VXLAN header`,其中最重要的是`VNI`(VXLAN network identifier)字段,它用来唯一标识一个`VXLAN`。也就是说,使用不同的`VNI`来区分不同的虚拟二层网络。`VNI`有24位,基本够公用云厂商使用了。要知道原先用来网络隔离的虚拟局域网VLAN只支持4096个虚拟网络。 25 | 26 | 在 `VXLAN header`外面封装了正常的UDP包。`VXLAN`在UDP之上,实现了一个虚拟的二层网络,连接在这个虚拟二层网络上的主机,就像连接在普通的局域网上一样,可以互相通信。 27 | 28 | 介绍完背景知识,我们可以开始动手实验了。 29 | 30 | ## 实现方案一 31 | 32 | 参照[Flannel](https://github.com/coreos/flannel)的实现方案: 33 | 34 | ![2018-11-11 20.35.36](media/docker/2018-11-11%2020.35.36.png) 35 | 36 | 37 | * **配置内核参数,允许IP forwarding** 38 | 39 | 分别在`Node-1`、`Node-2`上执行: 40 | 41 | ``` 42 | sudo sysctl net.ipv4.conf.all.forwarding=1 43 | ``` 44 | 45 | * **创建“容器”** 46 | 47 | 在`Node-1`上执行: 48 | 49 | ``` 50 | sudo ip netns add docker1 51 | ``` 52 | 53 | 在`Node-2`上执行: 54 | 55 | ``` 56 | sudo ip netns add docker2 57 | ``` 58 | 59 | 为什么创建个`Namesapce`就说是“容器”?请参考[上一篇文章](./docker-network-bridge.md)。 60 | 61 | * **创建Veth pairs** 62 | 63 | 分别在`Node-1`、`Node-2`上执行: 64 | 65 | ``` 66 | sudo ip link add veth0 type veth peer name veth1 67 | ``` 68 | 69 | * **将Veth的一端放入“容器”** 70 | 71 | 在`Node-1`上执行: 72 | 73 | ``` 74 | sudo ip link set veth0 netns docker1 75 | ``` 76 | 77 | 在`Node-2`上执行: 78 | 79 | ``` 80 | sudo ip link set veth0 netns docker2 81 | ``` 82 | 83 | * **创建bridge** 84 | 85 | 分别在`Node-1`、`Node-2`上创建bridge `br0`: 86 | 87 | ``` 88 | sudo brctl addbr br0 89 | ``` 90 | 91 | * **将Veth的另一端接入bridge** 92 | 93 | 分别在`Node-1`、`Node-2`上执行: 94 | 95 | ``` 96 | sudo brctl addif br0 veth1 97 | ``` 98 | 99 | * **为"容器“内的网卡分配IP地址,并激活上线** 100 | 101 | 在`Node-1`上执行: 102 | 103 | ``` 104 | sudo ip netns exec docker1 ip addr add 172.18.10.2/24 dev veth0 105 | sudo ip netns exec docker1 ip link set veth0 up 106 | ``` 107 | 108 | 在`Node-2`上执行: 109 | 110 | ``` 111 | sudo ip netns exec docker2 ip addr add 172.18.20.2/24 dev veth0 112 | sudo ip netns exec docker2 ip link set veth0 up 113 | ``` 114 | 115 | * **Veth另一端的网卡激活上线** 116 | 117 | 分别在`Node-1`、`Node-2`上执行: 118 | 119 | ``` 120 | sudo ip link set veth1 up 121 | ``` 122 | 123 | * **为bridge分配IP地址,激活上线** 124 | 125 | 在`Node-1`上执行: 126 | 127 | ``` 128 | sudo ip addr add 172.18.10.1/24 dev br0 129 | sudo ip link set br0 up 130 | ``` 131 | 132 | 在`Node-2`上执行: 133 | 134 | ``` 135 | sudo ip addr add 172.18.20.1/24 dev br0 136 | sudo ip link set br0 up 137 | ``` 138 | 139 | * **将bridge设置为“容器”的缺省网关** 140 | 141 | 在`Node-1`上执行: 142 | 143 | ``` 144 | sudo ip netns exec docker1 route add default gw 172.18.10.1 veth0 145 | ``` 146 | 147 | 在`Node-2`上执行: 148 | 149 | ``` 150 | sudo ip netns exec docker2 route add default gw 172.18.20.1 veth0 151 | ``` 152 | 153 | * **创建VXLAN虚拟网卡** 154 | 155 | `VXLAN`需要在宿主机上创建一个虚拟网络设备对 `VXLAN` 的包进行封装和解封装,实现这个功能的设备称为 `VTEP`(VXLAN Tunnel Endpoint)。宿主机之间通过`VTEP`建立“隧道”,在其中传输虚拟二层网络包。 156 | 157 | 在`Node-1`创建`vxlan100`: 158 | 159 | ``` 160 | sudo ip link add vxlan100 type vxlan \ 161 | id 100 \ 162 | local 192.168.31.183 \ 163 | dev enp0s3 \ 164 | dstport 4789 \ 165 | nolearning 166 | ``` 167 | 168 | 为`vxlan100`分配IP地址,然后激活: 169 | 170 | ``` 171 | sudo ip addr add 172.18.10.0/32 dev vxlan100 172 | sudo ip link set vxlan100 up 173 | ``` 174 | 175 | 为了让`Node-1`上访问`172.18.20.0/24`网段的数据包能进入“隧道”,我们需要增加如下的路由规则: 176 | 177 | ``` 178 | sudo ip route add 172.18.20.0/24 dev vxlan100 179 | ``` 180 | 181 | 在`Node-2`上执行相应的命令: 182 | 183 | ``` 184 | sudo ip link add vxlan100 type vxlan \ 185 | id 100 \ 186 | local 192.168.31.192 \ 187 | dev enp0s3 \ 188 | dstport 4789 \ 189 | nolearning 190 | sudo ip addr add 172.18.20.0/32 dev vxlan100 191 | sudo ip link set vxlan100 up 192 | sudo ip route add 172.18.10.0/24 dev vxlan100 scope global 193 | ``` 194 | 195 | * **手动更新ARP和FDB** 196 | 197 | 虚拟设备`vxlan100`会用`ARP`和`FDB` (forwarding database) 数据库中记录的信息,填充网络协议包,建立节点间转发虚拟网络数据包的“隧道”。 198 | 199 | 我们知道,在二层网络上传输IP包,需要先根据目的IP地址查询到目的MAC地址,这就是`ARP`(Address Resolution Protocol)协议的作用。我们应该可以通过ARP查询到其他节点上容器IP地址对应的MAC地址,然后填充在`VXLAN`内层的网络包中。 200 | 201 | `FDB`是记录网桥设备转发数据包的规则。虚拟网络数据包根据上面定义的路由规则,从`br0`进入了本机的`vxlan100`“隧道”入口,应该可以在`FDB`中查询到“隧道”出口的MAC地址应该如何到达,这样,两个`VTEP`就能完成”隧道“的建立。 202 | 203 | `vxlan`为了建立节点间的“隧道”,需要一种机制,能让一个节点的加入、退出信息通知到其他节点,可以采用`multicast`的方式进行节点的自动发现,也有很多`Unicast`的方案,这篇文章[](https://vincent.bernat.ch/en/blog/2017-vxlan-linux)有很详细的介绍。总之就是要找到一种方式,能够更新每个节点的`ARP`和`FDB`数据库。 204 | 205 | 如果是使用[Flannel](https://github.com/coreos/flannel),它在节点启动的时候会采用某种机制自动更新其他节点的ARP和FDB数据库。现在我们的实验只能在两个节点上手动更新`ARP`和`FDB`。 206 | 207 | 首先在两个节点上查询到设备`vxlan100`的MAC地址,例如在我当前的环境: 208 | 209 | `Node-1`上`vxlan100`的MAC地址是`3a:8d:b8:69:10:3e` 210 | `Node-2`上`vxlan100`的MAC地址是`0e:e6:e6:5d:c2:da` 211 | 212 | 然后在`Node-1`上增加ARP和FDB的记录: 213 | 214 | ``` 215 | sudo ip neighbor add 172.18.20.2 lladdr 0e:e6:e6:5d:c2:da dev vxlan100 216 | sudo bridge fdb append 0e:e6:e6:5d:c2:da dev vxlan100 dst 192.168.31.192 217 | ``` 218 | 219 | 我们可以确认下执行结果: 220 | 221 | ![2018-11-11 23.12.47](media/docker/2018-11-11%2023.12.47.png) 222 | 223 | `ARP`中已经记录了`Node-2`上容器IP对应的MAC地址。再看看`FDB`的情况: 224 | 225 | ![2018-11-11 23.12.47](media/docker/2018-11-11%2023.15.21.png) 226 | 227 | 根据最后一条新增规则,我们可以知道如何到达`Node-2`上“隧道”的出口`vxlan100`。“隧道”两端是使用UDP进行传输,即容器间通讯的二层网络包是靠UDP在宿主机之间通信。 228 | 229 | 类似的,在`Node-2`上执行下面的命令: 230 | 231 | ``` 232 | sudo ip neighbor add 172.18.10.2 lladdr 3a:8d:b8:69:10:3e dev vxlan100 233 | sudo bridge fdb append 3a:8d:b8:69:10:3e dev vxlan100 dst 192.168.31.183 234 | ``` 235 | 236 | * **测试容器的跨节点通信** 237 | 238 | 现在,容器`docker1`和`docker1`之间就可以相互访问了。 239 | 240 | 我们从`docker1`访问`docker2`,在`Node-1`上执行: 241 | 242 | ``` 243 | sudo ip netns exec docker1 ping -c 3 172.18.20.2 244 | ``` 245 | 246 | 同样可以从`docker2`访问`docker1`,在`Node-2`上执行: 247 | 248 | ``` 249 | sudo ip netns exec docker2 ping -c 3 172.18.10.2 250 | ``` 251 | 252 | 在测试过程中如果需要troubleshooting,可以使用`tcpdump`在`veth1`、`br0`、`vxlan100`等虚拟设备上抓包,确认网络包是按照预定路线在转发: 253 | 254 | ``` 255 | sudo tcpdump -i vxlan100 -n 256 | ``` 257 | 258 | * **测试环境恢复** 259 | 260 | 在两个节点上删除我们创建的虚拟设备: 261 | 262 | ``` 263 | sudo ip link set br0 down 264 | sudo brctl delbr br0 265 | sudo ip link del veth1 266 | sudo ip link del vxlan100 267 | ``` 268 | 269 | ## 实现方案二 270 | 271 | Docker原生的[overlay driver](https://docs.docker.com/network/overlay/)底层也是使用`VXLAN`技术,但实现方案和[Flannel](https://github.com/coreos/flannel)略有不同: 272 | 273 | ![2018-11-12 08.32.09](media/docker/2018-11-12%2008.32.09.png) 274 | 275 | 我们可以看到,`vxlan100`被“插”在了虚拟交换机`br0`上,虚拟网络数据包从`br0`到`vxlan100`不是通过本机路由,而是`vxlan100`根据`FDB`直接进行了转发。 276 | 277 | 执行的命令略有差异,我不再赘述过程,直接提供了命令,大家自己实验吧: 278 | 279 | ``` 280 | # 在Node-1上执行 281 | sudo sysctl net.ipv4.conf.all.forwarding=1 282 | 283 | sudo ip netns add docker1 284 | 285 | sudo ip link add veth0 type veth peer name veth1 286 | 287 | sudo ip link set veth0 netns docker1 288 | 289 | sudo brctl addbr br0 290 | 291 | sudo brctl addif br0 veth1 292 | 293 | sudo ip netns exec docker1 ip addr add 172.18.10.2/24 dev veth0 294 | sudo ip netns exec docker1 ip link set veth0 up 295 | sudo ip link set veth1 up 296 | 297 | sudo ip link set br0 up 298 | 299 | sudo ip netns exec docker1 route add default veth0 300 | 301 | sudo ip link add vxlan100 type vxlan \ 302 | id 100 \ 303 | local 192.168.31.183 \ 304 | dev enp0s5 \ 305 | dstport 4789 \ 306 | nolearning \ 307 | proxy 308 | 309 | sudo ip link set vxlan100 up 310 | 311 | sudo brctl addif br0 vxlan100 312 | 313 | 314 | sudo ip neigh add 172.18.20.2 lladdr [docker2的MAC地址] dev vxlan100 315 | sudo bridge fdb append [docker2的MAC地址] dev vxlan100 dst 192.168.31.192 316 | 317 | # 在Node-2上执行 318 | sudo sysctl net.ipv4.conf.all.forwarding=1 319 | 320 | sudo ip netns add docker2 321 | 322 | sudo ip link add veth0 type veth peer name veth1 323 | 324 | sudo ip link set veth0 netns docker2 325 | 326 | sudo brctl addbr br0 327 | 328 | sudo brctl addif br0 veth1 329 | 330 | sudo ip netns exec docker2 ip addr add 172.18.20.2/24 dev veth0 331 | sudo ip netns exec docker2 ip link set veth0 up 332 | sudo ip link set veth1 up 333 | 334 | sudo ip link set br0 up 335 | 336 | sudo ip netns exec docker2 route add default veth0 337 | 338 | sudo ip link add vxlan100 type vxlan \ 339 | id 100 \ 340 | local 192.168.31.192 \ 341 | dev enp0s5 \ 342 | dstport 4789 \ 343 | nolearning \ 344 | proxy 345 | 346 | sudo ip link set vxlan100 up 347 | 348 | sudo brctl addif br0 vxlan100 349 | 350 | 351 | sudo ip neigh add 172.18.10.2 lladdr [docker1的MAC地址] dev vxlan100 352 | sudo bridge fdb append [docker1的MAC地址] dev vxlan100 dst 192.168.31.183 353 | ``` 354 | 355 | 相信通过亲自动手实验,容器网络对你来说不再神秘。希望本文对你理解容器网络有所帮助。 356 | 357 | 下一篇我将动手实验容器跨主机通信的[路由模式](./docker-route-networks.md)。 358 | 359 | 360 | -------------------------------------------------------------------------------- /docs/docker-route-networks.md: -------------------------------------------------------------------------------- 1 | # Docker跨主机通信路由模式动手实验 2 | 3 | 容器的跨主机通信主要有两种方式:封包模式和路由模式。[上一篇文章](./docker-overlay-networks.md)演示了使用`VXLAN`协议的封包模式,这篇将介绍另一种方式,利用三层网络的路由转发实现容器的跨主机通信。 4 | 5 | ## 路由模式概述 6 | 7 | 宿主机将它负责的容器IP网段,以某种方式告诉其他节点,然后每个节点根据收到的`<宿主机-容器IP网段>`映射关系,配置本机路由表。 8 | 9 | 这样对于容器间跨节点的IP包,就可以根据本机路由表获得到达目的容器的网关地址,即目的容器所在的宿主机地址。接着在把IP包封装成二层网络数据帧时,将目的MAC地址设置为网关的MAC地址,IP包就可以通过二层网络送达目的容器所在的宿主机。 10 | 11 | 至于用什么方式将`<宿主机-容器IP网段>`映射关系发布出去,不同的项目采用了不同的实现方案。 12 | 13 | [Flannel](https://github.com/coreos/flannel)是将这些信息集中存储在[etcd](https://github.com/etcd-io/etcd)中,每个节点从[etcd](https://github.com/etcd-io/etcd)自动获取数据,更新宿主机路由表。 14 | 15 | [Calico](https://github.com/projectcalico/calico/)则使用[BGP](https://en.wikipedia.org/wiki/Border_Gateway_Protocol)(Border Gateway Protocol)协议交换共享路由信息,每个宿主机都是运行在它之上的容器的`边界网关`。 16 | 17 | 如果宿主机之间跨了网段怎么办?宿主机之间的二层网络不通,虽然知道目的容器所在的宿主机,但没办法将目的MAC地址设置为那台宿主机的MAC地址。 18 | 19 | `Calico`有两种解决方案: 20 | 21 | * IPIP 模式,在跨网段的宿主机之间建立“隧道” 22 | * 让宿主机之间的路由器“学习”到容器路由规则,每个路由器都知道某个容器IP网段是哪个宿主机负责的,容器间的IP包就能正常路由了。 23 | 24 | ## 动手实验 25 | 26 | ![2018-11-15 23.42.06](media/docker/2018-11-15%2023.42.06.png) 27 | 28 | 29 | 路由模式的实验比较简单,关键在于宿主机上路由规则的配置。为了简化实验,这些路由规则都是我们手工配置,而且两个节点之间二层网络互通,没有跨网段。 30 | 31 | 参照[Docker跨主机Overlay网络动手实验](./docker-overlay-networks.md),创建“容器”,`veth pairs`,`bridge`,设置IP,激活虚拟设备。 32 | 33 | 然后在`node-1`上增加路由规则: 34 | 35 | ``` 36 | sudo ip route add 172.18.20.0/24 via 192.168.31.192 37 | ``` 38 | 39 | 在`node-2`上增加路由规则: 40 | 41 | ``` 42 | sudo ip route add 172.18.10.0/24 via 192.168.31.183 43 | ``` 44 | 45 | 当`docker1`访问`docker2`时,IP包会从`veth`到达`br0`,然后根据`node-1`上刚设置的路由规则,访问`172.18.20.0/24`网段的网关地址为`node-2`,这样,IP包就能路由到`node-2`了。 46 | 47 | 同时,`node-2`的路由表中包含这样的一条规则: 48 | 49 | ``` 50 | $ ip route 51 | 52 | ... 53 | 172.18.20.0/24 dev br0 proto kernel scope link src 172.18.20.1 54 | ``` 55 | 56 | 到达`node-2`的IP包,会根据这条规则路由到网桥`br0`,最终到达`docker-2`。反过来从`docker2`访问`docker1`的过程也是类似。 57 | 58 | 59 | ## 总结 60 | 61 | 两种容器跨主机的通信方案我们都实验了一下,现在做个简单总结对比: 62 | 63 | * **封包模式**对基础设施要求低,三层网络通就可以了。但封包、解包带来的性能损耗较大。 64 | * **路由模式**性能好,但要求二层网络连通,或者在跨网段的情况下,要求路由器能配合“学习”路由规则。 65 | 66 | 67 | 至此,容器网络的三篇系列完成: 68 | 69 | * [Docker单机网络模型动手实验](./docker-network-bridge.md) 70 | * [Docker跨主机Overlay网络动手实验](./docker-overlay-networks.md) 71 | * Docker跨主机通信路由模式动手实验(本篇) 72 | 73 | -------------------------------------------------------------------------------- /docs/gcc-loongarch64.md: -------------------------------------------------------------------------------- 1 | # GCC 为龙芯 CPU的预定义宏 2 | 3 | GCC 会为不同 CPU 架构预定义宏,如 `__x86_64__` 代表Intel 64位CPU, `__aarch64__`代表 ARM64。 网上已经有文档对 GCC 为 CPU 的预定义宏进行了[总结](https://sourceforge.net/p/predef/wiki/Architectures/)。 4 | 5 | 这些预定义的宏有什么用呢?我们在代码中可以判断出当前的 CPU 架构,那么可以针对 不同CPU的特性,进行优化实现。例如`RocksDB` 对于获取当前时间,在 x86 平台上,会用到 [Time Stamp Counter (TSC)](https://en.wikipedia.org/wiki/Time_Stamp_Counter) 寄存器,使用 `RDTSC` 指令提取 TSC 中值。对于 ARM 64 也有类似的实现: 6 | 7 | ```c 8 | // Get the value of tokutime for right now. We want this to be fast, so we 9 | // expose the implementation as RDTSC. 10 | static inline tokutime_t toku_time_now(void) { 11 | #if defined(__x86_64__) || defined(__i386__) 12 | uint32_t lo, hi; 13 | __asm__ __volatile__("rdtsc" : "=a"(lo), "=d"(hi)); 14 | return (uint64_t)hi << 32 | lo; 15 | #elif defined(__aarch64__) 16 | uint64_t result; 17 | __asm __volatile__("mrs %[rt], cntvct_el0" : [ rt ] "=r"(result)); 18 | return result; 19 | #elif defined(__powerpc__) 20 | return __ppc_get_timebase(); 21 | #elif defined(__s390x__) 22 | uint64_t result; 23 | asm volatile("stckf %0" : "=Q"(result) : : "cc"); 24 | return result; 25 | #else 26 | #error No timer implementation for this platform 27 | #endif 28 | } 29 | ``` 30 | 31 | 而在将 `RocksDB` 移植到龙芯的过程中,需要修改上面的代码,判断出当前是龙芯 `loongarch64` 架构。 32 | 33 | 网上没有搜到 GCC 对龙芯 CPU 的预定宏的文档说明,只能从[源码](https://github.com/gcc-mirror/gcc/blob/master/gcc/config/loongarch/loongarch-c.cc)中找答案: 34 | 35 | ```c 36 | void 37 | loongarch_cpu_cpp_builtins (cpp_reader *pfile) 38 | { 39 | ... 40 | builtin_define ("__loongarch__"); 41 | ... 42 | } 43 | ``` 44 | 45 | 可以看到,`__loongarch__`代表龙芯CPU。 在暂时不知道龙芯是否支持`RDTSC`的情况下,只能给出通用的实现,以后再查龙芯的CPU手册进行优化。 46 | 47 | ```c 48 | #if defined(__x86_64__) || defined(__i386__) 49 | ... 50 | #elif defined(__aarch64__) 51 | ... 52 | #elif defined(__powerpc__) 53 | ... 54 | #elif defined(__loongarch__) 55 | struct timeval tv; 56 | gettimeofday(&tv,NULL); 57 | return tv.tv_sec*(uint64_t)1000000+tv.tv_usec; 58 | #else 59 | #error No implementation for this platform 60 | #endif 61 | 62 | ``` 63 | -------------------------------------------------------------------------------- /docs/glassfish_startup_cn.md: -------------------------------------------------------------------------------- 1 | # GlassFish 启动流程 2 | 3 | [asadmin](https://glassfish.org/docs/latest/reference-manual.html#asadmin) 是 [GlassFish](https://glassfish.org/) 的命令行工具,它提供了一系列子命令,使用 `asadmin` 可以让你完成 `Glassfish` 的所有管理任务。 4 | 5 | 使用 `asadmin` 的子命令 [start-domain](https://glassfish.org/docs/latest/reference-manual.html#start-domain) 可以启动 `GlassFish`。下面将描述 `GlassFish`启动过程的主要流程。先从 `asadmin` 命令的执行开始。 6 | 7 | ## asadmin 执行流程 8 | 9 | `asadmin` 命令的入口是 [org.glassfish.admin.cli.AsadminMain](https://github.com/eclipse-ee4j/glassfish/blob/master/appserver/admin/cli/src/main/java/org/glassfish/admin/cli/AsadminMain.java), 包含在 `${AS_INSTALL_LIB}/client/appserver-cli.jar`包中。 10 | 11 | `AsadminMain` 执行的主要流程如下: 12 | 13 | ![AsadminMain](https://cdn.mazhen.tech/images/202307191450899.png) 14 | 15 | 其中的一些关键点: 16 | 17 | * 调用`CLICommand.getCommand()`获得启动服务器的子命令。`asadmin` 的所有子命令的都继承自[com.sun.enterprise.admin.cli.CLICommand](https://github.com/eclipse-ee4j/glassfish/blob/master/nucleus/admin/cli/src/main/java/com/sun/enterprise/admin/cli/CLICommand.java) ,从下列目录或 Jar 中加载: 18 | 19 | * ${com.sun.aas.installRoot}/lib/asadmin 20 | 21 | * ${com.sun.aas.installRoot}/modules/admin-cli.jar 22 | 23 | * 所有子命令的执行都是调用`CLICommand.execute(String... argv)` 。 24 | 25 | * 启动 `GlassFish` 的子命令实现类为[StartDomainCommand](https://github.com/eclipse-ee4j/glassfish/blob/master/nucleus/admin/server-mgmt/src/main/java/com/sun/enterprise/admin/servermgmt/cli/StartDomainCommand.java),内部调用 `GFLauncher.launch()`启动服务器。 26 | 27 | * 最终 [GFLauncher](https://github.com/eclipse-ee4j/glassfish/blob/master/nucleus/admin/launcher/src/main/java/com/sun/enterprise/admin/launcher/GFLauncher.java) 使用 [ProcessBuilder](https://docs.oracle.com/javase/8/docs/api/index.html?java/lang/ProcessBuilder.html) 启动一个新的进程,就是 GlassFish 的主进程。这个新进程的入口是[com.sun.enterprise.glassfish.bootstrap.ASMain](https://github.com/eclipse-ee4j/glassfish/blob/master/nucleus/core/bootstrap/src/main/java/com/sun/enterprise/glassfish/bootstrap/ASMain.java)。 28 | 29 | 另外,如果设置了 `verbose` 或 `watchdog` 参数,作为父进程的`asadmin` 不会退出,一直等到 GlassFish 运行结束: 30 | 31 | ```java 32 | // If verbose, hang around until the domain stops 33 | if (getInfo().isVerboseOrWatchdog()) { 34 | wait(glassFishProcess); 35 | } 36 | ``` 37 | 38 | 下面分析 `GlassFish` 主进程的启动流程。 39 | 40 | ## 主进程启动流程 41 | 42 | `GlassFish` 主进程的入口是 [com.sun.enterprise.glassfish.bootstrap.ASMain](https://github.com/eclipse-ee4j/glassfish/blob/master/nucleus/core/bootstrap/src/main/java/com/sun/enterprise/glassfish/bootstrap/ASMain.java) 的 `main`方法,启动过程的主要流程如下: 43 | 44 | ![glassfish startup](https://cdn.mazhen.tech/images/202307210950607.png) 45 | 46 | 启动过程比较复杂,但主要步骤很清晰: 47 | 48 | 1. 使用 [RuntimeBuilder](https://github.com/eclipse-ee4j/glassfish/blob/master/nucleus/common/simple-glassfish-api/src/main/java/org/glassfish/embeddable/spi/RuntimeBuilder.java) 创建 [GlassFishRuntime](https://github.com/eclipse-ee4j/glassfish/blob/master/nucleus/common/simple-glassfish-api/src/main/java/org/glassfish/embeddable/GlassFishRuntime.java) 49 | 1. 使用 [GlassFishRuntime](https://github.com/eclipse-ee4j/glassfish/blob/master/nucleus/common/simple-glassfish-api/src/main/java/org/glassfish/embeddable/GlassFishRuntime.java) 创建 [GlassFish](https://github.com/eclipse-ee4j/glassfish/blob/master/nucleus/common/simple-glassfish-api/src/main/java/org/glassfish/embeddable/GlassFish.java) 实例 50 | 1. 调用 `GlassFish.start()` 启动 `Glassfish` 实例 51 | 52 | ### 创建 GlassFishRuntime 53 | 54 | 创建 `GlassFishRuntime` 的主要步骤包括: 55 | 56 | 1. 创建 [RuntimeBuilder](https://github.com/eclipse-ee4j/glassfish/blob/master/nucleus/common/simple-glassfish-api/src/main/java/org/glassfish/embeddable/spi/RuntimeBuilder.java) 57 | 2. 创建并初始化 OSGi [Framework](https://github.com/osgi/osgi/blob/main/org.osgi.framework/src/org/osgi/framework/launch/Framework.java) 58 | 3. 加载 OSGi bundles 59 | 4. 启动 OSGi [Framework](https://github.com/osgi/osgi/blob/main/org.osgi.framework/src/org/osgi/framework/launch/Framework.java) 60 | 5. 启动 bundles 中的 [BundleActivator](https://github.com/osgi/osgi/blob/main/org.osgi.framework/src/org/osgi/framework/BundleActivator.java) 61 | 6. 在 [HK2Main](https://github.com/eclipse-ee4j/glassfish-hk2/blob/master/osgi/adapter/src/main/java/org/jvnet/hk2/osgiadapter/HK2Main.java) 的启动过程中查找并注册 HK2 modules 62 | 63 | 在创建 `GlassFishRuntime`的过程中,[OSGiGlassFishRuntimeBuilder](https://github.com/eclipse-ee4j/glassfish/blob/master/nucleus/core/bootstrap/src/main/java/com/sun/enterprise/glassfish/bootstrap/osgi/OSGiGlassFishRuntimeBuilder.java) 会创建并初始化 OSGi [Framework](https://github.com/osgi/osgi/blob/main/org.osgi.framework/src/org/osgi/framework/launch/Framework.java) ,然后使用 [BundleProvisioner](https://github.com/eclipse-ee4j/glassfish/blob/master/nucleus/core/bootstrap/src/main/java/com/sun/enterprise/glassfish/bootstrap/osgi/BundleProvisioner.java) 的 [installBundles()](https://github.com/eclipse-ee4j/glassfish/blob/master/nucleus/core/bootstrap/src/main/java/com/sun/enterprise/glassfish/bootstrap/osgi/BundleProvisioner.java#L169) 方法向 OSGi 安装 GlassFish 的所有 bundles。 64 | 65 | `BundleProvisioner`从哪里找到要加载的 bundles?`config/osgi.properties` 文件中的 `glassfish.osgi.auto.install` 属性定义了 OSGi bundles 的加载路径。`BundleProvisioner.discoverJars()` 方法会扫描这些路径,发现需要加载的 Jar 包。 66 | 67 | 在完成 bundles 的加载后,`OSGiGlassFishRuntimeBuilder`会调用 `Framework.start()` 启动 OSGi [Framework](https://github.com/osgi/osgi/blob/main/org.osgi.framework/src/org/osgi/framework/launch/Framework.java)。 Framework 的启动过程中,bundles 中的 [BundleActivator](https://github.com/osgi/osgi/blob/main/org.osgi.framework/src/org/osgi/framework/BundleActivator.java) 会被启动。其中两个重要的 `BundleActivator` 是: 68 | 69 | * [GlassFishMainActivator](https://github.com/eclipse-ee4j/glassfish/blob/master/nucleus/core/bootstrap/src/main/java/com/sun/enterprise/glassfish/bootstrap/osgi/GlassFishMainActivator.java) 70 | * [HK2Main](https://github.com/eclipse-ee4j/glassfish-hk2/blob/master/osgi/adapter/src/main/java/org/jvnet/hk2/osgiadapter/HK2Main.java) 71 | 72 | [GlassFishMainActivator](https://github.com/eclipse-ee4j/glassfish/blob/master/nucleus/core/bootstrap/src/main/java/com/sun/enterprise/glassfish/bootstrap/osgi/GlassFishMainActivator.java) 启动过程中会向 OSGi 中注册 [EmbeddedOSGiGlassFishRuntime](https://github.com/eclipse-ee4j/glassfish/blob/master/nucleus/core/bootstrap/src/main/java/com/sun/enterprise/glassfish/bootstrap/osgi/EmbeddedOSGiGlassFishRuntime.java)。 73 | 74 | [HK2Main](https://github.com/eclipse-ee4j/glassfish-hk2/blob/master/osgi/adapter/src/main/java/org/jvnet/hk2/osgiadapter/HK2Main.java) 会创建 [ModulesRegistry](https://github.com/eclipse-ee4j/glassfish-hk2/blob/master/hk2-core/src/main/java/com/sun/enterprise/module/ModulesRegistry.java)。[ModulesRegistry](https://github.com/eclipse-ee4j/glassfish-hk2/blob/master/hk2-core/src/main/java/com/sun/enterprise/module/ModulesRegistry.java) 是 HK2 的关键组件,HK2 中的 modules 都注册在这里。在 OSGi 环境下,ModulesRegistry 的具体实现类是 [OSGiModulesRegistryImpl](https://github.com/eclipse-ee4j/glassfish-hk2/blob/master/osgi/adapter/src/main/java/org/jvnet/hk2/osgiadapter/OSGiModulesRegistryImpl.java),它会从所有 bundle Jar 的 `META-INF/hk2-locator` 目录中查找并注册该 bundle 包含的 HK2 modules。 75 | 76 | `ModulesRegistry` 和 `HK2Main` 都会注册为 OSGi 的 service。 77 | 78 | ### 创建 GlassFish 实例 79 | 80 | 通过`GlassFishRuntime.newGlassFish()` 创建出 GlassFish 实例,这个过程主要做了两件事: 81 | 82 | 1. 创建出 HK2 的 [ServiceLocator](https://github.com/eclipse-ee4j/glassfish-hk2/blob/master/hk2-api/src/main/java/org/glassfish/hk2/api/ServiceLocator.java) 83 | 2. 从 [ServiceLocator](https://github.com/eclipse-ee4j/glassfish-hk2/blob/master/hk2-api/src/main/java/org/glassfish/hk2/api/ServiceLocator.java) 获取 [ModuleStartup](https://github.com/eclipse-ee4j/glassfish-hk2/blob/master/hk2-core/src/main/java/com/sun/enterprise/module/bootstrap/ModuleStartup.java) 84 | 85 | 在 `EmbeddedOSGiGlassFishRuntime` 中使用 [ModulesRegistry.newServiceLocator()](https://github.com/eclipse-ee4j/glassfish-hk2/blob/master/hk2-core/src/main/java/com/sun/enterprise/module/ModulesRegistry.java#L44) 创建出 `ServiceLocator`,然后从 `ServiceLocator` 获取 [ModuleStartup](https://github.com/eclipse-ee4j/glassfish-hk2/blob/master/hk2-core/src/main/java/com/sun/enterprise/module/bootstrap/ModuleStartup.java)。在 GlassFish 启动场景获取的是 `ModuleStartup` 的一个具体实现 [AppServerStartup](https://github.com/eclipse-ee4j/glassfish/blob/master/nucleus/core/kernel/src/main/java/com/sun/enterprise/v3/server/AppServerStartup.java)。 86 | 87 | `ServiceLocator` 是 HK2 service 的注册表,它提供了一系列获取 HK2 service 的方法。 88 | 89 | HK2 Module 和 Service 的关系可以看作是容器和内容的关系。Module(容器)包含了一组 Service(内容),并且负责将这些 Service 注册到 `ServiceLocator` 中。当一个 Module 被初始化时,它的所有 Service 都会被注册到 `ServiceLocator` 中,然后这些 Service 就可以被其他 Service 查找和使用。 90 | 91 | 最后将 `AppServerStartup` 和 `ServiceLocator` 作为构造函数的参数,创建出 `GlassFish` 实例。 92 | 93 | ### 启动 Glassfish 实例 94 | 95 | 使用 `GlassFish.start()` 启动 `Glassfish` 实例。其中最关键的步骤是调用 `AppServerStartup.start()`,分级启动 HK2 的 service。HK2 的 service 可以指定启动级别,级别越低,越先启动。 96 | 97 | `AppServerStartup.start()` 运行完成,所有 service 启动,Glassfish 完成启动并运行。 98 | -------------------------------------------------------------------------------- /docs/helm-quickstart.md: -------------------------------------------------------------------------------- 1 | 2 | # Helm的安装和使用 3 | 4 | ## 安装helm客户端 5 | 6 | 在`macOS`上安装很简单: 7 | 8 | ``` 9 | brew install kubernetes-helm 10 | ``` 11 | 12 | 其他平台请参考[Installing Helm](https://helm.sh/docs/using_helm/#installing-helm) 13 | 14 | ## 配置RBAC 15 | 16 | 定义`rbac-config.yaml`文件,创建`tiller`账号,并和`cluster-admin`绑定: 17 | 18 | ``` 19 | apiVersion: v1 20 | kind: ServiceAccount 21 | metadata: 22 | name: tiller 23 | namespace: kube-system 24 | --- 25 | apiVersion: rbac.authorization.k8s.io/v1 26 | kind: ClusterRoleBinding 27 | metadata: 28 | name: tiller 29 | roleRef: 30 | apiGroup: rbac.authorization.k8s.io 31 | kind: ClusterRole 32 | name: cluster-admin 33 | subjects: 34 | - kind: ServiceAccount 35 | name: tiller 36 | namespace: kube-system 37 | ``` 38 | 39 | 执行命令: 40 | 41 | ``` 42 | $ kubectl create -f rbac-config.yaml 43 | serviceaccount "tiller" created 44 | clusterrolebinding "tiller" created 45 | ``` 46 | 47 | ## 安装Tiller镜像 48 | 49 | 在强国环境内,需要参考[kubernetes-for-china](https://github.com/maguowei/kubernetes-for-china),将`helm`服务端部分`Tiller`的镜像下载到集群节点上。 50 | 51 | 52 | ## 初始化helm 53 | 54 | 执行初始化命令,注意指定上一步创建的`ServiceAccount`: 55 | 56 | ``` 57 | helm init --service-account tiller --history-max 200 58 | ``` 59 | 60 | 命令执行成功,会在集群中安装`helm`的服务端部分`Tiller`。可以使用`kubectl get pods -n kube-system`命令查看: 61 | 62 | ``` 63 | $kubectl get pods -n kube-system 64 | 65 | NAME READY STATUS RESTARTS AGE 66 | ... 67 | tiller-deploy-7fbf5fc745-lxzxl 1/1 Running 0 179m 68 | ``` 69 | 70 | ## Quickstart 71 | 72 | * 增加`Chart Repository`(可选) 73 | 74 | 查看helm的`Chart Repository`: 75 | 76 | ``` 77 | $ helm repo list 78 | 79 | NAME URL 80 | stable https://kubernetes-charts.storage.googleapis.com 81 | local http://127.0.0.1:8879/charts 82 | ``` 83 | 84 | 如果你所处的网络环境无法访问缺省的`Chart Repository`,可以更换为其他repo,例如微软提供的 helm 仓库的镜像: 85 | 86 | ``` 87 | $ helm repo add stable http://mirror.azure.cn/kubernetes/charts/ 88 | "stable" has been added to your repositories 89 | 90 | $ helm repo add incubator http://mirror.azure.cn/kubernetes/charts-incubator/ 91 | "incubator" has been added to your repositories 92 | ``` 93 | 94 | * 所有可用`chart`列表: 95 | 96 | ``` 97 | helm repo update 98 | helm search 99 | ``` 100 | 101 | * 搜索`tomcat chart`: 102 | 103 | ``` 104 | helm search tomcat 105 | ``` 106 | 107 | * 查看`stable/tomcat`的详细信息 108 | ``` 109 | helm inspect stable/tomcat 110 | ``` 111 | 112 | `stable/tomcat`使用 `sidecar` 方式部署web应用,通过参数`image.webarchive.repository`指定`war`的镜像,不指定会部署缺省的`sample`应用。 113 | 114 | * 安装`tomcat`: 115 | 116 | 如果是在私有化集群部署,设置`service.type`为`NodePort`: 117 | 118 | ``` 119 | helm install --name my-web --set service.type=NodePort stable/tomcat 120 | ``` 121 | 122 | * 测试安装效果 123 | 124 | ``` 125 | export NODE_PORT=$(kubectl get --namespace default -o jsonpath="{.spec.ports[0].nodePort}" services my-web-tomcat) 126 | export NODE_IP=$(kubectl get nodes --namespace default -o jsonpath="{.items[0].status.addresses[0].address}") 127 | echo http://$NODE_IP:$NODE_PORT 128 | 129 | # 访问sample应用 130 | curl http://$NODE_IP:$NODE_PORT/sample/ 131 | ``` 132 | 133 | * 列表和删除 134 | 135 | ``` 136 | helm list 137 | helm del --purge my-web 138 | ``` -------------------------------------------------------------------------------- /docs/impl_trait.md: -------------------------------------------------------------------------------- 1 | # impl Trait 的使用 2 | 3 | Rust 通过 [RFC conservative impl trait](https://github.com/rust-lang/rfcs/blob/master/text/1522-conservative-impl-trait.md) 增加了新的语法 `impl Trait`,它被用在函数返回值的位置上,表示返回的类型将实现这个 Trait。随后的 [RFC expanding impl Trait](https://github.com/rust-lang/rfcs/blob/master/text/1951-expand-impl-trait.md) 更进一步,允许 `impl Trait` 用在函数参数的位置,表示由调用者决定参数的具体类型,其实就等价于函数的泛型参数。 4 | 5 | ## impl Trait 作为函数参数 6 | 7 | 根据  [RFC on expanding impl Trait](https://github.com/rust-lang/rfcs/blob/master/text/1951-expand-impl-trait.md), `impl Trait` 可以用在函数参数中,作用是作为函数的匿名泛型参数。 8 | 9 | > Expand `impl Trait` to allow use in arguments, where it behaves like an anonymous generic parameter. 10 | 11 | 也就是说,`impl Trait` 作为函数参数,和泛型参数是等价的: 12 | 13 | ```rust 14 | // These two are equivalent 15 | fn map(self, f: impl FnOnce(T) -> U) -> Option 16 | fn map(self, f: F) -> Option where F: FnOnce(T) -> U 17 | ``` 18 | 19 | 不过,`impl Trait`和泛型参数有一个不同的地方,`impl Trait` 作为参数,不能明确指定它的类型: 20 | 21 | ```rust 22 | fn foo(t: T) 23 | fn bar(t: impl Trait) 24 | 25 | foo::(0) // this is allowed 26 | bar::(0) // this is not 27 | ``` 28 | 29 | 除了这个差别,可以认为`impl Trait` 作为函数参数,和使用泛型参数是等价的。 30 | 31 | ## impl Trait 作为函数返回值 32 | 33 | `impl Trait` 作为函数的返回值,表示返回的类型将实现这个 Trait。 34 | 35 | ```rust 36 | fn foo(n: u32) -> impl Iterator { 37 | (0..n).map(|x| x * 100) 38 | } 39 | fn main() { 40 | for x in foo(10) { 41 | println!("{}", x); 42 | } 43 | } 44 | ``` 45 | 46 | 在这种情况下,需要注意函数的所有返回路径必须返回完全相同的具体类型。 47 | 48 | ```rust 49 | // 编译错误,即使这两个类型都实现了Bar 50 | fn f(a: bool) -> impl Bar { 51 | if a { 52 | Foo { ... } 53 | } else { 54 | Baz { ... } 55 | } 56 | } 57 | ``` 58 | 59 | 可以把函数返回值位置的 `impl Trait` 替换为泛型吗? 60 | 61 | ```rust 62 | // 不能编译 63 | fn bar>(n: u32) -> T { 64 | (0..n).map(|x| x * 100) 65 | } 66 | ``` 67 | 68 | 编译器给的错误信息是,期待返回值的类型是泛型类型 T,却实际却返回了一个具体类型。编译器很智能的给出了使用 `impl Iterator`作为返回类型的建议: 69 | 70 | ```rust 71 | --> src/main.rs:6:5 72 | | 73 | 5 | fn bar>(n: u32) -> T { 74 | | - - 75 | | | | 76 | | | expected `T` because of return type 77 | | this type parameter help: consider using an impl return type: `impl Iterator` 78 | 6 | (0..n).map(|x| x * 100) 79 | | ^^^^^^^^^^^^^^^^^^^^^^^ expected type parameter `T`, found struct `Map` 80 | | 81 | = note: expected type parameter `T` 82 | found struct `Map, [closure@src/main.rs:6:16: 6:27]>` 83 | ``` 84 | 85 | ## Universals vs. Existentials 86 | 87 | 在 [RFC on expanding impl Trait](https://github.com/rust-lang/rfcs/blob/master/text/1951-expand-impl-trait.md) 中使用了两个术语,Universal 和 Existential: 88 | 89 | > - Universal quantification, i.e. "for any type T", i.e. "caller chooses". This is how generics work today. When you write `fn foo(t: T)`, you're saying that the function will work for any choice of `T`, and leaving it to your caller to choose the `T`. 90 | > - Existential quantification, i.e. "for some type T", i.e. "callee chooses". This is how `impl Trait` works today (which is in return position only). When you write `fn foo() -> impl Iterator`, you're saying that the function will produce some type `T` that implements `Iterator`, but the caller is not allowed to assume anything else about that type. 91 | 92 | 简单来说: 93 | 94 | - `impl Trait` 用在参数位置是 universal type,也就是泛型类型,它可以是任意类型,由函数的调用者指定具体的类型。 95 | 96 | - `impl Trait` 用在返回值位置是 existential type,它不能是任意类型,而是由函数的实现者指定,一个实现了 Trait 的具体类型。调用者不能对这个类型做任何假设。 97 | 98 | 也就是说,`impl Trait` 用在返回位置不是泛型,编译时不需要单态化,抽象类型可以简单地替换为调用代码中的具体类型。 99 | 100 | ## 在 Trait 中使用 impl Trait 101 | 102 | Rust 目前还不支持在 Trait 里使用 `impl Trait` 做返回值: 103 | 104 | ```rust 105 | trait Foo { 106 | // ERROR: `impl Trait` not allowed outside of function and inherent 107 | // method return types 108 | fn foo(&self) -> impl Iterator; 109 | } 110 | ``` 111 | 112 | 因为 `impl Trait` 用在返回值位置是 existential type,意味着这个函数将返回一个实现了这个 Trait 的单一类型,而函数定义在 Trait 中,意味着每个实现了 Trait 的类型,都可以让这个函数返回不同类型,对编译器来说这很难处理,因为它需要知道被返回类型的具体大小。 113 | 114 | 一个简单的解决方法是让函数返回 `trait object`: 115 | 116 | ```rust 117 | trait Foo { 118 | fn foo(&self) -> Box>; 119 | } 120 | ``` 121 | 122 | 带有 `trait object` 的函数不是泛型函数,它只带有单一类型,这个类型就是 `trait object` 类型。`Trait object` 本身被实现为胖指针,其中,一个指针指向数据本身,另一个则指向虚函数表(vtable)。 123 | 124 | 这样定义在 Trait 中的函数,返回的不再是泛型,而是一个单一的 `trait object` 类型,大小固定(两个指针大小),编译器可以处理。 125 | -------------------------------------------------------------------------------- /docs/insider-docker.md: -------------------------------------------------------------------------------- 1 | # 深入浅出容器技术 2 | 3 | 4 | alt : 深入浅出容器技术 5 | 6 | -------------------------------------------------------------------------------- /docs/jakarta_ee_transaction.md: -------------------------------------------------------------------------------- 1 | # Jakarta EE应用服务器的事务处理 2 | 3 | ![jakartaone](https://raw.githubusercontent.com/mz1999/material/master/images/202310071008517.png) 4 | 5 | 我在 [Jakarta EE 中文技术大会上](https://jakartaone.org/zh/2023/chinese/)的分享 《Jakarta EE应用服务器的事务处理》 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/kubectl-multiple-clusters.md: -------------------------------------------------------------------------------- 1 | 2 | # 使用kubectl管理多集群 3 | 4 | `kubectl`会使用`$HOME/.kube`目录下的`config`文件作为缺省的配置文件。我们可以使用`kubectl config view`查看配置信息: 5 | 6 | ``` 7 | $kubectl config view 8 | 9 | apiVersion: v1 10 | clusters: 11 | - cluster: 12 | certificate-authority-data: DATA+OMITTED 13 | server: https://172.18.100.90:6443 14 | name: cluster-1 15 | contexts: 16 | - context: 17 | cluster: cluster-1 18 | user: cluster-1-admin 19 | name: cluster-1-admin@cluster-1 20 | current-context: cluster-1-admin@cluster-1 21 | kind: Config 22 | preferences: {} 23 | users: 24 | - name: cluster-1-admin 25 | user: 26 | client-certificate-data: REDACTED 27 | client-key-data: REDACTED 28 | ``` 29 | 30 | 可以看到,配置文件主要包含了`clusters`,`users`和`contexts`三部分信息。`context`是访问一个`kubernetes`集群所需要的参数集合。每个`context`有三个参数: 31 | 32 | * `cluster`:要访问的集群信息 33 | * `namespace`:用户工作的`namespace`,缺省值为`default` 34 | * `user`:连接集群的认证用户 35 | 36 | 缺省情况下,`kubectl`会使用`current-context`指定的`context`作为当前的工作集群环境。不难想象,切换`context`就可以切换到不同的`kubernetes`集群。 37 | 38 | 在不了解`context`的概念之前,想访问不同的集群,每次都要把集群对应的`config`文件copy到`$HOME/.kube`目录下,同时要记得使用`kubectl cluster-info`确认当前访问的集群: 39 | 40 | ``` 41 | $kubectl cluster-info 42 | 43 | Kubernetes master is running at https://172.18.100.90:6443 44 | KubeDNS is running at https://172.18.100.90:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy 45 | 46 | To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'. 47 | ``` 48 | 49 | 在看了[这篇文档](https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/)后,才知道`kubectl`可以切换`context`来管理多个集群。如果你有多个集群的`config`文件,可以在系统环境变量`KUBECONFIG`中指定每个`config`文件的路径,例如: 50 | 51 | ``` 52 | export KUBECONFIG=/home/mazhen/kube-config/config-cluster-1:/home/mazhen/kube-config/config-cluster-1 53 | ``` 54 | 55 | 再使用`kubectl config view`查看集群配置时,`kubectl`会自动合并多个`config`的信息: 56 | 57 | ``` 58 | $ kubectl config view 59 | 60 | apiVersion: v1 61 | clusters: 62 | - cluster: 63 | certificate-authority-data: DATA+OMITTED 64 | server: https://172.20.51.11:6443 65 | name: cluster-2 66 | - cluster: 67 | certificate-authority-data: DATA+OMITTED 68 | server: https://172.18.100.90:6443 69 | name: cluster-1 70 | contexts: 71 | - context: 72 | cluster: cluster-2 73 | user: cluster-2-admin 74 | name: cluster-2-admin@cluster-2 75 | - context: 76 | cluster: cluster-1 77 | user: cluster-1-admin 78 | name: cluster-1-admin@cluster-1 79 | current-context: cluster-1-admin@cluster-1 80 | kind: Config 81 | preferences: {} 82 | users: 83 | - name: cluster-2-admin 84 | user: 85 | client-certificate-data: REDACTED 86 | client-key-data: REDACTED 87 | - name: cluster-1-admin 88 | user: 89 | client-certificate-data: REDACTED 90 | client-key-data: REDACTED 91 | ``` 92 | 93 | 可以看到,配置中包含了两个集群,两个用户,以及两个`context`。我们可以使用`kubectl config get-contexts`查看配置中所有的`context`: 94 | 95 | ``` 96 | $ kubectl config get-contexts 97 | 98 | CURRENT NAME CLUSTER AUTHINFO NAMESPACE 99 | cluster-2-admin@cluster-2 cluster-2 cluster-2-admin 100 | * cluster-1-admin@cluster-1 cluster-1 cluster-1-admin 101 | ``` 102 | 103 | 星号`*`标识了当前的工作集群。如果想访问另一个集群,使用`kubectl config use-context`进行切换: 104 | 105 | ``` 106 | $ kubectl config use-context cluster-2-admin@cluster-2 107 | 108 | Switched to context "cluster-2-admin@cluster-2". 109 | ``` 110 | 111 | 我们可以再次确认切换的结果: 112 | 113 | ``` 114 | $ kubectl config get-contexts 115 | 116 | CURRENT NAME CLUSTER AUTHINFO NAMESPACE 117 | * cluster-2-admin@cluster-2 cluster-2 cluster-2-admin 118 | cluster-1-admin@cluster-1 cluster-1 cluster-1-admin 119 | 120 | $ kubectl cluster-info 121 | 122 | Kubernetes master is running at https://172.20.51.11:6443 123 | KubeDNS is running at https://172.20.51.11:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy 124 | kubernetes-dashboard is running at https://172.20.51.11:6443/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy 125 | 126 | To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'. 127 | ``` 128 | 129 | 看吧,`kubectl`切换`context`管理多集群是多么的方便。 -------------------------------------------------------------------------------- /docs/kubernetes-overview.md: -------------------------------------------------------------------------------- 1 | # Kubernetes 工作原理概述 2 | 3 | 刚接触[Kubernetes](https://kubernetes.io/)时很容易被它繁多的概念([POD](https://kubernetes.io/docs/concepts/workloads/pods/pod-overview/),[Service](https://kubernetes.io/docs/concepts/services-networking/service/),[Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/) ...)以及比较复杂的部署架构搞晕,本文希望能通过一个简单的例子,讲解`Kubernetes`最基本的工作原理。 4 | 5 | `Kubernetes`本质上是为用户提供了一个容器编排工具,可以管理和调度用户提交的作业。用户在 `YAML` 配置文件中描述应用所需的环境配置、参数等信息,以及应用期待平台提供的服务(负载均衡,水平扩展等),然后将 `YAML` 提交,`Kubernetes`会按照用户的要求,在集群上将应用运行起来。在遇到异常情况,或用户的主动调整时,`Kubernetes` 将始终保持应用实际的运行状态,符合用户的期待状态。 6 | 7 | `Kubernetes` 是由 `Master` 和 `Node` 两种节点组成。Master由3个独立的组件组成: 8 | 9 | * 负责 API 服务的 `kube-apiserver` 10 | * 负责容器编排的 `kube-controller-manager` 11 | * 负责调度的 `kube-scheduler` 12 | 13 | `Kubernetes` 集群的所有状态信息都存储在 `etcd`,其他组件对 `etcd` 的访问,必须通过 `kube-apiserver`。 14 | 15 | `Kubelet` 运行在所有节点上,它通过容器运行时(例如Docker),让应用真正的在节点上运行起来。 16 | 17 | ![2019-02-24 22.58.55](media/kubernetes/2019-02-24%2022.58.55.png) 18 | 19 | 下面通过一个简单的例子,描述 `Kubernetes` 的各个组件,是如何协作完成工作的。 20 | 21 | 用户将 `YAML` 提交给 `kube-apiserver`,`YAML` 经过校验后转换为 `API 对象`,存储在 `etcd` 中。 22 | 23 | ![2019-02-24 23.17.20](media/kubernetes/2019-02-24%2023.17.20.png) 24 | 25 | `kube-controller-manager` 是负责编排的组件,当它发现有新提交的应用,会根据配置的要求生成对应的 `Pod` 对象。`Pod` 是 `Kubernetes` 调度管理的最小单元,可以简单的认为,`Pod` 就是一个虚拟机,其中运行着关系紧密的进程,共同组成用户的应用。例如Web应用进程和日志收集agent,可以包含在一个`Pod`中。`Pod` 对象也存储在 `etcd` 中。本例子中用户定义 `replicas` 为2,也就是用户期待有两个 `Pod` 实例。 26 | 27 | 其实`kube-controller-manager` 内部一直在做循环检查,只要发现有应用没有对应的 `Pod`,或者 `Pod` 的数量不满足用户的期望,它都会进行适当的调整,创建或删除`Pod` 对象。 28 | 29 | ![2019-02-24 23.41.52](media/kubernetes/2019-02-24%2023.41.52.png) 30 | 31 | 32 | `kube-scheduler` 负责 `Pod` 的调度。`kube-scheduler` 发现有新的 `Pod` 出现,它会按照调度算法,为每个 `Pod` 寻找一个最合适的节点(Node)。`kube-scheduler` 对一个 `Pod` 的调度成功,实际上就是在 `Pod` 对象上记录了调度结果的节点名称。注意,`Pod` 调度成功,只是在 `Pod` 上标记了节点的名字,`Pod` 是否真正在节点上运行,就不是`kube-scheduler`的责任了。 33 | 34 | ![2019-02-24 23.56.12](media/kubernetes/2019-02-24%2023.56.12.png) 35 | 36 | `Kubelet` 运行在所有节点上,它会订阅所有 `Pod` 对象的变化,当发现一个 `Pod` 与 `Node` 绑定,也就是这个 `Pod` 上标记了`Node`的名字,而这个被绑定的 `Node` 就是它自己,`Kubelet` 就会在这个节点将 `Pod` 启动。 37 | 38 | ![2019-02-25 00.06.39](media/kubernetes/2019-02-25%2000.06.39.png) 39 | 40 | 至此,用户提交的应用在`Kubernetes`集群中就运行起来了。 41 | 42 | 同时,上述的过程一直在循环往复。例如,用户更新了 `YAML`,将 `replicas` 改为3,并将更新后的 `YAML` 再次提交。`kube-controller-manager`会发现实际运行的 `Pod` 数量与用户的期望不符,它会生成一个新的 `Pod` 对象。紧接着 `kube-scheduler` 发现一个没有绑定节点的 `Pod`,它会按照调度算法为这个`Pod`寻找一个最佳节点完成绑定。最后,某个`Kubelet` 发现新绑定节点的 `Pod` 应该在本节点上运行,它会通过接口调用`Docker`完成 `Pod` 的启动。 43 | 44 | ![2019-02-25 00.36.51](media/kubernetes/2019-02-25%2000.36.51.png) 45 | 46 | 上面就是 `Kubernetes` 基本工作流程的简单描述,希望对你理解它的工作原理有所帮助。 47 | 48 | 49 | -------------------------------------------------------------------------------- /docs/lambda.md: -------------------------------------------------------------------------------- 1 | # 深入理解 Java Lambda 表达式 2 | 3 | ## 引言 4 | 在 Java 8 之前,方法无法直接作为值传递,开发者需要通过匿名类或冗长的接口实现来实现行为参数化。Java 8 引入的 **Lambda 表达式**和**方法引用**彻底改变了这一局面,让函数式编程范式在 Java 中落地生根。本文将从行为参数化的设计思想出发,系统讲解 Lambda 的核心概念、语法特性及其在实践中的应用。 5 | 6 | --- 7 | 8 | ## 一、行为参数化:函数式编程的基石 9 | ### 1.1 什么是行为参数化? 10 | **行为参数化(Behavior Parameterization)** 是指将代码逻辑(即“行为”)作为参数传递给其他方法的能力。这种设计允许方法的执行逻辑动态变化,从而提高代码的灵活性和复用性。 11 | 12 | 例如,一个筛选苹果的方法 `filterApples`,其筛选条件可以是颜色、重量或其他属性。通过行为参数化,我们无需为每种条件编写独立的方法,而是将条件逻辑抽象为接口传递: 13 | 14 | ```java 15 | public interface ApplePredicate { 16 | boolean test(Apple apple); 17 | } 18 | 19 | // 筛选逻辑由调用者决定 20 | List filterApples(List inventory, ApplePredicate predicate) { 21 | List result = new ArrayList<>(); 22 | for (Apple apple : inventory) { 23 | if (predicate.test(apple)) { 24 | result.add(apple); 25 | } 26 | } 27 | return result; 28 | } 29 | ``` 30 | 31 | ### 1.2 从匿名类到 Lambda 32 | 在 Java 8 之前,行为参数化需要通过匿名类实现,但代码臃肿且不够直观: 33 | ```java 34 | filterApples(inventory, new ApplePredicate() { 35 | @Override 36 | public boolean test(Apple apple) { 37 | return "red".equals(apple.getColor()); 38 | } 39 | }); 40 | ``` 41 | 42 | Lambda 表达式简化了这一过程,直接传递逻辑: 43 | ```java 44 | filterApples(inventory, (Apple apple) -> "red".equals(apple.getColor())); 45 | ``` 46 | 47 | --- 48 | 49 | ## 二、Lambda 表达式:语法与核心概念 50 | ### 2.1 Lambda 的语法结构 51 | Lambda 表达式由三部分组成:**参数列表**、**箭头符号** `->` 和 **函数主体**。 52 | ```java 53 | // 基本语法 54 | (参数列表) -> { 函数主体 } 55 | 56 | // 示例:比较两个苹果的重量 57 | Comparator byWeight = 58 | (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()); 59 | ``` 60 | 61 | ### 2.2 函数式接口 62 | **函数式接口(Functional Interface)** 是只包含一个抽象方法的接口。Lambda 表达式本质上是函数式接口的实例。 63 | 64 | Java 8 提供了 `@FunctionalInterface` 注解标识这类接口,例如: 65 | ```java 66 | @FunctionalInterface 67 | public interface Predicate { 68 | boolean test(T t); 69 | } 70 | ``` 71 | 72 | 常见的函数式接口: 73 | - `Predicate`:接收 `T` 类型参数,返回 `boolean`。 74 | - `Consumer`:接收 `T` 类型参数,无返回值。 75 | - `Function`:接收 `T` 类型参数,返回 `R` 类型结果。 76 | 77 | ### 2.3 类型推断与上下文 78 | Lambda 的类型由上下文自动推断: 79 | ```java 80 | // 显式指定类型 81 | Comparator c1 = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()); 82 | 83 | // 类型推断(编译器根据目标类型推断参数类型) 84 | Comparator c2 = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight()); 85 | ``` 86 | 87 | --- 88 | 89 | ## 三、方法引用:简化 Lambda 的利器 90 | 方法引用允许直接通过方法名替代完整的 Lambda 表达式,主要分为四类: 91 | 92 | ### 3.1 静态方法引用 93 | 语法:`类名::静态方法名` 94 | ```java 95 | Function intToString = String::valueOf; 96 | ``` 97 | 98 | ### 3.2 实例方法引用 99 | 语法:`对象::实例方法名` 100 | ```java 101 | String str = "Hello"; 102 | Supplier lengthSupplier = str::length; 103 | ``` 104 | 105 | ### 3.3 任意对象的实例方法引用 106 | 语法:`类名::实例方法名`(适用于 Lambda 参数作为方法调用者) 107 | ```java 108 | BiFunction compareIgnoreCase = String::compareToIgnoreCase; 109 | ``` 110 | 111 | ### 3.4 构造函数引用 112 | 语法:`类名::new` 113 | ```java 114 | Supplier> listSupplier = ArrayList::new; 115 | ``` 116 | 117 | --- 118 | 119 | ## 四、复合 Lambda 表达式 120 | ### 4.1 比较器链 121 | 通过 `Comparator` 的链式调用实现多级排序: 122 | ```java 123 | inventory.sort( 124 | comparing(Apple::getWeight) 125 | .reversed() 126 | .thenComparing(Apple::getCountry) 127 | ); 128 | ``` 129 | 130 | ### 4.2 谓词组合 131 | 使用 `and`、`or`、`negate` 组合多个条件: 132 | ```java 133 | Predicate redAndHeavy = 134 | apple -> "red".equals(apple.getColor()) 135 | .and(apple -> apple.getWeight() > 150); 136 | ``` 137 | 138 | ### 4.3 函数组合 139 | 通过 `andThen` 和 `compose` 实现函数串联: 140 | ```java 141 | Function addOne = x -> x + 1; 142 | Function multiplyByTwo = x -> x * 2; 143 | 144 | // 先加 1,再乘以 2 145 | Function combined1 = addOne.andThen(multiplyByTwo); 146 | combined1.apply(3); // 结果为 8 147 | 148 | // 先乘以 2,再加 1 149 | Function combined2 = addOne.compose(multiplyByTwo); 150 | combined2.apply(3); // 结果为 7 151 | ``` 152 | 153 | --- 154 | 155 | ## 五、Lambda 的实践与注意事项 156 | ### 5.1 异常处理 157 | Lambda 表达式无法直接抛出受检异常(Checked Exception),需通过以下方式解决: 158 | 1. 在函数式接口中声明异常: 159 | ```java 160 | @FunctionalInterface 161 | public interface BufferedReaderProcessor { 162 | String process(BufferedReader br) throws IOException; 163 | } 164 | ``` 165 | 166 | 2. 在 Lambda 中捕获异常: 167 | ```java 168 | Function safeParse = s -> { 169 | try { 170 | return Integer.parseInt(s); 171 | } catch (NumberFormatException e) { 172 | return 0; 173 | } 174 | }; 175 | ``` 176 | 177 | ### 5.2 局部变量限制 178 | Lambda 可以捕获实例变量和静态变量,但局部变量必须为 `final` 或“等效 final”: 179 | ```java 180 | int localVar = 10; 181 | Runnable r = () -> System.out.println(localVar); // 合法 182 | localVar = 20; // 编译错误:localVar 必须是 final 或等效 final 183 | ``` 184 | 185 | --- 186 | 187 | ## 六、总结 188 | Lambda 表达式是 Java 函数式编程的核心工具,通过行为参数化显著提升了代码的简洁性和灵活性。结合方法引用、函数式接口及复合操作,开发者可以构建出高度抽象且易于维护的代码结构。理解并掌握这些特性,是迈向现代 Java 开发的关键一步。 -------------------------------------------------------------------------------- /docs/legacy_system.md: -------------------------------------------------------------------------------- 1 | # 遗留系统 2 | 3 | 遗留系统 4 | 5 | 最近在处理一个 [EJB](https://www.oracle.com/java/technologies/enterprise-javabeans-technology.html) 调用的问题,和底层的 [CORBA](https://www.corba.org/) 通信有关,都是很古老的技术名词。 6 | 7 | 二十多年前我刚参加工作的时候,EJB 带着神秘和时髦的色彩横空出世,可后来没几年就被 Spring Framework 祛魅,很少有人再使用 EJB 开发应用。 8 | 9 | CORBA 则更加古老,估计现在很多程序员都没听说过,更别说开发过 CORBA 组件了。实际上 CORBA 是最早的分布式服务规范,早在 1991 年就发布了 1.0。可以说后来的 EJB,Web Services,甚至微服务,service mesh 都有 CORBA 的影子。 10 | 11 | * CORBA 定义了 [IDL(Interface Definition Language)](https://www.corba.org/omg_idl.htm),用它来描述对象的接口、方法、参数和返回类型等信息,根据 IDL 可以生成各种语言的实现,不同语言编写的对象可以进行交互。 12 | * CORBA 定义了一系列服务,如[Naming Service](https://www.omg.org/spec/NAM/1.3),[Transaction Service](https://www.omg.org/spec/TRANS/1.4),[Security Service](https://www.omg.org/spec/SEC/1.8)等,作为分布式系统的基础服务。事务、安全等服务会随着远程调用进行传播。 13 | * CORBA 的 [ORB(Object Request Broker)](https://www.corba.org/orb_basics.htm) 负责分布式系统中对象之间的通信。用户可以像调用本地对象一样调用远程对象上的方法,ORB 会处理网络通信和远程调用的细节。ORB 之间通过 [IIOP(Internet Inter-ORB Protocol)](https://en.wikipedia.org/wiki/General_Inter-ORB_Protocol)协议进行通信。 14 | 15 | 就这样,IDL、一系列服务,再加上ORB,构成了 CORBA 的完整体系。其实 CORBA 的理念很好,面向对象,跨语言跨平台,服务传播和网络通信对用户透明。 16 | 17 | CORBA 作为一套成熟的工业规范,后来者自然会想办法吸收和兼容。1998年发布的 [JDK 1.2](https://web.archive.org/web/20070816170028/http://www.sun.com/smi/Press/sunflash/1998-12/sunflash.981208.9.xml),内置了 Java IDL ,以及全面兼容 ORB 规范的 Java ORB 实现。这时 Java 已经准备在企业端开发领域大展拳脚,JDK 内置了对 CORBA 的支持,为 [J2EE](https://www.oracle.com/java/technologies/appmodel.html) (也就是后来的 [Java EE](https://www.oracle.com/java/technologies/java-ee-glance.html),现在的 [Jakarta EE](https://jakarta.ee/))做好了准备。 18 | 19 | EJB 全面继承了 CORBA,[Java Transaction Service (JTS)](https://web.archive.org/web/20080418135325/http://java.sun.com/javaee/technologies/jts/) 是 CORBA 事务服务 [OTS](https://www.omg.org/spec/TRANS/1.4) 的 Java 映射,EJB 之间的远程调用走 [RMI/IIOP](https://docs.oracle.com/javase/7/docs/technotes/guides/rmi-iiop/index.html) 协议,事务、安全上下文会通过 IIOP 进行传播。理论上,部署在不同品牌应用服务器上的 EJB 之间可以互相调用,EJB 也可以和任何语言开发的 CORBA 对象进行交互,并且所有 EJB 和 CORBA 对象,可以运行在同一个事务、安全上下文中。 20 | 21 | EJB 的目标是做真正的中间件,连接不同厂商的 J2EE 应用服务器,连接不同语言开发、运行在不同平台上的 CORBA 对象,并且它们可以加入到同一个分布式事务中,受到同样的安全策略保护。 22 | 23 | 理想很丰满,现实是 EJB 的理想从来没有被实现过。[Rod Johnson](https://twitter.com/springrod) 在总结了 J2EE 的优缺点后,干脆[抛弃了 EJB(without EJB)](https://www.oreilly.com/library/view/expert-one-on-onetm-j2eetm/9780764558313/) ,开发了轻量级 Spring Framework。Spring 太成功了,以至于对很多人来说,Java 开发 ≈ 使用 Spring 进行开发。 24 | 25 | 后来的 Web Services/SOA 又把 CORBA、EJB 的路重新走了一遍, 定义了和 IDL 类似的 [WSDL](https://en.wikipedia.org/wiki/Web_Services_Description_Language),以及一系列的事务规范 [WS-Transaction](https://en.wikipedia.org/wiki/WS-Transaction),[WS-Coordination](https://en.wikipedia.org/wiki/WS-Coordination),[WS-Atomic Transaction](https://en.wikipedia.org/wiki/WS-Atomic_Transaction)。然后开发者又觉得大公司定义的规范太复杂,才有了轻量级的 REST,微服务。 26 | 27 | Java 的 CORBA 实现在 JDK 9 中被标记为 `deprecated`, 并最终在2018年发布的 JDK 11中被[正式移除](https://openjdk.org/jeps/320)。EJB 和 CORBA 都没有成功,Java 宣告和 CORBA 分手,一段历史结束。 28 | 29 | 在2024年的今天,有着30多年历史的 CORBA 和20多年历史的 EJB 已经是遗留系统,不会再有大批聪明的年轻人愿意投入到这个技术领域。不过对于像我这样还在一线搬砖的大龄程序员,遗留系统也是一种选择。它们和自身的境况很像:激情已过,一天天的老去。我们互相扶持着,每天对它们进行修修补补,打着补丁,它们也回报勉强够养家的报酬,然后一起等待着被淘汰的一天。在 AI 革命,号称要取代码农的今天,竟然还能靠着 20 多年前学到的技能挣到工资,也算一个小小的奇迹吧。 30 | 31 | 在翻阅 [Java ORB](https://github.com/eclipse-ee4j/orb) 的源代码时,注意到了很多源文件上都标记了作者的名字,于是顺手在网上一搜,还真找到了作者的信息。 32 | 33 | Harold Carr 34 | 35 | [Harold Carr](http://haroldcarr.com/),84年至94年在惠普实验室从事分布式 C++ 工作,94年加入 Sun,设计了 Sun 的 CORBA ORB,JAX-WS 2.0,负责过 GlassFish 中的 RMI-IIOP 负载平衡和故障切换。Sun 被收购后他一直留在 Oracle,目前仍在 Oracle 实验室从事技术工作。同时他组过乐队录过专辑,还出版过诗集。有意思的人,有意思的经历。 36 | 37 | 回到开头的 EJB 问题,仍然没有头绪,继续在代码里找线索。生活充满了眼前的苟且,还能有诗和远方吗? 38 | -------------------------------------------------------------------------------- /docs/media/aliyun/aliyun-docker-image-namespace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/aliyun/aliyun-docker-image-namespace.png -------------------------------------------------------------------------------- /docs/media/aliyun/docker-proxy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/aliyun/docker-proxy.png -------------------------------------------------------------------------------- /docs/media/aliyun/patriotic-networ-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/aliyun/patriotic-networ-2.png -------------------------------------------------------------------------------- /docs/media/atomikos/atomikosrestport.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/atomikos/atomikosrestport.png -------------------------------------------------------------------------------- /docs/media/atomikos/demo-services.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/atomikos/demo-services.png -------------------------------------------------------------------------------- /docs/media/atomikos/spring-remoting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/atomikos/spring-remoting.png -------------------------------------------------------------------------------- /docs/media/container/2018-12-15 12.14.10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/container/2018-12-15 12.14.10.png -------------------------------------------------------------------------------- /docs/media/container/2018-12-15 17.41.10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/container/2018-12-15 17.41.10.png -------------------------------------------------------------------------------- /docs/media/container/2018-12-15 22.22.35.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/container/2018-12-15 22.22.35.png -------------------------------------------------------------------------------- /docs/media/container/2018-12-15 22.22.49.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/container/2018-12-15 22.22.49.png -------------------------------------------------------------------------------- /docs/media/container/2018-12-16 11.16.37.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/container/2018-12-16 11.16.37.png -------------------------------------------------------------------------------- /docs/media/container/2018-12-16 15.03.35.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/container/2018-12-16 15.03.35.png -------------------------------------------------------------------------------- /docs/media/container/2018-12-16 18.16.08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/container/2018-12-16 18.16.08.png -------------------------------------------------------------------------------- /docs/media/container/2018-12-16 19.01.12-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/container/2018-12-16 19.01.12-1.png -------------------------------------------------------------------------------- /docs/media/container/2018-12-16 19.01.12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/container/2018-12-16 19.01.12.png -------------------------------------------------------------------------------- /docs/media/container/2018-12-16 19.35.38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/container/2018-12-16 19.35.38.png -------------------------------------------------------------------------------- /docs/media/container/firecracker_host_integration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/container/firecracker_host_integration.png -------------------------------------------------------------------------------- /docs/media/container/gvisor-Layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/container/gvisor-Layers.png -------------------------------------------------------------------------------- /docs/media/container/protection_rings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/container/protection_rings.png -------------------------------------------------------------------------------- /docs/media/distributedtransaction/jta-microservices.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/distributedtransaction/jta-microservices.png -------------------------------------------------------------------------------- /docs/media/distributedtransaction/jta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/distributedtransaction/jta.png -------------------------------------------------------------------------------- /docs/media/distributedtransaction/jts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/distributedtransaction/jts.png -------------------------------------------------------------------------------- /docs/media/distributedtransaction/local-transaction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/distributedtransaction/local-transaction.png -------------------------------------------------------------------------------- /docs/media/distributedtransaction/seata-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/distributedtransaction/seata-architecture.png -------------------------------------------------------------------------------- /docs/media/distributedtransaction/seata-microservices.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/distributedtransaction/seata-microservices.png -------------------------------------------------------------------------------- /docs/media/docker/2018-11-10 22.56.04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/docker/2018-11-10 22.56.04.png -------------------------------------------------------------------------------- /docs/media/docker/2018-11-10 23.39.03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/docker/2018-11-10 23.39.03.png -------------------------------------------------------------------------------- /docs/media/docker/2018-11-11 10.53.03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/docker/2018-11-11 10.53.03.png -------------------------------------------------------------------------------- /docs/media/docker/2018-11-11 13.27.42.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/docker/2018-11-11 13.27.42.png -------------------------------------------------------------------------------- /docs/media/docker/2018-11-11 14.22.58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/docker/2018-11-11 14.22.58.png -------------------------------------------------------------------------------- /docs/media/docker/2018-11-11 18.18.03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/docker/2018-11-11 18.18.03.png -------------------------------------------------------------------------------- /docs/media/docker/2018-11-11 20.35.36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/docker/2018-11-11 20.35.36.png -------------------------------------------------------------------------------- /docs/media/docker/2018-11-11 23.12.47.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/docker/2018-11-11 23.12.47.png -------------------------------------------------------------------------------- /docs/media/docker/2018-11-11 23.15.21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/docker/2018-11-11 23.15.21.png -------------------------------------------------------------------------------- /docs/media/docker/2018-11-12 08.32.09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/docker/2018-11-12 08.32.09.png -------------------------------------------------------------------------------- /docs/media/docker/2018-11-15 23.42.06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/docker/2018-11-15 23.42.06.png -------------------------------------------------------------------------------- /docs/media/docker/VM-Diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/docker/VM-Diagram.png -------------------------------------------------------------------------------- /docs/media/docker/vxlan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/docker/vxlan.png -------------------------------------------------------------------------------- /docs/media/ftrace/ftrace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/ftrace/ftrace.png -------------------------------------------------------------------------------- /docs/media/gdb-kernel-debugging/ip_link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/gdb-kernel-debugging/ip_link.png -------------------------------------------------------------------------------- /docs/media/gdb-kernel-debugging/kgdb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/gdb-kernel-debugging/kgdb.png -------------------------------------------------------------------------------- /docs/media/gdb-kernel-debugging/menuconfig.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/gdb-kernel-debugging/menuconfig.png -------------------------------------------------------------------------------- /docs/media/gdb-kernel-debugging/networking.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/gdb-kernel-debugging/networking.png -------------------------------------------------------------------------------- /docs/media/gdb-kernel-debugging/vnc-connection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/gdb-kernel-debugging/vnc-connection.png -------------------------------------------------------------------------------- /docs/media/gdb-kernel-debugging/vnc-viewer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/gdb-kernel-debugging/vnc-viewer.png -------------------------------------------------------------------------------- /docs/media/http2/alpn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/http2/alpn.png -------------------------------------------------------------------------------- /docs/media/http2/binary-framing-layer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/http2/binary-framing-layer.png -------------------------------------------------------------------------------- /docs/media/http2/chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/http2/chrome.png -------------------------------------------------------------------------------- /docs/media/http2/cipher-suites.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/http2/cipher-suites.png -------------------------------------------------------------------------------- /docs/media/http2/client-hello.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/http2/client-hello.png -------------------------------------------------------------------------------- /docs/media/http2/connection-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/http2/connection-view.png -------------------------------------------------------------------------------- /docs/media/http2/ending.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/http2/ending.jpg -------------------------------------------------------------------------------- /docs/media/http2/frame-format.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/http2/frame-format.png -------------------------------------------------------------------------------- /docs/media/http2/frame-type.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/http2/frame-type.png -------------------------------------------------------------------------------- /docs/media/http2/google-live.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/http2/google-live.png -------------------------------------------------------------------------------- /docs/media/http2/h2-h2c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/http2/h2-h2c.png -------------------------------------------------------------------------------- /docs/media/http2/head-of-line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/http2/head-of-line.png -------------------------------------------------------------------------------- /docs/media/http2/http-header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/http2/http-header.png -------------------------------------------------------------------------------- /docs/media/http2/http-keep-alive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/http2/http-keep-alive.png -------------------------------------------------------------------------------- /docs/media/http2/http-pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/http2/http-pipeline.png -------------------------------------------------------------------------------- /docs/media/http2/http2-connection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/http2/http2-connection.png -------------------------------------------------------------------------------- /docs/media/http2/http2-vs-http1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/http2/http2-vs-http1.gif -------------------------------------------------------------------------------- /docs/media/http2/jsse-openssl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/http2/jsse-openssl.png -------------------------------------------------------------------------------- /docs/media/http2/magic-frame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/http2/magic-frame.png -------------------------------------------------------------------------------- /docs/media/http2/quic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/http2/quic.png -------------------------------------------------------------------------------- /docs/media/http2/server-hello.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/http2/server-hello.png -------------------------------------------------------------------------------- /docs/media/http2/server-push.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/http2/server-push.png -------------------------------------------------------------------------------- /docs/media/http2/setting-frame1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/http2/setting-frame1.png -------------------------------------------------------------------------------- /docs/media/http2/setting-frame2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/http2/setting-frame2.png -------------------------------------------------------------------------------- /docs/media/http2/setting-frame3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/http2/setting-frame3.png -------------------------------------------------------------------------------- /docs/media/http2/start-http2-connection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/http2/start-http2-connection.png -------------------------------------------------------------------------------- /docs/media/http2/stream-message-frame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/http2/stream-message-frame.png -------------------------------------------------------------------------------- /docs/media/http2/tcp-connection1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/http2/tcp-connection1.png -------------------------------------------------------------------------------- /docs/media/http2/tcp-connection2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/http2/tcp-connection2.png -------------------------------------------------------------------------------- /docs/media/http2/tcp-tls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/http2/tcp-tls.png -------------------------------------------------------------------------------- /docs/media/http2/tls-1.3-handshake-performance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/http2/tls-1.3-handshake-performance.png -------------------------------------------------------------------------------- /docs/media/http2/tls-handshake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/http2/tls-handshake.png -------------------------------------------------------------------------------- /docs/media/http2/tlstest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/http2/tlstest.png -------------------------------------------------------------------------------- /docs/media/k8s-auth/ca-chain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/k8s-auth/ca-chain.png -------------------------------------------------------------------------------- /docs/media/k8s-auth/root-ca.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/k8s-auth/root-ca.png -------------------------------------------------------------------------------- /docs/media/k8s-auth/zhihu-crt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/k8s-auth/zhihu-crt.png -------------------------------------------------------------------------------- /docs/media/kubernetes/2019-02-24 22.58.55.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/kubernetes/2019-02-24 22.58.55.png -------------------------------------------------------------------------------- /docs/media/kubernetes/2019-02-24 23.17.20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/kubernetes/2019-02-24 23.17.20.png -------------------------------------------------------------------------------- /docs/media/kubernetes/2019-02-24 23.41.52.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/kubernetes/2019-02-24 23.41.52.png -------------------------------------------------------------------------------- /docs/media/kubernetes/2019-02-24 23.56.12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/kubernetes/2019-02-24 23.56.12.png -------------------------------------------------------------------------------- /docs/media/kubernetes/2019-02-25 00.06.39.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/kubernetes/2019-02-25 00.06.39.png -------------------------------------------------------------------------------- /docs/media/kubernetes/2019-02-25 00.36.51.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/kubernetes/2019-02-25 00.36.51.png -------------------------------------------------------------------------------- /docs/media/perf/async-profiler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/perf/async-profiler.png -------------------------------------------------------------------------------- /docs/media/perf/bcc-bpftrace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/perf/bcc-bpftrace.png -------------------------------------------------------------------------------- /docs/media/perf/bcc_tracing_tools_2019.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/perf/bcc_tracing_tools_2019.png -------------------------------------------------------------------------------- /docs/media/perf/before_and_after_using_BPF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/perf/before_and_after_using_BPF.png -------------------------------------------------------------------------------- /docs/media/perf/flame-graph-demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/perf/flame-graph-demo.png -------------------------------------------------------------------------------- /docs/media/perf/java-flamegraph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/perf/java-flamegraph.png -------------------------------------------------------------------------------- /docs/media/perf/java-profilers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/perf/java-profilers.png -------------------------------------------------------------------------------- /docs/media/perf/linux_ebpf_internals.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/perf/linux_ebpf_internals.png -------------------------------------------------------------------------------- /docs/media/perf/linux_ebpf_support.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/perf/linux_ebpf_support.png -------------------------------------------------------------------------------- /docs/media/perf/perf-report-context-switches.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/perf/perf-report-context-switches.png -------------------------------------------------------------------------------- /docs/media/perf/perf-report-stdio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/perf/perf-report-stdio.png -------------------------------------------------------------------------------- /docs/media/perf/perf-report.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/perf/perf-report.png -------------------------------------------------------------------------------- /docs/media/perf/perf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/perf/perf.png -------------------------------------------------------------------------------- /docs/media/perf/perf_events_map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/perf/perf_events_map.png -------------------------------------------------------------------------------- /docs/media/perf/tcpdump.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/perf/tcpdump.png -------------------------------------------------------------------------------- /docs/media/perf/thread_states.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/perf/thread_states.png -------------------------------------------------------------------------------- /docs/media/pid/pid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/pid/pid.png -------------------------------------------------------------------------------- /docs/media/pid/pstree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/pid/pstree.png -------------------------------------------------------------------------------- /docs/media/pid/session.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/pid/session.png -------------------------------------------------------------------------------- /docs/media/psi/DiscontinuousCrop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/psi/DiscontinuousCrop.png -------------------------------------------------------------------------------- /docs/media/psi/FullCrop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/psi/FullCrop.png -------------------------------------------------------------------------------- /docs/media/psi/someCrop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/psi/someCrop.png -------------------------------------------------------------------------------- /docs/media/tomcat/Threading-EPC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/tomcat/Threading-EPC.png -------------------------------------------------------------------------------- /docs/media/tomcat/Threading-PEC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/tomcat/Threading-PEC.png -------------------------------------------------------------------------------- /docs/media/tomcat/adapter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/tomcat/adapter.png -------------------------------------------------------------------------------- /docs/media/tomcat/connector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/tomcat/connector.png -------------------------------------------------------------------------------- /docs/media/tomcat/endpoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/tomcat/endpoint.png -------------------------------------------------------------------------------- /docs/media/tomcat/ewyk2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/tomcat/ewyk2.png -------------------------------------------------------------------------------- /docs/media/tomcat/netty-thread-model.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/tomcat/netty-thread-model.jpg -------------------------------------------------------------------------------- /docs/media/tomcat/nioendpoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/tomcat/nioendpoint.png -------------------------------------------------------------------------------- /docs/media/tomcat/processor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/tomcat/processor.png -------------------------------------------------------------------------------- /docs/media/tomcat/tomcat-ProtocolHandler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/tomcat/tomcat-ProtocolHandler.png -------------------------------------------------------------------------------- /docs/media/tomcat/tomcat-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/tomcat/tomcat-architecture.png -------------------------------------------------------------------------------- /docs/media/tty/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/tty/1.png -------------------------------------------------------------------------------- /docs/media/tty/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/tty/2.png -------------------------------------------------------------------------------- /docs/media/tty/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/tty/3.png -------------------------------------------------------------------------------- /docs/media/tty/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/tty/4.png -------------------------------------------------------------------------------- /docs/media/tty/ASR-33_at_CHM.agr.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/tty/ASR-33_at_CHM.agr.jpg -------------------------------------------------------------------------------- /docs/media/tty/WACsOperateTeletype.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/tty/WACsOperateTeletype.jpg -------------------------------------------------------------------------------- /docs/media/workout/health.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/workout/health.jpeg -------------------------------------------------------------------------------- /docs/media/workout/vo2max.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mz1999/blog/f29b64f0cef13bc91ad4d671d1e7c38353c41162/docs/media/workout/vo2max.jpg -------------------------------------------------------------------------------- /docs/minikube.md: -------------------------------------------------------------------------------- 1 | # 在Ubuntu上安装Minikube 2 | 3 | 为了方便开发者体验`Kubernetes`,社区提供了可以在本地部署的[Minikube](https://github.com/kubernetes/minikube)。由于在强国网络环境内,无法顺利的安装使用`Minikube`,我们可以从阿里云的镜像地址来获取所需Docker镜像和配置。 4 | 5 | * **安装VirtualBox** 6 | 7 | `sudo apt-get install virtualbox` 8 | 9 | * **安装 Minikube** 10 | 11 | ``` 12 | curl -Lo minikube http://kubernetes.oss-cn-hangzhou.aliyuncs.com/minikube/releases/v0.35.0/minikube-linux-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/ 13 | ``` 14 | 15 | * **启动Minikube** 16 | 17 | ``` 18 | $ minikube start --registry-mirror=https://registry.docker-cn.com 19 | 20 | 😄 minikube v0.35.0 on linux (amd64) 21 | 🔥 Creating virtualbox VM (CPUs=2, Memory=2048MB, Disk=20000MB) ... 22 | 📶 "minikube" IP address is 192.168.99.100 23 | 🐳 Configuring Docker as the container runtime ... 24 | ✨ Preparing Kubernetes environment ... 25 | 🚜 Pulling images required by Kubernetes v1.13.4 ... 26 | 🚀 Launching Kubernetes v1.13.4 using kubeadm ... 27 | ⌛ Waiting for pods: apiserver proxy etcd scheduler controller addon-manager dns 28 | 🔑 Configuring cluster permissions ... 29 | 🤔 Verifying component health ..... 30 | 💗 kubectl is now configured to use "minikube" 31 | 🏄 Done! Thank you for using minikube! 32 | ``` 33 | 34 | * 检查状态 35 | 36 | ``` 37 | $ minikube status 38 | 39 | host: Running 40 | kubelet: Running 41 | apiserver: Running 42 | kubectl: Correctly Configured: pointing to minikube-vm at 192.168.99.100 43 | ``` 44 | 45 | `kubernetes`已经成功运行,可以使用`kubectl`访问集群: 46 | 47 | ``` 48 | $ kubectl get pods -n kube-system 49 | 50 | NAME READY STATUS RESTARTS AGE 51 | coredns-89cc84847-2k67h 1/1 Running 0 18m 52 | coredns-89cc84847-95zsj 1/1 Running 0 18m 53 | etcd-minikube 1/1 Running 0 18m 54 | kube-addon-manager-minikube 1/1 Running 0 19m 55 | kube-apiserver-minikube 1/1 Running 0 18m 56 | kube-controller-manager-minikube 1/1 Running 0 18m 57 | kube-proxy-f66hz 1/1 Running 0 18m 58 | kube-scheduler-minikube 1/1 Running 0 18m 59 | kubernetes-dashboard-7d8d567b4d-h82vx 1/1 Running 0 18m 60 | storage-provisioner 1/1 Running 0 18m 61 | ``` 62 | 63 | * **停止Minikube** 64 | 65 | ``` 66 | $ minikube stop 67 | 68 | ✋ Stopping "minikube" in virtualbox ... 69 | 🛑 "minikube" stopped. 70 | ``` 71 | 72 | * **删除本地集群** 73 | 74 | ``` 75 | $ minikube delete 76 | 77 | 🔥 Deleting "minikube" from virtualbox ... 78 | 💔 The "minikube" cluster has been deleted. 79 | ``` -------------------------------------------------------------------------------- /docs/nginx-architecture.md: -------------------------------------------------------------------------------- 1 | # Nginx架构基础 2 | 3 | ## Nginx 进程模型 4 | 5 | ![process model](https://cdn.mazhen.tech/images/202211161536855.png) 6 | 7 | Nginx其实有两种进程结构,一种是单进程结构,一种是多进程结构。单进程结构只适合我们做开发调试,在生产环境下,为了保持 Nginx 足够健壮,以及可以利用到 CPU 的多核特性,我们用到的是多进程架构的Nginx。 8 | 9 | 多进程架构的Nginx,有一个父进程 master process,master 会有很多子进程,这些子进程分为两类,一类是worker 进程,一类是 cache 相关的进程。 10 | 11 | 在一个四核的Linux服务器上查看Nginx进程: 12 | 13 | ```shell 14 | $ ps -ef --forest | grep nginx 15 | mazhen 20875 13073 0 15:25 pts/0 00:00:00 | \_ grep --color=auto nginx 16 | mazhen 20862 1 0 15:25 ? 00:00:00 nginx: master process ./sbin/nginx 17 | mazhen 20863 20862 0 15:25 ? 00:00:00 \_ nginx: worker process 18 | mazhen 20864 20862 0 15:25 ? 00:00:00 \_ nginx: worker process 19 | mazhen 20865 20862 0 15:25 ? 00:00:00 \_ nginx: worker process 20 | mazhen 20866 20862 0 15:25 ? 00:00:00 \_ nginx: worker process 21 | mazhen 20867 20862 0 15:25 ? 00:00:00 \_ nginx: cache manager process 22 | mazhen 20868 20862 0 15:25 ? 00:00:00 \_ nginx: cache loader process 23 | ``` 24 | 25 | 可以看到,Nginx 的 master 进程创建了4个 worker 进程,以及用来管理磁盘内容缓存的缓存helper进程。 26 | 27 | 为什么Nginx使用的是多进程结构,而不是多线程结构呢?因为多线程结构,线程之间是共享同一个进程地址空间,当某一个第三方模块出现了地址空间的断错误时,会导致整个Nginx进程挂掉,而多进程模型就不会出现这样的问题,Nginx的第三方模块通常不会在 master 进程中加入自己的功能代码。 28 | 29 | **master** 进程执行一些特权操作,比如读取配置以及绑定端口,它管理 worker 进程的,负责监控每个 worke进程是否在正常工作,是否需要重载配置文件,以及做热部署等。 30 | 31 | **worker** 进程处理真正的请求,从磁盘读取内容或往磁盘中写入内容,以及与上游服务器通信。 32 | 33 | **cache manager** 进程会周期性地运行,从磁盘缓存中删除条目,以保证缓存没有超过配置的大小。 34 | 35 | **cache loader** 进程在启动时运行,用于将磁盘上的缓存加载到内存中,随后退出。 36 | 37 | Nginx 采用了事件驱动的模型,它希望 worker 进程的数量和 CPU 一致,并且每一个 worker 进程与某一颗CPU绑定,worker 进程以非阻塞的方式处理多个连接,减少了上下文切换,同时更好的利用到了 CPU 缓存,减少缓存失效。 38 | 39 | ## 请求处理流程 40 | 41 | ![request process](https://cdn.mazhen.tech/images/202211171050526.png) 42 | 43 | Nginx 使用的是非阻塞的事件驱动处理引擎,需要用状态机来把这个请求正确的识别和处理。Nginx 内部有三个状态机,分别是处理4层 TCP 流量的传输层状态机,处理7层流量的HTTP状态机和处理邮件的email 状态机。 44 | 45 | worker 进程首先等待监听套接字上的事件,新接入的连接会触发事件,然后连接分配到一个状态机。 46 | 47 | ![state machine](https://cdn.mazhen.tech/images/202211171113018.png) 48 | 49 | 状态机本质上是告诉 Nginx 如何处理请求的指令集。解析出的请求是要访问静态资源,那么就去磁盘加载静态资源,更多的时候 Nginx 是作为负载均衡或者反向代理使用,这个时候请求会通过4层或7层协议,传输到上游服务器。对于每一个处理完成的请求,Nginx会记录 access 日志和 error 日志。 50 | 51 | ## Nginx 进程管理 52 | 53 | Linux 上多进程之间进行通讯,可以使用共享内存和信号。Nginx 在做进程间的管理时,使用了信号。我们可以使用 kill 命令直接向 master 进程和 worker 进程发送信号,也可以使用 nginx 命令行。 54 | 55 | master 进程接收处理的信号: 56 | 57 | * **CHLD** 在 Linux 系统中,当子进程终止的时候,会向父进程发送 CHLD 信号。master 进程启动的 worker 进程,所以 master 是 worker 的父进程。如果 worker 进程由于一些原因意外退出,那么 master 进程会立刻收到通知,可以重新启动一个新的 worker进程。 58 | 59 | * **TERM** 和 **INT** 立刻终止 worker 和 master 进程。 60 | * **QUIT** 优雅的停止 worker 和 master 进程。worker 不会向客户端发送 reset 立即结束连接。 61 | * **HUP** 重新加载配置文件 62 | * **USR1** 重新打开日志文件,做日志文件的切割 63 | * **USR2** 通知 master 开始进行热部署 64 | * **WINCH** 在热部署过程中,通知旧的 master ,让它优雅关闭 worker 进程 65 | 66 | 我们也可以通过 `nginx -s` 命令向 master 进程发送信号。在 Nginx 启动过程中, Nginx 会把 master 的 PID 记录在文件中,这个文件的默认位置是 `$nginx/logs/nginx.pid` 。 当我们执行 `nginx -s` 命令时,`nginx` 命令会去读取 `nginx.pid` 文件中 master 进程的 PID,然后向 master 进程发送对应的信号。下面是 `nginx -s` 命令对应的信号: 67 | 68 | * **reload** - HUP 69 | * **reopen** - USR1 70 | * **stop** - TERM 71 | * **quit** - QUIT 72 | 73 | 使用 `nginx -s` 和 直接使用 kill 命令向 master 进程发送信号,效果是一样的。 74 | 75 | 注意,**USR2** 和 **WINCH** 没有对应的 `nginx -s` 命令,只能通过 kill 命令直接向 master 进程发送。 76 | 77 | worker 进程能接收的信号: 78 | 79 | * TERM 和 INT 80 | * QUIT 81 | * USR1 82 | * WINCH 83 | 84 | worker 进程收到这些信号,会产生和发给 master 一样的效果。但我们通常不会直接向 worker 进程发送信号,而是通过 master 进程来管理 worker 进程,master 进程收到信号以后,会再把信号转发给 worker 进程。 85 | 86 | ## Nginx 配置更新流程 87 | 88 | ![reload](https://cdn.mazhen.tech/images/202211171726947.png) 89 | 90 | 当更改了 Nginx 配置文件后,我们都会执行 `nginx -s reload` 命令重新加载配置文件。Nginx 不会停止服务,在处理新的请求的同时,平滑的进行配置文件的更新。 91 | 92 | 执行 `nginx -s reload` 命令,会向 master 进程发送 SIGHUP 信号。当 master 进程接收 SIGHUP信号后,会做如下处理: 93 | 94 | 1. 检查配置文件语法是否正确。 95 | 2. master 加载配置,启动一组新的 worker 进程。这些 worker 进程马上开始接收新连接和处理网络请求。子进程可以共享使用父进程已经打开的端口,所以新的 worker 可以和老的worker监听同样的端口。 96 | 3. master 向旧的 worker 发送 QUIT 信号,让旧的 worker 优雅退出。 97 | 4. 旧的 worker 进程停止接收新连接,完成现有连接的处理后结束进程。 98 | 99 | ## Nginx 热部署流程 100 | 101 | ![hot deploy](https://cdn.mazhen.tech/images/202211171739417.png) 102 | 103 | Nginx 支持热部署,在升级的过程中也实现了高可用性,不导致任何连接丢失,停机时间或服务中断。热部署的流程如下: 104 | 105 | 1. 备份旧的 nginx 二进制文件,将新的nginx二进制文件拷贝到 `$nginx_home/sbin`目录。 106 | 2. 向 master 进程发送 USR2 信号。 107 | 3. master 进程用新的nginx文件启动新的master进程,新的master进程会启动新的worker进程。 108 | 4. 向旧的 master 进程发送 WINCH 信号,让它优雅的关闭旧的 worker 进程。此时旧的 master 仍然在运行。 109 | 5. 如果想回滚到旧版本,可以向旧的 master 发送 HUP 信号,向新的master 发送QUIT信号。 110 | 6. 如果一切正常,可以向旧的 master 发送 QUIT 信号,关闭旧的 master。 111 | -------------------------------------------------------------------------------- /docs/nginx-compile-install.md: -------------------------------------------------------------------------------- 1 | # Nginx的编译和安装 2 | 3 | [Nginx](https://nginx.org/) 是最流行的Web服务器,根据 [W3Techs](https://w3techs.com) 最新的统计,[世界上三分之一的网站在使用Nginx](https://w3techs.com/technologies/overview/web_server)。 4 | 5 | ## 准备工作 6 | 7 | ### Linux 版本 8 | 9 | Nginx 需要 Linux 的内核为 2.6 及以上的版本,因为Linux 内核从 2.6 开始支持 epoll。可以使用 `uname -a` 查看 Linux 内核版本: 10 | 11 | ```shell 12 | $ uname -a 13 | Linux mazhen-laptop 5.15.0-52-generic #58-Ubuntu SMP Thu Oct 13 08:03:55 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux 14 | ``` 15 | 16 | 从输出看到内核版本为 5.15.0,满足要求。现在已经很难找到内核 2.6 以下的服务器了吧。 17 | 18 | ### 安装依赖 19 | 20 | 为了编译 Nginx 源码,需要安装一些依赖包。本文以 Ubuntu 为例。 21 | 22 | 1. **GCC编译器** 23 | GCC(GNU Compiler Collection)是必需的编译工具。使用下面的命令安装: 24 | 25 | ```shell 26 | sudo apt install build-essential 27 | ``` 28 | 29 | `build-essential` 是所谓的 `meta-package`,包含了 g++/GNU 编译器集合,GNU调试器,以及一些编译程序所需的工具和库。 30 | 31 | 2. **PCRE库** 32 | PCRE库支持正则表达式。如果我们在配置文件nginx.conf中使用了正则表达式,那么在编译Nginx时就必须把PCRE库编译进 Nginx,因为 Nginx的 HTTP 模块需要靠它来解析正则表达式。另外,pcre-devel 是使用PCRE做二次开发时所需要的开发库,包括头文件等,这也是编译Nginx所必须使用的。使用下面的命令安装: 33 | 34 | ```shell 35 | sudo apt install libpcre3 libpcre3-dev 36 | ``` 37 | 38 | 3. **zlib库** 39 | 40 | zlib库用于对 HTTP 包的内容做 gzip 格式的压缩,如果我们在 nginx.conf 中配置了gzip on,并指定对于某些类型(content-type)的 HTTP 响应使用 gzip 来进行压缩以减少网络传输量,则在编译时就必须把 zlib 编译进 Nginx。zlib-devel 是二次开发所需要的库。使用下面的命令安装: 41 | 42 | ``` 43 | sudo apt install zlib1g-dev 44 | ``` 45 | 46 | 4. **OpenSSL库** 47 | 48 | 如果我们需要 Nginx 支持 SSL 加密传输,需要安装 OpenSSL 库。另外,如果我们想使用MD5、SHA1等散列函数,那么也需要安装它。使用下面的命令安装: 49 | 50 | ```shell 51 | sudo apt install openssl libssl-dev 52 | ``` 53 | 54 | ## 下载Nginx源码 55 | 56 | 从 [http://nginx.org/en/download.html](http://nginx.org/en/download.html)下载当前稳定版本的源码。 57 | 58 | ![nginx](https://cdn.mazhen.tech/images/202211111506229.png) 59 | 60 | 当前稳定版为 1.22.1: 61 | 62 | ```shell 63 | wget https://nginx.org/download/nginx-1.22.1.tar.gz 64 | ``` 65 | 66 | ## Nginx配置文件的语法高亮 67 | 68 | 为了 Nginx 的配置文件在 vim 中能语法高亮,需要经过如下配置。 69 | 70 | 解压 Nginx 源码: 71 | 72 | ```sehll 73 | tar -zxvf nginx-1.22.1.tar.gz 74 | ``` 75 | 76 | 将 Nginx 源码目录 `contrib/vim/` 下的所有内容,复制到 `$HOME/.vim` 目录: 77 | 78 | ```shell 79 | mkdir ~/.vim 80 | cp -r contrib/vim/* ~/.vim/ 81 | ``` 82 | 83 | 现在使用 `vim` 打开 `nginx.conf`,可以看到配置文件已经可以语法高亮了。 84 | 85 | ## 编译前的配置 86 | 87 | 编译前需要使用 configure 命令进行相关参数的配置。 88 | 89 | 使用 `configure --help` 查看编译配置支持的参数: 90 | 91 | ```shell 92 | $ ./configure --help | more 93 | --help print this message 94 | 95 | --prefix=PATH set installation prefix 96 | --sbin-path=PATH set nginx binary pathname 97 | --modules-path=PATH set modules path 98 | --conf-path=PATH set nginx.conf pathname 99 | --error-log-path=PATH set error log pathname 100 | --pid-path=PATH set nginx.pid pathname 101 | --lock-path=PATH set nginx.lock pathname 102 | 103 | ...... 104 | 105 | --with-libatomic force libatomic_ops library usage 106 | --with-libatomic=DIR set path to libatomic_ops library sources 107 | 108 | --with-openssl=DIR set path to OpenSSL library sources 109 | --with-openssl-opt=OPTIONS set additional build options for OpenSSL 110 | 111 | --with-debug enable debug logging 112 | ``` 113 | 114 | `--with`开头的模块缺省不包括在编译结果中,如果想使用需要在编译配置时显示的指定。`--without`开头的模块则相反,如果不想包含在编译结果中需要显示设定。 115 | 116 | 例如我们可以这样进行编译前设置: 117 | 118 | ```shell 119 | ./configure --prefix=/home/mazhen/nginx --with-http_ssl_module 120 | ``` 121 | 122 | 设置了Nginx的安装目录,以及需要`http_ssl`模块。 123 | 124 | `configure`命令执行完后,会生成中间文件,放在目录`objs`下。其中最重要的是`ngx_modules.c`文件,它决定了最终那些模块会编译进`nginx`。 125 | 126 | ## 编译和安装 127 | 128 | * 执行编译 129 | 130 | 在nginx目录下执行`make`编译: 131 | 132 | ```shell 133 | make 134 | ``` 135 | 136 | 编译成功的`nginx`二进制文件在`objs`目录下。如果是做nginx的升级,可以直接将这个二进制文件copy到nginx的安装目录中。 137 | 138 | * 安装 139 | 140 | 在nginx目录下执行`make install`进行安装: 141 | 142 | ```shell 143 | make install 144 | ``` 145 | 146 | 安装完成后,我们到 `--prefix` 指定的目录中查看安装结果: 147 | 148 | ```shell 149 | $ tree -L 1 /home/mazhen/nginx 150 | nginx/ 151 | ├── conf 152 | ├── html 153 | ├── logs 154 | └── sbin 155 | ``` 156 | 157 | ## 验证安装结果 158 | 159 | 编辑 `nginx/conf/nginx.conf` 文件,设置监听端口为`8080`: 160 | 161 | ```nginx 162 | http { 163 | ... 164 | 165 | server { 166 | listen 8080; 167 | server_name localhost; 168 | ... 169 | ``` 170 | 171 | 启动 nginx 172 | 173 | ```shell 174 | ./sbin/nginx 175 | ``` 176 | 177 | 访问默认首页: 178 | 179 | ```shell 180 | $ curl -I http://localhost:8080 181 | HTTP/1.1 200 OK 182 | Server: nginx/1.22.1 183 | Date: Fri, 11 Nov 2022 08:04:46 GMT 184 | Content-Type: text/html 185 | Content-Length: 615 186 | Last-Modified: Tue, 08 Nov 2022 09:54:09 GMT 187 | Connection: keep-alive 188 | ETag: "636a2741-267" 189 | Accept-Ranges: bytes 190 | ``` 191 | -------------------------------------------------------------------------------- /docs/nginx-macos-debug.md: -------------------------------------------------------------------------------- 1 | # 在macOS上使用VSCode调试NGINX" 2 | 3 | ## 下载Nginx源码 4 | 5 | 在 [nginx: download](https://nginx.org/en/download.html) 选择当前稳定版本下载 6 | 7 | ```shell 8 | curl -OL https://nginx.org/download/nginx-1.22.1.tar.gz 9 | tar -zxvf nginx-1.22.1.tar.gz 10 | ``` 11 | 12 | ## 下载Nginx依赖 13 | 14 | 1. [PCRE]([http://www.pcre.org](https://links.jianshu.com/go?to=http%3A%2F%2Fwww.pcre.org) ) rewrite 模块依赖 15 | 16 | 从 [sourceforge](https://sourceforge.net/projects/pcre/files/pcre/8.45/pcre-8.45.tar.gz/download) 下载 [pcre-8.45.tar.gz](https://sourceforge.net/projects/pcre/files/pcre/8.45/pcre-8.45.tar.gz/download),和Nginx源码解压到同级目录。 17 | 18 | 2. [zlib](http://zlib.net) gzip 模块依赖 19 | 20 | ```shell 21 | curl -OL https://zlib.net/zlib-1.2.13.tar.gz 22 | tar -zxvf zlib-1.2.13.tar.gz 23 | ``` 24 | 25 | 3. [OpenSSL](https://www.openssl.org) 26 | 27 | ```shell 28 | curl -OL https://www.openssl.org/source/openssl-1.1.1s.tar.gz 29 | tar -zxvf openssl-1.1.1s.tar.gz 30 | ``` 31 | 32 | ## 修改默认配置 33 | 34 | Nginx默认以 daemon 形式运行,会使用 [double fork](https://stackoverflow.com/questions/881388/what-is-the-reason-for-performing-a-double-fork-when-creating-a-daemon) 技巧,调用 `fork()` 创建子进程并且把父进程直接丢弃,达到将 daemon 进程与会话的控制终端分离的目的。同时,Nginx 默认是多进程架构,有一个 master 父进程和多个 worker 子进程。为了调试方便,可以修改默认配置 `conf/nginx.conf`,关闭 daemon,并以单进程模式运行: 35 | 36 | ```nginx 37 | daemon off; 38 | master_process off; 39 | ``` 40 | 41 | ## 编译选项配置 42 | 43 | 使用 configure 命令进行相关编译参数配置: 44 | 45 | * `--with-debug` 启用 [debugging log](https://nginx.org/en/docs/debugging_log.html) 46 | * `--with-cc-opt='-O0 -g'` ,使用 `-g` 包含 debug 符号信息,`-O0`标志禁用编译器优化 47 | * `--prefix` 指定安装目录 48 | * `--with-...` 指定依赖的源码位置 49 | 50 | ```shell 51 | ./configure --with-debug --with-cc-opt='-O0 -g' \ 52 | --prefix=./dist \ 53 | --with-http_ssl_module \ 54 | --with-pcre=../pcre-8.45 \ 55 | --with-zlib=../zlib-1.2.13 \ 56 | --with-openssl=../openssl-1.1.1s 57 | ``` 58 | 59 | ## 编译和安装 60 | 61 | ```shell 62 | make 63 | make install 64 | ``` 65 | 66 | ## 配置VSCode 67 | 68 | 首先参考 [VSCode 官方文档](https://code.visualstudio.com/docs/cpp/config-clang-mac),完成 VS Code C++ 开发环境的配置。 69 | 70 | * 确认本机是否已经安装了 Clang 编译器: 71 | 72 | ```shell 73 | # 确认是否安装了Clang 74 | $ clang --version 75 | # 安装开发者命令行工具,包括Clang、git等 76 | $ xcode-select --install 77 | ``` 78 | 79 | * 安装 [C++ extension for VS Code](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools)。 80 | 81 | 完成C++开发环境准备后,使用 VSCode 打开 nginx 源码,点击菜单 "Run -> Starting Debugging",在提示中选择 `LLDB`,创建出 `launch.json`,编辑该文件进行 debug 配置。将 "program" 设置为上一步编译出带有debug信息的nginx。 82 | 83 | ```json 84 | { 85 | // Use IntelliSense to learn about possible attributes. 86 | // Hover to view descriptions of existing attributes. 87 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 88 | "version": "0.2.0", 89 | "configurations": [ 90 | { 91 | "type": "lldb", 92 | "request": "launch", 93 | "name": "Debug", 94 | "program": "${workspaceFolder}/dist/sbin/nginx", 95 | "args": [], 96 | "cwd": "${workspaceFolder}" 97 | } 98 | ] 99 | } 100 | ``` 101 | 102 | 现在就可以在代码中设置断点,再次点击 "Run -> Starting Debugging",开始调试 Nginx 吧。 103 | 104 | ![debug nginx](https://cdn.mazhen.tech/images/202211211116679.png) 105 | -------------------------------------------------------------------------------- /docs/nginx-phytium.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "在国产飞腾平台上编译安装nginx" 3 | date: 2019-04-02T09:45:05+08:00 4 | draft: false 5 | tags: [linux, nginx] 6 | categories: [tech] 7 | --- 8 | 9 | [飞腾芯片](http://www.phytium.com.cn) + [银河麒麟OS](http://www.kylinos.cn/)是目前国产自主可控市场上的主流基础平台。飞腾芯片是[aarch64](https://en.wikipedia.org/wiki/ARM_architecture#AArch64)架构,是支持64位的[ARM](https://en.wikipedia.org/wiki/ARM_architecture)芯片。银河麒麟是基于Ubuntu的发行版。因此可以认为`飞腾芯片` + `银河麒麟OS`相当于 `ARM64` + `Ubuntu`。 10 | 11 | 本文介绍在飞腾平台上编译安装nginx的步骤。 12 | 13 | * **下载nginx源码** 14 | 15 | 从[http://nginx.org/en/download.html](http://nginx.org/en/download.html)下载当前稳定版本的源码,例如 16 | 17 | ``` 18 | wget http://nginx.org/download/nginx-1.14.2.tar.gz 19 | ``` 20 | 21 | 解压nginx源码: 22 | 23 | ``` 24 | tar -zxvf nginx-1.14.2.tar.gz 25 | ``` 26 | 27 | * **nginx配置文件的语法高亮** 28 | 29 | 将nginx源码目录下`contrib/vim/`的所有内容,copy到用户的`$HOME/.vim`目录,可以实现nginx配置文件在`vim`中的语法高亮。 30 | 31 | ``` 32 | mkdir ~/.vim 33 | cp -r contrib/vim/* ~/.vim/ 34 | ``` 35 | 36 | 再使用`vim`打开`nginx.conf`,可以看到配置文件已经可以语法高亮。 37 | 38 | * **编译前的配置** 39 | 40 | 查看编译配置支持的参数 41 | 42 | ``` 43 | $ ./configure --help | more 44 | 45 | --help print this message 46 | 47 | --prefix=PATH set installation prefix 48 | --sbin-path=PATH set nginx binary pathname 49 | --modules-path=PATH set modules path 50 | --conf-path=PATH set nginx.conf pathname 51 | --error-log-path=PATH set error log pathname 52 | --pid-path=PATH set nginx.pid pathname 53 | --lock-path=PATH set nginx.lock pathname 54 | 55 | --user=USER set non-privileged user for 56 | worker processes 57 | --group=GROUP set non-privileged group for 58 | worker processes 59 | 60 | --build=NAME set build name 61 | --builddir=DIR set build directory 62 | 63 | --with-select_module enable select module 64 | --without-select_module disable select module 65 | --with-poll_module enable poll module 66 | --without-poll_module disable poll module 67 | 68 | ...... 69 | 70 | --with-libatomic force libatomic_ops library usage 71 | --with-libatomic=DIR set path to libatomic_ops library sources 72 | 73 | --with-openssl=DIR set path to OpenSSL library sources 74 | --with-openssl-opt=OPTIONS set additional build options for OpenSSL 75 | 76 | --with-debug enable debug logging 77 | ``` 78 | 79 | `--with`开头的模块缺省不包括在编译结果中,如果想使用需要在编译配置时显示的指定。`--without`开头的模块则相反,如果不想包含在编译结果中需要显示设定。 80 | 81 | 例如我们可以这样进行编译前设置: 82 | 83 | ``` 84 | ./configure --prefix=/home/adp/nginx --with-http_ssl_module 85 | ``` 86 | 87 | 设置了nginx的安装目录,需要`http_ssl`模块。 88 | 89 | 如果报错缺少`OpenSSL`,需要先安装`libssl`。在`/etc/apt/sources.list.d`目录下增加支持`ARM64`的apt源,例如国内的清华,创建`tsinghua.list`,内容如下: 90 | 91 | ``` 92 | deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ xenial main multiverse restricted universe 93 | deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ xenial-security main multiverse restricted universe 94 | deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ xenial-updates main multiverse restricted universe 95 | deb http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ xenial-backports main multiverse restricted universe 96 | deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ xenial main multiverse restricted universe 97 | deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ xenial-security main multiverse restricted universe 98 | deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ xenial-updates main multiverse restricted universe 99 | deb-src http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ xenial-backports main multiverse restricted universe 100 | ``` 101 | 102 | 执行命令安装`OpenSSL`: 103 | 104 | ``` 105 | $ sudo apt-get update 106 | $ sudo apt-get install libssl-dev 107 | ``` 108 | 109 | `configure`命令执行完后,会生成中间文件,放在目录`objs`下。其中最重要的是`ngx_modules.c`文件,它决定了最终那些模块会编译进`nginx`。 110 | 111 | * 执行编译 112 | 113 | 在nginx目录下执行`make`编译: 114 | 115 | ``` 116 | $ make 117 | ``` 118 | 119 | 编译成功的`nginx`二进制文件在`objs`目录下。如果是做nginx的升级,可以直接将这个二进制文件copy到nginx的安装目录中。 120 | 121 | * 安装 122 | 123 | 在nginx目录下执行`make install`进行安装: 124 | 125 | ``` 126 | $ make install 127 | ``` 128 | 129 | 安装完成后,我们到`--prefix`指定的目录中查看安装结果: 130 | 131 | ``` 132 | $ tree -L 1 /home/adp/nginx 133 | 134 | nginx/ 135 | ├── conf 136 | ├── html 137 | ├── logs 138 | └── sbin 139 | ``` 140 | 141 | * 验证安装结果 142 | 143 | 编辑`nginx/conf/nginx.conf`文件,设置监听端口为`8080`: 144 | 145 | ``` 146 | http { 147 | ... 148 | 149 | server { 150 | listen 8080; 151 | server_name localhost; 152 | ... 153 | ``` 154 | 155 | 启动nginx 156 | 157 | ``` 158 | ./sbin/nginx 159 | ``` 160 | 161 | 访问默认首页: 162 | 163 | ``` 164 | $ curl -I http://localhost:8080 165 | 166 | HTTP/1.1 200 OK 167 | Server: nginx/1.14.2 168 | Date: Tue, 02 Apr 2019 08:38:02 GMT 169 | Content-Type: text/html 170 | Content-Length: 612 171 | Last-Modified: Tue, 02 Apr 2019 08:30:04 GMT 172 | Connection: keep-alive 173 | ETag: "5ca31d8c-264" 174 | Accept-Ranges: bytes 175 | ``` 176 | 177 | 其他常用命令: 178 | 179 | ``` 180 | # 查看帮助 181 | $ ./sbin/nginx -? 182 | 183 | # 重新加载配置 184 | $ ./sbin/nginx -s reload 185 | 186 | # 立即停止服务 187 | $ ./sbin/nginx -s stop 188 | 189 | # 优雅停止服务 190 | $ ./sbin/nginx -s quit 191 | 192 | # 测试配置文件是否有语法错误 193 | $ ./sbin/nginx -t/-T 194 | 195 | # 打印nginx版本、编译信息 196 | $ ./sbin/nginx -v/-V 197 | ``` 198 | -------------------------------------------------------------------------------- /docs/nginx-reverse-proxy.md: -------------------------------------------------------------------------------- 1 | # Nginx反向代理配置 2 | 3 | 反向代理(reverse proxy)是指用代理服务器来接受外部的访问请求,然后将请求转发给内网的上游服务器,并将从上游服务器上得到的结果返回外部客户端。作为反向代理是 Nginx 的一种常见用法。 4 | 5 | ![reverse proxy](https://cdn.mazhen.tech/images/202211151520191.webp) 6 | 7 | 这里的负载均衡是指选择一种策略,尽量把请求平均地分布到每一台上游服务器上。下面介绍负载均衡的配置项。 8 | 9 | ## upstream 10 | 11 | 作为反向代理,一般都需要向上游服务器的集群转发请求。[upstream](https://nginx.org/en/docs/http/ngx_http_upstream_module.html#upstream) 块定义了一个上游服务器的集群,便于反向代理中的 proxy_pass使用。 12 | 13 | ```nginx 14 | http { 15 | ... 16 | upstream backend { 17 | server 127.0.0.1:8080; 18 | } 19 | ... 20 | } 21 | ``` 22 | 23 | upstream 定义了一组上游服务器,并命名为 `backend`。 24 | 25 | ## proxy_pass 26 | 27 | [proxy_pass](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass) 指令设置代理服务器的协议和地址。协议可以指定 "http "或 "https"。地址可以指定为域名或IP地址,也可以配置为 upstream 定义的上游服务器: 28 | 29 | ```nginx 30 | http { 31 | server { 32 | listen 6888; 33 | server_name localhost; 34 | 35 | location / { 36 | proxy_pass http://backend; 37 | } 38 | } 39 | } 40 | ``` 41 | 42 | ## proxy_set_header 43 | 44 | 在传递给上游服务器的请求头中,可以使用[proxy_set_header](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_set_header) 重新定义或添加字段。一般我们使用 proxy_set_header 向上游服务器传递一些必要的信息。 45 | 46 | ```nginx 47 | location / { 48 | proxy_set_header Host $http_host; 49 | proxy_set_header X-Real-IP $remote_addr; 50 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 51 | proxy_pass http://backend; 52 | } 53 | ``` 54 | 55 | 上面的配置使用 proxy_set_header 添加了三个 HTTP header: 56 | 57 | * Host 58 | 59 | Host 是表明请求的主机名。默认情况下,Nginx 向上游服务器发送请求时,请求头中的 Host 字段是上游真实服务器的IP和端口号。如果我们想让传递给上游服务器的 Host 字段,包含的是用户访问反向代理时使用的域名,就需要通过 `proxy_set_header` 设置 Host 字段,值可以为 **$host** 或 **$http_host**,区别是前者只包含IP,而后者包含IP和端口号。 60 | 61 | * X-Real-IP 62 | 63 | 经过反向代理后,上游服务器无法直接拿到客户端的 ip,也就是说,在应用中使用`request.getRemoteAddr()` 获得的是 Nginx 的地址。通过 `proxy_set_header X-Real-IP $remote_addr;`,将客户端的 ip 添加到了 HTTP header中,让应用可以使用 `request.getHeader(“X-Real-IP”)` 获取客户端的真实ip。 64 | 65 | * X-Forwarded-For 66 | 67 | 如果配置了多层反向代理,当一个请求经过多层代理到达上游服务器时,上游服务器通过 X-Real-IP 获得的就不是客户端的真实IP了。那么这个时候就要用到 **X-Forwarded-For** ,设置 X-Forwarded-For 时是增加,而不是覆盖,从客户的真实IP为起点,穿过多层级代理 ,最终到达上游服务器,都会被记录下来。 68 | 69 | ## proxy_cache 70 | 71 | Nginx 作为反向代理支持的所有特性和内置变量都可以在 [ngx_http_proxy_module](https://nginx.org/en/docs/http/ngx_http_proxy_module.html) 的文档页面找到: 72 | 73 | ![ngx_http_proxy_module](https://cdn.mazhen.tech/images/202211160945215.png) 74 | 75 | 其中一个比较重要的特性是 proxy cache,对访问上游服务器的请求进行缓存,极大减轻了对上游服务的压力。 76 | 77 | 配置示例: 78 | 79 | ```nginx 80 | http { 81 | ... 82 | proxy_cache_path /tmp/nginx/cache levels=1:2 keys_zone=myzone:10m inactive=1h max_size=10g use_temp_path=off; 83 | server { 84 | ... 85 | location / { 86 | ... 87 | proxy_cache myzone; 88 | proxy_cache_key $host$uri$is_args$args; 89 | proxy_cache_valid 200 304 302 12h; 90 | } 91 | } 92 | } 93 | ``` 94 | 95 | 配置说明: 96 | 97 | * `proxy_cache_path` 缓存路径,要把缓存放在哪里 98 | * `levels=1:2`:缓存的目录结构 99 | * `keys_zone=myzone:10m`:定义一块用于存放缓存key的共享内存区,命名为myzone,并分配 10MB 的内存;配至10MB的zone 大约可以存放 80000个key。 100 | * `inactive=1d`:不活跃的缓存文件 1 小时后将被清除 101 | * `max_size=10g`:缓存所占磁盘空间的上限 102 | * `use_temp_path=off`:不另设临时目录 103 | 104 | * `proxy_cache myzone;`:代表要使用上面定义的 myzone 105 | * `proxy_cache_key`:用于生成缓存键,区分不同的资源。key 是决定缓存命中率的因素之一。 106 | * `$host`:request header中的 Host字段 107 | * `$uri`:请求的uri 108 | * `$is_args` 反映请求的 URI 是否带参数,若没有即为空值。 109 | * `$args`:请求中的参数 110 | 111 | * `proxy_cache_valid`:控制缓存有效期,可以针对不同的 HTTP 状态码可以设定不同的有效期。示例针对 200,304,302 状态码的缓存有效期为12小时。 112 | 113 | 检验缓存配置的效果。 114 | 115 | 首先查看缓存路径,没有存放任何内容: 116 | 117 | ```shell 118 | $ tree /tmp/nginx/cache/ 119 | /tmp/nginx/cache/ 120 | 121 | 0 directories, 0 files 122 | ``` 123 | 124 | 然后访问Nginx反向代理服务器: 125 | 126 | ```shell 127 | ❯ curl -v http://172.21.32.84:6888/ 128 | 129 | ... 130 | ``` 131 | 132 | 再次查看缓存路径: 133 | 134 | ```shell 135 | $ tree /tmp/nginx/cache/ 136 | /tmp/nginx/cache/ 137 | └── 6 138 | └── ed 139 | └── 5e9596b7783c532f541535dd1a60eed6 140 | 141 | 2 directories, 1 file 142 | ``` 143 | 144 | 经过请求后,缓存路径中已经有内容,并且目录结构是我们配置的 `level=1:2`。 145 | -------------------------------------------------------------------------------- /docs/nginx-static-resource-service.md: -------------------------------------------------------------------------------- 1 | # Nginx静态资源服务的配置 2 | 3 | ## 配置文件语法 4 | 5 | Nginx的配置文件是一个文本文件,由指令和指令块构成。 6 | 7 | ### 指令 8 | 9 | **指令**以分号 `;` 结尾,指令和参数间以空格分割。 10 | 11 | **指令块**作为容器,将相关的指令组合在一起,用大括号 `{}` 将它们包围起来。 12 | 13 | ```nginx 14 | http { 15 | include mime.types; 16 | default_type application/octet-stream; 17 | 18 | server { 19 | listen 8080; 20 | server_name localhost; 21 | location / { 22 | root html; 23 | index index.html index.htm; 24 | } 25 | } 26 | } 27 | ``` 28 | 29 | 上面配置中的http、server、location等都是指令块。指令块配置项之后是否如参数(例如 location /),取决于解析这个块配置项的模块。 30 | 31 | 指令块配置项是可以嵌套的。内层块会继承父级块包含的指令的设置。有些指令可以出现在多层指令块内,你可以通过在内层指令块包含该指令,来覆盖从父级继承的设置。 32 | 33 | ### Context 34 | 35 | 一些 top-level 指令被称为 **context**,将适用于不同流量类型的指令组合在一起。 36 | 37 | * **events** – 通用的连接处理 38 | * **http** – HTTP流量 39 | * **mail** – Mail 流量 40 | * **stream** – TCP 和 UDP 流量 41 | 42 | 放在这些 context 之外的指令是在 main context中。 43 | 44 | 在每个流量处理 context 中,可以包括一个或多个 **server** 块,用来定义控制请求处理的虚拟服务器。 45 | 46 | 对于HTTP流量,每个 server 指令块是对特定域名或IP地址访问的控制。通过一个活多个 **location** 定义如何处理特定的URI。 47 | 48 | 对于 Mail 和 TCP/UDP 流量,server 指令块是对特定 TCP 端口流量的控制。 49 | 50 | ## 静态资源服务 51 | 52 | 将个人网站的静态资源 clone 到 nginx 根目录: 53 | 54 | ```shell 55 | git clone https://github.com/mz1999/mazhen.git 56 | ``` 57 | 58 | 在 `conf/nginx.conf` 文件中配置监听端口和 `location`: 59 | 60 | ```nginx 61 | http { 62 | server { 63 | listen 8080; 64 | server_name localhost; 65 | 66 | #charset koi8-r; 67 | #access_log logs/host.access.log main; 68 | 69 | location / { 70 | alias mazhen/; 71 | #index index.html index.htm; 72 | } 73 | 74 | } 75 | ``` 76 | 77 | location 的语法格式为: 78 | 79 | ``` 80 | location [ = | ~ | ~* | ^~ ] uri { ... } 81 | ``` 82 | 83 | `location` 会尝试根据用户请求中的 URI 来匹配上面的 `uri` 表达式,如果可以匹配,就选择这个 `location` 块中的配置来处理用户请求。 84 | 85 | `location` 指定文件路径有两种方式:**root**和**alias**。 86 | 87 | `root` 与`alias` 会以不同的方式将请求映射到服务器的文件上,它们的主要区别在于如何解释 `location` 后面的 `uri` 。 88 | 89 | * **root**的处理结果是,root+location uri。 90 | * **alias**的处理结果是,使用 `alias` 替换 `location uri`。 `alias` 作为一个目录别名的定义。 91 | 92 | 例如: 93 | 94 | ```nginx 95 | location /i/ { 96 | root /data/w3; 97 | } 98 | ``` 99 | 100 | 如果一个请求的 URI 是 `/i/top.gif` ,Nginx 将会返回服务器上的 `/data/w3/i/top.gif` 文件。 101 | 102 | ```nginx 103 | location /i/ { 104 | alias /data/w3/images/; 105 | } 106 | ``` 107 | 108 | 如果一个请求的 URI 是 `/i/top.gif`,Nginx 将会返回服务器上的 `/data/w3/images/top.gif`文件。alias 会把 `location` 后面配置的 `uri` 替换为 `alias` 定义的目录。 109 | 110 | 最后要注意,使用 `alias` 时,目录名后面一定要加 `/`。 111 | 112 | ## 开启gzip 113 | 114 | Nginx 的 `ngx_http_gzip_module` 模块是一个过滤器,它使用 "gzip "方法压缩响应。可以在 http context 下配置 gzip: 115 | 116 | ```nginx 117 | http { 118 | ... 119 | gzip on; 120 | gzip_min_length 1000; 121 | gzip_comp_level 2; 122 | gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; 123 | 124 | server { 125 | ... 126 | } 127 | } 128 | ``` 129 | 130 | * gzip_min_length:设置允许压缩的页面最小字节数 131 | * gzip_comp_level: 设置 gzip 压缩比,1 压缩比最小处理速度最快,9 压缩比最大但处理最慢 132 | * gzip_types:匹配MIME类型进行压缩。 133 | 134 | 更多的配置项,可以参考[官方文档](https://nginx.org/en/docs/http/ngx_http_gzip_module.html)。 135 | 136 | ## autoindex 137 | 138 | Nginx 的 `ngx_http_autoindex_module` 模块处理以斜线字符 `/` 结尾的请求,并产生一个目录列表。通常情况下,当 `ngx_http_index_module` 模块找不到index文件时,请求会被传递给 `ngx_http_autoindex_module` 模块。 139 | 140 | autoindex 的配置很简单: 141 | 142 | ```nginx 143 | location / { 144 | alias mazhen/; 145 | autoindex on; 146 | } 147 | ``` 148 | 149 | 注意,只有 index 模块找不到index文件时,请求才会被 autoindex 模块处理。我们可以把 `mazhen` 目录下的 index 文件删掉,或者为 index 指令配置一个不存在的文件。 150 | 151 | ## limit_rate 152 | 153 | 由于带宽的限制,我们有时候需要限制某些资源向客户端传输响应的速率,例如可以对大文件限速,避免传输大文件占用过多带宽,从而影响其他更重要的小文件(css,js)的传输。我们可以使用 `set` 指令配合内置变量 `$limit_rate` 实现这个功能: 154 | 155 | ```nginx 156 | location / { 157 | ... 158 | set $limit_rate 1k; 159 | } 160 | ``` 161 | 162 | 上面的指令限制了Nginx向客户端发送响应的速率为 1k/秒。 163 | 164 | `$limit_rate`是Nginx的内置变量,Nginx的文档详细列出了每个模块的内置变量。以 [ngx_http_core_module](https://nginx.org/en/docs/http/ngx_http_core_module.html) 为例,在 [Nginx文档首页](https://nginx.org/en/docs/)的 Modules reference 部分,点击进入 [ngx_http_core_module](https://nginx.org/en/docs/http/ngx_http_core_module.html) : 165 | 166 | ![http_core_module](https://cdn.mazhen.tech/images/202211151058224.png) 167 | 168 | 在 [ngx_http_core_module](https://nginx.org/en/docs/http/ngx_http_core_module.html) 文档目录的最下方,点击 [Embedded Variables](https://nginx.org/en/docs/http/ngx_http_core_module.html#variables) ,会跳转到 [ngx_http_core_module](https://nginx.org/en/docs/http/ngx_http_core_module.html) 内置变量列表: 169 | 170 | ![Embedded Variables](https://cdn.mazhen.tech/images/202211151116755.png) 171 | 172 | 这里有 http module 所有内置变量的说明,包括我们刚才使用 [$limit_rate](https://nginx.org/en/docs/http/ngx_http_core_module.html#var_limit_rate)。 173 | 174 | ## access log 175 | 176 | Nginx 的 access log 功能由 [ngx_http_log_module](https://nginx.org/en/docs/http/ngx_http_log_module.html) 模块提供。ngx_http_log_module 提供了两个指令: 177 | 178 | * **log_format** 指定日志格式 179 | * **access_log** 设置日志写入的路径 180 | 181 | 举例说明: 182 | 183 | ```nginx 184 | http { 185 | ... 186 | log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 187 | '$status $body_bytes_sent "$http_referer" ' 188 | '"$http_user_agent" "$http_x_forwarded_for"'; 189 | server { 190 | ... 191 | access_log logs/mazhen.access.log main; 192 | } 193 | } 194 | ``` 195 | 196 | log_format 使用内置变量定义日志格式,示例中的 log_format 可以使用 http module 定义的内置变量。log_format 还指定了这个日志格式的名称为 main,这样让我们定义多种格式的日志,为不同的 server 配置特定的日志格式。 197 | 198 | access_log 设置了日志路径为 `logs/mazhen.access.log`,并指定了日志格式为 main。示例中的 access_log 定义在 server 下,那所有发往这个 server 的请求日志都使用 main 格式,被记录在 `logs/mazhen.access.log`文件中。 199 | -------------------------------------------------------------------------------- /docs/nginx-usage.md: -------------------------------------------------------------------------------- 1 | # Nginx的基本使用 2 | 3 | ## Nginx 常用命令 4 | 5 | Nginx的指令格式为 `nginx [options argument]`。 6 | 7 | * **查看帮助** 8 | 9 | ```shell 10 | ./sbin/nginx -? 11 | ``` 12 | 13 | * **使用指定的配置文件** 14 | 15 | ```shell 16 | ./sbin/nginx -c filename 17 | ``` 18 | 19 | * **指定运行目录** 20 | 21 | ```shell 22 | ./sbin/nginx -p /home/mazhen/nginx/ 23 | ``` 24 | 25 | * **设置配置指令**,覆盖配置文件中的指令 26 | 27 | ```shell 28 | ./sbin/nginx -g directives 29 | ``` 30 | 31 | * **向 Nginx 发送信号** 32 | 33 | 我们可以向 Nginx 进程发送信号,控制运行中的 Nginx。一种方法是使用 `kill` 命令,也可以使用 `nginx -s` : 34 | 35 | ```shell 36 | # 重新加载配置 37 | $ ./sbin/nginx -s reload 38 | 39 | # 立即停止服务 40 | $ ./sbin/nginx -s stop 41 | 42 | # 优雅停止服务 43 | $ ./sbin/nginx -s quit 44 | 45 | # 重新开始记录日志文件 46 | $ ./sbin/nginx -s reopen 47 | ``` 48 | 49 | * **测试配置文件是否有语法错误** 50 | 51 | ```shell 52 | ./sbin/nginx -t/-T 53 | ``` 54 | 55 | * **打印nginx版本** 56 | 57 | ```shell 58 | ./sbin/nginx -v/-V 59 | ``` 60 | 61 | ## 热部署 62 | 63 | 在不停机的情况下升级正在运行的 Nginx 版本,就是热部署。 64 | 65 | 首先查看正在运行的 Nginx: 66 | 67 | ```shell 68 | $ ps aux | grep nginx 69 | mazhen 4376 0.0 0.0 9896 2372 ? Ss 16:47 0:00 nginx: master process ./sbin/nginx 70 | mazhen 4402 0.0 0.0 10324 2104 ? S 16:50 0:00 nginx: worker process 71 | mazhen 4403 0.0 0.0 10324 2104 ? S 16:50 0:00 nginx: worker process 72 | mazhen 4404 0.0 0.0 10324 2104 ? S 16:50 0:00 nginx: worker process 73 | mazhen 4405 0.0 0.0 10324 2104 ? S 16:50 0:00 nginx: worker process 74 | mazhen 4407 0.0 0.0 12184 2316 pts/0 S+ 16:51 0:00 grep --color=auto nginx 75 | ``` 76 | 77 | 备份现有 Nginx 的二进制文件: 78 | 79 | ```shell 80 | cp nginx nginx.old 81 | ``` 82 | 83 | 将构建好的最新版 Nginx 的二进制文件拷贝到 `$nginx/sbin` 目录: 84 | 85 | ```shell 86 | cp ~/works/nginx-1.22.1/objs/nginx ~/nginx/sbin/ -f 87 | ``` 88 | 89 | 给正在运行的Nginx的 master 进程发送信号,通知它我们要开始进行热部署: 90 | 91 | ```shell 92 | kill -USR2 4376 93 | ``` 94 | 95 | 这时候 Nginx master 进程会使用新的二进制文件,启动新的 master 进程。新的 master 会生成新的 worker,同时,老的worker并没有退出,也在运行中,但不再监听 80/443 端口,请求会平滑的过度到新 worker 中。 96 | 97 | ```shell 98 | $ ps aux | grep nginx 99 | mazhen 4376 0.0 0.0 9896 2536 ? Ss 16:47 0:00 nginx: master process ./sbin/nginx 100 | mazhen 4402 0.0 0.0 10324 2104 ? S 16:50 0:00 nginx: worker process 101 | mazhen 4403 0.0 0.0 10324 2104 ? S 16:50 0:00 nginx: worker process 102 | mazhen 4404 0.0 0.0 10324 2104 ? S 16:50 0:00 nginx: worker process 103 | mazhen 4405 0.0 0.0 10324 2104 ? S 16:50 0:00 nginx: worker process 104 | mazhen 4454 0.0 0.0 9768 6024 ? S 16:59 0:00 nginx: master process ./sbin/nginx 105 | mazhen 4455 0.0 0.0 10216 1980 ? S 16:59 0:00 nginx: worker process 106 | mazhen 4456 0.0 0.0 10216 1980 ? S 16:59 0:00 nginx: worker process 107 | mazhen 4457 0.0 0.0 10216 1980 ? S 16:59 0:00 nginx: worker process 108 | mazhen 4458 0.0 0.0 10216 1980 ? S 16:59 0:00 nginx: worker process 109 | mazhen 4461 0.0 0.0 12184 2436 pts/0 S+ 16:59 0:00 grep --color=auto nginx 110 | ``` 111 | 112 | 向老的 Nginx master 发送信号,让它优雅关闭 worker 进程。 113 | 114 | ```shell 115 | kill -WINCH 4376 116 | ``` 117 | 118 | 这时候再查看 Nginx 进程: 119 | 120 | ```shell 121 | $ ps aux | grep nginx 122 | mazhen 4376 0.0 0.0 9896 2536 ? Ss 16:47 0:00 nginx: master process ./sbin/nginx 123 | mazhen 4454 0.0 0.0 9768 6024 ? S 16:59 0:00 nginx: master process ./sbin/nginx 124 | mazhen 4455 0.0 0.0 10216 1980 ? S 16:59 0:00 nginx: worker process 125 | mazhen 4456 0.0 0.0 10216 1980 ? S 16:59 0:00 nginx: worker process 126 | mazhen 4457 0.0 0.0 10216 1980 ? S 16:59 0:00 nginx: worker process 127 | mazhen 4458 0.0 0.0 10216 1980 ? S 16:59 0:00 nginx: worker process 128 | mazhen 4475 0.0 0.0 12184 2292 pts/0 S+ 17:07 0:00 grep --color=auto nginx 129 | ``` 130 | 131 | 老的 worker 已经优雅退出,所有的请求已经切换到了新升级的 Nginx 中。 132 | 133 | 老的 master 仍然在运行,如果需要,我们可以向它发送 reload 信号,回退到老版本的 Nginx。 134 | 135 | ## 日志切割 136 | 137 | 首先使用 `mv` 命令,备份旧的日志: 138 | 139 | ```shell 140 | mv access.log bak.log 141 | ``` 142 | 143 | Linux 文件系统中,改名并不会影响已经打开文件的写入操作,因为内核 inode 不变,这样操作不会出现丢日志的情况。 144 | 145 | 然后给运行中的 Nginx 发送 reopen 信号: 146 | 147 | ```shell 148 | ./nginx -s reopen 149 | ``` 150 | 151 | Nginx 会重新生成 `access.log` 日志文件。 152 | 153 | 一般会写一个 `bash` 脚本,通过配置 crontab,每日进行日志切割。 154 | -------------------------------------------------------------------------------- /docs/numbers_everyone_should_know.md: -------------------------------------------------------------------------------- 1 | # Latency Numbers Every Programmer Should Know 2 | 3 | 对于[冯·诺伊曼体系结构](https://en.wikipedia.org/wiki/Von_Neumann_architecture)的计算机,CPU 要数据才能正常工作。如果没有可处理的数据,那么CPU的运算速度再快也没有用,它只能等待。 4 | 5 | 在计算机和芯片发展的历史中,CPU 速度不断提高,但主内存的访问速度改进相对较慢,导致 CPU经常处于等待数据的状态,无法充分发挥其处理能力。为了解决这个问题,出现了 CPU 缓存。 6 | 7 | 寄存器和 CPU 缓存共同构成 CPU 内部的高速缓冲存储体系。 8 | 9 | 寄存器直接位于 CPU 内部,是距离 CPU 最近的存储单元。CPU 缓存分为多级,距离 CPU 最近的一级缓存(L1缓存)接在寄存器之后。 10 | 11 | 在内存层次结构中,从和 CPU 的接近程度来看是: 12 | 13 | > 寄存器 > L1缓存 > L2缓存 > L3缓存 > 主内存 14 | 15 | ![Cache Hierarchy](https://upload.wikimedia.org/wikipedia/commons/0/00/Cache_Hierarchy_Updated.png) 16 | 17 | 寄存器访问速度最快,但容量最小。随着级别升高,访问速度下降但容量增大,以平衡访问速度和存储空间。 18 | 19 | [Brown University](https://www.brown.edu/) 的 [Fundamentals of Computer Systems](https://cs.brown.edu/courses/csci0300/2023/index.html)课程有专门介绍计算机的[存储层次结构](https://cs.brown.edu/courses/csci0300/2023/notes/l12.html):以**大小和速度**为标准,将不同类型的存储设备从**最快但容量最小**到**最慢但容量最大**进行排列。 20 | 21 | ![storage hierarchy](https://raw.githubusercontent.com/mz1999/material/master/images/202310111641125.png) 22 | 23 | 存储层次结构这样设计是基于不同存储设备的**成本和性能特点**。**内存成本高但访问速度快**,而**硬盘成本低但访问速度慢**。所以采用这种层次结构可以在**平衡成本和性能**的前提下更好地利用各种存储设备。 24 | 25 | 在2010年,Google 的 [Jeff Dean](https://en.wikipedia.org/wiki/Jeff_Dean_(computer_scientist)) 在斯坦福大学发表了一次精彩的演讲 [Designs, Lessons and Advice from Building Large Distributed Systems](https://www.cs.cornell.edu/projects/ladis2009/talks/dean-keynote-ladis2009.pdf),在演讲中总结了计算机工程师应该了解的一些重要数字: 26 | 27 | ![Numbers Everyone Should Know](https://raw.githubusercontent.com/mz1999/material/master/images/202310111506531.png) 28 | 29 | 后来有人做了一个非常好的[交互式web UI](https://colin-scott.github.io/personal_website/research/interactive_latency.html),展示了这些数字随着时间的变化。 30 | ![#### Latency Numbers Every Programmer Should Know](https://raw.githubusercontent.com/mz1999/material/master/images/202310111518524.png) 31 | 32 | 这些数字对人的感觉不那么直观,它们之间的差异可以相差数个数量级,让我们很难真正理解这些差距有多大。于是 [Brendan Gregg](https://www.brendangregg.com/index.html)在他的书 [Systems Performance](https://www.brendangregg.com/systems-performance-2nd-edition-book.html)中,以 3.3 GHz 的 CPU 寄存器访问开始,放大成日常生活的时间单位,直观感受各系统组件访问时间的数量级差异: 33 | 34 | ![Example time scale of system latencies](https://raw.githubusercontent.com/mz1999/material/master/images/202310111532122.png) 35 | 如果一个 CPU cycle 是1秒,那么内存访问的延迟是6分钟,从旧金山到纽约(相当于深圳到乌鲁木齐)的网络延迟就是4年! 36 | -------------------------------------------------------------------------------- /docs/pid.md: -------------------------------------------------------------------------------- 1 | 2 | # 进程ID及进程间的关系 3 | 4 | ## 进程ID 5 | 6 | 进程相关的 `ID` 有多种,除了进程标识 **PID** 外,还包括:线程组标识 **TGID**,进程组标识 **PGID**,回话标识 **SID**。**TGID/PGID/SID** 分别是相关线程组长/进程组长/回话 leader 进程的 **PID**。 7 | 8 | 下面分别介绍这几种ID。 9 | 10 | ### PID 11 | 12 | * 进程总是会被分配一个唯一标识它们的进程ID号,简称 **PID**。 13 | 14 | * 用 `fork` 或 `clone` 产生的每个进程都由内核自动地分配了一个唯一的 **PID** 。 15 | 16 | * **PID** 保存在 [task_struct->pid](https://github.com/torvalds/linux/blob/8bb7eca972ad531c9b149c0a51ab43a417385813/include/linux/sched.h?_pjax=%23js-repo-pjax-container%2C%20div%5Bitemtype%3D%22http%3A%2F%2Fschema.org%2FSoftwareSourceCode%22%5D%20main%2C%20%5Bdata-pjax-container%5D#L943)中。 17 | 18 | ### TGID 19 | 20 | * 进程以 `CLONE_THREAD` 标志调用 `clone` 方法,创建与该进程共享资源的线程。线程有独立的[task_struct](https://github.com/torvalds/linux/blob/8bb7eca972ad531c9b149c0a51ab43a417385813/include/linux/sched.h?_pjax=%23js-repo-pjax-container%2C%20div%5Bitemtype%3D%22http%3A%2F%2Fschema.org%2FSoftwareSourceCode%22%5D%20main%2C%20%5Bdata-pjax-container%5D#L723),但它 `task_struct`内的 [files_struct](https://github.com/torvalds/linux/blob/8bb7eca972ad531c9b149c0a51ab43a417385813/include/linux/sched.h?_pjax=%23js-repo-pjax-container%2C%20div%5Bitemtype%3D%22http%3A%2F%2Fschema.org%2FSoftwareSourceCode%22%5D%20main%2C%20%5Bdata-pjax-container%5D#L1070)、[fs_struct](https://github.com/torvalds/linux/blob/8bb7eca972ad531c9b149c0a51ab43a417385813/include/linux/sched.h?_pjax=%23js-repo-pjax-container%2C%20div%5Bitemtype%3D%22http%3A%2F%2Fschema.org%2FSoftwareSourceCode%22%5D%20main%2C%20%5Bdata-pjax-container%5D#L1067) 、[sighand_struct](https://github.com/torvalds/linux/blob/8bb7eca972ad531c9b149c0a51ab43a417385813/include/linux/sched.h?_pjax=%23js-repo-pjax-container%2C%20div%5Bitemtype%3D%22http%3A%2F%2Fschema.org%2FSoftwareSourceCode%22%5D%20main%2C%20%5Bdata-pjax-container%5D#L1081)、[signal_struct](https://github.com/torvalds/linux/blob/8bb7eca972ad531c9b149c0a51ab43a417385813/include/linux/sched.h?_pjax=%23js-repo-pjax-container%2C%20div%5Bitemtype%3D%22http%3A%2F%2Fschema.org%2FSoftwareSourceCode%22%5D%20main%2C%20%5Bdata-pjax-container%5D#L1080)和[mm_struct](https://github.com/torvalds/linux/blob/8bb7eca972ad531c9b149c0a51ab43a417385813/include/linux/sched.h?_pjax=%23js-repo-pjax-container%2C%20div%5Bitemtype%3D%22http%3A%2F%2Fschema.org%2FSoftwareSourceCode%22%5D%20main%2C%20%5Bdata-pjax-container%5D#L857) 等数据结构仅仅是对进程相应数据结构的引用。 21 | 22 | * 由进程创建的所有线程都有相同的线程组ID(**TGID**)。线程有自己的 **PID**,它的**TGID** 就是进程的主线程的 **PID**。如果进程没有使用线程,则其 **PID** 和 **TGID** 相同。 23 | 24 | * 在内核中进程和线程都用 `task_struct`表示,而有了 **TGID**,我们就可以知道 `task_struct` 代表的是一个进程还是一个线程。 25 | 26 | * **TGID** 保存在 [task_struct->tgid](https://github.com/torvalds/linux/blob/8bb7eca972ad531c9b149c0a51ab43a417385813/include/linux/sched.h?_pjax=%23js-repo-pjax-container%2C%20div%5Bitemtype%3D%22http%3A%2F%2Fschema.org%2FSoftwareSourceCode%22%5D%20main%2C%20%5Bdata-pjax-container%5D#L944) 中。 27 | 28 | * 当 `task_struct` 代表一个线程时,[task_struct->group_leader](https://github.com/torvalds/linux/blob/8bb7eca972ad531c9b149c0a51ab43a417385813/include/linux/sched.h?_pjax=%23js-repo-pjax-container%2C%20div%5Bitemtype%3D%22http%3A%2F%2Fschema.org%2FSoftwareSourceCode%22%5D%20main%2C%20%5Bdata-pjax-container%5D#L967) 指向主线程的 `task_struct`。 29 | 30 | ### PGID 31 | 32 | * 如果 `shell` 具有作业管理能力,则它所创建的相关进程构成一个进程组,同一进程组的进程都有相同的 **PGID**。例如,用管道连接的进程包含在同一个进程组中。 33 | 34 | * 进程组简化了向组的所有成员发送信号的操作。进程组提供了一种机制,让信号可以发送给组内的所有进程,这使得作业控制变得简单。 35 | 36 | * 当 `task_struct` 代表一个进程,且该进程属于某一个进程组,则 [task_struct->group_leader](https://github.com/torvalds/linux/blob/8bb7eca972ad531c9b149c0a51ab43a417385813/include/linux/sched.h?_pjax=%23js-repo-pjax-container%2C%20div%5Bitemtype%3D%22http%3A%2F%2Fschema.org%2FSoftwareSourceCode%22%5D%20main%2C%20%5Bdata-pjax-container%5D#L967) 指向组长进程的 `task_struct`。 37 | 38 | * **PGID** 保存在 [task_struct->signal->pids[PIDTYPE_PGID].pid](https://github.com/torvalds/linux/blob/8bb7eca972ad531c9b149c0a51ab43a417385813/include/linux/sched/signal.h?_pjax=%23js-repo-pjax-container%2C%20div%5Bitemtype%3D%22http%3A%2F%2Fschema.org%2FSoftwareSourceCode%22%5D%20main%2C%20%5Bdata-pjax-container%5D#L153)中。 `pids[]` 的数组下标是枚举类型,在 [include/linux/pid.h](https://github.com/torvalds/linux/blob/v5.15/include/linux/pid.h) 中定义了 `PID` 的类型: 39 | 40 | ```c 41 | enum pid_type 42 | { 43 | PIDTYPE_PID, 44 | PIDTYPE_TGID, 45 | PIDTYPE_PGID, 46 | PIDTYPE_SID, 47 | PIDTYPE_MAX, 48 | }; 49 | ``` 50 | 51 | * `task_struce->signal` 是 [signal_struct](https://github.com/torvalds/linux/blob/8bb7eca972ad531c9b149c0a51ab43a417385813/include/linux/sched/signal.h?_pjax=%23js-repo-pjax-container%2C%20div%5Bitemtype%3D%22http%3A%2F%2Fschema.org%2FSoftwareSourceCode%22%5D%20main%2C%20%5Bdata-pjax-container%5D#L82) 类型,维护了进程收到的信号,`task_struce->signal` 被该进程的所有线程共享。从 **PGID** 保存在 `task_struct->signal->pids[PIDTYPE_PGID]`中可以看出进程组和信号处理相关。 52 | 53 | 54 | ### SID 55 | 56 | * 用户一次登录所涉及所有活动称为一个会话(**session**),其间产生的所有进程都有相同的会话ID(**SID**),等于会话 leader 进程的 **PID**。 57 | 58 | * **SID** 保存在 [task_struct->signal->pids[PIDTYPE_SID].pid](https://github.com/torvalds/linux/blob/8bb7eca972ad531c9b149c0a51ab43a417385813/include/linux/sched/signal.h?_pjax=%23js-repo-pjax-container%2C%20div%5Bitemtype%3D%22http%3A%2F%2Fschema.org%2FSoftwareSourceCode%22%5D%20main%2C%20%5Bdata-pjax-container%5D#L153)中。 59 | 60 | ### **PID/TGID/PGID/SID**总结 61 | 62 | 用一幅图来总结 **PID/TGID/PGID/SID** : 63 | 64 | ![进程ID](./media/pid/pid.png) 65 | 66 | ## 进程间关系 67 | 68 | 内核中所有进程的 `task_struct` 会形成多种组织关系。根据进程的创建过程会有亲属关系,进程间的父子关系组织成一个进程树;根据用户登录活动会有会话和进程组关系。 69 | 70 | ### 亲属关系 71 | 72 | 进程通过 `fork()` 创建出一个子进程,就形成来父子关系,如果创建出多个子进程,那么这些子进程间属于兄弟关系。可以用 `pstree` 命令查看当前系统的进程树。 73 | 74 | ``` 75 | $ pstree -p 76 | systemd(1)─┬─ModemManager(759)─┬─{ModemManager}(802) 77 | │ └─{ModemManager}(806) 78 | ├─NetworkManager(685)─┬─{NetworkManager}(743) 79 | │ └─{NetworkManager}(750) 80 | ├─acpid(675) 81 | ├─agetty(814) 82 | ├─avahi-daemon(679)───avahi-daemon(712) 83 | ├─bluetoothd(680) 84 | ├─canonical-livep(754)─┬─{canonical-livep}(1224) 85 | │ ├─{canonical-livep}(1225) 86 | │ ├─{canonical-livep}(1226) 87 | ... 88 | ``` 89 | 90 | 进程描述符 `task_struct` 的 `parent` 指向父进程,`children`指向子进程链表的头部,`sibling` 把当前进程插入到兄弟链表中。 91 | 92 | 通常情况下,`real_parent` 和 `parent` 是一样的。如果在 `bash` 上使用 `GDB` 来 debug 一个进程,这时候进程的 `parent` 是 `GDB` ,进程的 `real_parent` 是 `bash`。 93 | 94 | ![pstree](./media/pid/pstree.png) 95 | 96 | 当一个进程创建了子进程后,它应该通过系统调用 `wait()` 或者 `waitpid()` 等待子进程结束,回收子进程的资源。而子进程在结束时,会向它的父进程发送 `SIGCHLD` 信号。因此父进程还可以注册 `SIGCHLD` 信号的处理函数,异步回收资源。 97 | 98 | 如果父进程提前结束,那么子进程将把1号进程 `init` 作为父进程。总之,进程都有父进程,负责进程结束后的资源回收。在子进程退出且父进程完成回收前,子进程变成僵尸进程。僵尸进程持续的时间通常比较短,在父进程回收它的资源后就会消亡。如果父进程没有处理子进程的终止,那么子进程就会一直处于僵尸状态。 99 | 100 | ### 会话、进程组关系 101 | 102 | Linux 系统中可以有多个会话(**session**),每个会话可以包含多个进程组,每个进程组可以包含多个进程。 103 | 104 | 会话是用户登录系统到退出的所有活动,从登录到结束前创建的所有进程都属于这次会话。登录后第一个被创建的进程(通常是 `shell`),被称为 **会话 leader**。 105 | 106 | 进程组用于作业控制。一个终端上可以启动多个作业,也就是进程组,并能控制哪个作业在前台,前台作业可以访问终端,哪些作业运行在后台,不能读写终端。 107 | 108 | 我们来看一个会话和进程组的例子。 109 | 110 | ``` 111 | $ cat | head 112 | hello 113 | hello 114 | ^Z 115 | [1]+ 已停止 cat | head 116 | $ ps j | more 117 | PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 118 | 1522 1532 1532 1532 pts/0 1762 Ss 1000 0:00 -bash 119 | 1532 1760 1760 1532 pts/0 1762 T 1000 0:00 cat 120 | 1532 1761 1760 1532 pts/0 1762 T 1000 0:00 head 121 | 1532 1762 1762 1532 pts/0 1762 R+ 1000 0:00 ps j 122 | 1532 1763 1762 1532 pts/0 1762 S+ 1000 0:00 more 123 | ``` 124 | 125 | 上面的命令通过 `cat | head` 创建了第一个进程组,包含 `cat` 和 `head `两个进程。这时这个作业是前台任务,可以控制终端。当我们按下 **Ctrl + z**,会发送信号 **SIGTSTP** 给前台进程组的所有进程,该信号的缺省行为是暂停作业执行。暂停的作业会让出终端,并且进程不会再被调度,直到它们收到 **SIGCONT** 信号恢复执行。 126 | 127 | 然后我们通过 `ps j | more` 创建了另一个进程组,包含 `ps` 和 `more` 两个进程。`ps` 的参数 `j` 表示用任务格式显示进程。输出中的 **STAT** 列是进程的状态码,前面的大写字母表示进程状态,我们可以从 `ps` 的 [man page](https://man7.org/linux/man-pages/man1/ps.1.html) 查看其含义: 128 | 129 | ``` 130 | D uninterruptible sleep (usually IO) 131 | I Idle kernel thread 132 | R running or runnable (on run queue) 133 | S interruptible sleep (waiting for an event to complete) 134 | T stopped by job control signal 135 | t stopped by debugger during the tracing 136 | W paging (not valid since the 2.6.xx kernel) 137 | X dead (should never be seen) 138 | Z defunct ("zombie") process, terminated but not reaped by its parent 139 | ``` 140 | 141 | 某些进程除了大写字母代表的进程状态,还跟着一个附加符号: 142 | 143 | * **s** :进程是会话 leader 进程 144 | * **+** :进程位于前台进程组中 145 | 146 | 从输出可以看出,`bash` 是这个会话的 leader 进程,它的 **PID**、**PGID** 和 **SID** 相同,都是`1532` 。这个会话其他所有进程的 **SID** 也都是 `1532`。 147 | 148 | `cat | head` 进程组的 **PGID** 是 `1760`,`ps j | more` 进程组的 **PGID** 是 `1762`。用管道连接的进程包含在同一个进程组中,每个进程组内第一个进程成为 Group Leader,并以 Group Leader 的 **PID** 作为组内进程的 **PGID**。 149 | 150 | 会话有一个前台进程组,还可以有一个或多个后台进程组,只有前台作业可以从终端读写数据。示例的进程组关系如图: 151 | 152 | ![session](./media/pid/session.png) 153 | 154 | 注意到上图中显示,终端设备可以向进程组发送信号。我们可以在终端输入特殊字符向前台进程发送信号: 155 | 156 | * **Ctrl + c** 发送 **SIGINT** 信号,默认行为是终止进程; 157 | * **Ctrl + \\** 发送 **SIGQUIT** 信号,默认行为是终止进程,并进行 `core dump`; 158 | * **Ctrl + z** 发送 **SIGTSTP** 信号,暂停进程。 159 | 160 | 只有前台进程可以从终端接收输入,也只有前台进程才被允许向终端输出。如果一个后台作业中的进程试图进行终端读写操作,终端会向整个作业发送 **SIGTTOU** 或 **SIGTTIN** 信号,默认的行为是暂停进程。 161 | 162 | 当终端关闭时,会向整个会话发送 **SIGHUP** 信号,通常情况下,这个会话的所有进程都会被终止。如果想让运用在后台的进程不随着 **session** 的结束而退出,可以使用 **nohup** 命令忽略 **SIGHUP** 信号: 163 | 164 | ``` 165 | $ nohup command >cmd.log 2>&1 & 166 | ``` 167 | 168 | 即使 `shell` 结束,运行于后台的进程也能无视 **SIGHUP** 信号继续执行。另外一个方法是可以让进程运行在 `screen` 或 `tmux` 这种终端多路复用器([terminal multiplexer](https://en.wikipedia.org/wiki/Terminal_multiplexer))中。 169 | 170 | -------------------------------------------------------------------------------- /docs/powershell_emacs.md: -------------------------------------------------------------------------------- 1 | # 为 PowerShell 配置 Emacs 模式 2 | 3 | 对于长期在 macOS 或 Linux 终端环境下工作的人来说,Windows 的终端体验可能会显得有些格格不入。尤其是 PowerShell,它的默认交互方式与熟悉的 bash 或 zsh 存在显著差异。本文将从配置 Emacs 模式入手,深入了解 Windows 终端的生态,并逐步打造一个更符合你习惯的 PowerShell 环境。 4 | 5 | ## Windows 终端:不止 cmd.exe 和 PowerShell 6 | 7 | 在 Windows 中,我们经常会接触到以下几个概念: 8 | 9 | - **命令提示符 (cmd.exe):** 这是 Windows 最早的命令行解释器,可以追溯到 DOS 时代。它执行简单的命令,但功能相对有限,交互体验也较为落后。我们可以将其视为 Windows 系统的“元老”,用于执行一些基本的命令操作。 10 | - **PowerShell:** 这是一个更加现代和强大的命令行外壳,基于 .NET 框架构建,以对象而非文本处理数据,更适合自动化、脚本编写和系统管理。它可以被视为 Windows 系统的“新贵”,旨在替代 cmd.exe,成为系统管理的主力工具。 11 | - **Windows Terminal (Windows 终端):** 这是一个独立的应用程序,用于承载各种命令行 shell(如 cmd.exe, PowerShell, WSL 等)。它提供了标签页、分屏、自定义主题等高级特性,使得在不同外壳之间切换更加便捷。你可以将它视为一个更强大、更现代的“终端模拟器”,用于统一管理各种命令行环境。 12 | 13 | **它们之间的关系:** **Windows Terminal** 是一个终端应用程序,它像一个容器,可以运行 **cmd.exe** 和 **PowerShell** 等命令行 shell。 **cmd.exe** 和 **PowerShell** 是不同的命令行解释器,负责解释用户输入的命令并执行操作。也就是说,**cmd.exe** 和 **PowerShell** 是 **Windows Terminal** 中的两种“引擎”。 14 | 15 | ## PSReadLine:PowerShell 的交互核心 16 | 17 | **PSReadLine** 是 PowerShell 的一个核心模块,负责处理终端中的文本输入、历史记录、命令补全、语法高亮等关键功能。它直接影响着你在 PowerShell 中的操作效率和流畅度。如果没有 **PSReadLine**,PowerShell 的交互体验会大打折扣。 18 | 19 | ### 版本与功能 20 | 21 | Windows 11 预装的 Windows PowerShell 5.1 虽然自带 `PSReadLine` 模块,但其功能较为基础。为了获得更流畅的体验和更强大的功能,例如预测性 IntelliSense 和更强大的多行编辑,强烈建议升级到 **PowerShell 7.2 或更高版本**。 22 | 23 | ### 升级 PowerShell (PowerShell Core) 24 | 25 | 1. **下载:** 从 [Microsoft 官方 GitHub 仓库](https://github.com/PowerShell/PowerShell/releases) 下载最新版本的 PowerShell 安装包,并按照安装向导完成安装。 26 | 2. **配置终端:** 在终端应用程序 Windows Terminal 中,增加一个新的配置文件,将命令行选项设置为你最新安装的 PowerShell 可执行文件路径,例如 `"C:\Program Files\PowerShell\7\pwsh.exe"`。 27 | 3. **验证版本:** 在 PowerShell 中运行 `Get-Module PSReadLine -ListAvailable`,检查 PSReadLine 版本。如果没有任何输出或版本低于 2.0,则需要升级,详见下文的 PSReadLine 的升级方法。 28 | 29 | ### PSReadLine 的升级 30 | 31 | 如果你的 PSReadLine 版本过旧,请运行以下命令更新: 32 | 33 | ```powershell 34 | Install-Module -Name PSReadLine -Force 35 | ``` 36 | 37 | ## Emacs 模式:让 PowerShell 更顺手 38 | 39 | 在深入配置 Emacs 模式之前,我们先来了解一下 PowerShell 的 profile 文件。 40 | 41 | ### 什么是 PowerShell Profile 42 | 43 | PowerShell profile 相当于 PowerShell 的启动脚本。每次启动 PowerShell 时,它都会自动执行 profile 文件中的命令。这使得你可以自定义 PowerShell 环境,例如设置别名、函数、环境变量,以及在本例中,配置 PSReadLine 的编辑模式等。可以把它看作是 PowerShell 的“初始化文件”。 44 | 45 | PowerShell 支持多个 profile 文件,它们根据不同的作用域加载。最常用的 profile 文件路径通常是 `C:\Users\<用户名>\Documents\PowerShell\Microsoft.PowerShell_profile.ps1`,它针对当前用户的所有 PowerShell 会话加载。 46 | 47 | 理解了 profile 文件的作用后,我们就可以开始配置 Emacs 模式了。 48 | 49 | ### 如何在 PowerShell profile 中设置 Emacs 模式 50 | 51 | 1. **获取 profile 文件路径**  52 | 53 | 在 PowerShell 中运行 `$PROFILE`,获取 profile 文件路径。通常这个路径类似于`C:\Users\<用户名>\Documents\PowerShell\Microsoft.PowerShell_profile.ps1`。 54 | 55 | 2. **创建配置文件** 56 | 57 | 在默认情况下,PowerShell 的 `$PROFILE` 文件可能并不存在。可以使用以下命令检查文件是否存在: 58 | 59 | ```powershell 60 | Test-Path $PROFILE 61 | ``` 62 | 63 | 如果返回 `False`,说明该文件还没有创建。可以使用以下命令来创建该文件: 64 | 65 | ```powershell 66 | New-Item -Path $PROFILE -ItemType File -Force 67 | ``` 68 | 69 | 3. **编辑 profile 文件**  70 | 71 | 一旦文件创建完成,您可以使用任意文本编辑器打开并编辑 `$PROFILE` 文件。例如使用 `notepad` 打开配置文件:`notepad $PROFILE` 72 | 73 | 4. **添加配置** 74 | 75 | 在配置文件中,添加以下行来启用 Emacs 模式: 76 | 77 | ```powershell 78 | set-PSReadLineOption -EditMode Emacs 79 | ``` 80 | 81 | 这行代码会在每次启动 PowerShell 时自动将编辑模式设置为 Emacs。 82 | 83 | 5. **保存并重启 PowerShell** 84 | 85 | 编辑并保存 `$PROFILE` 文件后,关闭并重新启动 PowerShell。重新打开后,PowerShell 将自动加载您的配置文件,并启用 Emacs 编辑模式。 86 | 87 | 另外,`$PROFILE` 配置文件可以用于保存各种个性化设置,包括自定义函数、别名、环境变量等。在 PowerShell 7+ 中,PowerShell 使用的 `$PROFILE` 文件路径会有细微变化,确保在编辑前检查 `$PROFILE` 的具体路径。 88 | 89 | ### 其他 PSReadLine 配置 90 | 91 | 在配置了 Emacs 模式后,PSReadLine 还有许多其他有用的配置选项,可以进一步提升你的使用体验。以下是一些常用的配置示例: 92 | 93 | - **自定义历史记录保存路径:** 94 | 95 | ```powershell 96 | Set-PSReadLineOption -HistorySavePath "path/to/your/history.txt" 97 | ``` 98 | 99 | 通过此设置,你可以将 PowerShell 的命令历史记录保存到指定的文件中。 100 | 101 | - **设置历史记录的最大条数:** 102 | 103 | ```POWERSHELL 104 | Set-PSReadLineOption -MaximumHistoryCount 1000 105 | ``` 106 | 107 | 此设置可以限制 PowerShell 保存的历史记录条数,防止历史记录文件过大。 108 | 109 | - **设置预测命令的来源为历史记录:** 110 | 111 | ```powershell 112 | Set-PSReadLineOption -PredictionSource History 113 | ``` 114 | 115 | 设置命令预测的来源,可以使用历史记录或插件。 116 | 117 | - **查看和自定义快捷键:** 118 | 119 | ```powershell 120 | Get-PSReadLineKeyHandler 121 | ``` 122 | 123 | 使用此命令可以查看当前 PSReadLine 定义的快捷键。你还可以自定义快捷键,例如: 124 | 125 | ```powershell 126 | Set-PSReadLineKeyHandler -Key Ctrl+l -Function ClearScreen 127 | ``` 128 | 129 | 这个命令将 Ctrl + L 键绑定到 `ClearScreen` 函数,实现清屏的功能。 130 | 131 | ### 常用快捷键 132 | 133 | 掌握一些常用的快捷键可以大大提高在 PowerShell 中的工作效率。以下是一些常用的快捷键: 134 | 135 | - **PowerShell 快捷键:** 136 | - `Ctrl + C`: 中断当前正在执行的命令。 137 | - `Tab`: 自动补全命令、路径或变量名。 138 | - `Ctrl + R`: 搜索历史命令。 139 | - `↑` 或 `↓`: 浏览历史命令。 140 | - `Ctrl + Shift + T`: 在 Windows Terminal 中打开新的 tab 141 | 142 | - **Emacs 模式快捷键:** 143 | - `Ctrl + A`: 将光标移动到行首。 144 | - `Ctrl + E`: 将光标移动到行尾。 145 | - `Ctrl + B`: 光标左移一个字符。 146 | - `Ctrl + F`: 光标右移一个字符。 147 | - `Alt + B`: 将光标移动到上一个单词的开头。 148 | - `Alt + F`: 将光标移动到下一个单词的开头。 149 | - `Alt + D`: 删除光标后的单词。 150 | - `Ctrl + K`: 删除光标到行尾的所有内容。 151 | - `Ctrl + U`: 删除光标到行首的所有内容。 152 | 153 | 通过这个设置和快捷键的掌握,您可以在 PowerShell 中体验更接近 Emacs 的编辑体验,并提高命令行的使用效率。 -------------------------------------------------------------------------------- /docs/programmer-workout.md: -------------------------------------------------------------------------------- 1 | # 给程序员的健身锻炼指南 2 | 3 | 程序员的工作需要长期坐在电脑前,可能还会经常熬夜,作息时间不规律,所以码农是身体素质比较差的一群人。因此程序员健身锻炼的首要目标,不是为了有好看的身材,而是让你精力充沛,面对高强度工作游刃有余,同时还有精力去享受生活,这才是最关键的。 4 | 5 | ## 心肺系统的重要性 6 | 7 | 这一切的基础是你必须要有一个好的**心血管系统**。心脏和全身的血管组成了**心血管系统**。通过持续不间断地跳动,心脏把富含氧气的血液不断输送到人体各个部位,并且将各个部位产生的废物和有害物质带到相应的排泄器官排出体外,这才维持起人体的各项机能。 8 | 9 | **心血管系统**就好比汽车的发动机,没了发动机,其他零件再好也没有用,这个发动机是最重要的。 10 | 11 | 如果通过运动,让心肌得到有效的锻炼,那受益的是整个心血管系统。至于体重的減轻、体型的变化,这些都是随时而来的副产品。 12 | 13 | ## 自测心肺功能水平 14 | 15 | 那么如何锻炼才能有效的增强心血管系统呢? 16 | 17 | 首先我们要知道自己当前心血管系统的水平如何。一个专业的指标叫做**最大摄氧量**(VO2 max)。这个指标可以看出你现在心血管和心肺功能的水平。简单来说,**最大摄氧量**就是你在运动中能获取的最大氧气量。这个指标越高,说明你的心血管系统、心肺功能越好。 18 | 19 | 对一个正常成年人,男性这个指标达到`40`,女性达到`36`才算是及格。普通人`54`以上可以算是优秀,而职业长跑运动员,最大摄氧量指标能达到`88`以上。 20 | 21 | 如何测量自己的最大摄氧量呢?一般专业的心率表都会有这个指标。例如`Apple watch`可以通过你的体能训练,估算出最大摄氧量。 22 | 23 | ![vo2max](./media/workout/vo2max.jpg) 24 | 25 | `iOS`系统自带的“健康”App里能查看到这个指标。 26 | 27 | ![health](./media/workout/health.jpeg) 28 | 29 | ## 合适强度的运动改善心肺功能 30 | 31 | 了解了自己当前的心肺功能水平后,如何提高改善你的心肺功能呢?需要选择合适的运动强度,不能一上来就强度过大。如果平时缺乏锻炼,心肺功能不达标,高强度的训练是比较危险的。 32 | 33 | 建议使用“卡氏公式”计算一下你合适的运动心率区间。这个心率区间是和你的年龄,以及早上起来的静态心率状况有关。 34 | 35 | **适合心肺功能训练的卡氏公式** 36 | 37 | ``` 38 | 心肺训练心率 = 39 | (220 - 年龄 − 静态心率)×(55% ~ 65%) 40 | + 静态心率 41 | ``` 42 | 43 | 如果是刚开始健身,比较推荐你在跑步机上进行走路。因为跑步机的速度是恒定的,把跑步机调成上坡的时候,你会发现很容易达到你想要达到的心率,只要在这个心率范围之内,就是你最合适的运动区间。随着你的心肺能力不断提高,你会逐渐提高坡度和速度。 44 | 45 | ## 如何有效减脂 46 | 47 | 健身锻炼除了能充沛精力,另外一个大家关心的问题是,如何通过运动有效的减脂呢? 48 | 49 | 实际上,**饮食才是最有效的控制体重的方法**。对于想减脂的人来说,最好的办法就是**控制好你糖和脂肪的摄入**,然后多吃一些蛋白质类食物,这样你会有足够的饱腹感。 50 | 51 | 此外,可以做一些低强度运动,身体会消耗更多的脂肪。 52 | 53 | 为什么低强度的运动能消耗脂肪呢?因为**脂肪的消耗是需要氧气参与,运动过程中必须有充足的氧气才能消耗脂肪**,因此这个运动强度应该比心肺训练的强度更低。 54 | 55 | 减脂的运动强度就是卡氏公式的**35%到55%**,在这个强度运动是消耗脂肪最多的。 56 | 57 | **适合减脂训练的卡氏公式** 58 | 59 | ``` 60 | 减脂训练心率 = 61 | (220 - 年龄 − 静态心率)×(35% ~ 55%) 62 | + 静态心率 63 | ``` 64 | 65 | 最后,男性的健康体脂率应该是`15%`到`20%`,女性的健康体脂率是`20`%到`25%`。推荐买一个体脂秤测试跟踪自己的体脂率变化。 -------------------------------------------------------------------------------- /docs/psi.md: -------------------------------------------------------------------------------- 1 | # 使用PSI(Pressure Stall Information)监控服务器资源 2 | 3 | 我们通常会使用 **load average** 了解服务器的健康状况,检查服务器的负载是否正常。但 `load average` 有几个缺点: 4 | 5 | - `load average` 的计算包含了 **TASK_RUNNING** 和 **TASK_UNINTERRUPTIBLE** 两种状态的进程。`TASK_RUNNING` 是进程处于运行、或等待分配 CPU 的准备运行状态。`TASK_UNINTERRUPTIBLE` 是进程处于不可中断的等待,一般是等待磁盘的输入输出。因此 `load average` 值飙高,可能是因为 CPU 资源不够,让很多处于 `TASK_RUNNING` 状态的进程等待 CPU,也可能是由于磁盘 I/O 资源紧张,造成很多进程因为等待 IO 而处于 `TASK_UNINTERRUPTIBLE` 状态。你可以通过 `load average` 发现系统很忙,但没法区分是因为争夺 CPU 还是 IO 引起的。 6 | - `load average` 最短的时间窗口为1分钟,没法观察更短窗口的负载平均值,例如想了解最近10秒的`load average`。 7 | - `load average` 报告的是活跃进程数的原始数据,你还需要知道可用的 CPU 核数,这样 `load average` 的值才有意义。 8 | 9 | 所以,当用户遇到服务器 `load average` 飙高的时候,还需要继续查看 CPU、I/O 和内存等资源的统计数据,才能进一步分析问题。 10 | 11 | 于是,Facebook的工程师 Johannes Weiner 发明了一个新的指标 [PSI(Pressure Stall Information)](https://www.kernel.org/doc/html/latest/accounting/psi.html),并向内核提交了这个[patch](https://lwn.net/Articles/763629/)。 12 | 13 | 14 | ## PSI 概览 15 | 16 | 当 CPU、内存或 IO 设备争夺激烈的时候,系统会出现负载的延迟峰值、吞吐量下降,并可能触发内核的 `OOM Killer`。**PSI(Pressure Stall Information)** 字面意思就是由于资源(CPU、内存和 IO)压力造成的任务执行停顿。**PSI** 量化了由于硬件资源紧张造成的任务执行中断,统计了系统中任务等待硬件资源的时间。我们可以用 **PSI** 作为指标,来衡量硬件资源的压力情况。停顿的时间越长,说明资源面临的压力越大。 17 | 18 | 如果持续监控 `PSI` 指标并绘制变化曲线图,可以发现吞吐量下降与资源短缺的关系,让用户在资源变得紧张前,采取更主动的措施,例如将任务迁移到其他服务器,杀死低优先级的任务等。 19 | 20 | `PSI` 已经包含在 4.20及以上版本的 Linux 内核中。 21 | 22 | ## PSI 接口文件 23 | 24 | CPU、内存和 IO 的压力信息导出到了 `/proc/pressure/` 目录下对应的文件,你可以使用 `cat` 命令查询资源的压力统计信息: 25 | 26 | ``` 27 | $ cat /proc/pressure/cpu 28 | some avg10=0.03 avg60=0.07 avg300=0.06 total=8723835 29 | 30 | $ cat /proc/pressure/io 31 | some avg10=0.00 avg60=0.00 avg300=0.00 total=56385169 32 | full avg10=0.00 avg60=0.00 avg300=0.00 total=54915860 33 | 34 | $ cat /proc/pressure/memory 35 | some avg10=0.00 avg60=0.00 avg300=0.00 total=149158 36 | full avg10=0.00 avg60=0.00 avg300=0.00 total=34054 37 | ``` 38 | 39 | 内存和 IO 显示了两行指标:**some** 和 **full**,CPU 只有一行指标 **some**。关于 some 和 full 的定义下一节解释。 40 | 41 | **avg** 给出了任务由于硬件资源不可用而被停顿的时间百分比。**avg10**、**avg60**和**avg300**分别是最近10秒、60秒和300秒的停顿时间百分比。 42 | 43 | 例如上面 `/proc/pressure/cpu` 的输出,**avg10=0.03** 意思是任务因为CPU资源的不可用,在最近的10秒内,有0.03%的时间停顿等待 CPU。如果 avg 大于 40 ,也就是有 40% 时间在等待硬件资源,就说明这种资源的压力已经比较大了。 44 | 45 | **total** 是任务停顿的总时间,以微秒(microseconds)为单位。通过 total 可以检测出停顿持续太短而无法影响平均值的情况。 46 | 47 | 48 | 49 | ## some 和 full 的定义 50 | 51 | **some** 指标说明一个或多个任务由于等待资源而被停顿的时间百分比。在下图的例子中,在最近的60秒内,任务A的运行没有停顿,而由于内存紧张,任务B在运行过程中花了30秒等待内存,则 some 的值为50%。 52 | 53 | ![some](./media/psi/someCrop.png) 54 | 55 | some 表明了由于缺乏资源而造成至少一个任务的停顿。 56 | 57 | **full** 指标表示所有的任务由于等待资源而被停顿的时间百分比。在下图的例子中,在最近的60秒内,任务 B 等待了 30 秒的内存,任务 A 等待了 10 秒内存,并且和任务 B 的等待时间重合。在这个重合的时间段10秒内,任务 A 和 任务 B 都在等待内存,结果是 some 指标为 50%,full 指标为 **10/60 = 16.66%**。 58 | 59 | ![full](./media/psi/FullCrop.png) 60 | 61 | **full** 表明了总吞吐量的损失,在这种状态下,所有任务都在等待资源,CPU 周期将被浪费。 62 | 63 | 请注意,**some** 和 **full** 的计算是用整个时间窗口内累计的等待时间,等待时间可以是连续的,也可能是离散的。 64 | 65 | ![discontinuous](./media/psi/DiscontinuousCrop.png) 66 | 67 | 理解了 **some** 和 **full** 的含义,就明白了 CPU 为什么没有 **full** 指标,因为不可能所有的任务都同时饿死在 CPU 上,CPU 总是在执行一个任务。 68 | 69 | ## PSI 阈值监控 70 | 71 | 用户可以向 PSI 注册触发器,在资源压力超过自定义的阈值时获得通知。一个触发器定义了特定时间窗口内最大累积停顿时间,例如,在任何 500ms 的窗口内,累计 100ms 的停顿时间会产生一个通知事件。 72 | 73 | 如何向 PSI 注册触发器呢?打开 `/proc/pressure/` 目录下资源对应的 PSI 接口文件,写入想要的阈值和时间窗口,然后在打开的文件描述符上使用 `select()`、`poll()` 或 `epoll()` 方法等待通知事件。写入 PSI 接口文件的数据格式为: 74 | 75 | ``` 76 | <停顿阈值> <时间窗口> 77 | ``` 78 | 79 | 阈值和时间窗口的单位都是微秒(us)。内核接受的窗口大小范围为500ms到10秒。 80 | 81 | 举个例子,向 `/proc/pressure/io` 写入 "some 500000 1000000",代表着在任何 1 秒的时间窗口内,如果一个或多个进程因为等待 IO 而造成的时间停顿超过了阈值 500ms,将触发通知事件。 82 | 83 | 当用于定义触发器的 PSI 接口文件描述符被关闭时,触发器将被取消注册。 84 | 85 | 我们通过一个例子演示触发器的使用: 86 | 87 | ```c 88 | #include 89 | #include 90 | #include 91 | #include 92 | #include 93 | #include 94 | 95 | int main() { 96 | const char trig[] = "some 500000 1000000"; 97 | struct pollfd fds; 98 | int n; 99 | 100 | fds.fd = open("/proc/pressure/io", O_RDWR | O_NONBLOCK); 101 | if (fds.fd < 0) { 102 | printf("/proc/pressure/io open error: %s\n", 103 | strerror(errno)); 104 | return 1; 105 | } 106 | fds.events = POLLPRI; 107 | 108 | if (write(fds.fd, trig, strlen(trig) + 1) < 0) { 109 | printf("/proc/pressure/io write error: %s\n", 110 | strerror(errno)); 111 | return 1; 112 | } 113 | 114 | printf("waiting for events...\n"); 115 | while (1) { 116 | n = poll(&fds, 1, -1); 117 | if (n < 0) { 118 | printf("poll error: %s\n", strerror(errno)); 119 | return 1; 120 | } 121 | if (fds.revents & POLLERR) { 122 | printf("got POLLERR, event source is gone\n"); 123 | return 0; 124 | } 125 | if (fds.revents & POLLPRI) { 126 | printf("event triggered!\n"); 127 | } else { 128 | printf("unknown event received: 0x%x\n", fds.revents); 129 | return 1; 130 | } 131 | } 132 | 133 | return 0; 134 | } 135 | ``` 136 | 137 | 在服务器上编译并运行该程序,如果当前服务器比较空闲,我们会看到程序一直在等待 IO 压力超过阈值的通知: 138 | 139 | ``` 140 | $ sudo ./monitor 141 | waiting for events... 142 | ``` 143 | 144 | 我们为服务器制造点 IO 压力,生成一个5G大小的文件: 145 | 146 | ``` 147 | $ dd if=/dev/zero of=/home/mazhen/testfile bs=4096 count=1310720 148 | ``` 149 | 150 | 再回到示例程序的运行窗口,会发现已经收到事件触发的通知: 151 | 152 | ``` 153 | $ sudo ./monitor 154 | waiting for events... 155 | event triggered! 156 | event triggered! 157 | event triggered! 158 | event triggered! 159 | event triggered! 160 | ... 161 | ``` 162 | 163 | ## PSI 应用案例 164 | 165 | Facebook 是因为一些实际的需求开发了 PSI。其中一个案例是为了避免内核 OOM(Out-Of-Memory) killer 的触发。 166 | 167 | 应用在申请内存的时候,如果没有足够的 free 内存,可以通过回收 **Page Cache** 释放内存,如果这时 free 内存还是不够,就会触发内核的 OOM Killer,挑选一个进程 kill 掉释放内存。这个过程是同步的,申请分配内存的进程一直被阻塞等待,而且内核选择 kill 掉哪个进程释放内存,用户不可控。因此,Facebook 开发了用户空间的 OOM Killer 工具 [oomd](https://github.com/facebookincubator/oomd)。 168 | 169 | [oomd](https://github.com/facebookincubator/oomd) 使用 PSI 阈值作为触发器,在内存压力增加到一定程度时,执行指定的动作,避免最终 OOM 的发生。oomd 作为第一道防线,确保服务器工作负载的健康,并能自定义复杂的清除策略,这些都是内核做不到的。 170 | 171 | [cgroup2](https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html) 也支持 group 内任务的 PSI 指标追踪,这样就可以知道容器内 CPU、内存和 IO 的真实压力情况,进行更精细化的容器调度,在资源利用率最大化的同时保证任务的延迟和吞吐量。 -------------------------------------------------------------------------------- /docs/rust-async.md: -------------------------------------------------------------------------------- 1 | # Rust 异步编程笔记 2 | 3 | ## Future trait 4 | 5 | Rust 异步编程最核心的是 [Future](https://doc.rust-lang.org/std/future/trait.Future.html) trait: 6 | 7 | ```rust 8 | pub trait Future { 9 | type Output; 10 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll; 11 | } 12 | pub enum Poll { 13 | Ready(T), 14 | Pending, 15 | } 16 | ``` 17 | 18 | `Future` 代表一个异步计算,它会产生一个值。通过调用 `poll` 方法来推进 `Future` 的运行,如果 `Future` 完成了,它将返回 `Poll::Ready(result)`,我们拿到运算结果。如果 `Future` 还不能完成,可能是因为需要等待其他资源,它返回 `Poll::Pending`。等条件具备,如资源已经准备好,这个 `Future` 将被唤醒,再次进入 `poll`,直到计算完成获得结果。 19 | 20 | ## async/.await 21 | 22 | 如果产生一个 `Future` 呢,使用 `async` 是产生 `Future` 最方便的方法。使用 `async` 有两种方式:`async fn` 和 `async blocks`。每种方法都返回一个实现了`Future` trait 的匿名结构: 23 | 24 | ```rust 25 | // `foo()` returns a type that implements `Future`. 26 | async fn foo() -> u8 { 5 } 27 | 28 | fn bar() -> impl Future { 29 | // This `async` block results in a type that implements 30 | // `Future`. 31 | async { 32 | 5 33 | } 34 | } 35 | ``` 36 | 37 | 这两种方式是等价的,都返回了 `impl Future`。`async` 关键字相当于一个返回 `impl Future` 的语法糖。 38 | 39 | 调用 `async fn` 并不会让函数执行,而是返回 `impl Future`,你只有在返回值上使用 `.await`,才能触发函数的实际执行。 40 | 41 | ```rust 42 | async fn say_world() { 43 | println!("world"); 44 | } 45 | 46 | #[tokio::main] 47 | async fn main() { 48 | // Calling `say_world()` does not execute the body of `say_world()`. 49 | let op = say_world(); 50 | 51 | // This println! comes first 52 | println!("hello"); 53 | 54 | // Calling `.await` on `op` starts executing `say_world`. 55 | op.await; 56 | } 57 | ``` 58 | 59 | 上面的程序输出为: 60 | 61 | ```shell 62 | hello 63 | world 64 | ``` 65 | 66 | 在 `Future` 上调用 `await`,相当于执行 `Future::poll`。如果 `Future` 被某些条件阻塞,它将放弃对当前线程的控制。当条件准备好后, `Future`会被唤醒恢复执行。 67 | 68 | 简单总结,我们用`async` 生成 `Future`,用 `await` 来触发 `Future` 的执行。尽管其他语言也实现了async/.await,但 Rust 的 async 是 lazy 的,只有在主动 await 后才开始执行。 69 | 70 | 我们当然也可以手工为数据结构实现 `Future`: 71 | 72 | ```rust 73 | struct Delay { 74 | when: Instant, 75 | } 76 | 77 | impl Future for Delay { 78 | type Output = &'static str; 79 | 80 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) 81 | -> Poll<&'static str> 82 | { 83 | if Instant::now() >= self.when { 84 | println!("Hello world"); 85 | Poll::Ready("done") 86 | } else { 87 | // Ignore this line for now. 88 | cx.waker().wake_by_ref(); 89 | Poll::Pending 90 | } 91 | } 92 | } 93 | ``` 94 | 95 | 同样用 `await` 触发 Future 的实际执行: 96 | 97 | ```rust 98 | #[tokio::main] 99 | async fn main() { 100 | let when = Instant::now() + Duration::from_millis(10); 101 | let future = Delay { when }; 102 | 103 | let out = future.await; 104 | assert_eq!(out, "done"); 105 | } 106 | ``` 107 | 108 | ## Pin 109 | 110 | `Future` 被每个 `await` 分成多段,执行到 `await` 可能因为资源没准备好而让出 CPU 暂停执行,随后该 `future` 可能被调度到其他线程接着执行。所以 `future` 结构中需要保存跨await的数据,形成了自引用结构。 111 | 112 | future 113 | 114 | 自引用结构不能被移动,否则内部引用因为指向移动前的地址,引发不可控的问题。所以future需要被pin住,不能移动。 115 | 116 | Self-Referential Structure 117 | 118 | 如何让 `future` 不被move? 方法调用时只传递引用,那么就没有移动 `future`。但是通过可变引用仍然可以使用 replace,swap 等方式移动数据。那么用 `Pin` 包装可变引用 `Pin<&mut T>`,让用户没法拿到 `&mut T`,就把这个漏洞堵上了。 119 | 120 | pin 121 | 122 | 总之 `Pin<&mut T>` 不是数据的 owner,也没法获得 `&mut T`,所以就不能移动 T。 123 | 124 | 注意,`Pin` 拿住的是一个可以解引用成 T 的指针类型 P,而不是直接拿原本的类型 T。Pin 本身是可 move 的,T 被 pin 住,不能 move。 125 | 126 | ## async runtime的内部实现 127 | 128 | 要运行异步函数,必须将最外层的 `Future` 提交给 executor。 executor 负责调用Future::poll,推动异步计算的前进。 129 | 130 | ![executor](https://cdn.mazhen.tech/images/202209201433572.png) 131 | 132 | `executor` 内部会有一个 `Task` 队列,`executor` 在 `run` 方法内,不停的从 `receiver` 获取 `Task`,然后执行。 133 | 134 | `Task` 包装了一个 `future`,同时内部持有一个 `sender`,用于将自身放回 executor 的 Task 队列。 135 | 136 | `Future` 的 `poll` 方法,接收的是 `Pin<&mut Self>`,而不是 `&mut Self`。所以在向 executor 提交 Future 时,需要先 pin 住,然后才能用来初始化 Task: 137 | 138 | ```rust 139 | fn spawn(future: F, sender: &channel::Sender>) 140 | where 141 | F: Future + Send + 'static, 142 | { 143 | let task = Arc::new(Task { 144 | future: Mutex::new(Box::pin(future)), 145 | executor: sender.clone(), 146 | }); 147 | 148 | let _ = sender.send(task); 149 | } 150 | ``` 151 | 152 | 保存在 Task 字段中的 Future 是 `Pin>`,保证了以后每次调用 `poll` 传入的是 `Pin<&mut Self>`。注意,Pin 是可以移动的,Task 也是可以移动的,只是 Future 不能移动。 153 | 154 | 在执行 Future 时,如果遇到资源未准备好,需要让出 CPU,那么 Task 可以将自己放入 Reactor。Task 实现了 `ArcWake trait`,实际上放入 Reactor 的 Waker 就是 Task 的包装: 155 | 156 | ```rust 157 | fn poll(self: Arc) { 158 | // Get a waker referencing the task. 159 | let waker = task::waker(self.clone()); 160 | // Initialize the task context with the waker. 161 | let mut cx = Context::from_waker(&waker); 162 | 163 | // This will never block as only a single thread ever locks the future. 164 | let mut future = self.future.try_lock().unwrap(); 165 | 166 | // Poll the future 167 | let _ = future.as_mut().poll(&mut cx); 168 | } 169 | ``` 170 | 171 | 当 Reactor 得到了满足条件的事件,它会调用 `Waker.wake()` 唤醒之前挂起的任务。Waker.wake 会调用 `Task::wake_by_ref` 方法,将 Task 放回 executor 的任务队列: 172 | 173 | ```rust 174 | impl ArcWake for Task { 175 | fn wake_by_ref(arc_self: &Arc) { 176 | let _ = arc_self.executor.send(arc_self.clone()); 177 | } 178 | } 179 | ``` 180 | 181 | ## Stream trait 182 | 183 | 对于 `Iterator`,可以不断调用其 `next()` 方法,获得新的值,直到 `Iterator` 返回 `None`。`Iterator` 是阻塞式返回数据的,每次调用 `next()`,必然独占 CPU 直到得到一个结果,而异步的 `Stream` 是非阻塞的,在等待的过程中会空出 CPU 做其他事情。 184 | 185 | `Stream::poll_next()` 方法和 `Future::poll()` 类似, 除了它可以被重复调用,以便从 `Stream` 中接收多个值。然而,`poll_next()` 调用起来不方便,我们需要自己处理 Poll 状态。也就是说,`await` 语法糖只能应用在 Future 上,没法使用 `stream.await` 。所以,我们要想办法用 `Future` 包装 `Stream`,在 `Future::poll()` 中调用 `Stream::poll_next()`,这样就可以使用 await。 [StreamExt](https://docs.rs/futures/latest/futures/stream/trait.StreamExt.html) 提供了 `next()` 方法,返回一个实现了 `Future trait` 的 `Next` 结构,这样,我们就可以直接通过 `stream.next().await` 来获取下一个值了。看一下 `next()` 方法以及 `Next` 结构的实现。 186 | 187 | ```rust 188 | pub trait StreamExt: Stream { 189 | fn next(&mut self) -> Next<'_, Self> where Self: Unpin { 190 | assert_future::, _>(Next::new(self)) 191 | } 192 | } 193 | 194 | // next 返回的 Next 结构 195 | pub struct Next<'a, St: ?Sized> { 196 | stream: &'a mut St, 197 | } 198 | 199 | // Next 实现了 Future,每次 poll() 实际上就是从 stream 中 poll_next() 200 | impl Future for Next<'_, St> { 201 | type Output = Option; 202 | 203 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 204 | self.stream.poll_next_unpin(cx) 205 | } 206 | } 207 | ``` 208 | 209 | 当手动实现一个 stream 时,它通常是通过合成 futures 和其他stream 来完成的。例如下面的例子,将 [Lines](https://docs.rs/tokio/1.14.0/tokio/io/struct.Lines.html) 封装为 Stream,在 Stream::poll_next() 中利用了 Lines::poll_next_line(): 210 | 211 | ```rust 212 | #[pin_project] 213 | struct LineStream { 214 | #[pin] 215 | lines: Lines>, 216 | } 217 | 218 | impl LineStream { 219 | /// 从 BufReader 创建一个 LineStream 220 | pub fn new(reader: BufReader) -> Self { 221 | Self { 222 | lines: reader.lines(), 223 | } 224 | } 225 | } 226 | 227 | /// 为 LineStream 实现 Stream trait 228 | impl Stream for LineStream { 229 | type Item = std::io::Result; 230 | 231 | fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 232 | self.project() 233 | .lines 234 | .poll_next_line(cx) 235 | .map(Result::transpose) 236 | } 237 | } 238 | ``` 239 | 240 | `Stream` 可以是 unpin 的,`Future` 也可以是 unpin 的,如果他们内部包含了其他 `!Unpin` 的 `Stream` 或 `Future`,只需要把他们用 pin 包装,外面的 `Stream` 和 `Future` 就可以是 unpin 的。 241 | 242 | 一般我们使用的 `Stream` 都是 unpin 的,如果不是,就用 pin 把它变成 unpin 的。为啥我们用的都是 unpin 的?因为能 move 的 Stream 更加灵活,可以作为参数和返回值。 243 | 244 | ## AsyncRead 和 AsyncWrite 245 | 246 | 所有同步的 Read / Write / Seek trait,前面加一个 Async,就构成了对应的异步 IO 接口。 247 | 248 | AsyncRead / AsyncWrite 的方法会返回一个实现了 `Future` 的 `struct`,这样我们才能使用 await ,将 future 提交到 async runtime,触发 future 的执行。例如 `AsyncReadExt::read_to_end()`方法,返回 `ReadToEnd` 结构,而 `ReadToEnd` 实现了 Future: 249 | 250 | ```rust 251 | pub trait AsyncReadExt: AsyncRead { 252 | ... 253 | fn read_to_end<'a>(&'a mut self, buf: &'a mut Vec) -> ReadToEnd<'a, Self> 254 | where 255 | Self: Unpin, 256 | { 257 | read_to_end(self, buf) 258 | } 259 | } 260 | 261 | pin_project! { 262 | #[derive(Debug)] 263 | #[must_use = "futures do nothing unless you `.await` or poll them"] 264 | pub struct ReadToEnd<'a, R: ?Sized> { 265 | reader: &'a mut R, 266 | buf: VecWithInitialized<&'a mut Vec>, 267 | // The number of bytes appended to buf. This can be less than buf.len() if 268 | // the buffer was not empty when the operation was started. 269 | read: usize, 270 | // Make this future `!Unpin` for compatibility with async trait methods. 271 | #[pin] 272 | _pin: PhantomPinned, 273 | } 274 | } 275 | 276 | impl Future for ReadToEnd<'_, A> 277 | where 278 | A: AsyncRead + ?Sized + Unpin, 279 | { 280 | type Output = io::Result; 281 | 282 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 283 | let me = self.project(); 284 | 285 | read_to_end_internal(me.buf, Pin::new(*me.reader), me.read, cx) 286 | } 287 | } 288 | ``` 289 | -------------------------------------------------------------------------------- /docs/sudo.md: -------------------------------------------------------------------------------- 1 | 2 | # 如何让用户拥有sudo权限 3 | 4 | 先使用`visudo` 查看当前的配置,这个命令编辑的是`/etc/sudoers`文件。可以直接在这个文件中为用户设置`sudo`权限: 5 | 6 | ``` 7 | # User privilege specification 8 | root ALL=(ALL:ALL) ALL 9 | adp ALL=(ALL) ALL 10 | ``` 11 | 12 | 也可以看看哪个`group`有`root`权限,然后将用户加入这个`group`。例如下面的配置,`admin`组有root权限: 13 | 14 | ``` 15 | # Members of the admin group may gain root privileges 16 | %admin ALL=(ALL) ALL 17 | ``` 18 | 19 | 可以将用户加入admin组,自然就有了sudo权限: 20 | 21 | ``` 22 | usermod -a -G admin [user] 23 | ``` 24 | 25 | 如果提示`admin`不存在,可以先创建这个组,再将用户加入这个group: 26 | 27 | ``` 28 | groupadd admin 29 | usermod -a -G admin [user] 30 | ``` 31 | 32 | 如果不想编辑`/etc/sudoers`,可以在`/etc/sudoers.d/`目录下,为需要`sudo`权限的用户创建独立的文件,在文件中分别为用户授权,格式和`/etc/sudoers`一样: 33 | 34 | ``` 35 | adp ALL=(ALL) ALL 36 | ``` 37 | 38 | 修改文件权限: 39 | 40 | ``` 41 | chmod 440 adp 42 | ``` 43 | 44 | 这样做的好处每个用户都有独立的配置文件,是方便管理。 45 | 46 | 最后,**建议**将`/sbin` 和 `/usr/sbin` 加入到用户路径。 47 | 48 | ``` 49 | PATH=$PATH:/usr/sbin:/sbin 50 | ``` -------------------------------------------------------------------------------- /docs/sync-docker-repo.md: -------------------------------------------------------------------------------- 1 | 2 | # 自己动手将谷歌k8s镜像同步到阿里云 3 | 4 | `Kubernetes` 相关的`docker`镜像存放在Google的镜像仓库 [k8s.gcr.io](https://cloud.google.com/container-registry/),在强国网络环境内无法访问。有人已经将这些镜像同步到了阿里云,你可以在[阿里云容器镜像服务](https://cr.console.aliyun.com/cn-shenzhen/instances/images)中搜索到它们。几乎所有的k8s镜像都已经同步到了阿里云。[阿里云容器服务](https://www.aliyun.com/product/containerservice)团队甚至还有一个开源项目[sync-repo](https://github.com/AliyunContainerService/sync-repo),专门做`Docker Registry`之间的同步。但是,如果你不放心别人同步的镜像,或者最新版本的镜像还没人同步过来,你可以按照本文将介绍的步骤,自己将`gcr.io`上的`docker`镜像搬到阿里云。 5 | 6 | ## 安装配置shadowsocks客户端 7 | 8 | 先简单介绍下`Shadowsocks`协议,详细的工作原理可以参考[这篇博客](https://www.desgard.com/Shadowsocks-1/): 9 | 10 | ![shadowsocks](./media/aliyun/patriotic-networ-2.png) 11 | 12 | 当我们启动`shadowsocks client`时,实际上是启动了一个 `ss-local` 进程,左侧绿色的 `Socks5 Client` 可以是浏览器,也可以是`Telegram`等本地应用,它们和`ss-local`之间是使用 `socks` 协议进行通信。也就是说,浏览器像连接普通 `socks` 代理一样,连接到`ss-local`进程。`ss-local` 会将收到的请求,转发给`ss-server`,由`ss-server`完成实际的访问,并将结果通过`ss-local`返回给浏览器。`ss-server`部署在强国网络之外,和`ss-local`之间是加密传输,这样就实现了跨越长城。其实防火长城已经能够识别`Shadowsocks`协议,但发现我们是在努力学习先进技术,就先放我们过关。 13 | 14 | 好了,我们现在首先要做的是在本机安装配置`shadowsocks`客户端。推荐使用[shadowsocks-libev](https://github.com/shadowsocks/shadowsocks-libev),纯C实现的`shadowsocks`协议,已经在很多操作系统的官方`repository`中 ,安装非常方便。 15 | 16 | * `macOS` 17 | 18 | ``` 19 | brew install shadowsocks-libev 20 | ``` 21 | 22 | * `Ubuntu` 23 | 24 | ``` 25 | sudo apt install shadowsocks-libev 26 | ``` 27 | 28 | 接下来填写`shadowsocks client`配置文件,`JSON`格式,简单易懂: 29 | 30 | ``` 31 | { 32 | "server":"ss服务器IP", 33 | "server_port":443, // ss服务器port 34 | "local_port":1080, // 本地监听端口 35 | "password":"xxxx", // ss服务器密码 36 | "timeout":600, 37 | "method":"aes-256-cfb" // ss服务器加密方法 38 | } 39 | ``` 40 | 至于`shadowsocks`服务器端,可以租用强国网络外的云主机自己搭建,也可以购买现成的[机场服务](https://www.emptyus.com/aff.php?uid=16723),本文就不讨论了。 41 | 42 | 然后启动`shadowsocks client`: 43 | 44 | ``` 45 | nohup ss-local -c ss-client.conf & 46 | ``` 47 | 48 | 49 | ## 安装配置HTTP代理 50 | 51 | `shadowsocks client`创建的是`socks5`代理,不过一些程序无法使用 `socks5` ,它们需要通过 `http_proxy` 和 `https_proxy` 环境变量,使用 `HTTP` 代理。`polipo` 可以帮助我们将 `socks5` 代理转换为 `HTTP` 代理。 52 | 53 | * `macOS`下安装`polipo` 54 | 55 | ``` 56 | brew install polipo 57 | ``` 58 | 59 | * `Ubuntu`下安装`polipo` 60 | 61 | ``` 62 | sudo apt install polipo 63 | 64 | # 建议停掉polipo服务,需要的时候自己启动 65 | sudo systemctl stop polipo.service 66 | sudo systemctl disable polipo.service 67 | ``` 68 | 69 | 启动`HTTP`代理 70 | 71 | ``` 72 | sudo polipo socksParentProxy=127.0.0.1:1080 proxyPort=1087 73 | ``` 74 | 75 | `socksParentProxy`配置为`localhost`和`ss-local`监听端口,`proxyPort`是启动的`HTTP`代理端口。 76 | 77 | 我们可以在命令行终端测试`HTTP`代理的效果: 78 | 79 | ``` 80 | $ export http_proxy=http://localhost:1087 81 | $ export https_proxy=http://localhost:1087 82 | $ curl https://www.google.com 83 | ``` 84 | 85 | 应该可以正常访问到Google。 86 | 87 | ## 设置Docker HTTP代理 88 | 89 | 如果是在`macOS`上使用`Docker Desctop`,可以在`Preference`中的`Proxies`设置上一步启动的HTTP代理: 90 | 91 | ![docker proxy](./media/aliyun/docker-proxy.png) 92 | 93 | 如果是`Linux`平台,请参考Docker的[官方文档](https://docs.docker.com/config/daemon/systemd/#httphttps-proxy)进行设置。 94 | 95 | ## 在阿里云创建容器镜像的命名空间 96 | 97 | 为了将镜像同步到阿里云,首先需要在阿里云的[容器镜像服务控制台](https://cr.console.aliyun.com/cn-shenzhen/instances/namespaces)创建镜像的命名空间。 98 | 99 | ![aliyun docker image namespace](./media/aliyun/aliyun-docker-image-namespace.png) 100 | 101 | 建议将仓库类型设置为“公开”,这样其他人也能搜索、下载到镜像。 102 | 103 | ## 从`gcr.io`下载镜像 104 | 105 | 在本机从`gcr.io`下载镜像,我们以镜像`pause:3.1`为例: 106 | 107 | ``` 108 | docker pull k8s.gcr.io/pause:3.1 109 | ``` 110 | 111 | ## 给镜像标记新的`tag` 112 | 113 | 根据前面在阿里云创建的命名空间,给镜像标记新的`tag`: 114 | 115 | ``` 116 | docker tag k8s.gcr.io/pause:3.1 registry.cn-shenzhen.aliyuncs.com/mz-k8s/pause:3.1 117 | ``` 118 | 119 | `mz-k8s`是在前面创建的命名空间。 查看tag结果: 120 | 121 | ``` 122 | $ docker images 123 | 124 | REPOSITORY TAG IMAGE ID CREATED SIZE 125 | k8s.gcr.io/pause 3.1 da86e6ba6ca1 17 months ago 742kB 126 | registry.cn-shenzhen.aliyuncs.com/mz-k8s/pause 3.1 da86e6ba6ca1 17 months ago 742kB 127 | ``` 128 | 129 | 通过`IMAGE ID`可以看出,两个镜像为同一个。 130 | 131 | ## 将镜像上传到阿里云 132 | 133 | 登录阿里云镜像仓库: 134 | 135 | ``` 136 | $ docker login --username=(阿里云账号) registry.cn-shenzhen.aliyuncs.com 137 | ``` 138 | 139 | 根据提示输入password,登录成功后,显示Login Succeeded。 140 | 141 | 上传镜像: 142 | 143 | ``` 144 | docker push registry.cn-shenzhen.aliyuncs.com/mz-k8s/pause:3.1 145 | ``` 146 | 147 | ## 从阿里云下载镜像 148 | 149 | 现在可以在其他机器上从阿里云下载`pause:3.1`镜像,这时候已经不需要科学上网了: 150 | 151 | ``` 152 | $ docker pull registry.cn-shenzhen.aliyuncs.com/mz-k8s/pause:3.1 153 | ``` 154 | 155 | 给镜像打上原来的tag,这样`kubeadm`等工具就可以使用本地仓库中的`pause:3.1`镜像了: 156 | 157 | ``` 158 | $ docker tag registry.cn-shenzhen.aliyuncs.com/mz-k8s/pause:3.1 k8s.gcr.io/pause:3.1 159 | ``` 160 | 161 | 至此,我们跨越长城,将一个docker镜像从`gcr.io`搬到了`Aliyun`。 162 | 163 | 如果是需要批量、定时的从`gcr.io`同步镜像,建议考虑使用阿里开源的[sync-repo](https://github.com/AliyunContainerService/sync-repo)。 -------------------------------------------------------------------------------- /docs/tomcat-architecture.md: -------------------------------------------------------------------------------- 1 | 2 | # Tomcat系统架构简介 3 | 4 | ## Tomcat系统架构图 5 | 6 | ![tomcat-architecture](./media/tomcat/tomcat-architecture.png) 7 | 8 | 从架构图可以看出,顶层组件`Server`代表一个Tomcat Server实例,一个`Server`中有一个或者多个`Service`,每个`Service`有多个`Connector`,以及一个`Engine`。 9 | 10 | `Connector`和`Engine`是Tomcat最核心的两个组件。 11 | 12 | `Connector`负责处理网络通信,以及应用层协议(HTTP,AJP)的解析,生成标准的`ServletRequest`和`ServletResponse`对象,然后传递给`Engine`处理。每个`Connector`监听不同的网络端口。 13 | 14 | `Engine`代表整个`Servlet`引擎,可以包含多个`Host`,表示它可以管理多个虚拟站点。`Host`代表的是一个虚拟主机,而一个虚拟主机下可以部署多个Web应用程序,`Context`表示一个Web应用程序。`Wrapper`表示一个`Servlet`,一个Web应用程序中可能会有多个`Servlet`。 15 | 16 | 从Tomcat的配置文件`server.xml`也能看出Tomcat的系统架构设计。 17 | 18 | ``` 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ``` 30 | 31 | ## `Connector` 32 | 33 | 我们再仔细看一下`Connector`的内部实现。 34 | 35 | ![connector](./media/tomcat/connector.png) 36 | 37 | * `Endpoint` 负责网络通信 38 | * `Processor` 实现应用层协议(HTTP,AJP)解析 39 | * `Adapter` 将Tomcat的`Request`/`Response`转换为标准的`ServletRequest`/`ServletResponse` 40 | 41 | Tomcat的网络通信层支持多种 I/O 模型: 42 | 43 | * **NIO**:使用`Java NIO`实现 44 | * **NIO.2**:异步I/O,使用`JDK NIO.2`实现 45 | * **APR**:使用了Apache Portable Runtime (APR)实现 46 | 47 | ![endpoint](./media/tomcat/endpoint.png) 48 | 49 | Tomcat实现支持了多种应用层协议: 50 | 51 | * **HTTP/1.1** 52 | * **HTTP/2** 53 | * **AJP**:二进制协议,Web Server和Tomcat之间的通信协议 54 | 55 | ![processor](./media/tomcat/processor.png) 56 | 57 | `Processor`解析网络字节流生成Tomcat的`Request`对象后,会调用`Adapter.service(request, response)`方法。`Adapter`是`Servlet`引擎的入口,`Adapter`负责将Tomcat的`Request`对象转换为标准的`ServletRequest`,然后再调用`Servlet`引擎的`service`方法。 58 | 59 | ![adapter](./media/tomcat/adapter.png) 60 | 61 | ## `ProtocolHandler` 62 | 63 | Tomcat允许一个`Engine`对接多个`Connector`,每个`Connector`可以使用不同的 I/O 模型,实现不同的应用层协议解析。`Connector`屏蔽了 I/O 模型和协议的区别,传递给`Engine`的是标准的`ServletRequest`/`ServletResponse`对象。 64 | 65 | 由于 I/O 模型和应用层协议解析可以自由组合,Tomcat使用`ProtocolHandler`实现这种组合。各种组合都有相应的具体实现类。比如:Http11NioProtocol 和 AjpNio2Protocol。 66 | 67 | ![tomcat-ProtocolHandler](./media/tomcat/tomcat-ProtocolHandler.png) 68 | 69 | 关于`NioEndpoint`和`Nio2Endpoint`组件的内部实现,会在后续文章进行分析。 -------------------------------------------------------------------------------- /docs/tomcat-nioendpoint.md: -------------------------------------------------------------------------------- 1 | 2 | # Tomcat的NioEndpoint实现分析 3 | 4 | 在上一篇[](./tomcat-architecture.md)中提到,Tomcat的网络通信层支持多种 I/O 模型。本文将介绍`NioEndpoint`,它是直接使用`NIO`实现了 I/O 多路复用。 5 | 6 | ## `NioEndpoint`的处理流程 7 | 8 | `NioEndpoint`的处理流程如下: 9 | 10 | ![NioEndpoint](./media/tomcat/nioendpoint.png) 11 | 12 | * `Acceptor`实现了`Runnable`接口,运行在一个独立的线程中。`Acceptor`的`run`方法在循环中调用`ServerSocketChannel.accept()`,将返回的`SocketChannel`包装成`NioSocketWrapper`,然后将`NioSocketWrapper`注册进`Poller`。 13 | 14 | * `Poller`同样实现了`Runnable`接口,运行在一个独立的线程中。`Poller`的核心任务是检测I/O事件,它在无限循环中调用`Selector.select()`,会得到准备就绪的`NioSocketWrapper`列表,为每个`NioSocketWrapper`生成一个`SocketProcessor`任务,然后把任务扔进线程池`Executor`去处理。 15 | 16 | * `Executor`是可配置的线程池,负责运行`SocketProcessor`任务。`SocketProcessor`实现了`Runnable`接口,在`run`方法中会调用`ConnectionHandler.process(NioSocketWrapper, SocketEvent)`处理当前任务关联的`NioSocketWrapper`。 17 | 18 | `ConnectionHandler`内部使用一个`ConcurrentHashMap`建立了`NioSocketWrapper`和`Processor`之间的映射。从上一篇[](./tomcat-architecture.md)的介绍我们知道,`Processor`负责应用层协议的解析,那么我们需要为每个`NioSocketWrapper`创建并关联一个`Processor`。 19 | 20 | 为什么要建立`NioSocketWrapper`和`Processor`之间的关联呢?因为`Processor`在从`NioSocketWrapper`中读取字节流进行协议解析时,数据可能并不完整,这时需要释放工作线程,当`Poller`再次触发I/O读取事件时,可以根据`NioSocketWrapper`找回关联的`Processor`,继续进行未完成的协议解析工作。 21 | 22 | `Processor`解析的结果是生成Tomcat的`Request`对象,然后调用`Adapter.service(request, response)`方法。`Adapter`的职责是将Tomcat的`Request`对象转换为标准的`ServletRequest`后,传递给`Servlet`引擎,最终会调用到用户编写的`Servlet.service(ServletRequest, ServletResponse)`。 23 | 24 | ## `NioEndpoint`的线程模型 25 | 26 | 我们注意到,在Tomcat 9的实现中,`Acceptor`和`Poller`都只有一个线程,并且不可配置。`Poller`检测到的I/O事件会被扔进`Executor`线程池中处理,最终`Servlet.service`也是在`Executor`中执行。这是一种常见的NIO线程模型,将I/O事件的检测和处理分开在不同的线程。 27 | 28 | 但这种处理方式也有缺点。当`Selector`检测到数据就绪事件时,运行`Selector`线程的CPU已经在CPU cache中缓存了数据。这时切换到另外一个线程去读,这个读取线程很可能运行在另一个CPU核,此前缓存在CPU cache中的数据就没用了。同时这样频繁的线程切换也增加了系统内核的开销。 29 | 30 | 同样是基于NIO,Jetty使用了不同的线程模型:线程自己产生的I/O事件,由当前线程处理,"Eat What You Kill",同时,Jetty可能会新建一个新线程继续检测和处理I/O事件。 31 | 32 | [这篇博客](https://webtide.com/eat-what-you-kill/)详细的介绍了Jetty的 "Eat What You Kill" 策略。Jetty也支持类似Tomcat的`ProduceExecuteConsume`策略,即I/O事件的产出和消费用不同的线程处理。 33 | 34 | ![Threading-PEC](./media/tomcat/Threading-PEC.png) 35 | 36 | `ExecuteProduceConsume`策略,也就是 "Eat What You Kill",I/O事件的生产者自己消费任务。 37 | 38 | ![Threading-EPC](./media/tomcat/Threading-EPC.png) 39 | 40 | Jetty对比了这两种策略,使用`ExecuteProduceConsume`能达到更高的吞吐量。 41 | 42 | ![benchmark](./media/tomcat/ewyk2.png) 43 | 44 | 其实,Netty也使用了和 "Eat What You Kill" 类似的线程模型。 45 | 46 | ![netty-thread-model](./media/tomcat/netty-thread-model.jpg) 47 | 48 | `Channel`注册到`EventLoop`,一个`EventLoop`能够服务多个`Channel`。`EventLoop`仅在一个线程上运行,因此所有I/O事件均由同一线程处理。 49 | 50 | ## `blocking` write的实现 51 | 52 | 当通过`Response`向客户端返回数据时,最终会调用`NioSocketWrapper.write(boolean block, ByteBuffer from)`或`NioSocketWrapper.write(boolean block, byte[] buf, int off, int len)`,将数据写入socket。 53 | 54 | 我们注意到`write`方法的第一个参数`block`,它决定了write是使用`blocking`还是`non-blocking`方式。比较奇怪,虽然是`NioEndpoint`,但write动作也不全是`non-blocking`。 55 | 56 | 一般NIO框架在处理write时都是`non-blocking`方式,先尝试`SocketChannel.write(ByteBuffer)`,如果`buffer.remaining() > 0`,将剩余数据以某种方式缓存,然后把`SelectionKey.OP_WRITE`添加到`SelectionKey`的`interest set`,等待被`Selector`触发时再次尝试写出,直到`buffer`中没有剩余数据。 57 | 58 | **那是什么因素决定了`NioSocketWrapper.write`是`blocking`还是`non-blocking`呢?** 59 | 60 | 我们看一下`Http11OutputBuffer.isBlocking`的实现: 61 | 62 | ``` 63 | /** 64 | * Is standard Servlet blocking IO being used for output? 65 | * @return true if this is blocking IO 66 | */ 67 | protected final boolean isBlocking() { 68 | return response.getWriteListener() == null; 69 | } 70 | ``` 71 | 72 | 如果`response.getWriteListener()`不为`null`,说明我们注册了`WriteListener`接收write事件的通知,这时我们肯定是在使用异步Servlet。 73 | 74 | 也就是说,当我们使用异步Servlet时,才会使用`NioSocketWrapper.write`的`non-blocking`方式,普通的Servlet都是使用`blocking`方式的write。 75 | 76 | `NioEndpoint`在实现`non-blocking`的write时和一般的NIO框架类似,**那它是如何实现`blocking`方式的write呢?** 77 | 78 | Tomcat的NIO connector有一个配置参数`selectorPool.shared`。`selectorPool.shared`的缺省值为`true`,这时会创建一个运行在独立线程中`BlockPoller`。调用者在发起`blocking` write时,会将`SocketChannel`注册到这个`BlockPoller`中,然后`await`在一个`CountDownLatch`上。当`BlockPoller`检测到准备就绪的`SocketChannel`,会通过关联的`CountDownLatch`唤醒被阻塞的调用者。这时调用者尝试往`SocketChannel`中写入,如果buffer中还有剩余数据,那么会再把`SocketChannel`注册回`BlockPoller`,并继续`await`,重复前面的过程,直到数据完全写出,最后调用者从`blocking`的write方法返回。 79 | 80 | 当设置`selectorPool.shared`为`false`时,`NioEndpoint`会为每个发起`blocking` write的线程创建一个`Selector`,执行和上面类似的过程。当然`NioEndpoint`会使用`NioSelectorPool`来缓存`Selector`,并不是每次都创建一个新的`Selector`。`NioSelectorPool`中缓存的`Selector`的最大数量由`selectorPool.maxSelectors`参数控制。 81 | 82 | 至此,相信你对NioEndpoint的内部实现已经有了整体的了解。 -------------------------------------------------------------------------------- /docs/vscode.md: -------------------------------------------------------------------------------- 1 | # vs code 快捷键 2 | 3 | ## 界面概览 4 | 5 | | 快捷键 | 描述 | 6 | | --- | --- | 7 | | cmd + shift + e | 文件资源管理器 | 8 | | cmd + shift + f | 跨文件搜索 | 9 | | ctrl + shift + g | 源代码管理 | 10 | | cmd + shift + d | 启动和调试 | 11 | | cmd + shift + x | 扩展管理 | 12 | | cmd + shift + p | 查找并运行所有命令 | 13 | | cmd + j | 打开、关闭panel | 14 | 15 | ## 命令行的使用 16 | 17 | | 命令 | 描述 | 18 | | --- | --- | 19 | | code $path | 新窗口中打开这个文件或文件夹 | 20 | | code -r $path | 窗口复用打开文件 | 21 | | code -r -g $file:lineno | 打开文件,跳转到指定行 | 22 | | code -r -d $file1 $file2 | 比较两个文件 | 23 | | ls | code - | 接收管道中的数据,在窗口中展示 | 24 | 25 | ## 光标移动 26 | 27 | | 快捷键 | 描述 | 28 | | --- | --- | 29 | | option + 左/右方向键 | 针对单词的光标移动 | 30 | | cmd + 左/右方向键 | 移动到行首、行尾 | 31 | | cmd + shift + \ | 在花括号之间跳转 | 32 | | cmd + 上/下方向键 | 移动到文档的第一行、最后一行 | 33 | 34 | ## 文本选择 35 | 36 | shift + 光标移动 37 | 38 | ## 删除操作 39 | 40 | 可以先选择,再删除 41 | 42 | | 快捷键 | 描述 | 43 | | --- | --- | 44 | | cmd + fn + del | 删除到行尾 | 45 | | cmd + del | 删除到行首 | 46 | | option + del | 向前删除单词 | 47 | | option + fn + del | 向后删除单词 | 48 | 49 | ## 代码行编辑 50 | 51 | | 快捷键 | 描述 | 52 | | --- | --- | 53 | | cmd + shift + k | 删除行 | 54 | | cmd + x | 剪切行 | 55 | | cmd + enter | 在当前行下一行新开始一行 | 56 | | cmd + shift + enter| 在当前行上一行新开始一行 | 57 | | option + 上/下方向键 | 将当前行上下移动 | 58 | | option + shift + 上/下方向键 | 将当前行上下复制 | 59 | | cmd + / | 将一行代码注释 | 60 | | option + shift + a | 注释整块代码 | 61 | | option + shift + f | 代码格式化 | 62 | | cmd+k cmd+f | 选中代码格式化 | 63 | | ctrl + t | 光标前后字符调换位置 | 64 | | cmd+shift+p transform to up/low case | 转换大小写 | 65 | | ctrl + j | 合并代码行 | 66 | | cmd + u | 撤销光标移动 | 67 | 68 | ## 创建多个光标 69 | 70 | * 使用鼠标 71 | 72 | `option + 鼠标左键` 73 | 74 | * 使用键盘 75 | 76 | | 快捷键 | 描述 | 77 | | --- | --- | 78 | | cmd + option + 上/下方向键 | 创建多个光标 | 79 | | cmd + d | 选中相同单词,并创建多个光标 | 80 | | option + shift+ i | 在选择的多行后创建光标 | 81 | 82 | ## 文件跳转 83 | 84 | | 快捷键 | 描述 | 85 | | --- | --- | 86 | | ctrl + tab | 文件标签之间跳转 | 87 | | cmd + p | 打开文件列表 | 88 | 89 | ## 行跳转 90 | 91 | | 快捷键 | 描述 | 92 | | --- | --- | 93 | | ctrl + g | 跳转到指定行 | 94 | 95 | ## 符号跳转 96 | 97 | | 快捷键 | 描述 | 98 | | --- | --- | 99 | | cmd + shift + o | 当前文件所有符号列表 | 100 | | @: | 符号列表@后输入冒号,符号分类排列 | 101 | | cmd + t | 在多个文件进行符号跳转 | 102 | | cmd + F12 | 跳转到函数的实现位置 | 103 | | shift + F12 | 函数引用列表 | 104 | | ctrl + - | 跳回上一次光标所在位置 | 105 | | ctrl + shift + - | 跳回下一次光标所在位置 | 106 | 107 | ## 代码自动补全 108 | 109 | | 快捷键 | 描述 | 110 | | --- | --- | 111 | | ctrl+ space | 调出建议列表 | 112 | | cmd + shift + space | 调出参数预览窗口 | 113 | | cmd + . | 快速修复建议列表 | 114 | | F2 | 函数名重构 | 115 | 116 | ## 代码折叠 117 | 118 | | 快捷键 | 描述 | 119 | | --- | --- | 120 | | cmd+ option + [ | 最内层折叠 | 121 | | cmd + option + ] | 最内层展开 | 122 | | cmd+k cmd+0 | 全部折叠 | 123 | | cmd+k cmd+j | 全部展开 | 124 | 125 | ## 搜索 126 | | 快捷键 | 描述 | 127 | | --- | --- | 128 | | cmd + f | 搜索 | 129 | | cmd + g | 搜索,光标在编辑器内跳转 | 130 | | cmd + option + f | 查找替换 | 131 | | cmd + shift + f | 多文件搜索 | 132 | 133 | ## 编辑器操作 134 | 135 | | 快捷键 | 描述 | 136 | | --- | --- | 137 | | cmd + \ | 拆分编辑器 | 138 | | option + cmd + 左/右方向键 | 编辑器间切换 | 139 | | cmd + num | 在拆分的编辑器窗口跳转 | 140 | | Cmd +/- | 缩放整个工作区 | 141 | | cmd + shift + p `reset zoom` | 重置缩放 | 142 | 143 | ## 专注模式 144 | 145 | | 快捷键 | 描述 | 146 | | --- | --- | 147 | | cmd + b | 打开或者关闭整个视图 | 148 | | cmd + j | 打开或者关闭面板 | 149 | | cmd+shift+p `Toggle Zen Mode` | 切换禅模式 | 150 | | cmd+shift+p `Toggle Centered Layout` | 切换剧中布局 | 151 | 152 | ## 命令面板 153 | 154 | | 快捷键 | 描述 | 155 | | --- | --- | 156 | | cmd + shift + p | 命令面板 | 157 | 158 | 命令面板的第一个符合对应着不同的功能: 159 | 160 | * `?` 列出所有可用功能 161 | * `>` 用于显示所有的命令 162 | * `@` 用于显示和跳转文件中的 “符号”(Symbols) 163 | * `@:` 可以把符号们按类别归类 164 | * `#` 用于显示和跳转工作区中的 “符号”(Symbols)。 165 | * `:` 用于跳转到当前文件中的某一行。 166 | * `edt` 显示所有已经打开的文件 167 | * `edt active` 显示当前活动组中的文件 168 | * `ext` 插件的管理 169 | * `ext install` 搜索和安装插件。 170 | * `task` 任务 171 | * `debug` 调试功能 172 | * `term`创建和管理终端实例 173 | * `view` 打开各个 UI 组件 174 | 175 | ## 窗口管理 176 | 177 | | 快捷键 | 描述 | 178 | | --- | --- | 179 | | ctrl + w | 窗口切换 | 180 | | ctrl + r | 切换文件夹 | 181 | | ctrl+r cmd+enter | 新建窗口打开文件夹 | 182 | 183 | ## 集成终端 184 | 185 | | 快捷键 | 描述 | 186 | | --- | --- | 187 | | ctrl + ` | 切换集成终端 | 188 | | ctrl + shift + ` | 新建集成终端 | 189 | | cmd+shift+p `Run Active File In Active Terminal` | 在集成终端中运行当前脚本 | 190 | | cmd+shift+p `Run Selected Text In Active Terminal` | 在集成终端中运行所选文本 | 191 | 192 | ## 任务管理 193 | 194 | | 快捷键 | 描述 | 195 | | --- | --- | 196 | | cmd+shift+p `run task` | 自动检测当前项目中可运行的任务 | 197 | | cmd+shift+p `Configure Task` | 配置任务 | 198 | | Cmd + Shift + b | 运行默认的生成任务(build task)| 199 | 200 | ## 鼠标操作 201 | 202 | * 文本选择 203 | * 双击鼠标,选中单词 204 | * 三击鼠标,选中一行 205 | * 四击鼠标,选中整个文档 206 | * 单击行号,选中行 207 | * 文本编辑 208 | * 选中后可以拖动文本到指定区域 209 | * 拖动过程中按`option`,变成复制文本到指定区域 210 | * 在悬停窗口上按下`cmd`,提示函数的实现 --------------------------------------------------------------------------------