首页 文章 精选 留言 我的

精选列表

搜索[部署],共10000篇文章
优秀的个人博客,低调大师

走进JavaWeb技术世界8:浅析Tomcat9请求处理流程与启动部署过程

本系列文章将整理到我在GitHub上的《Java面试指南》仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下Star哈 文章首发于我的个人博客: www.how2playlife.com 本文是微信公众号【Java技术江湖】的《走进JavaWeb技术世界》其中一篇,本文部分内容来源于网络,为了把本文主题讲得清晰透彻,也整合了很多我认为不错的技术博客内容,引用其中了一些比较好的博客文章,如有侵权,请联系作者。 该系列博文会告诉你如何从入门到进阶,从servlet到框架,从ssm再到SpringBoot,一步步地学习JavaWeb基础知识,并上手进行实战,接着了解JavaWeb项目中经常要使用的技术和组件,包括日志组件、Maven、Junit,等等内容,以便让你更完整地了解整个Java Web技术体系,形成自己的知识框架。 为了更好地总结和检验你的学习成果,本系列文章也会提供每个知识点对应的面试题以及参考答案。 如果对本系列文章有什么建议,或者是有什么疑问的话,也可以关注公众号【Java技术江湖】联系作者,欢迎你参与本系列博文的创作和修订。 文末赠送8000G的Java架构师学习资料,需要的朋友可以到文末了解领取方式,资料包括Java基础、进阶、项目和架构师等免费学习资料,更有数据库、分布式、微服务等热门技术学习视频,内容丰富,兼顾原理和实践,另外也将赠送作者原创的Java学习指南、Java程序员面试指南等干货资源) 很多东西在时序图中体现的已经非常清楚了,没有必要再一步一步的作介绍,所以本文以图为主,然后对部分内容加以简单解释。 绘制图形使用的工具是 PlantUML + Visual Studio Code + PlantUML Extension 本文对 Tomcat 的介绍以 Tomcat-9.0.0.M22 为标准。 Tomcat-9.0.0.M22 是 Tomcat 目前最新的版本,但尚未发布,它实现了 Servlet4.0 及 JSP2.3 并提供了很多新特性,需要 1.8 及以上的 JDK 支持等等,详情请查阅 Tomcat-9.0-doc。 https://tomcat.apache.org/tomcat-9.0-doc/index.html Overview Connector 启动以后会启动一组线程用于不同阶段的请求处理过程。 Acceptor 线程组。用于接受新连接,并将新连接封装一下,选择一个 Poller 将新连接添加到 Poller 的事件队列中。 Poller 线程组。用于监听 Socket 事件,当 Socket 可读或可写等等时,将 Socket 封装一下添加到 worker 线程池的任务队列中。 worker 线程组。用于对请求进行处理,包括分析请求报文并创建 Request 对象,调用容器的 pipeline 进行处理。 Acceptor、Poller、worker 所在的 ThreadPoolExecutor 都维护在 NioEndpoint 中。 Connector Init and Start initServerSocket(),通过 ServerSocketChannel.open() 打开一个 ServerSocket,默认绑定到 8080 端口,默认的连接等待队列长度是 100, 当超过 100 个时会拒绝服务。我们可以通过配置 conf/server.xml 中 Connector 的 acceptCount 属性对其进行定制。 createExecutor() 用于创建 Worker 线程池。默认会启动 10 个 Worker 线程,Tomcat 处理请求过程中,Woker 最多不超过 200 个。我们可以通过配置 conf/server.xml 中 Connector 的 minSpareThreads 和 maxThreads 对这两个属性进行定制。 Pollor 用于检测已就绪的 Socket。默认最多不超过 2 个,Math.min(2,Runtime.getRuntime().availableProcessors());。我们可以通过配置 pollerThreadCount 来定制。 Acceptor 用于接受新连接。默认是 1 个。我们可以通过配置 acceptorThreadCount 对其进行定制。 Request Process Acceptor Acceptor 在启动后会阻塞在 ServerSocketChannel.accept(); 方法处,当有新连接到达时,该方法返回一个 SocketChannel。 配置完 Socket 以后将 Socket 封装到 NioChannel 中,并注册到 Poller,值的一提的是,我们一开始就启动了多个 Poller 线程,注册的时候,连接是公平的分配到每个 Poller 的。NioEndpoint 维护了一个 Poller 数组,当一个连接分配给 pollers[index] 时,下一个连接就会分配给 pollers[(index+1)%pollers.length]. addEvent() 方法会将 Socket 添加到该 Poller 的 PollerEvent 队列中。到此 Acceptor 的任务就完成了。 Poller selector.select(1000)。当 Poller 启动后因为 selector 中并没有已注册的 Channel,所以当执行到该方法时只能阻塞。所有的 Poller 共用一个 Selector,其实现类是 sun.nio.ch.EPollSelectorImpl events() 方法会将通过 addEvent() 方法添加到事件队列中的 Socket 注册到 EPollSelectorImpl,当 Socket 可读时,Poller 才对其进行处理 createSocketProcessor() 方法将 Socket 封装到 SocketProcessor 中,SocketProcessor 实现了 Runnable 接口。worker 线程通过调用其 run() 方法来对 Socket 进行处理。 execute(SocketProcessor) 方法将 SocketProcessor 提交到线程池,放入线程池的 workQueue 中。workQueue 是 BlockingQueue 的实例。到此 Poller 的任务就完成了。 Worker worker 线程被创建以后就执行 ThreadPoolExecutor 的 runWorker() 方法,试图从 workQueue 中取待处理任务,但是一开始 workQueue 是空的,所以 worker 线程会阻塞在 workQueue.take() 方法。 当新任务添加到 workQueue后,workQueue.take() 方法会返回一个 Runnable,通常是 SocketProcessor,然后 worker 线程调用 SocketProcessor 的 run() 方法对 Socket 进行处理。 createProcessor() 会创建一个 Http11Processor, 它用来解析 Socket,将 Socket 中的内容封装到 Request 中。注意这个 Request 是临时使用的一个类,它的全类名是 org.apache.coyote.Request, postParseRequest() 方法封装一下 Request,并处理一下映射关系(从 URL 映射到相应的 Host、Context、Wrapper)。 CoyoteAdapter 将 Rquest 提交给 Container 处理之前,并将 org.apache.coyote.Request 封装到 org.apache.catalina.connector.Request,传递给 Container 处理的 Request 是 org.apache.catalina.connector.Request。 connector.getService().getMapper().map(),用来在 Mapper 中查询 URL 的映射关系。映射关系会保留到 org.apache.catalina.connector.Request 中,Container 处理阶段 request.getHost() 是使用的就是这个阶段查询到的映射主机,以此类推 request.getContext()、request.getWrapper() 都是。 connector.getService().getContainer().getPipeline().getFirst().invoke() 会将请求传递到 Container 处理,当然了 Container 处理也是在 Worker 线程中执行的,但是这是一个相对独立的模块,所以单独分出来一节。 Container 需要注意的是,基本上每一个容器的 StandardPipeline 上都会有多个已注册的 Valve,我们只关注每个容器的 Basic Valve。其他 Valve 都是在 Basic Valve 前执行。 request.getHost().getPipeline().getFirst().invoke() 先获取对应的 StandardHost,并执行其 pipeline。 request.getContext().getPipeline().getFirst().invoke() 先获取对应的 StandardContext,并执行其 pipeline。 request.getWrapper().getPipeline().getFirst().invoke() 先获取对应的 StandardWrapper,并执行其 pipeline。 最值得说的就是 StandardWrapper 的 Basic Valve,StandardWrapperValve allocate() 用来加载并初始化 Servlet,值的一提的是 Servlet 并不都是单例的,当 Servlet 实现了 SingleThreadModel 接口后,StandardWrapper 会维护一组 Servlet 实例,这是享元模式。当然了 SingleThreadModel在 Servlet 2.4 以后就弃用了。 createFilterChain() 方法会从 StandardContext 中获取到所有的过滤器,然后将匹配 Request URL 的所有过滤器挑选出来添加到 filterChain 中。 doFilter() 执行过滤链,当所有的过滤器都执行完毕后调用 Servlet 的 service() 方法。 Reference 《How Tomcat works》 https://www.amazon.com/How-Tomcat-Works-Budi-Kurniawan/dp/097521280X 《Tomcat 架构解析》– 刘光瑞 http://product.dangdang.com/25084132.html Tomcat-9.0-doc https://tomcat.apache.org/tomcat-9.0-doc/index.html apache-tomcat-9.0.0.M22-src http://www-eu.apache.org/dist/tomcat/tomcat-9/v9.0.0.M22/src/ tomcat架构分析 (connector NIO 实现) http://gearever.iteye.com/blog/1844203

优秀的个人博客,低调大师

Java程序在K8S容器部署CPU和Memory资源限制相关设置

背景 在k8s docker环境中执行Java程序,因为我们设置了cpu,memory的limit,所以Java程序执行时JVM的参数没有跟我们设置的参数关联,导致JVM感知到的cpu和memory是我们k8s的work node上的cpu和memory大小。这样造成的问题是:当容器中Java程序使用内存超过memory limit时,直接造成Out of Memory错误,从而引起容器重启。JVM很多参数也是很智能的,启动时内存的分配也会根据cpu和memory进行调整,比如GC相关的参数就是动态调整的。如果容器感知到的cpu核数不对,那么对程序的性能也会造成很大的影响。 内存 Java对内存的使用有几个参数可以配置。以前的版本可以用-Xms, -Xmx来分别设置初始化Java堆大小和最大的Java堆大小。但因为Java堆大小并不等于所有可用的内存大小,所以在设置memory limit的时候会加一个值。这样避免Java使用的内存超过分配给容器的最大内存限制。这个增加的值需要一定的经验和测试来获取。 JVM后来提供了UseCGroupMemoryLimitForHeap参数来让JVM自动根据我们提供的内存限制来分配堆的大小。这样也就避免了我们人为去确定应该给堆多大的空间。只要经过测试,确定这个Java程序占用的总共空间就行了。使用方法是在java运行后面加上参数:java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap ⋯ CPU 配了上面的参数,我们还没有完全解决问题。因为JVM GC相关的参数跟CPU处理器核相关联的,可使用的CPU核数越多,分配给GC的线程资源也越多。如果我们不设置正确的CPU核数给容器,那么它看到的就是整个k8s worker node的CPU个数,比如我们限制容器可使用2core,但worker node有32core。那么这个容器会给GC分配很多的线程资源,从而严重影响正常Java线程的运行。 CPU个数对JVM GC的影响 JVM提供了ActiveProcessorCount参数来设置这个值。但这个参数只在java 1.8.0_191以后版本才支持。下面我在笔记本上做了测试(total 8 cores),看看这个参数如何影响GC的参数。 Step1: 写一个hello wold程序。 root@kyle:~# cat Hello.java public class Hello{ public static void main(String[] args){ System.out.println("hello world"); } Step2: 编译 root@kyle:~# javac Hello.java Step3: 不加参数运行 root@kyle:~# java -XX:+PrintFlagsFinal Hello > init.txt [Global flags] intx ActiveProcessorCount = -1 {product} uintx AdaptiveSizeDecrementScaleFactor = 4 {product} uintx AdaptiveSizeMajorGCDecayTimeScale = 10 {product} uintx AdaptiveSizePausePolicy = 0 {product} uintx AdaptiveSizePolicyCollectionCostMargin = 50 {product} … Step4: 加不同参数值运行 root@kyle:~# java -XX:ActiveProcessorCount=1 -XX:+PrintFlagsFinal Hello > p1.txt root@kyle:~# java -XX:ActiveProcessorCount=2 -XX:+PrintFlagsFinal Hello > p2.txt root@kyle:~# java -XX:ActiveProcessorCount=4 -XX:+PrintFlagsFinal Hello > p4.txt root@kyle:~# java -XX:ActiveProcessorCount=8 -XX:+PrintFlagsFinal Hello > p8.txt Step5: 看看不同参数对GC的影响:1个处理器跟2个处理器的比较: root@kyle:~# diff p1.txt p2.txt 2c2 < intx ActiveProcessorCount := 1 {product} --- > intx ActiveProcessorCount := 2 {product} 304c304 < uintx MarkSweepDeadRatio = 5 {product} --- > uintx MarkSweepDeadRatio = 1 {product} 311c311 < uintx MaxHeapFreeRatio = 70 {manageable} --- > uintx MaxHeapFreeRatio = 100 {manageable} 335,336c335,336 < uintx MinHeapDeltaBytes := 196608 {product} < uintx MinHeapFreeRatio = 40 {manageable} --- > uintx MinHeapDeltaBytes := 524288 {product} > uintx MinHeapFreeRatio = 0 {manageable} 388c388 < uintx ParallelGCThreads = 0 {product} --- > uintx ParallelGCThreads = 2 {product} 682,683c682,683 < bool UseParallelGC = false {product} < bool UseParallelOldGC = false {product} --- > bool UseParallelGC := true {product} > bool UseParallelOldGC = true {product} 2个处理器跟4个处理器的比较: root@kyle:~# diff p2.txt p4.txt 2c2 < intx ActiveProcessorCount := 2 {product} --- > intx ActiveProcessorCount := 4 {product} 59c59 < intx CICompilerCount := 2 {product} --- > intx CICompilerCount := 3 {product} 388c388 < uintx ParallelGCThreads = 2 {product} --- > uintx ParallelGCThreads = 4 {product} 4个处理器跟8个处理器的比较: root@kyle:~# diff p4.txt p8.txt 2c2 < intx ActiveProcessorCount := 4 {product} --- > intx ActiveProcessorCount := 8 {product} 59c59 < intx CICompilerCount := 3 {product} --- > intx CICompilerCount := 4 {product} 388c388 < uintx ParallelGCThreads = 4 {product} --- > uintx ParallelGCThreads = 8 {product} 不加参数跟8个处理器的比较: root@kyle:~# diff init.txt p8.txt 2c2 < intx ActiveProcessorCount = -1 {product} --- > intx ActiveProcessorCount := 8 {product} 从上面比较可以看出,不设这个参数跟设置最大参数(当前系统是8core)是一样的。2,4,8核设置只影响ParallelGCThreads, CICompilerCount。但如果只用1核的话,UseParallelGC,UseParallelOldGC都变为false,同时也会影响其它几个参数。见上面diff p1.txt p2.txt比较结果。 CPU个数对Java程序的影响 CPU个数的设置除了对JVM GC性能产生影响外,对Java的工作线程也会产生影响。以下的代码常用于Java库,它会根据CPU的个数产生工作线程。如果没有正确设置docker中的参数,对实际的程序性能会产生很大的影响。 Runtime.getRuntime().availableProcessors() 以下代码摘自aliyun-log-java-producer库,是根据可用处理器来产生相应个数的IO线程来发送loghub数据。 # ProducerConfig.java: public class ProducerConfig { public static final int DEFAULT_IO_THREAD_COUNT = Math.max(Runtime.getRuntime().availableProcessors(), 1); OpenJDK版本 我们运行以下命令检查JDK的版本。openjdk version "1.8.0_131"以后支持UseCGroupMemoryLimitForHeap参数,"1.8.0_191"以后才支持ActiveProcessorCount这个参数。 root@kyle:~# java -version openjdk version "1.8.0_191" OpenJDK Runtime Environment (build 1.8.0_191-8u191-b12-2ubuntu0.16.04.1-b12) OpenJDK 64-Bit Server VM (build 25.191-b12, mixed mode) 改进方案 如果我们使用的JDK版本支持这2个参数,那么我们只需要在运行Java程序时把这UseCGroupMemoryLimitForHeap参数加上,同时再给ActiveProcessorCount参数赋值实际分配给容器的cpu limit就可以了。如果目前的JDK版本低于1.8.0_191,即不支持ActiveProcessorCount,针对这个情况,有2种方法可以进行: 建议升级到191以后的版本,然后根据cpu limit配置ActiveProcessorCount。 不升级jdk版本,直接设置跟ActiveProcessorCount参数相关的GC参数:比如ParallelGCThreads,CICompilerCount。如果是1.8.0_131以前的版本,可以用-Xms, -Xmx参数进行堆空间的大小分配,注意这两个参数只设置了分配给堆的大小,实际的memory limit应该比这个大。这种方案不是一个best practice,毕竟这样没有用到JVM自动适配的一些参数。最关键的,此种方法不能避免很多Java库根据availableProcessors()来做相应逻辑处理。 参考资料 Assign Memory Resources to Containers and Pods Assign CPU Resources to Containers and Pods Kubernetes Demystified: Restrictions on Java Application Resources JVM 对 docker 容器 CPU 限制的兼容 Java SE support for Docker CPU and memory limits 关于Jvm知识看这一篇就够了

优秀的个人博客,低调大师

在阿里云服务器如何一步步部署java项目

一、连接阿里云服务器 根据阿里云控制台设置的远程连接密码,可以通过SecureCRT远程连接阿里云服务器。 二、安装jdk1、上传jdk压缩包 连接好后Alt+p 快捷键打开文件上传窗口,把需要安装的jdk压缩包拖进去。 2、解压缩 tar –zxvf jdk-7u71-linux-i586.tar.gz 3、创建目录 mkdir /usr/local/src/java 放到指定目录mv jdk1.7.0_71 /usr/local/src/java 4、修改环境变量 vim /etc/profile 在这个配置文件的末尾(先i,可以移动光标,通过上下左右箭头),添加如下2行代码(建议复制) export JAVA_HOME=/usr/local/src/java/jdk1.7.0_71

优秀的个人博客,低调大师

Oracle学习1--阿里云ECS上部署单实例数据库11.2.0.4

阿里云ECS安装Oracle11.2.0.4单实例数据库软件一、 环境准备阿里云ECS: 2C 8G 系统类型:centos7.4 Oracle包: 11.2.0.4 二、 云盘挂载这里使用了文件系统:先把云磁盘挂载到 ECS上使用,在 ECS上执行 fdisk -l 检查确认挂载磁盘使用 fdisk 对没有使用的磁盘进行分区,fdisk /dev/vdb ,(图为磁盘为整个分区)按照提示:选择 n,创建一个新的分区,选择作为主分区,分区号 1,开始位置 1,结束位置为最大值,即用整个磁盘作为分区。然后 w 保存退出。格式化成 ext4 分区,执行 mkfs -t ext4 /dev/vdb1修改/etc/fstab,在文件最后加上新加两个分区的定义,保存退出。挂载磁盘,执行mount /dev/vdb1 /orada

资源下载

更多资源
优质分享App

优质分享App

近一个月的开发和优化,本站点的第一个app全新上线。该app采用极致压缩,本体才4.36MB。系统里面做了大量数据访问、缓存优化。方便用户在手机上查看文章。后续会推出HarmonyOS的适配版本。

Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

用户登录
用户注册