Map 是键值对,键 Key 是唯一不能重复的,一个键对应一个值,值可以重复。
TreeMap 可以保证顺序,HashMap 不保证顺序,即为无序的。
Map 中可以将 Key 和 Value 单独抽取出来,其中 KeySet()方法可以将所有的 keys 抽取成一个 Set,而 Values()方法可以将 map 中所有的 values 抽取成一个集合。
内存泄漏(memoryleak),是指应用程序在申请内存后,无法释放已经申请的内存空间,一次内存泄漏危害可以忽略,但如果任其发展最终会导致内存溢出(outofmemory)。如读取文件后流要进行及时的关闭以及对数据库连接的释放。
内存溢出(outofmemory)是指应用程序在申请内存时,没有足够的内存空间供其使用。如我们在项目中对于大批量数据的导入,采用分批量提交的方式。
Class.forName(className) 方 法 , 内 部 实 际 调 用 的 方 法 是Class.forName(className,true,classloader); 第 2 个 boolean 参数表示类是否需要初始化, Class.forName(className)默认是需要初始化。
一旦初始化,就会触发目标对象的 static 块代码执行, static 参数也会被再次初始化 ,ClassLoader.loadClass(className) 方 法 , 内 部 实 际 调 用 的 方 法 是ClassLoader.loadClass(className,false);第 2 个 boolean 参数表示目标对象是否进行链接, false 表示不进行链接,由上面介绍可以,不进行链接意味着不进行包括初始化等一些列步骤,那么静态块和静态对象就不会得到执行。
String | 字符串常量 | 不可变 | 使用字符串拼接时是不同的 2 个空间 | |
---|---|---|---|---|
StringBuffer | 字符串变量 | 可变 | 线程安全 | 字符串拼接直接在字符串后追加 |
StringBuilder 字符串变量 | 可变 | 非线程安全 | 字符串拼接直接在字符串后追加 |
byte | short | int | long | float | double | char | boolean |
---|---|---|---|---|---|---|---|
1个字节8位 | 2个字节16 位 | 4个字节32位 | 8个字节64 位 | 4个字节32位 | 8个字节64位 | 2个字节16位 | 8分之1个字节1位 |
从高到低:public、protected、 没有访问修饰符(默认)、private
抽象类的意义可以用三句话来概括:
对象的状态在构造函数之后都不能被修改,任何修改应该通过创建一个新对象来实现。
所有的对象属性应该都设置为 final。
对象创建要正确,例如:对象的应用不能在构造函数中被泄露出去。
对象要设置为 final,确保不要继承的 Class 修改了 immutability 特性。
静态属性:随着类的加载而加载,该属性不再属于某个对象,而是属于整个类;
静态方法:直接用类名调用,静态方法中不能访问非静态成员变量;
静态类:不能直接创建对象,也不能被继承。
break;
使用别名。
内部类可以很好的实现隐藏,一般的非内部类,是不允许有 private 与 protected 权限的,但内部类可以。
内部类拥有外部类的所有元素的访问权限。
可以实现多重继承。
可以避免修改接口而实现同一个类中两种同名方法的调用。
浅拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象.换言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象。
深拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象,换言之,深拷贝把要复制的对象所引用的对象都复制了一遍。
静态方法和实例方法的区别主要体现在两个方面:
允许不同类的对象对同一消息做出响应,即同一消息可以根据发送对象的不同而采用多种不同的行为方式(发送消息就是函数调用)。
抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。
继承:继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类(超类、基类);得到继承信息的类被称为子类(派生类)。继承让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要手段。
封装:通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。面向对象的本质就是将现实世界描绘成一系列完全自治、封闭的对象。我们在类中编写的方法就是对实现细节的一种封装;我们编写一个类就是对数据和数据操作的封装。可以说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口。
多态性:多态性是指允许不同子类型的对象对同一消息作出不同的响应。简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。多态性分为编译时的多态性和运行时的多态性。如果将对象的方法视为对象向外界提供的服务,那么运行时的多态性可以解释为:当 A 系统访问 B 系统提供的服务时,B 系统有多种提供服务的方式,但一切对 A 系统来说都是透明的。方法重载(overload)实现的是编译时的多态性(也称为前绑定),而方法重写(override)实现的是运行时的多态性(也称为后绑定)。运行时的多态是面向对象最精髓的东西,要实现多态需要做两件事:1. 方法重写(子类继承父类并重写父类中已有的或抽象的方法);2. 对象造型(用父类型引用引用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为)
break 和 continue 都是用来控制循环的语句。
下面列出了它们的区别:
虽然 JDK 1.7 不像 JDK 5 和 8 一样的大版本,但是,还是有很多新的特性:
Java 8 在 Java 历史上是一个开创新的版本,下面 JDK 8 中 5 个主要的特性:
传统 IO 一般是一个线程等待连接,连接过来之后分配给 processor 线程,processor 线程与通道连接后如果通道没有数据过来就会阻塞(线程被动挂起)不能做别的事情。NIO 则不同,首先,在 selector 线程轮询的过程中就已经过滤掉了不感兴趣的事件,其次,在 processor 处理感兴趣事件的 read 和 write 都是非阻塞操作即直接返回的,线程没有被挂起。
传统 IO 的管道是单向的,NIO 的管道是双向的。
两者都是同步的,也就是 java 程序亲力亲为的去读写数据,不管传统 io 还是 nio 都需要 read 和 write 方法,这些都是 java 程序调用的而不是系统帮我们调用的,nio2.0 里这点得到了改观,即使用异步非阻塞 AsynchronousXXX 四个类来处理。
IO 操作包括:对硬盘的读写、对 socket 的读写以及外设的读写。
当用户线程发起一个 IO 请求操作(本文以读请求操作为例),内核会去查看要读取的数据是否就绪,对于阻塞 IO 来说,如果数据没有就绪,则会一直在那等待,直到数据就绪;对于非阻塞 IO 来说,如果数据没有就绪,则会返回一个标志信息告知用户线程当前要读的数据没有就绪。当数据就绪之后,便将数据拷贝到用户线程,这样才完成了一个完整的 IO 读请求操作,也就是说一个完整的 IO 读请求操作包括两个阶段:
那么阻塞(blocking IO)和非阻塞(non-blocking IO)的区别就在于第一个阶段,如果数据没有就绪,在查看数据是否就绪的过程中是一直等待,还是直接返回一个标志信息。
Java 中传统的 IO 都是阻塞 IO,比如通过 socket 来读数据,调用 read()方法之后,如果数据没有就绪,当前线程就会一直阻塞在 read 方法调用那里,直到有数据才返回;而如果是非阻塞 IO 的话,当数据没有就绪,read()方法应该返回一个标志信息,告知当前线程数据没有就绪,而不是一直在那里等待。
Servlet 生命周期可以分成四个阶段:加载和实例化、初始化、服务、销毁。
当客户第一次请求时,首先判断是否存在 Servlet 对象,若不存在,则由 Web 容器创建对象,而后调用 init()方法对其初始化,此初始化方法在整个 Servlet 生命周期中只调用一次。完成 Servlet 对象的创建和实例化之后,Web 容器会调用 Servlet 对象的 service()方法来处理请求。
Web 容器关闭或者 Servlet 对象要从容器中被删除时,会自动调用 destory()方法。
JSP 的 9 大内置对象:
第一步:Class.forName() 加载数据库连接驱动。
第二步:DriverManager.getConnection() 获取数据连接对象。
第三步:根据 SQL 获取 sql 会话对象,有 2 种方式:Statement 和 PreparedStatement。
第四步:执行 SQL,执行 SQL 前如果有参数值就设置参数值 setXXX()。
第五步:处理结果集。
第六步:关闭结果集、关闭会话、关闭连接。
PreparedStatement 接口继承 Statement,PreparedStatement 实例包含已编译的 SQL 语句,所以其执行速度要快于 Statement 对象。
作为 Statement 的子类, PreparedStatement 继承了 Statement 的所有功能。三种方法 execute、 executeQuery 和 executeUpdate 已被更改以使之不再需要参数。
在 JDBC 应用中,多数情况下使用 PreparedStatement,原因如下:
代码的可读性和可维护性。Statement 需要不断地拼接,而 PreparedStatement 不会。
PreparedStatement 尽最大可能提高性能。DB 有缓存机制,相同的预编译语句再次被调用不会再次需要编译。
最重要的一点是极大地提高了安全性。Statement 容易被 SQL 注入,而 PreparedStatement传入的内容不会和 sql 语句发生任何匹配关系。
1、数据库连接是一件费时的操作,连接池可以使多个操作共享一个连接。
2、数据库连接池的基本思想就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从 “缓冲池” 中取出一个,使用完毕之后再放回去。我们可以通过设定连接池最大连接数来防止系统无尽的与数据库连接。更为重要的是我们可以通过连接池的管理机制监视数据库的连接的数量、使用情况,为系统开发,测试及性能调整提供依据。
3、使用连接池是为了提高对数据库连接资源的管理。
1、Statement 的 execute(String query)方法用来执行任意的 SQL 查询,如果查询的结果是一个ResultSet,这个方法就返回 true。如果结果不是 ResultSet,比如 insert 或者 update 查询,它就会返回 false 。我们可以通过它的 getResultSet 方法来获取 ResultSet ,或者通过getUpdateCount()方法来获取更新的记录条数。
2、Statement 的 executeQuery(String query) 接口用来执行 select 查询,并且返回 ResultSet,即使查询不到记录返回的 ResultSet 也不会为 null。我们通常使用 executeQuery 来执行查询语句,这样的话如果传进来的是 insert 或者 update 语句的话,它会抛出错误信息为 “executeQuery method can not be used for update” 的 java.util.SQLException。
3、Statement 的 executeUpdate(String query) 方法用来执行 insert、update 和 delete(DML)语句,或者什么也不返回,对于 DDL 语句,返回值是 int 类型,如果是 DML 语句的话,它就是更新的条数,如果是 DDL 的话,就返回 0。只有当你不确定是什么语句的时候才应该使用 execute() 方法,否则应该使用 executeQuery 或者 executeUpdate 方法。
在查询数据库后会返回一个 ResultSet,它就像是查询结果集的一张数据表。ResultSet 对象维护了一个游标,指向当前的数据行。开始的时候这个游标指向的是第一行。如果调用了 ResultSet 的 next()方法游标会下移一行,如果没有更多的数据了,next()方法会返回 false。可以在 for 循环中用它来遍历数据集。
默认的 ResultSet 是不能更新的,游标也只能往下移。也就是说你只能从第一行到最后一行遍历一遍。不过也可以创建可以回滚或者可更新的 ResultSet,当生成 ResultSet 的 Statement 对象要关闭或者重新执行或是获取下一个 ResultSet 的时候,ResultSet 对象也会自动关闭。可以通过 ResultSet 的 getter 方法,传入列名或者从 1 开始的序号来获取列数据。
Servlet 是使用 Java Servlet 应用程序接口(API)及相关类和方法的 Java 程序,所有的 Servlet 都必须要实现的核心接口是 javax.servlet.servlet。每一个 servlet 都必须要直接或者间接实现这个接口,或者继承javax.servlet.GenericServlet 或 javax.servlet.HTTPServlet。Servlet 主要用于处理客户端传来的 HTTP 请求,并返回一个响应。
doGet:GET 方法会把名值对追加在请求的 URL 后面。因为 URL 对字符数目有限制,进而限制了用在客户端请求的参数值的数目。并且请求中的参数值是可见的,因此,敏感信息不能用这种方式传递。
doPOST:POST 方法通过把请求参数值放在请求体中来克服 GET 方法的限制,因此,可以发送的参数的数目是没有限制的。最后,通过 POST 请求传递的敏感信息对外部客户端是不可见的。
JSP 共有以下 6 种基本动作:
jsp:include:在页面被请求的时候引入一个文件。
jsp:useBean:寻找或者实例化一个 JavaBean。
jsp:setProperty:设置 JavaBean 的属性。
jsp:getProperty:输出某个 JavaBean 的属性。
jsp:forward:把请求转到一个新的页面。
jsp:plugin:根据浏览器类型为 Java 插件生成 OBJECT 或 EMBED 标记。
page:针对当前页面的指令。
include:包含另一个页面。
taglib:定义和访问自定义标签。
request、session、application、cookie
动态 include 用于 jsp:include 动作实现
静态 include 用于 include 伪码实现,不会检查所含文件的变化,适用于包含静态页面 <%@include file=“include.html”%>。
JSP 中的四种作用域包括 page、request、session 和 application,具体来说:
page 代表与一个页面相关的对象和属性。
request 代表与 Web 客户机发出的一个请求相关的对象和属性。一个请求可能跨越多个页面,涉及多个 Web 组件;需要在页面显示的临时数据可以置于此作用域。
session 代表与某个用户与服务器建立的一次会话相关的对象和属性。跟某个用户相关的数据应该放在用户自己的 session 中。
application 代表与整个 Web 应用程序相关的对象和属性,它实质上是跨越整个 Web 应用程序,包括多个页面、请求和会话的一个全局作用域。
1、硬件环境不同:
C/S 一般建立在专用的网络上, 小范围里的网络环境, 局域网之间再通过专门服务器提供连接和数据交换服务.
B/S 建立在广域网之上的, 不必是专门的网络硬件环境,例与电话上网, 租用设备. 信息自己管理. 有比 C/S 更强的适应范围, 一般只要有操作系统和浏览器就行
2、对安全要求不同
C/S 一般面向相对固定的用户群, 对信息安全的控制能力很强. 一般高度机密的信息系统采用 C/S 结构适宜. 可以通过 B/S 发布部分可公开信息.
B/S 建立在广域网之上, 对安全的控制能力相对弱, 可能面向不可知的用户。
3、对程序架构不同
C/S 程序可以更加注重流程, 可以对权限多层次校验, 对系统运行速度可以较少考虑. B/S 对安全以及访问速度的多重的考虑, 建立在需要更加优化的基础之上. 比 C/S 有更高的要求 B/S 结构的程序架构是发展的趋势, 从 MS 的.Net 系列的 BizTalk 2000 Exchange 2000 等, 全面支持网络的构件搭建的系统. SUN 和 IBM 推的 JavaBean 构件技术等,使 B/S 更加成熟.
4、软件重用不同C/S 程序可以不可避免的整体性考虑, 构件的重用性不如在 B/S 要求下的构件的重用性
好.B/S 对的多重结构,要求构件相对独立的功能. 能够相对较好的重用.就入买来的餐桌可以再利用,而不是做在墙上的石头桌子
5、系统维护不同
C/S 程序由于整体性, 必须整体考察, 处理出现的问题以及系统升级. 升级难. 可能是再做一个全新的系统
B/S 构件组成,方面构件个别的更换,实现系统的无缝升级. 系统维护开销减到最小.用户从网上自己下载安装就可以实现升级.
6、处理问题不同
C/S 程序可以处理用户面固定, 并且在相同区域, 安全要求高需求, 与操作系统相关.应该都是相同的系统
B/S 建立在广域网上, 面向不同的用户群, 分散地域, 这是 C/S 无法作到的. 与操作系统平台关系最小.
7、用户接口不同
C/S 多是建立的 Window 平台上,表现方法有限,对程序员普遍要求较高 B/S 建立在浏览器上, 有更加丰富和生动的表现方式与用户交流. 并且大部分难度减低,
减低开发成本.
8、信息流不同
C/S 程序一般是典型的中央集权的机械式处理, 交互性相对低
B/S 信息流向可变化, B-B B-C B-G 等信息、流向的变化, 更像交易中心。
Web 容器加载 Servlet 并将其实例化后,Servlet 生命周期开始,容器运行其 init 方法进行 Servlet 的初始化,请求到达时运行其 service 方法,service 方法自动派遣运行与请求对应的 doXXX 方法(doGet,doPost)等,当服务器决定将实例销毁的时候调用其 destroy 方法。
与 cgi 的区别在于 servlet 处于服务器进程中,它通过多线程方式运行其 service 方法,一个实例可以服务于多个请求,并且其实例一般不会销毁,而 CGI 对每个请求都产生新的进程,服务完成后就销毁,所以效率上低于 servlet。
针对于重复提交的整体解决方案:
1、用 redirect(重定向)来解决重复提交的问题。
2、点击一次之后,按钮失效。
3、通过 loading (Loading 原理是在点击提交时,生成 Loading 样式,在提交完成之后隐藏该样式)。
4、自定义重复提交过滤器。
1、获取请求参数 getParameter()。
2、获取当前 Web 应用的虚拟路径 getContextPath。
3、转发 getRequestDispatcher(路径).forward(request,response)。
4、它还是一个域对象。
1、乱码的根本原因:
浏览器的编码方式 UTF-8 和 服务器的解码方式 ISO-859-1 不一样。
2、解决方法:
(1)第一种方式 使用 URLEncoder 和 URLDecoder 两个类编解码。先以 iso-8895-1 进行编码,然后再以 utf-8 进行解码。
(2)第二种方式 使用 String 类的方法进行编解码。
(3)第三种方式 更改 server.xml 配置文件,GET 请求是在 URL 地址栏中传递请求参数的,它会被 Tomcat 服务器自动解码,而 Tomcat 服务器默认的字符集也是 ISO-8859-1,所以我们需要修改 Tomcat 服务器的字符集为 UTF-8。
1、post 请求方式乱码的原因是:因为 post 是以二进制流的形式发送到的服务器,服务器收到数据后,默认以 iso-8859-1 进行编码。
2、post 请求乱码解决,只需要在获取请求参数之前调用 request.setCharacterEncoding(“UTF-8”) 方法设置字符集即可。
1、原因:由服务器编码,默认使用 ISO-8859-1 进行编码,由浏览器解码,默认使用 GBK 进行解码。
2、解决方案:
方法 1:设置响应头
response.setHeader(“Content-Type”,“text/html;charset=utf-8”)。
方法 2:设置响应的内容类型
response.setContentType(“text/html;charset=utf-8”),通过这种方式可以在响应头中告诉浏览器响应体的编码方式是 UTF-8,同时服务器也会采用该字符集进行编码但需要注意的是,两种方法一定要在 response.getWriter()之前进行。
1、Cookie 是明文的,不安全。
2、不同的浏览器对 Cookie 对象的数量和大小有限制。
3、Cookie 对象携带过多费流量。
4、Cookie 对象中的 value 值只能是字符串,不能放对象网络中传递的数据,只能是字符串。
1、在服务器端创建 Session 对象,该对象有一个全球唯一的 ID。
2、在创建 Session 对象的同时创建一个特殊的 Cookie 对象,该 Cookie 对象的名字是 JSESSIONID,该 Cookie 对象的 value 值是 Session 对象的那个全球唯一的 ID,并且会将这个特殊的 Cookie 对象携带发送给浏览器。
3、以后浏览器再发送请求就会携带这个特殊的 Cookie 对象。
4、服务器根据这个特殊的 Cookie 对象的 value 值在服务器中寻找对应的 Session 对象,以此区分不同的用户。
1、Session 与 session 域中的对象一起从内存中被序列化到硬盘上的过程我们称为钝化,服务器关闭时会发生钝化。
2、Session 与 session 域中的对象一起从硬盘上反序列化到内存中的过程我们称为活化,服务器再次开启时会发生活化。
3、要保证 session 域中的对象能和 Session 一起被钝化和活化,必须保证对象对应的类实现 Serializable 接口。
Filter 接口中有一个 doFilter 方法,当我们编写好 Filter,并配置对哪个 web 资源进行拦截后, WEB 服务器每次在调用 web 资源的 service 方法之前,都会先调用一下 filter 的 doFilter 方法,因此,在该方法内编写代码可以达到如下目的:
调用目标资源之前,让一段代码执行。
是否调用目标资源(即是否让用户访问 web 资源),调用目标资源之后,让一段代码执行。
web 服务器在调用 doFilter 方法时,会传递一个 filterChain 对象进来,filterChain 对象是filter 接口中最重要的一个对象,它也提供了一个 doFilter 方法,开发人员可以根据需求决定是否调用此方法,调用该方法,则 web 服务器就会调用 web 资源的 service 方法,即 web 资源就会被访问,否则 web 资源不会被访问。
在一个 web 应用中,可以开发编写多个 Filter,这些 Filter 组合起来称之为一个 Filter 链。web服务器根据 Filter 在 web.xml 文件中的注册顺序,决定先调用哪个 Filter,当第一个 Filter 的 doFilter 方法被调用时,web 服务器会创建一个代表 Filter 链的 FilterChain 对象传递给该方法,在 doFilter 方法中,开发人员如果调用了 FilterChain 对象的 doFilter 方法,则 web 服务器会检查 FilterChain 对象中是否还有 filter,如果有,则调用第 2 个 filter,如果没有,则调用目标资源。
按监听的对象划分:servlet2.4 规范定义的事件有三种:
1、用于监听应用程序环境对象(ServletContext)的事件监听器。
2、用于监听用户会话对象(HttpSession)的事件监听器。
3、用于监听请求消息对象(ServletRequest)的事件监听器,按监听的事件类项划分为:
(1)用于监听域对象自身的创建和销毁的事件监听器;
(2)用于监听域对象中的属性的增加和删除的事件监听器;
(3)用于监听绑定到 HttpSession 域中的某个对象的状态的事件监听器,在一个 web 应用程序的整个运行周期内, web 容器会创建和销毁三个重要的对象,即:ServletContext,HttpSession,ServletRequest。
启动的顺序为:listener->Filter->servlet,简单记为:理(Listener)发(Filter)师(servlet),执行的顺序不会因为三个标签在配置文件中的先后顺序而改变。
第一:程序计数器(PC,Program Counter Register)。在 JVM 规范中,每个线程都有它自己的程序计数器,并且任何时间一个线程都只有一个方法在执行,也就是所谓的当前方法。程序计数器会存储当前线程正在执行的 Java 方法的 JVM 指令地址;或者,如果是在执行本地方法,则是未指定值(undefined)。(唯一不会抛出 OutOfMemoryError)。
第二:Java 虚拟机栈(Java Virtual Machine Stack),早期也叫 Java 栈。每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧(Stack Frame),对应着一次次的 Java 方法调用。
前面谈程序计数器时,提到了当前方法;同理,在一个时间点,对应的只会有一个活动的栈帧,通常叫作当前帧,方法所在的类叫作当前类。如果在该方法中调用了其他方法,对应的新的栈帧会被创建出来,成为新的当前帧,一直到它返回结果或者执行结束。JVM 直接对 Java 栈的操作只有两个,就是对栈帧的压栈和出栈,栈帧中存储着局部变量表、操作数(operand)栈、动态链接、方法正常退出或者异常退出的定义等。
第三:堆(Heap),它是 Java 内存管理的核心区域,用来放置 Java 对象实例,几乎所有创建的 Java 对象实例都是被直接分配在堆上。堆被所有的线程共享,在虚拟机启动时,我们指定的“Xmx”之类参数就是用来指定最大堆空间等指标( 编译器通过逃逸分析,确定对象是在栈上分配还是在堆上分配),理所当然,堆也是垃圾收集器重点照顾的区域,所以堆内空间还会被不同的垃圾收集器进行进一步的细分,最有名的就是新生代、老年代的划分。
第四:方法区(Method Area)。这也是所有线程共享的一块内存区域,用于存储所谓的元(Meta)数据,例如类结构信息,以及对应的运行时常量池、字段、方法代码等,由于早期的 Hotspot JVM 实现,很多人习惯于将方法区称为永久代(Permanent Generation),Oracle JDK 8 中将永久代移除,同时增加了元数据区(Metaspace)。
第五:运行时的常量池(Run-Time Constant Pool),这是方法区的一部分。如果仔细分析过反编译的类文件结构,你能看到版本号、字段、方法、超类、接口等各种信息,还有一项信息就是常量池。Java 的常量池可以存放各种常量信息,不管是编译期生成的各种字面量,还是需要在运行时决定的符号引用,所以它比一般语言的符号表存储的信息更加宽泛。
第六:本地方法栈(Native Method Stack),它和 Java 虚拟机栈是非常相似的,支持对本地方法的调用,也是每个线程都会创建一个,在 Oracle Hotspot JVM 中,本地方法栈和 Java 虚拟机栈是在同一块儿区域,这完全取决于技术实现的决定,并未在规范中强制。
Java 虚拟机是一个可以执行 Java 字节码的虚拟机进程,Java 源文件被编译成能被 Java 虚拟机执行的字节码文件, Java 被设计成允许应用程序可以运行在任意的平台,而不需要程序员为每一个平台单独重写或者是重新编译,Java 虚拟机让这个变为可能,因为它知道底层硬件平台的指令长度和其他特性。
1、在 Java 中采取了 可达性分析法
通过一系列的“GC Roots”对象作为起点进行搜索,如果在“GC Roots”和一个对象之间没有可达路径,则称该对象是不可达的,不过要注意的是被判定为不可达的对象不一定就会成为可回收对象。被判定为不可达的对象要成为可回收对象必须至少经历两次标记过程,如果在这两次标记过程中仍然没有逃脱成为可回收对象的可能性,则基本上就真的成为可回收对象了。
2、虚拟机栈中引用的对象、方法区类静态属性引用的对象、方法区常量池引用的对象、本地方法栈 JNI 引用的对象。
1)程序调用 System.gc 时可以触发;
2)系统自身来决定 GC 触发的时机。
1、虚拟机栈中引用的对象;
2、方法区中类静态属性引用的对象;
3、方法区中常量引用的对象;
4、本地方法栈中引用的对象。
Java 堆 = 老年代 + 新生代 新生代 = Eden + S0 + S1
当 Eden 区的空间满了,Java 虚拟机会触发一次 Minor GC,以收集新生代的垃圾,存活下来的对象,则会转移到 Survivor 区。
大对象(需要大量连续内存空间的 Java 对象,如那种很长的字符串)直接进入老年态;如果对象在 Eden 出生,并经过第一次 Minor GC 后仍然存活,并且被 Survivor 容纳的话,年龄设为 1,每熬过一次 Minor GC,年龄+1,若年龄超过一定限制(15),则被晋升到老年态。即长期存活的对象进入老年态。
老年代满了而无法容纳更多的对象,Minor GC 之后通常就会进行 Full GC,Full GC 清理整个内存堆 – 包括年轻代和年老代。
Major GC 发生在老年代的 GC,清理老年区,经常会伴随至少一次 Minor GC,比 Minor GC 慢 10 倍以上。
双亲委派模型工作过程是:
如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(即 ClassNotFoundException),子类加载器才会尝试自己去加载。
因为为了防止内存中出现多份同样的字节码。
打破双亲委派机制不仅要继承 ClassLoader 类,而且还要重写 loadClass 和 findClass 方法。
(1)新生代设置过小
一是新生代 GC 次数非常频繁,增大系统消耗
二是导致大对象直接进入旧生代,占据了旧生代剩余空间,诱发 Full GC。
(2)新生代设置过大
一是新生代设置过大会导致旧生代过小(堆总量一定),从而诱发 Full GC
二是新生代 GC 耗时大幅度增加
(3)Survivor 设置过小
导致对象从 eden 直接到达旧生代
(4)Survivor 设置过大
导致 eden 过小,增加了 GC 频率一般说来新生代占整个堆 1/3 比较合适 GC 策略的设置方式。
1)吞吐量优先 可由-XX:GCTimeRatio=n 来设置
2)暂停时间优先 可由-XX:MaxGCPauseRatio=n 来设置
Minor GC 触发条件:
当 Eden 区满时,触发 Minor GC。
Full GC 触发条件:
(1)调用 System.gc 时,系统建议执行 Full GC,但是不必然执行。
(2)老年代空间不足。
(3)方法区空间不足。
(4)通过 Minor GC 后进入老年代的平均大小大于老年代的可用内存。
(5)由 Eden 区、From Space 区向 To Sp3ace 区复制时,对象大小大于 To Space 可存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小
1、设定堆内存大小-Xmx:堆内存最大限制。
2、设定新生代大小。 新生代不宜太小,否则会有大量对象涌入老年代
-XX:NewSize:新生代大小 -XX:NewRatio 新生代和老生代占比
-XX:SurvivorRatio:伊甸园空间和幸存者空间的占比3、设定垃圾回收器 年轻代用 -XX:+UseParNewGC 年老代用-XX:+UseConcMarkSweepGC
Java 内存模型定义了多线程之间共享变量的可见性以及如何在需要的时候对共享变量进行同步。JMM 内部的实现通常是依赖于所谓的内存屏障,通过禁止某些重排序的方式,提供内存可见性保证,也就是实现了各种 happen-before 规则。
与 JVM 内存模型不同。
Java 内存模型即 Java Memory Model,简称 JMM。JMM 定义了 Java 虚拟机(JVM)在计算机内存(RAM)中的工作方式。JVM 是整个计算机虚拟模型,所以 JMM 是隶属于 JVM 的。
Java 内存模型定义了多线程之间共享变量的可见性以及如何在需要的时候对共享变量进行同步。
Java 线程之间的通信采用的是过共享内存模型,这里提到的共享内存模型指的就是 Java 内存模型(简称 JMM),JMM 决定一个线程对共享变量的写入何时对另一个线程可见。从抽象的角度来看,JMM 定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。
最主要的区别就是栈内存用来存储局部变量和方法调用。
而堆内存用来存储 Java 中的对象。无论是成员变量,局部变量,还是类变量,它们指向的对象都存储在堆内存中。
独有还是共享
栈内存归属于单个线程,每个线程都会有一个栈内存,其存储的变量只能在其所属线程中可见,即栈内存可以理解成线程的私有内存。
而堆内存中的对象对所有线程可见。堆内存中的对象可以被所有线程访问。异常错误如果栈内存没有可用的空间存储方法调用和局部变量,JVM 会抛出
java.lang.StackOverFlowError。
而如果是堆内存没有可用的空间存储生成的对象,JVM 会抛出 java.lang.OutOfMemoryError。
空间大小
栈的内存要远远小于堆内存,如果你使用递归的话,那么你的栈很快就会充满。如果递归没有及时跳出,很可能发生 StackOverFlowError 问题。
GC 最基础的算法有三种: 标记 -清除算法、复制算法、标记-压缩算法,我们常用的垃圾回收器一般都采用分代收集算法。
(1)标记 -清除算法,“标记-清除”(Mark-Sweep)算法,如它的名字一样,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。
(2)复制算法,“复制”(Copying)的收集算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
(3)标记-压缩算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存分代收集算法,“分代收集”(Generational Collection)算法,把 Java 堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。
通常我们定义一个基本数据类型的变量,一个对象的引用,还有就是函数调用的现场保存都使用 JVM 中的栈空间;而通过 new 关键字和构造器创建的对象则放在堆空间,堆是垃圾收集器管理的主要区域,由于现在的垃圾收集器都采用分代收集算法,所以堆空间还可以细分为新生代和老生代,再具体一点可以分为 Eden、Survivor(又可分为 From Survivor 和 To Survivor)、Tenured;方法区和堆都是各个线程共享的内存区域,用于存储已经被 JVM 加载的类信息、常量、静态变量、JIT 编译器编译后的代码等数据;程序中的字面量(literal)如直接书写的 100、”hello”和常量都是放在常量池中,常量池是方法区的一部分,。栈空间操作起来最快但是栈很小,通常大量的对象都是放在堆空间,栈和堆的大小都可以通过 JVM 的启动参数来进行调整,栈空间用关了的话则会引发 StackOverflowError,而堆和常量池空间不足的话则会引发 OutOfMemoryError。
类的加载指的是将类的.class 文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个 java.lang.Class 对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的 Class 对象,Class 对象封装了类在方法区内的数据结构,并且向 Java 程序员提供了访问方法区内的数据结构的接口。
1、启动类加载器:Bootstrap ClassLoader,负责加载存放在 JDK\jre\lib(JDK 代表 JDK 的安装目录,下同)下,或被-Xbootclasspath 参数指定的路径中的,并且能被虚拟机识别的类库。
2、扩展类加载器:Extension ClassLoader,该加载器由 sun.misc.Launcher$ExtClassLoader 实现,它负责加载 DK\jre\lib\ext 目录中,或者由 java.ext.dirs 系统变量指定的路径中的所有类库(如javax.开头类),开发者可以直接使用扩展类加载器。
3、应用程序类加载器:Application ClassLoader,该类加载器由 sun.misc.Launcher$AppClassLoader来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器。
1、JVM 遇到一条新建对象的指令时首先去检查这个指令的参数是否能在常量池中定义到一个类的符号引用,然后加载这个类(类加载过程在后边讲)。
2、为对象分配内存。一种办法“指针碰撞”、一种办法“空闲列表”,最终常用的办法是“本地线程缓冲分配(TLAB)”。
3、将除对象头外的对象内存空间初始化为 0。
4、对对象头进行必要设置。
1、加载,查找并加载类的二进制数据,在 Java 堆中也创建一个 java.lang.Class 类的对象。
2、连接,连接又包含三块内容:验证、准备、初始化。
(1)验证,文件格式、元数据、字节码、符号引用的验证。
(2)准备,为类的静态变量分配内存,并将其初始化为默认值。
(3)解析,把类中的符号引用转换为直接引用。
3、初始化,为类的静态变量赋予正确的初始值。
4、使用,new 出对象程序中使用。
5、卸载,执行垃圾回收。
1、Serial 收集器,串行收集器是最古老,最稳定以及效率高的收集器,可能会产生较长的停顿,只使用一个线程去回收。
2、ParNew 收集器,ParNew 收集器其实就是 Serial 收集器的多线程版本。
3、Parallel 收集器,Parallel Scavenge 收集器类似 ParNew 收集器,Parallel 收集器更关注系统的吞吐量。
4、Parallel Old 收集器,Parallel Old 是 Parallel Scavenge 收集器的老年代版本,使用多线程和“标记-整理”算法。
5、CMS 收集器,CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。
6、G1 收集器,G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器,以极高概率满足 GC 停顿时间要求的同时还具备高吞吐量性能特征。
Sun JDK 监控和故障处理命令主要有 jps、jstat、jmap、jhat、jstack、jinfo。
1、jps,JVM Process Status Tool 用于显示指定系统内所有的 HotSpot 虚拟机进程。
2、jstat,JVM statistics Monitoring 用于监视虚拟机运行时状态信息的命令,它可以显示出虚拟机进程中的类装载、内存、垃圾收集、JIT 编译等运行数据。
3、jmap,JVM Memory Map 命令用于生成 heap dump 文件。
4、jhat,JVM Heap Analysis Tool 命令是与 jmap 搭配使用,用来分析 jmap 生成的 dump,jhat 内置了一个微型的 HTTP/HTML 服务器,生成 dump 的分析结果后,可以在浏览器中查看。
5、jstack,用于生成 java 虚拟机当前时刻的线程快照。
6、jinfo,JVM Configuration info 这个命令作用是实时查看和调整虚拟机的运行参数。
常用调优工具分为两类,jdk 自带监控工具:jconsole 和 jvisualvm,第三方有:MAT(Memory AnalyzerTool)、GChisto。
1、jconsole,Java Monitoring and Management Console 是从 java5 开始,在 JDK 中自带的 java 监控和管理控制台,用于对 JVM 中内存,线程和类等的监控。
2、jvisualvm,jdk 自带全能工具,可以分析内存快照、线程快照,监控内存变化、GC 变化等。
3、MAT,Memory Analyzer Tool,一个基于 Eclipse 的内存分析工具,是一个快速、功能丰富的 Java heap 分析工具,它可以帮助我们查找内存泄漏和减少内存消耗4、GChisto,一款专业分析 gc 日志的工具。
JVM 中类的加载是由类加载器(ClassLoader)和它的子类来实现的,Java 中的类加载器是一个重要的 Java 运行时系统组件,它负责在运行时查找和装入类文件中的类。类的加载是指把类的 .class 文件中的数据读入到内存中,通常是创建一个字节数组读入到 .class 文件中。
GC 是垃圾收集的意思(Gabage Collection),内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java 提供的 GC 功能可以自动监测对象是否超过作用域,从而达到自动回收内存的目的, Java 语言没有提供释放已分配内存的显示操作方法。
对于 GC 来说,当程序员创建对象时,GC 就开始监控这个对象的地址、大小以及使用情况。通常,GC 采用有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式确定哪些对象是"可达的",哪些对象是"不可达的"。当 GC 确定一些对象为"不可达"时,GC 就有责任回收这些内存空间,程序员可以手动执行 System.gc(),通知 GC 运行,但是 Java 语言规范并不保证 GC 一定会执行。
1、强引用
如果一个对象具有强引用,它就不会被垃圾回收器回收。即使当前内存空间不足,JVM也不会回收它,而是抛出 OutOfMemoryError 错误,使程序异常终止。如果想中断强引用和某个对象之间的关联,可以显式地将引用赋值为 null,这样一来的话,JVM 在合适的时间就会回收该对象。
2、软引用
在使用软引用时,如果内存的空间足够,软引用就能继续被使用,而不会被垃圾回收器回收;只有在内存空间不足时,软引用才会被垃圾回收器回收。
3、弱引用
具有弱引用的对象拥有的生命周期更短暂。因为当 JVM 进行垃圾回收,一旦发现弱引用对象,无论当前内存空间是否充足,都会将弱引用回收。不过由于垃圾回收器是一个优先级较低的线程,所以并不一定能迅速发现弱引用对象。
4、虚引用
顾名思义,就是形同虚设,如果一个对象仅持有虚引用,那么它相当于没有引用,在任何时候都可能被垃圾回收器回收。
虚引用必须和引用队列关联使用,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动
启动一个线程是调用 start()方法,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由 JVM 调度并执行。这并不意味着线程就会立即运行。run()方法可以产生必须退出的标志来停止一个线程。
volatile 修饰变量;
synchronized 修饰修改变量的方法;
wait/notify;
while 轮询
synchronized 是悲观锁,属于抢占式,会引起其他线程阻塞。
volatile 提供多线程共享变量可见性和禁止指令重排序优化。
CAS 是基于冲突检测的乐观锁(非阻塞)。
wait 和 notify 必须配合 synchronized 关键字使用。
wait 方法释放锁,notify 方法不释放锁。
还要注意一点就是涉及到线程之间的通信,就肯定会用到 validate 修饰。
1、普通线程死循环
2、使用定时器 timer
3、使用定时调度线程池 ScheduledExecutorService
wait():使一个线程处于等待状态,并且释放所持有的对象的 lock。
sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉
InterruptedException 异常。
notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由 JVM 确定唤醒哪个线程,而且不是按优先级。notityAll():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。
1、调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位。
2、并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可以并发执行。
3、拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源。
4、系统开销:在创建或撤销进程的时候,由于系统都要为之分配和回收资源,导致系统的明显大于创建或撤销线程时的开销。但进程有独立的地址空间,进程崩溃后,在保护模式下不会对其他的进程产生影响,而线程只是一个进程中的不同的执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但是在进程切换时,耗费的资源较大,效率要差些。
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量 的值也和预期的是一样的,就是线程安全的。一个线程安全的计数器类的同一个实例对象在被多个线程使用的情况下也不会出现计算失误。很显然你可以将集合类分 成两组,线程安全和非线程安全的。
1、新建状态(New):新创建了一个线程对象。
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的 start()方法。该状态的线程位于“可运行线程池”中,变得可运行,只等待获取 [CPU ](http://product.it168.com/list/b/0217_1.shtml)的使用权。即在就绪状态的进程除 CPU 之外,其它的运行所需资源都已全部获得。
3、运行状态(Running):就绪状态的线程获取了 CPU,执行程序代码。
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃 CPU 使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
阻塞的情况分三种:
(1)、等待阻塞:运行的线程执行 wait()方法,该线程会释放占用的所有资源,JVM 会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用 notify()
或 notifyAll()方法才能被唤醒,
(2)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线程放入“锁池”中。
(3)、其他阻塞:运行的线程执行 sleep()或 join()方法,或者发出了 I/O 请求时,JVM 会把该线程置为阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入就绪状态。
5、死亡状态(Dead):线程执行完了或者因异常退出了 run()方法,该线程结束生命周期。
volatile 变量和 atomic 变量看起来很像,但功能却不一样。Volatile 变量可以确保先行关系,即写操作会发生在后续的读操作之前, 但它并不能保证原子性。例如用 volatile 修饰 count 变量那么 count++ 操作就不是原子性的。而 AtomicInteger 类提供的 atomic 方法可以让这种操作具有原子性如 getAndIncrement()方法会原子性 的进行增量操作把当前值加一,其它数据类型和引用变量也可以进行相似操作。
竞态条件会导致程序在并发情况下出现一些 bugs。多线程对一些资源的竞争的时候就会产生竞态条件,如果首先要执行的程序竞争失败排到后面执行了, 那么整个程序就会出现一些不确定的 bugs。这种 bugs 很难发现而且会重复出现,因为线程间的随机竞争。
Java 提供了很丰富的 API 但没有为停止线程提供 API。JDK 1.0 本来有一些像 stop(), suspend()
和 resume()的控制方法但是由于潜在的死锁威胁因此在后续的 JDK 版本中他们被弃用了,之后 Java API 的设计者就没有提供一个兼容且线程安全的方法来停止一个线程。当 run() 或者 call() 方法执行完的时候线程会自动结束,如果要手动结束一个线程,你可以用 volatile 布尔变量来退出 run()方法的循环或者是取消任务来中断线程。
1)重用存在的线程,减少对象创建销毁的开销。
2)可有效的控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
3)提供定时执行、定期执行、单线程、并发数控制等功能。
volatile 关键字的两层语义
一旦一个共享变量(类的成员变量、类的静态成员变量)被 volatile 修饰之后,那么就具备了两层语义:1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2)禁止进行指令重排序。
用 volatile 修饰之后,变量的操作:
第一:使用 volatile 关键字会强制将修改的值立即写入主存;第二:使用 volatile 关键字的话,当线程 2 进行修改时,会导致线程 1 的工作内存中缓存变
量 stop 的缓存行无效(反映到硬件层的话,就是 CPU 的 L1 或者 L2 缓存中对应的缓存行无效);第三:由于线程 1 的工作内存中缓存变量 stop 的缓存行无效,所以线程 1 再次读取变量 stop
的值时会去主存读取。
在语言层面有两种方式。 java.lang.Thread 类的实例就是一个线程但是它需要调用 java.lang.Runnable 接口来执行,由于线程类本身就是调用的 Runnable 接口所以你可以继承 java.lang.Thread 类或者直接调用 Runnable 接口来重写 run()方法实现线程。
notify()方法不能唤醒某个具体的线程,所以只有一个线程在等待的时候它才有用武之地。而 notifyAll()唤醒所有线程并允许他们争夺锁确保了至少有一个线程能继续运行。
1)乐观锁:就像它的名字一样,对于并发间操作产生的线程安全问题持乐观状态,乐观锁认为竞争不总是会发生,因此它不需要持有锁,将比较-替换这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。
2)悲观锁:还是像它的名字一样,对于并发间操作产生的线程安全问题持悲观状态,悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十一,直接上了锁就操作资源了。
方式一:继承 Thread 类
方式二:实现 Runnable 接口
方式三:实现 Callable 接口
方式四:使用线程池的方式
创建线程要花费昂贵的资源和时间,如果任务来了才创建线程那么响应时间会变长,而且一个进程能创建的线程数有限。为了避免这些问题,在程序启动的时 候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程。从 JDK1.5 开始,Java API 提供了 Executor 框架让你可以创建不同的线程池。比如单线程池,每次处理一个任务;数目固定的线程池或者是缓存线程池(一个适合很多生存期短 的任务的程序的可扩展线程池)。
sleep 是线程类(Thread)的方法,导致此线程暂停执行指定时间,给执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用 sleep 不会释放对象锁。
wait 是 Object 类的方法,对此对象调用 wait 方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出 notify 方法(或 notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。
1、互斥条件:一个资源每次只能被一个进程使用。
2、请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
3、不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
4、循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
方式一:使用同步代码块
方式二:使用同步方法
方式三:使用 ReentrantLock
程序运行完毕,jvm 会等待非守护线程完成后关闭,但是 jvm 不会等待守护线程.守护线程最典型的例子就是 GC 线程.
多线程的上下文切换是指 CPU 控制权由一个已经正在运行的线程切换到另外一个就绪并等待获取 CPU 执行权的线程的过程.
两者都能用来编写多线程,但实现 Callable 接口的任务线程能返回执行结果,而实现 Runnable 接口的任务线程不能返回结果.Callable 通常需要和 Future/FutureTask 结合使用,用于获取异步计算结果.
1、sleep() 允许 指定以毫秒为单位的一段时间作为参数,它使得线程在指定的时间内进入阻塞状态,不能得到 CPU 时间,指定的时间一过,线程重新进入可执行状态。典型地,sleep()被用在等待某个资源就绪的情形:测试发现条件不满足后,让线程阻塞一段时间后重新测试,直到条件满足为止
2、suspend() 和 resume() 两个方法配套使用,suspend()使得线程进入阻塞状态,并且不会
自动恢复,必须其对应的 resume() 被调用,才能使得线程重新进入可执行状态。典型地, suspend() 和 resume() 被用在等待另一个线程产生的结果的情形:测试发现结果还没有产生后,让线程阻塞,另一个线程产生了结果后,调用 resume() 使其恢复。
3、yield() 使当前线程放弃当前已经分得的 CPU 时间,但不使当前线程阻塞,即线程仍处于可执行状态,随时可能再次分得 CPU 时间。调用 yield() 的效果等价于调度程序认为该线程已执行了足够的时间从而转到另一个线程4、wait() 和 notify() 两个方法配套使用,wait() 使得线程进入阻塞状态,它有两种形式,
一种允许 指定以毫秒为单位的一段时间作为参数,另一种没有参数,前者当对应的 notify() 被调用或者超出指定时间时线程重新进入可执行状态,后者则必须对应的 notify() 被调用.
主要相同点:Lock 能完成 synchronized 所实现的所有功能
主要不同点:Lock 有比 synchronized 更精确的线程语义和更好的性能。synchronized 会自动释放锁,而 Lock 一定要求程序员手工释放,并且必须在 finally 从句中释放。
ThreadLocal 是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用,特别适用于各个线程依赖不通的变量值完成操作的场景。
简单说 ThreadLocal 就是一种以空间换时间的做法,在每个 Thread 里面维护了一个以开地址法实现的 ThreadLocal.ThreadLocalMap,把数据进行隔离,数据不共享,自然就没有线程安全方面的问题了。
同步交互:指发送一个请求,需要等待返回,然后才能够发送下一个请求,有个等待过程;异步交互:指发送一个请求,不需要等待返回,随时可以再发送下一个请求,即不需要等待。区别:一个需要等待,一个不需要等待,在部分情况下,我们的项目开发中都会优先选择不需要等待的异步交互方式。
线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。程序员可以通过它进行多处理器编程,你可以使用多线程对 运算密集型任务提速
在 Java 并发程序中 FutureTask 表示一个可以取消的异步运算。它有启动和取消运算、查询运算是否完成和取回运算结果等方法。只有当运算完成的时候结果才能取回,如果运算尚未完成 get 方法将会阻塞。一个 FutureTask 对象可以对调用了 Callable 和 Runnable 的对象进行包 装,由于 FutureTask 也是调用了 Runnable 接口所以它可以提交给 Executor 来执行。
interrupted() 和 isInterrupted()的主要区别是前者会将中断状态清除而后者不会。Java 多线程的中断机制是用内部标识来实现的,调用 Thread.interrupt()来中断一个线程就会设置中断标识为 true。当中断线程调用静态方法 Thread.interrupted()来 检查中断状态时,中断状态会被清零。而非静态方法 isInterrupted()用来查询其它线程的中断状态且不会改变中断状态标识。简单的说就是任何抛 出 InterruptedException 异常的方法都会将中断状态清零。无论如何,一个线程的中断状态有有可能被其它线程调用中断来改变。
1)是多个线程涉及到多个锁,这些锁存在着交叉,所以可能会导致了一个锁依赖的闭环。例如:线程在获得了锁 A 并且没有释放的情况下去申请锁 B,这时,另一个线程已经获得了锁 B,在释放锁 B 之前又要先获得锁 A,因此闭环发生,陷入死锁循环。
2)默认的锁申请操作是阻塞的。
所以要避免死锁,就要在一遇到多个对象锁交叉的情况,就要仔细审查这几个对象的类中的所有方法,是否存在着导致锁依赖的环路的可能性。总之是尽量避免在一个同步方法中调用其它对象的延时方法和同步方法。
很多 synchronized 里面的代码只是一些很简单的代码,执行时间非常快,此时等待的线程都加锁可能是一种不太值得的操作,因为线程阻塞涉及到用户态和内核态切换的问题。既然 synchronized 里面的代码执行得非常快,不妨让等待锁的线程不要被阻塞,而是在 synchronized 的边界做忙循环,这就是自旋。如果做了多次忙循环发现还没有获得锁,再阻塞,这样可能是一种更好的策略。
如果线程是因为调用了 wait()、sleep()或者 join()方法而导致的阻塞,可以中断线程,并且通过抛出 InterruptedException 来唤醒它;如果线程遇到了 IO 阻塞,无能为力,因为 IO 是操作系统实现的,Java 代码并没有办法直接接触到操作系统。
许多程序员会认为该任务会阻塞直到线程池队列有空位。事实上如果一个任务不能被调度执行,那么ThreadPoolExecutor’s submit()方法将会抛出一个 RejectedExecutionException 异常。
线程局部变量是局限于线程内部的变量,属于线程自身所有,不在多个线程间共享。Java 提供 ThreadLocal 类来支持线程局部变量,是一种实现线程安全的方式。但是在管理环境下(如 web 服务器)使用线程局部变量的时候要特别小心,在这种情况下,工作线程的生命周期比任何应用变量的生命周期都要长。任何线程局部变量一旦在工作完成后没有释放,Java 应用就存在内存泄露的风险。
synchronized 关键字是防止多个线程同时执行一段代码,那么就会很影响程序执行效率,而 volatile 关键字在某些情况下性能要优于 synchronized,但是要注意 volatile 关键字是无法替代 synchronized 关键字的,因为 volatile 关键字无法保证操作的原子性。通常来说,使用 volatile 必须具备以下 2 个条件:
1)对变量的写操作不依赖于当前值
2)该变量没有包含在具有其他变量的不变式中
ThreadPoolExecutor(intcorePoolSize,intmaximumPoolSize,longkeepAliveTi me,TimeUnit unit,BlockingQueue workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)
参数说明: corePoolSize 核心线程数
maximumPoolSize 最大线程数,一般大于等于核心线程数 keepAliveTime 线程存活时间(针对最大线程数大于核心线程数时,非核心线程) unit 存活时间单位,和线程存活时间配套使用 workQueue 任务队列 threadFactory 创建线程的工程 handler 拒绝策略
五种线程池:
ExecutorService threadPool = null;
threadPool = Executors.newCachedThreadPool();//有缓冲的线程池,线程数 JVM 控制 threadPool = Executors.newFixedThreadPool(3);//固定大小的线程池
threadPool = Executors.newScheduledThreadPool(2);
threadPool = Executors.newSingleThreadExecutor();//单线程的线程池,只有一个线程在工
作
threadPool = new ThreadPoolExecutor();//默认线程池,可控制参数比较多
三种阻塞队列:
BlockingQueue workQueue = null;
workQueue = new ArrayBlockingQueue<>(5);//基于数组的先进先出队列,有界 workQueue = new LinkedBlockingQueue<>();//基于链表的先进先出队列,无界 workQueue = new SynchronousQueue<>();//无缓冲的等待队列,无界
四种拒绝策略
等待队列已经排满了,再也塞不下新任务,同时线程池中线程也已经达到 maximumPoolSize 数量,无法继续为新任务服务,这个时候就需要使用拒绝策略来处理。
RejectedExecutionHandler rejected = null;
rejected = new ThreadPoolExecutor.AbortPolicy();//默认,队列满了丢任务抛出异常,直接抛出 RejectedExecutionException 异常阻止系统正常运行。
rejected = new ThreadPoolExecutor.DiscardPolicy();//队列满了丢任务不异常,直接丢弃任
务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种方案。
rejected = new ThreadPoolExecutor.DiscardOldestPolicy();//将最早进入队列的任务删,之后再尝试加入队列, 抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务。
rejected = new ThreadPoolExecutor.CallerRunsPolicy();//如果添加到线程池失败,那么主线程会自己去执行该任务,调用者运行”一种调节机制,该策略既不会丢弃任务,也不会抛出异常,而是将某些任务回退给调用者,从而降低新任务的流量。
单例模式:保证被创建一次,节省系统开销。
工厂模式(简单工厂、抽象工厂):解耦代码。
观察者模式:定义了对象之间的一对多的依赖,这样一来,当一个对象改变时,它的所有的依赖者都会收到通知并自动更新。
外观模式:提供一个统一的接口,用来访问子系统中的一群接口,外观定义了一个高层的接口,让子系统更容易使用。
模版方法模式:定义了一个算法的骨架,而将一些步骤延迟到子类中,模版方法使得子类可以在不改变算法结构的情况下,重新定义算法的步骤。
状态模式:允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。
简单工厂:用来生产同一等级结构中的任意产品,对于增加新的产品,无能为力。工厂方法:用来生产同一等级结构中的固定产品,支持增加任意产品。
抽象工厂:用来生产不同产品族的全部产品,对于增加新的产品,无能为力;支持增加产品族。
设计模式可在多个项目中重用。
设计模式提供了一个帮助定义系统架构的解决方案。设计模式吸收了软件工程的经验。
设计模式为应用程序的设计提供了透明性。
设计模式是被实践证明切实有效的,由于它们是建立在专家软件开发人员的知识和经验之上的。
(1)单一原则(Single Responsibility Principle):一个类只负责一项职责,尽量做到类的只有一个
行为原因引起变化;
(2)里氏替换原则(LSP liskov substitution principle):子类可以扩展父类的功能,但不能改变
原有父类的功能;
(3)依赖倒置原则(dependence inversion principle):面向接口编程;
(4)接口隔离(interface segregation principle):建立单一接口;
(5)迪米特原则(law of demeter LOD):最少知道原则,尽量降低类与类之间的耦合;
(6)开闭原则(open closed principle):用抽象构建架构,用实现扩展原则;
单例就是该类只能返回一个实例。单例所具备的特点:1.私有化的构造函数2.私有的静态的全局变量3.公有的静态的方法
单例分为懒汉式、饿汉式和双层锁式饿汉式:
public class Singleton1 { private Singleton1() {};
private static Singleton1 single = new Singleton1(); public static Singleton1 getInstance() {
return single;
}
}
懒汉式:
public class Singleton2 { private Singleton2() {}
private static Singleton2 single=null; public tatic Singleton2 getInstance() {
if (single == null) {
single = new Singleton2();
}
return single;
}
}
线程安全: public class Singleton3 {
private Singleton3() {}
private static Singleton3 single ; public static Singleton3 getInstance() {
if(null == single){ synchronized(single ){
if(null == single){
single = new Singleton3();
}
}
}
return single;
}
}
\1. 根据目的来分根据模式是用来完成什么工作来划分,这种方式可分为创建型模式、结构型模式和行为型模式 3 种。
创建型模式:用于描述“怎样创建对象”,它的主要特点是“将对象的创建与使用分离”。 GoF 中提供了单例、原型、工厂方法、抽象工厂、建造者等 5 种创建型模式。
结构型模式:用于描述如何将类或对象按某种布局组成更大的结构,GoF 中提供了代理、适配器、桥接、装饰、外观、享元、组合等 7 种结构型模式。
行为型模式:用于描述类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,以及怎样分配职责。GoF 中提供了模板方法、策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录、解释器等 11 种行为型模式。
\2. 根据作用范围来分
根据模式是主要用于类上还是主要用于对象上来分,这种方式可分为类模式和对象模式两种。
类模式:用于处理类与子类之间的关系,这些关系通过继承来建立,是静态的,在编译时刻便确定下来了。GoF 中的工厂方法、(类)适配器、模板方法、解释器属于该模式。
对象模式:用于处理对象之间的关系,这些关系可以通过组合或聚合来实现,在运行时刻是可以变化的,更具动态性。GoF 中除了以上 4 种,其他的都是对象模式。
单例(Singleton)模式:某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。
原型(Prototype)模式:将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。
工厂方法(Factory Method)模式:定义一个用于创建产品的接口,由子类决定生产什么产品。
抽象工厂(AbstractFactory)模式:提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品。
建造者(Builder)模式:将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。
代理(Proxy)模式:为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。
适配器(Adapter)模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
桥接(Bridge)模式:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
装饰(Decorator)模式:动态的给对象增加一些职责,即增加其额外的功能。
外观(Facade)模式:为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。
享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复用。
组合(Composite)模式:将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。
模板方法(TemplateMethod)模式:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
策略(Strategy)模式:定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。
命令(Command)模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。
职责链(Chain of Responsibility)模式:把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。
状态(State)模式:允许一个对象在其内部状态发生改变时改变其行为能力。
观察者(Observer)模式:多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为。
中介者(Mediator)模式:定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。
迭代器(Iterator)模式:提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
访问者(Visitor)模式:在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问。
备忘录(Memento)模式:在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。
解释器(Interpreter)模式:提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。
统一建模语言(Unified Modeling Language,UML)是用来设计软件蓝图的可视化建模语言, 1997 年被国际对象管理组织(OMG)采纳为面向对象的建模语言的国际标准。它的特点是简单、统一、图形化、能表达软件设计中的动态与静态信息。
统一建模语言能为软件开发的所有阶段提供模型化和可视化支持。而且融入了软件工程领域的新思想、新方法和新技术,使软件设计人员沟通更简明,进一步缩短了设计时间,减少开发成本。它的应用领域很宽,不仅适合于一般系统的开发,而且适合于并行与分布式系统的建模。
UML 从目标系统的不同角度出发,定义了用例图、类图、对象图、状态图、活动图、时序图、协作图、构件图、部署图等 9 种图。
桥接(Bridge)模式的定义如下:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
桥接(Bridge)模式的优点是:
由于抽象与实现分离,所以扩展能力强;其实现细节对客户透明。
缺点是:由于聚合关系建立在抽象层,要求开发者针对抽象化进行设计与编程,这增加了系统的理解与设计难度类适配器:
策略(Strategy)模式的定义:该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
策略模式的主要优点如下多重条件语句不易维护,而使用策略模式可以避免使用多重条件语句。
策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码。策略模式可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的。策略模式提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法。策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。其主要缺点如下客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类。策略模式造成很多的策略类
相同点:
1)都属于 ORM 框架
2)都是对 jdbc 的包装
3)都属于持久层的框架
不同点:
1)hibernate 是面向对象的,mybatis 是面向 sql 的;
2)hibernate 全自动的 orm,mybatis 是半自动的 orm;
3)hibernate 查询映射实体对象必须全字段查询,mybatis 可以不用;
4)hibernate 级联操作,mybatis 则没有;
5)hibernate 编写 hql 查询数据库大大降低了对象和数据库的耦合性,mybatis 提供动态 sql,需要手写 sql,与数据库之间的耦合度取决于程序员所写的 sql 的方法,所以 hibernate 的移植性要远大于 mybatis。
6)hibernate 有方言夸数据库,mybatis 依赖于具体的数据库。
7)hibernate 拥有完整的日志系统,mybatis 则相对比较欠缺。
1、基于 SQL 语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响, SQL 写在 XML 里,解除 sql 与程序代码的耦合,便于统一管理;提供 XML 标签,支持编写动态 SQL 语句,并可重用。
2、与 JDBC 相比,减少了 50%以上的代码量,消除了 JDBC 大量冗余的代码,不需要手动开关连接;3、很好的与各种数据库兼容(因为 MyBatis 使用 JDBC 来连接数据库,所以只要 JDBC 支持的数据库 MyBatis 都支持)。
4、能够与 Spring 很好的集成;
5、提供映射标签,支持对象与数据库的 ORM 字段关系映射;提供对象关系映射标签,支持对象关系组件维护。
3.MyBatis 框架的缺点?
(1)SQL 语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写 SQL 语句的功底有一定要求。
(2)SQL 语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。
1、用户发送请求至前端控制器 DispatcherServlet。
2、DispatcherServlet 收到请求调用 HandlerMapping 处理器映射器。
3、处理器映射器根据请求 url 找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给 DispatcherServlet。
4、DispatcherServlet 通过 HandlerAdapter 处理器适配器调用处理器。
5、执行处理器(Controller,也叫后端控制器)。
6、Controller 执行完成返回 ModelAndView
7、HandlerAdapter 将 controller 执行结果 ModelAndView 返回给 DispatcherServlet
8、DispatcherServlet 将 ModelAndView 传给 ViewReslover 视图解析器
9、ViewReslover 解析后返回具体 View
10、DispatcherServlet 对 View 进行渲染视图(即将模型数据填充至视图中)。
11、DispatcherServlet 响应用户。
SpringMVC是 Spring 提供的一个 mvc 模式的框架。
(1)MyBatis 专注于 SQL 本身,是一个足够灵活的 DAO 层解决方案。
(2)对性能的要求很高,或者需求变化较多的项目,如互联网项目,MyBatis 将是不错的选择。
BeanFactory 是 spring 中较为原始的 Factory,无法支持 spring 的许多插件,如 AOP 功能、
Web 应用等。
ApplicationContext 接口是通过 BeanFactory 接口派生而来的,除了具备 BeanFactory 接口的
功能外,还具备资源访问、事件传播、国际化消息访问等功能。总体区别如下:
1)使用 ApplicationContext,配置 bean 默认配置是 singleton,无论是否使用,都会被实例化。
优点是预先加载,缺点是浪费内存;2)使用 BeanFactory 实例化对象时,配置的 bean 等到使用的时候才会被实例化。优点是节
约内存,缺点是速度比较慢,多用于移动设备的开发;
3)没有特殊要求的情况下,应该使用 ApplicationContext 完成,ApplicationContext 可以实现
BeanFactory 所有可实现的功能,还具备其他更多的功能。
构造器注入
set 方法注入
接口注入
1、拦截器是基于 java 的反射机制的,而过滤器是基于函数回调
2、拦截器不依赖与 servlet 容器,过滤器依赖与 servlet 容器。
3、拦截器只能对 action 请求起作用,而过滤器则可以对几乎所有的请求起作用。
4、拦截器可以访问 action 上下文、值栈里的对象,而过滤器不能访问。
5、在 action 的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次
Spring IOC 负责创建对象,管理对象(通过依赖注入(DI),装配对象,配置对象,并且管理这些对象的整个生命周期。
实现 AOP 的技术,主要分为两大类:
静态代理 - 指使用 AOP 框架提供的命令进行编译,从而在编译阶段就可生成 AOP 代理类,因此也称为编译时增强;编译时编织(特殊编译器实现)
类加载时编织(特殊的类加载器实现)。
动态代理 - 在运行时在内存中“临时”生成 AOP 动态代理类,因此也被称为运行时增强。
1、代理模式: 代理模式就是本该我做的事,我不做,我交给代理人去完成。就比如,我生产了一些产品,我自己不卖,我委托代理商帮我卖,让代理商和顾客打交道,我自己负责主要产品的生产就可以了。 代理模式的使用,需要有本类,和代理类,本类和代理类共同实现统一的接口。然后在 main 中调用就可以了。本类中的业务逻辑一般是不会变动的,在我们需要的时候可以不断的添加代理对象,或者修改代理类来实现业务的变更。
2、代理模式可以分为: 静态代理 优点:可以做到在不修改目标对象功能的前提下,对目标功能扩展 缺点:因为本来和代理类要实现统一的接口,所以会产生很多的代理类,类太多,一旦接口增加方法,目标对象和代理对象都要维护。 动态代理(JDK 代理/接口代理)代理对象,不需要实现接口,代理对象的生成,是利用 JDK 的 API,动态的在内存中构建代理对象,需要我们指定代理对象/目标对象实现的接口的类型。 Cglib 代理 特点: 在内存中构建一个子类对象,从而实现对目标对象功能的扩展。
3、使用场景: 修改代码的时候。不用随便去修改别人已经写好的代码,如果需要修改的话,可以通过代理的方式来扩展该方法。 隐藏某个类的时候,可以为其提供代理类 当我们要扩展某个类功能的时候,可以使用代理类 当一个类需要对不同的调用者提供不同的调用权限的时候,可以使用代理类来实现。 减少本类代码量的时候。 需要提升处理速度的时候。就比如我们在访问某个大型系统的时候,一次生成实例会耗费大量的时间,我们可以采用代理模式,当用来需要的时候才生成实例,这样就能提高访问的速度。
第一种是使用标签,逐一定义数据库列名和对象属性名之间的映射关系。第二种是使用 sql 列的别名功能,将列的别名书写为对象属性名。
有了列名与属性名的映射关系后,Mybatis 通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。
1、Spring 容器根据配置中的 bean 定义中实例化 bean。
2、Spring 使用依赖注入填充所有属性,如 bean 中所定义的配置。
3 、如果 bean | 实现 BeanNameAware 接口,则工厂通过传递 bean 的 ID 来调用 |
---|---|
setBeanName()。 | |
4 、如果 bean | 实现 BeanFactoryAware 接口,工厂通过传递自身的实例来调用 |
setBeanFactory()。 |
5、如果存在与 bean 关联的任何 BeanPostProcessors,则调用 preProcessBeforeInitialization()
方法。
6、如果为 bean 指定了 init 方法( 的 init-method 属性),那么将调用它。
7 、 最 后 , 如 果 存 在 与 bean 关 联 的 任 何 BeanPostProcessors , 则 将 调 用
postProcessAfterInitialization() 方法。
8、如果 bean 实现 DisposableBean 接口,当 spring 容器关闭时,会调用 destory()。9、如果为 bean 指定了 destroy 方法( 的 destroy-method 属性),那么将调用它。
51
代理模式,在 AOP 中被使用最多。
单例模式,在 Spring 配置文件中定义 bean 的时候默认的是单例模式。
工厂模式, BeanFactory 用来创建对象的实例。
模板方法, 用来解决重复性代码。
前端控制器,Spring 提供了 DispatcherSerclet 来对请求进行分发。视图帮助,Spring 提供了一系列的 JSP 标签。
依赖注入,它是惯穿于 BeanFactory/ApplicationContext 接口的核心理念。
1、Spring 的核心是 ApplicatonContext,它负责管理 bean 的完整的生命周期。Spring 提供了以下 内 置 事 件 : ContextRefreshedEvent ContextStartedEvent ContextStoppedEvent ContextClosedEvent RequestHandleEvent 2、由于 Spring 的事件处理是单线程的,所以如果一个事件被发布,直至并且除非所有的
接收者得到的该消息,该进程被阻塞并且流程将不会继续。因此,如果事件处理被使用,在设计应用程序时应注意。
3、监听上下文事件
4、自定义事件
1、简化开发,解耦,集成其它框架。
2、低侵入式设计,代码污染级别低。
3、Spring 的 DI 机制降低了业务对象替换的复杂性,提高了软件之间的解耦。
4、Spring AOP 支持将一些通用的任务进行集中式的管理,例如:安全,事务,日志等,从而使代码能更好的复用。
当通过 Spring 容器创建一个 Bean 实例的时候,不仅可以完成 bean 实例的实力化,还可以为 bean 指定作用域。Spring bean 元素的支持以下 5 种作用域:
Singleton:单例模式,在整个 spring IOC 容器中,使用 singleton 定义的 bean 将只有一个实
例。
Prototype:多例模式,每次通过容器中的 getBean 方法获取 prototype 定义的 beans 时,都会产生一个新的 bean 的实例。
Request:对于每次 Http 请求,使用 request 定义的 bean 都会产生一个新的实例,只有在 web 应用时候,该作用域才会有效。
Session:对于每次 Http Session,使用 session 定义的 Bean 都将产生一个新的实例。Globalsession:每个全局的 Http Sesisonn,使用 session 定义的本都将产生一个新的实例
Spring 提供理论四种集合类的配置元素:
lt;List&: 该标签用来装配 有重复值的 list 值
lt;set&: 该标签用来装配没有重复值的 set 值lt;map&:该标签科以用来注入键值对 lt;props&: 该标签用来支持注入键值对和字符串类型键值对。
它们是构成用户应用程序主干的对象。
Bean 由 Spring IoC 容器管理。
它们由 Spring IoC 容器实例化,配置,装配和管理。 Bean 是基于用户提供给容器的配置元数据创建。
就是将一个 Bean 注入到其它的 Bean 的 Property 中,默认情况下,容器不会自动装配,需要我们手动设定。Spring 可以通过向 Bean Factory 中注入的方式来搞定 bean 之间的依赖关系,达到自动装配的目的。
自动装配建议少用,如果要使用,建议使用 ByName
1、no - 这是默认设置,表示没有自动装配。应使用显式 bean 引用进行装配。
2、byName - 它根据 bean 的名称注入对象依赖项。它匹配并装配其属性与 XML 文件中由相同名称定义的 bean。
3、byType - 它根据类型注入对象依赖项。如果属性的类型与 XML 文件中的一个 bean 名称匹配,则匹配并装配属性。4、构造函数 - 它通过调用类的构造函数来注入依赖项。它有大量的参数。
5、autodetect - 首先容器尝试通过构造函数使用 autowire 装配,如果不能,则尝试通过 byType 自动装配。
1、覆盖的可能性 - 您始终可以使用 和 设置指定依赖项,这将
覆盖自动装配。
2、基本元数据类型 - 简单属性(如原数据类型,字符串和类)无法自动装配。
3、令人困惑的性质 - 总是喜欢使用明确的装配,因为自动装配不太精确。
@Controller - 用于 Spring MVC 项目中的控制器类。 @Service - 用于服务类。
@RequestMapping - 用于在控制器处理程序方法中配置 URI 映射。
@ResponseBody - 用于发送 Object 作为响应,通常用于发送 XML 或 JSON 数据作为响应。
@PathVariable - 用于将动态值从 URI 映射到处理程序方法参数。
@Autowired - 用于在 spring bean 中自动装配依赖项。
@Qualifier - 使用 @Autowired 注解,以避免在存在多个 bean 类型实例时出现混淆。 @Scope - 用于配置 spring bean 的范围。
@Configuration,@ComponentScan 和 @Bean - 用于基于 java 的配置。@Aspect,@Before,@After,@Around,@Pointcut - 用于切面编程(AOP)。
1、 @Component:这将 java 类标记为 bean。它是任何 Spring 管理组件的通用构造型。 spring 的组件扫描机制现在可以将其拾取并将其拉入应用程序环境中。
2、@Controller:这将一个类标记为 Spring Web MVC 控制器。标有它的 Bean 会自动导入到 IoC 容器中。
3、@Service:此注解是组件注解的特化。它不会对 @Component 注解提供任何其他行为。您可以在服务层类中使用 @Service 而不是 @Component,因为它以更好的方式指定了意图。
4、@Repository:这个注解是具有类似用途和功能的 @Component 注解的特化。它为 DAO
提供了额外的好处。它将 DAO 导入 IoC 容器,并使未经检查的异常有资格转换为 Spring DataAccessException。
Spring 支持两种类型的事务管理:
程序化事务管理:在此过程中,在编程的帮助下管理事务。它为您提供极大的灵活性,但维护起来非常困难。
声明式事务管理:在此,事务管理与业务代码分离。仅使用注解或基于 XML 的配置来管理事务。
1、它为不同的事务 API(如 JTA, JDBC, Hibernate, JPA, 和 JDO)提供了统一的编程模型。
2、它为编程式事务管理提供了一个简单的 API 而非一系列复杂的事务 API(如 JTA).
3、它支持声明式事务管理。
4、它可以和 Spring 的多种数据访问技术很好的融合。
1、AOP 面向切面编程,它是一种思想。它就是针对业务处理过程中的切面进行提取,以达到优化代码的目的,减少重复代码的目的。 就比如,在编写业务逻辑代码的时候,我们习惯性的都要写:日志记录,事物控制,以及权限控制等,每一个子模块都要写这些代码,代码明显存在重复。这时候,我们运用面向切面的编程思想,采用横切技术,将代码中重复的部分,不影响主业务逻辑的部分抽取出来,放在某个地方进行集中式的管理,调用。 形成日志切面,事物控制切面,权限控制切面。 这样,我们就只需要关系业务的逻辑处理,即提高了工作的效率,又使得代码变的简洁优雅。这就是面向切面的编程思想,它是面向对象编程思想的一种扩展。
2、AOP 的使用场景: 缓存、权限管理、内容传递、错误处理、懒加载、记录跟踪、优化、
校准、调试、持久化、资源池、同步管理、事物控制等。 AOP 的相关概念: 切面(Aspect)
连接点(JoinPoint) 通知(Advice) 切入点(Pointcut) 代理(Proxy): 织入(WeaVing)3、Spring AOP 的编程原理? 代理机制 JDK 的动态代理:只能用于实现了接口的类产生代理。 Cglib 代理:针对没有实现接口的类产生代理,应用的是底层的字节码增强技术,生成当前类的子类对象。
Spring Web MVC 框架提供 模型-视图-控制器 架构和随时可用的组件,用于开发灵活且松散耦合的 Web 应用程序。 MVC 模式有助于分离应用程序的不同方面,如输入逻辑,业务逻辑和 UI 逻辑,同时在所有这些元素之间提供松散耦合。
WebApplicationContext 是 ApplicationContext 的扩展。它具有 Web 应用程序所需的一些额外功能。它与普通的 ApplicationContext 在解析主题和决定与哪个 servlet 关联的能力方面有所不同。
一、拦截机制的不同
Struts2 是类级别的拦截,每次请求就会创建一个 Action,和 Spring 整合时 Struts2 的 ActionBean 注入作用域是原型模式 prototype,然后通过 setter,getter 吧 request 数据注入到属性。Struts2 中,一个 Action 对应一个 request,response 上下文,在接收参数时,可以通过属性接收,这说明属性参数是让多个方法共享的。Struts2 中 Action 的一个方法可以对
应一个 url,而其类属性却被所有方法共享,这也就无法用注解或其他方式标识其所属方法了,只能设计为多例。
SpringMVC 是方法级别的拦截,一个方法对应一个 Request 上下文,所以方法直接基本上是独立的,独享 request,response 数据。而每个方法同时又何一个 url 对应,参数的传递是直接注入到方法中的,是方法所独有的。处理结果通过 ModeMap 返回给框架。在 Spring
整合时,SpringMVC 的 Controller Bean 默认单例模式 Singleton,所以默认对所有的请求,只会创建一个 Controller,有应为没有共享的属性,所以是线程安全的,如果要改变默认的作用域,需要添加@Scope 注解修改。
Struts2 有自己的拦截 Interceptor 机制,SpringMVC 这是用的是独立的 Aop 方式,这样导致 Struts2 的配置文件量还是比 SpringMVC 大。
二、底层框架的不同
Struts2 采用 Filter(StrutsPrepareAndExecuteFilter)实现,SpringMVC(DispatcherServlet)
则采用 Servlet 实现。Filter 在容器启动之后即初始化;服务停止以后坠毁,晚于 Servlet。Servlet在是在调用时初始化,先于 Filter 调用,服务停止后销毁。
三、性能方面
Struts2 是类级别的拦截,每次请求对应实例一个新的 Action,需要加载所有的属性值注入,SpringMVC 实现了零配置,由于 SpringMVC 基于方法的拦截,有加载一次单例模式 bean 注入。所以,SpringMVC 开发效率和性能高于 Struts2。
四、配置方面
spring MVC 和 Spring 是无缝的。从这个项目的管理和安全上也比 Struts2 高。
#{}是预编译处理,KaTeX parse error: Expected 'EOF', got '#' at position 22: …替换。 Mybatis 在处理#̲{}时,会将 sql 中的#{…{}时,就是把${}替换成变量的值。使用#{}可以有效的防止 SQL 注入,提高系统安全性。
@Autowire 默认按照类型装配,默认情况下它要求依赖对象必须存在如果允许为 null,可以设置它 required 属性为 false,如果我们想使用按照名称装配,可以结合@Qualifier 注解一起使用;
@Resource 默认按照名称装配,当找不到与名称匹配的 bean 才会按照类型装配,可以通过 name 属性指定,如果没有指定 name 属性,当注解标注在字段上,即默认取字段的名称作为 bean 名称寻找依赖对象,当注解标注在属性的 setter 方法上,即默认取属性名作为 bean 名称寻找依赖对象
IOC:就是对象之间的依赖关系由容器来创建,对象之间的关系本来是由我们开发者自己创建和维护的,在我们使用 Spring 框架后,对象之间的关系由容器来创建和维护,将开发者做的事让容器做,这就是控制反转。BeanFactory 接口是 Spring Ioc 容器的核心接口。
DI:我们在使用 Spring 容器的时候,容器通过调用 set 方法或者是构造器来建立对象之间的依赖关系。
控制反转是目标,依赖注入是我们实现控制反转的一种手段。
1、内部最核心的就是 IOC 了,之前是 new 对象,现在可以直接从容器中获取,动态注入,这其实就是利用 java 里的反射。反射其实就是在运行时动态的去创建、调用对象,Spring就是在运行时,根据 xml Spring 的配置文件来动态的创建对象,和调用对象里的方法的。
2、Spring 另一个核心就是 AOP 面向切面编程,可以为某一类对象 进行监督和控制(也就是在调用这类对象的具体方法的前后去调用你指定的 模块)从而达到对一个模块扩充的功能。这些都是通过配置类达到的。(日志、事务等)
3、Spring 目的:就是让对象与对象(模块与模块)之间的关系没有通过代码来关联,都是通过配置类说明管理的(Spring 根据这些配置 内部通过反射去动态的组装对象)要记住: Spring 是一个容器,凡是在容器里的对象才会有 Spring 所提供的这些服务和功能。
4、Spring 里用的最经典设计模式:模板方法模式。(有兴趣同学可以了解一下)、核心容器组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转(IOC)模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
PROPAGATION(蔓延、传播、传输)
事务传播行为类型 说明
事务传播行为类型 说明
如果当前没有事务,就新建一个事务,如果已经存在一个事务
PROPAGATION_REQUIRED
中,加入到这个事务中。这是默认的事务传播行为
PROPAGATION_SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY 使用当前的事务,如果当前没有事务,就抛出异常。
新建事务,如果当前存在事务,把当前事务挂起。(一个新的 PROPAGATION_REQUIRES_NEW 事务将启动,而且如果有一个现有的事务在运行的话,则这个方法将在运行期被挂起,直到新的事务提交或者回滚才恢复执
行。)
以非事务方式执行操作,如果当前存在事务,就把当前事务挂
PROPAGATION_NOT_SUPPORTED 起。
PROPAGATION_NEVER 以非事务方式执行,如果当前存在事务,则抛出异常。
如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,
则执行与 PROPAGATION_REQUIRED 类似的操作。(外层事务抛
PROPAGATION_NESTED
出异常回滚,那么内层事务必须回滚,反之内层事务并不影响外层事务)
DispatcherServlet 前置控制器 |HandlerMapping 请求映射(到 Controller)
|HandlerAdapter 请求映射(到 Controller 类的方法上)|Controller 控制器| HandlerIntercepter 拦截器|ViewResolver 视图映射|View 视图处理
1、存储方式 Memecache 把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。 Redis 有部份存在硬盘上,redis 可以持久化其数据
2、数据支持类型 memcached 所有的值均是简单的字符串,redis 作为其替代者,支持更为丰富的数据类型 ,提供 list,set,zset,hash 等数据结构的存储
3、使用底层模型不同 它们之间底层实现方式 以及与客户端之间通信的应用协议不一样。 Redis 直接自己构建了 VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。
4、value 值大小不同:Redis 最大可以达到 1gb;memcache 只有 1mb。5、redis 的速度比 memcached 快很多6、Redis 支持数据的备份,即 master-slave 模式的数据备份。
持久化就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失。比较:
1、aof 文件比 rdb 更新频率高,优先使用 aof 还原数据。
2、aof 比 rdb 更安全也更大
3、rdb 性能比 aof 好
4、如果两个都配了优先加载 AOF
(1)、会话缓存(Session Cache)
最常用的一种使用 Redis 的情景是会话缓存(session cache)。用 Redis 缓存会话比其他存储(如 Memcached)的优势在于:Redis 提供持久化。
(2)、全页缓存(FPC)
除基本的会话 token 之外,Redis 还提供很简便的 FPC 平台。回到一致性问题,即使重启了 Redis 实例,因为有磁盘的持久化,用户也不会看到页面加载速度的下降,这是一个极大改进,类似 PHP 本地 FPC。
再次以 Magento 为例,Magento 提供一个插件来使用 Redis 作为全页缓存后端。
此外,对 WordPress 的用户来说,Pantheon 有一个非常好的插件 wp-redis,这个插件能帮助你以最快速度加载你曾浏览过的页面。
(3)、队列
Reids 在内存存储引擎领域的一大优点是提供 list 和 set 操作,这使得 Redis 能作为一个很好的消息队列平台来使用。Redis 作为队列使用的操作,就类似于本地程序语言(如 Python)
对 list 的 push/pop 操作。
(4),排行榜/计数器
Redis 在内存中对数字进行递增或递减的操作实现的非常好。集合(Set)和有序集合(Sorted Set)也使得我们在执行这些操作的时候变的非常简单,Redis 只是正好提供了这两种数据结构。所以,我们要从排序集合中获取到排名最靠前的 10 个用户–我们称之为“user_scores”,我们只需要像下面一样执行即可:
(5)、发布/订阅
最后(但肯定不是最不重要的)是 Redis 的发布/订阅功能。发布/订阅的使用场景确实非常多。我已看见人们在社交网络连接中使用,还可作为基于发布/订阅的脚本触发器,甚至用 Redis 的发布/订阅功能来建立聊天系统!
Redis 集群没有使用一致性 hash,而是引入了哈希槽的概念,Redis 集群有 16384 个哈希槽,每个 key 通过 CRC16 校验后对 16384 取模来决定放置哪个槽,集群的每个节点负责一部分 hash 槽。
事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行,事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。
noeviction:返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但 DEL 和几个例外) allkeys-lru: 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。
volatile-lru: 尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。 allkeys-random: 回收随机的键使得新添加的数据有空间存放。
volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。
volatile-ttl: 回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。
String、List、Set、Zset(Sorted Set)、hash
缓存穿透:无效 ID,在 redis 缓存中查不到,去查询 DB,造成 DB 压力增大。解决方法:
1、解决方法 1:布隆过滤器,提供一个很大的 Bit-Map,提供多个 hash 函数,分别对查询参数值【比如 UUID】,进行求 hash,然后分别对多个 hash 结果,在对应位置对比是否全为 1 或者某个位置为 0,一旦有一个位置标识为 0,表示本次查询 UUID,不存在于缓存,再去查询 DB.起到一个再过滤的效果。
2、解决方法 2:把无效的 ID,也在 redis 缓存起来,并设置一个很短的超时时间。缓存雪崩:缓存同一时间批量失效,导致大量的访问直接访问 DB 解决方法:
在做缓存时候,就做固定失效时间+随机时间段,保证所有的缓存不会同一时间失效缓存击穿:在缓存失效的时候,会有高并发访问失效的缓存【热点数据】解决方法:
最简单的解决方法,就是将热点数据设置永不超时!
第二个解决方法:对访问的 Key 加上互斥锁,请求的 Key 如果不存在,则加锁,去数据库取,新请求过来,如果相同 KEy,则暂停 10s 再去缓存取值;如果 Key 不同,则直接去缓存取!
redis 通过一主多从,主节点负责写,从节点负责读,读写分离,从而实现高并发。
主备切换,哨兵集群,主节点宕机的情况下,自动选举出一个从节点变成主节点,从而保证了 redis 集群的高可用。
首先,redis 是单进程单线程的 k-v 内存型可持久化数据库。单线程还能处理速度很快的原因:
1、redis 操作是基于内存的,内存的读写速度非常快
2、正是由于 redis 的单线程模式,避免了线程上下文切换的损耗
3、redis 采用的 IO 多路复用技术,可以很好的解决多请求并发的问题。 多路代表多请求,复用代表多个请求重复使用同一个线程。
对于 Redis 而言,命令的原子性指的是:一个操作的不可以再分,操作要么执行,要么不执行。
Redis 的操作之所以是原子性的,是因为 Redis 是单线程的。
Redis 本身提供的所有 API 都是原子操作,Redis 中的事务其实是要保证批量操作的原子性。多个命令在并发中也是原子性的吗?
不一定, 将 get 和 set 改成单命令操作,incr 。使用 Redis 的事务,或者使用 Redis+Lua==
的方式实现.
1、从服务发送一个 sync 同步命令给主服务要求全量同步
2、主服务接收到从服务的 sync 同步命令时,会 fork 一个子进程后台执行 bgsave 命令(非阻塞)快照保存,生成 RDB 文件,并将 RDB 文件发送给从服务
3、从服务再将接收到的 RDB 文件载入自己的 redis 内存
4、待从服务将 RDB 载入完成后,主服务再将缓冲区所有写命令发送给从服务
5、从服务在将主服务所有的写命令载入内存从而实现数据的完整同步
6、从服务下次在需要同步数据时只需要发送自己的 offset 位置(相当于 mysql binlog 的位置)即可,只同步新增加的数据,再不需要全量同步
1、监控:Sentinel 会不断的检查主服务器和从服务器是否正常运行。
2、通知:当被监控的某个 redis 服务器出现问题,Sentinel 通过 API 脚本向管理员或者其他的应用程序发送通知。
3、自动故障转移:当主节点不能正常工作时,Sentinel 会开始一次自动的故障转移操作,它会将与失效主节点是主从关系 的其中一个从节点升级为新的主节点,并且将其他的从节点指向新的主节点。
硬盘上的数据,缓存在别的计算机上(非程序运行的计算机)的内存上,而且可以缓存的计算机的个数不止一个,可以使用 n 个用户通过访问 http 服务器,然后访问应用服务器资源,应用服务器调用后端的数据库,在第一次访问的时候,直接访问数据库,然后将要缓存的内容放入到 memcached 集群,集群规模根据缓存文件的大小而定。在第二次访问的时候就直接进入缓存读取,不需要进行数据库的操作。这个适合数据变化不频繁的场景,比如:互联网站显示的榜单,阅读排行等。
Nginx 是一个高性能的 HTTP 和反向代理服务器,及电子邮件代理服务器,同时也是一个非常高效的反向代理、负载平衡。
轻量级,同样起 web 服务,比 apache 占用更少的内存及资源抗并发,nginx 处理请求是异步非阻塞的,而 apache 则是阻塞型的,在高并发下 nginx 能保持低资源低消耗高性能高度模块化的设计,编写模块相对简单
社区活跃,各种高性能模块出品迅速啊 rewrite ,比 nginx 的 rewrite 强大模块超多,基本想到的都可以找到
少 bug ,nginx 的 bug 相对较多
Nginx 运行工作进程数量 Nginx 运行工作进程个数一般设置 CPU 的核心或者核心数 x2 Nginx 运行 CPU 亲和力比如 4 核配置: worker_processes 4;
worker_cpu_affinity 0001 0010 0100 1000 Nginx 最大打开文件数 worker_rlimit_nofile 65535; Nginx 事件处理模型 events {
use epoll; worker_connections 65535; multi_accept on;
}
nginx 采用 epoll 事件模型,处理效率高。开启高效传输模式连接超时时间
主要目的是保护服务器资源,CPU,内存,控制连接数,因为建立连接也是需要消耗资源的
首先,nginx 在启动时,会解析配置文件,得到需要监听的端口与 ip 地址,然后在 nginx 的 master 进程里面先初始化好这个监控的 socket,再进行 listen,然后再 fork 出多个子进程出来, 子进程会竞争 accept 新的连接。此时,客户端就可以向 nginx 发起连接了。当客户端与 nginx 进行三次握手,与 nginx 建立好一个连接后,此时,某一个子进程会 accept 成功,然后创建 nginx 对连接的封装,即 ngx_connection_t 结构体,接着,根据事件调用相应的事件处理模块,如 http 模块与客户端进行数据的交换。最后,nginx 或客户端来主动关掉连接,到此,一个连接就寿终正寝了
nginx 之所以可以实现高并发,与它采用的 epoll 模型有很大的关系。epoll 模型采用异步非阻塞的事件处理机制。这种机制可让 nginx 进程同时监控多个事件。
简单来说,就是异步非阻塞,使用了 epoll 模型和大量的底层代码优化。如果深入一点的话,
就是 nginx 的特殊进程模型和事件模型的设计,才使其可以实现高并发。
它是采用一个 master 进程和多个 worker 进程的工作模式。
1、master 进程主要负责收集、分发请求。当一个请求过来时,master 拉起一个 worker 进程负责处理这个请求。;2、master 进程也要负责监控 worker 的状态,保证高可靠性;
3、worker 进程议案设置为和 CPU 核心数一致或者其二倍。nginx 的 worker 进程和 Apache
的不一样。apache 的进程在同一时间只能处理一个请求,所以它会开启很多个进程,几百甚至几千个。而 nginx 的 worker 进程在同一时间可以处理的请求数只受内存限制,因此可以处理更多请求。
1、轮询(默认)
每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器 down 掉,能自动剔除。
2、weight
指定轮询几率,weight 和访问比率成正比,用于后端服务器性能不均的情况。
3、ip_hash
每个请求按访问 ip 的 hash 结果分配,这样每个访客固定访问一个后端服务器,可以解决的问题。
4、fair(第三方)
按后端服务器的响应时间来分配请求,响应时间短的优先分配。
、url_hash(第三方)
按访问 url 的 hash 结果来分配请求,使同样的 url 定向到同一个后端服务器,后端服务器为缓存时比较有效
跨平台、配置简单,非阻塞、高并发连接:处理 2-3 万并发连接数,官方监测能支持 5 万并发,内存消耗小:开启 10 个 nginx 才占 150M 内存,nginx 处理静态文件好,耗费内存少,
内置的健康检查功能:如果有一个服务器宕机,会做一个健康检查,再发送的请求就不会发送到宕机的服务器了。重新将请求提交到其他的节点上。
节省宽带:支持 GZIP 压缩,可以添加浏览器本地缓存稳定性高:宕机的概率非常小
接收用户请求是异步的:浏览器将请求发送到 nginx 服务器,它先将用户请求全部接收下来,再一次性发送给后端 web 服务器,极大减轻了 web 服务器的压力,一边接收 web 服务器的返回数据,一边发送给浏览器客户端, 网络依赖性比较低,只要 ping 通就可以负载均衡,可以有多台 nginx 服务器使用 dns 做负载均衡,事件驱动:通信机制采用 epoll 模型(nio2 异步非阻塞)
一个位于客户端和原始服务器之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。客户端才能使用正向代理正向代理总结就一句话:代理端代理的是客户端
反向代理是指以代理服务器来接受 internet 上的连接请求,然后将请求,发给内部网络上的服务器,并将从服务器上得到的结果返回给 internet 上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器反向代理总结就一句话:代理端代理的是服务端
负载均衡即是代理服务器将接收的请求均衡的分发到各服务器中,负载均衡主要解决网络拥塞问题,提高服务器响应速度,服务就近提供,达到更好的访问质量,减少后台服务器大并发压力
1、sticky:通过 nginx-sticky 模块,来实现 cookie 黏贴的方式将来自同一个客户端的请求发送到同一个后端服务器上处理,这样一定程度上可以解决多个后端服务器的 session 会话同步的问题;2、round-robin(RR):轮询,每个请求按时间顺序依次分配到不同的后端服务器,如果后端某台服务器死机,自动剔除故障系统,使用户访问不受影响;3、weight:轮询权重,weight 的值越大分配到的访问概率就越高,主要用于后端每台服务器性能不均衡的情况下,或者仅仅为在主从的情况下设置不同的权重,达到合理有效的利用主机资源。
4、least_conn:请求被发送到当前活跃连接最少的 realserver 上,会考虑到 weight 的值;
5、ip_hash:每个请求按照 IP 的哈希结果分配,使来自同一个 IP 的访客固定访问后端服务器,可以有效的解决动态网页存在的 session 共享问题。
6、fair:比 weight、ip_hash 更加智能的负载均衡算法,fair 算法可以根据页面的大小和加载时间长短智能地进行负载均衡,也就是根据后端服务器的响应时间来分配请求,相应时间短的优先分配。nginx 本身不支持 fair,如果需要使用这种调度算法,则必须安装 upstream_fair 模块。
7、url_hash:按访问的 URL 的哈希结果来分配请求,使每个 URL 定向到后端服务器,可以进一步提高后端缓存服务器的效率。同样,nginx 本身不支持 url_hash,如果需要这种调度算法,则必须安装 nginx 的 hash 软件包。
常用的状态有:
1、down:表示当前的 server 暂时不参与负载均衡;
2、backup:预留的备份机器。当其他所有的非 backup 机器出现故障或者繁忙的时候,才会请求 backup 机器,因此这台机器的访问压力最低;
3、max_fails:允许请求失败的次数,默认为 1,当超过最大次数时,返回 proxy_next_upstraem
模块定义的错误;
4、fail_timeout:请求失败超时时间,在经历了 max_fails 次失败后,暂停服务的时间。max_fails和 fail_timeout 可以一起使用。
1、配置 nginx 的 proxy 缓存;
2、对静态页面开启压缩功能,如 br 压缩或者 gzip 压缩;
3、调整 nginx 运行工作进程个数,最多开启 8 个,8 个以上话性能就不会再提升了,而且稳定性变得更低,所以 8 个足够用了;4、调整 nginx 运行 CPU 的亲和力;
5、修改 nginx 最多可打开的文件数,若超过系统限制的最多打开文件数(ulimit -n 命令查看系统的最多打开文件数),还需要修改系统默认的文件数;6、修改单个 worker 的最大连接数;7、开启高效传输;
8、设置连接超时时间,以便保护服务器资源,因为建立连接也是需要消耗资源的;
9、优化 fastCGI 的一个超时时间,也可以根据实际情况对其配置缓存动态页面;10、expires 缓存调优,主要针对图片、css、js 等元素更改较少的情况下使用。11、配置防盗链;12、优化内核参数,如进程可以同时打开的最大句柄数;开启 tcp 重用机制,以便允许
TIME_WAIT sockets 重新用于新的 TCP 连接
1、解耦:如果多个模块或者系统中,互相调用很复杂,维护起来比较麻烦,但是这个调用又不是同步调用,就可以运用 MQ 到这个业务中。2、异步:这个很好理解,比如用户的操作日志的维护,可以不用同步处理,节约响应时间。3、削峰:在高峰期的时候,系统每秒的请求量达到 5000,那么调用 MySQL 的请求也是5000,一般情况下 MySQL 的请求大概在 2000 左右,那么在高峰期的时候,数据库就被打垮了,那系统就不可用了。此时引入 MQ,在系统 A 前面加个 MQ,用户请求先到 MQ,系统 A 从 MQ 中每秒消费 2000 条数据,这样就把本来 5000 的请求变为 MySQL 可以接受的请求数量了,可以保证系统不挂掉,可以继续提供服务。MQ 里的数据可以慢慢的把它消费掉。
(1)降低了系统可用性 (2)增加了系统的复杂性
RabbitMQ 是比较有代表性的,因为是基于主从做高可用性的。以他为例,自行查阅以下模式。 rabbitmq 有三种模式:单机模式、普通集群模式、镜像集群模式。
在特殊场景下有其对应的好处,解耦、异步、削峰。缺点有以下几个:
系统可用性降低系统引入的外部依赖越多,越容易挂掉。万一 MQ 挂了,MQ 一挂,整套系统崩溃,你不就完了?
系统复杂度提高硬生生加个 MQ 进来,你怎么保证消息没有重复消费?怎么处理消息丢失的情况?怎么保证消息传递的顺序性?问题一大堆。
一致性问题
A 系统处理完了直接返回成功了,人都以为你这个请求就成功了;但是问题是,要是 BCD 三个系统那里,BD 两个系统写库成功了,结果 C 系统写库失败了,咋整?你这数据就不一致了。
对于吞吐量来说 kafka 和 RocketMQ 支撑高吞吐,ActiveMQ 和 RabbitMQ 比他们低一个数量级。对于延迟量来说 RabbitMQ 是最低的。
1.从社区活跃度
按照目前网络上的资料,RabbitMQ、activeM 、ZeroMQ 三者中,综合来看,RabbitMQ 是首
选。
2.持久化消息比较
ActiveMq 和 RabbitMq 都支持。持久化消息主要是指我们机器在不可抗力因素等情况下挂掉了,消息不会丢失的机制。
3.综合技术实现
可靠性、灵活的路由、集群、事务、高可用的队列、消息排序、问题追踪、可视化管理工具、插件系统等等。
RabbitMq/Kafka 最好,ActiveMq 次之,ZeroMq 最差。当然 ZeroMq 也可以做到,不过自己
必须手动写代码实现,代码量不小。尤其是可靠性中的:持久性、投递确认、发布者证实和高可用性。
4.高并发
毋庸置疑,RabbitMQ 最高,原因是它的实现语言是天生具备高并发高可用的 erlang 语言。
5.比较关注的比较,RabbitMQ 和 Kafka RabbitMq 比 Kafka 成熟,在可用性上,稳定性上,可靠性上,RabbitMq 胜于 Kafka(理论上)。
另外,Kafka 的定位主要在日志等方面, 因为 Kafka 设计的初衷就是处理日志的,可以看做是一个日志(消息)系统一个重要组件,针对性很强,所以 如果业务方面还是建议选择 RabbitMq 。
还有就是,Kafka 的性能(吞吐量、TPS)比 RabbitMq 要高出来很多。
设置队列属性,队列中所有消息都有相同的过期时间对消息本身进行单独设置,每条消息的 TTL 可以不同
如果两种方法一起使用,则消息的 TTL 以两者之间较小的那个数值为准
RabbitMQ 的持久化分为:交换器的持久化、队列的持久化和消息的持久化交换器和队列的持久化都是通过在声明时将 durable 参数置为 true 实现的消息的持久化是在发送消息指定 deliveryMode 为 2 实现的
ZooKeeper 是一个开放源码的分布式协调服务,它是集群的管理者,监视着集群中各个节点的状态根据节点提交的反馈进行下一步合理操作。最终,将简单易用的接口和性能高效、功能稳定的系统提供给用户。
数据发布/订阅负载均衡命名服务
分布式协调/通知集群管理
Master 选举
分布式锁分布式队列
PERSISTENT-持久节点
除非手动删除,否则节点一直存在于 Zookeeper 上
EPHEMERAL-临时节点
临时节点的生命周期与客户端会话绑定,一旦客户端会话失效(客户端与 zookeeper 连接断开不一定会话失效),那么这个客户端创建的所有临时节点都会被移除。
PERSISTENT_SEQUENTIAL-持久顺序节点
基本特性同持久节点,只是增加了顺序属性,节点名后边会追加一个由父节点维护的自增整型数字。
EPHEMERAL_SEQUENTIAL-临时顺序节点
基本特性同临时节点,增加了顺序属性,节点名后边会追加一个由父节点维护的自增整型数字。
1、一次性
无论是服务端还是客户端,一旦一个 Watcher 被触发,Zookeeper 都会将其从相应的存储中移除。这样的设计有效的减轻了服务端的压力,不然对于更新非常频繁的节点,服务端会不断的向客户端发送事件通知,无论对于网络还是服务端的压力都非常大。
2、客户端串行执行
客户端 Watcher 回调的过程是一个串行同步的过程。3、轻量
3.1Watcher 通知非常简单,只会告诉客户端发生了事件,而不会说明事件的具体内容。 3.2 客户端向服务端注册 Watcher 的时候,并不会把客户端真实的 Watcher 对象实体传递到服务端,仅仅是在客户端请求中使用 boolean 类型属性进行了标记。
4、watcher event 异步发送 watcher 的通知事件从 server 发送到 client 是异步的,这就存在一个问题,不同的客户端和服务器之间通过 socket 进行通信,由于网络延迟或其他因素导致客户端在不通的时刻监听到事件,由于 Zookeeper 本身提供了 ordering guarantee,即客户端监听事件后,才会感知它所监视 znode 发生了变化。所以我们使用 Zookeeper 不能期望能够监控到节点每次的变化。Zookeeper 只能保证最终的一致性,而无法保证强一致性。
5、注册 watcher getData、exists、getChildren
6、触发 watcher create、delete、setData
7、当一个客户端连接到一个新的服务器上时,watch 将会被以任意会话事件触发。当与一个服务器失去连接的时候,是无法接收到 watch 的。而当 client 重新连接时,如果需要的话,所有先前注册过的 watch,都会被重新注册。通常这是完全透明的。只有在一个特殊情况下, watch 可能会丢失:对于一个未创建的 znode 的 exist watch,如果在客户端断开连接期间被创建了,并且随后在客户端连接上之前又删除了,这种情况下,这个 watch 事件可能会被丢失。
服务器具有四种状态,分别是 LOOKING、FOLLOWING、LEADING、OBSERVING。LOOKING:寻找 Leader 状态。当服务器处于该状态时,它会认为当前集群中没有 Leader,因此需要进入 Leader 选举状态。
FOLLOWING:跟随者状态。表明当前服务器角色是 Follower。LEADING:领导者状态。表明当前服务器角色是 Leader。OBSERVING:观察者状态。表明当前服务器角色是 Observer。
zookeeper 采用了全局递增的事务 Id 来标识,所有的 proposal(提议)都在被提出的时候加上了 zxid,zxid 实际上是一个 64 位的数字,高 32 位是 epoch(时期; 纪元; 世; 新时代)用来标识 leader 周期,如果有新的 leader 产生出来,epoch 会自增,低 32 位用来递增计数。当新产生 proposal 的时候,会依据数据库的两阶段过程,首先会向其他的 server 发出事务执行请求,如果超过半数的机器都能执行并且能够成功,那么就会开始执行。
Zookeeper 本身也是集群,推荐配置不少于 3 个服务器。Zookeeper 自身也要保证当一个节点宕机时,其他节点会继续提供服务。
如果是一个 Follower 宕机,还有 2 台服务器提供访问,因为 Zookeeper 上的数据是有多个副本的,数据并不会丢失;如果是一个 Leader 宕机,Zookeeper 会选举出新的 Leader。
ZK 集群的机制是只要超过半数的节点正常,集群就能正常提供服务。只有在 ZK 节点挂得太多,只剩一半或不到一半节点能工作,集群才失效。所以3 个节点的 cluster 可以挂掉 1 个节点(leader 可以得到 2 票>1.5)2 个节点的 cluster 就不能挂掉任何 1 个节点了(leader 可以得到 1 票<=1)
部署模式:单机模式、伪集群模式、集群模式。
Spring Container
Jetty Container
Log4j Container
Provider:暴露服务的服务提供方
Consumer:调用远程服务的服务消费方
Registry:服务注册与发现的注册中心
Monitor:统计服务的调用次数和调用时间的监控中心
Container:服务运行容器
Failover Cluster:失败自动切换,自动重试其他服务器(默认)
Failfast Cluster:快速失败,立即报错,只发起一次调用
Failsafe Cluster:失败安全,出现异常时,直接忽略
Failback Cluster:失败自动恢复,记录失败请求,定时重发
Forking Cluster:并行调用多个服务器,只要一个成功即返回
Broadcast Cluster:广播逐个调用所有提供者,任意一个报错则报错
Random LoadBalance:随机,按权重设置随机概率(默认)
RoundRobin LoadBalance:轮询,按公约后的权重设置轮询比率
LeastActive LoadBalance:最少活跃调用次数,相同活跃数的随机
ConsistentHash LoadBalance:一直性 Hash,相同参数的请求总是发到同一提供者
管理控制台主要包含:路由规则,动态配置,服务降级,访问控制,权重调整,负载均衡,等管理功能。
多年来,随着新功能的增加,spring 变得越来越复杂。只需访问 https://spring.io/projects 页
面,我们就会看到可以在我们的应用程序中使用的所有 Spring 项目的不同功能。如果必须启动一个新的 Spring 项目,我们必须添加构建路径或添加 Maven 依赖关系,配置应用程序服务器,添加 spring 配置。因此,开始一个新的 spring 项目需要很多努力,因为我们现在必须从头开始做所有事情。
Spring Boot 是解决这个问题的方法。Spring Boot 已经建立在现有 spring 框架之上。使用 spring
启动,我们避免了之前我们必须做的所有样板代码和配置。因此,Spring Boot 可以帮助我们以最少的工作量,更加健壮地使用现有的 Spring 功能。
减少开发,测试时间和努力。
使用 JavaConfig 有助于避免使用 XML。
避免大量的 Maven 导入和各种版本冲突。提供意见发展方法。
通过提供默认值快速开始开发。
没有单独的 Web 服务器需要。这意味着你不再需要启动 Tomcat,Glassfish 或其他任何东西。需要更少的配置 因为没有 web.xml 文件。只需添加用@ Configuration 注释的类,然后添加用@Bean 注释的方法,Spring 将自动加载对象并像以前一样对其进行管理。您甚至可以将 @Autowired 添加到 bean 方法中,以使 Spring 自动装入需要的依赖关系中。
基于环境的配置 使用这些属性,您可以将您正在使用的环境传递到应用程序:
-Dspring.profiles.active = {enviornment} 。在加载主应用程序属性文件后, Spring 将在(application{environment} .properties)中加载后续的应用程序属性文件。
Spring JavaConfig 是 Spring 社区的产品,它提供了配置 Spring IoC 容器的纯 Java 方法。因此它有助于避免使用 XML 配置。使用 JavaConfig 的优点在于:
面向对象的配置。由于配置被定义为 JavaConfig 中的类,因此用户可以充分利用 Java 中的面向对象功能。一个配置类可以继承另一个,重写它的@Bean 方法等。
减少或消除 XML 配置。基于依赖注入原则的外化配置的好处已被证明。但是,许多开发人员不希望在 XML 和 Java 之间来回切换。JavaConfig 为开发人员提供了一种纯 Java 方法来配置与 XML 配置概念相似的 Spring 容器。从技术角度来讲,只使用 JavaConfig 配置类来配置容器是可行的,但实际上很多人认为将 JavaConfig 与 XML 混合匹配是理想的。
类型安全和重构友好。JavaConfig 提供了一种类型安全的方法来配置 Spring 容器。由于 Java 5.0 对泛型的支持,现在可以按类型而不是按名称检索 bean,不需要任何强制转换或基于字符串的查找。
这可以使用 DEV 工具来实现。通过这种依赖关系,您可以节省任何更改,嵌入式 tomcat 将重新启动。Spring Boot 有一个开发工具(DevTools)模块,它有助于提高开发人员的生产力。 Java 开发人员面临的一个主要挑战是将文件更改自动部署到服务器并自动重启服务器。开发人员可以重新加载 Spring Boot 上的更改,而无需重新启动服务器。这将消除每次手动部署更改的需要。Spring Boot 在发布它的第一个版本时没有这个功能。这是开发人员最需要的功能。DevTools 模块完全满足开发人员的需求。该模块将在生产环境中被禁用。它还提供 H2 数据库控制台以更好地测试应用程序。
Spring boot actuator 是 spring 启动框架中的重要功能之一。Spring boot 监视器可帮助您访问
生产环境中正在运行的应用程序的当前状态。有几个指标必须在生产环境中进行检查和监控。即使一些外部应用程序可能正在使用这些服务来向相关人员触发警报消息。监视器模块公开了一组可直接作为 HTTP URL 访问的 REST 端点来检查状态。
默认情况下,所有敏感的 HTTP 端点都是安全的,只有具有 ACTUATOR 角色的用户才能访问它们。安全性是使用标准的 HttpServletRequest.isUserInRole 方法实施的。 我们可以使用 management.security.enabled = false 来禁用安全性。只有在执行机构端点在防火墙后访问时,才建议禁用安全性。
WebSocket 是一种计算机通信协议,通过单个 TCP 连接提供全双工通信信道。 WebSocket 是双向的 -使用 WebSocket 客户端或服务器可以发起消息发送。 WebSocket 是全双工的 -客户端和服务器通信是相互独立的。
单个 TCP 连接 -初始连接使用 HTTP,然后将此连接升级到基于套接字的连接。然后这个单一连接用于所有未来的通信Light -与 http 相比,WebSocket 消息数据交换要轻得多。
Swagger 广泛用于可视化 API,使用 Swagger UI 为前端开发人员提供在线沙箱。Swagger 是用于生成 RESTful Web 服务的可视化表示的工具,规范和完整框架实现。它使文档能够以与服务器相同的速度更新。当通过 Swagger 正确定义时,消费者可以使用最少量的实现逻辑来理解远程服务并与其进行交互。因此,Swagger 消除了调用服务时的猜测。
Apache Kafka 是一个分布式发布 - 订阅消息系统。它是一个可扩展的,容错的发布 - 订阅消息系统,它使我们能够构建分布式应用程序。这是一个 Apache 顶级项目。Kafka 适合离线和在线消息消费。
Spring cloud 流应用程序启动器是基于 Spring Boot 的 Spring 集成应用程序,提供与外部系统的集成。 Spring cloud Task,一个生命周期短暂的微服务框架,用于快速构建执行有限数据处理的应用程序。
使用 Spring Boot 开发分布式微服务时,我们面临以下问题
1、与分布式系统相关的复杂性-这种开销包括网络问题,延迟开销,带宽问题,安全问题。
2、服务发现-服务发现工具管理群集中的流程和服务如何查找和互相交谈。它涉及一个服务目录,在该目录中注册服务,然后能够查找并连接到该目录中的服务。
3、冗余-分布式系统中的冗余问题。
4、负载平衡 --负载平衡改善跨多个计算资源的工作负荷,诸如计算机,计算机集群,网络链路,中央处理单元,或磁盘驱动器的分布。
5、性能-问题 由于各种运营开销导致的性能问题。
6、部署复杂性-Devops 技能的要求。
当我们开始一个项目时,我们通常在属性文件中进行所有的配置。随着越来越多的服务开发和部署,添加和修改这些属性变得更加复杂。有些服务可能会下降,而某些位置可能会发生变化。手动更改属性可能会产生问题。 Eureka 服务注册和发现可以在这种情况下提供帮助。由于所有服务都在 Eureka 服务器上注册并通过调用 Eureka 服务器完成查找,因此无需处理服务地点的任何更改和处理。
Feign 是受到 Retrofit,JAXRS-2.0 和 WebSocket 启发的 java 客户端联编程序。Feign 的第一个目标是将约束分母的复杂性统一到 http apis,而不考虑其稳定性。在 employee-consumer 的例子中,我们使用了 employee-producer 使用 REST 模板公开的 REST 服务。
但是我们必须编写大量代码才能执行以下步骤
之前的代码,有像 NullPointer 这样的例外的机会,并不是最优的。我们将看到如何使用 Netflix Feign 使呼叫变得更加轻松和清洁。如果 Netflix Ribbon 依赖关系也在类路径中,那么 Feign 默认也会负责负载平衡。
考虑以下情况:我们有多个应用程序使用 Spring Cloud Config 读取属性,而 Spring Cloud Config 从 GIT 读取这些属性。
下面的例子中多个员工生产者模块从 Employee Config Module 获取 Eureka 注册的财产。
如果假设 GIT 中的 Eureka 注册属性更改为指向另一台 Eureka 服务器,会发生什么情况。在这种情况下,我们将不得不重新启动服务以获取更新的属性。
还有另一种使用执行器端点/刷新的方式。但是我们将不得不为每个模块单独调用这个 url。例如,如果
Employee Producer1 部署在端口 8080 上,则调用 http:// localhost:8080 / refresh。同样对于
Employee Producer2 http:// localhost:8081 / refresh 等等。这又很麻烦。这就是 Spring Cloud Bus
发挥作用的地方。
Spring Cloud Bus 提供了跨多个实例刷新配置的功能。因此,在上面的示例中,如果我们刷新 Employee Producer1,则会自动刷新所有其他必需的模块。如果我们有多个微服务启动并运行,这特别有用。这是通过将所有微服务连接到单个消息代理来实现的。无论何时刷新实例,此事件都会订阅到侦听此代理的所有微服务,并且它们也会刷新。可以通过使用端点/总线/刷新来实现对任何单个实例的刷新。
由于某些原因,employee-consumer 公开服务会引发异常。在这种情况下使用 Hystrix 我们定义了一个回退方法。如果在公开服务中发生异常,则回退方法返回一些默认值。
Elasticsearch是一个基于Lucene的搜索引擎。它提供了具有HTTP Web界面和无架构JSON文档的分布式,多租户能力的全文搜索引擎。Elasticsearch是用Java开发的,根据Apache许可条款作为开源发布
每个索引的主分片数,默认值是 5
。这个配置在索引创建后不能修改 , 每个主分片的副本数,默认值是 1
。对于活动的索引库,这个配置可以随时修改
倒排索引是搜索引擎的核心。搜索引擎的主要目标是在查找发生搜索条件的文档时提供快速搜索。倒排索引是一种像数据结构一样的散列图,可将用户从单词导向文档或网页。它是搜索引擎的核心。其主要目标是快速搜索从数百万文件中查找数据
Elasticsearch的选主是ZenDiscovery模块负责的,主要包含Ping(节点之间通过这个RPC来发现彼此)和Unicast(单播模块包含一个主机列表以控制哪些节点需要ping通)这两部分 ; 对所有可以成为master的节点(node.master: true)根据nodeId字典排序,每次选举每个节点都把自己所知道节点排一次序,然后选出第一个(第0位)节点,暂且认为它是master节点 ; 如果对某个节点的投票数达到一定的值(可以成为master节点数n/2+1)并且该节点自己也选举自己,那这个节点就是master。否则重新选举一直到满足上述条件
关闭缓存 swap; 堆内存设置为:Min(节点内存/2, 32GB); 设置最大文件句柄数; 线程池+队列大小根据业务需要做调整; 磁盘存储 raid 方式——存储有条件使用 RAID10,增加单节点性能以及避免单 节点存储故障
微服务架构就是将单体的应用程序分成多个应用程序,这多个应用程序就成为微服务,每个微服务运行在自己的进程中,并使用轻量级的机制通信。这些服务围绕业务能力来划分,并通过自动化部署机制来独立部署。这些服务可以使用不同的编程语言,不同数据库,以保证最低限度的集中式管理。
- SpringCloud是分布式微服务治理解决方案。提供了一系列框架技术的有序集合。
- 利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、智能路由、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。
- Spring Cloud是集大成者,它只是将各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。
优点:
1.耦合度比较低。不会影响其他模块的开发。
2.配置比较简单,基本用注解就能实现,不用使用过多的配置文件。
3.微服务跨平台的,可以用任何一种语言开发。
4.每个微服务可以有自己的独立的数据库也有用公共的数据库。
5.直接写后端的代码,不用关注前端怎么开发,直接写自己的后端代码即可,然后暴露接口,通过组件进行服务通信。6.组件化(模块化)、随时开箱拆箱。
缺点:
1.微服务落地部署
2.微服务监控
3.微服务日志管理
4.微服务集成测试与质量管理
- SpringBoot专注于快速方便的开发单个个体微服务。
- SpringCloud是全局的微服务协调整理治理框架,它将SpringBoot开发的一个个单体微服务整合并管理起来,为各个微服务之间提供,配置管理、服务发现、断路器、路由、微代理、事件总线、全局锁、决策竞选、分布式会话等等集成服务
- SpringBoot可以离开SpringCloud独立使用开发项目, SpringCloud基于SpringBoot ,不能脱离SpringBoot。
- SpringBoot专注于快速、方便的开发单个微服务个体,SpringCloud关注全局的服务治理框架。
- Spring Cloud Eureka:服务注册与发现
- Spring Cloud Ribbon:客户端负载均衡
- Spring Cloud Feign:声明性的Web服务客户端
- Spring Cloud Hystrix:断路器
- Spring Cloud Zuul:服务网关
- Spring Cloud Config:分布式统一配置管理
其它组件详细地址:
- https://www.processon.com/view/link/5ef6e8a96376891e81e8009d
如果你在写代码调用一个有REST API或Thrift API的服务,你的代码需要知道一个服务实例的网络地址(IP地址和端口)。运行在物理硬件上的传统应用中,服务实例的网络地址是相对静态的,你的代码可以从一个很少更新的配置文件中读取网络地址。
在一个现代的,基于云的微服务应用中,
服务实例的网络地址是动态分配的。而且,由于自动扩展,失败和更新,服务实例的配置也经常变化。此时,需要服务注册与发现的组件来解决动态IP的变化等问题。
注册中心应具备以下功能:
- 服务注册表
服务注册表是注册中心的核心,它用来记录各个微服务的信息,例如微服务的名称、IP、端口等。服务注册表提供查询API和管理API,查询API用于查询可用的微服务实例,管理API用于服务的注册与注销。- 服务注册与发现
服务注册是指微服务在启动时,将自己的信息注册到注册中心的过程。服务发现是指查询可用的微服务列表及网络地址的机制。- 服务检查
注册中心使用一定的机制定时检测已注册的服务,如发现某实例长时间无法访问,就会从服务注册表移除该实例。
Eureka是作为SpringCloud的服务注册与发现组件功能服务器,是服务注册中心,系统中的其他服务使用Eureka的客户端将其连接到Eureka Service中,并且保持心跳,这样开发人员可以通过Eureka Service来监控各个微服务是否运行正常。
默认情况下,如果Eureka Service在一定时间内没有接收到某个微服务的心跳,Eureka Service会进入自我保护模式,在该模式下Eureka Service会保护服务注册表中的信息,不在删除注册表中的数据,当网络故障恢复后,Eureka Servic 节点会自动退出自我保护模式
Eureka Client 在默认的情况下会每隔 30 秒发送一次心跳来进行服务续约。通过服务续约 来告知 Eureka Server 该 Eureka Client 仍然可用,没有出现故障。正常情况下,如果 Eureka Server 在 90 秒内没有收到 Eureka Client 的心跳, Eureka Server 会将 Eureka Client 实例从注册列表中 删除。
在默认情况下,当 Eureka Client 连续 90 秒没有向 Eureka Server 发送服务续约(即心跳〉 时, Eureka Server 会将该服务实例从服务注册列表删除,即服务剔除。
注册多台Eureka,SpringCloud服务间互相注册,客户端从Eureka获取信息时,按照Eureka的顺序依次来访问。
- Ribbon是Netflix发布的开源项目,主要功能是提供客户端的服务间调用和服务的负载均衡。
- Ribbon客户端组件提供一系列完善的配置项,如连接超时,重试等。简单的说,就是在配置文件中列出后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随即连接等)去连接这些机器。我们也很容易使用Ribbon实现自定义的负载均衡算法。(有点类似Nginx)
Nginx是反向代理同时可以实现负载均衡,nginx拦截客户端请求采用负载均衡策略根据upstream配置进行转发,相当于请求通过nginx服务器进行转发。
Ribbon是客户端负载均衡,从注册中心读取目标服务器信息,然后客户端采用轮询策略对服务直接访问,全程在客户端操作。(默认是轮循操作)
- Ribbon使用discoveryClient从注册中心读取目标服务信息,对同一接口请求进行计数,使用%取余算法获取目标服务集群索引,返回获取到的目标服务信息。
- Feign 是一个声明web服务客户端,这使得编写web服务客户端更容易
- Feign可帮助我们更加便捷,优雅的调用HTTP API。
在SpringCloud中,使用Feign非常简单——创建一个接口,并在接口上添加一些注解,代码就完 成了。 Feign支持多种注解,例如Feign自带的注解或者JAX-RS注解等。
SpringCloud对Feign进行了增强,使Feign支持了SpringMVC注解,并整合了Ribbon和Eureka, 从而让Feign的使用更加方便。
- Spring Cloud 集成 Ribbon 和 Eureka 提供的负载均衡的HTTP客户端Feign。(Feign默认集成了Ribbon,并和Eureka结合)。默认实现了负载均衡的效果。
- 调用方式同:Ribbon需要我们自己构建Http请求,模拟Http请求然后通过RestTemplate发给其他服务,步骤相当繁琐
- 而Feign则是在Ribbon的基础上进行了一次改进,采用接口的形式,将我们需要调用的服务方法定义成抽象方法保存在本地就可以了,不需要自己构建Http请求了,直接调用接口就行了,不过要注意,调用方法要和本地抽象方法的签名完全一致。
- 在分布式系统,我们一定会依赖各种服务,那么这些个服务一定会出现失败的情况,就会导致雪崩,
- Hystrix是由Netflix开源的一个针对分布式系统容错处理的开源组件。能够提供断路,降级,监控等多种服务。它具有服务降级,服务熔断,服务隔离,监控等一些防止雪崩的技术。
Hystrix有四种防雪崩方式:
- 服务降级:接口调用失败就调用本地的方法返回一个空
- 服务熔断:接口调用失败就会进入调用接口提前定义好的一个熔断的方法,返回错误信息
- 服务隔离:隔离服务之间相互影响
- 服务监控:在服务发生调用时,会将每秒请求数、成功请求数等运行指标记录下来。
因为Tomcat默认情况下只有一个线程池来维护客户端发送的所有的请求,这时候某一接口在某一时刻被大量访问就会占据tomcat线程池中的所有线程,其他请求处于等待状态,无法连接到服务接口。
一般使用使用Hystrix框架,实现服务隔离来避免出现服务的雪崩效应,从而达到保护服务的效果。当微服务中,高并发的数据库访问量导致服务线程阻塞,使单个服务宕机,服务的不可用会蔓延到其他服务,引起整体服务灾难性后果,使用服务降级能有效为不同的服务分配资源,一旦服务不可用则返回友好提示,不占用其他服务资源,从而避免单个服务崩溃引发整体服务的不可用.
服务容错的核心知识
雪崩效应
在微服务架构中,一个请求需要调用多个服务是非常常见的。如客户端访问A服务,而A服务需要调用B 服务,B服务需要调用C服务,由于网络原因或者自身的原因,如果B服务或者C服务不能及时响应,A服 务将处于阻塞状态,直到B服务C服务响应。此时若有大量的请求涌入,容器的线程资源会被消耗完毕, 导致服务瘫痪。服务与服务之间的依赖性,故障会传播,造成连锁反应,会对整个微服务系统造成灾难 性的严重后果,这就是服务故障的“雪崩”效应。(服务熔断和服务降级就可以视为解决服务雪崩的手段之一)
服务隔离
顾名思义,它是指将系统按照一定的原则划分为若干个服务模块,各个模块之间相对独立,无强依赖。 当有故障发生时,能将问题和影响隔离在某个模块内部,而不扩散风险,不波及其它模块,不影响整体 的系统服务。
服务熔断
熔断这一概念来源于电子工程中的断路器(Circuit Breaker)。
在互联网系统中,当下游服务因访问压力过大而响应变慢或失败,上游服务为了保护系统整体的可用性,可以暂时切断对下游服务的调用。这种牺牲局部,保全整体的措施就叫做熔断。
服务降级
降级,就是当某个服务熔断之后,服务器将不再被调用,此时客户端可以自己准备一个本地的 fallback回调,返回一个缺省值。
@HystrixCommand 注解
@FeignClient注解:fallback 如果接口服务不能访问或延迟,则运行对应其接口实现类。
网关相当于一个网络服务架构的入口,所有网络请求必须通过网关转发到具体的服务。
API网关,顾名思义,是统一管理API的一个网络关口、通道,是整个微服务平台所有请求的唯一入口,所有的客户端和消费端都通过统一的网关接入微服务,在网关层处理所有的非业务功能。
统一管理微服务请求,权限控制、负载均衡、路由转发、监控、安全控制黑名单和白名单等
Zuul是对SpringCloud提供的成熟对的路由方案,他会根据请求的路径不同,网关会定位到指定的微服务,并代理请求到不同的微服务接口,他对外隐蔽了微服务的真正接口地址。
三个重要概念:动态路由表,路由定位,反向代理:
- 动态路由表:Zuul支持Eureka路由,手动配置路由,这俩种都支持自动更新
- 路由定位:根据请求路径,Zuul有自己的一套定位服务规则以及路由表达式匹配
- 反向代理:客户端请求到路由网关,网关受理之后,在对目标发送请求,拿到响应之后在 给客户端
它可以和Eureka,Ribbon,Hystrix等组件配合使用,
Zuul的应用场景:
- 对外暴露,权限校验,服务聚合,日志审计等
网关是对所有服务的请求进行分析过滤,过滤器是对单个服务而言。
- Run():过滤器的具体业务逻辑
- shouldFilter():判断过滤器是否有效
- filterOrder():过滤器执行顺序
- filterType():过滤器拦截位置
Zuul是java语言实现的,主要为java服务提供网关服务,尤其在微服务架构中可以更加灵活的对网关进行操作。Nginx是使用C语言实现,性能高于Zuul,但是实现自定义操作需要熟悉lua语言,对程序员要求较高,可以使用Nginx做Zuul集群
Zuul是SpringCloud集成的网关,使用Java语言编写,可以对SpringCloud架构提供更灵活的服务。
通过path配置拦截请求,通过ServiceId到配置中心获取转发的服务列表,Zuul内部使用Ribbon实现本地负载均衡和转发。
使用Nginx的upstream设置Zuul服务集群,通过location拦截请求并转发到upstream,默认使用轮询机制对Zuul集群发送请求。
SpringCloud Config、Apollo、Zookeeper。
Spring Cloud Config为分布式系统中的外部配置提供服务器和客户端支持,可以方便的对微服务各个环境下的配置进行集中式管理。Spring Cloud Config分为Config Server和Config Client两部分。Config Server负责读取配置文件,并且暴露Http API接口,Config Client通过调用Config Server的接口来读取配置文件。
- Config能够管理所有微服务的配置文件
- 集中配置管理工具,分布式系统中统一的外部配置管理,默认使用Git来存储配置,可以支持客户端配置的刷新及加密、解密操作。
动态变更项目配置信息而不必重新部署项目。
SpringCloud Config实时刷新采用SpringCloud Bus消息总线。
http://assets.processon.com/chart_image/5e46afc1e4b00aefb7e063aa.png
- 用于传播集群状态变化的消息总线,使用轻量级消息代理链接分布式系统中的节点,可以用来动态刷新集群中的服务配置信息。
- 简单来说就是修改了配置文件,发送一次请求,所有客户端便会重新读取配置文件。需要利用中间插件MQ
在微服务中,通常根据业务模块分服务,项目中前端发起一个请求,后端可能跨几个服务调用才能完成这个请求。如果系统越来越庞大,服务之间的调用与被调用关系就会变得很复杂,假如一个请求中需要跨几个服务调用,其中一个服务由于网络延迟等原因挂掉了,那么这时候我们需要分析具体哪一个服务出问题了就会显得很困难。
Spring Cloud Sleuth服务链路跟踪功能就可以帮助我们快速的发现错误根源以及监控分析每条请求链路上的性能等等。
- 轻量级事件驱动微服务框架,可以使用简单的声明式模型来发送及接收消息,主要实现为Apache Kafka及RabbitMQ。
Spring Cloud Task的目标是为Spring Boot应用程序提供创建短运行期微服务的功能。在Spring Cloud Task中,我们可以灵活地动态运行任何任务,按需分配资源并在任务完成后检索结果。Tasks是Spring Cloud Data Flow中的一个基础项目,允许用户将几乎任何Spring Boot应用程序作为一个短期任务执行。
在我们的分布式项目中,经常会出现一部分服务是 Java 语言写的,一部分服务是非 Java 语言写的,Java 语言写的服务可以通过我们的 SpringCloud 组件来进行服务发现,网关路由等操作,但是非 Java语言的程序 则无法实现这个功能,为了解决这个问题,Netfilix 提供了 Sidecar 来解决,其基本思想就是 sidecar 是一个 Java 语言的程序,然后内容通过配置访问非 Java语言的程序,然后注册到我们的 SpringCloud组件中,实现我们的功能,本质上其就是一个代理