JavaEE平台技术——预备知识(Web、Sevlet、Tomcat)

JavaEE平台技术——预备知识(Web、Sevlet、Tomcat)

  • 1. Web基础知识
  • 2. Servlet
  • 3. Tomcat并发原理

在这里插入图片描述

在这里插入图片描述

1. Web基础知识

🆒🆒上个CSDN我们讲的是JavaEE的这个渊源,实际上讲了两个小时的历史课,给大家梳理了一下,这二三十年来,JavaEE的规范,和Spring的规范如何是相生相克的。

我们在整个课程设计中间或这门课中间,会用到一些什么样的相关知识,有一部分的知识可能很简单,大家就当听听过去了,有一部分知识可能大家没有接触到,可能听不懂也没关系,知道我们会用到它,然后知道它用的目的是,为了什么之后的话,大家在课后或者用到它的时候,再去深入的去了解这些相关的预备知识。

🆒🆒引出HTTP
我们首先从最简单最基础的开始讲起啊,我们知道JavaEE的规范主要是用来做后台服务器系统的,后台的服务器系统基本上都是现在是HTTP协议的一统天下,基本上我们不会再用其它的协议去实现我们的后台的服务器系统。

🆒🆒引出统一资源定位符URL
在HTTP的这样的一个协议上头,它就是它依赖的是,我们的统一资源定位符(Uniform Resource Locator),大家都很熟悉了它其实是由这么几个部分构成。

🆒🆒资源定位符URL的构成

  1. 协议,我们主要用的是HTTP的协议,所以这个部分是HTTP或者HTTPS;
  2. 服务器的域名,那域名实际上对应的是IP,我们会用域名服务域名去查那个IP,所以IP是什么其实是没有关系的,这里看到的是一个DNS的域名,我们在用的时候其实我们,往往不会用DNS的域名,而是用我们自己的一个名字,DNS域名是到DNS服务器上去查,我们自己的名字其实就是到我们的服务器上,去查那个名字所对应的IP,所以后面会看到我们的程序,其实可以任意装在任何一个地方,只要我们给它提供了查名服务,用名字去查到这个IP,这个问题就能够解决,这是第二个部分。

DNS(Domain Name System)域名系统是互联网的一项基础设施技术。它的主要功能是将易于记忆的域名(例如www.example.com)转换为IP地址(例如192.0.2.1),以便在网络上定位和识别计算机服务和设备。DNS域名系统可以看作是互联网的电话簿,它通过将域名映射到相应的IP地址来帮助用户定位所需的网络资源。

在实际应用中,当您在浏览器中键入网址时,浏览器首先会向DNS服务器发送查询请求,以获取相应域名所对应的IP地址,然后才能建立连接并加载所需的网页内容。这个过程使得用户无需记住复杂的数字地址,而是能够使用更易于记忆的域名来访问网站。

  1. 端口号因为在同一台服务器上可以装多个应用,所以每个应用需要自己的一个端口号,你才会知道说你访问这个服务器的时候,是访问的是这个应用,而不是其它的应用,这是端口号。
  2. 后面的东西是路径,路径通常是用来在一个应用里头去标识,说这个部分是什么样的东西,这个路径其实它并不代表说在这台服务器上的,某个文件的目录,它就是用来去标识去区分,在我们的应用之间去区分说,这是应用的这半部分,还是这是应用的另外一个部分,比如说我们以后在用到路径的时候,通常是用来区分什么,区分模块,我们会有若干个模块,那就会用路径去区分这个模块,这是路径。
  3. 最后则是资源,就是我们真正提供的东西,这个不是一个文件,往往可能是我们在服务器上头的一段代码。

现在我们拿到应该网址,我们可以清楚的看到以下部分是什么!
在这里插入图片描述
我们会描述了,我们用什么样的协议,访问哪台服务器的哪个端口,在这台服务器的端口的应用里头,我们要访问哪一部分,以及这一部分的什么东西,构成了这样的一个URL。

在这里插入图片描述

🆒应该大家都熟悉,在web的服务中间访问的过程这样子,我们前端不一定是人,也可能是另外一台服务器,它通过一个URL的地址,发送到一台服务器上,它中间会有比较复杂的查名过程,所以它查到这个服务器的IP,然后把这个请求发到服务器上,然后服务器就根据端口,根据路径,然后根据资源就是后面的三个部分,去找到对应的东西,去执行它,得到一个结果,这个结果再返回给前端,然后前端去用这个结果,我们在图上看到是HTML,我们现在在这个过程里面,很多的时候是一段数据,就是发一个请求,向服务器去执行一个什么样的东西,然后服务器通过这个端口,知道是什么样的应用,通过后面的路径知道是应用的哪一块部分,通过资源知道说我在这块部分要执行什么,然后执行它得到一个结果,把这个结果再返回给前端,这就是这样的一个访问过程,我想这个应该大家都很熟悉。
在这里插入图片描述

🆒其中HTTP协议或者HTTPS协议,是这个里头最基本的部分,最上面一层应用层的协议,对吧,所以它这种协议是要基于底层的TCP/IP来实现的,所以它是一层一层的啊,网络的7层协议。

在这里插入图片描述

🆒我们这里讨论的,就是它这个http协议的特点在于说——它是一对的,一问一答发出一个请求,我们称之为HTTP的Request,然后它返回一个HTTP的Response,不问不答,如果我不发HTTP Request,它不会回来一个HTTP Response,而且我不会记录之前问过什么,所以它的每一个相关的内容,都应该是基于它的HTTP的Request,去返回HTTP的Response。

在这里插入图片描述

🆒🆒🆒HTTP之后我们会引出Request Response

当然我们在应用中间,我们会发现HTTP的协议的这个特性,其实挺挺恼火的,因为我们确实是需要知道它之前干了什么,但是HTTP协议本身来说,它只提供一问一答的这样一个机制,所以我们要靠其它的部分来完成这个东西。

🆒🆒HTTPS是在这个协议上做了加密, HTTP协议是明文的,所以我们如果说,想要对我们在网络上头传输的内容,无论是Request的内容,还是Response内容,进行加密的话,进行保密处理的话,都应该使用HTTPS的协议。

原来Google警告这么来的!

所以大家现在可以看到,基本上所有的应用都是用的,HTTPS不是HTTP,甚至极端情况下的比如像谷歌的浏览器,你用HTTP它要警告,你说这个服务器用的是HTTP是不安全的,你确定要不要用,你用才能继续访问它。

🆒虽然我们在课上一直是,用HTTP的协议来举例子,但真实在用的时候其实是用HTTPS,但是无论是HTTP还是HTTPS,对于我们的程序来说其实没差别的,它是一个网络传输层的问题,它是用了加密还是没有用加密的传输对于我们程序来说,我们其实是不关心,它是用HTTP还是HTTPS的。

🆒只是在配置上如果说,要用HTTPS的话,在Web服务器的配置上,是要做些很特别的配置,不管是HTTP还是HTTPS的协议,它都是HTTP的Request和Response的过程,所有的协议都是分为包头和正文的部分,就是Head和Body的部分,Head是控制信息

我们为什么要把这个协议的,控制信息拿出来讲——因为我们有很多重要的数据,都在控制信息里头

🆒控制信息其实我们在写程序的时候,我们也会人为的在Head里头 ,在头里头插入我们所需要的控制信息,我们在服务和服务之间调用的时候,我们也会在转发 HTTP协议时候,加入我们所需要的头的信息。

所以每年在做课程设计的时候,同学们在做微服务体系结构的时候,会犯错的一个主要的地方,就是这个协议的头没控制好,调进来的时候是ok的,然后再去调别人就不ok了,出现这种情况你首先就,应该怀疑说这个头有问题,你要看看它进来的头ok的头是什么,你再去调别的时候你应该保持一样的头,才能保证整个的调用是连贯的,所以头的格式大家应该都很清楚,它描述了请求的方法。

在这里插入图片描述

HTTP Request 部分
在这里插入图片描述

Head的大致论述
🆒我们后面讲HTTP有若干种请求的方法,也是跟我们的设计有关,因为我们后面会讲RestFul的API,其实它就利用 HTTP的请求的方法,去做 API的设计,然后是URL,然后是它的协议的版本号,然后是它的主机然后后面它的控制字符,我们在里头会要用到其中的一些控制字符,或者要去加一些控制字符,就是它的变量它的头里头所放的这些字段名和值,这是我们要用到的东西。

Body的大致论述
🆒Body里头放的就是内容,在Body里头通常放的,就是一个JSON的字串,一个JSON的内容放在里面,对于这个请求的方法,就是HTTP的Request方法,这个也应该知道,我们一共有OPTIONS GET,POST PUT DELETE,CONNECT和TRACE这些方法。

请求方法描述
OPTIONS返回服务器针对特定资源所支持的HTTP请求方法,
GET向特定的资源发出请求。请求体中无内容
POST向指定资源提交数据进行处理请求。数据被包含在请求体中。
PUT从客户端向服务器传送的数据取代指定的文档的内容。
DELETE请求服务器删除指定的页面。
CONNECTHTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。
TRACE回显服务器收到的请求,主要用于测试或诊断。

🆒我们其中会用到的部分来说,主要是前面的5个,就是OPTIONS GET POST PUTDELETE这五种方法,这5种方法在RestFul API中间,它是有特别的定义的,就是我们会利用HTTP的请求,针对同一个URL的,不同的请求的方式,去做不同的语义,然后去设计说,这是用来去实现什么样特定的功能。

在这里插入图片描述

HTTP Response部分

在这里插入图片描述

🆒Response部分,跟Request的部分一样,仍然分为包头和正文

🆒包头依然是控制信息,所以我们在Response的包头部分,也会要插入一些,我们所要的东西。

🆒而Body的部分主要是JSON的数据,我们会以JSON的格式,把数据放到正文里头,放到Body的部分,这是HTTP的协议。

在这里插入图片描述

最后是它的状态码,状态码是在HTTP的,Response里头的字段,大家应该知道在头里头,它应该会返回它的状态码是什么。

🆒🆒这个状态码表示了什么?
表示了服务器端向客户端返回,说服务器端的你发的请求它的结果是什么

🆒🆒成功
我们最常见到的结果是200和201,200和210都是ok,说明服务器一切正常,你无论是一个请求,还是一个POST的请求, GET请求都是200,POST的请求的返回的ok是201,这是它的规范所决定的。

🆒🆒400系列小解读
其它的这些值就代表特别的含义,比如说我们说400开头的这些东西,通常是你发过来的东西有问题,比如说你发过来的 Request,它的正文的值我解释不出来,不符合我的格式,所以就会以400的方式:

  • 你发过来的请求要访问的这个程序,是需要登录的,但是你没有登录就会返回401
  • 你如果登录了你来访问它这个部分,如果你没有权限,它就会返回403;
  • 如果你要请求的 URL根本就是错的,在我这里头就不知道这是个什么东西,我就会给你返回404

所以400系列的状态,基本上是跟你的请求有关的,就是你请求有问题,说到底你请求有问题,就会返回400类的错误码,这些错误都是会用程序去完成的,比如说我们在解析它的一个Request的时候,发现它对不到我们的一个参数上,那就会返回400。

🆒🆒500系列小解读
500开头的错误,通常就是我们服务器本身有问题

  • 比如说你的服务器崩掉了,你就会返回500。
  • 比如说你的服务器阻塞了,就是我服务器没崩,但是我服务器已经响应不过来了,就会返回503。

所以400的系列的错误,是表示说Request本身有问题,500的错误是表示说Request,没问题,但是我服务器有问题,Response返回里头,值是没有意义的,就会返回500的错误,这是我们主要的状态码

🆒对于所有的状态码都是有定义的,就是我们除了这里所看到的,400和500的状态码以外,我们在这个基础上头,针对比如说200 201的状态码上头,我们还有新的错误码去定义,表示说更新的含义,但是状态码我们还是符合,HTTP协议所规定的状态码,在它这个语义上头去扩充,我们需要返回的更多的值,这是这个状态码。

在这里插入图片描述

2. Servlet

简单来个小导读
💻这个部分我们要讲的就是,关于Servlet,它是一个有着悠久历史,JavaEE的第一版的规范Servlet就出现了,然后到JavaEE的最后一版规范,它还在它!

💻为什么会这么长的时间,存在于JavaEE的规范中间,Spring中也有这个规范?

因为它是我们目前一个,非常重要的技术规范,也是重要基础的规范。在Spring的框架中间的,Servlet Stack的所有的技术,都是基于Servlet规范来实现的。

我们略微讲一下Servlet规范的定义是什么,以及它的实现机制是什么?

虽然我们今天已经很少有机会,去直接来写这部分的Servlet的代码,但是因为它是我们整个Servlet Stack的,一个最基础的东西,所以我们还是有必要去,了解它的实现的机理,这样的话会方便我们去理解,后面的东西是怎样,在这个机制上头去实现的,以及我们如何去调优我们的服务器,特别是Servlet Stack的这个调优,其实跟Servlet的机制是有密切的关系的。

💻Servlet这样的一个机制,我们在JavaEE的规范中间其实有提到,它是用来去做服务器的表现层的,最开始是出于这样的一个目的,它接收的是HTTP的Request

  • 根据这个HTTP的Request,它把服务器端分成了容器和组件两个部分。
  • 容器叫做Servlet容器,用规范去定义了这个容器该做什么。
  • 组件叫做Servlet,规范去定义了Servlet要做什么。

在这里插入图片描述

  • 当一个HTTP的Request过来的时候,它是发到容器;
  • 容器则会根据这个HTTP的,Request去运行Servlet,执行一些逻辑;
  • 这个逻辑就是用Java代码写的你爱做什么做什么,比如说我们这里看到的是访问数据库,然后得到数据,产生你的HTTP Response,里头要放的那些数据,给它送回来这样的一个机制。

💻💻我们要稍微深入讨论的是内部发生了什么?
这就是JavaEE的Servlet的规范里头所定义的东西
在这里插入图片描述

💻JavaEE的Servlet的规范里头定义了一系列的接口和一系列的类,我们这个里头这张图上头,只有第一个接口,是有写了包名的
在这里插入图片描述

💻其它的没写,不是说它没有包名,它的包名跟第一个都是一样的,全在javax.servlet的包里头,这个就是JavaEE的Servlet的规范,定义了一系列的接口和基类。

💻💻这一个接口里头定义了什么?
你要去实现一个Servlet,你首先要去实现一个叫做javax.servlet.Servlet的接口,这个接口里头有很多的方法,其实它最重要的一个方法,就是service方法,service方法有两个参数,一个是ServletRequest,一个是ServletResponse,就这张图上的这个东西,我要实现一个这样的对象,这个对象必须实现Servlet的接口,这个接口里头有service的方法,这个方法接收过来的是,HTTP的Request,返回的是HTTP的response,送过来的Request,返回去的response,作为接口的两个参数。
在这里插入图片描述

但它在这个接口上没有写HTTP两个字,原因是它在设定规范的时候,它想Servlet其实还可以在其它的网络协议上去实现,但事实30年过去了,Servlet也没有在其它的网络协议上去实现,但是它是想这么来做的。

在这里插入图片描述

💻这左右两个接口又有子接口,这个子接口才是HTTP协议的,HttpServletRequest,和HttpServletResponse,这就具体到我们真正的HTTP协议上了。
在这里插入图片描述

💻对于Servlet的接口来说,它有实现了基类,但是基类做了两层。
在这里插入图片描述

💻一个是GenericServlet,一个是HttpServlet,大家知道,这个HttpServlet就是用来去实现HTTP协议的Servlet,在这个基类中间大家可以看到,它实现了一系列的do的方法,这些do的方法全是两个参数,分别是HttpServletRequest,和HttpServletResponse,全是这两个参数,这两个参数是这个(ServletRequest和ServletResponse)的子类。

💻这张图就画的是我刚才所说的这样的一个事情,对于Servlet容器来说,它接到HTTP的Request以后,它把Request变成Request对象,然后创建了一个response的对象

💻然后把Request对象和Response对象,(Servlet是一个对象,实例化成一个对象)通过一个线程去调Servlet的service的方法, service的方法会根据请求的类型,去调doGet还是doPost,doPut还是doDelete,然后把传过来的 HTTP的Request对象和HTTP的response对象传给它,里头你自己去写代码,就这样的一个过程。

💻💻JavaEE规范的特征——就是它针对我们在JavaEE规范中间所定义的接口和基类。

你去继承或者实现这些接口和基类,在它的规范下去写代码,你就是符合它的规范的!!
符合它的规范有什么样的好处,Servlet它的容器是可以替换的,我们目前常见的Servlet的容器有两种,Tomcat、Jetty。

Tomcat是大家最熟悉的,我们一般在写程序的时候,可能优选就用Tomcat,用Tomcat以后,你会发现说有时候它特别慢,然后你就想说我能不能换一个容器,它的速度会更快一点,Jetty就是一个比Tomcat更加高效的容器,但是它用的比较少。

在这里插入图片描述
在这里插入图片描述

我们有一段演示的代码,如何在JavaEE规范上去写一个Servlet,当然这个代码的主要的作用,只是说给大家演示一下代码怎么写,然后大家去理解,Servlet的这样一个机制,我们今天基本上是不会去手写Servlet代码了,但是我觉得还是有必要,大家看一下那个代码怎么写的,在这里我们之所以给大家来讲,这个Servlet的代码,目的并不让大家去学习,如何基于JavaEE的Servlet的规范,来写代码,因为这个部分的技术其实已经过时了,今天我们是不会再来写Servlet代码的,主要的目的就是让大家去了解,Servlet的原理,因为它到今天依然是我们Servlet Stack的一个最基础的部分,这个代码非常的简单。

在这里插入图片描述
在这里插入图片描述

💻我们一共写了三个,Servlet第一个Servlet,是WelcomeServlet
在这里插入图片描述

💻我们可以看到就像我们前面在课上所说的,一个Servlet的代码,是需要实现一个特定的基类的,所以我们可以看到我们的,WelcomeServlet类,extends了HTTPServlet,就是我们前面所说的,实现了HTTP协议的Servlet基类。
在这里插入图片描述

💻在WelcomeServlet的,代码中间其实非常的简单,里头只写了一个方法,就是我们重写了它的doGet的方法,这意味着说我们会去响应它的get的请求。doGet的方法里头两个参数,大家可以看到HttpServletRequest,HttpServletResponse。
在这里插入图片描述

💻既没有从HttpServletRequest拿到任何的数据
在这里插入图片描述

HttpServletResponse中间,获得它的Writer,这个Writer的目的,就是向Http Response中间,写入数据,当然这我们写的数据是一串的HTML,这个HTML就是为了打印出来一个欢迎使用Servlet这样的一个欢迎语,所以把这一堆的,HTML的东西,通过out,把它输出到Http Response里头去,那它就会被送到前端浏览器,就会解析这一堆送过去的HTML,然后就把它呈现出来

在这里插入图片描述
💻💻第二个例子我们就稍微复杂一点,我们写了一个HTML,把一些数据传过去,Servlet的工程中HTML其实是,放在,另外一个目录里头的,我们把它叫做webapp目录,在这个目录底下,我们放了一个get.html的文件
在这里插入图片描述

💻当然还有我们后面要讲的,post.html文件,所有的HTML文件,包括图片等等,静态文件,都是放在webapp的目录底下,我们看一下get.html,其实非常的简单,它里头就是一个form action,在 form action中间,我们主要是提供了一个input框,这个input框是要向前端,去提交一个你的姓名的

在这里插入图片描述

💻回到Servlet代码,我们回来看,WelcomeGetServlet,同样继承的是HttpServlet的,它也是个get请求,所以同样重载了这个doGet的方法,它从Http Resquest中间用它的getParameter的方法去获得了我们从前端传过来的值。

在这里插入图片描述

💻然后把这个值输出到了,Http Response里去,当然作为HTML输出,到Http Response里去,这就是一个非常简单的get,但是跟我们前面那个例子相比,它从Http Resquest中间去拿到了一些值。

在这里插入图片描述
在这里插入图片描述

最后我们看一下,HTTP的post的这种请求,是怎样实现的,post通常是用来传更多的值,当然这个例子中间我们其实就传了一个,非常少的两个值,依然是依赖于 HTML,post.html,来把两个值把它送过来,对于Servlet的这一端来说,它依然还是一个HTTP的,Servlet,但是我们重载了doPost的方法,依然是两个参数HTTP的,Request,HTTP的response,无论是 get的请求,还是post的请求,其实它的值都放到了,HttpServletRequest里头,所以我们依然从HTTP的,Request里头去获得,我们从前端传过来的两个值,然后又把它写到了,Http Response去,然后把它送回前端。

在这里插入图片描述
我们看一下这些代码和HTML,是怎样能够合起来,被Servlet的容器知道说我有多少Servlet的它依赖的是一个叫做web.xml的配置文件,在webapp/WEB-INF的目录下,当然这个是约定在这个目录下,它才会去认web.xml文件,在 web.xml中间其实主要是两个部分。
在这里插入图片描述

一个部分是Servlet的mapping,就是我们看到的下面这个部分,这个部分主要是把我们的请求,映射到对应的Servlet上头,所以我们可以看到,映射到了名字叫做Welcome的Servlet的上头。

在这里插入图片描述

一个部分都是关于Servlet的定义,我们可以看到它定义了三个Servlet,对应的就是我们说的Servlet层的名字,还有我们的代码中间的Servlet的类,所以这样的话Servlet的容器,才会根据请求去,从线程池里头去拿到线程去,执行对应的Servlet的类。

在这里插入图片描述

3. Tomcat并发原理

这样跑的一个过程,我们再要讨论一下它的细节,它是用多线程来跑的,我们做的是服务器端的一个应用,服务器端一个应用,同时都要响应很多的请求,就是有不同的客户端,会同时发过来,很多的HTTP的Request,针对每一个HTTP的Request,在Servlet的容器中间,都是以一个独立的线程去跑。
在这里插入图片描述

就我们这张图上其实有看到,它是用一个独立的线程去跑的,会起一个线程里头去调Servlet,对象的service的方法,Servlet的对象是唯一的,因为我们在代码中间,会写很多的Servlet的对象,然后这些Servlet的对象会被很多的请求来调。

如果说Servlet的对象不唯一的话,内存吃得太厉害,所有的对象就只有一个。

另外一个问题:Servlet的对象是不能有属性的,线程和进程的差异,线程是共享Java虚拟机的内存空间的,a线程和b线程,如果说它去用一个Servlet的对象,它们俩是同一个对象,它们俩的属性就是同一个属性,如果a线程改了b线程就会受影响,就会出现这样的问题为了避免这样的问题,简单的方法就是Servlet对象是没有属性的,没有属性你就不存在改的问题,它在不同的线程里头去调的话也不会打架。

在比如说service方法或者doGet方法中间的局域变量,局域变量是不会有问题的,在Java虚拟机里头,不同的线程是有线程栈的,不同的线程它的局域变量全是分开的,分别保存在自己的线程栈里头,这里就会带来另外一个调优的问题,如果当线程很多的时候,线程栈占的空间也会很大,所以你要适当调整线程栈的大小,才能容纳更多的线程,不是说你简单的把线程调到1万,它就能够跑的调到1万,线程栈不够了它也跑不了。
所以 线程与线程栈 是有对映关系的

为了避免这个问题,我们对象通通不写属性,凡是会有存在的线程冲突的这些对象,我们都不写属性这样的话它,线程就是安全了就没有问题。

每一个请求用一个线程去执行,这个线程里头会去调Servlet的service方法,其实在这个容器中间,在执行线程的时候,它是分成了5步,哪5步?

在这里插入图片描述


💻💻Read的重要性——读为什么还要单独写一个?

因为网络的特性,它并不是一个可靠的稳定的,你永远是一个匀速的在读一个东西,你的网络会受到各种各样的因素影响,比如说你从美国发一个请求过来,Request读起来可能从头读到到完整的读完,大家知道HTTP是应用层协议,底下的TCP/IP协议可能一个Request,会被分成了若干个TCP/IP的包,所以说你拿到第一个包到最后一个包完整读完,最后拼成一个HTTP的Request,可能是要不知道多少时间,完全是看对面发过来的有多远,网络的状况是怎么样,会不会丢包会不会重发都不知道,read的过程其实是完整的读完HTTP的Request的过程,这个是要时间的


💻💻Decode的重要性

读完以后读到的是一个Head和Body的这样的一个协议,然后它要转成一个HTTP的Request的对象(实现了一个接口的对象),所以它要做decode,就是把网络协议读到的数据,变成我们的一个Java的对象,这叫做decode。


💻💻Compute的重要性

decode的主要是指 Request的对象,拿到这个对象以后,创建一个Response对象一起传给了service的方法。

Service方法中间会去根据你的请求的,类型去调doGet还是doPost,还是doDelete等等,这就是我们说的compute过程,因为那个方法是你自己写的,就数据已经拿到了你去做逻辑,然后产生结果这个都是compute的过程。


💻💻Encode的重要性
拿到这个结果以后,你把它丢到 Response里头返回回去,它要把它变成Response的协议,那是encode的过程。


💻💻send的重要性
变成协议以后再传回给客户端,那是send的过程同样传回去的数据.


在TCP/IP上有可能如果比较大的话,可能会分成若干个包,然后一个包中间会有丢包,所以send的过程也是不可控的,整个这个过程,都是在一个线程中间去完成的,这样的话它用多线程的方式,就是多少个请求开一个线程,我们Servlet对象的不写属性大家相互不打架,所有的局域变量都在自己的线程栈里头,所有的读写decode encode,都各自在线程中间去处理,这样就能做到并发。

💻这样的话会有一定的问题,我们其实对于一台具体的物理机上,是没有办法无限制的增加线程的,为什么没办法无限制增加线程?

  • 我们已经说到每个线程要占一定的,内存空间,针对一台特定的物理机内存是有限的,你不可能无限制的1万 2万 3万, 4万的线程上去内存就没有了。
  • 第二个是你的计算资源是有限的,因为你任何一台物理机的,CPU就是那么多核,或者就是那么多个CPU,你中间这个计算过程就要靠CPU来完成。

如果你无限制的增加线程的话,我们在操作系统中间会讲到,说CPU会忙不过来, CPU就会要不断的分片轮转,然后CPU就会把大量的时间,花在这个轮转上头,其实它并不是一个高效的方式,我们会根据一台物理机器的,实际状况它有多少内存,它的CPU的计算能力,再结合我们任务的这样一个情况,去制定一个线程池。

💻 线程池是什么?

  • 就是我们最多允许你跑多少个线程。
  • 如果多于这个线程的话怎么办,就让它去等待。
  • 如果在多于等待数的时候我就拒绝,这就是线程池的概念。

有了这个线程池以后,我们实际上是预先创建了若干个线程,放到线程池里头,因为孵化线程是需要消耗时间的,要用代码来消耗时间的,比如说内存中间开一些空间等等,所以我们把线程事先孵化出来,把它放到线程池里头,当请求过来的时候,我们是从线程池里头,拿空闲的线程,拿出来直接去用的,这样可以加快它的响应时间,而且可以管理整个服务器上,对于内存和CPU的消耗,不会出现说它的请求多了,它的内存不够,它的服务器崩溃的这样的一种情况,因为它用线程池卡住了它的上限。

💻其实我们现在如果在做是Servlet Stack,这一条技术线的时候,我们始终会关心这么4个参数

#最大工作线程数、默认200
server.tomcat.max-threads = 200
#最大连接数默认是10000
server.tomcat.max-connections = 10000
#等待队列长度,默认100
server.tomcat.accept-count = 100
#最小工作空闲线程数,默认10
server.tomcat.min-spare-threads = 100
  • max-threads,这个就是线程池的上限,这个就是因为你的服务器的特性,你的物理性能决定的你有多少内存,你的CPU计算能力是多少,所以你要定一个上限,上限不能超过你的物理资源所提供的能力,
  • accept-count,这个是排队队列的长度,对如果说发来的请求,超过了你的处理的上限的话,我还可以让它排队等待不是直接拒绝它,我现在定的是100,就意味着说上限是200,它最多可以同时处理多少,300个请求,其中200是在处理的,100是在等待的,如果超过300以后就会怎么样,就会拒绝,就是我们刚才看到的503, server unavailable,就直接就拒绝了,发挥503的错误码回去,这是根据你的服务器的能力来决定的,
  • max-connections,最大连接数通常我们不会动,因为它默认值是1万,我们现在基本上,我们的服务器很难达到,1万的这样的一个东西,1万是代表什么意思,是max-threads加上accept-count,这些连接全部都在的,就是你正在处理的最大数,和你让它等待的最大数,加起来就是它最大连接数,所以我们现在是300,它默认值是1万还远的了,所以一般来说我们不会去动它
  • min-spare-threads,这个是在说线程池的问题,当然我们可以说线程池有200,我们一起来就占200,占200你觉得有点浪费了,有些时候因为占了200以后,它其实会占一定的内存空间,但其实很多时候我们其实还没有到200,所以我们会定一个min-spare-threads,就是线程在启动的时候,它只有100个线程在里头,如果说超过了100个线程,它就开始孵化线程放进去,上限是200。然后如果说它的压力下来了,就是没有200个请求慢慢下来了,它又会把孵化的线程给它释放掉,释放出我们物理机的一些内存空间,下限又会降到慢慢最后降到100,少于100请求以后,它就一直保持100个线程,线程在里头少于100个线程,它也不会再去释放线程池里的线程了,所以这是一个下限。

这4个参数是我们之后会特别是在用Tomcat时候,会频繁去调的参数,这个参数其实不是一个恒定的值,这个值其实除了跟你的服务器的物理的性能有关以外,还跟你的应用有关,你的应用到底是io密集型的,还是计算密集型的。

如果说你的应用,是io密集型的意味着什么,因为这在read和send的,过程中间是需要消耗尽量多的时间的,计算的时间其实是比较少的,意味着如果你内存足够大的话,你可以尽量多开线程,就是max-threads可以开很高,开1000 ,2000都没问题,因为对CPU压力不大,只要内存够你就尽量的开到顶。

如果说你是计算型的,就是你主要会消耗它的CPU的资源,意味着说CPU就是你的上限,你不能说内存足够,你就开很多的线程那个没用,因为CPU只算那么快,你开到1000 2000它也算不过来。

💻💻所以到底会开多少线程数,你其实要去分析说你的应用,到底是io密集型的,还是计算密集型的,去综合决定说,你的线程数是一个什么样的值,这个是多线程管理。

在这里插入图片描述

💻什么是NIO的模式和APR的模式

它们都是异步io的模式,差别在于NIO用的是Java来实现的,APR用的是原生库,大家知道Java的字节码的运行效率是低于原生库的,APR利用的是阿帕奇的,HTTP的原生库,来实现的异步io 这个就,意味着说你不能单纯的,装一个Tomcat就好了,你还需要先装阿帕奇的原生库,再装Tomcat才能跑出它的APR模式,所以它装起来会稍微麻烦一点,NIO的话还是基于Java来做的,所以说就不需要去装原生库,我们现在的新的版本的,默认方式是NIO。

在这里插入图片描述
Compute

BIO和NIO的差别

所谓"BIO"即我们前文所述的,指的是在线程中进行的I/O操作。因此,在其内部代码实现过程中,正如我们刚才所观察到的那个过程,客户端发出请求,在其Servlet容器的代码中,首先是一个名为"Acceptor"的对象。
在这里插入图片描述
该"Acceptor"对象接收到客户端的请求后,随即创建一个"SocketProcessor"。
在这里插入图片描述

大家应该对这个名字有所了解,它与网络通信有关,它打开了一个Socket,并将此对象传递给从线程池中获取的一个线程。
在这里插入图片描述

正如我们所提到的,我们使用线程池来控制线程数量的上限和下限。因此,它从线程池中取出一个空闲线程,并将"SocketProcessor"交给该线程,然后开始读取网络数据。

整个读取过程都在线程范围内完成。如果读取速度很慢,它将一直占用线程。当读取完成后,线程才会调用Servlet对象的service方法,然后根据请求类型调用其doDelete、doPut或doGet方法。读取完成后,它会生成一个HTTP响应,并依靠"SocketProcessor"将HTTP响应传回给前端。

无论是产生HTTP请求还是获取HTTP响应,整个过程都是在线程中进行的。直到最后完成并将其放回线程池,供下一个请求使用。因此,它被称为阻塞式I/O方式,也就是所谓的"Blocked IO"。之所以如此命名,是因为在线程的I/O过程中,该线程一直被占用。这是传统模式,默认情况下仍采用这种模式。

在这里插入图片描述
在这里插入图片描述

这种模式与传统模式的不同之处在于,I/O不在线程范围内。它是如何实现的呢?

前面提到,请求仍然由一个"Acceptor"处理,当请求到达时,也仍然是一个"Acceptor"。然而,在这种情况下,它不是创建一个"SocketProcessor",而是创建一个名为"NioChannel"的对象。
在这里插入图片描述

这是什么呢?它同样能够接收来自客户端的数据,并将其放入缓冲区中。关键是,当传输完成后,"NioChannel"将产生一个事件,表示数据已经读取完成。

因此,它会将"NioChannel"放入一个事件列表或队列中。无论前面有多少个请求过来,它都会为每一个请求产生一个"NioChannel",并将其放入列表中。此时与线程无关,它只是将其放入列表中。

然后,使用一个独立的"Poller"线程来轮询列表。这个轮询的作用是查看哪个通道已经读取完成。

在这里插入图片描述

一旦某个通道读取完成,后续操作与我们之前所描述的"BIO"相同。它仍然会创建一个"SocketProcessor",将读取完成的数据交给"SocketProcessor",然后将其交给一个线程。接下来的步骤与之前相同。

在这里插入图片描述

首先是读取过程。当线程获得"SocketProcessor"时,将数据从读取状态转换为HTTP请求时,此时没有延迟,因为数据已经准备好并放入缓冲区,所以可以直接进行解码,并将其转换为HTTP请求对象。然后执行后续操作,如调用service方法,根据请求类型调用doGet、doPut或doDelete方法。在获得HTTP响应后,它仍然会将其返回给"SocketProcessor",然后将其传回"NioChannel"。线程就完成了其使命,然后再次回到线程池中。

"NioChannel"会负责将数据传回给前端。整个I/O过程都发生在这个过程中,而不是在线程的范围内。线程中只有我们前面提到的部分,或者是这个部分,或者是那个部分,具体取决于实现方式。总之,前面的读取和后面的发送都不在该线程的范围内。
在这里插入图片描述
Compute

进入NIO时代或者APR时代。

APR也是异步的,只不过与NIO的异步方式不同。它是使用原生代码来实现的,因此速度更快,I/O速度也会更快。进入这个时代之后,当我们考虑线程池时,我们只考虑其中间部分的计算消耗。无论是在Tomcat的NIO还是APR上,我们实际上无法进行优化,因为当您进行数据库读写时,I/O会阻塞。

对于计算部分来说,它仍然会阻塞。如果想要进一步提高效率,我们只能放弃Servlet这种架构,转而使用Reactive Stack,完全采用函数式编程。通过将与数据库交互的这些慢速操作从代码中分离出来,您才有可能将这些慢速操作转换为事件响应方式。

将非慢速操作转换为可以直接在CPU上调度的方式,才能进一步提高性能。

技术的发展已经很明显,它是一步一步地将代码中与I/O相关的部分分离出来,无论是网络I/O还是本地I/O部分。只有在将这些部分从代码中分离出来之后,它才可能在多线程上进行更进一步的优化。如果不进行分离,实际上是不可能的。就目前而言,对于命令式编程来说,最优化的方式就是采用APR。其中实际上还包括了一些本地数据库的I/O操作。由于命令式编程,即Servlet堆栈,并未将这部分代码分离出来。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/115626.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++算法 —— 贪心(2)

文章目录 1、柠檬水找零2、将数组和减半的最少操作次数3、最大数4、摆动序列5、最长递增子序列6、递增的三元子序列7、最长连续递增子序列 1、柠檬水找零 860. 柠檬水找零 如果一开始顾客给了10块,那就直接结束。给了5块那就收下。之后每一个位置,都需要…

Rtthread源码分析<1>启动文件和链接脚本

启动文件和链接脚本 1)启动文件 ​ 启动文件里面使用的是汇编语言,汇编语言常常可以分为两个部分语法风格和而不同的toolchain有不同的汇编语法风格,通常分配unified 和 非 unified。常见的工具包有 ARM toolchains 和 GNU toolchains 。比…

一篇博客读懂顺序表 —— Sequence-List

目录 一、顺序表的初始定义 1.1新建头文件和源文件 1.2 SeqList.h 中的准备工作 二、顺序表的初始化与销毁 三、首尾插入元素 四、首尾删除元素 五、中间插入元素 六、中间删除元素 七、查找指定元素下标 八、源代码 一、顺序表的初始定义 1.1新建头文件和源文件 当我…

Swift语言配合HTTP写的一个爬虫程序

下段代码使用Embassy库编写一个Swift爬虫程序来爬取jshk的内容。我会使用proxy_host为duoip,proxy_port为8000的爬虫IP服务器。 使用Embassy库编写一个Swift爬虫程序可以实现从网页上抓取数据的功能。下面是一个简单的步骤: 1、首先,需要在X…

虹科示波器 | 汽车免拆检修 | 2010款江铃陆风X8车发动机怠速抖动、加速无力

一、故障现象 一辆2010款江铃陆风X8车,搭载4G6GS4N发动机,累计行驶里程约为20万km。该车在其他修理厂进行发动机大修,维修后试车,发动机怠速抖动、加速无力。用故障检测仪检测,发动机控制模块(ECM&#xff…

JumpServer开源堡垒机与万里安全数据库完成兼容性认证

近日,中国领先的开源软件提供商FIT2CLOUD飞致云宣布,JumpServer开源堡垒机已经与万里安全数据库软件GreatDB完成兼容性认证。针对产品的功能、性能、兼容性方面,经过双方共同测试,万里安全数据库软件(简称:…

Vue组件化开发,组件的创建,注册,使用,详解Vue,vm,VueComponent,vc

组件化开发 模块是指将一个大的js文件按照模块化拆分规则进行拆分成的每个js文件, 凡是采用模块方式开发的应用都可以称为模块化应用(组件包括模块) 传统方式开发的一个网页通常包括三部分: 结构(HTML)、样式(CSS)、交互(JavaScript) 关系纵横交织复杂,牵一发动全…

组件与Props:React中构建可复用UI的基石

目录 组件:构建现代UI的基本单位 Props:组件之间的数据传递 Props的灵活性:构建可配置的组件 组件间的通信:通过回调函数传递数据 总结: 组件:构建现代UI的基本单位 组件是前端开发中的关键概念之一。…

如何使用Ruby 多线程爬取数据

现在比较主流的爬虫应该是用python,之前也写了很多关于python的文章。今天在这里我们主要说说ruby。我觉得ruby也是ok的,我试试看写了一个爬虫的小程序,并作出相应的解析。 Ruby中实现网页抓取,一般用的是mechanize,使…

手机转接器实现原理,低成本方案讲解

USB-C PD协议里,SRC和SNK双方之间通过CC通信来协商请求确定充电功率及数据传输速率。当个设备需要充电时,它会发送消息去给适配器请求充电,此时充电器会回应设备的请求,并告知其可提供的档位功率,设备端会根据适配器端…

SpringBoot集成-阿里云对象存储OSS

文章目录 阿里云 OSS 介绍准备工作SpringBoot 集成 OSS 阿里云 OSS 介绍 阿里云对象存储 OSS (Object Storage Service),是一款海量、安全、低成本、高可靠的云存储服务。使用 OSS,你可以通过网络随时存储和调用包括文本、图片、…

单行自动横向滚动——css实现

效果 封装组件 <template><div ref"container" class"scroll-area"><divref"content":class"[isScroll ? scroll : no-scroll]":style"{ color: fontColor }">{{ content }}</div></div> &…

【2023-10-31】某钩招聘网站加密参数分析

声明:该专栏涉及的所有案例均为学习使用,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!如有侵权,请私信联系本人删帖! 文章目录 一、前言二、网站分析1.X-S-HEADER参数2.请求参数data3.响应机密值data一、前言 网址: aHR0cHM6Ly93d3cubGFnb3UuY29t…

11.Z-Stack协议栈使用

f8wConfig.cfg文件 选择信道、设置PAN ID 选择信道 #define DEFAULT_CHANLIST 0x00000800 DEFAULT_CHANLIST 表明Zigbee模块要工作的网络&#xff0c;当有多个信道参数值进行或操作之后&#xff0c;把结果作为 DEFAULT_CHANLIST值 对于路由器、终端、协调器的意义&#xff1…

【MySQL索引与优化篇】数据库的设计规范

数据库的设计规范 文章目录 数据库的设计规范1. 范式2. 键和相关属性的概念3. 第一范式4. 第二范式5. 第三范式6. 小结7. 反范式化7.1 概述7.2 反范式的新问题7.3 反范式适用场景 8. 巴斯范式9. 第四范式、第五范式和域键范式 1. 范式 在关系型数据库中&#xff0c;关于数据表…

免费获得临时域名/内网穿透

文章目录 Coplar 介绍Coplar 使用场景Coplar 使用 Coplar 介绍 》官网地址《 官网介绍&#xff1a; cpolar极点云: 公开一个本地Web站点至公网 只需一行命令&#xff0c;就可以将内网站点发布至公网&#xff0c;方便给客户演示。高效调试微信公众号、小程序、对接支付宝网关…

Jmeter之JSR223

一、JSR223组件 JSR是Java Specification Requests的缩写,意思是Java规范提案。JSR已成为Java界的一个重要标准. JSR223其实包含了有好几种组件,但是其用法都是一致的,并且都是执行一段代码&#xff0c;主要分类如下&#xff1a; JSR223 PreProcessor JSR223 Timer JSR223 S…

使用Gorm进行CRUD操作指南

使用GORM在Go中创建、读取、更新和删除记录的逐步教程 在数据库管理中&#xff0c;CRUD操作是应用程序的支柱&#xff0c;它们使数据的创建、检索、更新和删除成为可能。强大的Go对象关系映射库GORM通过抽象SQL语句的复杂性&#xff0c;使这些操作变得轻松。本文将作为您全面指…

基于ASP.NET MVC + Bootstrap的仓库管理系统

基于ASP.NET MVC Bootstrap的仓库管理系统。源码亲测可用&#xff0c;含有简单的说明文档。 适合单仓库&#xff0c;基本的仓库入库管理&#xff0c;出库管理&#xff0c;盘点&#xff0c;报损&#xff0c;移库&#xff0c;库位等管理&#xff0c;有着可视化图表。 系统采用Bo…

Linux学习笔记之二(环境变量)

Linux learning note 1、环境变量1.1、修好PATH环境变量 1、环境变量 环境变量(environment variables)即系统运行的一些环境参数。主要的环境变量有以下这些&#xff1a; PATH&#xff1a;决定了系统查找可执行文件的目录范围。HOME&#xff1a;指定当前用户的主目录路径。U…